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 } }