2016-12-08 08:12:18 +00:00
|
|
|
package api
|
2016-01-20 21:44:34 +00:00
|
|
|
|
2017-12-27 00:53:01 +00:00
|
|
|
import (
|
2019-07-21 19:54:48 +00:00
|
|
|
"encoding/hex"
|
2019-07-21 21:46:23 +00:00
|
|
|
"fmt"
|
2022-05-16 09:38:46 +00:00
|
|
|
"log"
|
|
|
|
"net/http"
|
2022-10-31 17:52:29 +00:00
|
|
|
"path/filepath"
|
2022-05-16 09:38:46 +00:00
|
|
|
"strconv"
|
2016-12-08 08:12:18 +00:00
|
|
|
|
2019-07-21 19:54:48 +00:00
|
|
|
"srs.epita.fr/fic-server/admin/sync"
|
2017-12-27 00:53:01 +00:00
|
|
|
"srs.epita.fr/fic-server/libfic"
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
"github.com/gin-gonic/gin"
|
2017-12-27 00:53:01 +00:00
|
|
|
)
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
func declareFilesGlobalRoutes(router *gin.RouterGroup) {
|
|
|
|
router.DELETE("/files/", clearFiles)
|
2017-12-27 00:53:01 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
// Remote
|
|
|
|
router.GET("/remote/themes/:thid/exercices/:exid/files", sync.ApiGetRemoteExerciceFiles)
|
|
|
|
}
|
2019-07-21 21:46:23 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
func declareFilesRoutes(router *gin.RouterGroup) {
|
|
|
|
router.GET("/files", listFiles)
|
|
|
|
router.POST("/files", createExerciceFile)
|
2019-07-21 19:54:48 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
apiFilesRoutes := router.Group("/files/:fileid")
|
|
|
|
apiFilesRoutes.Use(FileHandler)
|
|
|
|
apiFilesRoutes.GET("", showFile)
|
|
|
|
apiFilesRoutes.PUT("", updateFile)
|
|
|
|
apiFilesRoutes.DELETE("", deleteFile)
|
2019-07-21 19:54:48 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
apiFileDepsRoutes := apiFilesRoutes.Group("/dependancies/:depid")
|
|
|
|
apiFileDepsRoutes.Use(FileDepHandler)
|
|
|
|
apiFileDepsRoutes.DELETE("", deleteFileDep)
|
2020-01-18 23:54:00 +00:00
|
|
|
|
2019-07-21 19:54:48 +00:00
|
|
|
// Check
|
2022-05-16 09:38:46 +00:00
|
|
|
apiFilesRoutes.POST("/check", checkFile)
|
2024-03-17 14:03:30 +00:00
|
|
|
apiFilesRoutes.POST("/gunzip", gunzipFile)
|
2022-05-16 09:38:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2019-07-21 19:54:48 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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()
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
|
|
|
|
2019-07-21 21:46:23 +00:00
|
|
|
type APIFile struct {
|
2021-11-22 14:35:07 +00:00
|
|
|
*fic.EFile
|
2019-07-21 21:46:23 +00:00
|
|
|
Depends []fic.Flag `json:"depends,omitempty"`
|
|
|
|
}
|
|
|
|
|
2021-11-22 14:35:07 +00:00
|
|
|
func genFileList(in []*fic.EFile, e error) (out []APIFile, err error) {
|
2019-07-21 21:46:23 +00:00
|
|
|
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 {
|
2021-11-22 14:35:07 +00:00
|
|
|
if k, ok := d.(*fic.FlagKey); ok {
|
2019-07-21 21:46:23 +00:00
|
|
|
k, err = fic.GetFlagKey(k.Id)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Depends = append(g.Depends, k)
|
2021-11-22 14:35:07 +00:00
|
|
|
} else if m, ok := d.(*fic.MCQ); ok {
|
2020-01-20 16:28:37 +00:00
|
|
|
m, err = fic.GetMCQ(m.Id)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
g.Depends = append(g.Depends, m)
|
2019-07-21 21:46:23 +00:00
|
|
|
} else {
|
2020-04-15 05:39:38 +00:00
|
|
|
err = fmt.Errorf("Unknown type %T to handle file dependancy", k)
|
2019-07-21 21:46:23 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
out = append(out, g)
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
func listFiles(c *gin.Context) {
|
|
|
|
var files []APIFile
|
|
|
|
var err error
|
2017-12-27 00:53:01 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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)
|
2019-07-21 19:54:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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)
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
func showFile(c *gin.Context) {
|
|
|
|
c.JSON(http.StatusOK, c.MustGet("file").(*fic.EFile))
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
|
|
|
|
2019-07-21 19:54:48 +00:00
|
|
|
type uploadedFile struct {
|
|
|
|
URI string
|
|
|
|
Digest string
|
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-10-31 17:52:29 +00:00
|
|
|
paramsFiles, err := sync.GetExerciceFilesParams(sync.GlobalImporter, exercice.(*fic.Exercice))
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-07-21 19:54:48 +00:00
|
|
|
var uf uploadedFile
|
2022-10-31 17:52:29 +00:00
|
|
|
err = c.ShouldBindJSON(&uf)
|
2022-05-16 09:38:46 +00:00
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
2019-07-21 19:54:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
ret, err := sync.ImportFile(sync.GlobalImporter, uf.URI,
|
2019-07-21 19:54:48 +00:00
|
|
|
func(filePath string, origin string) (interface{}, error) {
|
|
|
|
if digest, err := hex.DecodeString(uf.Digest); err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else {
|
2022-10-31 17:52:29 +00:00
|
|
|
published := true
|
2022-11-23 15:55:49 +00:00
|
|
|
disclaimer := ""
|
|
|
|
|
2022-10-31 17:52:29 +00:00
|
|
|
if f, exists := paramsFiles[filepath.Base(filePath)]; exists {
|
|
|
|
published = !f.Hidden
|
2022-11-23 15:55:49 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2022-10-31 17:52:29 +00:00
|
|
|
}
|
|
|
|
|
2022-11-23 15:55:49 +00:00
|
|
|
return exercice.(*fic.Exercice).ImportFile(filePath, origin, digest, nil, disclaimer, published)
|
2019-07-21 19:54:48 +00:00
|
|
|
}
|
|
|
|
})
|
2022-05-16 09:38:46 +00:00
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.JSON(http.StatusOK, ret)
|
2019-07-21 19:54:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
func updateFile(c *gin.Context) {
|
|
|
|
file := c.MustGet("file").(*fic.EFile)
|
|
|
|
|
2017-12-27 00:53:01 +00:00
|
|
|
var uf fic.EFile
|
2022-05-16 09:38:46 +00:00
|
|
|
err := c.ShouldBindJSON(&uf)
|
|
|
|
if err != nil {
|
|
|
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
|
|
|
return
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uf.Id = file.Id
|
|
|
|
|
|
|
|
if _, err := uf.Update(); err != nil {
|
2022-05-16 09:38:46 +00:00
|
|
|
log.Println("Unable to updateFile:", err.Error())
|
|
|
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update file."})
|
|
|
|
return
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
2022-05-16 09:38:46 +00:00
|
|
|
|
|
|
|
c.JSON(http.StatusOK, uf)
|
2017-12-27 00:53:01 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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)
|
2016-01-20 21:44:34 +00:00
|
|
|
}
|
2018-08-19 20:19:49 +00:00
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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)
|
2019-07-21 21:46:23 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 09:38:46 +00:00
|
|
|
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)
|
2018-08-19 20:19:49 +00:00
|
|
|
}
|
2024-03-17 14:03:30 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|