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) } }) surveysRoutes.GET("shares", func(c *gin.Context) { survey := c.MustGet("survey").(*Survey) if sh, err := survey.getShares(); err != nil { log.Println("Unable to getShares survey:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey shares listing: %s", err.Error())}) return } else { c.JSON(http.StatusOK, sh) } }) surveysRoutes.POST("shares", func(c *gin.Context) { survey := c.MustGet("survey").(*Survey) if sh, err := survey.Share(); err != nil { log.Println("Unable to Share survey:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey sharing: %s", err.Error())}) return } else if url, err := sh.GetURL(); err != nil { log.Println("Unable to GetURL share:", err) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during survey sharing: %s", err.Error())}) return } else { c.JSON(http.StatusOK, url.String()) } }) 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 kind = 'survey' AND id=? 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 kind = 'survey' AND 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) { DBExec("DELETE FROM survey_shared WHERE id_survey = ?", s.Id) 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 } }