diff --git a/db.go b/db.go
index e6f6722..66dde25 100644
--- a/db.go
+++ b/db.go
@@ -148,6 +148,11 @@ CREATE TABLE IF NOT EXISTS student_corrected(
FOREIGN KEY(id_user) REFERENCES users(id_user),
FOREIGN KEY(id_template) REFERENCES correction_templates(id_template)
) 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 {
return err
}
diff --git a/grades.go b/grades.go
new file mode 100644
index 0000000..b6d762c
--- /dev/null
+++ b/grades.go
@@ -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
+}
diff --git a/htdocs/index.html b/htdocs/index.html
index 99dd878..8afb33a 100644
--- a/htdocs/index.html
+++ b/htdocs/index.html
@@ -28,6 +28,7 @@
AdLin
FIC
Questionnaires
+ Étudiants
VIRLI
diff --git a/htdocs/js/atsebayt.js b/htdocs/js/atsebayt.js
index bb14663..53b2554 100644
--- a/htdocs/js/atsebayt.js
+++ b/htdocs/js/atsebayt.js
@@ -5,6 +5,10 @@ angular.module("AtsebaytApp", ["ngRoute", "ngResource", "ngSanitize"])
controller: "AuthController",
templateUrl: "views/auth.html"
})
+ .when("/grades", {
+ controller: "GradesController",
+ templateUrl: "views/grades.html"
+ })
.when("/surveys", {
controller: "SurveysController",
templateUrl: "views/surveys.html"
@@ -21,6 +25,14 @@ angular.module("AtsebaytApp", ["ngRoute", "ngResource", "ngSanitize"])
controller: "QuestionController",
templateUrl: "views/correction.html"
})
+ .when("/users", {
+ controller: "UsersController",
+ templateUrl: "views/users.html"
+ })
+ .when("/users/:userId", {
+ controller: "UserController",
+ templateUrl: "views/user.html"
+ })
.when("/", {
controller: "SurveysController",
templateUrl: "views/home.html"
@@ -42,14 +54,26 @@ angular.module("AtsebaytApp")
.factory("MyResponse", function($resource) {
return $resource("/api/surveys/:surveyId/responses/:respId", { surveyId: '@id', respId: '@id' })
})
+ .factory("Grades", function($resource) {
+ return $resource("/api/grades")
+ })
.factory("Survey", function($resource) {
return $resource("/api/surveys/:surveyId", { surveyId: '@id' }, {
'update': {method: 'PUT'},
})
})
+ .factory("SurveyGrades", function($resource) {
+ return $resource("/api/surveys/:surveyId/grades", { surveyId: '@id' })
+ })
.factory("SurveyScore", function($resource) {
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) {
return $resource("/api/surveys/:surveyId/questions/:questId", { surveyId: '@id', questId: '@id' }, {
'update': {method: 'PUT'},
@@ -236,6 +260,7 @@ angular.module("AtsebaytApp")
.controller("SurveysController", function($scope, $rootScope, Survey, $location) {
$rootScope.qactive = $location.$$path == "/surveys";
+ $rootScope.uactive = $location.$$path.indexOf("/users") != -1;
$scope.surveys = Survey.query();
$scope.surveys.$promise.then(function(data) {
data.forEach(function(d,k) {
@@ -247,6 +272,7 @@ angular.module("AtsebaytApp")
.controller("SurveyController", function($scope, $rootScope, Survey, $routeParams, $location) {
$rootScope.qactive = true;
+ $rootScope.uactive = false;
$scope.survey = Survey.get({ surveyId: $routeParams.surveyId });
$scope.survey.$promise.then(function(survey) {
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) {
$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 })
})
+ .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) {
$scope.notCorrected = true
$scope.highlight = ''
diff --git a/htdocs/views/grades.html b/htdocs/views/grades.html
new file mode 100644
index 0000000..08b05c4
--- /dev/null
+++ b/htdocs/views/grades.html
@@ -0,0 +1,21 @@
+
+ Étudiants
+ Notes
+
+
+
+
+
+ ID |
+ Login |
+ {{ survey.title }} |
+
+
+
+
+ {{ user.id }} |
+ {{ user.login }} |
+ {{ grades[user.id][survey.id] | number:2 }} |
+
+
+
diff --git a/htdocs/views/responses.html b/htdocs/views/responses.html
index 2005544..d03cfbe 100644
--- a/htdocs/views/responses.html
+++ b/htdocs/views/responses.html
@@ -12,14 +12,19 @@
Question |
Réponses |
- |
+ Moyenne |
-
+
|
{{ responses.length }} |
+ {{ grades[question.id] }} % |
+
+
+ Moyenne |
|
+ {{ mean }} % |
diff --git a/htdocs/views/survey.html b/htdocs/views/survey.html
index 7ec4d9c..dfb62bd 100644
--- a/htdocs/views/survey.html
+++ b/htdocs/views/survey.html
@@ -1,5 +1,5 @@
-
-
+
+
{{ survey.title }}
diff --git a/htdocs/views/user.html b/htdocs/views/user.html
new file mode 100644
index 0000000..08fd4f8
--- /dev/null
+++ b/htdocs/views/user.html
@@ -0,0 +1,62 @@
+
+ Étudiant
+
+ {{ user.login }}
+
+
+
+
+
+
+
+ - ID
+ - {{ user.id }}
+
+ - Login
+ - {{ user.login }}
+
+ - E-mail
+ - {{ user.email }}
+
+ - Nom
+ - {{ user.lastname }}
+
+ - Prénom
+ - {{ user.firstname }}
+
+ - Date d'inscription
+ - {{ user.time }}
+
+ - Admin
+ - {{ user.id_admin?"Oui":"Non" }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID |
+ Titre |
+ Avancement |
+ Note |
+
+
+
+
+ {{ survey.id }} |
+ {{ survey.title }} |
+ {{ avancement * 100 | number:2 }} % |
+ {{ score.score | number:2 }}/20 |
+
+
+
+
diff --git a/htdocs/views/users.html b/htdocs/views/users.html
new file mode 100644
index 0000000..350fc70
--- /dev/null
+++ b/htdocs/views/users.html
@@ -0,0 +1,29 @@
+
+
+ Étudiants
+
+
+
+
+
+ ID |
+ Login |
+ E-mail |
+ Nom |
+ Prénom |
+ Date d'inscription |
+ admin |
+
+
+
+
+ {{ user.id }} |
+ {{ user.login }} |
+ {{ user.email }} |
+ {{ user.lastname }} |
+ {{ user.firstname }} |
+ {{ user.time }} |
+ {{ user.is_admin?"Oui":"Non" }} |
+
+
+
diff --git a/static-dev.go b/static-dev.go
index 5e7d63f..94fe79f 100644
--- a/static-dev.go
+++ b/static-dev.go
@@ -21,12 +21,21 @@ func init() {
Router().GET("/auth", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
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) {
http.ServeFile(w, r, path.Join(StaticDir, "index.html"))
})
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 944089e..c1910c4 100644
--- a/static.go
+++ b/static.go
@@ -28,6 +28,13 @@ func init() {
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) {
if data, err := Asset("htdocs/index.html"); err != nil {
fmt.Fprintf(w, "{\"errmsg\":%q}", err)
@@ -42,6 +49,20 @@ func init() {
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) {
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 1114325..cd531d3 100644
--- a/surveys.go
+++ b/surveys.go
@@ -70,6 +70,26 @@ func init() {
}
}
}), 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 {
@@ -142,17 +162,35 @@ func NewSurvey(title string, shown bool, startAvailability time.Time, endAvailab
}
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 {
- return
+ 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
+}
- var questions []Question
- if questions, err = s.GetQuestions(); err != nil {
- 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
+ }
+ scores[id_user] = score
+ }
+ if err = rows.Err(); err != nil {
+ return
+ }
}
- *score = *score / (float64(len(questions)) * 5.0)
-
return
}
diff --git a/users.go b/users.go
index 9b4519d..5fc9171 100644
--- a/users.go
+++ b/users.go
@@ -2,8 +2,8 @@ package main
import (
"encoding/json"
- "time"
"strconv"
+ "time"
"github.com/julienschmidt/httprouter"
)
@@ -17,23 +17,23 @@ func init() {
func(u User, _ []byte) HTTPResponse {
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(
func(u User, _ []byte) HTTPResponse {
return formatApiResponse(u.Delete())
- })))
+ }), adminRestricted))
}
func userHandler(f func(User, []byte) HTTPResponse) func(httprouter.Params, []byte) HTTPResponse {
return func(ps httprouter.Params, body []byte) HTTPResponse {
if uid, err := strconv.Atoi(string(ps.ByName("uid"))); err != nil {
if user, err := getUserByLogin(ps.ByName("uid")); err != nil {
- return APIErrorResponse{err:err}
+ return APIErrorResponse{err: err}
} else {
return f(user, body)
}
} else if user, err := getUser(uid); err != nil {
- return APIErrorResponse{err:err}
+ return APIErrorResponse{err: err}
} else {
return f(user, body)
}
@@ -140,7 +140,7 @@ func ClearUsers() (int64, error) {
func updateUser(current User, body []byte) HTTPResponse {
var new User
if err := json.Unmarshal(body, &new); err != nil {
- return APIErrorResponse{err:err}
+ return APIErrorResponse{err: err}
}
current.Login = new.Login