server/admin/api/exercice.go

1157 lines
32 KiB
Go

package api
import (
"fmt"
"log"
"net/http"
"reflect"
"strconv"
"strings"
"time"
"srs.epita.fr/fic-server/admin/sync"
"srs.epita.fr/fic-server/libfic"
"github.com/gin-gonic/gin"
)
func declareGlobalExercicesRoutes(router *gin.RouterGroup) {
router.GET("/resolutions.json", exportResolutionMovies)
router.GET("/exercices_stats.json", getExercicesStats)
router.GET("/tags", listTags)
}
func declareExercicesRoutes(router *gin.RouterGroup) {
router.GET("/exercices", listExercices)
router.POST("/exercices", createExercice)
apiExercicesRoutes := router.Group("/exercices/:eid")
apiExercicesRoutes.Use(ExerciceHandler)
apiExercicesRoutes.GET("", showExercice)
apiExercicesRoutes.PUT("", updateExercice)
apiExercicesRoutes.PATCH("", partUpdateExercice)
apiExercicesRoutes.DELETE("", deleteExercice)
apiExercicesRoutes.GET("/stats.json", getExerciceStats)
apiExercicesRoutes.GET("/history.json", getExerciceHistory)
apiHistoryRoutes := apiExercicesRoutes.Group("/history.json")
apiHistoryRoutes.Use(AssigneeCookieHandler)
apiHistoryRoutes.PUT("", appendExerciceHistory)
apiHistoryRoutes.PATCH("", updateExerciceHistory)
apiHistoryRoutes.DELETE("", delExerciceHistory)
apiExercicesRoutes.GET("/hints", listExerciceHints)
apiExercicesRoutes.POST("/hints", createExerciceHint)
apiHintsRoutes := apiExercicesRoutes.Group("/hints/:hid")
apiHintsRoutes.Use(HintHandler)
apiHintsRoutes.GET("", showExerciceHint)
apiHintsRoutes.PUT("", updateExerciceHint)
apiHintsRoutes.DELETE("", deleteExerciceHint)
apiHintsRoutes.GET("/dependancies", showExerciceHintDeps)
apiExercicesRoutes.GET("/flags", listExerciceFlags)
apiExercicesRoutes.POST("/flags", createExerciceFlag)
apiFlagsRoutes := apiExercicesRoutes.Group("/flags/:kid")
apiFlagsRoutes.Use(FlagKeyHandler)
apiFlagsRoutes.GET("", showExerciceFlag)
apiFlagsRoutes.PUT("", updateExerciceFlag)
apiFlagsRoutes.POST("/try", tryExerciceFlag)
apiFlagsRoutes.DELETE("/", deleteExerciceFlag)
apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps)
apiFlagsRoutes.GET("/choices/", listFlagChoices)
apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid")
apiFlagsChoicesRoutes.Use(FlagChoiceHandler)
apiFlagsChoicesRoutes.GET("", showFlagChoice)
apiFlagsRoutes.POST("/choices/", createFlagChoice)
apiFlagsChoicesRoutes.PUT("", updateFlagChoice)
apiFlagsChoicesRoutes.DELETE("", deleteFlagChoice)
apiQuizRoutes := apiExercicesRoutes.Group("/quiz/:qid")
apiQuizRoutes.Use(FlagQuizHandler)
apiExercicesRoutes.GET("/quiz", listExerciceQuiz)
apiQuizRoutes.GET("", showExerciceQuiz)
apiQuizRoutes.PUT("", updateExerciceQuiz)
apiQuizRoutes.DELETE("", deleteExerciceQuiz)
apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps)
apiExercicesRoutes.GET("/tags", listExerciceTags)
apiExercicesRoutes.POST("/tags", addExerciceTag)
apiExercicesRoutes.PUT("/tags", updateExerciceTags)
declareFilesRoutes(apiExercicesRoutes)
declareExerciceClaimsRoutes(apiExercicesRoutes)
// Remote
router.GET("/remote/themes/:thid/exercices/:exid", sync.ApiGetRemoteExercice)
router.GET("/remote/themes/:thid/exercices/:exid/hints", sync.ApiGetRemoteExerciceHints)
router.GET("/remote/themes/:thid/exercices/:exid/flags", sync.ApiGetRemoteExerciceFlags)
}
type Exercice struct {
*fic.Exercice
ForgeLink string `json:"forge_link,omitempty"`
}
func ExerciceHandler(c *gin.Context) {
eid, err := strconv.ParseInt(string(c.Params.ByName("eid")), 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid exercice identifier"})
return
}
var exercice *fic.Exercice
if theme, exists := c.Get("theme"); exists {
exercice, err = theme.(*fic.Theme).GetExercice(int(eid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"})
return
}
} else {
exercice, err = fic.GetExercice(eid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Exercice not found"})
return
}
if exercice.IdTheme != nil {
theme, err = exercice.GetTheme()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to find the attached theme."})
return
}
c.Set("theme", theme)
} else {
c.Set("theme", &fic.Theme{Path: sync.StandaloneExercicesDirectory})
}
}
c.Set("exercice", exercice)
c.Next()
}
func HintHandler(c *gin.Context) {
hid, err := strconv.ParseInt(string(c.Params.ByName("hid")), 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid hint identifier"})
return
}
exercice := c.MustGet("exercice").(*fic.Exercice)
hint, err := exercice.GetHint(hid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Hint not found"})
return
}
c.Set("hint", hint)
c.Next()
}
func FlagKeyHandler(c *gin.Context) {
kid, err := strconv.ParseInt(string(c.Params.ByName("kid")), 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid flag identifier"})
return
}
var flag *fic.FlagKey
if exercice, exists := c.Get("exercice"); exists {
flag, err = exercice.(*fic.Exercice).GetFlagKey(int(kid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"})
return
}
} else {
flag, err = fic.GetFlagKey(int(kid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Flag not found"})
return
}
}
c.Set("flag-key", flag)
c.Next()
}
func FlagChoiceHandler(c *gin.Context) {
cid, err := strconv.ParseInt(string(c.Params.ByName("cid")), 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid choice identifier"})
return
}
flagkey := c.MustGet("flag-key").(*fic.FlagKey)
choice, err := flagkey.GetChoice(int(cid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Choice not found"})
return
}
c.Set("flag-choice", choice)
c.Next()
}
func FlagQuizHandler(c *gin.Context) {
qid, err := strconv.ParseInt(string(c.Params.ByName("qid")), 10, 64)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid quiz identifier"})
return
}
var quiz *fic.MCQ
if exercice, exists := c.Get("exercice"); exists {
quiz, err = exercice.(*fic.Exercice).GetMCQById(int(qid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"})
return
}
} else {
quiz, err = fic.GetMCQ(int(qid))
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Quiz not found"})
return
}
}
c.Set("flag-quiz", quiz)
c.Next()
}
func listExercices(c *gin.Context) {
if theme, exists := c.Get("theme"); exists {
exercices, err := theme.(*fic.Theme).GetExercices()
if err != nil {
log.Println("Unable to listThemedExercices:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."})
return
}
c.JSON(http.StatusOK, exercices)
} else {
exercices, err := fic.GetExercices()
if err != nil {
log.Println("Unable to listThemedExercices:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercices listing."})
return
}
c.JSON(http.StatusOK, exercices)
}
}
func listTags(c *gin.Context) {
exercices, err := fic.GetExercices()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
ret := map[string][]*fic.Exercice{}
for _, exercice := range exercices {
tags, err := exercice.GetTags()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
for _, t := range tags {
if _, ok := ret[t]; !ok {
ret[t] = []*fic.Exercice{}
}
ret[t] = append(ret[t], exercice)
}
}
c.JSON(http.StatusOK, ret)
}
// Generate the csv to export with:
//
// curl -s http://127.0.0.1:8081/api/resolutions.json | jq -r ".[] | [ .theme,.level,.title, @uri \"https://fic.srs.epita.fr/$(date +%Y)/\\(.videoURI)\" ] | join(\";\")"
func exportResolutionMovies(c *gin.Context) {
exercices, err := fic.GetExercices()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
export := []map[string]string{}
for _, exercice := range exercices {
var tname string
if exercice.IdTheme != nil {
theme, err := fic.GetTheme(*exercice.IdTheme)
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
tname = theme.Name
}
if len(exercice.VideoURI) > 0 {
level, _ := exercice.GetLevel()
export = append(export, map[string]string{
"videoURI": strings.Replace(exercice.VideoURI, "$FILES$/", "files/", 1),
"theme": tname,
"title": exercice.Title,
"level": fmt.Sprintf("%d", level),
})
}
}
c.JSON(http.StatusOK, export)
}
func loadFlags(n func() ([]fic.Flag, error)) (interface{}, error) {
if flags, err := n(); err != nil {
return nil, err
} else {
var ret []fic.Flag
for _, flag := range flags {
if f, ok := flag.(*fic.FlagKey); ok {
if k, err := fic.GetFlagKey(f.Id); err != nil {
return nil, err
} else {
ret = append(ret, k)
}
} else if f, ok := flag.(*fic.MCQ); ok {
if m, err := fic.GetMCQ(f.Id); err != nil {
return nil, err
} else {
ret = append(ret, m)
}
} else {
return nil, fmt.Errorf("Flag type %T not implemented for this flag.", f)
}
}
return ret, nil
}
}
func listExerciceHints(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
hints, err := exercice.GetHints()
if err != nil {
log.Println("Unable to listExerciceHints:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving hints"})
return
}
c.JSON(http.StatusOK, hints)
}
func listExerciceFlags(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
flags, err := exercice.GetFlagKeys()
if err != nil {
log.Println("Unable to listExerciceFlags:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice flags"})
return
}
c.JSON(http.StatusOK, flags)
}
func listFlagChoices(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
choices, err := flag.GetChoices()
if err != nil {
log.Println("Unable to listFlagChoices:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag choices"})
return
}
c.JSON(http.StatusOK, choices)
}
func listExerciceQuiz(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
quiz, err := exercice.GetMCQ()
if err != nil {
log.Println("Unable to listExerciceQuiz:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving quiz list"})
return
}
c.JSON(http.StatusOK, quiz)
}
func showExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var forgelink string
if fli, ok := sync.GlobalImporter.(sync.ForgeLinkedImporter); ok {
if u, _ := fli.GetExerciceLink(exercice); u != nil {
forgelink = u.String()
}
}
c.JSON(http.StatusOK, Exercice{exercice, forgelink})
}
func getExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
history, err := exercice.GetHistory()
if err != nil {
log.Println("Unable to getExerciceHistory:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"})
return
}
c.JSON(http.StatusOK, history)
}
type exerciceStats struct {
IdExercice int64 `json:"id_exercice,omitempty"`
TeamTries int64 `json:"team_tries"`
TotalTries int64 `json:"total_tries"`
SolvedCount int64 `json:"solved_count"`
FlagSolved []int64 `json:"flag_solved"`
MCQSolved []int64 `json:"mcq_solved"`
CurrentGain int64 `json:"current_gain"`
}
func getExerciceStats(c *gin.Context) {
e := c.MustGet("exercice").(*fic.Exercice)
current_gain := e.Gain
if fic.DiscountedFactor > 0 {
decoted_exercice, err := fic.GetDiscountedExercice(e.Id)
if err == nil {
current_gain = decoted_exercice.Gain
} else {
log.Println("Unable to fetch decotedExercice:", err.Error())
}
}
c.JSON(http.StatusOK, exerciceStats{
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
CurrentGain: current_gain,
})
}
func getExercicesStats(c *gin.Context) {
exercices, err := fic.GetDiscountedExercices()
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
return
}
ret := []exerciceStats{}
for _, e := range exercices {
ret = append(ret, exerciceStats{
IdExercice: e.Id,
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
SolvedCount: e.SolvedCount(),
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
CurrentGain: e.Gain,
})
}
c.JSON(http.StatusOK, ret)
}
func AssigneeCookieHandler(c *gin.Context) {
myassignee, err := c.Cookie("myassignee")
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action."})
return
}
aid, err := strconv.ParseInt(myassignee, 10, 32)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: invalid assignee identifier."})
return
}
assignee, err := fic.GetAssignee(aid)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "You must be authenticated to perform this action: assignee not found."})
return
}
c.Set("assignee", assignee)
c.Next()
}
type uploadedExerciceHistory struct {
IdTeam int64 `json:"team_id"`
Kind string
Time time.Time
Secondary *int64
Coeff float32
}
func appendExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)
var uh uploadedExerciceHistory
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
err = exercice.AppendHistoryItem(uh.IdTeam, uh.Kind, uh.Secondary)
if err != nil {
log.Println("Unable to appendExerciceHistory:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history moditication."})
return
}
log.Printf("AUDIT: %s performs an history append: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary)
c.JSON(http.StatusOK, uh)
}
func updateExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)
var uh uploadedExerciceHistory
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
_, err = exercice.UpdateHistoryItem(uh.Coeff, uh.IdTeam, uh.Kind, uh.Time, uh.Secondary)
if err != nil {
log.Println("Unable to updateExerciceHistory:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history update."})
return
}
log.Printf("AUDIT: %s performs an history update: %s for team %d, exercice %d and optional %v, with coeff %f", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary, uh.Coeff)
c.JSON(http.StatusOK, uh)
}
func delExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)
var uh uploadedExerciceHistory
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
_, err = exercice.DelHistoryItem(uh.IdTeam, uh.Kind, uh.Time, uh.Secondary)
if err != nil {
log.Println("Unable to delExerciceHistory:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during history deletion."})
return
}
log.Printf("AUDIT: %s performs an history deletion: %s for team %d, exercice %d and optional %v", myassignee.Name, uh.Kind, uh.IdTeam, exercice.Id, uh.Secondary)
c.JSON(http.StatusOK, true)
}
func deleteExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
_, err := exercice.DeleteCascade()
if err != nil {
log.Println("Unable to deleteExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice deletion"})
return
}
c.JSON(http.StatusOK, true)
}
func updateExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var ue fic.Exercice
err := c.ShouldBindJSON(&ue)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
ue.Id = exercice.Id
if len(ue.Title) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Exercice's title not filled"})
return
}
if _, err := ue.Update(); err != nil {
log.Println("Unable to updateExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice update"})
return
}
c.JSON(http.StatusOK, ue)
}
type patchExercice struct {
Language *string `json:"lang,omitempty"`
Title *string `json:"title"`
Disabled *bool `json:"disabled"`
WIP *bool `json:"wip"`
URLId *string `json:"urlid"`
Statement *string `json:"statement"`
Overview *string `json:"overview"`
Headline *string `json:"headline"`
Finished *string `json:"finished"`
Issue *string `json:"issue"`
IssueKind *string `json:"issuekind"`
Gain *int64 `json:"gain"`
Coefficient *float64 `json:"coefficient"`
}
func partUpdateExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var ue patchExercice
err := c.ShouldBindJSON(&ue)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
for _, field := range reflect.VisibleFields(reflect.TypeOf(ue)) {
if !reflect.ValueOf(ue).FieldByName(field.Name).IsNil() {
reflect.ValueOf(exercice).Elem().FieldByName(field.Name).Set(reflect.ValueOf(reflect.ValueOf(ue).FieldByName(field.Name).Elem().Interface()))
}
}
if _, err := exercice.Update(); err != nil {
log.Println("Unable to partUpdateExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice update."})
return
}
c.JSON(http.StatusOK, exercice)
}
func createExercice(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
// Create a new exercice
var ue fic.Exercice
err := c.ShouldBindJSON(&ue)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(ue.Title) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Title not filled"})
return
}
var depend *fic.Exercice = nil
if ue.Depend != nil {
if d, err := fic.GetExercice(*ue.Depend); err != nil {
log.Println("Unable to createExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})
return
} else {
depend = d
}
}
exercice, err := theme.AddExercice(ue.Title, ue.Authors, ue.Image, ue.BackgroundColor, ue.WIP, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Resolution, ue.SeeAlso, ue.Finished)
if err != nil {
log.Println("Unable to createExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})
return
}
c.JSON(http.StatusOK, exercice)
}
type uploadedHint struct {
Title string
Path string
Content string
Cost int64
URI string
}
func createExerciceHint(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var uh uploadedHint
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uh.Content) != 0 {
hint, err := exercice.AddHint(uh.Title, uh.Content, uh.Cost)
if err != nil {
log.Println("Unable to AddHint in createExerciceHint:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."})
return
}
c.JSON(http.StatusOK, hint)
} else if len(uh.URI) != 0 {
hint, err := sync.ImportFile(sync.GlobalImporter, uh.URI,
func(filePath string, origin string) (interface{}, error) {
return exercice.AddHint(uh.Title, "$FILES"+strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost)
})
if err != nil {
log.Println("Unable to AddHint (after ImportFile) in createExerciceHint:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add hint."})
return
}
c.JSON(http.StatusOK, hint)
} else {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's content not filled"})
return
}
}
func showExerciceHint(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("hint").(*fic.EHint))
}
func showExerciceHintDeps(c *gin.Context) {
hint := c.MustGet("hint").(*fic.EHint)
deps, err := loadFlags(hint.GetDepends)
if err != nil {
log.Println("Unable to loaddeps:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."})
return
}
c.JSON(http.StatusOK, deps)
}
func updateExerciceHint(c *gin.Context) {
hint := c.MustGet("hint").(*fic.EHint)
var uh fic.EHint
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
uh.Id = hint.Id
if len(uh.Title) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's title not filled"})
return
}
if _, err := uh.Update(); err != nil {
log.Println("Unable to updateExerciceHint:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update hint."})
return
}
c.JSON(http.StatusOK, uh)
}
func deleteExerciceHint(c *gin.Context) {
hint := c.MustGet("hint").(*fic.EHint)
_, err := hint.Delete()
if err != nil {
log.Println("Unable to deleteExerciceHint:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete hint."})
return
}
c.JSON(http.StatusOK, true)
}
type uploadedFlag struct {
Type string
Label string
Placeholder string
Help string
IgnoreCase bool
Multiline bool
NoTrim bool
CaptureRe *string `json:"capture_regexp"`
SortReGroups bool `json:"sort_re_grps"`
Flag string
Value []byte
ChoicesCost int32 `json:"choices_cost"`
BonusGain int32 `json:"bonus_gain"`
}
func createExerciceFlag(c *gin.Context) {
var uk uploadedFlag
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uk.Flag) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Flag not filled"})
return
}
var vre *string = nil
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
vre = uk.CaptureRe
}
exercice := c.MustGet("exercice").(*fic.Exercice)
flag, err := exercice.AddRawFlagKey(uk.Label, uk.Type, uk.Placeholder, uk.IgnoreCase, uk.NoTrim, uk.Multiline, vre, uk.SortReGroups, []byte(uk.Flag), uk.ChoicesCost, uk.BonusGain)
if err != nil {
log.Println("Unable to createExerciceFlag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to create flag."})
return
}
c.JSON(http.StatusOK, flag)
}
func showExerciceFlag(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-key").(*fic.FlagKey))
}
func showExerciceFlagDeps(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
deps, err := loadFlags(flag.GetDepends)
if err != nil {
log.Println("Unable to loaddeps:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve hint dependencies."})
return
}
c.JSON(http.StatusOK, deps)
}
func tryExerciceFlag(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
var uk uploadedFlag
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uk.Flag) == 0 {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty submission"})
return
}
if flag.Check([]byte(uk.Flag)) == 0 {
c.AbortWithStatusJSON(http.StatusOK, true)
return
}
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad submission"})
}
func updateExerciceFlag(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
var uk uploadedFlag
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uk.Label) == 0 {
flag.Label = "Flag"
} else {
flag.Label = uk.Label
}
flag.Placeholder = uk.Placeholder
flag.Help = uk.Help
flag.IgnoreCase = uk.IgnoreCase
flag.Multiline = uk.Multiline
if len(uk.Flag) > 0 {
var err error
flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag))
if err != nil {
log.Println("Unable to ComputeChecksum:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to compute flag checksum"})
return
}
} else {
flag.Checksum = uk.Value
}
flag.ChoicesCost = uk.ChoicesCost
flag.BonusGain = uk.BonusGain
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
flag.CaptureRegexp = uk.CaptureRe
} else {
flag.CaptureRegexp = nil
}
if _, err := flag.Update(); err != nil {
log.Println("Unable to updateExerciceFlag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update flag."})
return
}
c.JSON(http.StatusOK, flag)
}
func deleteExerciceFlag(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
_, err := flag.Delete()
if err != nil {
log.Println("Unable to deleteExerciceFlag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete flag."})
return
}
c.JSON(http.StatusOK, true)
}
func createFlagChoice(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
var uc fic.FlagChoice
err := c.ShouldBindJSON(&uc)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uc.Label) == 0 {
uc.Label = uc.Value
}
choice, err := flag.AddChoice(&uc)
if err != nil {
log.Println("Unable to createFlagChoice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to create flag choice."})
return
}
c.JSON(http.StatusOK, choice)
}
func showFlagChoice(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-choice").(*fic.FlagChoice))
}
func updateFlagChoice(c *gin.Context) {
choice := c.MustGet("flag-choice").(*fic.FlagChoice)
var uc fic.FlagChoice
err := c.ShouldBindJSON(&uc)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(uc.Label) == 0 {
choice.Label = uc.Value
} else {
choice.Label = uc.Label
}
choice.Value = uc.Value
if _, err := choice.Update(); err != nil {
log.Println("Unable to updateFlagChoice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update flag choice."})
return
}
c.JSON(http.StatusOK, choice)
}
func deleteFlagChoice(c *gin.Context) {
choice := c.MustGet("flag-choice").(*fic.FlagChoice)
_, err := choice.Delete()
if err != nil {
log.Println("Unable to deleteExerciceChoice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete choice."})
return
}
c.JSON(http.StatusOK, true)
}
func showExerciceQuiz(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-quiz").(*fic.MCQ))
}
func showExerciceQuizDeps(c *gin.Context) {
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
deps, err := loadFlags(quiz.GetDepends)
if err != nil {
log.Println("Unable to loaddeps:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve quiz dependencies."})
return
}
c.JSON(http.StatusOK, deps)
}
func updateExerciceQuiz(c *gin.Context) {
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
var uq fic.MCQ
err := c.ShouldBindJSON(&uq)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
quiz.Title = uq.Title
if _, err := quiz.Update(); err != nil {
log.Println("Unable to updateExerciceQuiz:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update quiz."})
return
}
// Update and remove old entries
var delete []int
for i, cur := range quiz.Entries {
seen := false
for _, next := range uq.Entries {
if cur.Id == next.Id {
seen = true
if cur.Label != next.Label || cur.Response != next.Response {
cur.Label = next.Label
cur.Response = next.Response
if _, err := cur.Update(); err != nil {
log.Println("Unable to update MCQ entry:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update some MCQ entry"})
return
}
}
break
}
}
if seen == false {
if _, err := cur.Delete(); err != nil {
log.Println("Unable to delete MCQ entry:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to delete some MCQ entry"})
return
} else {
delete = append(delete, i)
}
}
}
for n, i := range delete {
quiz.Entries = append(quiz.Entries[:i-n-1], quiz.Entries[:i-n+1]...)
}
// Add new choices
for _, choice := range uq.Entries {
if choice.Id == 0 {
if ch, err := quiz.AddEntry(choice); err != nil {
log.Println("Unable to add MCQ entry:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to add some MCQ entry"})
return
} else {
quiz.Entries = append(quiz.Entries, ch)
}
}
}
c.JSON(http.StatusOK, quiz)
}
func deleteExerciceQuiz(c *gin.Context) {
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
for _, choice := range quiz.Entries {
if _, err := choice.Delete(); err != nil {
log.Println("Unable to deleteExerciceQuiz (entry):", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz entry."})
return
}
}
_, err := quiz.Delete()
if err != nil {
log.Println("Unable to deleteExerciceQuiz:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete quiz."})
return
}
c.JSON(http.StatusOK, true)
}
func listExerciceTags(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
tags, err := exercice.GetTags()
if err != nil {
log.Println("Unable to listExerciceTags:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to get tags."})
return
}
c.JSON(http.StatusOK, tags)
}
func addExerciceTag(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var ut []string
err := c.ShouldBindJSON(&ut)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
// TODO: a DB transaction should be done here: on error we should rollback
for _, t := range ut {
if _, err := exercice.AddTag(t); err != nil {
log.Println("Unable to addExerciceTag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add some tag."})
return
}
}
c.JSON(http.StatusOK, ut)
}
func updateExerciceTags(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
exercice.WipeTags()
addExerciceTag(c)
}