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 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") } 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 func (r *Question) GetQuestionList(ctx *fiber.Ctx) error { 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'") } 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 func (r *Question) UpdateQuestion(ctx *fiber.Ctx) error { 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") } 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 func (r *Question) CopyQuestion(ctx *fiber.Ctx) error { 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") } 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 func (r *Question) GetQuestionHistory(ctx *fiber.Ctx) error { 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") } 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 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") } 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, }) }