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