server/admin/api/sync.go

227 lines
7.0 KiB
Go

package api
import (
"fmt"
"log"
"net/http"
"net/url"
"path"
"strings"
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
"srs.epita.fr/fic-server/settings"
"github.com/gin-gonic/gin"
)
func declareSyncRoutes(router *gin.RouterGroup) {
apiSyncRoutes := router.Group("/sync")
// Base sync checks if the local directory is in sync with remote one.
apiSyncRoutes.POST("/base", func(c *gin.Context) {
err := sync.GlobalImporter.Sync()
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
} else {
c.JSON(http.StatusOK, true)
}
})
// Speedy sync performs a recursive synchronization without importing files.
apiSyncRoutes.POST("/speed", func(c *gin.Context) {
st := sync.SpeedySyncDeep(sync.GlobalImporter)
sync.EditDeepReport(&st, false)
c.JSON(http.StatusOK, st)
})
// Deep sync: a fully recursive synchronization (can be limited by theme).
apiSyncRoutes.GET("/deep", func(c *gin.Context) {
if sync.DeepSyncProgress == 0 {
c.AbortWithStatusJSON(http.StatusTooEarly, gin.H{"errmsg": "Pas de synchronisation en cours"})
return
}
c.JSON(http.StatusOK, gin.H{"progress": sync.DeepSyncProgress})
})
apiSyncRoutes.POST("/deep", func(c *gin.Context) {
c.JSON(http.StatusOK, sync.SyncDeep(sync.GlobalImporter))
})
apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid")
apiSyncDeepRoutes.Use(ThemeHandler)
apiSyncDeepRoutes.POST("", func(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]error{theme.Name: st}}, false)
sync.DeepSyncProgress = 255
c.JSON(http.StatusOK, st)
})
// Auto sync: to use with continuous deployment, in a development env
apiSyncRoutes.POST("/auto/*p", autoSync)
// Themes
apiSyncRoutes.POST("/fixurlids", fixAllURLIds)
apiSyncRoutes.POST("/themes", func(c *gin.Context) {
c.JSON(http.StatusOK, sync.SyncThemes(sync.GlobalImporter))
})
apiSyncThemesRoutes := apiSyncRoutes.Group("/themes/:thid")
apiSyncThemesRoutes.Use(ThemeHandler)
apiSyncThemesRoutes.POST("/fixurlid", func(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
if theme.FixURLId() {
v, err := theme.Update()
if err != nil {
log.Println("Unable to UpdateTheme after fixurlid:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the theme."})
return
}
c.JSON(http.StatusOK, v)
} else {
c.AbortWithStatusJSON(http.StatusOK, 0)
}
})
// Exercices
declareSyncExercicesRoutes(apiSyncRoutes)
declareSyncExercicesRoutes(apiSyncThemesRoutes)
// Videos sync imports resolution.mp4 from path stored in database.
apiSyncRoutes.POST("/videos", func(c *gin.Context) {
exercices, err := fic.GetExercices()
if err != nil {
log.Println("Unable to GetExercices:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve exercices list."})
return
}
for _, e := range exercices {
if len(e.VideoURI) == 0 || !strings.HasPrefix(e.VideoURI, "$RFILES$/") {
continue
}
vpath, err := url.PathUnescape(strings.TrimPrefix(e.VideoURI, "$RFILES$/"))
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": fmt.Sprintf("Unable to perform URL unescape: %s", err.Error())})
return
}
_, err = sync.ImportFile(sync.GlobalImporter, vpath, func(filePath, URI string) (interface{}, error) {
e.VideoURI = path.Join("$FILES$", strings.TrimPrefix(filePath, fic.FilesDir))
return e.Update()
})
if err != nil {
c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()})
return
}
}
c.JSON(http.StatusOK, true)
})
}
func declareSyncExercicesRoutes(router *gin.RouterGroup) {
router.POST("/exercices", func(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
c.JSON(http.StatusOK, sync.SyncExercices(sync.GlobalImporter, theme))
})
apiSyncExercicesRoutes := router.Group("/exercices/:eid")
apiSyncExercicesRoutes.Use(ExerciceHandler)
apiSyncExercicesRoutes.POST("", func(c *gin.Context) {
theme, exists := c.Get("theme")
if !exists {
c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "You should sync exercice only through a theme."})
return
}
exercice := c.MustGet("exercice").(*fic.Exercice)
_, _, errs := sync.SyncExercice(sync.GlobalImporter, theme.(*fic.Theme), exercice.Path, nil)
c.JSON(http.StatusOK, errs)
})
apiSyncExercicesRoutes.POST("/files", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
c.JSON(http.StatusOK, sync.SyncExerciceFiles(sync.GlobalImporter, exercice))
})
apiSyncExercicesRoutes.POST("/fixurlid", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
if exercice.FixURLId() {
v, err := exercice.Update()
if err != nil {
log.Println("Unable to UpdateExercice after fixurlid:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when saving the exercice."})
return
}
c.JSON(http.StatusOK, v)
} else {
c.AbortWithStatusJSON(http.StatusOK, 0)
}
})
apiSyncExercicesRoutes.POST("/hints", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
_, errs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
c.JSON(http.StatusOK, errs)
})
apiSyncExercicesRoutes.POST("/flags", func(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
_, errs := sync.SyncExerciceFlags(sync.GlobalImporter, exercice)
_, herrs := sync.SyncExerciceHints(sync.GlobalImporter, exercice, sync.ExerciceFlagsMap(sync.GlobalImporter, exercice))
c.JSON(http.StatusOK, append(errs, herrs...))
})
}
// autoSync tries to performs a smart synchronization, when in development environment.
// It'll sync most of modified things, and will delete out of sync data.
// Avoid using it in a production environment.
func autoSync(c *gin.Context) {
p := strings.TrimPrefix(c.Params.ByName("p"), "/")
themes, err := fic.GetThemes()
if err != nil {
log.Println("Unable to GetThemes:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve theme list."})
return
}
if p == "" {
if !IsProductionEnv {
for _, theme := range themes {
theme.DeleteDeep()
}
}
st := sync.SyncDeep(sync.GlobalImporter)
c.JSON(http.StatusOK, st)
return
}
for _, theme := range themes {
if theme.Path == p {
if !IsProductionEnv {
exercices, err := theme.GetExercices()
if err == nil {
for _, exercice := range exercices {
exercice.DeleteDeep()
}
}
}
st := sync.SyncThemeDeep(sync.GlobalImporter, theme, 0, 250)
sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]error{theme.Name: st}}, false)
sync.DeepSyncProgress = 255
settings.ForceRegeneration()
c.JSON(http.StatusOK, st)
return
}
}
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": fmt.Sprintf("Theme not found %q", p)})
}