server/admin/api/exercice.go

1157 lines
32 KiB
Go
Raw Normal View History

package api
import (
"fmt"
2022-05-16 09:38:46 +00:00
"log"
"net/http"
"reflect"
2022-05-16 09:38:46 +00:00
"strconv"
2017-01-05 01:21:32 +00:00
"strings"
2019-02-03 21:30:29 +00:00
"time"
"srs.epita.fr/fic-server/admin/sync"
2018-03-09 18:07:08 +00:00
"srs.epita.fr/fic-server/libfic"
2022-05-16 09:38:46 +00:00
"github.com/gin-gonic/gin"
)
2022-05-16 09:38:46 +00:00
func declareGlobalExercicesRoutes(router *gin.RouterGroup) {
router.GET("/resolutions.json", exportResolutionMovies)
router.GET("/exercices_stats.json", getExercicesStats)
2022-05-24 19:25:27 +00:00
router.GET("/tags", listTags)
2022-05-16 09:38:46 +00:00
}
func declareExercicesRoutes(router *gin.RouterGroup) {
router.GET("/exercices", listExercices)
router.POST("/exercices", createExercice)
2022-05-16 09:38:46 +00:00
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)
2022-05-16 09:38:46 +00:00
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
2022-05-16 09:38:46 +00:00
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)
}
2022-05-24 19:25:51 +00:00
type Exercice struct {
*fic.Exercice
ForgeLink string `json:"forge_link,omitempty"`
}
2022-05-16 09:38:46 +00:00
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})
}
2022-05-16 09:38:46 +00:00
}
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()
}
2022-05-16 09:38:46 +00:00
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)
}
}
2022-05-24 19:25:27 +00:00
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:
2023-06-14 15:11:21 +00:00
//
// 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(\";\")"
2022-05-16 09:38:46 +00:00
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 {
2022-06-19 22:08:07 +00:00
level, _ := exercice.GetLevel()
2022-05-16 09:38:46 +00:00
export = append(export, map[string]string{
2022-06-19 22:08:07 +00:00
"videoURI": strings.Replace(exercice.VideoURI, "$FILES$/", "files/", 1),
"theme": tname,
2022-05-16 09:38:46 +00:00
"title": exercice.Title,
2022-06-19 22:08:07 +00:00
"level": fmt.Sprintf("%d", level),
2022-05-16 09:38:46 +00:00
})
}
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, export)
}
2020-04-15 05:39:38 +00:00
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 {
2021-11-22 14:35:07 +00:00
if f, ok := flag.(*fic.FlagKey); ok {
if k, err := fic.GetFlagKey(f.Id); err != nil {
return nil, err
} else {
ret = append(ret, k)
}
2021-11-22 14:35:07 +00:00
} 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 {
2020-04-15 05:39:38 +00:00
return nil, fmt.Errorf("Flag type %T not implemented for this flag.", f)
}
}
return ret, nil
}
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
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)
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
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)
2017-12-17 01:50:01 +00:00
}
2022-05-16 09:38:46 +00:00
func showExercice(c *gin.Context) {
2022-05-24 19:25:51 +00:00
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})
2016-12-26 00:14:46 +00:00
}
2022-05-16 09:38:46 +00:00
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)
2019-02-03 21:30:29 +00:00
}
2020-01-28 13:01:13 +00:00
type exerciceStats struct {
2020-01-29 14:57:34 +00:00
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"`
2020-01-28 13:01:13 +00:00
}
2022-05-16 09:38:46 +00:00
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())
}
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, exerciceStats{
2020-04-15 05:39:38 +00:00
TeamTries: e.TriedTeamCount(),
TotalTries: e.TriedCount(),
2020-01-28 13:01:13 +00:00
SolvedCount: e.SolvedCount(),
2020-04-15 05:39:38 +00:00
FlagSolved: e.FlagSolved(),
MCQSolved: e.MCQSolved(),
CurrentGain: current_gain,
2022-05-16 09:38:46 +00:00
})
2020-01-28 13:01:13 +00:00
}
2022-05-16 09:38:46 +00:00
func getExercicesStats(c *gin.Context) {
exercices, err := fic.GetDiscountedExercices()
2022-05-16 09:38:46 +00:00
if err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()})
2023-06-14 16:46:30 +00:00
return
2020-01-29 14:57:34 +00:00
}
2022-05-16 09:38:46 +00:00
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,
2022-05-16 09:38:46 +00:00
})
}
c.JSON(http.StatusOK, ret)
2020-01-29 14:57:34 +00:00
}
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()
}
2019-02-03 21:30:29 +00:00
type uploadedExerciceHistory struct {
IdTeam int64 `json:"team_id"`
2019-02-03 21:30:29 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func updateExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)
2022-05-16 09:38:46 +00:00
var uh uploadedExerciceHistory
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
2022-05-16 09:38:46 +00:00
_, 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)
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, uh)
2019-02-03 21:30:29 +00:00
}
2022-05-16 09:38:46 +00:00
func delExerciceHistory(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
myassignee := c.MustGet("assignee").(*fic.ClaimAssignee)
2022-05-16 09:38:46 +00:00
2019-02-03 21:30:29 +00:00
var uh uploadedExerciceHistory
2022-05-16 09:38:46 +00:00
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
2019-02-03 21:30:29 +00:00
}
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)
2019-02-03 21:30:29 +00:00
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, true)
2019-02-03 21:30:29 +00:00
}
2022-05-16 09:38:46 +00:00
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)
2016-12-26 00:14:46 +00:00
}
2022-05-16 09:38:46 +00:00
func updateExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
2016-12-26 00:14:46 +00:00
var ue fic.Exercice
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&ue)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2016-12-26 00:14:46 +00:00
}
2016-12-26 00:14:46 +00:00
ue.Id = exercice.Id
2016-12-26 00:14:46 +00:00
if len(ue.Title) == 0 {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Exercice's title not filled"})
return
2016-12-26 00:14:46 +00:00
}
if _, err := ue.Update(); err != nil {
2022-05-16 09:38:46 +00:00
log.Println("Unable to updateExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "An error occurs during exercice update"})
return
}
2016-12-26 00:14:46 +00:00
2022-05-16 09:38:46 +00:00
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"`
}
2022-05-16 09:38:46 +00:00
func partUpdateExercice(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var ue patchExercice
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
log.Println("Unable to partUpdateExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice update."})
return
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, exercice)
}
2022-05-16 09:38:46 +00:00
func createExercice(c *gin.Context) {
theme := c.MustGet("theme").(*fic.Theme)
// Create a new exercice
2016-12-26 00:14:46 +00:00
var ue fic.Exercice
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&ue)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if len(ue.Title) == 0 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
log.Println("Unable to createExercice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during exercice creation."})
return
} else {
2021-11-22 14:35:07 +00:00
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)
2022-05-16 09:38:46 +00:00
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)
}
2017-01-05 01:21:32 +00:00
type uploadedHint struct {
2018-03-09 18:07:08 +00:00
Title string
Path string
Content string
Cost int64
URI string
2017-01-05 01:21:32 +00:00
}
2022-05-16 09:38:46 +00:00
func createExerciceHint(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
2017-01-05 01:21:32 +00:00
var uh uploadedHint
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uh)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2016-12-04 18:15:39 +00:00
}
2017-01-05 01:21:32 +00:00
if len(uh.Content) != 0 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
hint, err := sync.ImportFile(sync.GlobalImporter, uh.URI,
func(filePath string, origin string) (interface{}, error) {
2018-03-09 18:07:08 +00:00
return exercice.AddHint(uh.Title, "$FILES"+strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost)
2017-01-05 01:21:32 +00:00
})
2022-05-16 09:38:46 +00:00
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)
2017-01-05 01:21:32 +00:00
} else {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's content not filled"})
return
2016-12-04 18:15:39 +00:00
}
}
2022-05-16 09:38:46 +00:00
func showExerciceHint(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("hint").(*fic.EHint))
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func updateExerciceHint(c *gin.Context) {
hint := c.MustGet("hint").(*fic.EHint)
var uh fic.EHint
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Hint's title not filled"})
return
}
if _, err := uh.Update(); err != nil {
2022-05-16 09:38:46 +00:00
log.Println("Unable to updateExerciceHint:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update hint."})
return
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, uh)
}
2022-05-16 09:38:46 +00:00
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)
}
2018-09-24 08:00:17 +00:00
type uploadedFlag struct {
2022-01-21 07:28:39 +00:00
Type string
Label string
Placeholder string
2022-06-08 10:24:38 +00:00
Help string
2022-01-21 07:28:39 +00:00
IgnoreCase bool
Multiline bool
NoTrim bool
2024-03-27 10:24:54 +00:00
CaptureRe *string `json:"capture_regexp"`
2022-01-21 07:28:39 +00:00
SortReGroups bool `json:"sort_re_grps"`
Flag string
Value []byte
2022-05-31 20:03:51 +00:00
ChoicesCost int32 `json:"choices_cost"`
BonusGain int32 `json:"bonus_gain"`
2017-12-12 04:06:34 +00:00
}
2022-05-16 09:38:46 +00:00
func createExerciceFlag(c *gin.Context) {
2018-09-24 08:00:17 +00:00
var uk uploadedFlag
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2017-12-12 04:06:34 +00:00
}
2018-09-24 08:00:17 +00:00
if len(uk.Flag) == 0 {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Flag not filled"})
return
2017-12-12 04:06:34 +00:00
}
2018-11-16 19:46:19 +00:00
var vre *string = nil
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
vre = uk.CaptureRe
2018-11-16 19:46:19 +00:00
}
2022-05-16 09:38:46 +00:00
exercice := c.MustGet("exercice").(*fic.Exercice)
2022-05-31 20:03:51 +00:00
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)
2022-05-16 09:38:46 +00:00
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)
2017-12-12 04:06:34 +00:00
}
2022-05-16 09:38:46 +00:00
func showExerciceFlag(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-key").(*fic.FlagKey))
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func tryExerciceFlag(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
2018-12-08 02:13:16 +00:00
var uk uploadedFlag
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2018-12-08 02:13:16 +00:00
}
if len(uk.Flag) == 0 {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Empty submission"})
return
2018-12-08 02:13:16 +00:00
}
if flag.Check([]byte(uk.Flag)) == 0 {
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusOK, true)
return
2018-12-08 02:13:16 +00:00
}
2022-05-16 09:38:46 +00:00
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad submission"})
2018-12-08 02:13:16 +00:00
}
2022-05-16 09:38:46 +00:00
func updateExerciceFlag(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
2018-09-24 08:00:17 +00:00
var uk uploadedFlag
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uk)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
2017-12-12 04:06:34 +00:00
if len(uk.Label) == 0 {
2018-09-24 08:00:17 +00:00
flag.Label = "Flag"
2017-12-12 04:06:34 +00:00
} else {
2018-09-24 08:00:17 +00:00
flag.Label = uk.Label
}
2020-09-07 17:33:09 +00:00
flag.Placeholder = uk.Placeholder
2022-06-08 10:24:38 +00:00
flag.Help = uk.Help
2018-11-16 19:46:19 +00:00
flag.IgnoreCase = uk.IgnoreCase
2020-01-28 09:49:12 +00:00
flag.Multiline = uk.Multiline
if len(uk.Flag) > 0 {
var err error
flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag))
if err != nil {
2022-05-16 09:38:46 +00:00
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
}
2018-12-02 22:18:32 +00:00
flag.ChoicesCost = uk.ChoicesCost
2022-05-31 20:03:51 +00:00
flag.BonusGain = uk.BonusGain
2018-11-16 19:46:19 +00:00
if uk.CaptureRe != nil && len(*uk.CaptureRe) > 0 {
flag.CaptureRegexp = uk.CaptureRe
2018-11-16 19:46:19 +00:00
} else {
flag.CaptureRegexp = nil
2018-11-16 19:46:19 +00:00
}
2018-01-26 09:57:15 +00:00
2018-09-24 08:00:17 +00:00
if _, err := flag.Update(); err != nil {
2022-05-16 09:38:46 +00:00
log.Println("Unable to updateExerciceFlag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to update flag."})
return
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, flag)
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func createFlagChoice(c *gin.Context) {
flag := c.MustGet("flag-key").(*fic.FlagKey)
var uc fic.FlagChoice
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uc)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2018-11-21 03:10:22 +00:00
}
if len(uc.Label) == 0 {
uc.Label = uc.Value
}
2022-05-16 09:38:46 +00:00
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)
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
func showFlagChoice(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-choice").(*fic.FlagChoice))
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
func updateFlagChoice(c *gin.Context) {
choice := c.MustGet("flag-choice").(*fic.FlagChoice)
var uc fic.FlagChoice
2022-05-16 09:38:46 +00:00
err := c.ShouldBindJSON(&uc)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
2018-11-21 03:10:22 +00:00
}
if len(uc.Label) == 0 {
choice.Label = uc.Value
} else {
choice.Label = uc.Label
}
2020-01-28 09:49:12 +00:00
choice.Value = uc.Value
2018-11-21 03:10:22 +00:00
if _, err := choice.Update(); err != nil {
2022-05-16 09:38:46 +00:00
log.Println("Unable to updateFlagChoice:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to update flag choice."})
return
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, choice)
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
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)
2018-11-21 03:10:22 +00:00
}
2022-05-16 09:38:46 +00:00
func showExerciceQuiz(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("flag-quiz").(*fic.MCQ))
2017-12-17 01:50:01 +00:00
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func updateExerciceQuiz(c *gin.Context) {
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
var uq fic.MCQ
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
quiz.Entries = append(quiz.Entries, ch)
}
}
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, quiz)
}
2022-05-16 09:38:46 +00:00
func deleteExerciceQuiz(c *gin.Context) {
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
for _, choice := range quiz.Entries {
if _, err := choice.Delete(); err != nil {
2022-05-16 09:38:46 +00:00
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
}
}
2022-05-16 09:38:46 +00:00
_, 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)
2017-12-17 01:50:01 +00:00
}
2022-05-16 09:38:46 +00:00
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)
}
2022-05-16 09:38:46 +00:00
func addExerciceTag(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
var ut []string
2022-05-16 09:38:46 +00:00
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 {
2022-05-16 09:38:46 +00:00
log.Println("Unable to addExerciceTag:", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to add some tag."})
return
}
}
2022-05-16 09:38:46 +00:00
c.JSON(http.StatusOK, ut)
}
2022-05-16 09:38:46 +00:00
func updateExerciceTags(c *gin.Context) {
exercice := c.MustGet("exercice").(*fic.Exercice)
exercice.WipeTags()
2022-05-16 09:38:46 +00:00
addExerciceTag(c)
}