core/service/quiz_svc.go

513 lines
15 KiB
Go
Raw Normal View History

2024-02-19 17:48:04 +00:00
package service
import (
2024-05-13 11:15:06 +00:00
"fmt"
2024-02-19 17:48:04 +00:00
"github.com/gofiber/fiber/v2"
2024-05-13 11:15:06 +00:00
"net/url"
2024-03-13 16:57:12 +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"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/repository/quiz"
"time"
"unicode/utf8"
)
type CreateQuizReq struct {
Fingerprinting bool `json:"fingerprinting"` // true if you need to store device id
Repeatable bool `json:"repeatable"` // make it true for allow more than one quiz checkouting
NotePrevented bool `json:"note_prevented"` // note answers even if the quiz was aborted
MailNotifications bool `json:"mail_notifications"` // set true if you want get an email with every quiz passing
UniqueAnswers bool `json:"unique_answers"` // set true if we you mention only last quiz passing
2024-04-20 09:45:24 +00:00
Name string `json:"name"` // max 700 chars
2024-02-19 17:48:04 +00:00
Description string `json:"description"`
Config string `json:"config"` // serialize json with config for page rules. fill it up only if implement one form scenario
Status string `json:"status"` // status of quiz as enum. see Status const. fill it up only if implement one form scenario
Limit uint64 `json:"limit"` // max count of quiz passing
DueTo uint64 `json:"due_to"` // time when quiz is end
QuestionCnt uint64 `json:"question_cnt"` // for creating at one request
TimeOfPassing uint64 `json:"time_of_passing"` // amount of seconds for give all appropriate answers for quiz
Pausable bool `json:"pausable"` // true allows to pause the quiz taking
Super bool `json:"super"` // set true if you want to create group
GroupId uint64 `json:"group_id"` // if you create quiz in group provide there the id of super quiz
}
// CreateQuiz handler for quiz creating request
func (s *Service) CreateQuiz(ctx *fiber.Ctx) error {
var req CreateQuizReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
// check that we can store name
2024-04-20 09:45:24 +00:00
if utf8.RuneCountInString(req.Name) > 700 {
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 700 chars")
2024-02-19 17:48:04 +00:00
}
// status should be empty or equal one of status enum strings
// I mean not draft, template, stop, start statuses
if req.Status != "" &&
req.Status != model.StatusDraft &&
req.Status != model.StatusTemplate &&
req.Status != model.StatusStop &&
req.Status != model.StatusStart {
return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
}
// DueTo should be bigger then now
if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
}
// you can pause quiz only if it has deadline for passing
if req.Pausable && req.TimeOfPassing == 0 {
return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
}
record := model.Quiz{
AccountId: accountId,
Fingerprinting: req.Fingerprinting,
Repeatable: req.Repeatable,
NotePrevented: req.NotePrevented,
MailNotifications: req.MailNotifications,
UniqueAnswers: req.UniqueAnswers,
Name: req.Name,
Description: req.Description,
Config: req.Config,
Status: req.Status,
Limit: req.Limit,
DueTo: req.DueTo,
TimeOfPassing: req.TimeOfPassing,
Pausable: req.Pausable,
QuestionsCount: req.QuestionCnt,
ParentIds: []int32{},
Super: req.Super,
GroupId: req.GroupId,
}
if err := s.dal.QuizRepo.CreateQuiz(ctx.Context(), &record); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.Status(fiber.StatusCreated).JSON(record)
}
// GetQuizListReq request struct for paginated quiz table
type GetQuizListReq struct {
Limit uint64 `json:"limit"`
Page uint64 `json:"page"`
From int64 `json:"from"`
To int64 `json:"to"`
Search string `json:"search"`
Status string `json:"status"`
Deleted bool `json:"deleted"`
Archived bool `json:"archived"`
Super bool `json:"super"`
GroupId uint64 `json:"group_id"`
}
type GetQuizListResp struct {
Count uint64 `json:"count"`
Items []model.Quiz `json:"items"`
}
// GetQuizList handler for paginated list quiz
func (s *Service) GetQuizList(ctx *fiber.Ctx) error {
var req GetQuizListReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Status != "" &&
req.Status != model.StatusStop &&
req.Status != model.StatusStart &&
req.Status != model.StatusDraft &&
req.Status != model.StatusTemplate &&
req.Status != model.StatusTimeout &&
req.Status != model.StatusOffLimit {
return ctx.Status(fiber.StatusNotAcceptable).SendString("inappropriate status, allowed only '', " +
"'stop','start','draft', 'template','timeout','offlimit'")
}
res, cnt, err := s.dal.QuizRepo.GetQuizList(ctx.Context(),
quiz.GetQuizListDeps{
Limit: req.Limit,
Offset: req.Limit * req.Page,
From: uint64(req.From),
To: uint64(req.To),
Group: req.GroupId,
Deleted: req.Deleted,
Archived: req.Archived,
Super: req.Super,
Search: req.Search,
Status: req.Status,
AccountId: accountId,
})
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(GetQuizListResp{
Items: res,
Count: cnt,
})
}
type UpdateQuizReq struct {
Id uint64 `json:"id"`
Fingerprinting bool `json:"fp"`
Repeatable bool `json:"rep"`
NotePrevented bool `json:"note_prevented"`
MailNotifications bool `json:"mailing"`
UniqueAnswers bool `json:"uniq"`
Name string `json:"name"`
Description string `json:"desc"`
Config string `json:"conf"`
Status string `json:"status"`
Limit uint64 `json:"limit"`
DueTo uint64 `json:"due_to"`
TimeOfPassing uint64 `json:"time_of_passing"`
Pausable bool `json:"pausable"`
QuestionCnt uint64 `json:"question_cnt"` // for creating at one request
Super bool `json:"super"`
GroupId uint64 `json:"group_id"`
}
func (s *Service) UpdateQuiz(ctx *fiber.Ctx) error {
var req UpdateQuizReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("need id of question for update")
}
2024-04-20 09:45:24 +00:00
if utf8.RuneCountInString(req.Name) > 700 {
return ctx.Status(fiber.StatusUnprocessableEntity).SendString("name field should have less then 700 chars")
2024-02-19 17:48:04 +00:00
}
// status should be empty or equal one of status enum strings
// I mean not draft, template, stop, start statuses
if req.Status != "" &&
req.Status != model.StatusDraft &&
req.Status != model.StatusTemplate &&
req.Status != model.StatusStop &&
req.Status != model.StatusStart {
return ctx.Status(fiber.StatusNotAcceptable).SendString("status on creating must be only draft,template,stop,start")
}
// DueTo should be bigger then now
if req.DueTo != 0 && req.DueTo <= uint64(time.Now().Unix()) {
return ctx.Status(fiber.StatusNotAcceptable).SendString("due to time must be lesser then now")
}
// you can pause quiz only if it has deadline for passing
if req.Pausable && req.TimeOfPassing == 0 {
return ctx.Status(fiber.StatusConflict).SendString("you can pause quiz only if it has deadline for passing")
}
quiz, err := s.dal.QuizRepo.MoveToHistoryQuiz(ctx.Context(), req.Id, accountId)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))
quiz.Id = req.Id
quiz.Version += 1
if req.Fingerprinting != quiz.Fingerprinting {
quiz.Fingerprinting = req.Fingerprinting
}
if req.Repeatable != quiz.Repeatable {
quiz.Repeatable = req.Repeatable
}
if req.MailNotifications != quiz.MailNotifications {
quiz.MailNotifications = req.MailNotifications
}
if req.NotePrevented != quiz.NotePrevented {
quiz.NotePrevented = req.NotePrevented
}
if req.UniqueAnswers != quiz.UniqueAnswers {
quiz.UniqueAnswers = req.UniqueAnswers
}
if req.Pausable != quiz.Pausable {
quiz.Pausable = req.Pausable
}
if req.Name != "" && req.Name != quiz.Name {
quiz.Name = req.Name
}
if req.Description != "" && req.Description != quiz.Description {
quiz.Description = req.Description
}
if req.Status != "" && req.Status != quiz.Status {
quiz.Status = req.Status
}
if req.TimeOfPassing != quiz.TimeOfPassing {
quiz.TimeOfPassing = req.TimeOfPassing
}
if req.DueTo != quiz.DueTo {
quiz.DueTo = req.DueTo
}
if req.Limit != quiz.Limit {
quiz.Limit = req.Limit
}
if req.Config != "" && req.Config != quiz.Config {
quiz.Config = req.Config
}
if req.Super != quiz.Super {
quiz.Super = req.Super
}
if req.GroupId != quiz.GroupId {
quiz.GroupId = req.GroupId
}
quiz.QuestionsCount = req.QuestionCnt
quiz.ParentIds = append(quiz.ParentIds, int32(quiz.Id))
if err := s.dal.QuizRepo.UpdateQuiz(ctx.Context(), accountId, quiz); err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(UpdateResp{
Updated: quiz.Id,
})
}
// CopyQuizReq request struct for copy quiz
type CopyQuizReq struct {
Id uint64 `json:"id"`
}
// CopyQuiz request handler for copy quiz
func (s *Service) CopyQuiz(ctx *fiber.Ctx) error {
var req CopyQuizReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
}
quiz, err := s.dal.QuizRepo.CopyQuiz(ctx.Context(), accountId, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(UpdateResp{
Updated: quiz.Id,
})
}
// GetQuizHistoryReq struct of get history request
type GetQuizHistoryReq struct {
Id uint64 `json:"id"`
Limit uint64 `json:"l"`
Page uint64 `json:"p"`
}
// GetQuizHistory handler for history of quiz
func (s *Service) GetQuizHistory(ctx *fiber.Ctx) error {
var req GetQuizHistoryReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("no id provided")
}
history, err := s.dal.QuizRepo.QuizHistory(ctx.Context(), quiz.QuizHistoryDeps{
Id: req.Id,
Limit: req.Limit,
Offset: req.Page * req.Limit,
AccountId: accountId,
})
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.Status(fiber.StatusCreated).JSON(history)
}
// DeactivateReq request structure for archiving and deleting
type DeactivateReq struct {
Id uint64 `json:"id"`
}
type DeactivateResp struct {
Deactivated uint64 `json:"deactivated"`
}
// DeleteQuiz handler for fake delete quiz
func (s *Service) DeleteQuiz(ctx *fiber.Ctx) error {
var req DeactivateReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for deleting is required")
}
deleted, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(DeactivateResp{
Deactivated: deleted.Id,
})
}
// ArchiveQuiz handler for archiving quiz
func (s *Service) ArchiveQuiz(ctx *fiber.Ctx) error {
var req DeactivateReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
accountId, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
if req.Id == 0 {
return ctx.Status(fiber.StatusFailedDependency).SendString("id for archive quiz is required")
}
archived, err := s.dal.QuizRepo.DeleteQuiz(ctx.Context(), accountId, req.Id)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.JSON(DeactivateResp{
Deactivated: archived.Id,
})
}
2024-03-19 17:29:34 +00:00
type QuizMoveReq struct {
Qid, AccountID string
}
func (s *Service) QuizMove(ctx *fiber.Ctx) error {
var req QuizMoveReq
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Qid == "" || req.AccountID == "" {
2024-05-13 11:15:06 +00:00
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid and accountID is required")
2024-03-19 17:29:34 +00:00
}
resp, err := s.dal.QuizRepo.QuizMove(ctx.Context(), req.Qid, req.AccountID)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return ctx.Status(fiber.StatusOK).JSON(resp)
}
2024-03-22 12:42:52 +00:00
func (s *Service) MiniPart(ctx *fiber.Ctx) error {
qid := ctx.Query("q")
if qid == "" {
return ctx.Status(fiber.StatusBadRequest).SendString("qid is nil")
}
ctx.Cookie(&fiber.Cookie{
Name: "quizFrom",
Value: qid,
})
userID, err := s.dal.AccountRepo.GetQidOwner(ctx.Context(), qid)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
shifr, err := s.encrypt.EncryptStr(userID)
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
2024-04-12 22:41:30 +00:00
fmt.Println("OLOLO", string(shifr))
2024-03-22 12:42:52 +00:00
ctx.Cookie(&fiber.Cookie{
Name: "quizUser",
2024-04-12 22:41:30 +00:00
Value: url.QueryEscape(string(shifr)),
2024-03-22 12:42:52 +00:00
})
return ctx.Redirect(s.redirectURl, fiber.StatusFound)
}
2024-05-13 11:15:06 +00:00
func (s *Service) TemplateCopy(ctx *fiber.Ctx) error {
accountID, ok := middleware.GetAccountId(ctx)
if !ok {
return ctx.Status(fiber.StatusUnauthorized).SendString("account id is required")
}
var req struct {
2024-05-13 11:17:45 +00:00
Qid string `json:"qid"`
2024-05-13 11:15:06 +00:00
}
if err := ctx.BodyParser(&req); err != nil {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request data")
}
if req.Qid == "" {
return ctx.Status(fiber.StatusBadRequest).SendString("Invalid request qid is required")
}
2024-05-17 14:25:32 +00:00
qizID, err := s.dal.QuizRepo.TemplateCopy(ctx.Context(), accountID, req.Qid)
2024-05-13 11:15:06 +00:00
if err != nil {
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
2024-05-17 14:25:32 +00:00
return ctx.Status(fiber.StatusOK).JSON(fiber.Map{"id": qizID})
2024-05-13 11:15:06 +00:00
}