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) }