Add users and grades display
This commit is contained in:
parent
42b15a1546
commit
c9d64640e2
5
db.go
5
db.go
@ -148,6 +148,11 @@ CREATE TABLE IF NOT EXISTS student_corrected(
|
|||||||
FOREIGN KEY(id_user) REFERENCES users(id_user),
|
FOREIGN KEY(id_user) REFERENCES users(id_user),
|
||||||
FOREIGN KEY(id_template) REFERENCES correction_templates(id_template)
|
FOREIGN KEY(id_template) REFERENCES correction_templates(id_template)
|
||||||
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin;
|
||||||
|
`); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := db.Exec(`
|
||||||
|
CREATE VIEW IF NOT EXISTS student_scores AS SELECT U.id_user, id_survey, Q.id_question, MAX(R.score) as score FROM survey_quests Q CROSS JOIN users U LEFT JOIN survey_responses R ON Q.id_question = R.id_question AND R.id_user = U.id_user GROUP BY Q.id_question, U.id_user;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
128
grades.go
Normal file
128
grades.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.GET("/api/users/:uid/surveys/:sid/grades", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||||
|
return surveyAuthHandler(func(s Survey, uauth *User, _ []byte) HTTPResponse {
|
||||||
|
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||||
|
if uauth != nil && ((s.Shown && u.Id == uauth.Id) || uauth.IsAdmin) {
|
||||||
|
if score, err := s.GetUserGrades(&u); err != nil {
|
||||||
|
return APIErrorResponse{err: err}
|
||||||
|
} else if score == nil {
|
||||||
|
return APIResponse{"N/A"}
|
||||||
|
} else {
|
||||||
|
return APIResponse{score}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return APIErrorResponse{
|
||||||
|
status: http.StatusForbidden,
|
||||||
|
err: errors.New("Not accessible"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(ps, body)
|
||||||
|
})(uauth, ps, body)
|
||||||
|
}, loggedUser))
|
||||||
|
router.GET("/api/grades", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||||
|
if uauth != nil && uauth.IsAdmin {
|
||||||
|
if score, err := GetAllGrades(); err != nil {
|
||||||
|
return APIErrorResponse{err: err}
|
||||||
|
} else if score == nil {
|
||||||
|
return APIResponse{"N/A"}
|
||||||
|
} else {
|
||||||
|
return APIResponse{score}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return APIErrorResponse{
|
||||||
|
status: http.StatusForbidden,
|
||||||
|
err: errors.New("Not accessible"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, adminRestricted))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllGrades() (scores map[int64]map[int64]*float64, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_user, id_survey, SUM(score)/COUNT(*) FROM student_scores GROUP BY id_user, id_survey"); err != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
scores = map[int64]map[int64]*float64{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id_user int64
|
||||||
|
var id_survey int64
|
||||||
|
var score *float64
|
||||||
|
|
||||||
|
if err = rows.Scan(&id_user, &id_survey, &score); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if scores[id_user] == nil {
|
||||||
|
scores[id_user] = map[int64]*float64{}
|
||||||
|
}
|
||||||
|
|
||||||
|
scores[id_user][id_survey] = score
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Survey) GetGrades() (scores map[int64]*float64, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_question, SUM(score)/COUNT(*) FROM student_scores WHERE id_survey=? GROUP BY id_question", s.Id); err != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
scores = map[int64]*float64{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id_question int64
|
||||||
|
var score *float64
|
||||||
|
|
||||||
|
if err = rows.Scan(&id_question, &score); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scores[id_question] = score
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Survey) GetUserGrades(u *User) (scores map[int64]*float64, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_question, MAX(score) FROM student_scores WHERE id_survey=? AND id_user = ? GROUP BY id_question", s.Id, u.Id); err != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
scores = map[int64]*float64{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id_question int64
|
||||||
|
var score *float64
|
||||||
|
|
||||||
|
if err = rows.Scan(&id_question, &score); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scores[id_question] = score
|
||||||
|
}
|
||||||
|
if err = rows.Err(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -28,6 +28,7 @@
|
|||||||
<li class="nav-item"><a class="nav-link" href="adlin" target="_self">AdLin</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" 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" ng-class="{'active': qactive}" href="surveys">Questionnaires</a></li>
|
||||||
|
<li class="nav-item" ng-if="user.is_admin" ng-class="{'active': uactive}" ng-cloak><a class="nav-link" href="users">Étudiants</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
<li class="nav-item"><a class="nav-link" href="virli" target="_self">VIRLI</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
@ -5,6 +5,10 @@ angular.module("AtsebaytApp", ["ngRoute", "ngResource", "ngSanitize"])
|
|||||||
controller: "AuthController",
|
controller: "AuthController",
|
||||||
templateUrl: "views/auth.html"
|
templateUrl: "views/auth.html"
|
||||||
})
|
})
|
||||||
|
.when("/grades", {
|
||||||
|
controller: "GradesController",
|
||||||
|
templateUrl: "views/grades.html"
|
||||||
|
})
|
||||||
.when("/surveys", {
|
.when("/surveys", {
|
||||||
controller: "SurveysController",
|
controller: "SurveysController",
|
||||||
templateUrl: "views/surveys.html"
|
templateUrl: "views/surveys.html"
|
||||||
@ -21,6 +25,14 @@ angular.module("AtsebaytApp", ["ngRoute", "ngResource", "ngSanitize"])
|
|||||||
controller: "QuestionController",
|
controller: "QuestionController",
|
||||||
templateUrl: "views/correction.html"
|
templateUrl: "views/correction.html"
|
||||||
})
|
})
|
||||||
|
.when("/users", {
|
||||||
|
controller: "UsersController",
|
||||||
|
templateUrl: "views/users.html"
|
||||||
|
})
|
||||||
|
.when("/users/:userId", {
|
||||||
|
controller: "UserController",
|
||||||
|
templateUrl: "views/user.html"
|
||||||
|
})
|
||||||
.when("/", {
|
.when("/", {
|
||||||
controller: "SurveysController",
|
controller: "SurveysController",
|
||||||
templateUrl: "views/home.html"
|
templateUrl: "views/home.html"
|
||||||
@ -42,14 +54,26 @@ angular.module("AtsebaytApp")
|
|||||||
.factory("MyResponse", function($resource) {
|
.factory("MyResponse", function($resource) {
|
||||||
return $resource("/api/surveys/:surveyId/responses/:respId", { surveyId: '@id', respId: '@id' })
|
return $resource("/api/surveys/:surveyId/responses/:respId", { surveyId: '@id', respId: '@id' })
|
||||||
})
|
})
|
||||||
|
.factory("Grades", function($resource) {
|
||||||
|
return $resource("/api/grades")
|
||||||
|
})
|
||||||
.factory("Survey", function($resource) {
|
.factory("Survey", function($resource) {
|
||||||
return $resource("/api/surveys/:surveyId", { surveyId: '@id' }, {
|
return $resource("/api/surveys/:surveyId", { surveyId: '@id' }, {
|
||||||
'update': {method: 'PUT'},
|
'update': {method: 'PUT'},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.factory("SurveyGrades", function($resource) {
|
||||||
|
return $resource("/api/surveys/:surveyId/grades", { surveyId: '@id' })
|
||||||
|
})
|
||||||
.factory("SurveyScore", function($resource) {
|
.factory("SurveyScore", function($resource) {
|
||||||
return $resource("/api/surveys/:surveyId/score", { surveyId: '@id' })
|
return $resource("/api/surveys/:surveyId/score", { surveyId: '@id' })
|
||||||
})
|
})
|
||||||
|
.factory("SurveyUserScore", function($resource) {
|
||||||
|
return $resource("/api/users/:userId/surveys/:surveyId/score", { userId: '@id', surveyId: '@id' })
|
||||||
|
})
|
||||||
|
.factory("SurveyUserGrades", function($resource) {
|
||||||
|
return $resource("/api/users/:userId/surveys/:surveyId/grades", { userId: '@id', surveyId: '@id' })
|
||||||
|
})
|
||||||
.factory("SurveyQuest", function($resource) {
|
.factory("SurveyQuest", function($resource) {
|
||||||
return $resource("/api/surveys/:surveyId/questions/:questId", { surveyId: '@id', questId: '@id' }, {
|
return $resource("/api/surveys/:surveyId/questions/:questId", { surveyId: '@id', questId: '@id' }, {
|
||||||
'update': {method: 'PUT'},
|
'update': {method: 'PUT'},
|
||||||
@ -236,6 +260,7 @@ angular.module("AtsebaytApp")
|
|||||||
|
|
||||||
.controller("SurveysController", function($scope, $rootScope, Survey, $location) {
|
.controller("SurveysController", function($scope, $rootScope, Survey, $location) {
|
||||||
$rootScope.qactive = $location.$$path == "/surveys";
|
$rootScope.qactive = $location.$$path == "/surveys";
|
||||||
|
$rootScope.uactive = $location.$$path.indexOf("/users") != -1;
|
||||||
$scope.surveys = Survey.query();
|
$scope.surveys = Survey.query();
|
||||||
$scope.surveys.$promise.then(function(data) {
|
$scope.surveys.$promise.then(function(data) {
|
||||||
data.forEach(function(d,k) {
|
data.forEach(function(d,k) {
|
||||||
@ -247,6 +272,7 @@ angular.module("AtsebaytApp")
|
|||||||
|
|
||||||
.controller("SurveyController", function($scope, $rootScope, Survey, $routeParams, $location) {
|
.controller("SurveyController", function($scope, $rootScope, Survey, $routeParams, $location) {
|
||||||
$rootScope.qactive = true;
|
$rootScope.qactive = true;
|
||||||
|
$rootScope.uactive = false;
|
||||||
$scope.survey = Survey.get({ surveyId: $routeParams.surveyId });
|
$scope.survey = Survey.get({ surveyId: $routeParams.surveyId });
|
||||||
$scope.survey.$promise.then(function(survey) {
|
$scope.survey.$promise.then(function(survey) {
|
||||||
survey.readonly = Date.now() > Date.parse(survey.end_availability)
|
survey.readonly = Date.now() > Date.parse(survey.end_availability)
|
||||||
@ -277,6 +303,26 @@ angular.module("AtsebaytApp")
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.controller("SurveyGradesController", function($scope, SurveyGrades) {
|
||||||
|
$scope.grades = SurveyGrades.get({ surveyId: $scope.survey.id })
|
||||||
|
$scope.mean = 0;
|
||||||
|
$scope.grades.$promise.then(function (grades) {
|
||||||
|
var sum = 0
|
||||||
|
var total = 0
|
||||||
|
for (var gid in grades) {
|
||||||
|
if (parseInt(gid, 10) > 0) {
|
||||||
|
total++
|
||||||
|
if (grades[gid]) {
|
||||||
|
sum += grades[gid]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total > 0) {
|
||||||
|
$scope.mean = sum/total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
.controller("ResponsesController", function($scope, AllResponses) {
|
.controller("ResponsesController", function($scope, AllResponses) {
|
||||||
$scope.responses = AllResponses.query({ surveyId: $scope.survey.id, questId: $scope.question.id });
|
$scope.responses = AllResponses.query({ surveyId: $scope.survey.id, questId: $scope.question.id });
|
||||||
})
|
})
|
||||||
@ -285,6 +331,56 @@ angular.module("AtsebaytApp")
|
|||||||
$scope.score = SurveyScore.get({ surveyId: $scope.survey.id })
|
$scope.score = SurveyScore.get({ surveyId: $scope.survey.id })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
.controller("UserScoreController", function($scope, SurveyUserScore) {
|
||||||
|
$scope.score = SurveyUserScore.get({ userId: $scope.user.id, surveyId: $scope.survey.id })
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller("UserGradesController", function($scope, SurveyUserGrades) {
|
||||||
|
$scope.grades = SurveyUserGrades.get({ userId: $scope.user.id, surveyId: $scope.survey.id })
|
||||||
|
$scope.avancement = 0;
|
||||||
|
$scope.grades.$promise.then(function (grades) {
|
||||||
|
var answered = 0
|
||||||
|
var total = 0
|
||||||
|
for (var gid in grades) {
|
||||||
|
if (parseInt(gid, 10) > 0) {
|
||||||
|
total++
|
||||||
|
if (grades[gid]) {
|
||||||
|
answered++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total > 0) {
|
||||||
|
$scope.avancement = answered/total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller("GradesController", function($scope, $rootScope, Grades, Survey, User, $location) {
|
||||||
|
$scope.users = User.query();
|
||||||
|
$scope.surveys = Survey.query();
|
||||||
|
$scope.grades = Grades.get();
|
||||||
|
|
||||||
|
$scope.showUser = function() {
|
||||||
|
$location.url("users/" + this.user.id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller("UsersController", function($scope, $rootScope, User, $location) {
|
||||||
|
$rootScope.qactive = false;
|
||||||
|
$rootScope.uactive = true;
|
||||||
|
$scope.users = User.query()
|
||||||
|
|
||||||
|
$scope.showUser = function() {
|
||||||
|
$location.url("users/" + this.user.id);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
.controller("UserController", function($scope, $routeParams, $rootScope, User) {
|
||||||
|
$rootScope.qactive = false;
|
||||||
|
$rootScope.uactive = true;
|
||||||
|
$scope.user = User.get({ userId: $routeParams.userId})
|
||||||
|
})
|
||||||
|
|
||||||
.controller("QuestionController", function($scope, Survey, SurveyQuest, SurveyQuest, AllResponses, CorrectionTemplate, $http, $routeParams) {
|
.controller("QuestionController", function($scope, Survey, SurveyQuest, SurveyQuest, AllResponses, CorrectionTemplate, $http, $routeParams) {
|
||||||
$scope.notCorrected = true
|
$scope.notCorrected = true
|
||||||
$scope.highlight = ''
|
$scope.highlight = ''
|
||||||
|
21
htdocs/views/grades.html
Normal file
21
htdocs/views/grades.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<h2>
|
||||||
|
Étudiants
|
||||||
|
<small class="text-muted">Notes</small>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Login</th>
|
||||||
|
<th ng-repeat="(sid,survey) in surveys" ng-if="survey.corrected">{{ survey.title }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="(uid,user) in users" ng-click="showUser(user)">
|
||||||
|
<td>{{ user.id }}</td>
|
||||||
|
<td>{{ user.login }}</td>
|
||||||
|
<td ng-repeat="(sid,survey) in surveys" ng-if="survey.corrected">{{ grades[user.id][survey.id] | number:2 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -12,14 +12,19 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Question</th>
|
<th>Question</th>
|
||||||
<th>Réponses</th>
|
<th>Réponses</th>
|
||||||
<th></th>
|
<th>Moyenne</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody ng-controller="SurveyGradesController">
|
||||||
<tr ng-repeat="(qid,question) in questions" ng-click="showResponses()" ng-controller="ResponsesController">
|
<tr ng-repeat="(qid,question) in questions" ng-click="showResponses()" ng-controller="ResponsesController">
|
||||||
<td ng-bind="question.title"></td>
|
<td ng-bind="question.title"></td>
|
||||||
<td>{{ responses.length }}</td>
|
<td>{{ responses.length }}</td>
|
||||||
|
<td>{{ grades[question.id] }} %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Moyenne</th>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
<th>{{ mean }} %</th>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<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>
|
<button class="btn btn-primary ml-1 float-right" ng-click="editSurvey()" title="Éditer" 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>
|
<a href="surveys/{{ survey.id }}/responses" class="btn btn-success ml-1 float-right" title="Voir les réponses" 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>
|
<h2>
|
||||||
{{ survey.title }}
|
{{ survey.title }}
|
||||||
<small>
|
<small>
|
||||||
|
62
htdocs/views/user.html
Normal file
62
htdocs/views/user.html
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<h2>
|
||||||
|
Étudiant
|
||||||
|
<small class="text-muted">
|
||||||
|
{{ user.login }}
|
||||||
|
</small>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<div class="offset-1 col-7">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-2">ID</dt>
|
||||||
|
<dd class="col-10">{{ user.id }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-2">Login</dt>
|
||||||
|
<dd class="col-10">{{ user.login }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-2">E-mail</dt>
|
||||||
|
<dd class="col-10"><a href="mailto:{{ user.email }}">{{ user.email }}</a></dd>
|
||||||
|
|
||||||
|
<dt class="col-2">Nom</dt>
|
||||||
|
<dd class="col-10">{{ user.lastname }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-2">Prénom</dt>
|
||||||
|
<dd class="col-10">{{ user.firstname }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-2">Date d'inscription</dt>
|
||||||
|
<dd class="col-10">{{ user.time }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-2">Admin</dt>
|
||||||
|
<dd class="col-10">{{ user.id_admin?"Oui":"Non" }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-3">
|
||||||
|
<img src="https://photos.cri.epita.fr/thumb/{{ user.login }}" alt="avatar" style="max-width: inherit; max-height: inherit">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" ng-controller="SurveysController">
|
||||||
|
<div class="card-header">
|
||||||
|
Questionnaires
|
||||||
|
</div>
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Titre</th>
|
||||||
|
<th>Avancement</th>
|
||||||
|
<th>Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="(sid,survey) in surveys">
|
||||||
|
<td>{{ survey.id }}</td>
|
||||||
|
<td>{{ survey.title }}</td>
|
||||||
|
<td ng-controller="UserGradesController" title="{{ grades }}">{{ avancement * 100 | number:2 }} %</td>
|
||||||
|
<td ng-controller="UserScoreController">{{ score.score | number:2 }}<span ng-show="score.score >= 0">/20</span></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
29
htdocs/views/users.html
Normal file
29
htdocs/views/users.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<a href="grades" class="btn btn-success ml-1 float-right" title="Notes" 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>
|
||||||
|
Étudiants
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Login</th>
|
||||||
|
<th>E-mail</th>
|
||||||
|
<th>Nom</th>
|
||||||
|
<th>Prénom</th>
|
||||||
|
<th>Date d'inscription</th>
|
||||||
|
<th>admin</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr ng-repeat="(uid,user) in users" ng-click="showUser(user)">
|
||||||
|
<td>{{ user.id }}</td>
|
||||||
|
<td>{{ user.login }}</td>
|
||||||
|
<td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td>
|
||||||
|
<td>{{ user.lastname }}</td>
|
||||||
|
<td>{{ user.firstname }}</td>
|
||||||
|
<td>{{ user.time }}</td>
|
||||||
|
<td>{{ user.is_admin?"Oui":"Non" }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
@ -21,12 +21,21 @@ func init() {
|
|||||||
Router().GET("/auth", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
Router().GET("/auth", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||||
})
|
})
|
||||||
|
Router().GET("/grades", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||||
|
})
|
||||||
Router().GET("/surveys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
Router().GET("/surveys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
||||||
})
|
})
|
||||||
Router().GET("/surveys/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
Router().GET("/surveys/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
|
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) {
|
Router().GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
serveStaticAsset(w, r)
|
serveStaticAsset(w, r)
|
||||||
})
|
})
|
||||||
|
21
static.go
21
static.go
@ -28,6 +28,13 @@ func init() {
|
|||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Router().GET("/grades", 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) {
|
Router().GET("/surveys", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
if data, err := Asset("htdocs/index.html"); err != nil {
|
if data, err := Asset("htdocs/index.html"); err != nil {
|
||||||
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
|
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
|
||||||
@ -42,6 +49,20 @@ func init() {
|
|||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
Router().GET("/users", 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("/users/*_", 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) {
|
Router().GET("/css/*_", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
w.Header().Set("Content-Type", "text/css")
|
w.Header().Set("Content-Type", "text/css")
|
||||||
if data, err := Asset(path.Join("htdocs", r.URL.Path)); err != nil {
|
if data, err := Asset(path.Join("htdocs", r.URL.Path)); err != nil {
|
||||||
|
50
surveys.go
50
surveys.go
@ -70,6 +70,26 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}), loggedUser))
|
}), loggedUser))
|
||||||
|
router.GET("/api/users/:uid/surveys/:sid/score", apiAuthHandler(func(uauth *User, ps httprouter.Params, body []byte) HTTPResponse {
|
||||||
|
return surveyAuthHandler(func(s Survey, uauth *User, _ []byte) HTTPResponse {
|
||||||
|
return userHandler(func(u User, _ []byte) HTTPResponse {
|
||||||
|
if uauth != nil && ((s.Shown && u.Id == uauth.Id) || uauth.IsAdmin) {
|
||||||
|
if score, err := s.GetScore(&u); err != nil {
|
||||||
|
return APIErrorResponse{err: err}
|
||||||
|
} else if score == nil {
|
||||||
|
return APIResponse{map[string]string{"score": "N/A"}}
|
||||||
|
} else {
|
||||||
|
return APIResponse{map[string]float64{"score": *score}}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return APIErrorResponse{
|
||||||
|
status: http.StatusForbidden,
|
||||||
|
err: errors.New("Not accessible"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(ps, body)
|
||||||
|
})(uauth, ps, body)
|
||||||
|
}, loggedUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
func surveyHandler(f func(Survey, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
func surveyHandler(f func(Survey, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||||
@ -142,16 +162,34 @@ func NewSurvey(title string, shown bool, startAvailability time.Time, endAvailab
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s Survey) GetScore(u *User) (score *float64, err error) {
|
func (s Survey) GetScore(u *User) (score *float64, err error) {
|
||||||
if err = DBQueryRow("SELECT SUM(M.score) FROM (SELECT MAX(score) AS 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 Q.id_question) M", s.Id, u.Id).Scan(&score); err != nil || score == nil {
|
err = DBQueryRow("SELECT SUM(score)/COUNT(*) FROM student_scores WHERE id_survey=? AND id_user=?", s.Id, u.Id).Scan(&score)
|
||||||
|
if score != nil {
|
||||||
|
*score = *score / 5.0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Survey) GetScores() (scores map[int64]*float64, err error) {
|
||||||
|
if rows, errr := DBQuery("SELECT id_user, SUM(score)/COUNT(*) FROM student_scores WHERE id_survey=? GROUP BY id_user", s.Id); err != nil {
|
||||||
|
return nil, errr
|
||||||
|
} else {
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
scores = map[int64]*float64{}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
var id_user int64
|
||||||
|
var score *float64
|
||||||
|
|
||||||
|
if err = rows.Scan(&id_user, &score); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
scores[id_user] = score
|
||||||
var questions []Question
|
}
|
||||||
if questions, err = s.GetQuestions(); err != nil {
|
if err = rows.Err(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
*score = *score / (float64(len(questions)) * 5.0)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
12
users.go
12
users.go
@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
@ -17,23 +17,23 @@ func init() {
|
|||||||
func(u User, _ []byte) HTTPResponse {
|
func(u User, _ []byte) HTTPResponse {
|
||||||
return APIResponse{u}
|
return APIResponse{u}
|
||||||
})))
|
})))
|
||||||
router.PUT("/api/users/:uid", apiHandler(userHandler(updateUser)))
|
router.PUT("/api/users/:uid", apiHandler(userHandler(updateUser), adminRestricted))
|
||||||
router.DELETE("/api/users/:uid", apiHandler(userHandler(
|
router.DELETE("/api/users/:uid", apiHandler(userHandler(
|
||||||
func(u User, _ []byte) HTTPResponse {
|
func(u User, _ []byte) HTTPResponse {
|
||||||
return formatApiResponse(u.Delete())
|
return formatApiResponse(u.Delete())
|
||||||
})))
|
}), adminRestricted))
|
||||||
}
|
}
|
||||||
|
|
||||||
func userHandler(f func(User, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
func userHandler(f func(User, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
|
||||||
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
return func(ps httprouter.Params, body []byte) HTTPResponse {
|
||||||
if uid, err := strconv.Atoi(string(ps.ByName("uid"))); err != nil {
|
if uid, err := strconv.Atoi(string(ps.ByName("uid"))); err != nil {
|
||||||
if user, err := getUserByLogin(ps.ByName("uid")); err != nil {
|
if user, err := getUserByLogin(ps.ByName("uid")); err != nil {
|
||||||
return APIErrorResponse{err:err}
|
return APIErrorResponse{err: err}
|
||||||
} else {
|
} else {
|
||||||
return f(user, body)
|
return f(user, body)
|
||||||
}
|
}
|
||||||
} else if user, err := getUser(uid); err != nil {
|
} else if user, err := getUser(uid); err != nil {
|
||||||
return APIErrorResponse{err:err}
|
return APIErrorResponse{err: err}
|
||||||
} else {
|
} else {
|
||||||
return f(user, body)
|
return f(user, body)
|
||||||
}
|
}
|
||||||
@ -140,7 +140,7 @@ func ClearUsers() (int64, error) {
|
|||||||
func updateUser(current User, body []byte) HTTPResponse {
|
func updateUser(current User, body []byte) HTTPResponse {
|
||||||
var new User
|
var new User
|
||||||
if err := json.Unmarshal(body, &new); err != nil {
|
if err := json.Unmarshal(body, &new); err != nil {
|
||||||
return APIErrorResponse{err:err}
|
return APIErrorResponse{err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
current.Login = new.Login
|
current.Login = new.Login
|
||||||
|
Reference in New Issue
Block a user