diff --git a/admin/api/exercice.go b/admin/api/exercice.go index afe6b8d7..024fd8b1 100644 --- a/admin/api/exercice.go +++ b/admin/api/exercice.go @@ -1,9 +1,11 @@ package api import ( + "bytes" "fmt" "log" "net/http" + "path" "reflect" "strconv" "strings" @@ -32,6 +34,8 @@ 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) @@ -91,8 +95,8 @@ func declareExercicesRoutes(router *gin.RouterGroup) { // Remote 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) + router.GET("/remote/themes/:thid/exercices/:exid/hints", sync.ApiGetRemoteExerciceHints) } type Exercice struct { @@ -130,7 +134,7 @@ func ExerciceHandler(c *gin.Context) { c.Set("theme", theme) } else { - c.Set("theme", &fic.Theme{Path: sync.StandaloneExercicesDirectory}) + c.Set("theme", &fic.StandaloneExercicesTheme) } } @@ -1275,3 +1279,343 @@ 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 0ea7f0ea..c16d698d 100644 --- a/admin/api/settings.go +++ b/admin/api/settings.go @@ -310,6 +310,7 @@ 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 @@ -329,6 +330,7 @@ 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 0bcba713..225b6e96 100644 --- a/admin/api/sync.go +++ b/admin/api/sync.go @@ -79,12 +79,14 @@ 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.Theme{Path: sync.StandaloneExercicesDirectory}, 0, 250, nil)) { + for _, se := range multierr.Errors(sync.SyncThemeDeep(sync.GlobalImporter, &fic.StandaloneExercicesTheme, 0, 250, nil)) { st = append(st, se.Error()) } sync.EditDeepReport(&sync.SyncReport{Exercices: st}, false) @@ -378,3 +380,32 @@ 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 37668ffd..c1057397 100644 --- a/admin/api/theme.go +++ b/admin/api/theme.go @@ -5,7 +5,9 @@ import ( "log" "net/http" "path" + "reflect" "strconv" + "strings" "srs.epita.fr/fic-server/admin/sync" "srs.epita.fr/fic-server/libfic" @@ -48,6 +50,8 @@ func declareThemesRoutes(router *gin.RouterGroup) { apiThemesRoutes.PUT("", updateTheme) apiThemesRoutes.DELETE("", deleteTheme) + apiThemesRoutes.POST("/diff-sync", APIDiffThemeWithRemote) + apiThemesRoutes.GET("/exercices_stats.json", getThemedExercicesStats) declareExercicesRoutes(apiThemesRoutes) @@ -70,13 +74,17 @@ func ThemeHandler(c *gin.Context) { return } - theme, err := fic.GetTheme(thid) - if err != nil { - c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Theme not found"}) - 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) + c.Set("theme", theme) + } c.Next() } @@ -127,6 +135,10 @@ func listThemes(c *gin.Context) { return } + if has, _ := fic.HasStandaloneExercice(); has { + themes = append([]*fic.Theme{&fic.StandaloneExercicesTheme}, themes...) + } + c.JSON(http.StatusOK, themes) } @@ -255,3 +267,110 @@ 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 47d31c72..06470166 100644 --- a/admin/main.go +++ b/admin/main.go @@ -199,10 +199,10 @@ func main() { } log.Println("Using", sync.GlobalImporter.Kind()) - // 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 { + 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) { if fd, err := os.Create(path.Join(settings.SettingsDir, settings.ChallengeFile)); err != nil { log.Fatal("Unable to open SETTINGS/challenge.json:", err) } else { @@ -213,6 +213,10 @@ 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 3a2bd877..82772993 100644 --- a/admin/static.go +++ b/admin/static.go @@ -127,7 +127,6 @@ 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", }) @@ -136,7 +135,6 @@ 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 315c9a10..19a3f518 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -923,6 +923,21 @@ 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) { @@ -1819,6 +1834,21 @@ 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) { @@ -1998,6 +2028,21 @@ 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 1236261d..2077395e 100644 --- a/admin/static/views/exercice.html +++ b/admin/static/views/exercice.html @@ -10,11 +10,27 @@
+