cleaning pj
This commit is contained in:
parent
f42cb1ce44
commit
ff8956355f
103
app/app.go
103
app/app.go
@ -4,27 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.pena/PenaSide/common/log_mw"
|
|
||||||
"gitea.pena/PenaSide/hlog"
|
|
||||||
"gitea.pena/PenaSide/trashlog/wrappers/zaptrashlog"
|
|
||||||
"gitea.pena/SQuiz/answerer/clients"
|
"gitea.pena/SQuiz/answerer/clients"
|
||||||
dalBS "gitea.pena/SQuiz/answerer/dal"
|
|
||||||
"gitea.pena/SQuiz/answerer/models"
|
|
||||||
"gitea.pena/SQuiz/answerer/savewc"
|
|
||||||
"gitea.pena/SQuiz/answerer/service"
|
"gitea.pena/SQuiz/answerer/service"
|
||||||
"gitea.pena/SQuiz/common/dal"
|
"gitea.pena/SQuiz/common/dal"
|
||||||
"gitea.pena/SQuiz/common/healthchecks"
|
"gitea.pena/SQuiz/common/healthchecks"
|
||||||
"gitea.pena/SQuiz/common/middleware"
|
"gitea.pena/SQuiz/common/middleware"
|
||||||
"gitea.pena/SQuiz/common/model"
|
|
||||||
"gitea.pena/SQuiz/common/utils"
|
"gitea.pena/SQuiz/common/utils"
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
"github.com/skeris/appInit"
|
"github.com/skeris/appInit"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
@ -55,21 +43,13 @@ var _ appInit.CommonApp = (*App)(nil)
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"`
|
LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"`
|
||||||
IsProd bool `env:"IS_PROD" default:"false"`
|
IsProd bool `env:"IS_PROD" default:"false"`
|
||||||
MinioEP string `env:"MINIO_EP" default:"localhost:3002"`
|
|
||||||
MinioAK string `env:"MINIO_AK" default:"minio"`
|
|
||||||
MinioSK string `env:"MINIO_SK" default:"miniostorage"`
|
|
||||||
NumberPort string `env:"PORT" default:"1490"`
|
NumberPort string `env:"PORT" default:"1490"`
|
||||||
CrtFile string `env:"CRT" default:"server.crt"`
|
CrtFile string `env:"CRT" default:"server.crt"`
|
||||||
KeyFile string `env:"KEY" default:"server.key"`
|
KeyFile string `env:"KEY" default:"server.key"`
|
||||||
PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
|
PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
|
||||||
RedisHost string `env:"REDIS_HOST"`
|
|
||||||
RedisPassword string `env:"REDIS_PASSWORD"`
|
|
||||||
RedisDB uint64 `env:"REDIS_DB"`
|
|
||||||
RedirectURL string `env:"REDIRECT_URL" default:"https://squiz.pena.digital"`
|
RedirectURL string `env:"REDIRECT_URL" default:"https://squiz.pena.digital"`
|
||||||
PubKey string `env:"PUBLIC_KEY"`
|
PubKey string `env:"PUBLIC_KEY"`
|
||||||
PrivKey string `env:"PRIVATE_KEY"`
|
PrivKey string `env:"PRIVATE_KEY"`
|
||||||
TrashLogHost string `env:"TRASH_LOG_HOST" default:"localhost:7113"`
|
|
||||||
ModuleLogger string `env:"MODULE_LOGGER" default:"answerer-local"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) {
|
func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) {
|
||||||
@ -102,77 +82,26 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co
|
|||||||
zap.String("SvcVersion", ver.Release),
|
zap.String("SvcVersion", ver.Release),
|
||||||
zap.String("SvcBuildTime", ver.BuildTime),
|
zap.String("SvcBuildTime", ver.BuildTime),
|
||||||
)
|
)
|
||||||
clickHouseLogger, err := zaptrashlog.NewCore(ctx, zap.InfoLevel, options.TrashLogHost, ver.Release, ver.Commit, time.Now().Unix())
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
loggerForHlog := zapLogger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
|
pgdal, err := dal.New(ctx, options.PostgresCredentials, nil)
|
||||||
return zapcore.NewTee(core, clickHouseLogger)
|
|
||||||
}))
|
|
||||||
|
|
||||||
loggerHlog := hlog.New(loggerForHlog).Module(options.ModuleLogger)
|
|
||||||
|
|
||||||
loggerHlog.With(models.AllFields{})
|
|
||||||
loggerHlog.Emit(InfoSvcStarted{})
|
|
||||||
|
|
||||||
// Initialize minio client object.
|
|
||||||
minioClient, err := minio.New(options.MinioEP, &minio.Options{
|
|
||||||
Creds: credentials.NewStaticV4(options.MinioAK, options.MinioSK, ""),
|
|
||||||
Secure: options.IsProd,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("MINIOERR", options.MinioEP, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pgdal, err := dal.New(ctx, options.PostgresCredentials, minioClient)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
zapLogger.Info("config", zap.Any("options", options))
|
zapLogger.Info("config", zap.Any("options", options))
|
||||||
//init redis
|
|
||||||
redisClient := redis.NewClient(&redis.Options{
|
|
||||||
Addr: options.RedisHost,
|
|
||||||
Password: options.RedisPassword,
|
|
||||||
DB: int(options.RedisDB),
|
|
||||||
})
|
|
||||||
|
|
||||||
encrypt := utils.NewEncrypt(options.PubKey, options.PrivKey)
|
encrypt := utils.NewEncrypt(options.PubKey, options.PrivKey)
|
||||||
|
|
||||||
workerSendClientCh := make(chan model.Answer, 50)
|
svc, err := service.New(service.ServiceDeps{
|
||||||
workerRespondentCh := make(chan []model.Answer, 50)
|
Dal: pgdal,
|
||||||
blobstore, err := dalBS.New(ctx, minioClient)
|
Encrypt: encrypt,
|
||||||
|
RedirectURl: options.RedirectURL,
|
||||||
|
AiClient: clients.NewAiClient("https://alvatar.com/api/engine/send_answer"),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
zapLogger.Error("failed to create service", zap.Error(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//svc := service.New(blobstore, pgdal, workerRespondentCh, workerSendClientCh)
|
|
||||||
svc := service.New(service.ServiceDeps{
|
|
||||||
Store: blobstore,
|
|
||||||
Dal: pgdal,
|
|
||||||
WorkerSendClientCh: workerSendClientCh,
|
|
||||||
WorkerRespondentCh: workerRespondentCh,
|
|
||||||
Encrypt: encrypt,
|
|
||||||
RedirectURl: options.RedirectURL,
|
|
||||||
AiClient: clients.NewAiClient("https://alvatar.com/api/engine/send_answer"),
|
|
||||||
})
|
|
||||||
|
|
||||||
saveRespWcData := savewc.DepsForResp{
|
|
||||||
WorkerRespondentCh: workerRespondentCh,
|
|
||||||
Redis: redisClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
saveClientWcData := savewc.DepsForClient{
|
|
||||||
WorkerSendClientCh: workerSendClientCh,
|
|
||||||
Redis: redisClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
saveRespWorker := savewc.NewSaveRespWorker(saveRespWcData, errChan, loggerHlog)
|
|
||||||
|
|
||||||
saveClientWorker := savewc.NewSaveClientWorker(saveClientWcData, errChan, loggerHlog)
|
|
||||||
|
|
||||||
go saveRespWorker.Start(ctx)
|
|
||||||
go saveClientWorker.Start(ctx)
|
|
||||||
|
|
||||||
app := fiber.New(fiber.Config{BodyLimit: 70 * 1024 * 1024})
|
app := fiber.New(fiber.Config{BodyLimit: 70 * 1024 * 1024})
|
||||||
|
|
||||||
@ -186,38 +115,28 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.Use(middleware.AnswererChain())
|
app.Use(middleware.AnswererChain())
|
||||||
app.Use(log_mw.ContextLogger(loggerHlog))
|
|
||||||
app.Get("/liveness", healthchecks.Liveness)
|
app.Get("/liveness", healthchecks.Liveness)
|
||||||
app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
|
app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
|
||||||
app = svc.Register(app)
|
app = svc.Register(app)
|
||||||
|
|
||||||
fmt.Println("SERVERSTART", fmt.Sprintf(":%s", options.NumberPort))
|
fmt.Println("SERVERSTART", fmt.Sprintf(":%s", options.NumberPort))
|
||||||
|
|
||||||
loggerHlog.Emit(InfoSvcReady{})
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
//if pgdal != nil {
|
|
||||||
// pgdal.CloseAnswerer()
|
|
||||||
//}
|
|
||||||
err := app.Shutdown()
|
err := app.Shutdown()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loggerHlog.Emit(InfoSvcShutdown{Signal: err.Error()})
|
zapLogger.Error("failed graceful shutdown", zap.Error(err))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if options.IsProd {
|
if options.IsProd {
|
||||||
if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil {
|
if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil {
|
||||||
loggerHlog.Emit(ErrorCanNotServe{
|
zapLogger.Error("failed start server", zap.Error(err))
|
||||||
Err: err,
|
|
||||||
})
|
|
||||||
errChan <- err
|
errChan <- err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil {
|
if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil {
|
||||||
loggerHlog.Emit(ErrorCanNotServe{
|
zapLogger.Error("failed start server", zap.Error(err))
|
||||||
Err: err,
|
|
||||||
})
|
|
||||||
errChan <- err
|
errChan <- err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
dal/dal.go
32
dal/dal.go
@ -1,32 +0,0 @@
|
|||||||
package dal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
bucket = "3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b"
|
|
||||||
bucketAnswers = "squizanswer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Storer struct {
|
|
||||||
client *minio.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(ctx context.Context, minioClient *minio.Client) (*Storer, error) {
|
|
||||||
return &Storer{
|
|
||||||
client: minioClient,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Storer) PutAnswer(ctx context.Context, file io.Reader, quizId, name string, question uint64, size int64) error {
|
|
||||||
if _, err := s.client.PutObject(ctx, bucket, bucketAnswers + "/" +fmt.Sprintf("%s/%d/%s", quizId, question, name), file, size,
|
|
||||||
minio.PutObjectOptions{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type AllFields struct {
|
|
||||||
CtxUserIP string
|
|
||||||
CtxUserPort string
|
|
||||||
KeyDomain string
|
|
||||||
KeyPath string
|
|
||||||
KeyOS string
|
|
||||||
KeyDevice string
|
|
||||||
KeyDeviceType string
|
|
||||||
KeyBrowser string
|
|
||||||
CtxQuiz string
|
|
||||||
CtxReferrer string
|
|
||||||
CtxIDInt int64
|
|
||||||
CtxSession string
|
|
||||||
CtxQuizID int64
|
|
||||||
CtxQuestionID int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type InfoQuizOpen struct { // при получении настроек квиза
|
|
||||||
KeyOS string
|
|
||||||
KeyDevice string
|
|
||||||
KeyDeviceType string
|
|
||||||
KeyBrowser string // то самое, что получается из заголовков и складывается в модель ответа. на самом деле, ему место тут
|
|
||||||
CtxQuiz string // айдишник квиза, который qid
|
|
||||||
CtxQuizID int64 // айдишник квиза
|
|
||||||
CtxReferrer string // тоже из заголовков
|
|
||||||
CtxIDInt int64 // айдишник ответа
|
|
||||||
CtxSession string // сессия
|
|
||||||
}
|
|
||||||
|
|
||||||
type InfoAnswer struct { // при любом ответе на вопрос
|
|
||||||
KeyOS string
|
|
||||||
KeyDevice string
|
|
||||||
KeyDeviceType string
|
|
||||||
KeyBrowser string // то самое, что получается из заголовков и складывается в модель ответа. на самом деле, ему место тут
|
|
||||||
CtxQuiz string // айдишник квиза, который qid
|
|
||||||
CtxQuizID int64 // айдишник квиза
|
|
||||||
CtxReferrer string // тоже из заголовков
|
|
||||||
CtxQuestionID int64 // айдишник вопроса, на который отвечено
|
|
||||||
CtxIDInt int64 // айдишник ответа
|
|
||||||
CtxSession string // сессия
|
|
||||||
}
|
|
||||||
|
|
||||||
type InfoResult struct { // если ответ на вопрос с типом result
|
|
||||||
KeyOS string
|
|
||||||
KeyDevice string
|
|
||||||
KeyDeviceType string
|
|
||||||
KeyBrowser string // то самое, что получается из заголовков и складывается в модель ответа. на самом деле, ему место тут
|
|
||||||
CtxQuiz string // айдишник квиза, который qid
|
|
||||||
CtxQuizID int64 // айдишник квиза
|
|
||||||
CtxReferrer string // тоже из заголовков
|
|
||||||
CtxQuestionID int64 // айдишник вопроса, на который отвечено
|
|
||||||
CtxIDInt int64 // айдишник ответа
|
|
||||||
CtxSession string // сессия
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo понять для чего это событие вроде как контакты приходят в ответахс с result = true там парситься контент с контактной информацией
|
|
||||||
type InfoContactForm struct { // если ответ на вопрос с типом result, без result == true (возможно перепутал с предыдущим. в этом ответе приходят контактные данные респондента)
|
|
||||||
KeyOS string
|
|
||||||
KeyDevice string
|
|
||||||
KeyDeviceType string
|
|
||||||
KeyBrowser string // то самое, что получается из заголовков и складывается в модель ответа. на самом деле, ему место тут
|
|
||||||
CtxQuiz string // айдишник квиза, который qid
|
|
||||||
CtxQuizID int64 // айдишник квиза
|
|
||||||
CtxReferrer string // тоже из заголовков
|
|
||||||
CtxQuestionID int64 // айдишник вопроса, на который отвечено
|
|
||||||
CtxIDInt int64 // айдишник ответа
|
|
||||||
CtxSession string // сессия
|
|
||||||
}
|
|
3
quiz_config.json
Normal file
3
quiz_config.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
package savewc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gitea.pena/PenaSide/hlog"
|
|
||||||
"gitea.pena/SQuiz/common/model"
|
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DepsForClient struct {
|
|
||||||
WorkerSendClientCh chan model.Answer
|
|
||||||
Redis *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type SaveForClient struct {
|
|
||||||
deps DepsForClient
|
|
||||||
errChan chan<- error
|
|
||||||
logger hlog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSaveClientWorker(deps DepsForClient, errChan chan<- error, logger hlog.Logger) *SaveForClient {
|
|
||||||
return &SaveForClient{
|
|
||||||
deps: deps,
|
|
||||||
errChan: errChan,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *SaveForClient) Start(ctx context.Context) {
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case answer, ok := <-w.deps.WorkerSendClientCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println("SAVECLINT")
|
|
||||||
err := w.saveAnswer(ctx, answer)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error save answer")
|
|
||||||
w.errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
fmt.Println("Save for client worker terminated")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *SaveForClient) saveAnswer(ctx context.Context, answer model.Answer) error {
|
|
||||||
answerJSON, err := json.Marshal(answer)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error marshal answer to redis", err)
|
|
||||||
w.errChan <- err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf("answer:%d", time.Now().UnixNano())
|
|
||||||
|
|
||||||
err = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error saving answer to redis", err)
|
|
||||||
w.errChan <- err
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package savewc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"gitea.pena/PenaSide/hlog"
|
|
||||||
"gitea.pena/SQuiz/common/model"
|
|
||||||
"github.com/go-redis/redis/v8"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DepsForResp struct {
|
|
||||||
WorkerRespondentCh chan []model.Answer
|
|
||||||
Redis *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
type SaveForRespondent struct {
|
|
||||||
deps DepsForResp
|
|
||||||
errChan chan<- error
|
|
||||||
logger hlog.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSaveRespWorker(deps DepsForResp, errChan chan<- error, logger hlog.Logger) *SaveForRespondent {
|
|
||||||
return &SaveForRespondent{
|
|
||||||
deps: deps,
|
|
||||||
errChan: errChan,
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *SaveForRespondent) Start(ctx context.Context) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case answer, ok := <-w.deps.WorkerRespondentCh:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println("SAVERESP")
|
|
||||||
err := w.saveAnswers(ctx, answer)
|
|
||||||
if err != nil {
|
|
||||||
w.logger.Module("Error save answers")
|
|
||||||
w.errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
w.logger.Module("Save for respondent worker terminated")
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *SaveForRespondent) saveAnswers(ctx context.Context, answers []model.Answer) error {
|
|
||||||
for _, answer := range answers {
|
|
||||||
answerJSON, err := json.Marshal(answer)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error marshal answer", err)
|
|
||||||
w.errChan <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := fmt.Sprintf("toRespondent:%d", time.Now().UnixNano())
|
|
||||||
|
|
||||||
err = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error setting to redis", err)
|
|
||||||
w.errChan <- err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
21
service/config.go
Normal file
21
service/config.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadQuizDataConfig() (GetQuizDataResp, error) {
|
||||||
|
configPath := "./quiz_config.json"
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return GetQuizDataResp{}, fmt.Errorf("failed to read config file %s: %w", configPath, err)
|
||||||
|
}
|
||||||
|
var config GetQuizDataResp
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
return GetQuizDataResp{}, fmt.Errorf("failed to parse config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
@ -1,23 +1,19 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"unicode/utf8"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.pena/PenaSide/common/log_mw"
|
|
||||||
"gitea.pena/SQuiz/answerer/clients"
|
"gitea.pena/SQuiz/answerer/clients"
|
||||||
"gitea.pena/SQuiz/answerer/dal"
|
|
||||||
"gitea.pena/SQuiz/answerer/models"
|
|
||||||
quizdal "gitea.pena/SQuiz/common/dal"
|
quizdal "gitea.pena/SQuiz/common/dal"
|
||||||
"gitea.pena/SQuiz/common/middleware"
|
"gitea.pena/SQuiz/common/middleware"
|
||||||
"gitea.pena/SQuiz/common/model"
|
"gitea.pena/SQuiz/common/model"
|
||||||
"gitea.pena/SQuiz/common/utils"
|
"gitea.pena/SQuiz/common/utils"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
@ -28,58 +24,45 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store *dal.Storer
|
dal *quizdal.DAL
|
||||||
dal *quizdal.DAL
|
batch []model.Answer
|
||||||
batch []model.Answer
|
m sync.Mutex
|
||||||
m sync.Mutex
|
encrypt *utils.Encrypt
|
||||||
workerRespondentCh chan<- []model.Answer
|
redirectURl string
|
||||||
workerSendClientCh chan<- model.Answer
|
aiClient *clients.AIClient
|
||||||
encrypt *utils.Encrypt
|
quizConfigData GetQuizDataResp
|
||||||
redirectURl string
|
|
||||||
aiClient *clients.AIClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceDeps struct {
|
type ServiceDeps struct {
|
||||||
Store *dal.Storer
|
Dal *quizdal.DAL
|
||||||
Dal *quizdal.DAL
|
Encrypt *utils.Encrypt
|
||||||
WorkerRespondentCh chan<- []model.Answer
|
RedirectURl string
|
||||||
WorkerSendClientCh chan<- model.Answer
|
AiClient *clients.AIClient
|
||||||
Encrypt *utils.Encrypt
|
|
||||||
RedirectURl string
|
|
||||||
AiClient *clients.AIClient
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(deps ServiceDeps) *Service {
|
func New(deps ServiceDeps) (*Service, error) {
|
||||||
return &Service{
|
quizData, err := loadQuizDataConfig()
|
||||||
store: deps.Store,
|
if err != nil {
|
||||||
dal: deps.Dal,
|
return nil, err
|
||||||
m: sync.Mutex{},
|
|
||||||
batch: []model.Answer{},
|
|
||||||
workerRespondentCh: deps.WorkerRespondentCh,
|
|
||||||
workerSendClientCh: deps.WorkerSendClientCh,
|
|
||||||
encrypt: deps.Encrypt,
|
|
||||||
redirectURl: deps.RedirectURl,
|
|
||||||
aiClient: deps.AiClient,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
dal: deps.Dal,
|
||||||
|
m: sync.Mutex{},
|
||||||
|
batch: []model.Answer{},
|
||||||
|
encrypt: deps.Encrypt,
|
||||||
|
redirectURl: deps.RedirectURl,
|
||||||
|
aiClient: deps.AiClient,
|
||||||
|
quizConfigData: quizData,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Register(app *fiber.App) *fiber.App {
|
func (s *Service) Register(app *fiber.App) *fiber.App {
|
||||||
app.Post("/answer", s.PutAnswersOnePiece)
|
app.Post("/answer", s.PutAnswersOnePiece)
|
||||||
app.Post("/settings", s.GetQuizData)
|
app.Post("/settings", s.GetQuizData)
|
||||||
app.Get("/logo", s.MiniPart)
|
|
||||||
return app
|
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
|
|
||||||
Auditory int64 `json:"auditory"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQuizDataResp response with prepared data for user
|
|
||||||
type GetQuizDataResp struct {
|
type GetQuizDataResp struct {
|
||||||
Settings *ShavedQuiz `json:"settings,omitempty"`
|
Settings *ShavedQuiz `json:"settings,omitempty"`
|
||||||
Items []ShavedQuestion `json:"items"`
|
Items []ShavedQuestion `json:"items"`
|
||||||
@ -87,7 +70,6 @@ type GetQuizDataResp struct {
|
|||||||
ShowBadge bool `json:"show_badge"`
|
ShowBadge bool `json:"show_badge"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShavedQuiz shortened struct for delivery data to customer
|
|
||||||
type ShavedQuiz struct {
|
type ShavedQuiz struct {
|
||||||
Fingerprinting bool `json:"fp"`
|
Fingerprinting bool `json:"fp"`
|
||||||
Repeatable bool `json:"rep"`
|
Repeatable bool `json:"rep"`
|
||||||
@ -100,7 +82,6 @@ type ShavedQuiz struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShavedQuestion shortened struct for delivery data to customer
|
|
||||||
type ShavedQuestion struct {
|
type ShavedQuestion struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
@ -113,209 +94,7 @@ type ShavedQuestion struct {
|
|||||||
|
|
||||||
// GetQuizData handler for obtaining data for quiz front rendering
|
// GetQuizData handler for obtaining data for quiz front rendering
|
||||||
func (s *Service) GetQuizData(c *fiber.Ctx) error {
|
func (s *Service) GetQuizData(c *fiber.Ctx) error {
|
||||||
cs, ok := c.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string)
|
return c.Status(fiber.StatusOK).JSON(s.quizConfigData)
|
||||||
if !ok {
|
|
||||||
return c.Status(fiber.StatusUnauthorized).SendString("no session in cookie")
|
|
||||||
}
|
|
||||||
|
|
||||||
hlogger := log_mw.ExtractLogger(c)
|
|
||||||
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 {
|
|
||||||
quizDto := dao2dtoQuiz(quiz)
|
|
||||||
return c.Status(fiber.StatusOK).JSON(GetQuizDataResp{
|
|
||||||
Settings: &quizDto,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if quiz.UniqueAnswers {
|
|
||||||
//todo implement after creating store answers
|
|
||||||
}
|
|
||||||
if quiz.Status != model.StatusStart && quiz.Status != model.StatusAI {
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
account, err := s.dal.AccountRepo.GetAccountByID(c.Context(), quiz.AccountId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString("can`t get account by quiz.AccountId")
|
|
||||||
}
|
|
||||||
|
|
||||||
showBadge := true
|
|
||||||
|
|
||||||
if priv, ok := account.Privileges["squizHideBadge"]; ok {
|
|
||||||
expiration := priv.CreatedAt.Add(time.Duration(priv.Amount) * 24 * time.Hour)
|
|
||||||
|
|
||||||
if time.Now().Before(expiration) {
|
|
||||||
showBadge = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var questions []model.Question
|
|
||||||
var cnt uint64
|
|
||||||
|
|
||||||
if quiz.Status == model.StatusAI {
|
|
||||||
if req.Page >= 1 {
|
|
||||||
req.Page+=1
|
|
||||||
}
|
|
||||||
questions, cnt, err = s.dal.QuestionRepo.GetQuestionsAI(c.Context(), int64(quiz.Id), cs, int32(req.Limit), int32(req.Page*req.Limit), req.Auditory)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
questions, cnt, err = s.dal.QuestionRepo.GetQuestionList(
|
|
||||||
c.Context(),
|
|
||||||
req.Limit,
|
|
||||||
req.Page*req.Limit,
|
|
||||||
0, 0, quiz.Id, false, false, "", "", req.Auditory,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := GetQuizDataResp{
|
|
||||||
Count: cnt,
|
|
||||||
Items: []ShavedQuestion{},
|
|
||||||
ShowBadge: showBadge,
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.NeedConfig {
|
|
||||||
quizDto := dao2dtoQuiz(quiz)
|
|
||||||
result.Settings = &quizDto
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, q := range questions {
|
|
||||||
result.Items = append(result.Items, dao2dtoQuestion(q))
|
|
||||||
}
|
|
||||||
|
|
||||||
utmData := model.UTMSavingMap{
|
|
||||||
"utm_content": c.Query("utm_content"),
|
|
||||||
"utm_medium": c.Query("utm_medium"),
|
|
||||||
"utm_campaign": c.Query("utm_campaign"),
|
|
||||||
"utm_source": c.Query("utm_source"),
|
|
||||||
"utm_term": c.Query("utm_term"),
|
|
||||||
"utm_referrer": c.Query("utm_referrer"),
|
|
||||||
"roistat": c.Query("roistat"),
|
|
||||||
"referrer": c.Query("referrer"),
|
|
||||||
"openstat_service": c.Query("openstat_service"),
|
|
||||||
"openstat_campaign": c.Query("openstat_campaign"),
|
|
||||||
"openstat_ad": c.Query("openstat_ad"),
|
|
||||||
"openstat_source": c.Query("openstat_source"),
|
|
||||||
"from": c.Query("from"),
|
|
||||||
"gclientid": c.Query("gclientid"),
|
|
||||||
"_ym_uid": c.Query("_ym_uid"),
|
|
||||||
"_ym_counter": c.Query("_ym_counter"),
|
|
||||||
"gclid": c.Query("gclid"),
|
|
||||||
"yclid": c.Query("yclid"),
|
|
||||||
"fbclid": c.Query("fbclid"),
|
|
||||||
}
|
|
||||||
|
|
||||||
deviceType := c.Get("DeviceType")
|
|
||||||
os := c.Get("OS")
|
|
||||||
browser := c.Get("Browser")
|
|
||||||
ip := c.IP()
|
|
||||||
device := c.Get("Device")
|
|
||||||
referrer := c.Get("Referer")
|
|
||||||
fp := ""
|
|
||||||
if cfp := c.Cookies(fingerprintCookie); cfp != "" {
|
|
||||||
fp = cfp
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.NeedConfig {
|
|
||||||
if len(questions) == 0 {
|
|
||||||
if req.Auditory != 0 {
|
|
||||||
return c.Status(fiber.StatusAccepted).SendString("questions are not ready yet")
|
|
||||||
}
|
|
||||||
return c.Status(fiber.StatusNotFound).SendString("question not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
answers, errs := s.dal.AnswerRepo.CreateAnswers(c.Context(), []model.Answer{{
|
|
||||||
Content: "start",
|
|
||||||
QuestionId: questions[0].Id,
|
|
||||||
QuizId: quiz.Id,
|
|
||||||
Start: true,
|
|
||||||
DeviceType: deviceType,
|
|
||||||
Device: device,
|
|
||||||
Browser: browser,
|
|
||||||
IP: ip,
|
|
||||||
OS: os,
|
|
||||||
Utm: utmData,
|
|
||||||
}}, cs, fp, quiz.Id)
|
|
||||||
if len(errs) != 0 {
|
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString(errs[0].Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
hlogger.Emit(models.InfoQuizOpen{
|
|
||||||
KeyOS: os,
|
|
||||||
KeyDevice: device,
|
|
||||||
KeyDeviceType: deviceType,
|
|
||||||
KeyBrowser: browser,
|
|
||||||
CtxQuiz: req.QuizId,
|
|
||||||
CtxQuizID: int64(quiz.Id),
|
|
||||||
CtxReferrer: referrer,
|
|
||||||
CtxIDInt: int64(answers[0].Id),
|
|
||||||
CtxSession: cs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fmt.Println("SETTIIIIII", cnt <= req.Limit, result)
|
|
||||||
|
|
||||||
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,
|
|
||||||
Status: quiz.Status,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MB Size constants
|
// MB Size constants
|
||||||
@ -329,17 +108,12 @@ type PutAnswersResponse struct {
|
|||||||
Stored []uint64 `json:"stored"`
|
Stored []uint64 `json:"stored"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo не отдавать ответы с типом start но сохранять брать из контента ответа
|
|
||||||
// поле бул также в GetQuizdata не отдавать его, но по запросам идет GetQuizdata получаем данные квиза
|
|
||||||
// аотом отправляем ответы в PutAnswers и сохраяняем их подумать как делать
|
|
||||||
func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||||
cs, ok := c.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string)
|
cs, ok := c.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return c.Status(fiber.StatusUnauthorized).SendString("no session in cookie")
|
return c.Status(fiber.StatusUnauthorized).SendString("no session in cookie")
|
||||||
}
|
}
|
||||||
|
|
||||||
hlogger := log_mw.ExtractLogger(c)
|
|
||||||
|
|
||||||
form, err := c.MultipartForm()
|
form, err := c.MultipartForm()
|
||||||
if err != nil || form == nil || form.File == nil {
|
if err != nil || form == nil || form.File == nil {
|
||||||
return c.Status(fiber.StatusBadRequest).SendString("expecting multipart form file")
|
return c.Status(fiber.StatusBadRequest).SendString("expecting multipart form file")
|
||||||
@ -361,8 +135,8 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
answersRaw, answers, trueRes []model.Answer
|
answersRaw, answers []model.Answer
|
||||||
errs []error
|
errs []error
|
||||||
)
|
)
|
||||||
|
|
||||||
deviceType := c.Get("DeviceType")
|
deviceType := c.Get("DeviceType")
|
||||||
@ -370,7 +144,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
browser := c.Get("Browser")
|
browser := c.Get("Browser")
|
||||||
ip := c.IP()
|
ip := c.IP()
|
||||||
device := c.Get("Device")
|
device := c.Get("Device")
|
||||||
referrer := c.Get("Referer")
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(answersStr[0]), &answersRaw); err != nil {
|
if err := json.Unmarshal([]byte(answersStr[0]), &answersRaw); err != nil {
|
||||||
return c.Status(fiber.StatusBadRequest).SendString("not valid answers string")
|
return c.Status(fiber.StatusBadRequest).SendString("not valid answers string")
|
||||||
@ -394,42 +167,39 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
fileIDMap := make(map[uint64]string)
|
fileIDMap := make(map[uint64]string)
|
||||||
|
|
||||||
for _, ans := range answersRaw {
|
for _, ans := range answersRaw {
|
||||||
|
final := false
|
||||||
if quiz.Status == model.StatusAI {
|
if finalStr, exists := form.Value["final"]; exists && len(finalStr) > 0 {
|
||||||
final := false
|
parsedFinal, err := strconv.ParseBool(finalStr[0])
|
||||||
if finalStr, exists := form.Value["final"]; exists && len(finalStr) > 0 {
|
|
||||||
parsedFinal, err := strconv.ParseBool(finalStr[0])
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.StatusBadRequest).SendString("invalid final value")
|
|
||||||
}
|
|
||||||
final = parsedFinal
|
|
||||||
}
|
|
||||||
|
|
||||||
question, err := s.dal.QuestionRepo.GetQuestionListByIDs(c.Context(), []int32{int32(ans.QuestionId)})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString("can not get questions")
|
return c.Status(fiber.StatusBadRequest).SendString("invalid final value")
|
||||||
}
|
}
|
||||||
|
final = parsedFinal
|
||||||
|
}
|
||||||
|
|
||||||
if len(question) == 0 {
|
question, err := s.dal.QuestionRepo.GetQuestionListByIDs(c.Context(), []int32{int32(ans.QuestionId)})
|
||||||
return c.Status(fiber.StatusNotFound).SendString("no questions found")
|
if err != nil {
|
||||||
}
|
return c.Status(fiber.StatusInternalServerError).SendString("can not get questions")
|
||||||
|
}
|
||||||
|
|
||||||
questionText, err := s.aiClient.SendAnswerer(final, question[0].Type, ans.Content, cs)
|
if len(question) == 0 {
|
||||||
if err != nil {
|
return c.Status(fiber.StatusNotFound).SendString("no questions found")
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed send answer to ai, err: %s", err.Error()))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_, err = s.dal.QuestionRepo.CreateQuestion(c.Context(), &model.Question{
|
questionText, err := s.aiClient.SendAnswerer(final, question[0].Type, ans.Content, cs)
|
||||||
QuizId: quiz.Id,
|
if err != nil {
|
||||||
Title: truncateUTF8(questionText,512),
|
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed send answer to ai, err: %s", err.Error()))
|
||||||
Type: model.TypeText,
|
}
|
||||||
Session: cs,
|
|
||||||
Content: `{"id":"gcZ-9SET-sM6stZSzQMmu","hint":{"text":" ","video":" "},"rule":{"children":[],"main":[],"parentId":" ","default":" "},"back":" ","originalBack":" ","autofill":false,"placeholder":" ","innerNameCheck":false,"innerName":" ","required":false,"answerType":"single","onlyNumbers":false}`,
|
_, err = s.dal.QuestionRepo.CreateQuestion(c.Context(), &model.Question{
|
||||||
Description: questionText,
|
QuizId: quiz.Id,
|
||||||
})
|
Title: truncateUTF8(questionText, 512),
|
||||||
if err != nil {
|
Type: model.TypeText,
|
||||||
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed create question type ai, err: %s", err.Error()))
|
Session: cs,
|
||||||
}
|
Content: `{"id":"gcZ-9SET-sM6stZSzQMmu","hint":{"text":" ","video":" "},"rule":{"children":[],"main":[],"parentId":" ","default":" "},"back":" ","originalBack":" ","autofill":false,"placeholder":" ","innerNameCheck":false,"innerName":" ","required":false,"answerType":"single","onlyNumbers":false}`,
|
||||||
|
Description: questionText,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed create question type ai, err: %s", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
ans.DeviceType = deviceType
|
ans.DeviceType = deviceType
|
||||||
@ -449,16 +219,8 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
continue
|
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)
|
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
|
ans.Content = fname
|
||||||
|
|
||||||
fileIDMap[ans.QuestionId] = fname
|
fileIDMap[ans.QuestionId] = fname
|
||||||
@ -475,22 +237,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusInternalServerError).SendString("error unmarshalling answer content: " + err.Error())
|
return c.Status(fiber.StatusInternalServerError).SendString("error unmarshalling answer content: " + err.Error())
|
||||||
}
|
}
|
||||||
ans.Email = content.Email
|
ans.Email = content.Email
|
||||||
|
|
||||||
hlogger.Emit(models.InfoContactForm{
|
|
||||||
KeyOS: os,
|
|
||||||
KeyDevice: device,
|
|
||||||
KeyDeviceType: deviceType,
|
|
||||||
KeyBrowser: browser,
|
|
||||||
CtxQuiz: quizID[0],
|
|
||||||
CtxQuizID: int64(quiz.Id),
|
|
||||||
CtxReferrer: referrer,
|
|
||||||
CtxQuestionID: int64(ans.QuestionId),
|
|
||||||
CtxIDInt: int64(ans.Id),
|
|
||||||
CtxSession: cs,
|
|
||||||
})
|
|
||||||
|
|
||||||
s.workerSendClientCh <- ans
|
|
||||||
trueRes = append(trueRes, ans)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -500,10 +246,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusInternalServerError).SendString("can not unmarshal quiz config")
|
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)
|
stored, ers := s.dal.AnswerRepo.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id)
|
||||||
if len(ers) != 0 {
|
if len(ers) != 0 {
|
||||||
for _, err := range ers {
|
for _, err := range ers {
|
||||||
@ -517,33 +259,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
var questionIDs []uint64
|
var questionIDs []uint64
|
||||||
for _, ans := range stored {
|
for _, ans := range stored {
|
||||||
questionIDs = append(questionIDs, ans.QuestionId)
|
questionIDs = append(questionIDs, ans.QuestionId)
|
||||||
if ans.Result {
|
|
||||||
hlogger.Emit(models.InfoResult{
|
|
||||||
KeyOS: os,
|
|
||||||
KeyDevice: device,
|
|
||||||
KeyDeviceType: deviceType,
|
|
||||||
KeyBrowser: browser,
|
|
||||||
CtxQuiz: quizID[0],
|
|
||||||
CtxQuizID: int64(quiz.Id),
|
|
||||||
CtxReferrer: referrer,
|
|
||||||
CtxQuestionID: int64(ans.QuestionId),
|
|
||||||
CtxIDInt: int64(ans.Id),
|
|
||||||
CtxSession: cs,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
hlogger.Emit(models.InfoAnswer{
|
|
||||||
KeyOS: os,
|
|
||||||
KeyDevice: device,
|
|
||||||
KeyDeviceType: deviceType,
|
|
||||||
KeyBrowser: browser,
|
|
||||||
CtxQuiz: quizID[0],
|
|
||||||
CtxQuizID: int64(quiz.Id),
|
|
||||||
CtxReferrer: referrer,
|
|
||||||
CtxQuestionID: int64(ans.QuestionId),
|
|
||||||
CtxIDInt: int64(ans.Id),
|
|
||||||
CtxSession: cs,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response := PutAnswersResponse{
|
response := PutAnswersResponse{
|
||||||
@ -554,34 +269,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
|||||||
return c.Status(fiber.StatusOK).JSON(response)
|
return c.Status(fiber.StatusOK).JSON(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Cookie(&fiber.Cookie{
|
|
||||||
Name: "quizUser",
|
|
||||||
Value: url.QueryEscape(string(shifr)),
|
|
||||||
})
|
|
||||||
|
|
||||||
return ctx.Redirect(s.redirectURl, fiber.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func truncateUTF8(s string, maxLen int) string {
|
func truncateUTF8(s string, maxLen int) string {
|
||||||
if utf8.RuneCountInString(s) <= maxLen {
|
if utf8.RuneCountInString(s) <= maxLen {
|
||||||
return s
|
return s
|
||||||
|
Loading…
Reference in New Issue
Block a user