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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.pena/PenaSide/common/log_mw"
|
||||
"gitea.pena/PenaSide/hlog"
|
||||
"gitea.pena/PenaSide/trashlog/wrappers/zaptrashlog"
|
||||
"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/common/dal"
|
||||
"gitea.pena/SQuiz/common/healthchecks"
|
||||
"gitea.pena/SQuiz/common/middleware"
|
||||
"gitea.pena/SQuiz/common/model"
|
||||
"gitea.pena/SQuiz/common/utils"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/skeris/appInit"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"time"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
@ -55,21 +43,13 @@ var _ appInit.CommonApp = (*App)(nil)
|
||||
type Options struct {
|
||||
LoggerProdMode bool `env:"IS_PROD_LOG" 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"`
|
||||
CrtFile string `env:"CRT" default:"server.crt"`
|
||||
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"`
|
||||
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"`
|
||||
PubKey string `env:"PUBLIC_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) {
|
||||
@ -102,77 +82,26 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co
|
||||
zap.String("SvcVersion", ver.Release),
|
||||
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 {
|
||||
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)
|
||||
pgdal, err := dal.New(ctx, options.PostgresCredentials, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
workerSendClientCh := make(chan model.Answer, 50)
|
||||
workerRespondentCh := make(chan []model.Answer, 50)
|
||||
blobstore, err := dalBS.New(ctx, minioClient)
|
||||
svc, err := service.New(service.ServiceDeps{
|
||||
Dal: pgdal,
|
||||
Encrypt: encrypt,
|
||||
RedirectURl: options.RedirectURL,
|
||||
AiClient: clients.NewAiClient("https://alvatar.com/api/engine/send_answer"),
|
||||
})
|
||||
if err != nil {
|
||||
zapLogger.Error("failed to create service", zap.Error(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})
|
||||
|
||||
@ -186,38 +115,28 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co
|
||||
})
|
||||
|
||||
app.Use(middleware.AnswererChain())
|
||||
app.Use(log_mw.ContextLogger(loggerHlog))
|
||||
app.Get("/liveness", healthchecks.Liveness)
|
||||
app.Get("/readiness", healthchecks.Readiness(&workerErr)) //todo parametrized readiness. should discuss ready reason
|
||||
app = svc.Register(app)
|
||||
|
||||
fmt.Println("SERVERSTART", fmt.Sprintf(":%s", options.NumberPort))
|
||||
|
||||
loggerHlog.Emit(InfoSvcReady{})
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
//if pgdal != nil {
|
||||
// pgdal.CloseAnswerer()
|
||||
//}
|
||||
err := app.Shutdown()
|
||||
if err != nil {
|
||||
loggerHlog.Emit(InfoSvcShutdown{Signal: err.Error()})
|
||||
zapLogger.Error("failed graceful shutdown", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
if options.IsProd {
|
||||
if err := app.ListenTLS(fmt.Sprintf(":%s", options.NumberPort), options.CrtFile, options.KeyFile); err != nil {
|
||||
loggerHlog.Emit(ErrorCanNotServe{
|
||||
Err: err,
|
||||
})
|
||||
zapLogger.Error("failed start server", zap.Error(err))
|
||||
errChan <- err
|
||||
}
|
||||
} else {
|
||||
if err := app.Listen(fmt.Sprintf(":%s", options.NumberPort)); err != nil {
|
||||
loggerHlog.Emit(ErrorCanNotServe{
|
||||
Err: err,
|
||||
})
|
||||
zapLogger.Error("failed start server", zap.Error(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
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.pena/PenaSide/common/log_mw"
|
||||
"gitea.pena/SQuiz/answerer/clients"
|
||||
"gitea.pena/SQuiz/answerer/dal"
|
||||
"gitea.pena/SQuiz/answerer/models"
|
||||
quizdal "gitea.pena/SQuiz/common/dal"
|
||||
"gitea.pena/SQuiz/common/middleware"
|
||||
"gitea.pena/SQuiz/common/model"
|
||||
"gitea.pena/SQuiz/common/utils"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
@ -28,58 +24,45 @@ const (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
store *dal.Storer
|
||||
dal *quizdal.DAL
|
||||
batch []model.Answer
|
||||
m sync.Mutex
|
||||
workerRespondentCh chan<- []model.Answer
|
||||
workerSendClientCh chan<- model.Answer
|
||||
encrypt *utils.Encrypt
|
||||
redirectURl string
|
||||
aiClient *clients.AIClient
|
||||
dal *quizdal.DAL
|
||||
batch []model.Answer
|
||||
m sync.Mutex
|
||||
encrypt *utils.Encrypt
|
||||
redirectURl string
|
||||
aiClient *clients.AIClient
|
||||
quizConfigData GetQuizDataResp
|
||||
}
|
||||
|
||||
type ServiceDeps struct {
|
||||
Store *dal.Storer
|
||||
Dal *quizdal.DAL
|
||||
WorkerRespondentCh chan<- []model.Answer
|
||||
WorkerSendClientCh chan<- model.Answer
|
||||
Encrypt *utils.Encrypt
|
||||
RedirectURl string
|
||||
AiClient *clients.AIClient
|
||||
Dal *quizdal.DAL
|
||||
Encrypt *utils.Encrypt
|
||||
RedirectURl string
|
||||
AiClient *clients.AIClient
|
||||
}
|
||||
|
||||
func New(deps ServiceDeps) *Service {
|
||||
return &Service{
|
||||
store: deps.Store,
|
||||
dal: deps.Dal,
|
||||
m: sync.Mutex{},
|
||||
batch: []model.Answer{},
|
||||
workerRespondentCh: deps.WorkerRespondentCh,
|
||||
workerSendClientCh: deps.WorkerSendClientCh,
|
||||
encrypt: deps.Encrypt,
|
||||
redirectURl: deps.RedirectURl,
|
||||
aiClient: deps.AiClient,
|
||||
func New(deps ServiceDeps) (*Service, error) {
|
||||
quizData, err := loadQuizDataConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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 {
|
||||
app.Post("/answer", s.PutAnswersOnePiece)
|
||||
app.Post("/settings", s.GetQuizData)
|
||||
app.Get("/logo", s.MiniPart)
|
||||
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 {
|
||||
Settings *ShavedQuiz `json:"settings,omitempty"`
|
||||
Items []ShavedQuestion `json:"items"`
|
||||
@ -87,7 +70,6 @@ type GetQuizDataResp struct {
|
||||
ShowBadge bool `json:"show_badge"`
|
||||
}
|
||||
|
||||
// ShavedQuiz shortened struct for delivery data to customer
|
||||
type ShavedQuiz struct {
|
||||
Fingerprinting bool `json:"fp"`
|
||||
Repeatable bool `json:"rep"`
|
||||
@ -100,7 +82,6 @@ type ShavedQuiz struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ShavedQuestion shortened struct for delivery data to customer
|
||||
type ShavedQuestion struct {
|
||||
Id uint64 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
@ -113,209 +94,7 @@ type ShavedQuestion struct {
|
||||
|
||||
// GetQuizData handler for obtaining data for quiz front rendering
|
||||
func (s *Service) GetQuizData(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")
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(s.quizConfigData)
|
||||
}
|
||||
|
||||
// MB Size constants
|
||||
@ -329,17 +108,12 @@ type PutAnswersResponse struct {
|
||||
Stored []uint64 `json:"stored"`
|
||||
}
|
||||
|
||||
// todo не отдавать ответы с типом start но сохранять брать из контента ответа
|
||||
// поле бул также в GetQuizdata не отдавать его, но по запросам идет GetQuizdata получаем данные квиза
|
||||
// аотом отправляем ответы в PutAnswers и сохраяняем их подумать как делать
|
||||
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")
|
||||
}
|
||||
|
||||
hlogger := log_mw.ExtractLogger(c)
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil || form == nil || form.File == nil {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("expecting multipart form file")
|
||||
@ -361,8 +135,8 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
var (
|
||||
answersRaw, answers, trueRes []model.Answer
|
||||
errs []error
|
||||
answersRaw, answers []model.Answer
|
||||
errs []error
|
||||
)
|
||||
|
||||
deviceType := c.Get("DeviceType")
|
||||
@ -370,7 +144,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
browser := c.Get("Browser")
|
||||
ip := c.IP()
|
||||
device := c.Get("Device")
|
||||
referrer := c.Get("Referer")
|
||||
|
||||
if err := json.Unmarshal([]byte(answersStr[0]), &answersRaw); err != nil {
|
||||
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)
|
||||
|
||||
for _, ans := range answersRaw {
|
||||
|
||||
if quiz.Status == model.StatusAI {
|
||||
final := false
|
||||
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)})
|
||||
final := false
|
||||
if finalStr, exists := form.Value["final"]; exists && len(finalStr) > 0 {
|
||||
parsedFinal, err := strconv.ParseBool(finalStr[0])
|
||||
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 {
|
||||
return c.Status(fiber.StatusNotFound).SendString("no questions found")
|
||||
}
|
||||
question, err := s.dal.QuestionRepo.GetQuestionListByIDs(c.Context(), []int32{int32(ans.QuestionId)})
|
||||
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 err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed send answer to ai, err: %s", err.Error()))
|
||||
}
|
||||
if len(question) == 0 {
|
||||
return c.Status(fiber.StatusNotFound).SendString("no questions found")
|
||||
}
|
||||
|
||||
_, err = s.dal.QuestionRepo.CreateQuestion(c.Context(), &model.Question{
|
||||
QuizId: quiz.Id,
|
||||
Title: truncateUTF8(questionText,512),
|
||||
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}`,
|
||||
Description: questionText,
|
||||
})
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString(fmt.Sprintf("failed create question type ai, err: %s", err.Error()))
|
||||
}
|
||||
questionText, err := s.aiClient.SendAnswerer(final, question[0].Type, ans.Content, cs)
|
||||
if err != nil {
|
||||
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{
|
||||
QuizId: quiz.Id,
|
||||
Title: truncateUTF8(questionText, 512),
|
||||
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}`,
|
||||
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
|
||||
@ -449,16 +219,8 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
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
|
||||
@ -475,22 +237,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("error unmarshalling answer content: " + err.Error())
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
for _, err := range ers {
|
||||
@ -517,33 +259,6 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
var questionIDs []uint64
|
||||
for _, ans := range stored {
|
||||
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{
|
||||
@ -554,39 +269,11 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
|
||||
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 {
|
||||
if utf8.RuneCountInString(s) <= maxLen {
|
||||
return s
|
||||
}
|
||||
|
||||
|
||||
// Конвертируем строку в руны для корректной обработки Unicode
|
||||
runes := []rune(s)
|
||||
return string(runes[:maxLen])
|
||||
|
Loading…
Reference in New Issue
Block a user