core/service/question_svc.go

323 lines
9.7 KiB
Go
Raw Normal View History

2024-02-19 17:48:04 +00:00
package service
import (
"github.com/gofiber/fiber/v2"
"github.com/lib/pq"
"penahub.gitlab.yandexcloud.net/backend/penahub_common/log_mw"
2024-06-02 09:36:22 +00:00
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/middleware"
2024-02-19 17:48:04 +00:00
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
2024-06-12 13:27:42 +00:00
"penahub.gitlab.yandexcloud.net/backend/quiz/core/models"
2024-02-19 17:48:04 +00:00
"unicode/utf8"
)
// 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 (s *Service) CreateQuestion(ctx *fiber.Ctx) error {
2024-06-02 09:36:22 +00:00
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
hlogger := log_mw.ExtractLogger(ctx)
2024-06-02 09:36:22 +00:00
2024-02-19 17:48:04 +00:00
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,
}
2024-06-02 09:36:22 +00:00
questionID, err := s.dal.QuestionRepo.CreateQuestion(ctx.Context(), &result)
if err != nil {
2024-02-19 18:01:16 +00:00
if e, ok := err.(*pq.Error); ok {
2024-02-19 17:48:04 +00:00
if e.Constraint == "quiz_relation" {
return ctx.Status(fiber.StatusFailedDependency).SendString(e.Error())
}
}
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
2024-06-02 09:36:22 +00:00
hlogger.Emit(models.InfoQuestionCreate{
CtxUserID: accountID,
CtxIDInt: int64(req.QuizId),
CtxQuestionID: int64(questionID),
2024-06-02 09:36:22 +00:00
})
2024-02-19 17:48:04 +00:00
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 (s *Service) 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 := s.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 (s *Service) 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 := s.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 := s.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 (s *Service) 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 := s.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 (s *Service) GetQuestionHistory(ctx *fiber.Ctx) error {
var req GetQuizHistoryReq
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 := s.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)
}
// DeleteQuestion handler for fake delete question
func (s *Service) DeleteQuestion(ctx *fiber.Ctx) error {
2024-06-02 09:36:22 +00:00
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
hlogger := log_mw.ExtractLogger(ctx)
2024-06-02 09:36:22 +00:00
2024-02-19 17:48:04 +00:00
var req DeactivateReq
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 := s.dal.QuestionRepo.DeleteQuestion(ctx.Context(), req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
2024-06-02 09:36:22 +00:00
hlogger.Emit(models.InfoQuestionDelete{
CtxUserID: accountID,
CtxIDInt: int64(deleted.QuizId),
CtxQuestionID: int64(deleted.Id),
2024-06-02 09:36:22 +00:00
})
2024-02-19 17:48:04 +00:00
return ctx.JSON(DeactivateResp{
Deactivated: deleted.Id,
})
}