From 0a79763f6995fadc8f34bd1d46945779deed13e0 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Sun, 8 Mar 2020 01:06:44 +0100 Subject: [PATCH] v1 done --- auth_oidc.go | 4 +- db.go | 1 + handler.go | 32 +++--- htdocs/img/srstamps.png | Bin 0 -> 16124 bytes htdocs/index.html | 39 ++++--- htdocs/js/atsebayt.js | 212 +++++++++++++++++++++++++++++++---- htdocs/views/auth.html | 6 +- htdocs/views/correction.html | 46 ++++++++ htdocs/views/home.html | 33 +++--- htdocs/views/responses.html | 25 +++++ htdocs/views/survey.html | 20 +++- htdocs/views/surveys.html | 29 +---- questions.go | 30 +++-- responses.go | 91 ++++++++++----- static-dev.go | 6 - static.go | 14 +++ surveys.go | 31 +++-- 17 files changed, 460 insertions(+), 159 deletions(-) create mode 100644 htdocs/img/srstamps.png create mode 100644 htdocs/views/correction.html create mode 100644 htdocs/views/responses.html diff --git a/auth_oidc.go b/auth_oidc.go index 1b66120..8a96742 100644 --- a/auth_oidc.go +++ b/auth_oidc.go @@ -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(), diff --git a/db.go b/db.go index 85c1eab..c5d3f08 100644 --- a/db.go +++ b/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; diff --git a/handler.go b/handler.go index 0f22ce4..e28e993 100644 --- a/handler.go +++ b/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 diff --git a/htdocs/img/srstamps.png b/htdocs/img/srstamps.png new file mode 100644 index 0000000000000000000000000000000000000000..080963d33639abb811618e978bae21001e9e43d1 GIT binary patch literal 16124 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%0DgDYnh-D~lWG!>QUznx=-Csv zR09Ws-}HHJ7tI+$JX�IQb!XDPu!a#MlnNZllea?~S3+>XRa%ezeWM9AsiQl?gyq zNoR+~EETvf@2u@B!}XkaGluX0!@o2aKAD09zlB$m@*`Ev(bCKI>BRji$Xg-bStDA z)yI`%eWXW|IE9ctv4h{sUBPC|p4vF=SI(7%u)Xatj{!lcwM|P- zfw0cwXG{+V4^`F^{cugBPehK;w=+q9FA-fHpj{>|f%@n3PpP{v=&%-Wdx*{wNfxYw zpT|YFs~?0;4X!v@sHKs%vpoj-jI#hQKRmxJHZ$u+w40aRbV^Y)5!$IgPDGR!$O;me zhy+T&f5EbU7c!`Cl|YPESAWa@T>yJo*1=!cTME4VUJI(O<4yUhy)QGnnGzf*(1nMN zZqGdhyDh&MGTfhhRZl>D(<{_o9a07ss8-#ygN-lK3=P5o@wF9Kwuf(`Zr4Et4K6>l zvSjM>9a>d+v8P2#HA}vOA~SVbg-O@V(l=_C1;DJyUdvLJdjHF-Cm^mzqi&@fTsquY zYM_e%S-U4weXFVDW%DPmrS)~|4<%#&r~wB8RfiM5amJ5_(dLmdP`)FbKrhztQWo5V zNm$%^s0QE89mBDhp_ZagoYJV1!e1!&^c7(Z?_|xjpZ9Zk4L3eQOpe6(U%-;pvA=zp z9KnhOB><#N&|wM*d-bZtEl-&{N@V#Nh{ftmnc36WY_Yz%58ip5bIOhQ%B-Ire)M`j z8Lz7sm`*`dLEZN6EaHNS3@I&vJ(U3+XDD72dxhhLBjVytTC#wWgfwx{>a9*H0&)_2 zhAXa<9=~xkRnXyvzvR_MkC#2QgIg&=4~7THS%{B}l-LfB(Y6(lL^F3m>L(D@Rl%*A zTJ4uR&Iy&BhFt0F)CC^ zHExNWCp}P58PyfS03VYmed0F2N61`KOe*(jU57h=?m%7&4z+L75ineX`ewm4D|X^4 z<-Wk7UBv$|-b=M@I((5EIKNxC&z$Z`wDBFX3W7)X$7Xl_Ydmu#t>zm%qt&f~=PBg% zStN&lS1@bEWop$LmfD5O2LzQD;7ooSq!GxF_Ovx2z{sovqbBrsOb1UtIuSz#PR4uU zcV0d26<_c+Vr~zAKBSgAu+LsKX>V?o|7TZzam_~bc=J%l;Ff!fJv)#U?K8bFFWj1l z_eG}3-h8z)U=So$hDrO5{wW101e3ai^z)rR2|}oekG?u~sYl~lVME{&G$bdjBvme9 G5b!@MJlKu^ literal 0 HcmV?d00001 diff --git a/htdocs/index.html b/htdocs/index.html index be125a9..99dd878 100644 --- a/htdocs/index.html +++ b/htdocs/index.html @@ -1,50 +1,57 @@ - + - ât sebay.t SRS: MCQ and others course related stuff + SRS: MCQ and others course related stuff +
+
+
+
+ +
+ diff --git a/htdocs/js/atsebayt.js b/htdocs/js/atsebayt.js index ece2ba7..c97d862 100644 --- a/htdocs/js/atsebayt.js +++ b/htdocs/js/atsebayt.js @@ -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: `` + }) + + .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: ` + + + + + + + + + + + + + + + + + + + + + + + + + +
IntituléÉtatDateScore
{{ survey.title }}PrévuEn coursTerminéCorrigé{{ survey.start_availability | date: "medium" }}{{ survey.end_availability | date: "medium" }}N/A
+ Ajouter un questionnaire +
` + }) + + + .component('surveyBadges', { + bindings: { + survey: '=', + }, + controller: function() { + var now = Date.now() + this.test = function(a, b) { + return Date.parse(a) > now; + } + }, + template: `Prévu +En cours +Clos +Corrigé` + }); + 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") + "
Veuillez réessayer dans quelques instants.", + }); }); } diff --git a/htdocs/views/auth.html b/htdocs/views/auth.html index 9096d68..392bcd5 100644 --- a/htdocs/views/auth.html +++ b/htdocs/views/auth.html @@ -3,13 +3,13 @@

