server/admin/api/file.go

282 lines
6.5 KiB
Go

package api
import (
"encoding/hex"
"fmt"
"log"
"net/http"
"path/filepath"
"strconv"
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
"github.com/gin-gonic/gin"
)
func declareFilesGlobalRoutes(router *gin.RouterGroup) {
router.DELETE("/files/", clearFiles)
// Remote
router.GET("/remote/themes/:thid/exercices/:exid/files", sync.ApiGetRemoteExerciceFiles)
}
func declareFilesRoutes(router *gin.RouterGroup) {
router.GET("/files", listFiles)
router.POST("/files", createExerciceFile)
apiFilesRoutes := router.Group("/files/:fileid")
apiFilesRoutes.Use(FileHandler)
apiFilesRoutes.GET("", showFile)
apiFilesRoutes.PUT("", updateFile)
apiFilesRoutes.DELETE("", deleteFile)
apiFileDepsRoutes := apiFilesRoutes.Group("/dependancies/:depid")
apiFileDepsRoutes.Use(FileDepHandler)
apiFileDepsRoutes.DELETE("", deleteFileDep)
// Check
apiFilesRoutes.POST("/check", checkFile)
apiFilesRoutes.POST("/gunzip", gunzipFile)
}
func FileHandler(c *gin.Context) {
fileid, err := strconv.ParseInt(string(c.Params.ByName("fileid")), 10, 64)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid file identifier"})
return
}
var file *fic.EFile
if exercice, exists := c.Get("exercice"); exists {
file, err = exercice.(*fic.Exercice).GetFile(fileid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
return
}
} else {
file, err = fic.GetFile(fileid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "File not found"})
return
}
}
c.Set("file", file)
c.Next()
}
func FileDepHandler(c *gin.Context) {
depid, err := strconv.ParseInt(string(c.Params.ByName("depid")), 10, 64)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid dependency identifier"})
return
}
c.Set("file-depid", depid)
c.Next()
}
type APIFile struct {
*fic.EFile
Depends []fic.Flag `json:"depends,omitempty"`
}
func genFileList(in []*fic.EFile, e error) (out []APIFile, err error) {
if e != nil {
return nil, e
}
for _, f := range in {
g := APIFile{EFile: f}
var deps []fic.Flag
deps, err = f.GetDepends()
if err != nil {
return
}
for _, d := range deps {
if k, ok := d.(*fic.FlagKey); ok {
k, err = fic.GetFlagKey(k.Id)
if err != nil {
return
}
g.Depends = append(g.Depends, k)
} else if m, ok := d.(*fic.MCQ); ok {
m, err = fic.GetMCQ(m.Id)
if err != nil {
return
}
g.Depends = append(g.Depends, m)
} else {
err = fmt.Errorf("Unknown type %T to handle file dependancy", k)
return
}
}
out = append(out, g)
}
return
}
func listFiles(c *gin.Context) {
var files []APIFile
var err error
if exercice, exists := c.Get("exercice"); exists {
files, err = genFileList(exercice.(*fic.Exercice).GetFiles())
} else {
files, err = genFileList(fic.GetFiles())
}
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, files)
}
func clearFiles(c *gin.Context) {
_, err := fic.ClearFiles()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, true)
}
func showFile(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("file").(*fic.EFile))
}
type uploadedFile struct {
URI string
Digest string
}
func createExerciceFile(c *gin.Context) {
exercice, exists := c.Get("exercice")
if !exists {
c.AbortWithStatusJSON(http.StatusMethodNotAllowed, gin.H{"errmsg": "File can only be added inside an exercice."})
return
}
paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice.(*fic.Exercice))
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
var uf uploadedFile
err = c.ShouldBindJSON(&uf)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
ret, err := sync.ImportFile(sync.GlobalImporter, uf.URI,
func(filePath string, origin string) (interface{}, error) {
if digest, err := hex.DecodeString(uf.Digest); err != nil {
return nil, err
} else {
published := true
disclaimer := ""
if f, exists := paramsFiles[filepath.Base(filePath)]; exists {
published = !f.Hidden
if disclaimer, err = sync.ProcessMarkdown(sync.GlobalImporter, f.Disclaimer, exercice.(*fic.Exercice).Path); err != nil {
return nil, fmt.Errorf("error during markdown formating of disclaimer: %w", err)
}
}
return exercice.(*fic.Exercice).ImportFile(filePath, origin, digest, nil, disclaimer, published)
}
})
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, ret)
}
func updateFile(c *gin.Context) {
file := c.MustGet("file").(*fic.EFile)
var uf fic.EFile
err := c.ShouldBindJSON(&uf)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
uf.Id = file.Id
if _, err := uf.Update(); err != nil {
log.Println("Unable to updateFile:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
return
}
c.JSON(http.StatusOK, uf)
}
func deleteFile(c *gin.Context) {
file := c.MustGet("file").(*fic.EFile)
_, err := file.Delete()
if err != nil {
log.Println("Unable to updateFile:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
return
}
c.JSON(http.StatusOK, true)
}
func deleteFileDep(c *gin.Context) {
file := c.MustGet("file").(*fic.EFile)
depid := c.MustGet("file-depid").(int64)
err := file.DeleteDepend(&fic.FlagKey{Id: int(depid)})
if err != nil {
log.Println("Unable to deleteFileDep:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete file dependency."})
return
}
c.JSON(http.StatusOK, true)
}
func checkFile(c *gin.Context) {
file := c.MustGet("file").(*fic.EFile)
err := file.CheckFileOnDisk()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, true)
}
func gunzipFile(c *gin.Context) {
file := c.MustGet("file").(*fic.EFile)
err := file.GunzipFileOnDisk()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
c.JSON(http.StatusOK, true)
}