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/resolutions.json", apiHandler(exportResolutionMovies)) 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(flagKeyHandler(showExerciceFlag))) router.PUT("/api/exercices/:eid/flags/:kid", apiHandler(flagKeyHandler(updateExerciceFlag))) router.POST("/api/exercices/:eid/flags/:kid/try", apiHandler(flagKeyHandler(tryExerciceFlag))) router.DELETE("/api/exercices/:eid/flags/:kid", apiHandler(flagKeyHandler(deleteExerciceFlag))) router.GET("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagKeyHandler(listFlagChoices))) router.GET("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(showFlagChoice))) router.POST("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagKeyHandler(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/themes/:thid/exercices/:eid", apiHandler(themedExerciceHandler( func(theme fic.Theme, exercice fic.Exercice, _ []byte) (interface{}, error) { _, _, errs := sync.SyncExercice(sync.GlobalImporter, theme, exercice.Path, nil) return errs, nil }))) 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() } // Generate the csv to export with: // curl -s http://127.0.0.1:8081/api/resolutions.json | jq -r ".[] | [ .theme,.title, @uri \"https://fic.srs.epita.fr/resolution/\\(.videoURI)\" ] | join(\";\")" func exportResolutionMovies(_ httprouter.Params, body []byte) (interface{}, error) { if exercices, err := fic.GetExercices(); err != nil { return nil, err } else { export := []map[string]string{} for _, exercice := range exercices { if theme, err := fic.GetTheme(exercice.IdTheme); err != nil { return nil, err } else { export = append(export, map[string]string{ "videoURI": exercice.VideoURI, "theme": theme.Name, "title": exercice.Title, }) } } return export, nil } } 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.GetFlagKeys() } func listFlagChoices(flag fic.FlagKey, _ 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.Headline) > 0 { exercice.Headline = ue.Headline } if len(ue.Finished) > 0 { exercice.Finished = ue.Finished } 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, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Finished) } 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 ChoicesCost int64 } 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.AddRawFlagKey(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag), uk.ChoicesCost) } func showExerciceFlag(flag fic.FlagKey, _ fic.Exercice, body []byte) (interface{}, error) { return flag, nil } func tryExerciceFlag(flag fic.FlagKey, _ 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("Empty submission") } if flag.Check([]byte(uk.Flag)) == 0 { return true, nil } else { return nil, errors.New("Bad submission") } } func updateExerciceFlag(flag fic.FlagKey, 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 if len(uk.Flag) > 0 { var err error flag.Checksum, err = flag.ComputeChecksum([]byte(uk.Flag)) if err != nil { return nil, err } } else { flag.Checksum = uk.Value } flag.ChoicesCost = uk.ChoicesCost 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.FlagKey, _ fic.Exercice, _ []byte) (interface{}, error) { return flag.Delete() } type uploadedChoice struct { Label string Value string } func createFlagChoice(flag fic.FlagKey, 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) }