server/admin/api/sync.go

192 lines
5.9 KiB
Go

package api
import (
"fmt"
"log"
"net/http"
"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(map[string][]string{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)
}
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(map[string][]string{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)})
}