2024-02-19 17:48:04 +00:00
|
|
|
package service
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
"github.com/lib/pq"
|
2024-06-07 14:53:45 +00:00
|
|
|
"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")
|
|
|
|
}
|
2024-06-07 14:53:45 +00:00
|
|
|
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,
|
2024-06-12 13:51:32 +00:00
|
|
|
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")
|
|
|
|
}
|
2024-06-07 14:53:45 +00:00
|
|
|
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,
|
2024-06-12 13:51:32 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|