296 lines
7.9 KiB
Go
296 lines
7.9 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gofiber/fiber/v2"
|
|
"penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/dal"
|
|
"penahub.gitlab.yandexcloud.net/backend/quiz/answerer.git/middleware"
|
|
quizdal "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
|
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/xid"
|
|
)
|
|
|
|
const (
|
|
quizIdCookie = "qud"
|
|
fingerprintCookie = "fp"
|
|
)
|
|
|
|
type Service struct {
|
|
store *dal.Storer
|
|
dal *quizdal.DAL
|
|
batch []model.Answer
|
|
m sync.Mutex
|
|
workerRespondentCh chan<- []model.Answer
|
|
workerSendClientCh chan<- model.Answer
|
|
}
|
|
|
|
func New(s *dal.Storer, q *quizdal.DAL, workerRespondentCh chan<- []model.Answer, workerSendClientCh chan<- model.Answer) *Service {
|
|
return &Service{
|
|
store: s,
|
|
dal: q,
|
|
m: sync.Mutex{},
|
|
batch: []model.Answer{},
|
|
workerRespondentCh: workerRespondentCh,
|
|
workerSendClientCh: workerSendClientCh,
|
|
}
|
|
}
|
|
|
|
func (s *Service) Register(app *fiber.App) *fiber.App {
|
|
app.Post("/answer", s.PutAnswersOnePiece)
|
|
app.Post("/settings", s.GetQuizData)
|
|
return app
|
|
}
|
|
|
|
// GetQuizDataReq request data for get data for user
|
|
type GetQuizDataReq struct {
|
|
QuizId string `json:"quiz_id"` // relation to quiz
|
|
Limit uint64 `json:"limit"`
|
|
Page uint64 `json:"page"`
|
|
NeedConfig bool `json:"need_config"` // true if you need not only question page
|
|
}
|
|
|
|
// GetQuizDataResp response with prepared data for user
|
|
type GetQuizDataResp struct {
|
|
Settings ShavedQuiz `json:"settings"`
|
|
Items []ShavedQuestion `json:"items"`
|
|
Count uint64 `json:"cnt"`
|
|
}
|
|
|
|
// ShavedQuiz shortened struct for delivery data to customer
|
|
type ShavedQuiz struct {
|
|
Fingerprinting bool `json:"fp"`
|
|
Repeatable bool `json:"rep"`
|
|
Name string `json:"name"`
|
|
Config string `json:"cfg"`
|
|
Limit uint64 `json:"lim"`
|
|
DueTo uint64 `json:"due"`
|
|
TimeOfPassing uint64 `json:"delay"`
|
|
Pausable bool `json:"pausable"`
|
|
}
|
|
|
|
// ShavedQuestion shortened struct for delivery data to customer
|
|
type ShavedQuestion struct {
|
|
Id uint64 `json:"id"`
|
|
Title string `json:"title"`
|
|
Description string `json:"desc"`
|
|
Type string `json:"typ"`
|
|
Required bool `json:"req"`
|
|
Page int `json:"p"`
|
|
Content string `json:"c"`
|
|
}
|
|
|
|
// GetQuizData handler for obtaining data for quiz front rendering
|
|
func (s *Service) GetQuizData(c *fiber.Ctx) error {
|
|
var req GetQuizDataReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
|
}
|
|
|
|
if req.QuizId == "" {
|
|
return c.Status(fiber.StatusBadRequest).SendString("Invalid request data")
|
|
}
|
|
|
|
if req.Limit == 0 && !req.NeedConfig {
|
|
return c.Status(fiber.StatusLengthRequired).SendString("no data requested")
|
|
}
|
|
|
|
quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), req.QuizId)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
|
}
|
|
|
|
if req.Limit == 0 && req.NeedConfig {
|
|
return c.Status(fiber.StatusOK).JSON(GetQuizDataResp{
|
|
Settings: dao2dtoQuiz(quiz),
|
|
})
|
|
}
|
|
|
|
if quiz.UniqueAnswers {
|
|
//todo implement after creating store answers
|
|
}
|
|
if quiz.Status != model.StatusStart {
|
|
return c.Status(fiber.StatusLocked).SendString("quiz is inactive")
|
|
}
|
|
|
|
if quiz.Limit > 0 {
|
|
// todo implement after creating store answer
|
|
}
|
|
|
|
if quiz.DueTo < uint64(time.Now().Unix()) && quiz.DueTo > 0 {
|
|
return c.Status(fiber.StatusGone).SendString("quiz timeouted")
|
|
}
|
|
|
|
questions, cnt, err := s.dal.QuestionRepo.GetQuestionList(
|
|
c.Context(),
|
|
req.Limit,
|
|
req.Page*req.Limit,
|
|
0, 0, quiz.Id, false, false, "", "",
|
|
)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
|
}
|
|
|
|
result := GetQuizDataResp{
|
|
Count: cnt,
|
|
Items: []ShavedQuestion{},
|
|
}
|
|
|
|
if req.NeedConfig {
|
|
result.Settings = dao2dtoQuiz(quiz)
|
|
}
|
|
|
|
for _, q := range questions {
|
|
result.Items = append(result.Items, dao2dtoQuestion(q))
|
|
}
|
|
|
|
if cnt <= req.Limit {
|
|
return c.Status(fiber.StatusOK).JSON(result)
|
|
} else {
|
|
return c.Status(fiber.StatusPartialContent).JSON(result)
|
|
}
|
|
}
|
|
|
|
func dao2dtoQuestion(question model.Question) ShavedQuestion {
|
|
return ShavedQuestion{
|
|
Id: question.Id,
|
|
Title: question.Title,
|
|
Description: question.Description,
|
|
Type: question.Type,
|
|
Required: false,
|
|
Page: question.Page,
|
|
Content: question.Content,
|
|
}
|
|
}
|
|
|
|
func dao2dtoQuiz(quiz model.Quiz) ShavedQuiz {
|
|
|
|
return ShavedQuiz{
|
|
Fingerprinting: quiz.Fingerprinting,
|
|
Repeatable: quiz.Repeatable,
|
|
Name: quiz.Name,
|
|
Config: quiz.Config,
|
|
Limit: quiz.Limit,
|
|
DueTo: quiz.DueTo,
|
|
TimeOfPassing: quiz.TimeOfPassing,
|
|
Pausable: quiz.Pausable,
|
|
}
|
|
}
|
|
|
|
// MB Size constants
|
|
const (
|
|
MB = 1 << 20
|
|
filePrefix = "file:"
|
|
)
|
|
|
|
type PutAnswersResponse struct {
|
|
FileIDMap map[uint64]string `json:"fileIDMap"`
|
|
Stored []uint64 `json:"stored"`
|
|
}
|
|
|
|
func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|
cs, ok := c.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string)
|
|
if !ok {
|
|
return c.Status(fiber.StatusUnauthorized).SendString("no session in cookie")
|
|
}
|
|
|
|
form, err := c.MultipartForm()
|
|
if err != nil || form == nil || form.File == nil {
|
|
return c.Status(fiber.StatusBadRequest).SendString("expecting multipart form file")
|
|
}
|
|
|
|
answersStr := form.Value["answers"]
|
|
if len(answersStr) == 0 {
|
|
return c.Status(fiber.StatusFailedDependency).SendString("no answers provided")
|
|
}
|
|
|
|
var (
|
|
answersRaw, answers, trueRes []model.Answer
|
|
errs []error
|
|
)
|
|
|
|
if err := json.Unmarshal([]byte(answersStr[0]), &answersRaw); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).SendString("not valid answers string")
|
|
}
|
|
|
|
quizID, ok := form.Value["qid"]
|
|
if !ok {
|
|
return c.Status(fiber.StatusFailedDependency).SendString("no quiz id provided")
|
|
}
|
|
|
|
fp := ""
|
|
if cfp := c.Cookies(fingerprintCookie); cfp != "" {
|
|
fp = cfp
|
|
}
|
|
|
|
quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), quizID[0])
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).SendString("can not get quiz")
|
|
}
|
|
|
|
fileIDMap := make(map[uint64]string)
|
|
|
|
for _, ans := range answersRaw {
|
|
if strings.HasPrefix(ans.Content, filePrefix) {
|
|
filekey := strings.TrimPrefix(ans.Content, filePrefix)
|
|
filenameparts := strings.Split(filekey, ".")
|
|
filetail := filenameparts[len(filenameparts)-1]
|
|
fileparts := form.File[filekey]
|
|
|
|
if len(fileparts) == 0 {
|
|
errs = append(errs, fmt.Errorf("no parts for file: %s", filekey))
|
|
continue
|
|
}
|
|
|
|
r, err := fileparts[0].Open()
|
|
if err != nil {
|
|
errs = append(errs, fmt.Errorf("can not open part for file: %s", filekey))
|
|
continue
|
|
}
|
|
|
|
fname := fmt.Sprintf("%s.%s", xid.New().String(), filetail)
|
|
if err := s.store.PutAnswer(c.Context(), r, quizID[0], fname, ans.QuestionId, fileparts[0].Size); err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).SendString("can not upload file answers")
|
|
}
|
|
ans.Content = fname
|
|
|
|
fileIDMap[ans.QuestionId] = fname
|
|
}
|
|
|
|
ans.Session = cs
|
|
ans.QuizId = quiz.Id
|
|
ans.CreatedAt = time.Now()
|
|
answers = append(answers, ans)
|
|
if ans.Result {
|
|
s.workerSendClientCh <- ans
|
|
trueRes = append(trueRes, ans)
|
|
}
|
|
}
|
|
|
|
quizConfig := model.QuizConfig{}
|
|
err = json.Unmarshal([]byte(quiz.Config), &quizConfig)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).SendString("can not unmarshal quiz config")
|
|
}
|
|
|
|
if quizConfig.Mailing.When == "email" && len(trueRes) > 0 {
|
|
s.workerRespondentCh <- trueRes
|
|
}
|
|
|
|
stored, ers := s.dal.AnswerRepo.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id)
|
|
if len(ers) != 0 {
|
|
return c.Status(fiber.StatusInternalServerError).SendString("some errors are casualted: " + fmt.Sprint(ers))
|
|
}
|
|
|
|
response := PutAnswersResponse{
|
|
FileIDMap: fileIDMap,
|
|
Stored: stored,
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(response)
|
|
}
|