From 476d7c4c075cbfbfebcf8279c8cea01154c88d61 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sat, 11 Jan 2025 12:48:35 +0100 Subject: [PATCH 01/10] Readd disclaimer to my.json --- libfic/team_my.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libfic/team_my.go b/libfic/team_my.go index e7415fd7..e32cce96 100644 --- a/libfic/team_my.go +++ b/libfic/team_my.go @@ -37,6 +37,7 @@ type myTeamFile struct { Checksum string `json:"checksum"` Compressed bool `json:"compressed,omitempty"` Size int64 `json:"size"` + Disclaimer string `json:"disclaimer,omitempty"` } type myTeamHint struct { HintId int64 `json:"id"` @@ -213,7 +214,7 @@ func MyJSONTeam(t *Team, started bool) (interface{}, error) { cksum = f.ChecksumShown compressed = true } - exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size}) + exercice.Files = append(exercice.Files, myTeamFile{path.Join(FilesDir, f.Path), f.Name, hex.EncodeToString(cksum), compressed, f.Size, f.Disclaimer}) } } } From cc725c8e8ff0fac8fcb7ca7dd7234d1b0d8db252 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 13 Jan 2025 17:20:28 +0100 Subject: [PATCH 02/10] admin: Can delete a repository directory if needed --- admin/api/repositories.go | 22 ++++++++++++++++++++++ admin/static/js/app.js | 6 ++++++ admin/static/views/repositories.html | 12 ++++++++---- admin/sync/importer_git_common.go | 4 ++++ admin/sync/importer_localfs.go | 8 ++++++++ 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/admin/api/repositories.go b/admin/api/repositories.go index 70986aba..9afaea5d 100644 --- a/admin/api/repositories.go +++ b/admin/api/repositories.go @@ -41,5 +41,27 @@ func declareRepositoriesRoutes(router *gin.RouterGroup) { } c.JSON(http.StatusOK, mod) }) + + router.DELETE("/repositories/*repopath", func(c *gin.Context) { + di, ok := sync.GlobalImporter.(sync.DeletableImporter) + if !ok { + c.AbortWithStatusJSON(http.StatusNotImplemented, gin.H{"errmsg": "Not implemented"}) + return + } + + if strings.Contains(c.Param("repopath"), "..") { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Repopath contains invalid characters"}) + return + } + + repopath := strings.TrimPrefix(c.Param("repopath"), "/") + + err := di.DeleteDir(repopath) + if err != nil { + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) + return + } + c.JSON(http.StatusOK, true) + }) } } diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 0039770e..91c77a90 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -755,6 +755,12 @@ angular.module("FICApp") $http.get("api/repositories").then(function (response) { $scope.repositories = response.data.repositories; }); + + $scope.deleteRepository = function(repo) { + $http.delete("api/repositories/" + repo.path).then(function (response) { + $scope.repositories[$scope.repositories.indexOf(repo)].hash = "- DELETED -"; + }); + }; }) .component('repositoryUptodate', { bindings: { diff --git a/admin/static/views/repositories.html b/admin/static/views/repositories.html index 3946b181..daca48fa 100644 --- a/admin/static/views/repositories.html +++ b/admin/static/views/repositories.html @@ -9,16 +9,20 @@ Chemin Branche - Commit - Plus récent + Commit Plus récent {{ repository.path }} {{ repository.branch }} - {{ repository.hash }} - + + {{ repository.hash }}
+ + + + + diff --git a/admin/sync/importer_git_common.go b/admin/sync/importer_git_common.go index 44ffb9c7..d65711df 100644 --- a/admin/sync/importer_git_common.go +++ b/admin/sync/importer_git_common.go @@ -57,6 +57,10 @@ func (i GitImporter) Kind() string { return "git originated from " + i.Remote + " on " + i.li.Kind() } +func (i GitImporter) DeleteDir(filename string) error { + return i.li.DeleteDir(filename) +} + func getForgeBaseLink(remote string) (u *url.URL, err error) { res := gitRemoteRe.FindStringSubmatch(remote) u, err = url.Parse(res[2]) diff --git a/admin/sync/importer_localfs.go b/admin/sync/importer_localfs.go index dec51d0e..5f8301ae 100644 --- a/admin/sync/importer_localfs.go +++ b/admin/sync/importer_localfs.go @@ -113,3 +113,11 @@ func (i LocalImporter) ListDir(filename string) ([]string, error) { func (i LocalImporter) Stat(filename string) (os.FileInfo, error) { return os.Stat(path.Join(i.Base, filename)) } + +type DeletableImporter interface { + DeleteDir(filename string) error +} + +func (i LocalImporter) DeleteDir(filename string) error { + return os.RemoveAll(path.Join(i.Base, filename)) +} From 51f60e59d491df0458202eb0576205bd7338bffa Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 13 Jan 2025 18:00:27 +0100 Subject: [PATCH 03/10] admin: Refactor synchronization status report + display last git error --- admin/api/settings.go | 20 --------------- admin/api/sync.go | 42 +++++++++++++++++++++++++------ admin/static/js/app.js | 20 ++++++--------- admin/static/views/sync.html | 24 ++++++++++++------ admin/sync/full.go | 16 ++++++++++++ admin/sync/importer_git_common.go | 8 ++++++ 6 files changed, 81 insertions(+), 49 deletions(-) diff --git a/admin/api/settings.go b/admin/api/settings.go index 60065c3c..0ea7f0ea 100644 --- a/admin/api/settings.go +++ b/admin/api/settings.go @@ -8,7 +8,6 @@ import ( "net/http" "os" "path" - "reflect" "strconv" "time" @@ -26,7 +25,6 @@ func declareSettingsRoutes(router *gin.RouterGroup) { router.GET("/challenge.json", getChallengeInfo) router.PUT("/challenge.json", saveChallengeInfo) - router.GET("/settings-ro.json", getROSettings) router.GET("/settings.json", getSettings) router.PUT("/settings.json", saveSettings) router.DELETE("/settings.json", func(c *gin.Context) { @@ -98,24 +96,6 @@ func fullGeneration(c *gin.Context) { }) } -func getROSettings(c *gin.Context) { - syncMtd := "Disabled" - if sync.GlobalImporter != nil { - syncMtd = sync.GlobalImporter.Kind() - } - - var syncId *string - if sync.GlobalImporter != nil { - syncId = sync.GlobalImporter.Id() - } - - c.JSON(http.StatusOK, gin.H{ - "sync-type": reflect.TypeOf(sync.GlobalImporter).Name(), - "sync-id": syncId, - "sync": syncMtd, - }) -} - func GetChallengeInfo() (*settings.ChallengeInfo, error) { var challengeinfo string var err error diff --git a/admin/api/sync.go b/admin/api/sync.go index 113524c8..47bfaaad 100644 --- a/admin/api/sync.go +++ b/admin/api/sync.go @@ -7,6 +7,7 @@ import ( "net/url" "os" "path" + "reflect" "strings" "srs.epita.fr/fic-server/admin/generation" @@ -17,6 +18,8 @@ import ( "go.uber.org/multierr" ) +var lastSyncError = "" + func flatifySyncErrors(errs error) (ret []string) { for _, err := range multierr.Errors(errs) { ret = append(ret, err.Error()) @@ -27,12 +30,37 @@ func flatifySyncErrors(errs error) (ret []string) { func declareSyncRoutes(router *gin.RouterGroup) { apiSyncRoutes := router.Group("/sync") + // Return the global sync status + apiSyncRoutes.GET("/status", func(c *gin.Context) { + syncMtd := "Disabled" + if sync.GlobalImporter != nil { + syncMtd = sync.GlobalImporter.Kind() + } + + var syncId *string + if sync.GlobalImporter != nil { + syncId = sync.GlobalImporter.Id() + } + + c.JSON(http.StatusOK, gin.H{ + "sync-type": reflect.TypeOf(sync.GlobalImporter).Name(), + "sync-id": syncId, + "sync": syncMtd, + "pullMutex": !sync.OneGitPullStatus(), + "syncMutex": !sync.OneDeepSyncStatus() && !sync.OneThemeDeepSyncStatus(), + "progress": sync.DeepSyncProgress, + "lastError": lastSyncError, + }) + }) + // Base sync checks if the local directory is in sync with remote one. apiSyncRoutes.POST("/base", func(c *gin.Context) { err := sync.GlobalImporter.Sync() if err != nil { + lastSyncError = err.Error() c.JSON(http.StatusExpectationFailed, gin.H{"errmsg": err.Error()}) } else { + lastSyncError = "" c.JSON(http.StatusOK, true) } }) @@ -45,15 +73,10 @@ func declareSyncRoutes(router *gin.RouterGroup) { }) // Deep sync: a fully recursive synchronization (can be limited by theme). - apiSyncRoutes.GET("/deep", func(c *gin.Context) { - if sync.DeepSyncProgress == 0 { - c.AbortWithStatusJSON(http.StatusTooEarly, gin.H{"errmsg": "Pas de synchronisation en cours"}) - return - } - c.JSON(http.StatusOK, gin.H{"progress": sync.DeepSyncProgress}) - }) apiSyncRoutes.POST("/deep", func(c *gin.Context) { - c.JSON(http.StatusOK, sync.SyncDeep(sync.GlobalImporter)) + r := sync.SyncDeep(sync.GlobalImporter) + lastSyncError = "" + c.JSON(http.StatusOK, r) }) apiSyncDeepRoutes := apiSyncRoutes.Group("/deep/:thid") @@ -66,6 +89,7 @@ func declareSyncRoutes(router *gin.RouterGroup) { } sync.EditDeepReport(&sync.SyncReport{Exercices: st}, false) sync.DeepSyncProgress = 255 + lastSyncError = "" c.JSON(http.StatusOK, st) }) apiSyncDeepRoutes.POST("", func(c *gin.Context) { @@ -79,6 +103,7 @@ func declareSyncRoutes(router *gin.RouterGroup) { } sync.EditDeepReport(&sync.SyncReport{Themes: map[string][]string{theme.Name: st}}, false) sync.DeepSyncProgress = 255 + lastSyncError = "" c.JSON(http.StatusOK, st) }) @@ -90,6 +115,7 @@ func declareSyncRoutes(router *gin.RouterGroup) { apiSyncRoutes.POST("/themes", func(c *gin.Context) { _, errs := sync.SyncThemes(sync.GlobalImporter) + lastSyncError = "" c.JSON(http.StatusOK, flatifySyncErrors(errs)) }) diff --git a/admin/static/js/app.js b/admin/static/js/app.js index 91c77a90..77b9c2cd 100644 --- a/admin/static/js/app.js +++ b/admin/static/js/app.js @@ -230,9 +230,6 @@ angular.module("FICApp") .factory("File", function ($resource) { return $resource("api/files/:fileId", { fileId: '@id' }) }) - .factory("ROSettings", function ($resource) { - return $resource("api/settings-ro.json") - }) .factory("Settings", function ($resource) { return $resource("api/settings.json", null, { 'update': { method: 'PUT' }, @@ -787,9 +784,8 @@ angular.module("FICApp") template: `{{ $ctrl.status.hash }} {{ $ctrl.status.text }}` }) - .controller("SyncController", function ($scope, $rootScope, ROSettings, $location, $http, $interval) { + .controller("SyncController", function ($scope, $rootScope, $location, $http, $interval) { $scope.displayDangerousActions = false; - $scope.configro = ROSettings.get(); var needRefreshSyncReportWhenReady = false; var refreshSyncReport = function () { @@ -800,30 +796,28 @@ angular.module("FICApp") }; refreshSyncReport() + $scope.deepSyncInProgress = false; + var progressInterval = $interval(function () { - $http.get("api/sync/deep").then(function (response) { + $http.get("api/sync/status").then(function (response) { if (response.data.progress && response.data.progress != 255) needRefreshSyncReportWhenReady = true; else if (needRefreshSyncReportWhenReady) refreshSyncReport(); if (response.data && response.data.progress) { $scope.syncPercent = Math.floor(response.data.progress * 100 / 255); - $scope.syncProgress = Math.floor(response.data.progress * 100 / 255) + " %"; + $scope.deepSyncInProgress = response.data.pullMutex && response.data.syncMutex; } else { - $scope.syncProgress = response.data; $scope.syncPercent = 0; } + $scope.syncStatus = response.data; }, function (response) { $scope.syncPercent = 0; - if (response.data && response.data.errmsg) - $scope.syncProgress = response.data.errmsg; - else - $scope.syncProgress = response.data; + $scope.syncStatus = response.data; }) }, 1500); $scope.$on('$destroy', function () { $interval.cancel(progressInterval); }); - $scope.deepSyncInProgress = false; $scope.deepSync = function (theme) { if (theme) { question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?' diff --git a/admin/static/views/sync.html b/admin/static/views/sync.html index 847c9969..3643d4d5 100644 --- a/admin/static/views/sync.html +++ b/admin/static/views/sync.html @@ -32,17 +32,25 @@
Type
-
+
Synchronisation
-
-
ID
-
{{ configro['sync-id'] }}
-
Statut
-
{{ syncProgress }}
+
+
ID
+
+ {{ syncStatus['sync-id'] }} + +
+
Statut
+
+ {{ syncPercent }} % + Pull + Synchronisation +
-
- +
{{ syncStatus.lastError }}
+ +
From 76472231fa64ae62613a9267a87bd65cb9538078 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 13 Jan 2025 20:15:51 +0100 Subject: [PATCH 09/10] qa: Add logs to gitlab export --- qa/api/gitlab.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qa/api/gitlab.go b/qa/api/gitlab.go index 20789aed..f31c49cc 100644 --- a/qa/api/gitlab.go +++ b/qa/api/gitlab.go @@ -269,6 +269,7 @@ func GitLab_ExportQA(c *gin.Context) { gitlabid, err := GitLab_getExerciceId(exercice) if err != nil { + log.Println("Unable to retrieve Gitlab exercice id:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } @@ -301,6 +302,7 @@ func GitLab_ExportQA(c *gin.Context) { oauth2Token := c.MustGet("gitlab-token").(*oauth2.Token) iid, err := gitlab_newIssue(c.Request.Context(), oauth2Token, gitlabid, &issue) if err != nil { + log.Println("Unable to create Gitlab issue:", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": err.Error()}) return } From f82152a46227f6a9c851f46f17e7b65c548ce586 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 13 Jan 2025 20:17:28 +0100 Subject: [PATCH 10/10] qa: New to delete assigned work --- qa/api/todo.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qa/api/todo.go b/qa/api/todo.go index 3ee45c99..53a40878 100644 --- a/qa/api/todo.go +++ b/qa/api/todo.go @@ -21,6 +21,7 @@ func declareTodoRoutes(router *gin.RouterGroup) { func declareTodoManagerRoutes(router *gin.RouterGroup) { router.POST("/qa_assign_work", assignWork) + router.DELETE("/qa_assign_work", deleteAssignedWork) router.POST("/qa_my_exercices.json", addQAView) router.POST("/qa_work.json", createQATodo) @@ -316,3 +317,24 @@ func assignWork(c *gin.Context) { c.JSON(http.StatusOK, "true") } + +func deleteAssignedWork(c *gin.Context) { + teams, err := fic.GetTeams() + if err != nil { + log.Println("Unable to GetTeams: ", err.Error()) + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())}) + return + } + + for _, team := range teams { + todos, err := team.GetQATodo() + if err != nil { + c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Todo not found."}) + return + } + + for _, t := range todos { + t.Delete() + } + } +}