Accès à votre compte

- +
- +
- diff --git a/htdocs/views/correction.html b/htdocs/views/correction.html new file mode 100644 index 0000000..3d0a541 --- /dev/null +++ b/htdocs/views/correction.html @@ -0,0 +1,46 @@ +

+ {{ survey.title }} + + Prévu + En cours + Clos + +

+

+ {{ question.title }} +

+ +
+
+
+ +

+ +
+
+ +
+ /100 +
+
+
+
+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ + +
diff --git a/htdocs/views/home.html b/htdocs/views/home.html index 8004c29..87e528f 100644 --- a/htdocs/views/home.html +++ b/htdocs/views/home.html @@ -1,5 +1,5 @@ -
- avatar +
+ avatar

Bienvenue {{ isLogged.login }} !

@@ -7,20 +7,17 @@

Voici la liste des questionnaires :

- - - - - - - - - - - - - - -
IntituléÉtatDateScore
{{ survey }}
- + +
+ +
+

Cours

+ + +

Questionnaires

+
diff --git a/htdocs/views/responses.html b/htdocs/views/responses.html new file mode 100644 index 0000000..2005544 --- /dev/null +++ b/htdocs/views/responses.html @@ -0,0 +1,25 @@ +

+ {{ survey.title }} + + Prévu + En cours + Clos + +

+ + + + + + + + + + + + + + + + +
QuestionRéponses
{{ responses.length }}
diff --git a/htdocs/views/survey.html b/htdocs/views/survey.html index 4aadeec..ebb4429 100644 --- a/htdocs/views/survey.html +++ b/htdocs/views/survey.html @@ -1,12 +1,9 @@ +

{{ survey.title }} - Prévu - En cours - Clos - Validé - Corrigé +

@@ -40,6 +37,13 @@
+
+ + +
+
@@ -50,6 +54,7 @@
+
@@ -72,7 +77,8 @@
- + +

@@ -81,6 +87,8 @@

QCM non implémentés

+ +

{{question.response.score}} : {{ question.response.score_explaination }}

diff --git a/htdocs/views/surveys.html b/htdocs/views/surveys.html index dab62c0..b14e260 100644 --- a/htdocs/views/surveys.html +++ b/htdocs/views/surveys.html @@ -1,28 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - -
IntituléÉtatDateScore
{{ survey.title }}PrévuEn coursTerminé{{ survey.start_availability | date: "medium" }}{{ survey.end_availability | date: "medium" }}N/A
- Ajouter un questionnaire -
+ diff --git a/questions.go b/questions.go index 0506326..3b9760b 100644 --- a/questions.go +++ b/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 { diff --git a/responses.go b/responses.go index 6b24966..e110a9b 100644 --- a/responses.go +++ b/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 } diff --git a/static-dev.go b/static-dev.go index 1a0f01a..5e7d63f 100644 --- a/static-dev.go +++ b/static-dev.go @@ -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) }) diff --git a/static.go b/static.go index 4388a78..944089e 100644 --- a/static.go +++ b/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 { diff --git a/surveys.go b/surveys.go index 5fd4f61..b94ae8a 100644 --- a/surveys.go +++ b/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