493 lines
13 KiB
Go
493 lines
13 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
|
|
"srs.epita.fr/fic-server/admin/sync"
|
|
"srs.epita.fr/fic-server/libfic"
|
|
|
|
"github.com/julienschmidt/httprouter"
|
|
)
|
|
|
|
func init() {
|
|
router.GET("/api/exercices/", apiHandler(listExercices))
|
|
|
|
router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
|
|
router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
|
|
router.PATCH("/api/exercices/:eid", apiHandler(exerciceHandler(partUpdateExercice)))
|
|
router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
|
|
|
|
router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
|
|
router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
|
|
router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showExerciceFile)))
|
|
router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(deleteExerciceFile)))
|
|
|
|
router.GET("/api/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints)))
|
|
router.POST("/api/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint)))
|
|
router.GET("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(showExerciceHint)))
|
|
router.PUT("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(updateExerciceHint)))
|
|
router.DELETE("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(deleteExerciceHint)))
|
|
|
|
router.GET("/api/exercices/:eid/flags", apiHandler(exerciceHandler(listExerciceFlags)))
|
|
router.POST("/api/exercices/:eid/flags", apiHandler(exerciceHandler(createExerciceFlag)))
|
|
router.GET("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(showExerciceFlag)))
|
|
router.PUT("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(updateExerciceFlag)))
|
|
router.DELETE("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(deleteExerciceFlag)))
|
|
router.GET("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(listFlagChoices)))
|
|
router.GET("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(showFlagChoice)))
|
|
router.POST("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(createFlagChoice)))
|
|
router.PUT("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(updateFlagChoice)))
|
|
router.DELETE("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(deleteFlagChoice)))
|
|
|
|
router.GET("/api/exercices/:eid/quiz", apiHandler(exerciceHandler(listExerciceQuiz)))
|
|
router.GET("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(showExerciceQuiz)))
|
|
router.PUT("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(updateExerciceQuiz)))
|
|
router.DELETE("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(deleteExerciceQuiz)))
|
|
|
|
router.GET("/api/exercices/:eid/tags", apiHandler(exerciceHandler(listExerciceTags)))
|
|
router.POST("/api/exercices/:eid/tags", apiHandler(exerciceHandler(addExerciceTag)))
|
|
router.PUT("/api/exercices/:eid/tags", apiHandler(exerciceHandler(updateExerciceTags)))
|
|
|
|
// Synchronize
|
|
router.POST("/api/sync/exercices/:eid/files", apiHandler(exerciceHandler(
|
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
|
|
})))
|
|
router.POST("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler(
|
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil
|
|
})))
|
|
router.POST("/api/sync/exercices/:eid/flags", apiHandler(exerciceHandler(
|
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
return sync.SyncExerciceFlags(sync.GlobalImporter, exercice), nil
|
|
})))
|
|
|
|
router.POST("/api/sync/exercices/:eid/fixurlid", apiHandler(exerciceHandler(
|
|
func(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
if exercice.FixURLId() {
|
|
return exercice.Update()
|
|
}
|
|
return 0, nil
|
|
})))
|
|
}
|
|
|
|
func listExercices(_ httprouter.Params, body []byte) (interface{}, error) {
|
|
// List all exercices
|
|
return fic.GetExercices()
|
|
}
|
|
|
|
func listExerciceFiles(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
return exercice.GetFiles()
|
|
}
|
|
|
|
func listExerciceHints(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
return exercice.GetHints()
|
|
}
|
|
|
|
func listExerciceFlags(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
return exercice.GetFlags()
|
|
}
|
|
|
|
func listFlagChoices(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
|
|
return flag.GetChoices()
|
|
}
|
|
|
|
func listExerciceQuiz(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
return exercice.GetMCQ()
|
|
}
|
|
|
|
func showExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
return exercice, nil
|
|
}
|
|
|
|
func deleteExercice(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
return exercice.Delete()
|
|
}
|
|
|
|
func updateExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var ue fic.Exercice
|
|
if err := json.Unmarshal(body, &ue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ue.Id = exercice.Id
|
|
|
|
if len(ue.Title) == 0 {
|
|
return nil, errors.New("Exercice's title not filled")
|
|
}
|
|
|
|
if _, err := ue.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ue, nil
|
|
}
|
|
|
|
func partUpdateExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var ue fic.Exercice
|
|
if err := json.Unmarshal(body, &ue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(ue.Title) > 0 {
|
|
exercice.Title = ue.Title
|
|
}
|
|
|
|
if len(ue.URLId) > 0 {
|
|
exercice.URLId = ue.URLId
|
|
}
|
|
|
|
if len(ue.Statement) > 0 {
|
|
exercice.Statement = ue.Statement
|
|
}
|
|
|
|
if len(ue.Overview) > 0 {
|
|
exercice.Overview = ue.Overview
|
|
}
|
|
|
|
if len(ue.Issue) > 0 {
|
|
exercice.Issue = ue.Issue
|
|
}
|
|
|
|
if len(ue.IssueKind) > 0 {
|
|
exercice.IssueKind = ue.IssueKind
|
|
}
|
|
|
|
if ue.Depend != nil {
|
|
exercice.Depend = ue.Depend
|
|
}
|
|
|
|
if ue.Gain != 0 {
|
|
exercice.Gain = ue.Gain
|
|
}
|
|
|
|
if ue.Coefficient != 0 {
|
|
exercice.Coefficient = ue.Coefficient
|
|
}
|
|
|
|
if len(ue.VideoURI) > 0 {
|
|
exercice.VideoURI = ue.VideoURI
|
|
}
|
|
|
|
if _, err := exercice.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return exercice, nil
|
|
}
|
|
|
|
func createExercice(theme fic.Theme, body []byte) (interface{}, error) {
|
|
// Create a new exercice
|
|
var ue fic.Exercice
|
|
if err := json.Unmarshal(body, &ue); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(ue.Title) == 0 {
|
|
return nil, errors.New("Title not filled")
|
|
}
|
|
|
|
var depend *fic.Exercice = nil
|
|
if ue.Depend != nil {
|
|
if d, err := fic.GetExercice(*ue.Depend); err != nil {
|
|
return nil, err
|
|
} else {
|
|
depend = &d
|
|
}
|
|
}
|
|
|
|
return theme.AddExercice(ue.Title, ue.URLId, ue.Path, ue.Statement, ue.Overview, depend, ue.Gain, ue.VideoURI)
|
|
}
|
|
|
|
type uploadedHint struct {
|
|
Title string
|
|
Path string
|
|
Content string
|
|
Cost int64
|
|
URI string
|
|
}
|
|
|
|
func createExerciceHint(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uh uploadedHint
|
|
if err := json.Unmarshal(body, &uh); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(uh.Content) != 0 {
|
|
return exercice.AddHint(uh.Title, uh.Content, uh.Cost)
|
|
} else if len(uh.URI) != 0 {
|
|
return 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)
|
|
})
|
|
} else {
|
|
return nil, errors.New("Hint's content not filled")
|
|
}
|
|
}
|
|
|
|
func showExerciceHint(hint fic.EHint, body []byte) (interface{}, error) {
|
|
return hint, nil
|
|
}
|
|
|
|
func updateExerciceHint(hint fic.EHint, body []byte) (interface{}, error) {
|
|
var uh fic.EHint
|
|
if err := json.Unmarshal(body, &uh); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uh.Id = hint.Id
|
|
|
|
if len(uh.Title) == 0 {
|
|
return nil, errors.New("Hint's title not filled")
|
|
}
|
|
|
|
if _, err := uh.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return uh, nil
|
|
}
|
|
|
|
func deleteExerciceHint(hint fic.EHint, _ []byte) (interface{}, error) {
|
|
return hint.Delete()
|
|
}
|
|
|
|
type uploadedFlag struct {
|
|
Label string
|
|
Help string
|
|
IgnoreCase bool
|
|
ValidatorRe *string `json:"validator_regexp"`
|
|
Flag string
|
|
Value []byte
|
|
}
|
|
|
|
func createExerciceFlag(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uk uploadedFlag
|
|
if err := json.Unmarshal(body, &uk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(uk.Flag) == 0 {
|
|
return nil, errors.New("Flag not filled")
|
|
}
|
|
|
|
var vre *string = nil
|
|
if uk.ValidatorRe != nil && len(*uk.ValidatorRe) > 0 {
|
|
vre = uk.ValidatorRe
|
|
}
|
|
|
|
return exercice.AddRawFlag(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag))
|
|
}
|
|
|
|
func showExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
|
|
return flag, nil
|
|
}
|
|
|
|
func updateExerciceFlag(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uk uploadedFlag
|
|
if err := json.Unmarshal(body, &uk); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(uk.Label) == 0 {
|
|
flag.Label = "Flag"
|
|
} else {
|
|
flag.Label = uk.Label
|
|
}
|
|
|
|
flag.Help = uk.Help
|
|
flag.IgnoreCase = uk.IgnoreCase
|
|
flag.Checksum = uk.Value
|
|
|
|
if uk.ValidatorRe != nil && len(*uk.ValidatorRe) > 0 {
|
|
flag.ValidatorRegexp = uk.ValidatorRe
|
|
} else {
|
|
flag.ValidatorRegexp = nil
|
|
}
|
|
|
|
if _, err := flag.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return flag, nil
|
|
}
|
|
|
|
func deleteExerciceFlag(flag fic.Flag, _ fic.Exercice, _ []byte) (interface{}, error) {
|
|
return flag.Delete()
|
|
}
|
|
|
|
type uploadedChoice struct {
|
|
Label string
|
|
Value string
|
|
}
|
|
|
|
func createFlagChoice(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uc uploadedChoice
|
|
if err := json.Unmarshal(body, &uc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(uc.Label) == 0 {
|
|
uc.Label = uc.Value
|
|
}
|
|
|
|
return flag.AddChoice(uc.Label, uc.Value)
|
|
}
|
|
|
|
func showFlagChoice(choice fic.FlagChoice, _ fic.Exercice, body []byte) (interface{}, error) {
|
|
return choice, nil
|
|
}
|
|
|
|
func updateFlagChoice(choice fic.FlagChoice, _ fic.Exercice, body []byte) (interface{}, error) {
|
|
var uc uploadedChoice
|
|
if err := json.Unmarshal(body, &uc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(uc.Label) == 0 {
|
|
choice.Label = uc.Value
|
|
} else {
|
|
choice.Label = uc.Label
|
|
}
|
|
|
|
if _, err := choice.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return choice, nil
|
|
}
|
|
|
|
func deleteFlagChoice(choice fic.FlagChoice, _ fic.Exercice, _ []byte) (interface{}, error) {
|
|
return choice.Delete()
|
|
}
|
|
|
|
func showExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, body []byte) (interface{}, error) {
|
|
return quiz, nil
|
|
}
|
|
|
|
func updateExerciceQuiz(quiz fic.MCQ, exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uq fic.MCQ
|
|
if err := json.Unmarshal(body, &uq); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
quiz.Title = uq.Title
|
|
|
|
if _, err := quiz.Update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 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 {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if seen == false {
|
|
if _, err := cur.Delete(); err != nil {
|
|
return nil, err
|
|
} 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 c, err := quiz.AddEntry(choice.Label, choice.Response); err != nil {
|
|
return nil, err
|
|
} else {
|
|
quiz.Entries = append(quiz.Entries, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
return quiz, nil
|
|
}
|
|
|
|
func deleteExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, _ []byte) (interface{}, error) {
|
|
for _, choice := range quiz.Entries {
|
|
if _, err := choice.Delete(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return quiz.Delete()
|
|
}
|
|
|
|
type uploadedFile struct {
|
|
URI string
|
|
Digest string
|
|
}
|
|
|
|
func createExerciceFile(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var uf uploadedFile
|
|
if err := json.Unmarshal(body, &uf); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return sync.ImportFile(sync.GlobalImporter, uf.URI,
|
|
func(filePath string, origin string) (interface{}, error) {
|
|
if digest, err := hex.DecodeString(uf.Digest); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return exercice.ImportFile(filePath, origin, digest)
|
|
}
|
|
})
|
|
}
|
|
|
|
func showExerciceFile(file fic.EFile, body []byte) (interface{}, error) {
|
|
return file, nil
|
|
}
|
|
|
|
func deleteExerciceFile(file fic.EFile, _ []byte) (interface{}, error) {
|
|
return file.Delete()
|
|
}
|
|
|
|
|
|
func listExerciceTags(exercice fic.Exercice, _ []byte) (interface{}, error) {
|
|
return exercice.GetTags()
|
|
}
|
|
|
|
func addExerciceTag(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
var ut []string
|
|
if err := json.Unmarshal(body, &ut); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: a DB transaction should be done here: on error we should rollback
|
|
for _, t := range ut {
|
|
if _, err := exercice.AddTag(t); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return ut, nil
|
|
}
|
|
|
|
func updateExerciceTags(exercice fic.Exercice, body []byte) (interface{}, error) {
|
|
exercice.WipeTags()
|
|
return addExerciceTag(exercice, body)
|
|
}
|