diff --git a/admin/api/exercice.go b/admin/api/exercice.go index 024fd8b1..afe6b8d7 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -1,11 +1,9 @@ package api import ( - "bytes" "fmt" "log" "net/http" - "path" "reflect" "strconv" "strings" @@ -34,8 +32,6 @@ func declareExercicesRoutes(router *gin.RouterGroup) { apiExercicesRoutes.PATCH("", partUpdateExercice) apiExercicesRoutes.DELETE("", deleteExercice) - apiExercicesRoutes.POST("/diff-sync", APIDiffExerciceWithRemote) - apiExercicesRoutes.GET("/stats.json", getExerciceStats) apiExercicesRoutes.GET("/history.json", getExerciceHistory) @@ -95,8 +91,8 @@ func declareExercicesRoutes(router *gin.RouterGroup) { // Remote router.GET("/remote/themes/:thid/exercices/:exid", sync.ApiGetRemoteExercice) - router.GET("/remote/themes/:thid/exercices/:exid/flags", sync.ApiGetRemoteExerciceFlags) router.GET("/remote/themes/:thid/exercices/:exid/hints", sync.ApiGetRemoteExerciceHints) + router.GET("/remote/themes/:thid/exercices/:exid/flags", sync.ApiGetRemoteExerciceFlags) } type Exercice struct { @@ -134,7 +130,7 @@ func ExerciceHandler(c *gin.Context) { c.Set("theme", theme) } else { - c.Set("theme", &fic.StandaloneExercicesTheme) + c.Set("theme", &fic.Theme{Path: sync.StandaloneExercicesDirectory}) } } @@ -1279,343 +1275,3 @@ func updateExerciceTags(c *gin.Context) { exercice.WipeTags() addExerciceTag(c) } - -type syncDiff struct { - Field string `json:"field"` - Link string `json:"link"` - Before interface{} `json:"be"` - After interface{} `json:"af"` -} - -func diffExerciceWithRemote(exercice *fic.Exercice, theme *fic.Theme) ([]syncDiff, error) { - var diffs []syncDiff - - // Compare exercice attributes - thid := exercice.Path[:strings.Index(exercice.Path, "/")] - exid := exercice.Path[strings.Index(exercice.Path, "/")+1:] - exercice_remote, err := sync.GetRemoteExercice(thid, exid, theme) - if err != nil { - return nil, err - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*exercice)) { - if ((field.Name == "Image") && path.Base(reflect.ValueOf(*exercice_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*exercice).FieldByName(field.Name).String())) || ((field.Name == "Depend") && (((exercice_remote.Depend == nil || exercice.Depend == nil) && exercice.Depend != exercice_remote.Depend) || (exercice_remote.Depend != nil && exercice.Depend != nil && *exercice.Depend != *exercice_remote.Depend))) || (field.Name != "Image" && field.Name != "Depend" && !reflect.ValueOf(*exercice_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*exercice).FieldByName(field.Name))) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdTheme" || field.Name == "IssueKind" || field.Name == "Coefficient" || field.Name == "BackgroundColor" { - continue - } - - diffs = append(diffs, syncDiff{ - Field: field.Name, - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*exercice).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*exercice_remote).FieldByName(field.Name).Interface(), - }) - } - } - - // Compare files - files, err := exercice.GetFiles() - if err != nil { - return nil, fmt.Errorf("Unable to GetFiles: %w", err) - } - - files_remote, err := sync.GetRemoteExerciceFiles(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteFiles: %w", err) - } - - for i, file_remote := range files_remote { - if len(files) <= i { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("files[%d]", i), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: nil, - After: file_remote, - }) - continue - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*file_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if ((field.Name == "Path") && path.Base(reflect.ValueOf(*file_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*files[i]).FieldByName(field.Name).String())) || ((field.Name == "Checksum" || field.Name == "ChecksumShown") && !bytes.Equal(reflect.ValueOf(*file_remote).FieldByName(field.Name).Bytes(), reflect.ValueOf(*files[i]).FieldByName(field.Name).Bytes())) || (field.Name != "Checksum" && field.Name != "ChecksumShown" && field.Name != "Path" && !reflect.ValueOf(*file_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*files[i]).FieldByName(field.Name))) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("files[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*files[i]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*file_remote).FieldByName(field.Name).Interface(), - }) - } - } - } - - // Compare flags - flags, err := exercice.GetFlags() - if err != nil { - return nil, fmt.Errorf("Unable to GetFlags: %w", err) - } - - flags_remote, err := sync.GetRemoteExerciceFlags(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteFlags: %w", err) - } - - var flags_not_found []interface{} - var flags_extra_found []interface{} - - for i, flag_remote := range flags_remote { - if key_remote, ok := flag_remote.(*fic.FlagKey); ok { - found := false - - for _, flag := range flags { - if key, ok := flag.(*fic.FlagKey); ok && (key.Label == key_remote.Label || key.Order == key_remote.Order) { - found = true - - // Parse flag label - if len(key.Label) > 3 && key.Label[0] == '%' { - spl := strings.Split(key.Label, "%") - key.Label = strings.Join(spl[2:], "%") - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*key_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if (field.Name == "Checksum" && !bytes.Equal(key.Checksum, key_remote.Checksum)) || (field.Name == "CaptureRegexp" && ((key.CaptureRegexp == nil || key_remote.CaptureRegexp == nil) && key.CaptureRegexp != key_remote.CaptureRegexp) || (key.CaptureRegexp != nil && key_remote.CaptureRegexp != nil && *key.CaptureRegexp != *key_remote.CaptureRegexp)) || (field.Name != "Checksum" && field.Name != "CaptureRegexp" && !reflect.ValueOf(*key_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*key).FieldByName(field.Name))) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#flag-%d", exercice.Id, key.Id), - Before: reflect.ValueOf(*key).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*key_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, key_remote) - } - } else if mcq_remote, ok := flag_remote.(*fic.MCQ); ok { - found := false - - for _, flag := range flags { - if mcq, ok := flag.(*fic.MCQ); ok && (mcq.Title == mcq_remote.Title || mcq.Order == mcq_remote.Order) { - found = true - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*mcq_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if field.Name == "Entries" { - var not_found []*fic.MCQ_entry - var extra_found []*fic.MCQ_entry - - for i, entry_remote := range mcq_remote.Entries { - found := false - - for j, entry := range mcq.Entries { - if entry.Label == entry_remote.Label { - for _, field := range reflect.VisibleFields(reflect.TypeOf(*entry_remote)) { - if field.Name == "Id" { - continue - } - - if !reflect.ValueOf(*entry_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*entry).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].entries[%d].%s", i, j, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#quiz-%d", exercice.Id, mcq.Id), - Before: reflect.ValueOf(*mcq.Entries[j]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*entry_remote).FieldByName(field.Name).Interface(), - }) - } - } - - found = true - break - } - } - - if !found { - not_found = append(not_found, entry_remote) - } - } - - for _, entry := range mcq.Entries { - found := false - for _, entry_remote := range mcq_remote.Entries { - if entry.Label == entry_remote.Label { - found = true - break - } - } - - if !found { - extra_found = append(extra_found, entry) - } - } - - if len(not_found) > 0 || len(extra_found) > 0 { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].entries", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: extra_found, - After: not_found, - }) - } - } else if !reflect.ValueOf(*mcq_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*mcq).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: reflect.ValueOf(*mcq).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*mcq_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, mcq_remote) - } - } else if label_remote, ok := flag_remote.(*fic.FlagLabel); ok { - found := false - - for _, flag := range flags { - if label, ok := flag.(*fic.FlagLabel); ok && (label.Label == label_remote.Label || label.Order == label_remote.Order) { - found = true - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*label_remote)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if !reflect.ValueOf(*label_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*label).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("flags[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d/flags#flag-%d", exercice.Id, label.Id), - Before: reflect.ValueOf(*label).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*label_remote).FieldByName(field.Name).Interface(), - }) - } - } - - break - } - } - - if !found { - flags_not_found = append(flags_not_found, label_remote) - } - } else { - log.Printf("unknown flag type: %T", flag_remote) - } - } - - for _, flag := range flags { - if key, ok := flag.(*fic.FlagKey); ok { - found := false - - for _, flag_remote := range flags_remote { - if key_remote, ok := flag_remote.(*fic.FlagKey); ok && (key.Label == key_remote.Label || key.Order == key_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } else if mcq, ok := flag.(*fic.MCQ); ok { - found := false - - for _, flag_remote := range flags_remote { - if mcq_remote, ok := flag_remote.(*fic.MCQ); ok && (mcq.Title == mcq_remote.Title || mcq.Order == mcq_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } else if label, ok := flag.(*fic.FlagLabel); ok { - found := false - - for _, flag_remote := range flags_remote { - if label_remote, ok := flag_remote.(*fic.FlagLabel); ok && (label.Label == label_remote.Label || label.Order == label_remote.Order) { - found = true - break - } - } - - if !found { - flags_extra_found = append(flags_extra_found, flag) - } - } - } - - if len(flags_not_found) > 0 || len(flags_extra_found) > 0 { - diffs = append(diffs, syncDiff{ - Field: "flags", - Link: fmt.Sprintf("exercices/%d/flags", exercice.Id), - Before: flags_extra_found, - After: flags_not_found, - }) - } - - // Compare hints - hints, err := exercice.GetHints() - if err != nil { - return nil, fmt.Errorf("Unable to GetHints: %w", err) - } - - hints_remote, err := sync.GetRemoteExerciceHints(thid, exid) - if err != nil { - return nil, fmt.Errorf("Unable to GetRemoteHints: %w", err) - } - - for i, hint_remote := range hints_remote { - hint_remote.Hint.TreatHintContent() - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*hint_remote.Hint)) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdExercice" { - continue - } - if len(hints) <= i { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("hints[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: nil, - After: reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Interface(), - }) - } else if !reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Equal(reflect.ValueOf(*hints[i]).FieldByName(field.Name)) { - diffs = append(diffs, syncDiff{ - Field: fmt.Sprintf("hints[%d].%s", i, field.Name), - Link: fmt.Sprintf("exercices/%d", exercice.Id), - Before: reflect.ValueOf(*hints[i]).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*hint_remote.Hint).FieldByName(field.Name).Interface(), - }) - } - } - } - - return diffs, err -} - -func APIDiffExerciceWithRemote(c *gin.Context) { - theme := c.MustGet("theme").(*fic.Theme) - exercice := c.MustGet("exercice").(*fic.Exercice) - - diffs, err := diffExerciceWithRemote(exercice, theme) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, diffs) -} diff --git a/admin/api/settings.go b/admin/api/settings.go index c16d698d..0ea7f0ea 100644 --- a/admin/api/settings.go +++ b/admin/api/settings.go @@ -310,7 +310,6 @@ func ApplySettings(config *settings.Settings) { fic.SubmissionCostBase = config.SubmissionCostBase fic.SubmissionUniqueness = config.SubmissionUniqueness fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries - fic.QuestionGainRatio = config.QuestionGainRatio if config.DiscountedFactor != fic.DiscountedFactor { fic.DiscountedFactor = config.DiscountedFactor @@ -330,7 +329,6 @@ func ResetSettings() error { WChoiceCurCoefficient: 1, GlobalScoreCoefficient: 1, DiscountedFactor: 0, - QuestionGainRatio: 0, UnlockedStandaloneExercices: 10, UnlockedStandaloneExercicesByThemeStepValidation: 1, UnlockedStandaloneExercicesByStandaloneExerciceValidation: 0, diff --git a/admin/api/sync.go b/admin/api/sync.go index 225b6e96..0bcba713 100644 --- a/admin/api/sync.go +++ b/admin/api/sync.go @@ -79,14 +79,12 @@ func declareSyncRoutes(router *gin.RouterGroup) { c.JSON(http.StatusOK, r) }) - apiSyncRoutes.POST("/local-diff", APIDiffDBWithRemote) - apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid") apiSyncDeepRoutes.Use(ThemeHandler) // Special route to handle standalone exercices apiSyncRoutes.POST("/deep/0", func(c *gin.Context) { var st []string - for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, &fic.StandaloneExercicesTheme, 0, 250, nil)) { + for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, &fic.Theme{Path: sync.StandaloneExercicesDirectory}, 0, 250, nil)) { st = append(st, se.Error()) } sync.EditDeepReport(&sync.SyncReport{Exercices: st}, false) @@ -380,32 +378,3 @@ func autoSync(c *gin.Context) { c.JSON(http.StatusOK, st) } - -func diffDBWithRemote() (map[string][]syncDiff, error) { - diffs := map[string][]syncDiff{} - - themes, err := fic.GetThemesExtended() - if err != nil { - return nil, err - } - - // Compare inner themes - for _, theme := range themes { - diffs[theme.Name], err = diffThemeWithRemote(theme) - if err != nil { - return nil, fmt.Errorf("Unable to diffThemeWithRemote: %w", err) - } - } - - return diffs, err -} - -func APIDiffDBWithRemote(c *gin.Context) { - diffs, err := diffDBWithRemote() - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, diffs) -} diff --git a/admin/api/theme.go b/admin/api/theme.go index c1057397..37668ffd 100644 --- a/admin/api/theme.go +++ b/admin/api/theme.go @@ -5,9 +5,7 @@ import ( "log" "net/http" "path" - "reflect" "strconv" - "strings" "srs.epita.fr/fic-server/admin/sync" "srs.epita.fr/fic-server/libfic" @@ -50,8 +48,6 @@ func declareThemesRoutes(router *gin.RouterGroup) { apiThemesRoutes.PUT("", updateTheme) apiThemesRoutes.DELETE("", deleteTheme) - apiThemesRoutes.POST("/diff-sync", APIDiffThemeWithRemote) - apiThemesRoutes.GET("/exercices_stats.json", getThemedExercicesStats) declareExercicesRoutes(apiThemesRoutes) @@ -74,18 +70,14 @@ func ThemeHandler(c *gin.Context) { return } - if thid == 0 { - c.Set("theme", &fic.StandaloneExercicesTheme) - } else { - theme, err := fic.GetTheme(thid) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"}) - return - } - - c.Set("theme", theme) + theme, err := fic.GetTheme(thid) + if err != nil { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"}) + return } + c.Set("theme", theme) + c.Next() } @@ -135,10 +127,6 @@ func listThemes(c *gin.Context) { return } - if has, _ := fic.HasStandaloneExercice(); has { - themes = append([]*fic.Theme{&fic.StandaloneExercicesTheme}, themes...) - } - c.JSON(http.StatusOK, themes) } @@ -267,110 +255,3 @@ func getThemedExercicesStats(c *gin.Context) { } c.JSON(http.StatusOK, ret) } - -func diffThemeWithRemote(theme *fic.Theme) ([]syncDiff, error) { - var diffs []syncDiff - - // Compare theme attributes - theme_remote, err := sync.GetRemoteTheme(theme.Path) - if err != nil { - return nil, err - } - - for _, field := range reflect.VisibleFields(reflect.TypeOf(*theme)) { - if ((field.Name == "Image") && path.Base(reflect.ValueOf(*theme_remote).FieldByName(field.Name).String()) != path.Base(reflect.ValueOf(*theme).FieldByName(field.Name).String())) || (field.Name != "Image" && !reflect.ValueOf(*theme_remote).FieldByName(field.Name).Equal(reflect.ValueOf(*theme).FieldByName(field.Name))) { - if !field.IsExported() || field.Name == "Id" || field.Name == "IdTheme" || field.Name == "IssueKind" || field.Name == "BackgroundColor" { - continue - } - - diffs = append(diffs, syncDiff{ - Field: field.Name, - Link: fmt.Sprintf("themes/%d", theme.Id), - Before: reflect.ValueOf(*theme).FieldByName(field.Name).Interface(), - After: reflect.ValueOf(*theme_remote).FieldByName(field.Name).Interface(), - }) - } - } - - // Compare exercices list - exercices, err := theme.GetExercices() - if err != nil { - return nil, fmt.Errorf("Unable to GetExercices: %w", err) - } - - exercices_remote, err := sync.ListRemoteExercices(theme.Path) - if err != nil { - return nil, fmt.Errorf("Unable to ListRemoteExercices: %w", err) - } - - var not_found []string - var extra_found []string - - for _, exercice_remote := range exercices_remote { - found := false - for _, exercice := range exercices { - if exercice.Path[strings.Index(exercice.Path, "/")+1:] == exercice_remote { - found = true - break - } - } - - if !found { - not_found = append(not_found, exercice_remote) - } - } - - for _, exercice := range exercices { - found := false - for _, exercice_remote := range exercices_remote { - if exercice.Path[strings.Index(exercice.Path, "/")+1:] == exercice_remote { - found = true - break - } - } - - if !found { - extra_found = append(extra_found, exercice.Path[strings.Index(exercice.Path, "/")+1:]) - } - } - - if len(not_found) > 0 || len(extra_found) > 0 { - diffs = append(diffs, syncDiff{ - Field: "theme.Exercices", - Link: fmt.Sprintf("themes/%d", theme.Id), - Before: strings.Join(extra_found, ", "), - After: strings.Join(not_found, ", "), - }) - } - - // Compare inner exercices - for i, exercice := range exercices { - exdiffs, err := diffExerciceWithRemote(exercice, theme) - if err != nil { - return nil, fmt.Errorf("Unable to diffExerciceWithRemote: %w", err) - } - - for _, exdiff := range exdiffs { - if theme.Id == 0 { - exdiff.Field = fmt.Sprintf("exercices[%d].%s", exercice.Id, exdiff.Field) - } else { - exdiff.Field = fmt.Sprintf("exercices[%d].%s", i, exdiff.Field) - } - diffs = append(diffs, exdiff) - } - } - - return diffs, err -} - -func APIDiffThemeWithRemote(c *gin.Context) { - theme := c.MustGet("theme").(*fic.Theme) - - diffs, err := diffThemeWithRemote(theme) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) - return - } - - c.JSON(http.StatusOK, diffs) -} diff --git a/admin/main.go b/admin/main.go index 06470166..47d31c72 100644 --- a/admin/main.go +++ b/admin/main.go @@ -199,10 +199,10 @@ func main() { } log.Println("Using", sync.GlobalImporter.Kind()) - challengeinfo, err := sync.GetFileContent(sync.GlobalImporter, settings.ChallengeFile) - if err == nil { - // Initial distribution of challenge.json - if _, err := os.Stat(path.Join(settings.SettingsDir, settings.ChallengeFile)); os.IsNotExist(err) { + // Update distributed challenge.json + if _, err := os.Stat(path.Join(settings.SettingsDir, settings.ChallengeFile)); os.IsNotExist(err) { + challengeinfo, err := sync.GetFileContent(sync.GlobalImporter, settings.ChallengeFile) + if err == nil { if fd, err := os.Create(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil { log.Fatal("Unable to open SETTINGS/challenge.json:", err) } else { @@ -213,10 +213,6 @@ func main() { } } } - - if ci, err := settings.ReadChallengeInfo(challengeinfo); err == nil { - fic.StandaloneExercicesTheme.Authors = ci.Authors - } } } diff --git a/admin/static.go b/admin/static.go index 82772993..3a2bd877 100644 --- a/admin/static.go +++ b/admin/static.go @@ -127,6 +127,7 @@ func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseUR if st, err := os.Stat(filepath); os.IsNotExist(err) || st.Size() == 0 { if st, err := os.Stat(filepath + ".gz"); err == nil { if fd, err := os.Open(filepath + ".gz"); err == nil { + log.Println(filepath + ".gz") c.DataFromReader(http.StatusOK, st.Size(), "application/octet-stream", fd, map[string]string{ "Content-Encoding": "gzip", }) @@ -135,6 +136,7 @@ func declareStaticRoutes(router *gin.RouterGroup, cfg *settings.Settings, baseUR } } + log.Println(filepath) c.File(filepath) }) router.GET("/submissions/*_", func(c *gin.Context) { diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 19a3f518..315c9a10 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -923,21 +923,6 @@ angular.module("FICApp") }); }); }; - $scope.diffWithRepo = function () { - $scope.diff = null; - $http({ - url: "api/sync/local-diff", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; }) .controller("AuthController", function ($scope, $http) { @@ -1834,21 +1819,6 @@ angular.module("FICApp") $scope.addToast('danger', 'An error occurs when trying to delete theme:', response.data.errmsg); }); } - $scope.checkExoSync = function () { - $scope.diff = null; - $http({ - url: "api/themes/" + $scope.theme.id + "/diff-sync", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; }) .controller("TagsListController", function ($scope, $http) { @@ -2028,21 +1998,6 @@ angular.module("FICApp") $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); }); }; - $scope.checkExoSync = function () { - $scope.diff = null; - $http({ - url: ($scope.exercice.id_theme ? ("api/themes/" + $scope.exercice.id_theme + "/exercices/" + $routeParams.exerciceId) : ("api/exercices/" + $routeParams.exerciceId)) + "/diff-sync", - method: "POST" - }).then(function (response) { - $scope.diff = response.data; - if (response.data === null) { - $scope.addToast('success', 'Changements par rapport au dépôt', "Tout est pareil !"); - } - }, function (response) { - $scope.diff = null; - $scope.addToast('danger', 'An error occurs when synchronizing exercice:', response.data.errmsg); - }); - }; $scope.deleteExercice = function () { var tid = $scope.exercice.id_theme; diff --git a/admin/static/views/exercice.html b/admin/static/views/exercice.html index 2077395e..1236261d 100644 --- a/admin/static/views/exercice.html +++ b/admin/static/views/exercice.html @@ -10,27 +10,11 @@
Vidéo Flags -
- - -
+ Voir sur la forge
-
-

Différences par rapport au dépôt

-
- {{ diffline.field }} -
-
-{{ diffline.be }}
-
+{{ diffline.af }}
-
-
-
- -
-
diff --git a/admin/static/views/settings.html b/admin/static/views/settings.html index 203f2b2f..354f5b84 100644 --- a/admin/static/views/settings.html +++ b/admin/static/views/settings.html @@ -64,60 +64,46 @@
-
- -
- -
+ +
+
-
- -
- -
+ +
+
-
- -
- -
+ +
+
-
- -
- -
+ +
+
-
+
- +
-
- +
+
-
- +
+
-
- -
- -
-

diff --git a/admin/static/views/sync.html b/admin/static/views/sync.html index 886a28cd..106cc6c3 100644 --- a/admin/static/views/sync.html +++ b/admin/static/views/sync.html @@ -120,41 +120,3 @@
- -
-
- -

- Différences avec le dépôts -

-
-
-
Lancez la génération du rapport pour lister les différences.
-
-
-
-

- {{ th }} -

-
- - - - -
-
- -
-
diff --git a/admin/static/views/team-score.html b/admin/static/views/team-score.html index 2b4617ff..96ca8321 100644 --- a/admin/static/views/team-score.html +++ b/admin/static/views/team-score.html @@ -13,7 +13,7 @@ Date - + {{ exercice.title }} @@ -23,12 +23,9 @@ {{ row.points * row.coeff }} - + {{ row.points }} * {{ row.coeff }} - - {{ row.points }} * {{ settings.questionGainRatio }} / {{ settings.questionGainRatio / row.coeff }} - {{ row.time | date:"mediumTime" }} diff --git a/admin/static/views/theme.html b/admin/static/views/theme.html index 5c17fbb6..831a54f8 100644 --- a/admin/static/views/theme.html +++ b/admin/static/views/theme.html @@ -7,21 +7,8 @@
-
-

Différences par rapport au dépôt

-
- {{ diffline.field }} -
-
-{{ diffline.be }}
-
+{{ diffline.af }}
-
-
-
- -
-
- +
@@ -38,14 +25,11 @@
-
+

Exercices ({{ exercices.length }}) -
- - -
+

diff --git a/admin/sync/errors.go b/admin/sync/errors.go index b0e2a015..a422ffe1 100644 --- a/admin/sync/errors.go +++ b/admin/sync/errors.go @@ -23,7 +23,7 @@ func NewThemeError(theme *fic.Theme, err error) *ThemeError { if theme == nil { return &ThemeError{ error: err, - ThemePath: fic.StandaloneExercicesDirectory, + ThemePath: StandaloneExercicesDirectory, } } diff --git a/admin/sync/exercice_files.go b/admin/sync/exercice_files.go index 276a72e4..fe37eba1 100644 --- a/admin/sync/exercice_files.go +++ b/admin/sync/exercice_files.go @@ -420,67 +420,36 @@ func SyncExerciceFiles(i Importer, exercice *fic.Exercice, exceptions *CheckExce return } -func GetRemoteExerciceFiles(thid, exid string) ([]*fic.EFile, error) { - theme, exceptions, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, errs - } - - exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions) - if exercice == nil { - return nil, errs - } - - files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files") - if files == nil { - return nil, errs - } - - var ret []*fic.EFile - for _, fname := range files { - fPath := path.Join(exercice.Path, "files", fname) - fSize, _ := GetFileSize(GlobalImporter, fPath) - - file := fic.EFile{ - Path: fPath, - Name: fname, - Checksum: digests[fname], - Size: fSize, - Published: true, - } - - if d, exists := digests[strings.TrimSuffix(file.Name, ".gz")]; exists { - file.Name = strings.TrimSuffix(file.Name, ".gz") - file.Path = strings.TrimSuffix(file.Path, ".gz") - file.ChecksumShown = d - } - - ret = append(ret, &file) - } - - // Complete with attributes - if paramsFiles, err := GetExerciceFilesParams(GlobalImporter, exercice); err == nil { - for _, file := range ret { - if f, ok := paramsFiles[file.Name]; ok { - file.Published = !f.Hidden - - if disclaimer, err := ProcessMarkdown(GlobalImporter, fixnbsp(f.Disclaimer), exercice.Path); err == nil { - file.Disclaimer = disclaimer - } - } - } - } - - return ret, nil -} - // ApiGetRemoteExerciceFiles is an accessor to remote exercice files list. func ApiGetRemoteExerciceFiles(c *gin.Context) { - files, err := GetRemoteExerciceFiles(c.Params.ByName("thid"), c.Params.ByName("exid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) + theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if theme != nil { + exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions) + if exercice != nil { + files, digests, errs := BuildFilesListInto(GlobalImporter, exercice, "files") + if files != nil { + var ret []*fic.EFile + for _, fname := range files { + fPath := path.Join(exercice.Path, "files", fname) + fSize, _ := GetFileSize(GlobalImporter, fPath) + ret = append(ret, &fic.EFile{ + Path: fPath, + Name: fname, + Checksum: digests[fname], + Size: fSize, + }) + } + c.JSON(http.StatusOK, ret) + } else { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)}) + return + } + } else { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)}) + return + } + } else { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": fmt.Errorf("%q", errs)}) return } - - c.JSON(http.StatusOK, files) } diff --git a/admin/sync/exercice_hints.go b/admin/sync/exercice_hints.go index acb40bd6..ccb8fc9d 100644 --- a/admin/sync/exercice_hints.go +++ b/admin/sync/exercice_hints.go @@ -169,32 +169,25 @@ func SyncExerciceHints(i Importer, exercice *fic.Exercice, flagsBindings map[int return } -func GetRemoteExerciceHints(thid, exid string) ([]importHint, error) { - theme, exceptions, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, errs - } - - exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions) - if exercice == nil { - return nil, errs - } - - hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions) - if hints == nil { - return nil, errs - } - - return hints, nil -} - // ApiListRemoteExerciceHints is an accessor letting foreign packages to access remote exercice hints. func ApiGetRemoteExerciceHints(c *gin.Context) { - hints, errs := GetRemoteExerciceHints(c.Params.ByName("thid"), c.Params.ByName("exid")) - if hints != nil { + theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if theme != nil { + exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions) + if exercice != nil { + hints, errs := CheckExerciceHints(GlobalImporter, exercice, eexceptions) + if hints != nil { + c.JSON(http.StatusOK, hints) + return + } + + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) + return + } + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) return } - c.JSON(http.StatusOK, hints) + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) } diff --git a/admin/sync/exercice_keys.go b/admin/sync/exercice_keys.go index e62dcf34..d4a88f3c 100644 --- a/admin/sync/exercice_keys.go +++ b/admin/sync/exercice_keys.go @@ -699,32 +699,26 @@ func SyncExerciceFlags(i Importer, exercice *fic.Exercice, exceptions *CheckExce return } -func GetRemoteExerciceFlags(thid, exid string) ([]fic.Flag, error) { - theme, exceptions, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, errs - } - - exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, exid), nil, exceptions) - if exercice == nil { - return nil, errs - } - - flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions) - if flags == nil { - return nil, errs - } - - return flags, nil -} - // ApiListRemoteExerciceFlags is an accessor letting foreign packages to access remote exercice flags. func ApiGetRemoteExerciceFlags(c *gin.Context) { - flags, err := GetRemoteExerciceFlags(c.Params.ByName("thid"), c.Params.ByName("exid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()}) + theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if theme != nil { + exercice, _, _, eexceptions, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions) + if exercice != nil { + flags, errs := CheckExerciceFlags(GlobalImporter, exercice, []string{}, eexceptions) + if flags != nil { + c.JSON(http.StatusOK, flags) + return + } + + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) + return + } + + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) return } - c.JSON(http.StatusOK, flags) + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) + return } diff --git a/admin/sync/exercices.go b/admin/sync/exercices.go index 49e1eef9..158e96ba 100644 --- a/admin/sync/exercices.go +++ b/admin/sync/exercices.go @@ -63,17 +63,10 @@ func buildDependancyMap(i Importer, theme *fic.Theme) (dmap map[int64]*fic.Exerc continue } - // ename can be overrride by title.txt - if i.Exists(path.Join(theme.Path, edir, "title.txt")) { - if myTitle, err := GetFileContent(i, path.Join(theme.Path, edir, "title.txt")); err == nil { - ename = strings.TrimSpace(myTitle) - } - } - var e *fic.Exercice e, err = theme.GetExerciceByTitle(ename) if err != nil { - return dmap, fmt.Errorf("unable to GetExerciceByTitle(ename=%q, tid=%d): %w", ename, theme.Id, err) + return } dmap[int64(eid)] = e @@ -475,57 +468,59 @@ func SyncExercices(i Importer, theme *fic.Theme, exceptions *CheckExceptions) (e return } -func ListRemoteExercices(thid string) ([]string, error) { - if thid == "_" { - return GetExercices(GlobalImporter, &fic.StandaloneExercicesTheme) - } - - theme, _, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, errs - } - - return GetExercices(GlobalImporter, theme) -} - // ApiListRemoteExercices is an accessor letting foreign packages to access remote exercices list. func ApiListRemoteExercices(c *gin.Context) { - exercices, err := ListRemoteExercices(c.Params.ByName("thid")) - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + if c.Params.ByName("thid") == "_" { + exercices, err := GetExercices(GlobalImporter, &fic.Theme{Path: StandaloneExercicesDirectory}) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + return + } + + c.JSON(http.StatusOK, exercices) return } - c.JSON(http.StatusOK, exercices) -} + theme, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if theme != nil { + exercices, err := GetExercices(GlobalImporter, theme) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + return + } -func GetRemoteExercice(thid, exid string, inTheme *fic.Theme) (*fic.Exercice, error) { - if thid == fic.StandaloneExercicesDirectory || thid == "_" { - exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, nil, path.Join(fic.StandaloneExercicesDirectory, exid), nil, nil) - return exercice, errs - } - - theme, exceptions, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, fmt.Errorf("Theme not found") - } - - if inTheme == nil { - inTheme = theme - } - - exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, inTheme, path.Join(theme.Path, exid), nil, exceptions) - return exercice, errs -} - -// ApiGetRemoteExercice is an accessor letting foreign packages to access remote exercice attributes. -func ApiGetRemoteExercice(c *gin.Context) { - exercice, err := GetRemoteExercice(c.Params.ByName("thid"), c.Params.ByName("exid"), nil) - if exercice != nil { - c.JSON(http.StatusOK, exercice) - return + c.JSON(http.StatusOK, exercices) } else { - c.JSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + c.AbortWithStatusJSON(http.StatusInternalServerError, fmt.Errorf("%q", errs)) + return + } +} + +// ApiListRemoteExercice is an accessor letting foreign packages to access remote exercice attributes. +func ApiGetRemoteExercice(c *gin.Context) { + if c.Params.ByName("thid") == "_" { + exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, nil, path.Join(StandaloneExercicesDirectory, c.Params.ByName("exid")), nil, nil) + if exercice != nil { + c.JSON(http.StatusOK, exercice) + return + } else { + c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)}) + return + } + } + + theme, exceptions, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if theme != nil { + exercice, _, _, _, _, errs := BuildExercice(GlobalImporter, theme, path.Join(theme.Path, c.Params.ByName("exid")), nil, exceptions) + if exercice != nil { + c.JSON(http.StatusOK, exercice) + return + } else { + c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)}) + return + } + } else { + c.JSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)}) return } } diff --git a/admin/sync/full.go b/admin/sync/full.go index 275a1e83..b4b9e661 100644 --- a/admin/sync/full.go +++ b/admin/sync/full.go @@ -75,8 +75,8 @@ func SpeedySyncDeep(i Importer) (errs SyncReport) { if themes, err := fic.GetThemes(); err == nil { DeepSyncProgress = 2 - if i.Exists(fic.StandaloneExercicesDirectory) { - themes = append(themes, &fic.StandaloneExercicesTheme) + if i.Exists(StandaloneExercicesDirectory) { + themes = append(themes, &fic.Theme{Path: StandaloneExercicesDirectory}) } var themeStep uint8 = uint8(250) / uint8(len(themes)) @@ -147,8 +147,8 @@ func SyncDeep(i Importer) (errs SyncReport) { DeepSyncProgress = 2 // Also synchronize standalone exercices - if i.Exists(fic.StandaloneExercicesDirectory) { - themes = append(themes, &fic.StandaloneExercicesTheme) + if i.Exists(StandaloneExercicesDirectory) { + themes = append(themes, &fic.Theme{Path: StandaloneExercicesDirectory}) } var themeStep uint8 = uint8(250) / uint8(len(themes)) diff --git a/admin/sync/themes.go b/admin/sync/themes.go index 20b3c114..1605233d 100644 --- a/admin/sync/themes.go +++ b/admin/sync/themes.go @@ -22,13 +22,15 @@ import ( "srs.epita.fr/fic-server/libfic" ) +const StandaloneExercicesDirectory = "exercices" + // GetThemes returns all theme directories in the base directory. func GetThemes(i Importer) (themes []string, err error) { if dirs, err := i.ListDir("/"); err != nil { return nil, err } else { for _, dir := range dirs { - if !strings.HasPrefix(dir, ".") && !strings.HasPrefix(dir, "_") && dir != fic.StandaloneExercicesDirectory { + if !strings.HasPrefix(dir, ".") && !strings.HasPrefix(dir, "_") && dir != StandaloneExercicesDirectory { if _, err := i.ListDir(dir); err == nil { themes = append(themes, dir) } @@ -178,10 +180,8 @@ func BuildTheme(i Importer, tdir string) (th *fic.Theme, exceptions *CheckExcept th.URLId = fic.ToURLid(th.Name) if authors, err := getAuthors(i, tdir); err != nil { - if tdir != fic.StandaloneExercicesDirectory { - errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err))) - return nil, nil, errs - } + errs = multierr.Append(errs, NewThemeError(th, fmt.Errorf("unable to get AUTHORS.txt: %w", err))) + return nil, nil, errs } else { // Format authors th.Authors = strings.Join(authors, ", ") @@ -355,34 +355,18 @@ func ApiListRemoteThemes(c *gin.Context) { c.JSON(http.StatusOK, themes) } -func GetRemoteTheme(thid string) (*fic.Theme, error) { - if thid == fic.StandaloneExercicesTheme.URLId || thid == fic.StandaloneExercicesDirectory { - return &fic.StandaloneExercicesTheme, nil - } - - theme, _, errs := BuildTheme(GlobalImporter, thid) - if theme == nil { - return nil, errs - } - - return theme, nil -} - // ApiListRemoteTheme is an accessor letting foreign packages to access remote main theme attributes. func ApiGetRemoteTheme(c *gin.Context) { - var theme *fic.Theme - var err error - - if c.Params.ByName("thid") == fic.StandaloneExercicesTheme.URLId { - theme, err = GetRemoteTheme(fic.StandaloneExercicesDirectory) - } else { - theme, err = GetRemoteTheme(c.Params.ByName("thid")) - } - - if err != nil { - c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + if c.Params.ByName("thid") == "_" { + c.Status(http.StatusNoContent) return } - c.JSON(http.StatusOK, theme) + r, _, errs := BuildTheme(GlobalImporter, c.Params.ByName("thid")) + if r == nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Errorf("%q", errs)}) + return + } + + c.JSON(http.StatusOK, r) } diff --git a/checker/main.go b/checker/main.go index 1e16b1a7..03befb8d 100644 --- a/checker/main.go +++ b/checker/main.go @@ -90,7 +90,6 @@ func reloadSettings(config *settings.Settings) { fic.GlobalScoreCoefficient = config.GlobalScoreCoefficient fic.CountOnlyNotGoodTries = config.CountOnlyNotGoodTries fic.DiscountedFactor = config.DiscountedFactor - fic.QuestionGainRatio = config.QuestionGainRatio } func main() { diff --git a/frontend/fic/src/lib/components/ExerciceFlags.svelte b/frontend/fic/src/lib/components/ExerciceFlags.svelte index a84360e4..a5da7862 100644 --- a/frontend/fic/src/lib/components/ExerciceFlags.svelte +++ b/frontend/fic/src/lib/components/ExerciceFlags.svelte @@ -28,13 +28,11 @@ export let readonly = false; export let forcesolved = false; - let last_submission = { }; let responses = { }; async function submitFlags() { waitInProgress.set(true); sberr = ""; message = ""; - last_submission = JSON.parse(JSON.stringify(responses)); if ($my && $my.team_id === 0) { let allGoodResponse = true; @@ -121,11 +119,6 @@ mcqs: { }, justifications: { }, }; - last_submission = { - flags: { }, - mcqs: { }, - justifications: { }, - }; } let last_exercice = null; @@ -159,12 +152,10 @@ {#if exercice.tries || exercice.solved_time || exercice.submitted || sberr || $timeouted} {#if exercice.solved_time || exercice.tries} -
- - {#if exercice.tries > 0}{exercice.tries} {exercice.tries==1?"tentative effectuée":"tentatives effectuées"}.{/if} - Dernière solution envoyée le . - -
+ + {#if exercice.tries > 0}{exercice.tries} {exercice.tries==1?"tentative effectuée":"tentatives effectuées"}.{/if} + Dernière solution envoyée à . + {/if} {#if exercice.solve_dist} @@ -200,7 +191,6 @@ @@ -209,7 +199,6 @@ class="mb-3" exercice_id={exercice.id} {flag} - previous_value={$waitInProgress ? "" : last_submission.flags[flag.id]} bind:value={responses.flags[flag.id]} /> {/if} diff --git a/frontend/fic/src/lib/components/FlagKey.svelte b/frontend/fic/src/lib/components/FlagKey.svelte index a1084965..7a0d8201 100644 --- a/frontend/fic/src/lib/components/FlagKey.svelte +++ b/frontend/fic/src/lib/components/FlagKey.svelte @@ -16,7 +16,6 @@ export let exercice_id = 0; export let flag = { }; export let no_label = false; - export let previous_value = ""; export let value = ""; let values = [""]; @@ -142,7 +141,6 @@