7bb43b09b8994f765aea4e6b578e4feb104972fc
[fic/server.git] / admin / api / exercice.go
1 package api
2
3 import (
4         "encoding/hex"
5         "encoding/json"
6         "errors"
7         "strings"
8
9         "srs.epita.fr/fic-server/admin/sync"
10         "srs.epita.fr/fic-server/libfic"
11
12         "github.com/julienschmidt/httprouter"
13 )
14
15 func init() {
16         router.GET("/api/exercices/", apiHandler(listExercices))
17
18         router.GET("/api/exercices/:eid", apiHandler(exerciceHandler(showExercice)))
19         router.PUT("/api/exercices/:eid", apiHandler(exerciceHandler(updateExercice)))
20         router.PATCH("/api/exercices/:eid", apiHandler(exerciceHandler(partUpdateExercice)))
21         router.DELETE("/api/exercices/:eid", apiHandler(exerciceHandler(deleteExercice)))
22
23         router.GET("/api/exercices/:eid/files", apiHandler(exerciceHandler(listExerciceFiles)))
24         router.POST("/api/exercices/:eid/files", apiHandler(exerciceHandler(createExerciceFile)))
25         router.GET("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(showExerciceFile)))
26         router.DELETE("/api/exercices/:eid/files/:fid", apiHandler(exerciceFileHandler(deleteExerciceFile)))
27
28         router.GET("/api/exercices/:eid/hints", apiHandler(exerciceHandler(listExerciceHints)))
29         router.POST("/api/exercices/:eid/hints", apiHandler(exerciceHandler(createExerciceHint)))
30         router.GET("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(showExerciceHint)))
31         router.PUT("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(updateExerciceHint)))
32         router.DELETE("/api/exercices/:eid/hints/:hid", apiHandler(hintHandler(deleteExerciceHint)))
33
34         router.GET("/api/exercices/:eid/flags", apiHandler(exerciceHandler(listExerciceFlags)))
35         router.POST("/api/exercices/:eid/flags", apiHandler(exerciceHandler(createExerciceFlag)))
36         router.GET("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(showExerciceFlag)))
37         router.PUT("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(updateExerciceFlag)))
38         router.POST("/api/exercices/:eid/flags/:kid/try", apiHandler(flagHandler(tryExerciceFlag)))
39         router.DELETE("/api/exercices/:eid/flags/:kid", apiHandler(flagHandler(deleteExerciceFlag)))
40         router.GET("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(listFlagChoices)))
41         router.GET("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(showFlagChoice)))
42         router.POST("/api/exercices/:eid/flags/:kid/choices/", apiHandler(flagHandler(createFlagChoice)))
43         router.PUT("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(updateFlagChoice)))
44         router.DELETE("/api/exercices/:eid/flags/:kid/choices/:cid", apiHandler(choiceHandler(deleteFlagChoice)))
45
46         router.GET("/api/exercices/:eid/quiz", apiHandler(exerciceHandler(listExerciceQuiz)))
47         router.GET("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(showExerciceQuiz)))
48         router.PUT("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(updateExerciceQuiz)))
49         router.DELETE("/api/exercices/:eid/quiz/:qid", apiHandler(quizHandler(deleteExerciceQuiz)))
50
51         router.GET("/api/exercices/:eid/tags", apiHandler(exerciceHandler(listExerciceTags)))
52         router.POST("/api/exercices/:eid/tags", apiHandler(exerciceHandler(addExerciceTag)))
53         router.PUT("/api/exercices/:eid/tags", apiHandler(exerciceHandler(updateExerciceTags)))
54
55         // Synchronize
56         router.POST("/api/sync/themes/:thid/exercices/:eid", apiHandler(themedExerciceHandler(
57                 func(theme fic.Theme, exercice fic.Exercice, _ []byte) (interface{}, error) {
58                         _, _, errs := sync.SyncExercice(sync.GlobalImporter, theme, exercice.Path, nil)
59                         return errs, nil
60                 })))
61         router.POST("/api/sync/exercices/:eid/files", apiHandler(exerciceHandler(
62                 func(exercice fic.Exercice, _ []byte) (interface{}, error) {
63                         return sync.SyncExerciceFiles(sync.GlobalImporter, exercice), nil
64                 })))
65         router.POST("/api/sync/exercices/:eid/hints", apiHandler(exerciceHandler(
66                 func(exercice fic.Exercice, _ []byte) (interface{}, error) {
67                         return sync.SyncExerciceHints(sync.GlobalImporter, exercice), nil
68                 })))
69         router.POST("/api/sync/exercices/:eid/flags", apiHandler(exerciceHandler(
70                 func(exercice fic.Exercice, _ []byte) (interface{}, error) {
71                         return sync.SyncExerciceFlags(sync.GlobalImporter, exercice), nil
72                 })))
73
74         router.POST("/api/sync/exercices/:eid/fixurlid", apiHandler(exerciceHandler(
75                 func(exercice fic.Exercice, _ []byte) (interface{}, error) {
76                         if exercice.FixURLId() {
77                                 return exercice.Update()
78                         }
79                         return 0, nil
80                 })))
81 }
82
83 func listExercices(_ httprouter.Params, body []byte) (interface{}, error) {
84         // List all exercices
85         return fic.GetExercices()
86 }
87
88 func listExerciceFiles(exercice fic.Exercice, body []byte) (interface{}, error) {
89         return exercice.GetFiles()
90 }
91
92 func listExerciceHints(exercice fic.Exercice, body []byte) (interface{}, error) {
93         return exercice.GetHints()
94 }
95
96 func listExerciceFlags(exercice fic.Exercice, body []byte) (interface{}, error) {
97         return exercice.GetFlags()
98 }
99
100 func listFlagChoices(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
101         return flag.GetChoices()
102 }
103
104 func listExerciceQuiz(exercice fic.Exercice, body []byte) (interface{}, error) {
105         return exercice.GetMCQ()
106 }
107
108 func showExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
109         return exercice, nil
110 }
111
112 func deleteExercice(exercice fic.Exercice, _ []byte) (interface{}, error) {
113         return exercice.Delete()
114 }
115
116 func updateExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
117         var ue fic.Exercice
118         if err := json.Unmarshal(body, &ue); err != nil {
119                 return nil, err
120         }
121
122         ue.Id = exercice.Id
123
124         if len(ue.Title) == 0 {
125                 return nil, errors.New("Exercice's title not filled")
126         }
127
128         if _, err := ue.Update(); err != nil {
129                 return nil, err
130         }
131
132         return ue, nil
133 }
134
135 func partUpdateExercice(exercice fic.Exercice, body []byte) (interface{}, error) {
136         var ue fic.Exercice
137         if err := json.Unmarshal(body, &ue); err != nil {
138                 return nil, err
139         }
140
141         if len(ue.Title) > 0 {
142                 exercice.Title = ue.Title
143         }
144
145         if len(ue.URLId) > 0 {
146                 exercice.URLId = ue.URLId
147         }
148
149         if len(ue.Statement) > 0 {
150                 exercice.Statement = ue.Statement
151         }
152
153         if len(ue.Headline) > 0 {
154                 exercice.Headline = ue.Headline
155         }
156
157         if len(ue.Finished) > 0 {
158                 exercice.Finished = ue.Finished
159         }
160
161         if len(ue.Overview) > 0 {
162                 exercice.Overview = ue.Overview
163         }
164
165         if len(ue.Issue) > 0 {
166                 exercice.Issue = ue.Issue
167         }
168
169         if len(ue.IssueKind) > 0 {
170                 exercice.IssueKind = ue.IssueKind
171         }
172
173         if ue.Depend != nil {
174                 exercice.Depend = ue.Depend
175         }
176
177         if ue.Gain != 0 {
178                 exercice.Gain = ue.Gain
179         }
180
181         if ue.Coefficient != 0 {
182                 exercice.Coefficient = ue.Coefficient
183         }
184
185         if len(ue.VideoURI) > 0 {
186                 exercice.VideoURI = ue.VideoURI
187         }
188
189         if _, err := exercice.Update(); err != nil {
190                 return nil, err
191         }
192
193         return exercice, nil
194 }
195
196 func createExercice(theme fic.Theme, body []byte) (interface{}, error) {
197         // Create a new exercice
198         var ue fic.Exercice
199         if err := json.Unmarshal(body, &ue); err != nil {
200                 return nil, err
201         }
202
203         if len(ue.Title) == 0 {
204                 return nil, errors.New("Title not filled")
205         }
206
207         var depend *fic.Exercice = nil
208         if ue.Depend != nil {
209                 if d, err := fic.GetExercice(*ue.Depend); err != nil {
210                         return nil, err
211                 } else {
212                         depend = &d
213                 }
214         }
215
216         return theme.AddExercice(ue.Title, ue.URLId, ue.Path, ue.Statement, ue.Overview, ue.Headline, depend, ue.Gain, ue.VideoURI, ue.Finished)
217 }
218
219 type uploadedHint struct {
220         Title   string
221         Path    string
222         Content string
223         Cost    int64
224         URI     string
225 }
226
227 func createExerciceHint(exercice fic.Exercice, body []byte) (interface{}, error) {
228         var uh uploadedHint
229         if err := json.Unmarshal(body, &uh); err != nil {
230                 return nil, err
231         }
232
233         if len(uh.Content) != 0 {
234                 return exercice.AddHint(uh.Title, uh.Content, uh.Cost)
235         } else if len(uh.URI) != 0 {
236                 return sync.ImportFile(sync.GlobalImporter, uh.URI,
237                         func(filePath string, origin string) (interface{}, error) {
238                                 return exercice.AddHint(uh.Title, "$FILES"+strings.TrimPrefix(filePath, fic.FilesDir), uh.Cost)
239                         })
240         } else {
241                 return nil, errors.New("Hint's content not filled")
242         }
243 }
244
245 func showExerciceHint(hint fic.EHint, body []byte) (interface{}, error) {
246         return hint, nil
247 }
248
249 func updateExerciceHint(hint fic.EHint, body []byte) (interface{}, error) {
250         var uh fic.EHint
251         if err := json.Unmarshal(body, &uh); err != nil {
252                 return nil, err
253         }
254
255         uh.Id = hint.Id
256
257         if len(uh.Title) == 0 {
258                 return nil, errors.New("Hint's title not filled")
259         }
260
261         if _, err := uh.Update(); err != nil {
262                 return nil, err
263         }
264
265         return uh, nil
266 }
267
268 func deleteExerciceHint(hint fic.EHint, _ []byte) (interface{}, error) {
269         return hint.Delete()
270 }
271
272 type uploadedFlag struct {
273         Label       string
274         Help        string
275         IgnoreCase  bool
276         ValidatorRe *string `json:"validator_regexp"`
277         Flag        string
278         Value       []byte
279         ChoicesCost int64
280 }
281
282 func createExerciceFlag(exercice fic.Exercice, body []byte) (interface{}, error) {
283         var uk uploadedFlag
284         if err := json.Unmarshal(body, &uk); err != nil {
285                 return nil, err
286         }
287
288         if len(uk.Flag) == 0 {
289                 return nil, errors.New("Flag not filled")
290         }
291
292         var vre *string = nil
293         if uk.ValidatorRe != nil && len(*uk.ValidatorRe) > 0 {
294                 vre = uk.ValidatorRe
295         }
296
297         return exercice.AddRawFlag(uk.Label, uk.Help, uk.IgnoreCase, vre, []byte(uk.Flag), uk.ChoicesCost)
298 }
299
300 func showExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
301         return flag, nil
302 }
303
304 func tryExerciceFlag(flag fic.Flag, _ fic.Exercice, body []byte) (interface{}, error) {
305         var uk uploadedFlag
306         if err := json.Unmarshal(body, &uk); err != nil {
307                 return nil, err
308         }
309
310         if len(uk.Flag) == 0 {
311                 return nil, errors.New("Empty submission")
312         }
313
314         if flag.Check([]byte(uk.Flag)) {
315                 return true, nil
316         } else {
317                 return nil, errors.New("Bad submission")
318         }
319 }
320
321 func updateExerciceFlag(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
322         var uk uploadedFlag
323         if err := json.Unmarshal(body, &uk); err != nil {
324                 return nil, err
325         }
326
327         if len(uk.Label) == 0 {
328                 flag.Label = "Flag"
329         } else {
330                 flag.Label = uk.Label
331         }
332
333         flag.Help = uk.Help
334         flag.IgnoreCase = uk.IgnoreCase
335         flag.Checksum = uk.Value
336         flag.ChoicesCost = uk.ChoicesCost
337
338         if uk.ValidatorRe != nil && len(*uk.ValidatorRe) > 0 {
339                 flag.ValidatorRegexp = uk.ValidatorRe
340         } else {
341                 flag.ValidatorRegexp = nil
342         }
343
344         if _, err := flag.Update(); err != nil {
345                 return nil, err
346         }
347
348         return flag, nil
349 }
350
351 func deleteExerciceFlag(flag fic.Flag, _ fic.Exercice, _ []byte) (interface{}, error) {
352         return flag.Delete()
353 }
354
355 type uploadedChoice struct {
356         Label       string
357         Value       string
358 }
359
360 func createFlagChoice(flag fic.Flag, exercice fic.Exercice, body []byte) (interface{}, error) {
361         var uc uploadedChoice
362         if err := json.Unmarshal(body, &uc); err != nil {
363                 return nil, err
364         }
365
366         if len(uc.Label) == 0 {
367                 uc.Label = uc.Value
368         }
369
370         return flag.AddChoice(uc.Label, uc.Value)
371 }
372
373 func showFlagChoice(choice fic.FlagChoice, _ fic.Exercice, body []byte) (interface{}, error) {
374         return choice, nil
375 }
376
377 func updateFlagChoice(choice fic.FlagChoice, _ fic.Exercice, body []byte) (interface{}, error) {
378         var uc uploadedChoice
379         if err := json.Unmarshal(body, &uc); err != nil {
380                 return nil, err
381         }
382
383         if len(uc.Label) == 0 {
384                 choice.Label = uc.Value
385         } else {
386                 choice.Label = uc.Label
387         }
388
389         if _, err := choice.Update(); err != nil {
390                 return nil, err
391         }
392
393         return choice, nil
394 }
395
396 func deleteFlagChoice(choice fic.FlagChoice, _ fic.Exercice, _ []byte) (interface{}, error) {
397         return choice.Delete()
398 }
399
400 func showExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, body []byte) (interface{}, error) {
401         return quiz, nil
402 }
403
404 func updateExerciceQuiz(quiz fic.MCQ, exercice fic.Exercice, body []byte) (interface{}, error) {
405         var uq fic.MCQ
406         if err := json.Unmarshal(body, &uq); err != nil {
407                 return nil, err
408         }
409
410         quiz.Title = uq.Title
411
412         if _, err := quiz.Update(); err != nil {
413                 return nil, err
414         }
415
416         // Update and remove old entries
417         var delete []int
418         for i, cur := range quiz.Entries {
419                 seen := false
420                 for _, next := range uq.Entries {
421                         if cur.Id == next.Id {
422                                 seen = true
423
424                                 if cur.Label != next.Label || cur.Response != next.Response {
425                                         cur.Label = next.Label
426                                         cur.Response = next.Response
427                                         if _, err := cur.Update(); err != nil {
428                                                 return nil, err
429                                         }
430                                 }
431
432                                 break
433                         }
434                 }
435
436                 if seen == false {
437                         if _, err := cur.Delete(); err != nil {
438                                 return nil, err
439                         } else {
440                                 delete = append(delete, i)
441                         }
442                 }
443         }
444         for n, i := range delete {
445                 quiz.Entries = append(quiz.Entries[:i-n-1], quiz.Entries[:i-n+1]...)
446         }
447
448         // Add new choices
449         for _, choice := range uq.Entries {
450                 if choice.Id == 0 {
451                         if c, err := quiz.AddEntry(choice.Label, choice.Response); err != nil {
452                                 return nil, err
453                         } else {
454                                 quiz.Entries = append(quiz.Entries, c)
455                         }
456                 }
457         }
458
459         return quiz, nil
460 }
461
462 func deleteExerciceQuiz(quiz fic.MCQ, _ fic.Exercice, _ []byte) (interface{}, error) {
463         for _, choice := range quiz.Entries {
464                 if _, err := choice.Delete(); err != nil {
465                         return nil, err
466                 }
467         }
468
469         return quiz.Delete()
470 }
471
472 type uploadedFile struct {
473         URI    string
474         Digest string
475 }
476
477 func createExerciceFile(exercice fic.Exercice, body []byte) (interface{}, error) {
478         var uf uploadedFile
479         if err := json.Unmarshal(body, &uf); err != nil {
480                 return nil, err
481         }
482
483         return sync.ImportFile(sync.GlobalImporter, uf.URI,
484                 func(filePath string, origin string) (interface{}, error) {
485                         if digest, err := hex.DecodeString(uf.Digest); err != nil {
486                                 return nil, err
487                         } else {
488                                 return exercice.ImportFile(filePath, origin, digest)
489                         }
490                 })
491 }
492
493 func showExerciceFile(file fic.EFile, body []byte) (interface{}, error) {
494         return file, nil
495 }
496
497 func deleteExerciceFile(file fic.EFile, _ []byte) (interface{}, error) {
498         return file.Delete()
499 }
500
501
502 func listExerciceTags(exercice fic.Exercice, _ []byte) (interface{}, error) {
503         return exercice.GetTags()
504 }
505
506 func addExerciceTag(exercice fic.Exercice, body []byte) (interface{}, error) {
507         var ut []string
508         if err := json.Unmarshal(body, &ut); err != nil {
509                 return nil, err
510         }
511
512         // TODO: a DB transaction should be done here: on error we should rollback
513         for _, t := range ut {
514                 if _, err := exercice.AddTag(t); err != nil {
515                         return nil, err
516                 }
517         }
518
519         return ut, nil
520 }
521
522 func updateExerciceTags(exercice fic.Exercice, body []byte) (interface{}, error) {
523         exercice.WipeTags()
524         return addExerciceTag(exercice, body)
525 }