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 0000000..080963d Binary files /dev/null and b/htdocs/img/srstamps.png differ 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