This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
atsebay.t/surveys.go

391 lines
12 KiB
Go

package main
import (
"fmt"
"log"
"math"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
var (
_score_cache = map[int64]map[int64]*float64{}
_score_cache_mutex = sync.RWMutex{}
_surveys_cache = map[int64]*Survey{}
_surveys_cache_mutex = sync.RWMutex{}
)
func declareAPISurveysRoutes(router *gin.RouterGroup) {
router.GET("/surveys", func(c *gin.Context) {
u := c.MustGet("LoggedUser").(*User)
var response []*Survey
var err error
if u == nil {
response, err = getSurveys(fmt.Sprintf("WHERE shown = TRUE AND NOW() > start_availability AND promo = %d ORDER BY start_availability ASC", currentPromo))
} else if u.IsAdmin {
response, err = getSurveys("ORDER BY promo DESC, start_availability ASC")
} else {
var surveys []*Survey
surveys, err = getSurveys(fmt.Sprintf("WHERE shown = TRUE AND promo = %d ORDER BY start_availability ASC", u.Promo))
if err == nil {
for _, s := range surveys {
if s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",") {
s.Group = ""
response = append(response, s)
}
}
}
}
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Impossible de lister les questionnaires. Veuillez réessayer dans quelques instants"})
log.Printf("Unable to list surveys: %s", err.Error())
} else {
c.JSON(http.StatusOK, response)
}
})
surveysRoutes := router.Group("/surveys/:sid")
surveysRoutes.Use(surveyHandler)
surveysRoutes.Use(surveyUserAccessHandler)
surveysRoutes.GET("", func(c *gin.Context) {
u := c.MustGet("LoggedUser").(*User)
if u == nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": "Veuillez vous connecter pour accéder à cette page."})
return
}
c.JSON(http.StatusOK, c.MustGet("survey").(*Survey))
})
}
func declareAPIAuthSurveysRoutes(router *gin.RouterGroup) {
surveysRoutes := router.Group("/surveys/:sid")
surveysRoutes.Use(surveyHandler)
surveysRoutes.Use(surveyUserAccessHandler)
surveysRoutes.GET("/score", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
s := c.MustGet("survey").(*Survey)
if u.IsAdmin {
questions, err := s.GetQuestions()
if err != nil {
log.Println("Unable to getQuestions:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
return
}
itemCount := 0
itemCorrected := 0
for _, q := range questions {
res, err := q.GetResponses()
if err != nil {
log.Printf("Unable to GetResponses(qid=%d): %s", q.Id, err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during responses retrieval."})
return
}
for _, r := range res {
itemCount += 1
if r.TimeScored != nil && (r.TimeReported == nil || r.TimeScored.After(*r.TimeReported)) {
itemCorrected += 1
}
}
}
c.JSON(http.StatusOK, map[string]int{"count": itemCount, "corrected": itemCorrected})
} else if s.Promo == u.Promo && s.Shown {
score, err := s.GetScore(u)
if err != nil {
log.Printf("Unable to GetScore(uid=%d;sid=%d): %s", u.Id, s.Id, err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to retrieve score."})
return
}
if score == nil {
c.JSON(http.StatusOK, map[string]string{"score": "N/A"})
} else {
c.JSON(http.StatusOK, map[string]float64{"score": math.Round(*score*10) / 10})
}
} else {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
return
}
})
declareAPIAuthAsksRoutes(surveysRoutes)
declareAPIAuthDirectRoutes(surveysRoutes)
declareAPIAuthGradesRoutes(surveysRoutes)
declareAPIAuthQuestionsRoutes(surveysRoutes)
declareAPIAuthResponsesRoutes(surveysRoutes)
}
func declareAPIAdminSurveysRoutes(router *gin.RouterGroup) {
router.POST("/surveys", func(c *gin.Context) {
var new Survey
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if new.Promo == 0 {
new.Promo = currentPromo
}
if s, err := NewSurvey(new.IdCategory, new.Title, new.Promo, new.Group, new.Shown, new.Direct, new.StartAvailability, new.EndAvailability); err != nil {
log.Println("Unable to NewSurvey:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey creation: %s", err.Error())})
return
} else {
c.JSON(http.StatusOK, s)
}
})
surveysRoutes := router.Group("/surveys/:sid")
surveysRoutes.Use(surveyHandler)
surveysRoutes.PUT("", func(c *gin.Context) {
current := c.MustGet("survey").(*Survey)
var new Survey
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
new.Id = current.Id
if new.Direct != current.Direct {
if new.Direct == nil {
current.WSCloseAll("")
} else if *new.Direct == 0 {
current.WSWriteAll(WSMessage{
Action: "pause",
})
} else {
current.WSWriteAll(WSMessage{
Action: "new_question",
QuestionId: new.Direct,
})
}
}
if survey, err := new.Update(); err != nil {
log.Println("Unable to Update survey:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey updation: %s", err.Error())})
return
} else {
c.JSON(http.StatusOK, survey)
}
})
surveysRoutes.DELETE("", func(c *gin.Context) {
survey := c.MustGet("survey").(*Survey)
if _, err := survey.Delete(); err != nil {
log.Println("Unable to Delete survey:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey deletion: %s", err.Error())})
return
} else {
c.JSON(http.StatusOK, nil)
}
})
declareAPIAdminAsksRoutes(surveysRoutes)
declareAPIAdminDirectRoutes(surveysRoutes)
declareAPIAdminQuestionsRoutes(surveysRoutes)
}
func surveyHandler(c *gin.Context) {
if sid, err := strconv.Atoi(string(c.Param("sid"))); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad survey identifier."})
return
} else if survey, err := getSurvey(sid); err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Survey not found."})
return
} else {
c.Set("survey", survey)
c.Next()
}
}
func (s *Survey) checkUserAccessToSurvey(u *User) bool {
return u.IsAdmin || (u.Promo == s.Promo && s.Shown && (s.Group == "" || strings.Contains(u.Groups, ","+s.Group+",")))
}
func surveyUserAccessHandler(c *gin.Context) {
u := c.MustGet("LoggedUser").(*User)
s := c.MustGet("survey").(*Survey)
if !s.checkUserAccessToSurvey(u) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Survey not found."})
return
}
c.Next()
}
type Survey struct {
Id int64 `json:"id"`
IdCategory int64 `json:"id_category"`
Title string `json:"title"`
Promo uint `json:"promo"`
Group string `json:"group"`
Shown bool `json:"shown"`
Direct *int64 `json:"direct"`
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, id_category, title, promo, grp, shown, direct, 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.IdCategory, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability); err != nil {
return
}
surveys = append(surveys, &s)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getSurvey(id int) (s *Survey, err error) {
_surveys_cache_mutex.RLock()
survey, ok := _surveys_cache[int64(id)]
_surveys_cache_mutex.RUnlock()
if ok {
return survey, nil
}
s = new(Survey)
err = DBQueryRow("SELECT id_survey, id_category, title, promo, grp, shown, direct, corrected, start_availability, end_availability FROM surveys WHERE id_survey=?", id).Scan(&s.Id, &s.IdCategory, &s.Title, &s.Promo, &s.Group, &s.Shown, &s.Direct, &s.Corrected, &s.StartAvailability, &s.EndAvailability)
_surveys_cache_mutex.Lock()
_surveys_cache[int64(id)] = s
_surveys_cache_mutex.Unlock()
return
}
func NewSurvey(id_category int64, title string, promo uint, group string, shown bool, direct *int64, startAvailability time.Time, endAvailability time.Time) (*Survey, error) {
if res, err := DBExec("INSERT INTO surveys (id_category, title, promo, grp, shown, direct, start_availability, end_availability) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", id_category, title, promo, group, shown, direct, startAvailability, endAvailability); err != nil {
return nil, err
} else if sid, err := res.LastInsertId(); err != nil {
return nil, err
} else {
return &Survey{sid, id_category, title, promo, group, shown, direct, false, startAvailability, endAvailability}, nil
}
}
func (s Survey) GetScore(u *User) (score *float64, err error) {
_score_cache_mutex.RLock()
if _, ok := _score_cache[u.Id]; !ok {
_score_cache_mutex.RUnlock()
_score_cache_mutex.Lock()
_score_cache[u.Id] = map[int64]*float64{}
_score_cache_mutex.Unlock()
_score_cache_mutex.RLock()
}
v, ok := _score_cache[u.Id][s.Id]
_score_cache_mutex.RUnlock()
if ok {
score = v
} else {
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
}
_score_cache_mutex.Lock()
_score_cache[u.Id][s.Id] = score
_score_cache_mutex.Unlock()
}
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); errr != 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
}
}
return
}
func (s *Survey) Update() (*Survey, error) {
if _, err := DBExec("UPDATE surveys SET id_category = ?, title = ?, promo = ?, grp = ?, shown = ?, direct = ?, corrected = ?, start_availability = ?, end_availability = ? WHERE id_survey = ?", s.IdCategory, s.Title, s.Promo, s.Group, s.Shown, s.Direct, s.Corrected, s.StartAvailability, s.EndAvailability, s.Id); err != nil {
return nil, err
} else {
_surveys_cache_mutex.Lock()
_surveys_cache[s.Id] = s
_surveys_cache_mutex.Unlock()
return s, err
}
}
func (s Survey) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM surveys WHERE id_survey = ?", s.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
_surveys_cache_mutex.Lock()
delete(_surveys_cache, s.Id)
_surveys_cache_mutex.Unlock()
return nb, err
}
}
func ClearSurveys() (int64, error) {
if res, err := DBExec("DELETE FROM surveys"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
_surveys_cache_mutex.Lock()
_surveys_cache = map[int64]*Survey{}
_surveys_cache_mutex.Unlock()
return nb, err
}
}