v1 done
This commit is contained in:
parent
f073e69417
commit
0a79763f69
@ -17,6 +17,7 @@ import (
|
||||
var (
|
||||
oidcClientID = ""
|
||||
oidcSecret = ""
|
||||
oidcRedirectURL = "https://srs.nemunai.re"
|
||||
oauth2Config oauth2.Config
|
||||
oidcVerifier *oidc.IDTokenVerifier
|
||||
)
|
||||
@ -24,6 +25,7 @@ var (
|
||||
func init() {
|
||||
flag.StringVar(&oidcClientID, "oidc-clientid", oidcClientID, "ClientID for OIDC")
|
||||
flag.StringVar(&oidcSecret, "oidc-secret", oidcSecret, "Secret for OIDC")
|
||||
flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection")
|
||||
|
||||
router.GET("/auth/CRI", redirectOIDC_CRI)
|
||||
router.GET("/auth/complete", OIDC_CRI_complete)
|
||||
@ -39,7 +41,7 @@ func initializeOIDC() {
|
||||
oauth2Config = oauth2.Config{
|
||||
ClientID: oidcClientID,
|
||||
ClientSecret: oidcSecret,
|
||||
RedirectURL: "http://localhost:8081" + baseURL + "/auth/complete",
|
||||
RedirectURL: oidcRedirectURL + baseURL + "/auth/complete",
|
||||
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.Endpoint(),
|
||||
|
1
db.go
1
db.go
@ -80,6 +80,7 @@ CREATE TABLE IF NOT EXISTS surveys(
|
||||
id_survey INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
title VARCHAR(255),
|
||||
shown BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
corrected BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
start_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
end_availability TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||
|
32
handler.go
32
handler.go
@ -40,7 +40,7 @@ func (r APIResponse) WriteResponse(w http.ResponseWriter) {
|
||||
w.Write(bts)
|
||||
} else if j, err := json.Marshal(r.response); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err), http.StatusInternalServerError)
|
||||
http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err.Error()), http.StatusInternalServerError)
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@ -77,17 +77,17 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
|
||||
if cookie, err := r.Cookie("auth"); err == nil {
|
||||
if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusNotAcceptable)
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusNotAcceptable)
|
||||
return
|
||||
} else if session, err := getSession(sessionid); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
} else if session.IdUser == nil {
|
||||
user = nil
|
||||
} else if std, err := getUser(int(*session.IdUser)); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err), http.StatusUnauthorized)
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg": %q}`, err.Error()), http.StatusUnauthorized)
|
||||
return
|
||||
} else {
|
||||
user = &std
|
||||
@ -98,7 +98,7 @@ func rawHandler(f func(http.ResponseWriter, *http.Request, httprouter.Params, []
|
||||
for _, a := range access {
|
||||
if err := a(user, r); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg":%q}`, err), http.StatusForbidden)
|
||||
http.Error(w, fmt.Sprintf(`{"errmsg":%q}`, err.err.Error()), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -159,10 +159,7 @@ func formatApiResponse(i interface{}, err error) HTTPResponse {
|
||||
func apiAuthHandler(f func(*User, httprouter.Params, []byte) HTTPResponse, access ...func(*User, *http.Request) *APIErrorResponse) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return rawHandler(formatResponseHandler(func (r *http.Request, ps httprouter.Params, b []byte) HTTPResponse {
|
||||
if cookie, err := r.Cookie("auth"); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Authorization required"),
|
||||
}
|
||||
return f(nil, ps, b)
|
||||
} else if sessionid, err := base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusBadRequest,
|
||||
@ -174,10 +171,7 @@ func apiAuthHandler(f func(*User, httprouter.Params, []byte) HTTPResponse, acces
|
||||
err: err,
|
||||
}
|
||||
} else if session.IdUser == nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Authorization required"),
|
||||
}
|
||||
return f(nil, ps, b)
|
||||
} else if std, err := getUser(int(*session.IdUser)); err != nil {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusInternalServerError,
|
||||
@ -189,6 +183,18 @@ func apiAuthHandler(f func(*User, httprouter.Params, []byte) HTTPResponse, acces
|
||||
}), access...)
|
||||
}
|
||||
|
||||
func loggedUser(u *User, r *http.Request) *APIErrorResponse {
|
||||
if u != nil {
|
||||
return nil
|
||||
} else {
|
||||
ret := &APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Permission Denied"),
|
||||
}
|
||||
return ret
|
||||
}
|
||||
}
|
||||
|
||||
func adminRestricted(u *User, r *http.Request) *APIErrorResponse {
|
||||
if u != nil && u.IsAdmin {
|
||||
return nil
|
||||
|
BIN
htdocs/img/srstamps.png
Normal file
BIN
htdocs/img/srstamps.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@ -1,50 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="AtsebaytApp">
|
||||
<html ng-app="AtsebaytApp" ng-jq="jQuery">
|
||||
<head>
|
||||
<base href="/">
|
||||
<meta charset="utf-8">
|
||||
<title>ât sebay.t SRS: MCQ and others course related stuff</title>
|
||||
<title>SRS: MCQ and others course related stuff</title>
|
||||
<link href="css/bootstrap.min.css" type="text/css" rel="stylesheet">
|
||||
<style>
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
|
||||
display: none !important;
|
||||
[ng-cloak] {
|
||||
display:none !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="position: fixed; bottom: 20px; right: 20px; z-index: -1; background-image: url('img/srstamps.png'); background-size: cover; width: 125px; height: 125px;">
|
||||
</div>
|
||||
|
||||
<nav class="navbar sticky-top navbar-expand-sm navbar-dark bg-primary">
|
||||
<a class="navbar-brand" href=".">
|
||||
ât sebay.t
|
||||
SRS
|
||||
</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#adminMenu" aria-controls="adminMenu" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="loggedMenu" ng-if="isLogged">
|
||||
<div class="collapse navbar-collapse" id="loggedMenu">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="surveys">Questionnaires</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="users">Utilisateurs</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="adlin" target="_self">AdLin</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="fic" target="_self">FIC</a></li>
|
||||
<li class="nav-item"><a class="nav-link" ng-class="{'active': qactive}" href="surveys">Questionnaires</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
||||
</ul>
|
||||
|
||||
<button class="navbar-text btn btn-link" ng-click="switchAdminMode()" ng-if="user.was_admin">
|
||||
<button class="navbar-text btn btn-link" ng-click="switchAdminMode()" ng-if="user.was_admin" ng-cloak>
|
||||
Vue admin
|
||||
</button>
|
||||
<button class="navbar-text btn btn-link" ng-click="switchAdminMode()" ng-if="user.is_admin">
|
||||
<button class="navbar-text btn btn-link" ng-click="switchAdminMode()" ng-if="user.is_admin" ng-cloak>
|
||||
Vue étudiant
|
||||
</button>
|
||||
<button class="navbar-text btn btn-link" ng-click="disconnectCurrentUser()">
|
||||
<button class="navbar-text btn btn-link" ng-click="disconnectCurrentUser()" ng-if="isLogged" ng-cloak>
|
||||
Se déconnecter
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="loggedMenu" ng-if="!isLogged">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="auth">Se connecter</a></li>
|
||||
</ul>
|
||||
<a href="auth/CRI" target="_self" class="navbar-text btn btn-dark" ng-if="!isLogged" ng-cloak>
|
||||
Se connecter
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-1" ng-view></div>
|
||||
|
||||
<div style="position: fixed; top: 60px; right: 0; z-index: 10; min-width: 30vw;">
|
||||
<toast ng-repeat="toast in toasts" date="toast.date" msg="toast.msg" title="toast.title" variant="toast.variant"></toast>
|
||||
</div>
|
||||
|
||||
<script src="js/jquery-3.4.1.slim.min.js"></script>
|
||||
<script src="js/bootstrap.min.js"></script>
|
||||
<script src="js/angular.min.js"></script>
|
||||
|
@ -13,26 +13,38 @@ angular.module("AtsebaytApp", ["ngRoute", "ngResource", "ngSanitize"])
|
||||
controller: "SurveyController",
|
||||
templateUrl: "views/survey.html"
|
||||
})
|
||||
.when("/users", {
|
||||
controller: "UsersController",
|
||||
templateUrl: "views/users.html"
|
||||
.when("/surveys/:surveyId/responses", {
|
||||
controller: "SurveyController",
|
||||
templateUrl: "views/responses.html"
|
||||
})
|
||||
.when("/users/:userId", {
|
||||
controller: "UserController",
|
||||
templateUrl: "views/user.html"
|
||||
.when("/surveys/:surveyId/responses/:questId", {
|
||||
controller: "QuestionController",
|
||||
templateUrl: "views/correction.html"
|
||||
})
|
||||
.when("/", {
|
||||
controller: "SurveysController",
|
||||
templateUrl: "views/home.html"
|
||||
});
|
||||
$locationProvider.html5Mode(true);
|
||||
});
|
||||
|
||||
angular.module("AtsebaytApp")
|
||||
.factory("AllResponses", function($resource) {
|
||||
return $resource("/api/surveys/:surveyId/questions/:questId/responses/:respId", { surveyId: '@id', questId: '@id', respId: '@id' }, {
|
||||
'update': {method: 'PUT'},
|
||||
})
|
||||
})
|
||||
.factory("MyResponse", function($resource) {
|
||||
return $resource("/api/surveys/:surveyId/responses/:respId", { surveyId: '@id', respId: '@id' })
|
||||
})
|
||||
.factory("Survey", function($resource) {
|
||||
return $resource("/api/surveys/:surveyId", { surveyId: '@id' }, {
|
||||
'update': {method: 'PUT'},
|
||||
})
|
||||
})
|
||||
.factory("SurveyScore", function($resource) {
|
||||
return $resource("/api/surveys/:surveyId/score", { surveyId: '@id' })
|
||||
})
|
||||
.factory("SurveyQuest", function($resource) {
|
||||
return $resource("/api/surveys/:surveyId/questions/:questId", { surveyId: '@id', questId: '@id' }, {
|
||||
'update': {method: 'PUT'},
|
||||
@ -44,6 +56,110 @@ angular.module("AtsebaytApp")
|
||||
})
|
||||
});
|
||||
|
||||
angular.module("AtsebaytApp")
|
||||
.directive('integer', function() {
|
||||
return {
|
||||
require: 'ngModel',
|
||||
link: function(scope, ele, attr, ctrl){
|
||||
ctrl.$parsers.unshift(function(viewValue){
|
||||
return parseInt(viewValue, 10);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
.component('toast', {
|
||||
bindings: {
|
||||
date: '=',
|
||||
msg: '=',
|
||||
title: '=',
|
||||
variant: '=',
|
||||
},
|
||||
controller: function($element) {
|
||||
$element.children(0).toast('show')
|
||||
},
|
||||
template: `<div class="toast mb-2" role="alert" aria-live="assertive" aria-atomic="true" data-delay="7000">
|
||||
<div class="toast-header">
|
||||
<span ng-if="$ctrl.variant" class="badge badge-pill badge-{{ $ctrl.variant }}" style="padding: .25em .66em"> </span>
|
||||
<strong class="mr-auto" ng-bind="$ctrl.title"></strong>
|
||||
<small class="text-muted" ng-bind="$ctrl.date">just now</small>
|
||||
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="toast-body" ng-bind-html="$ctrl.msg"></div>
|
||||
</div>`
|
||||
})
|
||||
|
||||
.component('surveyList', {
|
||||
bindings: {
|
||||
surveys: '=',
|
||||
islogged: '=',
|
||||
isadmin: '=',
|
||||
},
|
||||
controller: function($location, $rootScope) {
|
||||
this.now = Date.now();
|
||||
this.show = function(id) {
|
||||
if ($rootScope.isLogged) {
|
||||
$location.url("surveys/" + id);
|
||||
} else {
|
||||
$rootScope.addToast({
|
||||
variant: "danger",
|
||||
title: "Authentification requise",
|
||||
msg: "Vous devez être connecté pour accéder aux questionnaires.",
|
||||
});
|
||||
$location.url("auth");
|
||||
}
|
||||
};
|
||||
},
|
||||
template: `<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Intitulé</th>
|
||||
<th>État</th>
|
||||
<th>Date</th>
|
||||
<th ng-if="$ctrl.islogged">Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="cursor: pointer;">
|
||||
<tr ng-repeat="survey in $ctrl.surveys" ng-if="survey.shown" ng-click="$ctrl.show(survey.id)">
|
||||
<td>{{ survey.title }}</td>
|
||||
<td class="bg-info" ng-if="survey.start_availability > $ctrl.now">Prévu</td>
|
||||
<td class="bg-warning" ng-if="survey.start_availability <= $ctrl.now && survey.end_availability >= $ctrl.now">En cours</td>
|
||||
<td class="bg-primary" ng-if="survey.end_availability < $ctrl.now && !survey.corrected">Terminé</td>
|
||||
<td class="bg-success" ng-if="survey.end_availability < $ctrl.now && survey.corrected">Corrigé</td>
|
||||
<td ng-if="survey.start_availability > $ctrl.now">{{ survey.start_availability | date: "medium" }}</td>
|
||||
<td ng-if="survey.start_availability <= $ctrl.now">{{ survey.end_availability | date: "medium" }}</td>
|
||||
<td ng-if="$ctrl.islogged">N/A</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot ng-if="$ctrl.isadmin">
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<a href="surveys/new" class="btn btn-sm btn-primary">Ajouter un questionnaire</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>`
|
||||
})
|
||||
|
||||
|
||||
.component('surveyBadges', {
|
||||
bindings: {
|
||||
survey: '=',
|
||||
},
|
||||
controller: function() {
|
||||
var now = Date.now()
|
||||
this.test = function(a, b) {
|
||||
return Date.parse(a) > now;
|
||||
}
|
||||
},
|
||||
template: `<span class="badge badge-danger" ng-if="$ctrl.test($ctrl.survey.start_availability)">Prévu</span>
|
||||
<span class="badge badge-warning" ng-if="!$ctrl.test($ctrl.survey.start_availability) && $ctrl.test($ctrl.survey.end_availability)">En cours</span>
|
||||
<span class="badge badge-primary" ng-if="!$ctrl.test($ctrl.survey.end_availability)">Clos</span>
|
||||
<span class="badge badge-success" ng-if="$ctrl.survey.corrected">Corrigé</span>`
|
||||
});
|
||||
|
||||
angular.module("AtsebaytApp")
|
||||
.run(function($rootScope, $interval, $http) {
|
||||
$rootScope.checkLoginState = function() {
|
||||
@ -66,6 +182,12 @@ angular.module("AtsebaytApp")
|
||||
$rootScope.user.was_admin = tmp;
|
||||
}
|
||||
|
||||
$rootScope.toasts = [];
|
||||
|
||||
$rootScope.addToast = function(toast) {
|
||||
$rootScope.toasts.unshift(toast);
|
||||
}
|
||||
|
||||
$rootScope.disconnectCurrentUser = function() {
|
||||
$http({
|
||||
method: 'POST',
|
||||
@ -101,8 +223,8 @@ angular.module("AtsebaytApp")
|
||||
}
|
||||
})
|
||||
|
||||
.controller("SurveysController", function($scope, Survey, $location) {
|
||||
$scope.now = Date.now();
|
||||
.controller("SurveysController", function($scope, $rootScope, Survey, $location) {
|
||||
$rootScope.qactive = $location.$$path == "/surveys";
|
||||
$scope.surveys = Survey.query();
|
||||
$scope.surveys.$promise.then(function(data) {
|
||||
data.forEach(function(d,k) {
|
||||
@ -110,18 +232,13 @@ angular.module("AtsebaytApp")
|
||||
data[k].end_availability = Date.parse(data[k].end_availability)
|
||||
})
|
||||
})
|
||||
|
||||
$scope.show = function(id) {
|
||||
$location.url("surveys/" + id);
|
||||
};
|
||||
})
|
||||
|
||||
.controller("SurveyController", function($scope, Survey, $routeParams, $location) {
|
||||
$scope.now = Date.now();
|
||||
.controller("SurveyController", function($scope, $rootScope, Survey, $routeParams, $location) {
|
||||
$rootScope.qactive = true;
|
||||
$scope.survey = Survey.get({ surveyId: $routeParams.surveyId });
|
||||
$scope.survey.$promise.then(function(survey) {
|
||||
survey.start_availability = Date.parse(survey.start_availability)
|
||||
survey.end_availability = Date.parse(survey.end_availability)
|
||||
survey.readonly = Date.now() > Date.parse(survey.end_availability)
|
||||
})
|
||||
|
||||
$scope.saveSurvey = function() {
|
||||
@ -143,10 +260,59 @@ angular.module("AtsebaytApp")
|
||||
$scope.deleteSurvey = function() {
|
||||
this.survey.$remove(function() { $location.url("/surveys/");});
|
||||
}
|
||||
|
||||
$scope.showResponses = function() {
|
||||
$location.url("surveys/" + this.survey.id + "/responses/" + this.question.id );
|
||||
}
|
||||
})
|
||||
|
||||
.controller("QuestionsController", function($scope, SurveyQuest, $http, $location) {
|
||||
.controller("ResponsesController", function($scope, AllResponses) {
|
||||
$scope.responses = AllResponses.query({ surveyId: $scope.survey.id, questId: $scope.question.id });
|
||||
})
|
||||
|
||||
.controller("ScoreController", function($scope, SurveyScore) {
|
||||
$scope.score = SurveyScore.get({ surveyId: $scope.survey.id, questId: $scope.question.id })
|
||||
})
|
||||
|
||||
.controller("QuestionController", function($scope, Survey, SurveyQuest, SurveyQuest, AllResponses, $http, $routeParams) {
|
||||
$scope.survey = Survey.get({ surveyId: $routeParams.surveyId });
|
||||
$scope.survey.$promise.then(function(survey) {
|
||||
survey.start_availability = Date.parse(survey.start_availability)
|
||||
survey.end_availability = Date.parse(survey.end_availability)
|
||||
})
|
||||
$scope.question = SurveyQuest.get({ surveyId: $routeParams.surveyId, questId: $routeParams.questId });
|
||||
$scope.responses = AllResponses.query({ surveyId: $routeParams.surveyId, questId: $routeParams.questId });
|
||||
|
||||
$scope.submitCorrection = function() {
|
||||
this.response.$update()
|
||||
}
|
||||
$scope.submitCorrections = function() {
|
||||
this.responses.forEach(function(response) {
|
||||
response.$update()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
.controller("QuestionsController", function($scope, SurveyQuest, MyResponse, $http, $location) {
|
||||
$scope.questions = SurveyQuest.query({ surveyId: $scope.survey.id });
|
||||
$scope.myresponses = MyResponse.query({ surveyId: $scope.survey.id });
|
||||
$scope.myresponses.$promise.then(function (responses) {
|
||||
$scope.questions.$promise.then(function (questions) {
|
||||
var idxquestions = {}
|
||||
questions.forEach(function(question, qid) {
|
||||
idxquestions[question.id] = qid;
|
||||
});
|
||||
|
||||
responses.forEach(function(response) {
|
||||
if (!questions[idxquestions[response.id_question]].response) {
|
||||
if (response.value) {
|
||||
questions[idxquestions[response.id_question]].value = response.value;
|
||||
questions[idxquestions[response.id_question]].response = response;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
$scope.submitAnswers = function() {
|
||||
$scope.submitInProgress = true;
|
||||
@ -154,16 +320,24 @@ angular.module("AtsebaytApp")
|
||||
$scope.questions.forEach(function(q) {
|
||||
res.push({"id_question": q.id, "value": q.value})
|
||||
});
|
||||
console.log(res)
|
||||
$http({
|
||||
url: "/api/surveys/" + $scope.survey.id,
|
||||
data: res,
|
||||
method: "POST"
|
||||
}).then(function(response) {
|
||||
$scope.submitInProgress = false;
|
||||
$scope.addToast({
|
||||
variant: "success",
|
||||
title: $scope.survey.title,
|
||||
msg: "Vos réponses ont bien étés sauvegardées.",
|
||||
});
|
||||
}, function(response) {
|
||||
$scope.submitInProgress = false;
|
||||
alert("Une erreur s'est produite durant l'envoie de vos réponses : " + response.data.errmsg + "\nVeuillez réessayer dans quelques instants.");
|
||||
$scope.addToast({
|
||||
variant: "danger",
|
||||
title: $scope.survey.title,
|
||||
msg: "Une erreur s'est produite durant l'envoie de vos réponses : " + (response.data ? response.data.errmsg : "impossible de contacter le serveur") + "<br>Veuillez réessayer dans quelques instants.",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,13 @@
|
||||
<h2>Accès à votre compte</h2>
|
||||
<div class="form-group">
|
||||
<label for="login">CRI login</label>
|
||||
<input class="form-control" id="login" ng-model="auth.username" placeholder="Entrer votre login" autofocus>
|
||||
<input class="form-control" id="login" ng-model="auth.username" placeholder="Entrer votre login" autofocus disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Mot de passe</label>
|
||||
<input type="password" class="form-control" id="password" ng-model="auth.password" placeholder="Mot de passe">
|
||||
<input type="password" class="form-control" id="password" ng-model="auth.password" placeholder="Mot de passe" disabled>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="btn btn-secondary disabled" disabled>
|
||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true" ng-show="pleaseWait"></span>
|
||||
Me connecter
|
||||
</button>
|
||||
|
46
htdocs/views/correction.html
Normal file
46
htdocs/views/correction.html
Normal file
@ -0,0 +1,46 @@
|
||||
<h2>
|
||||
<span class="text-muted">{{ survey.title }}</span>
|
||||
<small>
|
||||
<span class="badge badge-danger" ng-if="survey.start_availability > now">Prévu</span>
|
||||
<span class="badge badge-warning" ng-if="survey.start_availability <= now && survey.end_availability >= now">En cours</span>
|
||||
<span class="badge badge-danger" ng-if="survey.end_availability < now">Clos</span>
|
||||
</small>
|
||||
</h2>
|
||||
<h3>
|
||||
{{ question.title }}
|
||||
</h3>
|
||||
|
||||
<form class="mb-5" ng-submit="submitCorrections()" ng-cloak>
|
||||
<div class="card mb-2" ng-repeat="response in responses">
|
||||
<div class="card-body">
|
||||
<button class="btn btn-success ml-1 float-right" ng-click="submitCorrection()"><svg class="bi bi-check" width="1em" height="1em" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M15.854 5.646a.5.5 0 010 .708l-7 7a.5.5 0 01-.708 0l-3.5-3.5a.5.5 0 11.708-.708L8.5 12.293l6.646-6.647a.5.5 0 01.708 0z" clip-rule="evenodd"></path></svg></button>
|
||||
<p class="card-text" style="white-space: pre-line" ng-bind="response.value"></p>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="input-group mb-1">
|
||||
<input class="form-control" type="number" ng-model="response.score" integer>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">/100</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="1" ng-model="response.score_explaination" placeholder="Appréciation"></textarea>
|
||||
</div>
|
||||
<div class="form-group row" ng-if="response.time_scored">
|
||||
<label class="col-2 col-form-label col-form-label-sm">Correction effectuée :</label>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control-plaintext form-control-sm" value="{{ response.time_scored | date:'medium'}}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row" ng-if="response.time_scored">
|
||||
<label class="col-2 col-form-label col-form-label-sm">Correcteur :</label>
|
||||
<div class="col-10">
|
||||
<input type="text" class="form-control-plaintext form-control-sm" value="{{ response.id_corrector }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary" ng-disabled="submitInProgress">Soumettre les corrections</button>
|
||||
</form>
|
@ -1,5 +1,5 @@
|
||||
<div class="jumbotron mt-2">
|
||||
<img class="float-right" src="https://photos.cri.epita.fr/thumb/mercie_d" alt="avatar" style="max-height: 150px">
|
||||
<div class="jumbotron mt-2" ng-if="isLogged">
|
||||
<img class="float-right" src="https://photos.cri.epita.fr/thumb/{{ isLogged.login }}" alt="avatar" style="max-height: 150px">
|
||||
<h1>
|
||||
Bienvenue {{ isLogged.login }} !
|
||||
</h1>
|
||||
@ -7,20 +7,17 @@
|
||||
|
||||
<p class="lead">Voici la liste des questionnaires :</p>
|
||||
|
||||
<table class="table" ng-controller="SurveysController">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Intitulé</th>
|
||||
<th>État</th>
|
||||
<th>Date</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="survey in surveys">
|
||||
<td>{{ survey }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<survey-list surveys="surveys" islogged="isLogged"></survey-list>
|
||||
</div>
|
||||
|
||||
<div class="mt-2" ng-if="!isLogged">
|
||||
<h3>Cours</h3>
|
||||
<ul>
|
||||
<li><a href="adlin" target="_self">ADministration Linux avancée</a></li>
|
||||
<li><a href="fic" target="_self">FIC</a></li>
|
||||
<li><a href="virli" target="_self">Virtualisation légère</a></li>
|
||||
</ul>
|
||||
|
||||
<h3>Questionnaires</h3>
|
||||
<survey-list surveys="surveys"></survey-list>
|
||||
</div>
|
||||
|
25
htdocs/views/responses.html
Normal file
25
htdocs/views/responses.html
Normal file
@ -0,0 +1,25 @@
|
||||
<h2>
|
||||
{{ survey.title }}
|
||||
<small>
|
||||
<span class="badge badge-danger" ng-if="survey.start_availability > now">Prévu</span>
|
||||
<span class="badge badge-warning" ng-if="survey.start_availability <= now && survey.end_availability >= now">En cours</span>
|
||||
<span class="badge badge-danger" ng-if="survey.end_availability < now">Clos</span>
|
||||
</small>
|
||||
</h2>
|
||||
|
||||
<table class="table table-hover table-striped mb-5" ng-controller="QuestionsController" ng-if="survey.id" ng-cloak>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Question</th>
|
||||
<th>Réponses</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="(qid,question) in questions" ng-click="showResponses()" ng-controller="ResponsesController">
|
||||
<td ng-bind="question.title"></td>
|
||||
<td>{{ responses.length }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -1,12 +1,9 @@
|
||||
<button class="btn btn-primary ml-1 float-right" ng-click="editSurvey()" ng-if="user.is_admin && !survey.edit"><svg class="bi bi-pencil" width="1em" height="1em" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M13.293 3.293a1 1 0 011.414 0l2 2a1 1 0 010 1.414l-9 9a1 1 0 01-.39.242l-3 1a1 1 0 01-1.266-1.265l1-3a1 1 0 01.242-.391l9-9zM14 4l2 2-9 9-3 1 1-3 9-9z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M14.146 8.354l-2.5-2.5.708-.708 2.5 2.5-.708.708zM5 12v.5a.5.5 0 00.5.5H6v.5a.5.5 0 00.5.5H7v.5a.5.5 0 00.5.5H8v-1.5a.5.5 0 00-.5-.5H7v-.5a.5.5 0 00-.5-.5H5z" clip-rule="evenodd"></path></svg></button>
|
||||
<a href="surveys/{{ survey.id }}/responses" class="btn btn-success ml-1 float-right" ng-if="user.is_admin"><svg class="bi bi-documents" width="1em" height="1em" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 4h8a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V6a2 2 0 012-2zm0 1a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V6a1 1 0 00-1-1H5z" clip-rule="evenodd"></path><path d="M7 2h8a2 2 0 012 2v10a2 2 0 01-2 2v-1a1 1 0 001-1V4a1 1 0 00-1-1H7a1 1 0 00-1 1H5a2 2 0 012-2z"></path></svg></a>
|
||||
<h2>
|
||||
{{ survey.title }}
|
||||
<small>
|
||||
<span class="badge badge-danger" ng-if="survey.start_availability > now">Prévu</span>
|
||||
<span class="badge badge-warning" ng-if="survey.start_availability <= now && survey.end_availability >= now">En cours</span>
|
||||
<span class="badge badge-danger" ng-if="survey.end_availability < now">Clos</span>
|
||||
<span class="badge badge-success">Validé</span>
|
||||
<span class="badge badge-info">Corrigé</span>
|
||||
<survey-badges survey="survey"></survey-badges>
|
||||
</small>
|
||||
</h2>
|
||||
|
||||
@ -40,6 +37,13 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="corrected" ng-model="survey.corrected">
|
||||
<label class="form-check-label" for="corrected">
|
||||
Marqué comme corrigé
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
@ -50,6 +54,7 @@
|
||||
</form>
|
||||
|
||||
<form class="mb-5" ng-submit="submitAnswers()" ng-controller="QuestionsController" ng-if="survey.id" ng-cloak>
|
||||
|
||||
<div class="card mb-2" ng-repeat="(qid,question) in questions">
|
||||
<div class="card-body">
|
||||
<button class="btn btn-danger ml-1 float-right" ng-click="deleteQuestion()" ng-if="user.is_admin"><svg class="bi bi-trash-fill" width="1em" height="1em" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.5 3a1 1 0 00-1 1v1a1 1 0 001 1H5v9a2 2 0 002 2h6a2 2 0 002-2V6h.5a1 1 0 001-1V4a1 1 0 00-1-1H12a1 1 0 00-1-1H9a1 1 0 00-1 1H4.5zm3 4a.5.5 0 01.5.5v7a.5.5 0 01-1 0v-7a.5.5 0 01.5-.5zM10 7a.5.5 0 01.5.5v7a.5.5 0 01-1 0v-7A.5.5 0 0110 7zm3 .5a.5.5 0 00-1 0v7a.5.5 0 001 0v-7z" clip-rule="evenodd"></path></svg></button>
|
||||
@ -72,7 +77,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea class="form-control" ng-if="!question.edit && question.kind == 'text'" rows="6" ng-model="question.value" placeholder="{{ question.placeholder }}"></textarea>
|
||||
<textarea class="form-control" ng-if="!question.edit && question.kind == 'text' && !survey.readonly" rows="6" ng-model="question.value" placeholder="{{ question.placeholder }}"></textarea>
|
||||
<p class="card-text alert alert-secondary" style="white-space: pre-line" ng-bind="question.value" ng-if="!question.edit && survey.readonly"></p>
|
||||
<div class="form-group row" ng-if="question.edit && question.kind == 'text'">
|
||||
<label class="col-2 col-form-label" for="q{{qid}}placeholder">Placeholder</label>
|
||||
<div class="col">
|
||||
@ -81,6 +87,8 @@
|
||||
</div>
|
||||
|
||||
<p class="card-text text-center text-danger" ng-if="question.kind == 'mcq'">QCM non implémentés</p>
|
||||
|
||||
<p class="card-text alert alert-success" style="white-space: pre-line" ng-if="!question.edit && (question.response.score_explaination || question.response.score)"><strong>{{question.response.score}} :</strong> {{ question.response.score_explaination }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,28 +1 @@
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Intitulé</th>
|
||||
<th>État</th>
|
||||
<th>Date</th>
|
||||
<th>Score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody style="cursor: pointer;">
|
||||
<tr ng-repeat="survey in surveys" ng-if="survey.shown" ng-click="show(survey.id)">
|
||||
<td>{{ survey.title }}</td>
|
||||
<td class="bg-info" ng-if="survey.start_availability > now">Prévu</td>
|
||||
<td class="bg-warning" ng-if="survey.start_availability <= now && survey.end_availability >= now">En cours</td>
|
||||
<td class="bg-success" ng-if="survey.end_availability < now">Terminé</td>
|
||||
<td ng-if="survey.start_availability > now">{{ survey.start_availability | date: "medium" }}</td>
|
||||
<td ng-if="survey.start_availability <= now">{{ survey.end_availability | date: "medium" }}</td>
|
||||
<td>N/A</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot ng-if="user.is_admin">
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
<a href="surveys/new" class="btn btn-sm btn-primary">Ajouter un questionnaire</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<survey-list surveys="surveys" islogged="isLogged" isadmin="user.is_admin"></survey-list>
|
||||
|
30
questions.go
30
questions.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -12,12 +13,19 @@ func init() {
|
||||
router.GET("/api/questions", apiHandler(
|
||||
func(httprouter.Params, []byte) HTTPResponse {
|
||||
return formatApiResponse(getQuestions())
|
||||
}))
|
||||
router.GET("/api/surveys/:sid/questions", apiHandler(surveyHandler(
|
||||
func(s Survey, _ []byte) HTTPResponse {
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid/questions", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
if !s.Shown && !u.IsAdmin {
|
||||
return APIErrorResponse{err:errors.New("Not accessible")}
|
||||
}
|
||||
return formatApiResponse(s.GetQuestions())
|
||||
})))
|
||||
router.POST("/api/surveys/:sid/questions", apiHandler(surveyHandler(func(s Survey, body []byte) HTTPResponse {
|
||||
}), loggedUser))
|
||||
router.POST("/api/surveys/:sid/questions", apiAuthHandler(surveyAuthHandler(func(s Survey, u *User, body []byte) HTTPResponse {
|
||||
if !s.Shown && !u.IsAdmin {
|
||||
return APIErrorResponse{err:errors.New("Not accessible")}
|
||||
}
|
||||
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err:err}
|
||||
@ -28,11 +36,11 @@ func init() {
|
||||
router.GET("/api/questions/:qid", apiHandler(questionHandler(
|
||||
func(s Question, _ []byte) HTTPResponse {
|
||||
return APIResponse{s}
|
||||
})))
|
||||
}), adminRestricted))
|
||||
router.GET("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(
|
||||
func(s Question, _ []byte) HTTPResponse {
|
||||
return APIResponse{s}
|
||||
})))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/questions/:qid", apiHandler(questionHandler(func(current Question, body []byte) HTTPResponse {
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
@ -41,7 +49,7 @@ func init() {
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
})))
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(func(current Question, body []byte) HTTPResponse {
|
||||
var new Question
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
@ -50,15 +58,15 @@ func init() {
|
||||
|
||||
new.Id = current.Id
|
||||
return formatApiResponse(new.Update())
|
||||
})))
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/questions/:qid", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.Delete())
|
||||
})))
|
||||
}), adminRestricted))
|
||||
router.DELETE("/api/surveys/:sid/questions/:qid", apiHandler(questionHandler(
|
||||
func(q Question, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.Delete())
|
||||
})))
|
||||
}), adminRestricted))
|
||||
}
|
||||
|
||||
func questionHandler(f func(Question, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
|
91
responses.go
91
responses.go
@ -2,8 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
"strconv"
|
||||
|
||||
@ -18,37 +16,43 @@ func init() {
|
||||
}
|
||||
|
||||
for _, response := range responses {
|
||||
if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil {
|
||||
return APIErrorResponse{err:err}
|
||||
if len(response.Answer) > 0 {
|
||||
if _, err := s.NewResponse(response.IdQuestion, u.Id, response.Answer); err != nil {
|
||||
return APIErrorResponse{err:err}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return APIResponse{true}
|
||||
})))
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/responses", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.GetMyResponses(u))
|
||||
})))
|
||||
return formatApiResponse(s.GetMyResponses(u, s.Corrected))
|
||||
}), loggedUser))
|
||||
router.GET("/api/surveys/:sid/responses/:rid", apiAuthHandler(responseAuthHandler(
|
||||
func(r Response, _ *User, _ []byte) HTTPResponse {
|
||||
return APIResponse{r}
|
||||
})))
|
||||
router.PUT("/api/surveys/:sid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse {
|
||||
if u.Id != current.IdUser && !u.IsAdmin {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not Authorized"),
|
||||
}
|
||||
}
|
||||
}), adminRestricted))
|
||||
router.GET("/api/surveys/:sid/questions/:qid/responses", apiAuthHandler(questionAuthHandler(
|
||||
func(q Question, u *User, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(q.GetResponses())
|
||||
}), adminRestricted))
|
||||
router.PUT("/api/surveys/:sid/questions/:qid/responses/:rid", apiAuthHandler(responseAuthHandler(func(current Response, u *User, body []byte) HTTPResponse {
|
||||
var new Response
|
||||
if err := json.Unmarshal(body, &new); err != nil {
|
||||
return APIErrorResponse{err:err}
|
||||
}
|
||||
|
||||
if new.Score != nil && (current.Score != nil || *new.Score != *current.Score) {
|
||||
now := time.Now()
|
||||
new.IdCorrector = &u.Id
|
||||
new.TimeScored = &now
|
||||
}
|
||||
|
||||
new.Id = current.Id
|
||||
new.IdUser = current.IdUser
|
||||
return formatApiResponse(new.Update())
|
||||
})))
|
||||
}), adminRestricted))
|
||||
}
|
||||
|
||||
func responseHandler(f func(Response, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
@ -88,15 +92,15 @@ func responseAuthHandler(f func(Response, *User, []byte) HTTPResponse) func(*Use
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Id int64 `json:"id"`
|
||||
IdQuestion int64 `json:"id_question"`
|
||||
IdUser int64 `json:"id_user"`
|
||||
Answer string `json:"value"`
|
||||
TimeSubmit time.Time `json:"time_submit"`
|
||||
Score *int64 `json:"score,omitempty"`
|
||||
ScoreExplanation *string `json:"score_explanation,omitempty"`
|
||||
IdCorrector *int64 `json:"id_corrector,omitempty"`
|
||||
TimeScored *time.Time `json:"time_scored,omitempty"`
|
||||
Id int64 `json:"id"`
|
||||
IdQuestion int64 `json:"id_question"`
|
||||
IdUser int64 `json:"id_user"`
|
||||
Answer string `json:"value"`
|
||||
TimeSubmit time.Time `json:"time_submit"`
|
||||
Score *int64 `json:"score,omitempty"`
|
||||
ScoreExplaination *string `json:"score_explaination,omitempty"`
|
||||
IdCorrector *int64 `json:"id_corrector,omitempty"`
|
||||
TimeScored *time.Time `json:"time_scored,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
@ -107,7 +111,7 @@ func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
|
||||
for rows.Next() {
|
||||
var r Response
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.TimeSubmit, &r.Score, &r.ScoreExplanation, &r.IdCorrector, &r.TimeScored); err != nil {
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil {
|
||||
return
|
||||
}
|
||||
responses = append(responses, r)
|
||||
@ -120,7 +124,7 @@ func (s *Survey) GetResponses() (responses []Response, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Survey) GetMyResponses(u *User) (responses []Response, err error) {
|
||||
func (s *Survey) GetMyResponses(u *User, showScore bool) (responses []Response, err error) {
|
||||
if rows, errr := DBQuery("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=? AND R.id_user=? ORDER BY time_submit DESC", s.Id, u.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
@ -128,7 +132,32 @@ func (s *Survey) GetMyResponses(u *User) (responses []Response, err error) {
|
||||
|
||||
for rows.Next() {
|
||||
var r Response
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.TimeSubmit, &r.Score, &r.ScoreExplanation, &r.IdCorrector, &r.TimeScored); err != nil {
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil {
|
||||
return
|
||||
}
|
||||
if !showScore {
|
||||
r.Score = nil
|
||||
r.ScoreExplaination = nil
|
||||
}
|
||||
responses = append(responses, r)
|
||||
}
|
||||
if err = rows.Err(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Question) GetResponses() (responses []Response, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_response, id_question, S.id_user, answer, S.time_submit, score, score_explanation, id_corrector, time_scored FROM (SELECT id_user, MAX(time_submit) AS time_submit FROM survey_responses WHERE id_question=? GROUP BY id_user) R INNER JOIN survey_responses S ON S.id_user = R.id_user AND S.time_submit = R.time_submit AND S.id_question=?", q.Id, q.Id); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var r Response
|
||||
if err = rows.Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored); err != nil {
|
||||
return
|
||||
}
|
||||
responses = append(responses, r)
|
||||
@ -142,12 +171,12 @@ func (s *Survey) GetMyResponses(u *User) (responses []Response, err error) {
|
||||
}
|
||||
|
||||
func getResponse(id int) (r Response, err error) {
|
||||
err = DBQueryRow("SELECT id_response, id_question, id_user, answer, time_submit, score, score_explanation, id_corrector, time_scored FROM survey_responses WHERE id_response=?", id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.TimeSubmit, &r.Score, &r.ScoreExplanation, &r.IdCorrector, &r.TimeScored)
|
||||
err = DBQueryRow("SELECT id_response, id_question, id_user, answer, time_submit, score, score_explanation, id_corrector, time_scored FROM survey_responses WHERE id_response=?", id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Survey) GetResponse(id int) (r Response, err error) {
|
||||
err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE R.id_response=? AND Q.id_survey=?", id, s.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.TimeSubmit, &r.Score, &r.ScoreExplanation, &r.IdCorrector, &r.TimeScored)
|
||||
err = DBQueryRow("SELECT R.id_response, R.id_question, R.id_user, R.answer, R.time_submit, R.score, R.score_explanation, R.id_corrector, R.time_scored FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE R.id_response=? AND Q.id_survey=?", id, s.Id).Scan(&r.Id, &r.IdQuestion, &r.IdUser, &r.Answer, &r.TimeSubmit, &r.Score, &r.ScoreExplaination, &r.IdCorrector, &r.TimeScored)
|
||||
return
|
||||
}
|
||||
|
||||
@ -162,7 +191,7 @@ func (s *Survey) NewResponse(id_question int64, id_user int64, response string)
|
||||
}
|
||||
|
||||
func (r Response) Update() (Response, error) {
|
||||
_, err := DBExec("UPDATE survey_responses SET id_question = ?, id_user = ?, answer = ?, time_submit = ?, score = ?, score_explanation = ?, id_corrector = ?, time_scored = ? WHERE id_response = ?", r.IdQuestion, r.IdUser, r.Answer, r.TimeSubmit, r.Score, r.ScoreExplanation, r.IdCorrector, r.TimeScored, r.Id)
|
||||
_, err := DBExec("UPDATE survey_responses SET id_question = ?, id_user = ?, answer = ?, time_submit = ?, score = ?, score_explanation = ?, id_corrector = ?, time_scored = ? WHERE id_response = ?", r.IdQuestion, r.IdUser, r.Answer, r.TimeSubmit, r.Score, r.ScoreExplaination, r.IdCorrector, r.TimeScored, r.Id)
|
||||
return r, err
|
||||
}
|
||||
|
||||
|
@ -27,12 +27,6 @@ func init() {
|
||||
Router().GET("/surveys/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||
})
|
||||
Router().GET("/users", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||
})
|
||||
Router().GET("/users/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||
})
|
||||
Router().GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
serveStaticAsset(w, r)
|
||||
})
|
||||
|
14
static.go
14
static.go
@ -28,6 +28,20 @@ func init() {
|
||||
w.Write(data)
|
||||
}
|
||||
})
|
||||
Router().GET("/surveys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
if data, err := Asset("htdocs/index.html"); err != nil {
|
||||
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
|
||||
} else {
|
||||
w.Write(data)
|
||||
}
|
||||
})
|
||||
Router().GET("/surveys/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
if data, err := Asset("htdocs/index.html"); err != nil {
|
||||
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
|
||||
} else {
|
||||
w.Write(data)
|
||||
}
|
||||
})
|
||||
Router().GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
w.Header().Set("Content-Type", "text/css")
|
||||
if data, err := Asset(path.Join("htdocs", r.URL.Path)); err != nil {
|
||||
|
31
surveys.go
31
surveys.go
@ -13,7 +13,7 @@ import (
|
||||
func init() {
|
||||
router.GET("/api/surveys", apiAuthHandler(
|
||||
func(u *User, _ httprouter.Params, _ []byte) HTTPResponse {
|
||||
if u.IsAdmin {
|
||||
if u != nil && u.IsAdmin {
|
||||
return formatApiResponse(getSurveys(""))
|
||||
} else {
|
||||
return formatApiResponse(getSurveys("WHERE shown = TRUE"))
|
||||
@ -29,7 +29,7 @@ func init() {
|
||||
}, adminRestricted))
|
||||
router.GET("/api/surveys/:sid", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
if s.Shown || u.IsAdmin {
|
||||
if s.Shown || (u != nil && u.IsAdmin) {
|
||||
return APIResponse{s}
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
@ -51,6 +51,17 @@ func init() {
|
||||
func(s Survey, _ []byte) HTTPResponse {
|
||||
return formatApiResponse(s.Delete())
|
||||
}), adminRestricted))
|
||||
router.GET("/api/surveys/:sid/score", apiAuthHandler(surveyAuthHandler(
|
||||
func(s Survey, u *User, _ []byte) HTTPResponse {
|
||||
if s.Shown || (u != nil && u.IsAdmin) {
|
||||
return formatApiResponse(s.GetScore(u))
|
||||
} else {
|
||||
return APIErrorResponse{
|
||||
status: http.StatusForbidden,
|
||||
err: errors.New("Not accessible"),
|
||||
}
|
||||
}
|
||||
}), loggedUser))
|
||||
}
|
||||
|
||||
func surveyHandler(f func(Survey, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||
@ -81,19 +92,20 @@ type Survey struct {
|
||||
Id int64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Shown bool `json:"shown"`
|
||||
Corrected bool `json:"corrected"`
|
||||
StartAvailability time.Time `json:"start_availability"`
|
||||
EndAvailability time.Time `json:"end_availability"`
|
||||
}
|
||||
|
||||
func getSurveys(cnd string, param... interface{}) (surveys []Survey, err error) {
|
||||
if rows, errr := DBQuery("SELECT id_survey, title, shown, start_availability, end_availability FROM surveys " + cnd, param...); errr != nil {
|
||||
if rows, errr := DBQuery("SELECT id_survey, title, shown, corrected, start_availability, end_availability FROM surveys " + cnd, param...); errr != nil {
|
||||
return nil, errr
|
||||
} else {
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var s Survey
|
||||
if err = rows.Scan(&s.Id, &s.Title, &s.Shown, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||
if err = rows.Scan(&s.Id, &s.Title, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
|
||||
return
|
||||
}
|
||||
surveys = append(surveys, s)
|
||||
@ -107,7 +119,7 @@ func getSurveys(cnd string, param... interface{}) (surveys []Survey, err error)
|
||||
}
|
||||
|
||||
func getSurvey(id int) (s Survey, err error) {
|
||||
err = DBQueryRow("SELECT id_survey, title, shown, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Shown, &s.StartAvailability, &s.EndAvailability)
|
||||
err = DBQueryRow("SELECT id_survey, title, shown, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.Title, &s.Shown, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
|
||||
return
|
||||
}
|
||||
|
||||
@ -117,12 +129,17 @@ func NewSurvey(title string, shown bool, startAvailability time.Time, endAvailab
|
||||
} else if sid, err := res.LastInsertId(); err != nil {
|
||||
return Survey{}, err
|
||||
} else {
|
||||
return Survey{sid, title, shown, startAvailability, endAvailability}, nil
|
||||
return Survey{sid, title, shown, false, startAvailability, endAvailability}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s Survey) GetScore(u *User) (score int64, err error) {
|
||||
err = DBQueryRow("SELECT SUM(M.score) FROM (SELECT MAX(score) FROM survey_responses R INNER JOIN survey_quests Q ON Q.id_question = R.id_question WHERE Q.id_survey=? AND R.id_user=? GROUP BY id_question) M", s.Id, u.Id).Scan(&score)
|
||||
return
|
||||
}
|
||||
|
||||
func (s Survey) Update() (int64, error) {
|
||||
if res, err := DBExec("UPDATE surveys SET title = ?, shown = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Shown, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
||||
if res, err := DBExec("UPDATE surveys SET title = ?, shown = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.Title, s.Shown, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
|
||||
return 0, err
|
||||
} else if nb, err := res.RowsAffected(); err != nil {
|
||||
return 0, err
|
||||
|
Reference in New Issue
Block a user