Compare commits

...

5 Commits

Author SHA1 Message Date
59747f9525 - 2025-07-09 15:16:36 +03:00
9ade0ce99a drop sqlc, sqlc schema and query 2025-07-09 15:13:12 +03:00
18fe7fd62a copy from squiz common 2025-07-09 15:10:11 +03:00
2dd714b369 - 2025-07-09 14:43:14 +03:00
ff8956355f cleaning pj 2025-07-09 14:40:02 +03:00
17 changed files with 1139 additions and 832 deletions

@ -4,27 +4,14 @@ 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/dal"
"gitea.pena/SQuiz/answerer/models" "gitea.pena/SQuiz/answerer/healthchecks"
"gitea.pena/SQuiz/answerer/savewc" "gitea.pena/SQuiz/answerer/middleware"
"gitea.pena/SQuiz/answerer/service" "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/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 +42,10 @@ 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"`
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) { func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) {
@ -102,77 +78,22 @@ 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)
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{ svc, err := service.New(service.ServiceDeps{
Addr: options.RedisHost, Dal: pgdal,
Password: options.RedisPassword, AiClient: clients.NewAiClient("https://alvatar.com/api/engine/send_answer"),
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)
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 +107,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
} }
} }

@ -1,10 +0,0 @@
package app
type InfoSvcStarted struct{}
type InfoSvcReady struct{}
type InfoSvcShutdown struct {
Signal string
}
type ErrorCanNotServe struct {
Err error
}

@ -2,31 +2,254 @@ package dal
import ( import (
"context" "context"
"database/sql"
_ "embed"
"encoding/json"
"fmt" "fmt"
"github.com/minio/minio-go/v7" "gitea.pena/SQuiz/answerer/dal/sqlcgen"
"io" "gitea.pena/SQuiz/answerer/model"
_ "github.com/ClickHouse/clickhouse-go"
"github.com/google/uuid"
"github.com/lib/pq"
_ "github.com/lib/pq"
"time"
) )
const ( type DAL struct {
bucket = "3c580be9-cf31f296-d055-49cf-b39e-30c7959dc17b" pool *sql.DB
bucketAnswers = "squizanswer" queries *sqlcgen.Queries
)
type Storer struct {
client *minio.Client
} }
func New(ctx context.Context, minioClient *minio.Client) (*Storer, error) { func New(ctx context.Context, cred string) (*DAL, error) {
return &Storer{ pool, err := sql.Open("postgres", cred)
client: minioClient, if err != nil {
return nil, err
}
timeoutCtx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
if err := pool.PingContext(timeoutCtx); err != nil {
return nil, err
}
queries := sqlcgen.New(pool)
return &DAL{
pool: pool,
queries: queries,
}, nil }, nil
} }
func (s *Storer) PutAnswer(ctx context.Context, file io.Reader, quizId, name string, question uint64, size int64) error { func (d *DAL) Close(ctx context.Context) error {
if _, err := s.client.PutObject(ctx, bucket, bucketAnswers + "/" +fmt.Sprintf("%s/%d/%s", quizId, question, name), file, size, err := d.pool.Close()
minio.PutObjectOptions{}); err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func (d *DAL) GetQuizByQid(ctx context.Context, qid string) (model.Quiz, error) {
_, err := uuid.Parse(qid)
if err != nil {
return model.Quiz{}, err
}
fmt.Println("QUID", `
SELECT * FROM quiz
WHERE
deleted = false AND
archived = false AND
status = 'start' AND
qid = $1;
`)
rows, err := d.pool.QueryContext(ctx, `
SELECT * FROM quiz
WHERE
deleted = false AND
archived = false AND
(status = 'start' OR status = 'ai') AND
qid = $1;
`, qid)
if err != nil {
return model.Quiz{}, err
}
defer rows.Close()
if !rows.Next() {
return model.Quiz{}, rows.Err()
}
var piece model.Quiz
pIds := pq.Int32Array{}
if err := rows.Scan(
&piece.Id,
&piece.Qid,
&piece.AccountId,
&piece.Deleted,
&piece.Archived,
&piece.Fingerprinting,
&piece.Repeatable,
&piece.NotePrevented,
&piece.MailNotifications,
&piece.UniqueAnswers,
&piece.Super,
&piece.GroupId,
&piece.Name,
&piece.Description,
&piece.Config,
&piece.Status,
&piece.Limit,
&piece.DueTo,
&piece.TimeOfPassing,
&piece.Pausable,
&piece.Version,
&piece.VersionComment,
&pIds,
&piece.CreatedAt,
&piece.UpdatedAt,
&piece.QuestionsCount,
&piece.PassedCount,
&piece.AverageTime,
&piece.SessionCount,
&piece.GigaChat,
); err != nil {
return model.Quiz{}, err
}
piece.ParentIds = pIds
return piece, nil
}
func (d *DAL) GetQuestionListByIDs(ctx context.Context, ids []int32) ([]model.Question, error) {
rows, err := d.queries.GetQuestionListByIDs(ctx, ids)
if err != nil {
return nil, err
}
var questions []model.Question
for _, row := range rows {
question := model.Question{
Id: uint64(row.ID),
QuizId: uint64(row.QuizID),
Title: row.Title,
Description: row.Description.String,
Type: string(row.Questiontype.([]byte)),
Required: row.Required.Bool,
Deleted: row.Deleted.Bool,
Page: int(row.Page.Int16),
Content: row.Content.String,
Version: int(row.Version.Int16),
ParentIds: row.ParentIds,
CreatedAt: row.CreatedAt.Time,
UpdatedAt: row.UpdatedAt.Time,
Auditory: row.Auditory,
}
questions = append(questions, question)
}
return questions, nil
}
func (d *DAL) CreateQuestion(ctx context.Context, record *model.Question) (uint64, error) {
params := sqlcgen.InsertQuestionParams{
QuizID: int64(record.QuizId),
Title: record.Title,
Description: sql.NullString{String: record.Description, Valid: true},
Questiontype: record.Type,
Required: sql.NullBool{Bool: record.Required, Valid: true},
Page: sql.NullInt16{Int16: int16(record.Page), Valid: true},
Content: sql.NullString{String: record.Content, Valid: true},
ParentIds: record.ParentIds,
UpdatedAt: sql.NullTime{Time: time.Now(), Valid: true},
Session: record.Session,
Auditory: record.Auditory,
}
data, err := d.queries.InsertQuestion(ctx, params)
if err != nil {
return 0, err
}
record.Id = uint64(data.ID)
record.CreatedAt = data.CreatedAt.Time
record.UpdatedAt = data.UpdatedAt.Time
return record.Id, nil
}
func (d *DAL) CreateAnswers(ctx context.Context, answers []model.Answer, session, fp string, quizID uint64) ([]model.Answer, []error) {
var (
createdAnswers []model.Answer
errs []error
)
tx, err := d.pool.BeginTx(ctx, nil)
if err != nil {
return nil, []error{err}
}
for _, ans := range answers {
if ans.Utm == nil {
ans.Utm = make(model.UTMSavingMap)
}
utmJSON, err := json.Marshal(ans.Utm)
if err != nil {
return nil, []error{err}
}
params := sqlcgen.InsertAnswersParams{
Content: sql.NullString{String: ans.Content, Valid: true},
QuizID: int64(quizID),
QuestionID: int64(ans.QuestionId),
Fingerprint: sql.NullString{String: fp, Valid: true},
Session: sql.NullString{String: session, Valid: true},
Result: sql.NullBool{Bool: ans.Result, Valid: true},
Email: ans.Email,
Device: ans.Device,
DeviceType: ans.DeviceType,
Ip: ans.IP,
Browser: ans.Browser,
Os: ans.OS,
Start: ans.Start,
Utm: utmJSON,
Version: ans.Version,
}
row, err := d.queries.InsertAnswers(ctx, params)
createdAnswer := model.Answer{
Id: uint64(row.ID),
Content: row.Content.String,
QuizId: uint64(row.QuizID),
QuestionId: uint64(row.QuestionID),
Fingerprint: row.Fingerprint.String,
Session: row.Session.String,
Result: row.Result.Bool,
New: row.New.Bool,
Email: row.Email,
DeviceType: row.DeviceType,
Device: row.Device,
OS: row.Os,
Browser: row.Browser,
IP: row.Ip,
Start: row.Start,
Version: row.Version,
}
if err != nil {
errs = append(errs, err)
} else {
createdAnswers = append(createdAnswers, createdAnswer)
}
}
err = tx.Commit()
if err != nil {
errs = append(errs, err)
return nil, errs
}
return createdAnswers, errs
}

31
dal/sqlcgen/db.go Normal file

@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package sqlcgen
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

400
dal/sqlcgen/models.go Normal file

@ -0,0 +1,400 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
package sqlcgen
import (
"database/sql"
"encoding/json"
"time"
"github.com/google/uuid"
)
type Account struct {
ID uuid.UUID `db:"id" json:"id"`
UserID string `db:"user_id" json:"user_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Deleted bool `db:"deleted" json:"deleted"`
}
type Accountsamo struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Amoid int32 `db:"amoid" json:"amoid"`
Name string `db:"name" json:"name"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
Subdomain string `db:"subdomain" json:"subdomain"`
Country string `db:"country" json:"country"`
Driveurl string `db:"driveurl" json:"driveurl"`
}
type Amocontact struct {
ID int64 `db:"id" json:"id"`
Accountid int32 `db:"accountid" json:"accountid"`
Amoid int32 `db:"amoid" json:"amoid"`
Field string `db:"field" json:"field"`
}
type Amocrmstatus struct {
ID int64 `db:"id" json:"id"`
Accountid int32 `db:"accountid" json:"accountid"`
Dealid int32 `db:"dealid" json:"dealid"`
Answerid int64 `db:"answerid" json:"answerid"`
Status string `db:"status" json:"status"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type Answer struct {
ID int64 `db:"id" json:"id"`
Content sql.NullString `db:"content" json:"content"`
QuizID int64 `db:"quiz_id" json:"quiz_id"`
QuestionID int64 `db:"question_id" json:"question_id"`
Fingerprint sql.NullString `db:"fingerprint" json:"fingerprint"`
Session sql.NullString `db:"session" json:"session"`
CreatedAt sql.NullTime `db:"created_at" json:"created_at"`
Result sql.NullBool `db:"result" json:"result"`
New sql.NullBool `db:"new" json:"new"`
Deleted sql.NullBool `db:"deleted" json:"deleted"`
Email string `db:"email" json:"email"`
DeviceType string `db:"device_type" json:"device_type"`
Device string `db:"device" json:"device"`
Os string `db:"os" json:"os"`
Browser string `db:"browser" json:"browser"`
Ip string `db:"ip" json:"ip"`
Start bool `db:"start" json:"start"`
Utm json.RawMessage `db:"utm" json:"utm"`
Version int32 `db:"version" json:"version"`
}
type Bitrixaccount struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Bitrixid string `db:"bitrixid" json:"bitrixid"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
Subdomain string `db:"subdomain" json:"subdomain"`
}
type Bitrixaccountuser struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Bitrixiduserid string `db:"bitrixiduserid" json:"bitrixiduserid"`
Name string `db:"name" json:"name"`
Lastname string `db:"lastname" json:"lastname"`
Secondname string `db:"secondname" json:"secondname"`
Title string `db:"title" json:"title"`
Email string `db:"email" json:"email"`
Ufdepartment []int32 `db:"ufdepartment" json:"ufdepartment"`
Workposition string `db:"workposition" json:"workposition"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Bitrixcontact struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Bitrixid int32 `db:"bitrixid" json:"bitrixid"`
Field string `db:"field" json:"field"`
}
type Bitrixcrmstatus struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Dealid int32 `db:"dealid" json:"dealid"`
Answerid int64 `db:"answerid" json:"answerid"`
Status string `db:"status" json:"status"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Bitrixfield struct {
ID int64 `db:"id" json:"id"`
Bitrixid string `db:"bitrixid" json:"bitrixid"`
Accountid string `db:"accountid" json:"accountid"`
Entityid interface{} `db:"entityid" json:"entityid"`
Fieldname string `db:"fieldname" json:"fieldname"`
Editfromlabel string `db:"editfromlabel" json:"editfromlabel"`
Fieldtype interface{} `db:"fieldtype" json:"fieldtype"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Bitrixrule struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Quizid int32 `db:"quizid" json:"quizid"`
Performerid string `db:"performerid" json:"performerid"`
Pipelineid int32 `db:"pipelineid" json:"pipelineid"`
Typeid string `db:"typeid" json:"typeid"`
Stageid string `db:"stageid" json:"stageid"`
Sourceid string `db:"sourceid" json:"sourceid"`
Statusid string `db:"statusid" json:"statusid"`
Fieldsrule json.RawMessage `db:"fieldsrule" json:"fieldsrule"`
Tagstoadd json.RawMessage `db:"tagstoadd" json:"tagstoadd"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
Leadflag bool `db:"leadflag" json:"leadflag"`
}
type Bitrixtoken struct {
Accountid string `db:"accountid" json:"accountid"`
Refreshtoken string `db:"refreshtoken" json:"refreshtoken"`
Accesstoken string `db:"accesstoken" json:"accesstoken"`
Authcode string `db:"authcode" json:"authcode"`
Expiration time.Time `db:"expiration" json:"expiration"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Field struct {
ID int64 `db:"id" json:"id"`
Amoid int32 `db:"amoid" json:"amoid"`
Code string `db:"code" json:"code"`
Accountid int32 `db:"accountid" json:"accountid"`
Name string `db:"name" json:"name"`
Entity interface{} `db:"entity" json:"entity"`
Type interface{} `db:"type" json:"type"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type Gigachataudience struct {
ID int64 `db:"id" json:"id"`
Quizid int64 `db:"quizid" json:"quizid"`
Age string `db:"age" json:"age"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
Sex int32 `db:"sex" json:"sex"`
}
type Leadtarget struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Type interface{} `db:"type" json:"type"`
Quizid int32 `db:"quizid" json:"quizid"`
Target string `db:"target" json:"target"`
Invitelink string `db:"invitelink" json:"invitelink"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Pipeline struct {
ID int64 `db:"id" json:"id"`
Amoid int32 `db:"amoid" json:"amoid"`
Accountid int32 `db:"accountid" json:"accountid"`
Name string `db:"name" json:"name"`
Isarchive bool `db:"isarchive" json:"isarchive"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type Pipelinebitrix struct {
ID int64 `db:"id" json:"id"`
Bitrixid int32 `db:"bitrixid" json:"bitrixid"`
Accountid string `db:"accountid" json:"accountid"`
Name string `db:"name" json:"name"`
Entitytypeid int32 `db:"entitytypeid" json:"entitytypeid"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Privilege struct {
ID int32 `db:"id" json:"id"`
Privilegeid string `db:"privilegeid" json:"privilegeid"`
AccountID uuid.UUID `db:"account_id" json:"account_id"`
PrivilegeName string `db:"privilege_name" json:"privilege_name"`
Amount int32 `db:"amount" json:"amount"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
type Question struct {
ID int64 `db:"id" json:"id"`
QuizID int64 `db:"quiz_id" json:"quiz_id"`
Title string `db:"title" json:"title"`
Description sql.NullString `db:"description" json:"description"`
Questiontype interface{} `db:"questiontype" json:"questiontype"`
Required sql.NullBool `db:"required" json:"required"`
Deleted sql.NullBool `db:"deleted" json:"deleted"`
Page sql.NullInt16 `db:"page" json:"page"`
Content sql.NullString `db:"content" json:"content"`
Version sql.NullInt16 `db:"version" json:"version"`
ParentIds []int32 `db:"parent_ids" json:"parent_ids"`
CreatedAt sql.NullTime `db:"created_at" json:"created_at"`
UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"`
Session string `db:"session" json:"session"`
Auditory int64 `db:"auditory" json:"auditory"`
}
type Quiz struct {
ID int64 `db:"id" json:"id"`
Qid uuid.NullUUID `db:"qid" json:"qid"`
Accountid string `db:"accountid" json:"accountid"`
Deleted sql.NullBool `db:"deleted" json:"deleted"`
Archived sql.NullBool `db:"archived" json:"archived"`
Fingerprinting sql.NullBool `db:"fingerprinting" json:"fingerprinting"`
Repeatable sql.NullBool `db:"repeatable" json:"repeatable"`
NotePrevented sql.NullBool `db:"note_prevented" json:"note_prevented"`
MailNotifications sql.NullBool `db:"mail_notifications" json:"mail_notifications"`
UniqueAnswers sql.NullBool `db:"unique_answers" json:"unique_answers"`
Super sql.NullBool `db:"super" json:"super"`
GroupID sql.NullInt64 `db:"group_id" json:"group_id"`
Name sql.NullString `db:"name" json:"name"`
Description sql.NullString `db:"description" json:"description"`
Config sql.NullString `db:"config" json:"config"`
Status interface{} `db:"status" json:"status"`
LimitAnswers sql.NullInt32 `db:"limit_answers" json:"limit_answers"`
DueTo sql.NullInt32 `db:"due_to" json:"due_to"`
TimeOfPassing sql.NullInt32 `db:"time_of_passing" json:"time_of_passing"`
Pausable sql.NullBool `db:"pausable" json:"pausable"`
Version sql.NullInt16 `db:"version" json:"version"`
VersionComment sql.NullString `db:"version_comment" json:"version_comment"`
ParentIds []int32 `db:"parent_ids" json:"parent_ids"`
CreatedAt sql.NullTime `db:"created_at" json:"created_at"`
UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"`
QuestionsCount sql.NullInt32 `db:"questions_count" json:"questions_count"`
AnswersCount sql.NullInt32 `db:"answers_count" json:"answers_count"`
AverageTimePassing sql.NullInt32 `db:"average_time_passing" json:"average_time_passing"`
SessionsCount sql.NullInt32 `db:"sessions_count" json:"sessions_count"`
Gigachat bool `db:"gigachat" json:"gigachat"`
}
type QuizPrivilegeUsage struct {
ID int64 `db:"id" json:"id"`
QuizID int64 `db:"quiz_id" json:"quiz_id"`
PrivilegeID string `db:"privilege_id" json:"privilege_id"`
UsedCount int32 `db:"used_count" json:"used_count"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
type QuizUtm struct {
ID int64 `db:"id" json:"id"`
Quizid int64 `db:"quizid" json:"quizid"`
Utm string `db:"utm" json:"utm"`
Deleted bool `db:"deleted" json:"deleted"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}
type RespondentState struct {
ID int64 `db:"id" json:"id"`
TelegramID int32 `db:"telegram_id" json:"telegram_id"`
Quizid int32 `db:"quizid" json:"quizid"`
State int64 `db:"state" json:"state"`
Lang string `db:"lang" json:"lang"`
Contact string `db:"contact" json:"contact"`
Finish bool `db:"finish" json:"finish"`
Session sql.NullString `db:"session" json:"session"`
}
type Rule struct {
ID int64 `db:"id" json:"id"`
Accountid int32 `db:"accountid" json:"accountid"`
Quizid int32 `db:"quizid" json:"quizid"`
Performerid int32 `db:"performerid" json:"performerid"`
Pipelineid int32 `db:"pipelineid" json:"pipelineid"`
Stepid int32 `db:"stepid" json:"stepid"`
Fieldsrule json.RawMessage `db:"fieldsrule" json:"fieldsrule"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
Tagstoadd json.RawMessage `db:"tagstoadd" json:"tagstoadd"`
}
type Step struct {
ID int64 `db:"id" json:"id"`
Amoid int32 `db:"amoid" json:"amoid"`
Pipelineid int32 `db:"pipelineid" json:"pipelineid"`
Accountid int32 `db:"accountid" json:"accountid"`
Name string `db:"name" json:"name"`
Color string `db:"color" json:"color"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type Stepbitrix struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Bitrixid string `db:"bitrixid" json:"bitrixid"`
Entityid string `db:"entityid" json:"entityid"`
Statusid string `db:"statusid" json:"statusid"`
Name string `db:"name" json:"name"`
Nameinit string `db:"nameinit" json:"nameinit"`
Color string `db:"color" json:"color"`
Pipelineid int32 `db:"pipelineid" json:"pipelineid"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Tag struct {
ID int64 `db:"id" json:"id"`
Amoid int32 `db:"amoid" json:"amoid"`
Accountid int32 `db:"accountid" json:"accountid"`
Entity interface{} `db:"entity" json:"entity"`
Name string `db:"name" json:"name"`
Color string `db:"color" json:"color"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type TelegramIntegration struct {
ID int64 `db:"id" json:"id"`
Accountid string `db:"accountid" json:"accountid"`
Quizid int32 `db:"quizid" json:"quizid"`
BotToken string `db:"bot_token" json:"bot_token"`
BotName string `db:"bot_name" json:"bot_name"`
Repeatable sql.NullBool `db:"repeatable" json:"repeatable"`
Deleted bool `db:"deleted" json:"deleted"`
Status interface{} `db:"status" json:"status"`
InstanceID int32 `db:"instance_id" json:"instance_id"`
}
type TelegramIntegrationInstance struct {
ID int64 `db:"id" json:"id"`
Checkout int64 `db:"checkout" json:"checkout"`
Limited int32 `db:"limited" json:"limited"`
Amount int32 `db:"amount" json:"amount"`
}
type TelegramUserQuizResult struct {
ID int64 `db:"id" json:"id"`
BotID int64 `db:"bot_id" json:"bot_id"`
QuizID int64 `db:"quiz_id" json:"quiz_id"`
CurrentField sql.NullString `db:"current_field" json:"current_field"`
UserAnswer sql.NullString `db:"user_answer" json:"user_answer"`
State interface{} `db:"state" json:"state"`
Session string `db:"session" json:"session"`
QuestionID int64 `db:"question_id" json:"question_id"`
Iter int32 `db:"iter" json:"iter"`
}
type Tgaccount struct {
ID int64 `db:"id" json:"id"`
Apiid int32 `db:"apiid" json:"apiid"`
Apihash string `db:"apihash" json:"apihash"`
Phonenumber string `db:"phonenumber" json:"phonenumber"`
Password string `db:"password" json:"password"`
Status interface{} `db:"status" json:"status"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}
type Token struct {
Accountid string `db:"accountid" json:"accountid"`
Refreshtoken string `db:"refreshtoken" json:"refreshtoken"`
Accesstoken string `db:"accesstoken" json:"accesstoken"`
Authcode string `db:"authcode" json:"authcode"`
Expiration time.Time `db:"expiration" json:"expiration"`
Createdat sql.NullTime `db:"createdat" json:"createdat"`
}
type Usersamo struct {
ID int64 `db:"id" json:"id"`
Amoid int32 `db:"amoid" json:"amoid"`
Amouserid int32 `db:"amouserid" json:"amouserid"`
Name string `db:"name" json:"name"`
Email string `db:"email" json:"email"`
Role int32 `db:"role" json:"role"`
Group int32 `db:"Group" json:"Group"`
Deleted bool `db:"deleted" json:"deleted"`
Createdat time.Time `db:"createdat" json:"createdat"`
}

196
dal/sqlcgen/queries.sql.go Normal file

@ -0,0 +1,196 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.29.0
// source: queries.sql
package sqlcgen
import (
"context"
"database/sql"
"encoding/json"
"github.com/lib/pq"
)
const getQuestionListByIDs = `-- name: GetQuestionListByIDs :many
SELECT id, quiz_id, title, description, questiontype, required, deleted, page, content, version, parent_ids, created_at, updated_at, session, auditory FROM question WHERE id = ANY($1::int[]) AND deleted = FALSE
`
func (q *Queries) GetQuestionListByIDs(ctx context.Context, dollar_1 []int32) ([]Question, error) {
rows, err := q.db.QueryContext(ctx, getQuestionListByIDs, pq.Array(dollar_1))
if err != nil {
return nil, err
}
defer rows.Close()
var items []Question
for rows.Next() {
var i Question
if err := rows.Scan(
&i.ID,
&i.QuizID,
&i.Title,
&i.Description,
&i.Questiontype,
&i.Required,
&i.Deleted,
&i.Page,
&i.Content,
&i.Version,
pq.Array(&i.ParentIds),
&i.CreatedAt,
&i.UpdatedAt,
&i.Session,
&i.Auditory,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const insertAnswers = `-- name: InsertAnswers :one
INSERT INTO answer(
content,
quiz_id,
question_id,
fingerprint,
session,
result,
email,
device_type,
device,
os,
browser,
ip,
start,
utm,
version
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
RETURNING id, content, quiz_id, question_id, fingerprint, session, created_at, result, new, deleted, email, device_type, device, os, browser, ip, start, utm, version
`
type InsertAnswersParams struct {
Content sql.NullString `db:"content" json:"content"`
QuizID int64 `db:"quiz_id" json:"quiz_id"`
QuestionID int64 `db:"question_id" json:"question_id"`
Fingerprint sql.NullString `db:"fingerprint" json:"fingerprint"`
Session sql.NullString `db:"session" json:"session"`
Result sql.NullBool `db:"result" json:"result"`
Email string `db:"email" json:"email"`
DeviceType string `db:"device_type" json:"device_type"`
Device string `db:"device" json:"device"`
Os string `db:"os" json:"os"`
Browser string `db:"browser" json:"browser"`
Ip string `db:"ip" json:"ip"`
Start bool `db:"start" json:"start"`
Utm json.RawMessage `db:"utm" json:"utm"`
Version int32 `db:"version" json:"version"`
}
func (q *Queries) InsertAnswers(ctx context.Context, arg InsertAnswersParams) (Answer, error) {
row := q.db.QueryRowContext(ctx, insertAnswers,
arg.Content,
arg.QuizID,
arg.QuestionID,
arg.Fingerprint,
arg.Session,
arg.Result,
arg.Email,
arg.DeviceType,
arg.Device,
arg.Os,
arg.Browser,
arg.Ip,
arg.Start,
arg.Utm,
arg.Version,
)
var i Answer
err := row.Scan(
&i.ID,
&i.Content,
&i.QuizID,
&i.QuestionID,
&i.Fingerprint,
&i.Session,
&i.CreatedAt,
&i.Result,
&i.New,
&i.Deleted,
&i.Email,
&i.DeviceType,
&i.Device,
&i.Os,
&i.Browser,
&i.Ip,
&i.Start,
&i.Utm,
&i.Version,
)
return i, err
}
const insertQuestion = `-- name: InsertQuestion :one
INSERT INTO question (
quiz_id,
title,
description,
questiontype,
required,
page,
content,
parent_ids,
updated_at,
session,
auditory
)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
RETURNING id, created_at, updated_at
`
type InsertQuestionParams struct {
QuizID int64 `db:"quiz_id" json:"quiz_id"`
Title string `db:"title" json:"title"`
Description sql.NullString `db:"description" json:"description"`
Questiontype interface{} `db:"questiontype" json:"questiontype"`
Required sql.NullBool `db:"required" json:"required"`
Page sql.NullInt16 `db:"page" json:"page"`
Content sql.NullString `db:"content" json:"content"`
ParentIds []int32 `db:"parent_ids" json:"parent_ids"`
UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"`
Session string `db:"session" json:"session"`
Auditory int64 `db:"auditory" json:"auditory"`
}
type InsertQuestionRow struct {
ID int64 `db:"id" json:"id"`
CreatedAt sql.NullTime `db:"created_at" json:"created_at"`
UpdatedAt sql.NullTime `db:"updated_at" json:"updated_at"`
}
func (q *Queries) InsertQuestion(ctx context.Context, arg InsertQuestionParams) (InsertQuestionRow, error) {
row := q.db.QueryRowContext(ctx, insertQuestion,
arg.QuizID,
arg.Title,
arg.Description,
arg.Questiontype,
arg.Required,
arg.Page,
arg.Content,
pq.Array(arg.ParentIds),
arg.UpdatedAt,
arg.Session,
arg.Auditory,
)
var i InsertQuestionRow
err := row.Scan(&i.ID, &i.CreatedAt, &i.UpdatedAt)
return i, err
}

31
go.mod

@ -5,50 +5,27 @@ go 1.23.2
toolchain go1.23.4 toolchain go1.23.4
require ( require (
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 github.com/ClickHouse/clickhouse-go v1.5.4
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9
gitea.pena/PenaSide/trashlog v0.0.0-20250224122049-ddb4d72e9d07
gitea.pena/SQuiz/common v0.0.0-20250610213406-ac5c9d667dc7
github.com/go-redis/redis/v8 v8.11.5
github.com/gofiber/fiber/v2 v2.52.5 github.com/gofiber/fiber/v2 v2.52.5
github.com/minio/minio-go/v7 v7.0.81 github.com/google/uuid v1.6.0
github.com/lib/pq v1.10.9
github.com/rs/xid v1.6.0 github.com/rs/xid v1.6.0
github.com/skeris/appInit v1.0.2 github.com/skeris/appInit v1.0.2
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
) )
require ( require (
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735 // indirect
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/tealeg/xlsx v1.0.5 // indirect github.com/stretchr/testify v1.9.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
go.etcd.io/bbolt v1.3.10 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
) )

86
go.sum

@ -1,17 +1,3 @@
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 h1:EgBe8VcdPwmxbSzYLndncP+NmR73uYuXxkTeDlEttEE=
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU=
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9 h1:tBkXWNIt8icmkMMnq8MA421RWkUy4OZh5P7C3q8uCu4=
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9/go.mod h1:sanhSL8aEsfcq21P+eItYiAnKAre+B67nGJmDfk2cf0=
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735 h1:jDVeUhGBTXBibmW5dmtJg2m2+z5z2Rf6J4G0LpjVoJ0=
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735/go.mod h1:gdd+vOT6up9STkEbxa2qESLIMZFjCmRbkcheFQCVgZU=
gitea.pena/PenaSide/trashlog v0.0.0-20250224122049-ddb4d72e9d07 h1:bUIUgzXQt16aBqSccI//BaODpRCTIaqlddSepM98QSc=
gitea.pena/PenaSide/trashlog v0.0.0-20250224122049-ddb4d72e9d07/go.mod h1:GRfWJerTUlgy82CiYAxE4tVYSVV54zEJJQy17Fx46E4=
gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5 h1:C+iCsGMSUJonOTNNk8wWYOfzZ0Jjw+2IQ5FaEGwRVT0=
gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
gitea.pena/SQuiz/common v0.0.0-20250610192732-9f01faa53ea9 h1:y97VEguCy1Qf/hl/XT9IfX3EgwLgAkn2+1sBDuxjo7Q=
gitea.pena/SQuiz/common v0.0.0-20250610192732-9f01faa53ea9/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
gitea.pena/SQuiz/common v0.0.0-20250610213406-ac5c9d667dc7 h1:LdL8G958qTeDNNsLbqhnlnFbbiSMoqqULnNqxIR4c/w=
gitea.pena/SQuiz/common v0.0.0-20250610213406-ac5c9d667dc7/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
@ -19,38 +5,14 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -58,15 +20,9 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
@ -78,23 +34,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.81 h1:SzhMN0TQ6T/xSBu6Nvw3M5M8voM+Ht8RH3hE8S7zxaA=
github.com/minio/minio-go/v7 v7.0.81/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pioz/faker v1.7.3 h1:Tez8Emuq0UN+/d6mo3a9m/9ZZ/zdfJk0c5RtRatrceM=
github.com/pioz/faker v1.7.3/go.mod h1:xSpay5w/oz1a6+ww0M3vfpe40pSIykeUPeWEc3TvVlc=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
@ -109,18 +51,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tealeg/xlsx v1.0.5 h1:+f8oFmvY8Gw1iUXzPk+kz+4GpbDZPK1FhPiQRd+ypgE=
github.com/tealeg/xlsx v1.0.5/go.mod h1:btRS8dz54TDnvKNosuAqxrM1QgN1udgk9O34bDCnORM=
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33 h1:N9f/Q+2Ssa+yDcbfaoLTYvXmdeyUUxsJKdPUVsjSmiA=
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33/go.mod h1:rpcH99JknBh8seZmlOlUg51gasZH6QH34oXNsIwYT6E=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
@ -133,18 +69,12 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -152,31 +82,15 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tucnak/telebot.v2 v2.5.0 h1:i+NynLo443Vp+Zn3Gv9JBjh3Z/PaiKAQwcnhNI7y6Po=
gopkg.in/tucnak/telebot.v2 v2.5.0/go.mod h1:BgaIIx50PSRS9pG59JH+geT82cfvoJU/IaI5TJdN3v8=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

@ -0,0 +1,18 @@
package healthchecks
import (
"github.com/gofiber/fiber/v2"
)
func Liveness(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
}
func Readiness(err *error) fiber.Handler {
return func(c *fiber.Ctx) error {
if *err != nil {
return c.SendString((*err).Error())
}
return c.SendStatus(fiber.StatusOK)
}
}

28
middleware/middleware.go Normal file

@ -0,0 +1,28 @@
package middleware
import (
"github.com/gofiber/fiber/v2"
"github.com/rs/xid"
)
type ContextKey string
const (
SessionKey = "X-SessionKey"
)
func AnswererChain() fiber.Handler {
return func(c *fiber.Ctx) error {
session := c.Get(SessionKey)
if session == "" {
session := xid.New().String()
c.Set(SessionKey, session)
c.Locals(ContextKey(SessionKey), session)
} else {
c.Locals(ContextKey(SessionKey), session)
}
return c.Next()
}
}

133
model/model.go Normal file

@ -0,0 +1,133 @@
package model
import "time"
const TypeText = "text"
type Quiz struct {
Id uint64 `json:"id"`
Qid string `json:"qid"` // uuid for secure data get and post
AccountId string `json:"accountid"` // account that created the quiz
Deleted bool `json:"deleted"` // fake delete field
Archived bool `json:"archived"` // field for archiving quiz
Fingerprinting bool `json:"fingerprinting"` // field that need for storing 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
Name string `json:"name"`
Description string `json:"description"`
Config string `json:"config"` // serialize json with config for page rules
Status string `json:"status"` // status of quiz as enum. see Status const higher
Limit uint64 `json:"limit"` // max count of quiz passing
DueTo uint64 `json:"due_to"` // time when quiz is end
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
Version int `json:"version"`
VersionComment string `json:"version_comment"`
ParentIds []int32 `json:"parent_ids"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
QuestionsCount uint64 `json:"questions_count"`
SessionCount uint64 `json:"session_count"`
PassedCount uint64 `json:"passed_count"`
AverageTime uint64 `json:"average_time"`
Super bool `json:"super"`
GroupId uint64 `json:"group_id"`
GigaChat bool `json:"giga_chat"`
}
type Question struct {
Id uint64 `json:"id"`
QuizId uint64 `json:"quiz_id"` // relation to quiz table
Title string `json:"title"` // title of question
Description string `json:"description"` // html\text representation of question and question description for answerer
Type string `json:"type"` // type field. enum with constants from consts higher
Required bool `json:"required"` // answerer must answer this question
Deleted bool `json:"deleted"` // fake deleting field
Page int `json:"page"` // set page number for question
//serialized json. caption for button type, array of key-value pairs for checkbox and select types,
// placeholder and input title for text and file types
Content string `json:"content"`
Version int `json:"version"`
//todo check the best choice: question duplication and no statistics about the most unstable question or
//low performance of high complexity join-on-array query
ParentIds []int32 `json:"parent_ids"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Session string `json:"session"`
Auditory int64 `json:"auditory"`
}
type Answer struct {
Id uint64
Content string `json:"content"` //serialized json. empty for buttons
QuestionId uint64 `json:"question_id"` // relation for quiz
QuizId uint64 // relation for quiz
Fingerprint string // device Id
Session string // xid of session
Result bool
CreatedAt time.Time
New bool `json:"new"`
Deleted bool
Email string
DeviceType string
Device string
Browser string
IP string
OS string
Start bool
Utm UTMSavingMap
Version int32
}
type UTMSavingMap map[string]string
type ResultContent struct {
Text string `json:"text"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
Telegram string `json:"telegram"`
Wechat string `json:"wechat"`
Viber string `json:"viber"`
Vk string `json:"vk"`
Skype string `json:"skype"`
Whatsup string `json:"whatsup"`
Messenger string `json:"messenger"`
Custom map[string]string `json:"customs"`
Start bool `json:"start"`
//IMGContent ImageContent `json:"imagecontent"`
}
type QuizConfig struct {
Mailing ResultInfo `json:"resultInfo"`
}
type ResultInfo struct {
When string `json:"when"` // before|after|email
Theme string `json:"theme"` // тема письма
Reply string `json:"reply"` // email для ответов, указывается в создании письма
ReplName string `json:"repl_name"` // имя отправителя
}

@ -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

@ -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

@ -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,17 @@
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" quizdal "gitea.pena/SQuiz/answerer/dal"
"gitea.pena/SQuiz/answerer/models" "gitea.pena/SQuiz/answerer/middleware"
quizdal "gitea.pena/SQuiz/common/dal" "gitea.pena/SQuiz/answerer/model"
"gitea.pena/SQuiz/common/middleware"
"gitea.pena/SQuiz/common/model"
"gitea.pena/SQuiz/common/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"net/url"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"unicode/utf8"
"github.com/rs/xid" "github.com/rs/xid"
) )
@ -28,58 +22,35 @@ const (
) )
type Service struct { type Service struct {
store *dal.Storer dal *quizdal.DAL
dal *quizdal.DAL aiClient *clients.AIClient
batch []model.Answer quizConfigData GetQuizDataResp
m sync.Mutex
workerRespondentCh chan<- []model.Answer
workerSendClientCh chan<- model.Answer
encrypt *utils.Encrypt
redirectURl string
aiClient *clients.AIClient
} }
type ServiceDeps struct { type ServiceDeps struct {
Store *dal.Storer Dal *quizdal.DAL
Dal *quizdal.DAL AiClient *clients.AIClient
WorkerRespondentCh chan<- []model.Answer
WorkerSendClientCh chan<- model.Answer
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,
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 +58,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 +70,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 +82,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 +96,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 +123,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 +132,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")
@ -386,7 +147,7 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
fp = cfp fp = cfp
} }
quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), quizID[0]) quiz, err := s.dal.GetQuizByQid(c.Context(), quizID[0])
if err != nil { if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("can not get quiz") return c.Status(fiber.StatusInternalServerError).SendString("can not get quiz")
} }
@ -394,42 +155,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.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.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 +207,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 +225,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,11 +234,7 @@ 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 { stored, ers := s.dal.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id)
s.workerRespondentCh <- trueRes
}
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 {
if strings.Contains(err.Error(), "duplicate key value") { if strings.Contains(err.Error(), "duplicate key value") {
@ -517,33 +247,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,39 +257,11 @@ 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
} }
// Конвертируем строку в руны для корректной обработки Unicode // Конвертируем строку в руны для корректной обработки Unicode
runes := []rune(s) runes := []rune(s)
return string(runes[:maxLen]) return string(runes[:maxLen])