core/internal/controllers/http_controllers/question/question.go

423 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package question
import (
"gitea.pena/PenaSide/common/log_mw"
"gitea.pena/SQuiz/common/dal"
"gitea.pena/SQuiz/common/middleware"
"gitea.pena/SQuiz/common/model"
"gitea.pena/SQuiz/core/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/lib/pq"
"unicode/utf8"
)
type Deps struct {
DAL *dal.DAL
}
type Question struct {
dal *dal.DAL
}
func NewQuestionController(deps Deps) *Question {
return &Question{dal: deps.DAL}
}
// QuestionCreateReq request structure for creating Question
type QuestionCreateReq struct {
QuizId uint64 `json:"quiz_id"` // relation to quiz
Title string `json:"title"` // title of question
Description string `json:"description"` // additional content in question such as pics, html markup or plain text
Type string `json:"type"` // button/select/file/checkbox/text
Required bool `json:"required"` // set true if question must be answered for valid quiz passing
Page int `json:"page"` // set page of question
Content string `json:"content"` // json serialized config of question
}
// CreateQuestion service handler for creating question for quiz
// todo нужна проверка на то что квиз принадлежит пользователю, не помешает
func (r *Question) CreateQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
hlogger := log_mw.ExtractLogger(ctx)
var req QuestionCreateReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
isOwner, err := r.dal.QuizRepo.CheckQuizOwner(ctx.Context(), accountID, req.QuizId)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
if utf8.RuneCountInString(req.Title) >= 512 {
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
}
if req.Type != model.TypeText &&
req.Type != model.TypeVariant &&
req.Type != model.TypeImages &&
req.Type != model.TypeSelect &&
req.Type != model.TypeVarImages &&
req.Type != model.TypeEmoji &&
req.Type != model.TypeDate &&
req.Type != model.TypeNumber &&
req.Type != model.TypePage &&
req.Type != model.TypeRating &&
req.Type != model.TypeResult &&
req.Type != model.TypeFile {
return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none")
}
result := model.Question{
QuizId: req.QuizId,
Title: req.Title,
Description: req.Description,
Type: req.Type,
Required: req.Required,
Deleted: false,
Page: req.Page,
Content: req.Content,
}
questionID, err := r.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result)
if err != nil {
if e, ok := err.(*pq.Error); ok {
if e.Constraint == "quiz_relation" {
return ctx.Status(fiber.StatusFailedDependency).SendString(e.Error())
}
}
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
hlogger.Emit(models.InfoQuestionCreate{
CtxUserID: accountID,
CtxIDInt: int64(req.QuizId),
CtxQuestionID: int64(questionID),
})
return ctx.Status(fiber.StatusOK).JSON(result)
}
// GetQuestionListReq request structure for get question page
type GetQuestionListReq struct {
Limit uint64 `json:"limit"` // page size
Page uint64 `json:"page"` // page number
From int64 `json:"from"` // start of time period
To int64 `json:"to"` // end of time period
QuizId uint64 `json:"quiz_id"` // relation to quiz
Search string `json:"search"` // search string to search in files
Type string `json:"type"` // type of questions. check types in model
Deleted bool `json:"deleted"` // true to get only deleted questions
Required bool `json:"required"`
}
// GetQuestionListResp response to get page questions with count of all filtered items
type GetQuestionListResp struct {
Count uint64 `json:"count"`
Items []model.Question `json:"items"`
}
// GetQuestionList handler for paginated list question
// todo нужна проверка на то что квиз принадлежит пользователю, не помешает
func (r *Question) GetQuestionList(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
var req GetQuestionListReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Type != "" &&
req.Type != model.TypeText &&
req.Type != model.TypeVariant &&
req.Type != model.TypeImages &&
req.Type != model.TypeSelect &&
req.Type != model.TypeVarImages &&
req.Type != model.TypeEmoji &&
req.Type != model.TypeDate &&
req.Type != model.TypeNumber &&
req.Type != model.TypePage &&
req.Type != model.TypeRating &&
req.Type != model.TypeResult &&
req.Type != model.TypeFile {
return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate type, allowed only '', " +
"'test','none','file', 'button','select','checkbox'")
}
isOwner, err := r.dal.QuizRepo.CheckQuizOwner(ctx.Context(), accountID, req.QuizId)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
res, cnt, err := r.dal.QuestionRepo.GetQuestionList(ctx.Context(),
req.Limit,
req.Page*req.Limit,
uint64(req.From),
uint64(req.To),
req.QuizId,
req.Deleted,
req.Required,
req.Search,
req.Type,
)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(GetQuestionListResp{
Items: res,
Count: cnt,
})
}
// UpdateQuestionReq struct for request to update question
type UpdateQuestionReq struct {
Id uint64 `json:"id"`
Title string `json:"title"`
Description string `json:"desc"`
Type string `json:"type"`
Required bool `json:"required"`
Content string `json:"content"`
Page int `json:"page"`
}
// UpdateResp id you change question that you need only new question id
type UpdateResp struct {
Updated uint64 `json:"updated"`
}
// UpdateQuestion handler for update question
// todo нужна проверка на то что квиз принадлежит пользователю, не помешает
func (r *Question) UpdateQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
var req UpdateQuestionReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("need id of question for update")
}
isOwner, err := r.dal.QuestionRepo.CheckQuestionOwner(ctx.Context(), accountID, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
if utf8.RuneCountInString(req.Title) >= 512 {
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("title field should have less then 512 chars")
}
if req.Type != model.TypeText &&
req.Type != model.TypeVariant &&
req.Type != model.TypeImages &&
req.Type != model.TypeSelect &&
req.Type != model.TypeVarImages &&
req.Type != model.TypeEmoji &&
req.Type != model.TypeDate &&
req.Type != model.TypeNumber &&
req.Type != model.TypePage &&
req.Type != model.TypeRating &&
req.Type != model.TypeFile &&
req.Type != model.TypeResult &&
req.Type != "" {
return ctx.Status(fiber.StatusNotAcceptable).SendString("type must be only test,button,file,checkbox,select, none or empty string")
}
question, err := r.dal.QuestionRepo.MoveToHistoryQuestion(ctx.Context(), req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
question.ParentIds = append(question.ParentIds, int32(question.Id))
question.Id = req.Id
question.Version += 1
if req.Title != "" {
question.Title = req.Title
}
if req.Description != "" {
question.Description = req.Description
}
if req.Page != 0 {
question.Page = req.Page
}
if req.Type != "" {
question.Type = req.Type
}
if req.Required != question.Required {
question.Required = req.Required
}
if req.Content != question.Content {
question.Content = req.Content
}
if err := r.dal.QuestionRepo.UpdateQuestion(ctx.Context(), question); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(UpdateResp{
Updated: question.Id,
})
}
// CopyQuestionReq request struct for copy or duplicate question
type CopyQuestionReq struct {
Id uint64 `json:"id"`
QuizId uint64 `json:"quiz_id"`
}
// CopyQuestion handler for copy question
// todo копирование может происходить с чужого опроса? если нет тоже проверку надо делать на принадлежность
func (r *Question) CopyQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
var req CopyQuestionReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
}
isOwner, err := r.dal.QuestionRepo.CheckQuestionOwner(ctx.Context(), accountID, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
question, err := r.dal.QuestionRepo.CopyQuestion(ctx.Context(), req.Id, req.QuizId)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(UpdateResp{
Updated: question.Id,
})
}
// GetQuestionHistoryReq struct of get history request
type GetQuestionHistoryReq struct {
Id uint64 `json:"id"`
Limit uint64 `json:"l"`
Page uint64 `json:"p"`
}
// GetQuestionHistory handler for history of quiz
// todo нужна проверка на то что квиз принадлежит пользователю, не помешает
func (r *Question) GetQuestionHistory(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
var req GetQuestionHistoryReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
}
isOwner, err := r.dal.QuestionRepo.CheckQuestionOwner(ctx.Context(), accountID, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
history, err := r.dal.QuestionRepo.QuestionHistory(ctx.Context(), req.Id, req.Limit, req.Page*req.Limit)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.Status(fiber.StatusOK).JSON(history)
}
type DeactivateResp struct {
Deactivated uint64 `json:"deactivated"`
}
// DeleteQuestion handler for fake delete question
// todo нужна проверка на то что квиз принадлежит пользователю, не помешает
func (r *Question) DeleteQuestion(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
hlogger := log_mw.ExtractLogger(ctx)
var req struct {
Id uint64 `json:"id"`
}
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting question is required")
}
isOwner, err := r.dal.QuestionRepo.CheckQuestionOwner(ctx.Context(), accountID, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
if !isOwner {
return ctx.Status(fiber.StatusUnauthorized).SendString("not the owner")
}
deleted, err := r.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
hlogger.Emit(models.InfoQuestionDelete{
CtxUserID: accountID,
CtxIDInt: int64(deleted.QuizId),
CtxQuestionID: int64(deleted.Id),
})
return ctx.JSON(DeactivateResp{
Deactivated: deleted.Id,
})
}