Compare commits
6 Commits
45a0504c44
...
08a31898df
Author | SHA1 | Date | |
---|---|---|---|
08a31898df | |||
b409fa6806 | |||
63b4cdc622 | |||
650f933993 | |||
603b226955 | |||
55e829fa64 |
@ -63,6 +63,7 @@ func declareExercicesRoutes(router *gin.RouterGroup) {
|
|||||||
apiFlagsRoutes.DELETE("/", deleteExerciceFlag)
|
apiFlagsRoutes.DELETE("/", deleteExerciceFlag)
|
||||||
apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps)
|
apiFlagsRoutes.GET("/dependancies", showExerciceFlagDeps)
|
||||||
apiFlagsRoutes.GET("/statistics", showExerciceFlagStats)
|
apiFlagsRoutes.GET("/statistics", showExerciceFlagStats)
|
||||||
|
apiFlagsRoutes.DELETE("/tries", deleteExerciceFlagTries)
|
||||||
apiFlagsRoutes.GET("/choices/", listFlagChoices)
|
apiFlagsRoutes.GET("/choices/", listFlagChoices)
|
||||||
apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid")
|
apiFlagsChoicesRoutes := apiExercicesRoutes.Group("/choices/:cid")
|
||||||
apiFlagsChoicesRoutes.Use(FlagChoiceHandler)
|
apiFlagsChoicesRoutes.Use(FlagChoiceHandler)
|
||||||
@ -78,6 +79,8 @@ func declareExercicesRoutes(router *gin.RouterGroup) {
|
|||||||
apiQuizRoutes.PUT("", updateExerciceQuiz)
|
apiQuizRoutes.PUT("", updateExerciceQuiz)
|
||||||
apiQuizRoutes.DELETE("", deleteExerciceQuiz)
|
apiQuizRoutes.DELETE("", deleteExerciceQuiz)
|
||||||
apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps)
|
apiQuizRoutes.GET("/dependancies", showExerciceQuizDeps)
|
||||||
|
apiQuizRoutes.GET("/statistics", showExerciceQuizStats)
|
||||||
|
apiQuizRoutes.DELETE("/tries", deleteExerciceQuizTries)
|
||||||
|
|
||||||
apiExercicesRoutes.GET("/tags", listExerciceTags)
|
apiExercicesRoutes.GET("/tags", listExerciceTags)
|
||||||
apiExercicesRoutes.POST("/tags", addExerciceTag)
|
apiExercicesRoutes.POST("/tags", addExerciceTag)
|
||||||
@ -864,7 +867,7 @@ func showExerciceFlagStats(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var completed, tries, nteams int64
|
var completed int64
|
||||||
|
|
||||||
for _, hline := range history {
|
for _, hline := range history {
|
||||||
if hline["kind"].(string) == "flag_found" {
|
if hline["kind"].(string) == "flag_found" {
|
||||||
@ -874,13 +877,39 @@ func showExerciceFlagStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tries, err := flag.NbTries()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to nbTries:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, err := flag.TeamsOnIt()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to teamsOnIt:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"completed": completed,
|
"completed": completed,
|
||||||
"tries": tries,
|
"tries": tries,
|
||||||
"nteams": nteams,
|
"teams": teams,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func deleteExerciceFlagTries(c *gin.Context) {
|
||||||
|
flag := c.MustGet("flag-key").(*fic.FlagKey)
|
||||||
|
|
||||||
|
err := flag.DeleteTries()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, true)
|
||||||
|
}
|
||||||
|
|
||||||
func tryExerciceFlag(c *gin.Context) {
|
func tryExerciceFlag(c *gin.Context) {
|
||||||
flag := c.MustGet("flag-key").(*fic.FlagKey)
|
flag := c.MustGet("flag-key").(*fic.FlagKey)
|
||||||
|
|
||||||
@ -1051,6 +1080,60 @@ func showExerciceQuizDeps(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, deps)
|
c.JSON(http.StatusOK, deps)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showExerciceQuizStats(c *gin.Context) {
|
||||||
|
exercice := c.MustGet("exercice").(*fic.Exercice)
|
||||||
|
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
|
||||||
|
|
||||||
|
history, err := exercice.GetHistory()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to getExerciceHistory:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving exercice history"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var completed int64
|
||||||
|
|
||||||
|
for _, hline := range history {
|
||||||
|
if hline["kind"].(string) == "mcq_found" {
|
||||||
|
if *hline["secondary"].(*int) == quiz.Id {
|
||||||
|
completed += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tries, err := quiz.NbTries()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to nbTries:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag tries"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
teams, err := quiz.TeamsOnIt()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to teamsOnIt:", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when retrieving flag related teams"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"completed": completed,
|
||||||
|
"tries": tries,
|
||||||
|
"teams": teams,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteExerciceQuizTries(c *gin.Context) {
|
||||||
|
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
|
||||||
|
|
||||||
|
err := quiz.DeleteTries()
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, true)
|
||||||
|
}
|
||||||
|
|
||||||
func updateExerciceQuiz(c *gin.Context) {
|
func updateExerciceQuiz(c *gin.Context) {
|
||||||
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
|
quiz := c.MustGet("flag-quiz").(*fic.MCQ)
|
||||||
|
|
||||||
|
@ -358,6 +358,9 @@ angular.module("FICApp")
|
|||||||
})
|
})
|
||||||
.factory("ExerciceMCQDeps", function ($resource) {
|
.factory("ExerciceMCQDeps", function ($resource) {
|
||||||
return $resource("api/exercices/:exerciceId/quiz/:mcqId/dependancies", { exerciceId: '@idExercice', mcqId: '@id' })
|
return $resource("api/exercices/:exerciceId/quiz/:mcqId/dependancies", { exerciceId: '@idExercice', mcqId: '@id' })
|
||||||
|
})
|
||||||
|
.factory("ExerciceMCQStats", function ($resource) {
|
||||||
|
return $resource("api/exercices/:exerciceId/quiz/:mcqId/statistics", { exerciceId: '@idExercice', mcqId: '@id' })
|
||||||
});
|
});
|
||||||
|
|
||||||
angular.module("FICApp")
|
angular.module("FICApp")
|
||||||
@ -614,9 +617,18 @@ angular.module("FICApp")
|
|||||||
|
|
||||||
response.enableExerciceDepend = response.unlockedChallengeDepth >= 0;
|
response.enableExerciceDepend = response.unlockedChallengeDepth >= 0;
|
||||||
response.disabledsubmitbutton = response.disablesubmitbutton && response.disablesubmitbutton.length > 0;
|
response.disabledsubmitbutton = response.disablesubmitbutton && response.disablesubmitbutton.length > 0;
|
||||||
|
if (response.end) {
|
||||||
|
$scope.duration = (response.end - response.start)/60000;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
$scope.challenge = SettingsChallenge.get();
|
$scope.challenge = SettingsChallenge.get();
|
||||||
$scope.duration = 360;
|
$scope.duration = 360;
|
||||||
|
$scope.durationChange = function(endChanged) {
|
||||||
|
if (endChanged)
|
||||||
|
$scope.duration = (new Date($scope.config.end).getTime() - new Date($scope.config.start).getTime())/60000;
|
||||||
|
else
|
||||||
|
$scope.config.end = new Date(new Date($scope.config.start).getTime() + $scope.duration * 60000);
|
||||||
|
}
|
||||||
$scope.activateTime = "";
|
$scope.activateTime = "";
|
||||||
$scope.challenge.$promise.then(function (c) {
|
$scope.challenge.$promise.then(function (c) {
|
||||||
if (c.duration)
|
if (c.duration)
|
||||||
@ -651,7 +663,7 @@ angular.module("FICApp")
|
|||||||
$scope.saveChallengeInfo = function () {
|
$scope.saveChallengeInfo = function () {
|
||||||
this.challenge.duration = $scope.duration;
|
this.challenge.duration = $scope.duration;
|
||||||
this.challenge.$update(function (response) {
|
this.challenge.$update(function (response) {
|
||||||
$scope.addToast('success', 'Infos du challenge mises à jour avec succès !');
|
$scope.addToast('success', 'Infos du challenge mises à jour avec succès !');
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
$scope.addToast('danger', 'An error occurs when saving challenge info:', response.data.errmsg);
|
$scope.addToast('danger', 'An error occurs when saving challenge info:', response.data.errmsg);
|
||||||
});
|
});
|
||||||
@ -725,9 +737,9 @@ angular.module("FICApp")
|
|||||||
"teams": "En validant, vous supprimerez l'ensemble des équipes enregistreées.",
|
"teams": "En validant, vous supprimerez l'ensemble des équipes enregistreées.",
|
||||||
"game": "En validant, vous supprimerez toutes les tentatives, les validations, ... faites par les équipes.",
|
"game": "En validant, vous supprimerez toutes les tentatives, les validations, ... faites par les équipes.",
|
||||||
}
|
}
|
||||||
$scope.addToast('warning', txts[type], 'Êtes-vous sûr de vouloir continuer ?',
|
$scope.addToast('warning', txts[type], 'Êtes-vous sûr de vouloir continuer ?',
|
||||||
function () {
|
function () {
|
||||||
if (confirm("Êtes-vous vraiment sûr ?\n" + txts[type])) {
|
if (confirm("Êtes-vous vraiment sûr ?\n" + txts[type])) {
|
||||||
$http.post("api/reset", { "type": type }).then(function (time) {
|
$http.post("api/reset", { "type": type }).then(function (time) {
|
||||||
$scope.addToast('success', type + 'reseted');
|
$scope.addToast('success', type + 'reseted');
|
||||||
$location.url("/");
|
$location.url("/");
|
||||||
@ -739,7 +751,7 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.switchToProd = function () {
|
$scope.switchToProd = function () {
|
||||||
$scope.addToast('warning', "Activer le mode challenge ?", "L'activation du mode challenge est temporaire (vous devriez plutôt relancer le daemon avec l'option `-4real`). Ce mode permet d'éviter les mauvaises manipulations et désactive le hook git de synchronisation automatique. Êtes-vous sûr de vouloir continuer ?",
|
$scope.addToast('warning', "Activer le mode challenge ?", "L'activation du mode challenge est temporaire (vous devriez plutôt relancer le daemon avec l'option `-4real`). Ce mode permet d'éviter les mauvaises manipulations et désactive le hook git de synchronisation automatique. Êtes-vous sûr de vouloir continuer ?",
|
||||||
function () {
|
function () {
|
||||||
$http.put("api/prod", true).then(function (time) {
|
$http.put("api/prod", true).then(function (time) {
|
||||||
$rootScope.refreshSettings()
|
$rootScope.refreshSettings()
|
||||||
@ -762,6 +774,20 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.component('teamLink', {
|
||||||
|
bindings: {
|
||||||
|
idTeam: '=',
|
||||||
|
},
|
||||||
|
controller: function (Team) {
|
||||||
|
var ctrl = this;
|
||||||
|
ctrl.team = {};
|
||||||
|
|
||||||
|
ctrl.$onInit = function () {
|
||||||
|
ctrl.team = Team.get({teamId: ctrl.idTeam});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: `<a href="/teams/{{ $ctrl.idTeam }}">{{ $ctrl.team.name }}</a> `
|
||||||
|
})
|
||||||
.component('repositoryUptodate', {
|
.component('repositoryUptodate', {
|
||||||
bindings: {
|
bindings: {
|
||||||
repository: '<',
|
repository: '<',
|
||||||
@ -823,10 +849,10 @@ angular.module("FICApp")
|
|||||||
|
|
||||||
$scope.deepSync = function (theme) {
|
$scope.deepSync = function (theme) {
|
||||||
if (theme) {
|
if (theme) {
|
||||||
question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?'
|
question = 'Faire une synchronisation intégrale du thème ' + theme.name + ' ?'
|
||||||
url = "api/sync/deep/" + theme.id
|
url = "api/sync/deep/" + theme.id
|
||||||
} else {
|
} else {
|
||||||
question = 'Faire une synchronisation intégrale ?'
|
question = 'Faire une synchronisation intégrale ?'
|
||||||
url = "api/sync/deep"
|
url = "api/sync/deep"
|
||||||
}
|
}
|
||||||
$scope.addToast('warning', question, '',
|
$scope.addToast('warning', question, '',
|
||||||
@ -842,7 +868,7 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.speedyDeepSync = function () {
|
$scope.speedyDeepSync = function () {
|
||||||
$scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '',
|
$scope.addToast('warning', 'Faire une synchronisation profonde rapide, sans s\'occuper des fichiers ?', '',
|
||||||
function () {
|
function () {
|
||||||
$scope.deepSyncInProgress = true;
|
$scope.deepSyncInProgress = true;
|
||||||
$http.post("api/sync/speed").then(function () {
|
$http.post("api/sync/speed").then(function () {
|
||||||
@ -855,7 +881,7 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.baseSync = function () {
|
$scope.baseSync = function () {
|
||||||
$scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '',
|
$scope.addToast('warning', 'Tirer les mises à jour du dépôt ?', '',
|
||||||
function () {
|
function () {
|
||||||
$scope.deepSyncInProgress = true;
|
$scope.deepSyncInProgress = true;
|
||||||
$http.post("api/sync/base").then(function () {
|
$http.post("api/sync/base").then(function () {
|
||||||
@ -868,7 +894,7 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.syncVideos = function () {
|
$scope.syncVideos = function () {
|
||||||
$scope.addToast('warning', 'Synchroniser les vidéos de résolution ?', 'ATTENTION il ne faut pas lancer cette synchronisation durant le challenge. Seulement une fois le challenge terminé, cela permet de rendre les vidéos accessibles dans l\'interface joueurs.',
|
$scope.addToast('warning', 'Synchroniser les vidéos de résolution ?', 'ATTENTION il ne faut pas lancer cette synchronisation durant le challenge. Seulement une fois le challenge terminé, cela permet de rendre les vidéos accessibles dans l\'interface joueurs.',
|
||||||
function () {
|
function () {
|
||||||
$scope.deepSyncInProgress = true;
|
$scope.deepSyncInProgress = true;
|
||||||
$http.post("api/sync/videos").then(function () {
|
$http.post("api/sync/videos").then(function () {
|
||||||
@ -1113,7 +1139,7 @@ angular.module("FICApp")
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: "countdown",
|
type: "countdown",
|
||||||
params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" },
|
params: { color: "success", end: null, lead: "Go, go, go !", title: "Le challenge forensic va bientôt commencer !" },
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
$scope.display.side = [
|
$scope.display.side = [
|
||||||
@ -1232,8 +1258,8 @@ angular.module("FICApp")
|
|||||||
show: true,
|
show: true,
|
||||||
shadow: "#E8CF5C",
|
shadow: "#E8CF5C",
|
||||||
end: new Date($rootScope.getSrvTime().getTime() + 1802000).toISOString(),
|
end: new Date($rootScope.getSrvTime().getTime() + 1802000).toISOString(),
|
||||||
before: "Heure joyeuse : chaque résolution compte double !",
|
before: "Heure joyeuse : chaque résolution compte double !",
|
||||||
after: "Heure joyeuse terminée !",
|
after: "Heure joyeuse terminée !",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (scene == "freehintquarter") {
|
else if (scene == "freehintquarter") {
|
||||||
@ -1241,8 +1267,8 @@ angular.module("FICApp")
|
|||||||
show: true,
|
show: true,
|
||||||
shadow: "#3DD28F",
|
shadow: "#3DD28F",
|
||||||
end: new Date($rootScope.getSrvTime().getTime() + 902000).toISOString(),
|
end: new Date($rootScope.getSrvTime().getTime() + 902000).toISOString(),
|
||||||
before: "Quart d'heure facile : indices dévoilés !",
|
before: "Quart d'heure facile : indices dévoilés !",
|
||||||
after: "Quart d'heure facile terminée !",
|
after: "Quart d'heure facile terminée !",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1371,7 +1397,7 @@ angular.module("FICApp")
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
$scope.clearFilesDir = function () {
|
$scope.clearFilesDir = function () {
|
||||||
$scope.addToast('warning', 'Êtes-vous sûr de vouloir continuer ?', "Ceci va supprimer tout le contenu du dossier FILES. Il s'agit des fichiers ci-dessous, il faudra refaire une synchronisation ensuite.",
|
$scope.addToast('warning', 'Êtes-vous sûr de vouloir continuer ?', "Ceci va supprimer tout le contenu du dossier FILES. Il s'agit des fichiers ci-dessous, il faudra refaire une synchronisation ensuite.",
|
||||||
function () {
|
function () {
|
||||||
$scope.clearFilesWIP = true;
|
$scope.clearFilesWIP = true;
|
||||||
$http({
|
$http({
|
||||||
@ -2279,7 +2305,7 @@ angular.module("FICApp")
|
|||||||
method: "POST"
|
method: "POST"
|
||||||
}).then(function (response) {
|
}).then(function (response) {
|
||||||
flag.test_str = "";
|
flag.test_str = "";
|
||||||
$scope.addToast('success', "Flag Ok !");
|
$scope.addToast('success', "Flag Ok !");
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
flag.test_str = "";
|
flag.test_str = "";
|
||||||
$scope.addToast('danger', 'An error occurs: ', response.data.errmsg);
|
$scope.addToast('danger', 'An error occurs: ', response.data.errmsg);
|
||||||
@ -2348,10 +2374,16 @@ angular.module("FICApp")
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller("ExerciceFlagStatsController", function ($scope, $routeParams, ExerciceFlagStats) {
|
.controller("ExerciceFlagStatsController", function ($scope, $routeParams, ExerciceFlagStats, $http) {
|
||||||
$scope.init = function (flag) {
|
$scope.init = function (flag) {
|
||||||
|
$scope.flag_id = flag.id;
|
||||||
$scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: flag.id });
|
$scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: flag.id });
|
||||||
}
|
}
|
||||||
|
$scope.deleteTries = function () {
|
||||||
|
$http.delete(`/api/exercices/${$routeParams.exerciceId}/flags/${$scope.flag_id}/tries`).then(function () {
|
||||||
|
$scope.stats = ExerciceFlagStats.get({ exerciceId: $routeParams.exerciceId, flagId: $scope.flag_id });
|
||||||
|
});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
.controller("ExerciceMCQFlagsController", function ($scope, ExerciceMCQFlag, $routeParams, $rootScope) {
|
.controller("ExerciceMCQFlagsController", function ($scope, ExerciceMCQFlag, $routeParams, $rootScope) {
|
||||||
@ -2391,6 +2423,18 @@ angular.module("FICApp")
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.controller("ExerciceMCQStatsController", function ($scope, $routeParams, ExerciceMCQStats, $http) {
|
||||||
|
$scope.init = function (mcq) {
|
||||||
|
$scope.mcq_id = mcq.id;
|
||||||
|
$scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: mcq.id });
|
||||||
|
}
|
||||||
|
$scope.deleteTries = function () {
|
||||||
|
$http.delete(`/api/exercices/${$routeParams.exerciceId}/quiz/${$scope.mcq_id}/tries`).then(function () {
|
||||||
|
$scope.stats = ExerciceMCQStats.get({ exerciceId: $routeParams.exerciceId, mcqId: $scope.mcq_id });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
.controller("TeamsListController", function ($scope, $rootScope, Team, $location, $http) {
|
.controller("TeamsListController", function ($scope, $rootScope, Team, $location, $http) {
|
||||||
$scope.teams = Team.query();
|
$scope.teams = Team.query();
|
||||||
$scope.fields = ["id", "name"];
|
$scope.fields = ["id", "name"];
|
||||||
|
@ -83,9 +83,16 @@
|
|||||||
<div ng-controller="ExerciceFlagStatsController" ng-init="init(flag)">
|
<div ng-controller="ExerciceFlagStatsController" ng-init="init(flag)">
|
||||||
<strong>Statistiques</strong>
|
<strong>Statistiques</strong>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Validés : {{ stats["completed"] }}</li>
|
<li>Validés : {{ stats["completed"] }}</li>
|
||||||
<li>Tentés : {{ stats["tries"] }}</li>
|
<li>
|
||||||
<li>Équipes : {{ stats["nteams"] }}</li>
|
Tentés : {{ stats["tries"] }}
|
||||||
|
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Équipes :
|
||||||
|
<span ng-if="stats['teams'].length == 0">aucune</span>
|
||||||
|
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -177,6 +184,22 @@
|
|||||||
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
<dependancy ng-repeat="dep in deps" dep="dep"></dependancy>
|
||||||
</ul>
|
</ul>
|
||||||
<span ng-if="deps.length == 0"> sans</span>
|
<span ng-if="deps.length == 0"> sans</span>
|
||||||
|
<hr>
|
||||||
|
<div ng-controller="ExerciceMCQStatsController" ng-init="init(q)">
|
||||||
|
<strong>Statistiques</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Validés : {{ stats["completed"] }}</li>
|
||||||
|
<li>
|
||||||
|
Tentés : {{ stats["tries"] }}
|
||||||
|
<button type="button" ng-click="deleteTries()" class="btn btn-sm btn-danger" ng-if="stats['tries'] > 0"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span></button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Équipes :
|
||||||
|
<span ng-if="stats['teams'].length == 0">aucune</span>
|
||||||
|
<team-link ng-repeat="team in stats['teams']" id-team="team"></team-link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<label for="startTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.start != dist_config.start}">Début du challenge</label>
|
<label for="startTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.start != dist_config.start}">Début du challenge</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="datetime-local" class="form-control form-control-sm" id="startTime" ng-model="config.start" ng-class="{'border-primary': config.start != dist_config.start}">
|
<input type="datetime-local" class="form-control form-control-sm" id="startTime" ng-model="config.start" ng-change="durationChange()" ng-class="{'border-primary': config.start != dist_config.start}">
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<button ng-click="launchChallenge()" class="btn btn-sm btn-secondary" type="button"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Lancer le challenge</button>
|
<button ng-click="launchChallenge()" class="btn btn-sm btn-secondary" type="button"><span class="glyphicon glyphicon-play" aria-hidden="true"></span> Lancer le challenge</button>
|
||||||
</div>
|
</div>
|
||||||
@ -46,14 +46,14 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="endTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.end != dist_config.end}">Fin du challenge</label>
|
<label for="endTime" class="col-sm-3 col-form-label col-form-label-sm" ng-class="{'text-primary font-weight-bold': config.end != dist_config.end}">Fin du challenge</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input type="datetime-local" class="form-control form-control-sm" id="endTime" ng-model="config.end" ng-class="{'border-primary': config.end != dist_config.end}">
|
<input type="datetime-local" class="form-control form-control-sm" id="endTime" ng-model="config.end" ng-change="durationChange(true)" ng-class="{'border-primary': config.end != dist_config.end}">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-1 text-right">
|
<div class="col-sm-1 text-right">
|
||||||
<label for="duration" class="col-form-label col-form-label-sm">Durée</label>
|
<label for="duration" class="col-form-label col-form-label-sm">Durée</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-2">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="text" class="form-control form-control-sm" id="duration" ng-model="duration" integer>
|
<input type="number" class="form-control form-control-sm" id="duration" ng-model="duration" ng-change="durationChange()" integer>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<span class="input-group-text">min</span>
|
<span class="input-group-text">min</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -237,7 +237,7 @@ services:
|
|||||||
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
||||||
- /var/lib/fic/pki:/srv/PKI
|
- /var/lib/fic/pki:/srv/PKI
|
||||||
- /var/lib/fic/settings:/srv/SETTINGS
|
- /var/lib/fic/settings:/srv/SETTINGS
|
||||||
- /var/lib/fic/submissions:/srv/submissions:ro
|
- /var/lib/fic/submissions:/srv/submissions
|
||||||
- /var/lib/fic/sync:/srv/SYNC
|
- /var/lib/fic/sync:/srv/SYNC
|
||||||
- /var/lib/fic/teams:/srv/TEAMS
|
- /var/lib/fic/teams:/srv/TEAMS
|
||||||
net: /run/netns/fic-admin
|
net: /run/netns/fic-admin
|
||||||
@ -278,7 +278,10 @@ services:
|
|||||||
binds:
|
binds:
|
||||||
- /etc/hosts:/etc/hosts:ro
|
- /etc/hosts:/etc/hosts:ro
|
||||||
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
- /var/lib/fic/generator:/srv/GENERATOR:ro
|
||||||
|
# Uncomment this to disallow registrations
|
||||||
- /var/lib/fic/teams:/srv/TEAMS:ro
|
- /var/lib/fic/teams:/srv/TEAMS:ro
|
||||||
|
# Uncomment this to allow registrations
|
||||||
|
#- /var/lib/fic/teams:/srv/TEAMS
|
||||||
- /var/lib/fic/secrets/mysql_password:/run/secrets/mysql_password:ro
|
- /var/lib/fic/secrets/mysql_password:/run/secrets/mysql_password:ro
|
||||||
- /var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro
|
- /var/lib/fic/settingsdist:/srv/SETTINGSDIST:ro
|
||||||
- /var/lib/fic/submissions:/srv/submissions
|
- /var/lib/fic/submissions:/srv/submissions
|
||||||
|
21
libfic/db.go
21
libfic/db.go
@ -414,6 +414,7 @@ CREATE TABLE IF NOT EXISTS exercice_solved(
|
|||||||
}
|
}
|
||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
CREATE TABLE IF NOT EXISTS exercice_tries(
|
CREATE TABLE IF NOT EXISTS exercice_tries(
|
||||||
|
id_try INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||||
id_exercice INTEGER NOT NULL,
|
id_exercice INTEGER NOT NULL,
|
||||||
id_team INTEGER NOT NULL,
|
id_team INTEGER NOT NULL,
|
||||||
time TIMESTAMP NOT NULL,
|
time TIMESTAMP NOT NULL,
|
||||||
@ -423,6 +424,26 @@ CREATE TABLE IF NOT EXISTS exercice_tries(
|
|||||||
FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice),
|
FOREIGN KEY(id_exercice) REFERENCES exercices(id_exercice),
|
||||||
FOREIGN KEY(id_team) REFERENCES teams(id_team)
|
FOREIGN KEY(id_team) REFERENCES teams(id_team)
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS exercice_tries_flags(
|
||||||
|
id_try INTEGER NOT NULL,
|
||||||
|
id_flag INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(id_flag) REFERENCES exercice_flags(id_flag) ON DELETE CASCADE
|
||||||
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS exercice_tries_mcq(
|
||||||
|
id_try INTEGER NOT NULL,
|
||||||
|
id_mcq INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(id_try) REFERENCES exercice_tries(id_try) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(id_mcq) REFERENCES exercice_mcq(id_mcq) ON DELETE CASCADE
|
||||||
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package fic
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -444,11 +445,25 @@ func (e *Exercice) GetOrdinal() (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTry registers a solving attempt for the given Team.
|
// NewTry registers a solving attempt for the given Team.
|
||||||
func (e *Exercice) NewTry(t *Team, cksum []byte) error {
|
func (e *Exercice) NewTry(t *Team, cksum []byte, flags ...Flag) (int64, error) {
|
||||||
if _, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), cksum); err != nil {
|
if res, err := DBExec("INSERT INTO exercice_tries (id_exercice, id_team, time, cksum) VALUES (?, ?, ?, ?)", e.Id, t.Id, time.Now(), cksum); err != nil {
|
||||||
return err
|
return 0, err
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return res.LastInsertId()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exercice) NewTryFlag(tryid int64, flags ...Flag) {
|
||||||
|
for _, flag := range flags {
|
||||||
|
if fk, ok := flag.(*FlagKey); ok {
|
||||||
|
if _, err := DBExec("INSERT INTO exercice_tries_flags (id_try, id_flag) VALUES (?, ?)", tryid, fk.Id); err != nil {
|
||||||
|
log.Println("Unable to add detailed try: ", err.Error())
|
||||||
|
}
|
||||||
|
} else if fm, ok := flag.(*MCQ); ok {
|
||||||
|
if _, err := DBExec("INSERT INTO exercice_tries_mcq (id_try, id_mcq) VALUES (?, ?)", tryid, fm.Id); err != nil {
|
||||||
|
log.Println("Unable to add detailed try: ", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,7 +565,7 @@ func (e *Exercice) MCQSolved() (res []int64) {
|
|||||||
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
|
// CheckResponse, given both flags and MCQ responses, figures out if thoses are correct (or if they are previously solved).
|
||||||
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
|
// In the meanwhile, CheckResponse registers good answers given (but it does not mark the challenge as solved at the end).
|
||||||
func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq map[int]bool, t *Team) (bool, error) {
|
func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq map[int]bool, t *Team) (bool, error) {
|
||||||
if err := e.NewTry(t, cksum); err != nil {
|
if tryId, err := e.NewTry(t, cksum); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if flags, err := e.GetFlagKeys(); err != nil {
|
} else if flags, err := e.GetFlagKeys(); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@ -565,6 +580,10 @@ func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq
|
|||||||
|
|
||||||
// Check MCQs
|
// Check MCQs
|
||||||
for _, mcq := range mcqs {
|
for _, mcq := range mcqs {
|
||||||
|
if mcq.HasOneEntry(respmcq) {
|
||||||
|
e.NewTryFlag(tryId, mcq)
|
||||||
|
}
|
||||||
|
|
||||||
if d := mcq.Check(respmcq); d > 0 {
|
if d := mcq.Check(respmcq); d > 0 {
|
||||||
if !PartialValidation || t.HasPartiallySolved(mcq) == nil {
|
if !PartialValidation || t.HasPartiallySolved(mcq) == nil {
|
||||||
valid = false
|
valid = false
|
||||||
@ -589,15 +608,21 @@ func (e *Exercice) CheckResponse(cksum []byte, respflags map[int]string, respmcq
|
|||||||
for _, flag := range flags {
|
for _, flag := range flags {
|
||||||
if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) {
|
if res, ok := respflags[flag.Id]; !ok && (!PartialValidation || t.HasPartiallySolved(flag) == nil) {
|
||||||
valid = valid && flag.IsOptionnal()
|
valid = valid && flag.IsOptionnal()
|
||||||
} else if flag.Check([]byte(res)) != 0 {
|
} else if ok {
|
||||||
if !PartialValidation || t.HasPartiallySolved(flag) == nil {
|
if len(res) > 0 {
|
||||||
valid = valid && flag.IsOptionnal()
|
e.NewTryFlag(tryId, flag)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
err := flag.FoundBy(t)
|
if flag.Check([]byte(res)) != 0 {
|
||||||
if err == nil {
|
if !PartialValidation || t.HasPartiallySolved(flag) == nil {
|
||||||
// err is unicity issue, probably flag already found
|
valid = valid && flag.IsOptionnal()
|
||||||
goodResponses += 1
|
}
|
||||||
|
} else {
|
||||||
|
err := flag.FoundBy(t)
|
||||||
|
if err == nil {
|
||||||
|
// err is unicity issue, probably flag already found
|
||||||
|
goodResponses += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,8 @@ func (e *Exercice) AppendHistoryItem(tId int64, kind string, secondary *int64) e
|
|||||||
if kind == "tries" {
|
if kind == "tries" {
|
||||||
bid := make([]byte, 5)
|
bid := make([]byte, 5)
|
||||||
binary.LittleEndian.PutUint32(bid, rand.Uint32())
|
binary.LittleEndian.PutUint32(bid, rand.Uint32())
|
||||||
return (&Exercice{Id: e.Id}).NewTry(team, bid)
|
_, err = (&Exercice{Id: e.Id}).NewTry(team, bid)
|
||||||
|
return err
|
||||||
} else if kind == "hint" && secondary != nil {
|
} else if kind == "hint" && secondary != nil {
|
||||||
return team.OpenHint(&EHint{Id: *secondary})
|
return team.OpenHint(&EHint{Id: *secondary})
|
||||||
} else if kind == "wchoices" && secondary != nil {
|
} else if kind == "wchoices" && secondary != nil {
|
||||||
|
@ -14,6 +14,9 @@ type Flag interface {
|
|||||||
Check(val interface{}) int
|
Check(val interface{}) int
|
||||||
IsOptionnal() bool
|
IsOptionnal() bool
|
||||||
FoundBy(t *Team) error
|
FoundBy(t *Team) error
|
||||||
|
NbTries() (int64, error)
|
||||||
|
TeamsOnIt() ([]int64, error)
|
||||||
|
DeleteTries() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFlag returns a list of flags comming with the challenge.
|
// GetFlag returns a list of flags comming with the challenge.
|
||||||
|
@ -230,6 +230,54 @@ func (k *FlagKey) RecoverId() (Flag, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NbTries returns the flag resolution statistics.
|
||||||
|
func (k *FlagKey) NbTries() (tries int64, err error) {
|
||||||
|
err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_flags WHERE id_flag = ?", k.Id).Scan(&tries)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *FlagKey) TeamsOnIt() ([]int64, error) {
|
||||||
|
if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_flags F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_flag = ?", k.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
teams := []int64{}
|
||||||
|
for rows.Next() {
|
||||||
|
var idteam int64
|
||||||
|
if err := rows.Scan(&idteam); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
teams = append(teams, idteam)
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *FlagKey) DeleteTries() error {
|
||||||
|
if rows, err := DBQuery("SELECT id_try FROM exercice_tries_flags WHERE id_flag = ?", k.Id); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var idtry int64
|
||||||
|
err = rows.Scan(&idtry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
|
// AddFlagKey creates and fills a new struct Flag, from a hashed flag, and registers it into the database.
|
||||||
func (k *FlagKey) Create(e *Exercice) (Flag, error) {
|
func (k *FlagKey) Create(e *Exercice) (Flag, error) {
|
||||||
// Check the regexp compile
|
// Check the regexp compile
|
||||||
|
@ -71,6 +71,18 @@ func (k *FlagLabel) RecoverId() (Flag, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (k *FlagLabel) NbTries() (int64, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *FlagLabel) TeamsOnIt() ([]int64, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *FlagLabel) DeleteTries() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// AddFlagLabel creates and fills a new struct Flag and registers it into the database.
|
// AddFlagLabel creates and fills a new struct Flag and registers it into the database.
|
||||||
func (k *FlagLabel) Create(e *Exercice) (Flag, error) {
|
func (k *FlagLabel) Create(e *Exercice) (Flag, error) {
|
||||||
if res, err := DBExec("INSERT INTO exercice_flag_labels (id_exercice, ordre, label, variant) VALUES (?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Variant); err != nil {
|
if res, err := DBExec("INSERT INTO exercice_flag_labels (id_exercice, ordre, label, variant) VALUES (?, ?, ?, ?)", e.Id, k.Order, k.Label, k.Variant); err != nil {
|
||||||
|
@ -136,6 +136,54 @@ func (m *MCQ) RecoverId() (Flag, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NbTries returns the MCQ resolution statistics.
|
||||||
|
func (m *MCQ) NbTries() (tries int64, err error) {
|
||||||
|
err = DBQueryRow("SELECT COUNT(*) AS tries FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id).Scan(&tries)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MCQ) TeamsOnIt() ([]int64, error) {
|
||||||
|
if rows, err := DBQuery("SELECT DISTINCT M.id_team FROM exercice_tries_mcq F INNER JOIN exercice_tries T ON T.id_try = F.id_try INNER JOIN teams M ON M.id_team = T.id_team WHERE id_mcq = ?", m.Id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
teams := []int64{}
|
||||||
|
for rows.Next() {
|
||||||
|
var idteam int64
|
||||||
|
if err := rows.Scan(&idteam); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
teams = append(teams, idteam)
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MCQ) DeleteTries() error {
|
||||||
|
if rows, err := DBQuery("SELECT id_try FROM exercice_tries_mcq WHERE id_mcq = ?", m.Id); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var idtry int64
|
||||||
|
err = rows.Scan(&idtry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = DBExec("DELETE FROM exercice_tries WHERE id_try = ?", idtry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create registers a MCQ into the database and recursively add its entries.
|
// Create registers a MCQ into the database and recursively add its entries.
|
||||||
func (m *MCQ) Create(e *Exercice) (Flag, error) {
|
func (m *MCQ) Create(e *Exercice) (Flag, error) {
|
||||||
if res, err := DBExec("INSERT INTO exercice_mcq (id_exercice, ordre, title) VALUES (?, ?, ?)", e.Id, m.Order, m.Title); err != nil {
|
if res, err := DBExec("INSERT INTO exercice_mcq (id_exercice, ordre, title) VALUES (?, ?, ?)", e.Id, m.Order, m.Title); err != nil {
|
||||||
@ -319,6 +367,24 @@ func (m *MCQ) IsOptionnal() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the given vals contains at least a response for the given MCQ.
|
||||||
|
func (m *MCQ) HasOneEntry(v interface{}) bool {
|
||||||
|
var vals map[int]bool
|
||||||
|
if va, ok := v.(map[int]bool); !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
vals = va
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range m.Entries {
|
||||||
|
if _, ok := vals[n.Id]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the given vals are the expected ones to validate this flag.
|
// Check if the given vals are the expected ones to validate this flag.
|
||||||
func (m *MCQ) Check(v interface{}) int {
|
func (m *MCQ) Check(v interface{}) int {
|
||||||
var vals map[int]bool
|
var vals map[int]bool
|
||||||
|
Loading…
x
Reference in New Issue
Block a user