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/questions.go

366 lines
10 KiB
Go

package main
import (
"log"
"net/http"
"strconv"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/russross/blackfriday/v2"
)
var (
_questions_cache = map[int64]*Question{}
_questions_cache_mutex = sync.RWMutex{}
)
func declareAPIAuthQuestionsRoutes(router *gin.RouterGroup) {
router.GET("/questions", func(c *gin.Context) {
var s *Survey
if survey, ok := c.Get("survey"); ok {
s = survey.(*Survey)
}
u := c.MustGet("LoggedUser").(*User)
if s == nil {
if !u.IsAdmin {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Permission denied"})
return
}
if questions, err := getQuestions(); err != nil {
log.Println("Unable to getQuestions:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
return
} else {
c.JSON(http.StatusOK, questions)
}
} else {
if s.Direct != nil && !u.IsAdmin {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible"})
return
}
if s.StartAvailability.After(time.Now()) && !u.IsAdmin {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible yet"})
return
}
if questions, err := s.GetQuestions(); err != nil {
log.Println("Unable to GetQuestions:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve questions. Please try again later."})
return
} else {
c.JSON(http.StatusOK, questions)
}
}
})
questionsRoutes := router.Group("/questions/:qid")
questionsRoutes.Use(questionHandler)
questionsRoutes.Use(questionUserAccessHandler)
questionsRoutes.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("question").(*Question))
})
declareAPIAuthProposalsRoutes(questionsRoutes)
declareAPIAuthQuestionResponsesRoutes(questionsRoutes)
}
func declareAPIAdminQuestionsRoutes(router *gin.RouterGroup) {
router.POST("/questions", func(c *gin.Context) {
var s *Survey
if survey, ok := c.Get("survey"); ok {
s = survey.(*Survey)
} else {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Survey identifier not defined."})
return
}
var new Question
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
q, err := s.NewQuestion(new.Title, new.DescriptionRaw, new.Placeholder, new.Kind)
if err != nil {
log.Println("Unable to NewQuestion:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during question insertion."})
return
}
c.JSON(http.StatusOK, q)
})
questionsRoutes := router.Group("/questions/:qid")
questionsRoutes.Use(questionHandler)
questionsRoutes.Use(questionUserAccessHandler)
questionsRoutes.PUT("", func(c *gin.Context) {
current := c.MustGet("question").(*Question)
var new Question
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
new.Id = current.Id
if q, err := new.Update(); err != nil {
log.Println("Unable to Update question:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during question update."})
return
} else {
c.JSON(http.StatusOK, q)
}
})
questionsRoutes.DELETE("", func(c *gin.Context) {
q := c.MustGet("question").(*Question)
if _, err := q.Delete(); err != nil {
log.Println("Unable to Delete question:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to delete the question."})
return
}
c.JSON(http.StatusOK, nil)
})
declareAPIAdminCorrectionsRoutes(questionsRoutes)
declareAPIAdminProposalsRoutes(questionsRoutes)
declareAPIAdminResponsesRoutes(questionsRoutes)
}
func declareAPIAdminUserQuestionsRoutes(router *gin.RouterGroup) {
questionsRoutes := router.Group("/questions/:qid")
questionsRoutes.Use(questionHandler)
questionsRoutes.Use(questionUserAccessHandler)
questionsRoutes.GET("", func(c *gin.Context) {
question := c.MustGet("question").(*Question)
user := c.MustGet("user").(*User)
score, err := question.ComputeScoreQuestion(user)
if err != nil {
log.Println("Unable to ComputeScoreQuestion:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs when trying to compute score. Please try again later."})
return
}
c.JSON(http.StatusOK, score)
})
}
func questionHandler(c *gin.Context) {
var survey *Survey
if s, ok := c.Get("survey"); ok {
survey = s.(*Survey)
}
qid, err := strconv.Atoi(string(c.Param("qid")))
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Invalid question identifier."})
return
}
var question *Question
if survey == nil {
question, err = getQuestion(qid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Question not found"})
return
}
} else {
question, err = survey.GetQuestion(qid)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Question not found"})
return
}
}
c.Set("question", question)
c.Next()
}
func questionUserAccessHandler(c *gin.Context) {
var survey *Survey
if s, ok := c.Get("survey"); ok {
survey = s.(*Survey)
}
u := c.MustGet("LoggedUser").(*User)
question := c.MustGet("question").(*Question)
if survey == nil {
s, err := getSurvey(int(question.IdSurvey))
if err != nil {
log.Println("Unable to getSurvey:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "An error occurs during survey retrieval. Please try again later."})
return
}
survey = s
}
if !u.IsAdmin && (!survey.checkUserAccessToSurvey(u) || (survey.Direct != nil && *survey.Direct != question.Id)) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not authorized"})
return
}
if !u.IsAdmin && survey.StartAvailability.After(time.Now()) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Not accessible yet"})
return
}
c.Next()
}
type Question struct {
Id int64 `json:"id"`
IdSurvey int64 `json:"id_survey"`
Title string `json:"title"`
Description string `json:"description"`
DescriptionRaw string `json:"desc_raw,omitempty"`
Placeholder string `json:"placeholder,omitempty"`
Kind string `json:"kind"`
}
func getQuestions() (questions []*Question, err error) {
if rows, errr := DBQuery("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests"); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var q Question
if err = rows.Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind); err != nil {
return
}
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
questions = append(questions, &q)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func (s *Survey) GetQuestions() (questions []*Question, err error) {
if rows, errr := DBQuery("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_survey=?", s.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var q Question
if err = rows.Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind); err != nil {
return
}
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
questions = append(questions, &q)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getQuestion(id int) (q *Question, err error) {
_questions_cache_mutex.RLock()
question, ok := _questions_cache[int64(id)]
_questions_cache_mutex.RUnlock()
if ok {
return question, nil
}
q = new(Question)
err = DBQueryRow("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_question=?", id).Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind)
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
_questions_cache_mutex.Lock()
_questions_cache[int64(id)] = q
_questions_cache_mutex.Unlock()
return
}
func (s *Survey) GetQuestion(id int) (q *Question, err error) {
_questions_cache_mutex.RLock()
question, ok := _questions_cache[int64(id)]
_questions_cache_mutex.RUnlock()
if ok {
return question, nil
}
q = new(Question)
err = DBQueryRow("SELECT id_question, id_survey, title, description, placeholder, kind FROM survey_quests WHERE id_question=? AND id_survey=?", id, s.Id).Scan(&q.Id, &q.IdSurvey, &q.Title, &q.DescriptionRaw, &q.Placeholder, &q.Kind)
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
_questions_cache_mutex.Lock()
_questions_cache[int64(id)] = q
_questions_cache_mutex.Unlock()
return
}
func (s *Survey) NewQuestion(title string, description string, placeholder string, kind string) (*Question, error) {
if res, err := DBExec("INSERT INTO survey_quests (id_survey, title, description, placeholder, kind) VALUES (?, ?, ?, ?, ?)", s.Id, title, description, placeholder, kind); err != nil {
return nil, err
} else if qid, err := res.LastInsertId(); err != nil {
return nil, err
} else {
return &Question{qid, s.Id, title, string(blackfriday.Run([]byte(description))), description, placeholder, kind}, nil
}
}
func (q *Question) GetSurvey() (*Survey, error) {
return getSurvey(int(q.IdSurvey))
}
func (q *Question) Update() (*Question, error) {
if _, err := DBExec("UPDATE survey_quests SET id_survey = ?, title = ?, description = ?, placeholder = ?, kind = ? WHERE id_question = ?", q.IdSurvey, q.Title, q.DescriptionRaw, q.Placeholder, q.Kind, q.Id); err != nil {
return nil, err
} else {
q.Description = string(blackfriday.Run([]byte(q.DescriptionRaw)))
_questions_cache_mutex.Lock()
_questions_cache[q.Id] = q
_questions_cache_mutex.Unlock()
return q, err
}
}
func (q *Question) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM survey_quests WHERE id_question = ?", q.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
_questions_cache_mutex.Lock()
delete(_questions_cache, q.Id)
_questions_cache_mutex.Unlock()
return nb, err
}
}
func ClearQuestions() (int64, error) {
if res, err := DBExec("DELETE FROM survey_quests"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
_questions_cache_mutex.Lock()
_questions_cache = map[int64]*Question{}
_questions_cache_mutex.Unlock()
return nb, err
}
}