diff --git a/app/app.go b/app/app.go
index 2a57e17..3fbda83 100644
--- a/app/app.go
+++ b/app/app.go
@@ -5,10 +5,10 @@ import (
"errors"
"fmt"
"gitea.pena/SQuiz/answerer/clients"
+ "gitea.pena/SQuiz/answerer/dal"
+ "gitea.pena/SQuiz/answerer/healthchecks"
+ "gitea.pena/SQuiz/answerer/middleware"
"gitea.pena/SQuiz/answerer/service"
- "gitea.pena/SQuiz/common/dal"
- "gitea.pena/SQuiz/common/healthchecks"
- "gitea.pena/SQuiz/common/middleware"
"github.com/gofiber/fiber/v2"
"github.com/skeris/appInit"
"go.uber.org/zap"
@@ -79,7 +79,7 @@ func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.Co
zap.String("SvcBuildTime", ver.BuildTime),
)
- pgdal, err := dal.New(ctx, options.PostgresCredentials, nil)
+ pgdal, err := dal.New(ctx, options.PostgresCredentials)
if err != nil {
return nil, err
}
diff --git a/dal/dal.go b/dal/dal.go
new file mode 100644
index 0000000..e2f515b
--- /dev/null
+++ b/dal/dal.go
@@ -0,0 +1,255 @@
+package dal
+
+import (
+ "context"
+ "database/sql"
+ _ "embed"
+ "encoding/json"
+ "fmt"
+ "gitea.pena/SQuiz/answerer/dal/sqlcgen"
+ "gitea.pena/SQuiz/answerer/model"
+ _ "github.com/ClickHouse/clickhouse-go"
+ "github.com/google/uuid"
+ "github.com/lib/pq"
+ _ "github.com/lib/pq"
+ "time"
+)
+
+type DAL struct {
+ pool *sql.DB
+ queries *sqlcgen.Queries
+}
+
+func New(ctx context.Context, cred string) (*DAL, error) {
+ pool, err := sql.Open("postgres", cred)
+ 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
+}
+
+func (d *DAL) Close(ctx context.Context) error {
+ err := d.pool.Close()
+ if err != nil {
+ return err
+ }
+ 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
+}
diff --git a/dal/db_query/queries.sql b/dal/db_query/queries.sql
new file mode 100644
index 0000000..780de63
--- /dev/null
+++ b/dal/db_query/queries.sql
@@ -0,0 +1,39 @@
+-- name: GetQuestionListByIDs :many
+SELECT * FROM question WHERE id = ANY($1::int[]) AND deleted = FALSE;
+
+-- 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;
+
+-- 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 *;
\ No newline at end of file
diff --git a/dal/schema/000001_init.down.sql b/dal/schema/000001_init.down.sql
new file mode 100644
index 0000000..54966a0
--- /dev/null
+++ b/dal/schema/000001_init.down.sql
@@ -0,0 +1,24 @@
+-- Drop indexes
+DROP INDEX IF EXISTS subquizes;
+DROP INDEX IF EXISTS birthtime;
+DROP INDEX IF EXISTS groups;
+DROP INDEX IF EXISTS timeouted;
+DROP INDEX IF EXISTS active ON quiz;
+DROP INDEX IF EXISTS questiontype;
+DROP INDEX IF EXISTS required;
+DROP INDEX IF EXISTS relation;
+DROP INDEX IF EXISTS active ON question;
+
+-- Drop tables
+DROP TABLE IF EXISTS privileges;
+DROP TABLE IF EXISTS answer;
+DROP TABLE IF EXISTS question;
+DROP TABLE IF EXISTS quiz;
+DROP TABLE IF EXISTS account;
+
+-- Drop types
+DO $$
+BEGIN
+DROP TYPE IF EXISTS question_type;
+DROP TYPE IF EXISTS quiz_status;
+END$$;
\ No newline at end of file
diff --git a/dal/schema/000001_init.up.sql b/dal/schema/000001_init.up.sql
new file mode 100644
index 0000000..9aefa67
--- /dev/null
+++ b/dal/schema/000001_init.up.sql
@@ -0,0 +1,120 @@
+-- Create types
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'question_type') THEN
+CREATE TYPE question_type AS ENUM (
+ 'variant',
+ 'images',
+ 'varimg',
+ 'emoji',
+ 'text',
+ 'select',
+ 'date',
+ 'number',
+ 'file',
+ 'page',
+ 'rating'
+ );
+END IF;
+
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'quiz_status') THEN
+CREATE TYPE quiz_status AS ENUM (
+ 'draft',
+ 'template',
+ 'stop',
+ 'start',
+ 'timeout',
+ 'offlimit'
+ );
+END IF;
+
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+END$$;
+
+-- Create tables
+CREATE TABLE IF NOT EXISTS account (
+ id UUID PRIMARY KEY,
+ user_id VARCHAR(24),
+ email VARCHAR(50),
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ deleted BOOLEAN DEFAULT false
+ );
+
+CREATE TABLE IF NOT EXISTS quiz (
+ id bigserial UNIQUE NOT NULL PRIMARY KEY,
+ qid uuid DEFAULT uuid_generate_v4(),
+ accountid varchar(30) NOT NULL,
+ deleted boolean DEFAULT false,
+ archived boolean DEFAULT false,
+ fingerprinting boolean DEFAULT false,
+ repeatable boolean DEFAULT false,
+ note_prevented boolean DEFAULT false,
+ mail_notifications boolean DEFAULT false,
+ unique_answers boolean DEFAULT false,
+ super boolean DEFAULT false,
+ group_id bigint DEFAULT 0,
+ name varchar(280),
+ description text,
+ config text,
+ status quiz_status DEFAULT 'draft',
+ limit_answers integer DEFAULT 0,
+ due_to integer DEFAULT 0,
+ time_of_passing integer DEFAULT 0,
+ pausable boolean DEFAULT false,
+ version smallint DEFAULT 0,
+ version_comment text DEFAULT '',
+ parent_ids integer[],
+ created_at timestamp DEFAULT CURRENT_TIMESTAMP,
+ updated_at timestamp DEFAULT CURRENT_TIMESTAMP,
+ questions_count integer DEFAULT 0,
+ answers_count integer DEFAULT 0,
+ average_time_passing integer DEFAULT 0
+ );
+
+CREATE TABLE IF NOT EXISTS question (
+ id bigserial UNIQUE NOT NULL PRIMARY KEY,
+ quiz_id bigint NOT NULL,
+ title varchar(512) NOT NULL,
+ description text,
+ questiontype question_type DEFAULT 'text',
+ required boolean DEFAULT false,
+ deleted boolean DEFAULT false,
+ page smallint DEFAULT 0,
+ content text,
+ version smallint DEFAULT 0,
+ parent_ids integer[],
+ created_at timestamp DEFAULT CURRENT_TIMESTAMP,
+ updated_at timestamp DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT quiz_relation FOREIGN KEY(quiz_id) REFERENCES quiz(id)
+ );
+
+CREATE TABLE IF NOT EXISTS answer (
+ id bigserial UNIQUE NOT NULL PRIMARY KEY,
+ content text,
+ quiz_id bigint NOT NULL REFERENCES quiz(id),
+ question_id bigint NOT NULL REFERENCES question(id),
+ fingerprint varchar(1024),
+ session varchar(20),
+ created_at timestamp DEFAULT CURRENT_TIMESTAMP
+ );
+
+CREATE TABLE IF NOT EXISTS privileges (
+ id SERIAL PRIMARY KEY,
+ privilegeID VARCHAR(50),
+ account_id UUID,
+ privilege_name VARCHAR(255),
+ amount INT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (account_id) REFERENCES account (id)
+ );
+
+-- Create indexes
+CREATE INDEX IF NOT EXISTS active ON question(deleted) WHERE deleted=false;
+CREATE INDEX IF NOT EXISTS relation ON question(quiz_id DESC);
+CREATE INDEX IF NOT EXISTS required ON question(required DESC);
+CREATE INDEX IF NOT EXISTS questiontype ON question(questiontype);
+CREATE INDEX IF NOT EXISTS active ON quiz(deleted, archived, status) WHERE deleted = false AND archived = false AND status = 'start';
+CREATE INDEX IF NOT EXISTS timeouted ON quiz(due_to DESC) WHERE deleted = false AND due_to <> 0 AND status <> 'timeout';
+CREATE INDEX IF NOT EXISTS groups ON quiz(super) WHERE super = true;
+CREATE INDEX IF NOT EXISTS birthtime ON quiz(created_at DESC);
+CREATE INDEX IF NOT EXISTS subquizes ON quiz(group_id DESC) WHERE group_id <> 0;
diff --git a/dal/schema/000002_init.down.sql b/dal/schema/000002_init.down.sql
new file mode 100644
index 0000000..0aa3d9e
--- /dev/null
+++ b/dal/schema/000002_init.down.sql
@@ -0,0 +1 @@
+ALTER TABLE answer DROP COLUMN IF EXISTS result;
diff --git a/dal/schema/000002_init.up.sql b/dal/schema/000002_init.up.sql
new file mode 100644
index 0000000..4083153
--- /dev/null
+++ b/dal/schema/000002_init.up.sql
@@ -0,0 +1 @@
+ALTER TABLE answer ADD COLUMN result BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/dal/schema/000003_init.down.sql b/dal/schema/000003_init.down.sql
new file mode 100644
index 0000000..94e352f
--- /dev/null
+++ b/dal/schema/000003_init.down.sql
@@ -0,0 +1 @@
+ALTER TABLE quiz DROP COLUMN IF EXISTS sessions_count;
diff --git a/dal/schema/000003_init.up.sql b/dal/schema/000003_init.up.sql
new file mode 100644
index 0000000..a292d62
--- /dev/null
+++ b/dal/schema/000003_init.up.sql
@@ -0,0 +1 @@
+ALTER TABLE quiz ADD COLUMN sessions_count integer;
diff --git a/dal/schema/000004_init.down.sql b/dal/schema/000004_init.down.sql
new file mode 100644
index 0000000..b6d5ec4
--- /dev/null
+++ b/dal/schema/000004_init.down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE quiz DROP COLUMN IF EXISTS new;
+ALTER TABLE quiz DROP COLUMN IF EXISTS deleted;
\ No newline at end of file
diff --git a/dal/schema/000004_init.up.sql b/dal/schema/000004_init.up.sql
new file mode 100644
index 0000000..9dc5591
--- /dev/null
+++ b/dal/schema/000004_init.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE answer ADD COLUMN new BOOLEAN DEFAULT TRUE;
+ALTER TABLE answer ADD COLUMN deleted BOOLEAN DEFAULT FALSE;
\ No newline at end of file
diff --git a/dal/schema/000005_init.down.sql b/dal/schema/000005_init.down.sql
new file mode 100644
index 0000000..ccae4f5
--- /dev/null
+++ b/dal/schema/000005_init.down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE answer DROP COLUMN IF EXISTS email;
+DROP INDEX IF EXISTS answer_email_unique_idx;
\ No newline at end of file
diff --git a/dal/schema/000005_init.up.sql b/dal/schema/000005_init.up.sql
new file mode 100644
index 0000000..f2bfe97
--- /dev/null
+++ b/dal/schema/000005_init.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE answer ADD COLUMN email VARCHAR(50) NOT NULL DEFAULT '';
+CREATE UNIQUE INDEX IF NOT EXISTS answer_email_unique_idx ON answer (quiz_id, email) WHERE email <> '';
\ No newline at end of file
diff --git a/dal/schema/000006_init.down.sql b/dal/schema/000006_init.down.sql
new file mode 100644
index 0000000..355ba3b
--- /dev/null
+++ b/dal/schema/000006_init.down.sql
@@ -0,0 +1,6 @@
+ALTER TABLE answer
+DROP COLUMN device_type,
+DROP COLUMN device,
+DROP COLUMN os,
+DROP COLUMN browser,
+DROP COLUMN ip;
\ No newline at end of file
diff --git a/dal/schema/000006_init.up.sql b/dal/schema/000006_init.up.sql
new file mode 100644
index 0000000..4dc1487
--- /dev/null
+++ b/dal/schema/000006_init.up.sql
@@ -0,0 +1,6 @@
+ALTER TABLE answer
+ADD COLUMN device_type VARCHAR(50) NOT NULL DEFAULT '',
+ADD COLUMN device VARCHAR(100) NOT NULL DEFAULT '',
+ADD COLUMN os VARCHAR(100) NOT NULL DEFAULT '',
+ADD COLUMN browser VARCHAR(100) NOT NULL DEFAULT '',
+ADD COLUMN ip VARCHAR(50) NOT NULL DEFAULT '';
\ No newline at end of file
diff --git a/dal/schema/000007_init.down.sql b/dal/schema/000007_init.down.sql
new file mode 100644
index 0000000..374c55d
--- /dev/null
+++ b/dal/schema/000007_init.down.sql
@@ -0,0 +1,2 @@
+ALTER TABLE answer
+DROP COLUMN start;
\ No newline at end of file
diff --git a/dal/schema/000007_init.up.sql b/dal/schema/000007_init.up.sql
new file mode 100644
index 0000000..6b41425
--- /dev/null
+++ b/dal/schema/000007_init.up.sql
@@ -0,0 +1,2 @@
+ALTER TABLE answer
+ADD COLUMN start BOOLEAN NOT NULL DEFAULT FALSE;
\ No newline at end of file
diff --git a/dal/schema/000008_init.down.sql b/dal/schema/000008_init.down.sql
new file mode 100644
index 0000000..4f06fcb
--- /dev/null
+++ b/dal/schema/000008_init.down.sql
@@ -0,0 +1,4 @@
+ALTER TABLE answer
+ALTER COLUMN device TYPE VARCHAR(100) NOT NULL DEFAULT '',
+ALTER COLUMN os TYPE VARCHAR(100) NOT NULL DEFAULT '',
+ALTER COLUMN browser TYPE VARCHAR(100) NOT NULL DEFAULT '',
diff --git a/dal/schema/000008_init.up.sql b/dal/schema/000008_init.up.sql
new file mode 100644
index 0000000..fcba321
--- /dev/null
+++ b/dal/schema/000008_init.up.sql
@@ -0,0 +1,4 @@
+ALTER TABLE answer
+ALTER COLUMN device TYPE VARCHAR(1024),
+ALTER COLUMN os TYPE VARCHAR(1024),
+ALTER COLUMN browser TYPE VARCHAR(1024);
diff --git a/dal/schema/000009_init.down.sql b/dal/schema/000009_init.down.sql
new file mode 100644
index 0000000..34fb3c6
--- /dev/null
+++ b/dal/schema/000009_init.down.sql
@@ -0,0 +1,4 @@
+ALTER TABLE answer
+ALTER COLUMN device VARCHAR(100) NOT NULL DEFAULT '',
+ALTER COLUMN os VARCHAR(100) NOT NULL DEFAULT '',
+ALTER COLUMN browser VARCHAR(100) NOT NULL DEFAULT '',
diff --git a/dal/schema/000009_init.up.sql b/dal/schema/000009_init.up.sql
new file mode 100644
index 0000000..d0c81cf
--- /dev/null
+++ b/dal/schema/000009_init.up.sql
@@ -0,0 +1,4 @@
+ALTER TABLE answer
+ALTER COLUMN device TYPE VARCHAR(512),
+ALTER COLUMN os TYPE VARCHAR(512),
+ALTER COLUMN browser TYPE VARCHAR(512);
diff --git a/dal/schema/000010_init.down.sql b/dal/schema/000010_init.down.sql
new file mode 100644
index 0000000..02f12e4
--- /dev/null
+++ b/dal/schema/000010_init.down.sql
@@ -0,0 +1,31 @@
+DROP INDEX IF EXISTS idx_unique_users;
+DROP INDEX IF EXISTS idx_unique_pipeline;
+DROP INDEX IF EXISTS idx_unique_step;
+DROP INDEX IF EXISTS idx_unique_field;
+DROP INDEX IF EXISTS idx_unique_tag;
+DROP INDEX IF EXISTS idx_unique_rules;
+
+DROP TABLE IF EXISTS amoCRMStatuses;
+DROP TABLE IF EXISTS rules;
+DROP TABLE IF EXISTS utms;
+DROP TABLE IF EXISTS tags;
+DROP TABLE IF EXISTS fields;
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entitytype') THEN
+ DROP TYPE EntityType;
+ END IF;
+END $$;
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'fieldtype') THEN
+ DROP TYPE FieldType;
+ END IF;
+END $$;
+
+DROP TABLE IF EXISTS steps;
+DROP TABLE IF EXISTS pipelines;
+DROP TABLE IF EXISTS tokens;
+DROP TABLE IF EXISTS users;
\ No newline at end of file
diff --git a/dal/schema/000010_init.up.sql b/dal/schema/000010_init.up.sql
new file mode 100644
index 0000000..37ee29e
--- /dev/null
+++ b/dal/schema/000010_init.up.sql
@@ -0,0 +1,119 @@
+CREATE TABLE IF NOT EXISTS tokens (
+ AccountID VARCHAR(30) PRIMARY KEY, -- связь с users AccountID неявная посредством join
+ RefreshToken TEXT NOT NULL ,
+ AccessToken TEXT NOT NULL ,
+ AuthCode TEXT NOT NULL , -- код авторизации который получаем при вебхук
+ Expiration TIMESTAMP NOT NULL, -- время истечения токенов
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS users (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(30) NOT NULL DEFAULT '', -- id квизе из токена
+ AmoID INT NOT NULL , -- id в амо
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- имя в амо
+ Email VARCHAR(50) NOT NULL DEFAULT '', -- почта в амо
+ Role INT NOT NULL DEFAULT 0, -- роль в амо
+ "Group" INT NOT NULL DEFAULT 0, -- вложенная структура так как в амо группы хранятся массивом структур
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ Subdomain VARCHAR(50) NOT NULL DEFAULT '',
+ AmoUserID INT NOT NULL , -- id пользователя который подключал интеграцию
+ Country VARCHAR(50) NOT NULL DEFAULT '' -- страна в амо
+);
+
+CREATE TABLE IF NOT EXISTS pipelines (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoID INT NOT NULL , --id воронки в амо
+ AccountID INT NOT NULL , --id аккаунта в амо связь с таблицей users AmoID неявная посредством join
+ Name VARCHAR(512) NOT NULL DEFAULT '', --название воронки в амо
+ IsArchive BOOLEAN NOT NULL DEFAULT FALSE, --флаг архивной воронки в амо
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS steps (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoID INT NOT NULL, --id шага воронки в амо
+ PipelineID INT NOT NULL, --id воронки AmoID pipelines неявная посредством join
+ AccountID INT NOT NULL, --id аккаунта в амо связь с таблицей users AmoID неявная посредством join
+ Name VARCHAR(512) NOT NULL DEFAULT '', --название воронки в амо
+ Color VARCHAR(50) NOT NULL DEFAULT '', --цвет шага в амо*
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'entitytype') THEN
+ CREATE TYPE EntityType AS ENUM ('leads', 'contacts', 'companies', 'customers');
+ END IF;
+END $$;
+
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'fieldtype') THEN
+ CREATE TYPE FieldType AS ENUM ('text', 'numeric', 'checkbox', 'select', 'multiselect', 'date', 'url', 'textarea', 'radiobutton', 'streetaddress', 'smart_address', 'birthday', 'legal_entity', 'date_time', 'price', 'category', 'items', 'tracking_data', 'linked_entity', 'chained_list', 'monetary', 'file', 'payer', 'supplier', 'multitext');
+ END IF;
+END $$;
+
+CREATE TABLE IF NOT EXISTS fields (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoID INT NOT NULL, -- айдишник кастомного поля в амо
+ Code VARCHAR(255) NOT NULL DEFAULT '', -- кодовое слово в амо
+ AccountID INT NOT NULL, -- id аккаунта в амо связь с таблицей users AmoID неявная посредством join
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- название воронки в амо
+ Entity EntityType NOT NULL, -- тип сущности в амо, для которой это кастомное поле
+ Type FieldType NOT NULL, -- тип поля
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS tags (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoID INT NOT NULL, -- айдишник тега в амо
+ AccountID INT NOT NULL, -- id аккаунта в амо связь с таблицей users AmoID неявная посредством join
+ Entity EntityType NOT NULL, -- сущность, к которой принадлежит этот тег
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- название тега в амо
+ Color VARCHAR(50) NOT NULL DEFAULT '', -- цвет тега в амо
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS utms (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoFieldID INT NOT NULL DEFAULT 0, -- id field в амо
+ QuizID INT NOT NULL, -- id опроса
+ AccountID INT NOT NULL, -- id аккаунта в амо AMOID
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- название utm
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS rules (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID INT NOT NULL, -- id аккаунта в амо AMOID
+ QuizID INT NOT NULL, -- id опроса
+ PerformerID INT NOT NULL, -- айдишник ответственного за сделку
+ PipelineID INT NOT NULL, --id воронки AmoID pipelines неявная посредством join
+ StepID INT NOT NULL , -- id этапа steps AmoID join
+ UTMS INTEGER[], -- список UTM для этого опроса id utm
+ FieldsRule JSONB NOT NULL DEFAULT '{}', -- вложенная структура
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX idx_unique_users ON users (amoID);
+CREATE UNIQUE INDEX idx_unique_pipeline ON pipelines (amoID, accountID);
+CREATE UNIQUE INDEX idx_unique_step ON steps (amoID, accountID, PipelineID);
+CREATE UNIQUE INDEX idx_unique_field ON fields (amoID, accountID, entity);
+CREATE UNIQUE INDEX idx_unique_tag ON tags (amoID, accountID, entity);
+CREATE UNIQUE INDEX idx_unique_rules ON rules (accountID, QuizID);
+
+CREATE TABLE IF NOT EXISTS amoCRMStatuses (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID INT NOT NULL, -- id аккаунта в амо
+ DealID INT NOT NULL, -- id сделки в которую добавлялось
+ AnswerID BIGINT NOT NULL REFERENCES answer(id), -- id true result который вызвал действие
+ Status TEXT NOT NULL DEFAULT '', -- запись о ошибке, либо успехе
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
\ No newline at end of file
diff --git a/dal/schema/000011_init.down.sql b/dal/schema/000011_init.down.sql
new file mode 100644
index 0000000..bcaa9ff
--- /dev/null
+++ b/dal/schema/000011_init.down.sql
@@ -0,0 +1,15 @@
+ALTER TABLE answer
+DROP COLUMN utm;
+
+CREATE TABLE IF NOT EXISTS utms (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoFieldID INT NOT NULL DEFAULT 0, -- id field в амо
+ QuizID INT NOT NULL, -- id опроса
+ AccountID INT NOT NULL, -- id аккаунта в амо AMOID
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- название utm
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+ALTER TABLE rules
+ ADD COLUMN UTMS INTEGER[];
\ No newline at end of file
diff --git a/dal/schema/000011_init.up.sql b/dal/schema/000011_init.up.sql
new file mode 100644
index 0000000..f268036
--- /dev/null
+++ b/dal/schema/000011_init.up.sql
@@ -0,0 +1,5 @@
+ALTER TABLE answer
+ADD COLUMN utm jsonb NOT NULL DEFAULT '{}';
+DROP TABLE IF EXISTS utms;
+ALTER TABLE rules
+DROP COLUMN IF EXISTS UTMS;
\ No newline at end of file
diff --git a/dal/schema/000012_init.down.sql b/dal/schema/000012_init.down.sql
new file mode 100644
index 0000000..0f34183
--- /dev/null
+++ b/dal/schema/000012_init.down.sql
@@ -0,0 +1,4 @@
+DROP INDEX IF EXISTS idx_unique_users;
+DROP INDEX IF EXISTS idx_unique_rules;
+CREATE UNIQUE INDEX idx_unique_users ON users (amoID);
+CREATE UNIQUE INDEX idx_unique_rules ON rules (accountID, QuizID);
\ No newline at end of file
diff --git a/dal/schema/000012_init.up.sql b/dal/schema/000012_init.up.sql
new file mode 100644
index 0000000..87913a8
--- /dev/null
+++ b/dal/schema/000012_init.up.sql
@@ -0,0 +1,4 @@
+DROP INDEX IF EXISTS idx_unique_users;
+DROP INDEX IF EXISTS idx_unique_rules;
+CREATE UNIQUE INDEX idx_unique_users ON users (amoID) WHERE Deleted = false;
+CREATE UNIQUE INDEX idx_unique_rules ON rules (accountID, QuizID) WHERE Deleted = false;
\ No newline at end of file
diff --git a/dal/schema/000013_init.down.sql b/dal/schema/000013_init.down.sql
new file mode 100644
index 0000000..26cb247
--- /dev/null
+++ b/dal/schema/000013_init.down.sql
@@ -0,0 +1,4 @@
+ALTER TABLE rules
+DROP COLUMN IF EXISTS TagsToAdd;
+ALTER TABLE users
+DROP COLUMN IF EXISTS DriveURL;
\ No newline at end of file
diff --git a/dal/schema/000013_init.up.sql b/dal/schema/000013_init.up.sql
new file mode 100644
index 0000000..44d4155
--- /dev/null
+++ b/dal/schema/000013_init.up.sql
@@ -0,0 +1,4 @@
+ALTER TABLE rules
+ADD COLUMN TagsToAdd JSONB NOT NULL DEFAULT '{}';
+ALTER TABLE users
+ADD COLUMN DriveURL text NOT NULL DEFAULT '';
diff --git a/dal/schema/000014_init.down.sql b/dal/schema/000014_init.down.sql
new file mode 100644
index 0000000..5ddb16f
--- /dev/null
+++ b/dal/schema/000014_init.down.sql
@@ -0,0 +1,20 @@
+DROP TABLE IF EXISTS usersAmo;
+DROP INDEX IF EXISTS idx_unique_accountsAmo;
+DROP TABLE IF EXISTS accountsAmo;
+
+CREATE TABLE IF NOT EXISTS users (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(30) NOT NULL DEFAULT '', -- id квизе из токена
+ AmoID INT NOT NULL , -- id в амо
+ Name VARCHAR(512) NOT NULL DEFAULT '', -- имя в амо
+ Email VARCHAR(50) NOT NULL DEFAULT '', -- почта в амо
+ Role INT NOT NULL DEFAULT 0, -- роль в амо
+ "Group" INT NOT NULL DEFAULT 0, -- вложенная структура так как в амо группы хранятся массивом структур
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ Subdomain VARCHAR(50) NOT NULL DEFAULT '',
+ AmoUserID INT NOT NULL , -- id пользователя который подключал интеграцию
+ Country VARCHAR(50) NOT NULL DEFAULT '' -- страна в амо
+);
+
+CREATE UNIQUE INDEX idx_unique_users ON users (amoID) WHERE Deleted = false;
\ No newline at end of file
diff --git a/dal/schema/000014_init.up.sql b/dal/schema/000014_init.up.sql
new file mode 100644
index 0000000..9542bbb
--- /dev/null
+++ b/dal/schema/000014_init.up.sql
@@ -0,0 +1,28 @@
+DROP TABLE IF EXISTS users;
+DROP INDEX IF EXISTS idx_unique_users;
+
+CREATE TABLE IF NOT EXISTS accountsAmo (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(30) NOT NULL DEFAULT '', -- ID аккаунта у нас
+ AmoID INT NOT NULL, -- ID "компании" в амо
+ Name VARCHAR(512) NOT NULL DEFAULT '',
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Subdomain VARCHAR(50) NOT NULL DEFAULT '', -- поддомен - пример https://penadigitaltech.amocrm.ru
+ Country VARCHAR(50) NOT NULL DEFAULT '',
+ DriveURL VARCHAR(255) NOT NULL DEFAULT '' -- URL объктного хранилища
+);
+
+CREATE UNIQUE INDEX idx_unique_accountsAmo ON accountsAmo (amoID) WHERE Deleted = false;
+
+CREATE TABLE IF NOT EXISTS usersAmo (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AmoID INT NOT NULL, -- ID компании в амо (внешний ключ)
+ AmoUserID INT NOT NULL, -- ID пользователя в амо
+ Name VARCHAR(512) NOT NULL DEFAULT '',
+ Email VARCHAR(50) NOT NULL DEFAULT '',
+ Role INT NOT NULL DEFAULT 0,
+ "Group" INT NOT NULL DEFAULT 0,
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
\ No newline at end of file
diff --git a/dal/schema/000015_init.down.sql b/dal/schema/000015_init.down.sql
new file mode 100644
index 0000000..e69de29
diff --git a/dal/schema/000015_init.up.sql b/dal/schema/000015_init.up.sql
new file mode 100644
index 0000000..3985517
--- /dev/null
+++ b/dal/schema/000015_init.up.sql
@@ -0,0 +1,19 @@
+UPDATE answer
+SET content =
+ CASE
+ WHEN content ~ '
|' THEN
+ regexp_replace(content, '<\/?tr[^>]*>|<\/?td[^>]*>', '', 'g')
+ WHEN content ~ '[^<]+' THEN
+ regexp_replace(content, '([^<]+)', '\1', 'g')
+ WHEN content ~ ' ]*src="([^"]*)"[^>]*' THEN
+ regexp_replace(content, ' ]*src="\s*"[^>]*', '', 'g')
+ ELSE content
+ END;
+
+UPDATE answer
+SET content =
+ CASE
+ WHEN content ~ ' ]*src=["'']?([^"''>]+)["'']?[^>]*>)', '\1\3', 'g')
+ ELSE content
+ END;
diff --git a/dal/schema/000016_init.down.sql b/dal/schema/000016_init.down.sql
new file mode 100644
index 0000000..28b2333
--- /dev/null
+++ b/dal/schema/000016_init.down.sql
@@ -0,0 +1 @@
+DROP TABLE IF EXISTS amoContact;
\ No newline at end of file
diff --git a/dal/schema/000016_init.up.sql b/dal/schema/000016_init.up.sql
new file mode 100644
index 0000000..a4866d0
--- /dev/null
+++ b/dal/schema/000016_init.up.sql
@@ -0,0 +1,6 @@
+CREATE TABLE IF NOT EXISTS amoContact (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID INT NOT NULL, -- ID "компании" в амо
+ AmoID INT NOT NULL, -- ID контакта в амо
+ Field text NOT NULL DEFAULT '' -- значение чего то email? phone? etc
+)
\ No newline at end of file
diff --git a/dal/schema/000017_init.down.sql b/dal/schema/000017_init.down.sql
new file mode 100644
index 0000000..c2969e1
--- /dev/null
+++ b/dal/schema/000017_init.down.sql
@@ -0,0 +1,11 @@
+ALTER table question DROP column session;
+ALTER TABLE account ADD column email varchar(50) NOT NULL default '';
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'leadtargettype') THEN
+ DROP TYPE LeadTargetType;
+ END IF;
+ END $$;
+
+DROP TABLE IF EXISTS leadtarget;
diff --git a/dal/schema/000017_init.up.sql b/dal/schema/000017_init.up.sql
new file mode 100644
index 0000000..0f5aa5e
--- /dev/null
+++ b/dal/schema/000017_init.up.sql
@@ -0,0 +1,31 @@
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_enum
+ WHERE enumlabel = 'ai' AND enumtypid = 'quiz_status'::regtype
+ ) THEN
+ALTER TYPE quiz_status ADD VALUE 'ai';
+END IF;
+END $$;
+
+ALTER TABLE question ADD column session varchar(20) NOT NULL DEFAULT '';
+
+AlTER TABLE account DROP column email;
+
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'leadtargettype') THEN
+ CREATE TYPE LeadTargetType AS ENUM ('mail', 'telegram', 'whatsapp');
+ END IF;
+ END $$;
+
+CREATE TABLE IF NOT EXISTS leadtarget(
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID varchar(30) NOT NULL,
+ Type LeadTargetType NOT NULL,
+ QuizID integer NOT NULL DEFAULT 0,
+ Target text NOT NULL,
+ InviteLink text NOT NULL DEFAULT '',
+ Deleted boolean NOT NULL DEFAULT false,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
diff --git a/dal/schema/000018_init.down.sql b/dal/schema/000018_init.down.sql
new file mode 100644
index 0000000..6186e3f
--- /dev/null
+++ b/dal/schema/000018_init.down.sql
@@ -0,0 +1,12 @@
+DROP TABLE IF EXISTS gigachatAudience;
+
+DROP TABLE IF EXISTS tgAccounts;
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'tgAccountStatus') THEN
+ DROP TYPE TgAccountStatus;
+ END IF;
+ END $$;
+
+DROP INDEX IF EXISTS idx_apiid_apihash;
diff --git a/dal/schema/000018_init.up.sql b/dal/schema/000018_init.up.sql
new file mode 100644
index 0000000..d6cf7fa
--- /dev/null
+++ b/dal/schema/000018_init.up.sql
@@ -0,0 +1,29 @@
+CREATE TABLE IF NOT EXISTS gigachatAudience (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ QuizID BIGINT NOT NULL,
+ Sex BOOLEAN NOT NULL,
+ Age VARCHAR(5) NOT NULL,
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT fk_quiz FOREIGN KEY (QuizID) REFERENCES quiz(ID)
+);
+
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'tgAccountStatus') THEN
+ CREATE TYPE TgAccountStatus AS ENUM ('active', 'inactive', 'ban');
+ END IF;
+ END $$;
+
+CREATE TABLE IF NOT EXISTS tgAccounts (
+ id bigserial primary key ,
+ ApiID integer not null,
+ ApiHash text not null ,
+ PhoneNumber text not null ,
+ Password text not null ,
+ Status TgAccountStatus not null,
+ Deleted bool not null default false,
+ CreatedAt timestamp not null default current_timestamp
+);
+
+CREATE UNIQUE INDEX idx_apiid_apihash ON tgAccounts (ApiID, ApiHash) WHERE Deleted = false;
diff --git a/dal/schema/000019_init.down.sql b/dal/schema/000019_init.down.sql
new file mode 100644
index 0000000..e76faa2
--- /dev/null
+++ b/dal/schema/000019_init.down.sql
@@ -0,0 +1,11 @@
+ALTER TABLE account
+ALTER COLUMN user_id DROP NOT NULL,
+ALTER COLUMN created_at DROP NOT NULL,
+ALTER COLUMN deleted DROP NOT NULL;
+
+ALTER TABLE privileges
+ALTER COLUMN privilegeID DROP NOT NULL,
+ALTER COLUMN account_id DROP NOT NULL,
+ALTER COLUMN privilege_name DROP NOT NULL,
+ALTER COLUMN amount DROP NOT NULL,
+ALTER COLUMN created_at DROP NOT NULL;
\ No newline at end of file
diff --git a/dal/schema/000019_init.up.sql b/dal/schema/000019_init.up.sql
new file mode 100644
index 0000000..ac84b67
--- /dev/null
+++ b/dal/schema/000019_init.up.sql
@@ -0,0 +1,11 @@
+ALTER TABLE account
+ALTER COLUMN user_id SET NOT NULL,
+ALTER COLUMN created_at SET NOT NULL,
+ALTER COLUMN deleted SET NOT NULL;
+
+ALTER TABLE privileges
+ALTER COLUMN privilegeID SET NOT NULL,
+ALTER COLUMN account_id SET NOT NULL,
+ALTER COLUMN privilege_name SET NOT NULL,
+ALTER COLUMN amount SET NOT NULL,
+ALTER COLUMN created_at SET NOT NULL;
\ No newline at end of file
diff --git a/dal/schema/000020_init.down.sql b/dal/schema/000020_init.down.sql
new file mode 100644
index 0000000..b2ec5da
--- /dev/null
+++ b/dal/schema/000020_init.down.sql
@@ -0,0 +1,19 @@
+DROP TABLE IF EXISTS telegram_integration;
+DROP TABLE IF EXISTS respondent_state;
+DROP TABLE IF EXISTS telegram_integration_instance;
+drop table if exists telegram_user_quiz_result;
+DROP INDEX results_for_quiz;
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'bot_status') THEN
+ DROP TYPE bot_status;
+ END IF;
+END $$;
+
+DO $$
+ BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_result_tg_status') THEN
+ DROP TYPE user_result_tg_status;
+ END IF;
+END $$;
diff --git a/dal/schema/000020_init.up.sql b/dal/schema/000020_init.up.sql
new file mode 100644
index 0000000..aee2174
--- /dev/null
+++ b/dal/schema/000020_init.up.sql
@@ -0,0 +1,57 @@
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'bot_status') THEN
+ CREATE TYPE bot_status AS ENUM ('active','stopped', 'banned');
+ END IF;
+ END $$;
+
+DO $$
+ BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_result_tg_status') THEN
+ CREATE TYPE user_result_tg_status AS ENUM ('in_progress','completed');
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS results_for_quiz ON answer(quiz_id, result);
+
+CREATE TABLE IF NOT EXISTS telegram_integration(
+ id bigserial unique not null primary key,
+ accountid varchar(30) NOT NULL, -- связь с аккаунтом квиза
+ quizID integer NOT NULL , -- айдишник квиза, с которым связываем интеграцию
+ bot_token text NOT NULL, -- токен бота
+ bot_name text NOT NULL DEFAULT '', -- имя бота
+ repeatable boolean DEFAULT false, -- флаг возможности повторить опрос
+ deleted boolean NOT NULL default false, -- флаг удаленного
+ status bot_status NOT NULL DEFAULT 'active',
+ instance_id integer NOT NULL default 0
+);
+
+CREATE TABLE IF NOT EXISTS respondent_state(
+ id bigserial unique not null primary key,
+ telegram_id integer NOT NULL, -- айдишник пользователя в телеграме, скорее всего чат id
+ quizID integer NOT NULL, -- айдишник квиза, который проходится этим респондентом
+ state bigint NOT NULL,
+ lang text NOT NULL default '', -- язык опрашиваемого
+ contact text NOT NULL default '', -- ник пользователя в телеге, если доступен
+ finish boolean not null default false, -- статус, пройден опрос или нет
+ session varchar(20) -- сессия пользователя
+);
+
+CREATE TABLE IF NOT EXISTS telegram_integration_instance(
+ id bigserial unique not null primary key,
+ checkout bigint not null , -- время "подтверждения активности". в идеале должно быть актуальным, но отличаться от всех остальных минимум на 10 секунд. т.е. если у нас есть бот с временем в 10:10:10 и бот с временем в 10:10:31, а сейчас 10:10:32, то валидные слоты это 10:10:21 и 10:10:41. но как это сделать я не знаю. как вариант, можно просто взять бот с максимальным временем и сделать ему +10. и наверное это правильныйй вариант
+ limited integer not null, --константа с лимитом ботов
+ amount integer not null -- актуальное количество ботов
+);
+
+CREATE TABLE IF NOT EXISTS telegram_user_quiz_result(
+ id BIGSERIAL PRIMARY KEY,
+ bot_id BIGINT NOT NULL,
+ quiz_id BIGINT NOT NULL,
+ current_field TEXT,
+ user_answer TEXT DEFAULT '',
+ state user_result_tg_status not null default 'in_progress',
+ session varchar(20) NOT NULL ,
+ question_id bigint NOT NULL, -- id вопроса результата
+ iter integer not null
+)
diff --git a/dal/schema/000021_init.down.sql b/dal/schema/000021_init.down.sql
new file mode 100644
index 0000000..1324e47
--- /dev/null
+++ b/dal/schema/000021_init.down.sql
@@ -0,0 +1,35 @@
+DROP TABLE IF EXISTS BitrixTokens;
+DROP TABLE IF EXISTS StepBitrix;
+
+DROP TABLE IF EXISTS PipelineBitrix;
+
+DROP TABLE IF EXISTS BitrixAccounts;
+
+DROP TABLE IF EXISTS BitrixAccountUsers;
+
+DROP TABLE IF EXISTS BitrixFields;
+
+DROP TABLE IF EXISTS BitrixRule;
+
+DROP TABLE IF EXISTS bitrixCRMStatuses;
+
+DROP TABLE IF EXISTS bitrixContact;
+
+DO $$
+BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'CustomFieldsType') THEN
+DROP TYPE CustomFieldsType;
+END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'FieldsType') THEN
+DROP TYPE FieldsType;
+END IF;
+END $$;
+
+DROP INDEX IF EXISTS idx_unique_pipeline_bitrix;
+DROP INDEX IF EXISTS idx_unique_step_bitrix;
+DROP INDEX IF EXISTS idx_unique_field_bitrix;
+ALTER TABLE answer DROP COLUMN IF EXISTS version;
diff --git a/dal/schema/000021_init.up.sql b/dal/schema/000021_init.up.sql
new file mode 100644
index 0000000..d28c75e
--- /dev/null
+++ b/dal/schema/000021_init.up.sql
@@ -0,0 +1,149 @@
+CREATE TABLE IF NOT EXISTS BitrixTokens (
+ AccountID VARCHAR(30) PRIMARY KEY, -- связь с users AccountID неявная посредством join
+ RefreshToken TEXT NOT NULL ,
+ AccessToken TEXT NOT NULL ,
+ AuthCode TEXT NOT NULL , -- код авторизации который получаем при вебхук
+ Expiration TIMESTAMP NOT NULL, -- время истечения токенов
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'FieldsType') THEN
+CREATE TYPE FieldsType AS ENUM (
+ 'CRM_LEAD',
+ 'CRM_COMPANY',
+ 'CRM_CONTACT',
+ 'CRM_DEAL',
+ 'CRM_INVOICE',
+ 'CRM_SMART_INVOICE',
+ 'CRM_QUOTE',
+ 'CRM_REQUISITE'
+);
+END IF;
+END $$;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'CustomFieldsType') THEN
+CREATE TYPE CustomFieldsType AS ENUM (
+ 'string',
+ 'integer',
+ 'double',
+ 'boolean',
+ 'datetime',
+ 'enumeration',
+ 'iblock_section',
+ 'iblock_element',
+ 'employee',
+ 'crm_status',
+ 'crm',
+ 'address',
+ 'money',
+ 'url',
+ 'file',
+ 'crm_pena_tag'
+ );
+END IF;
+END $$;
+
+
+CREATE TABLE IF NOT EXISTS BitrixFields (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ BitrixID VARCHAR(255) NOT NULL, -- Айдишник field в битриксе
+ AccountID VARCHAR(255) NOT NULL,-- ID портала битрикса
+ EntityID FieldsType NOT NULL,-- тип поля
+ FieldName VARCHAR(255) NOT NULL,-- имя поля
+ EditFromLabel VARCHAR(255) NOT NULL,-- заголовок вопроса
+ FieldType CustomFieldsType NOT NULL, -- тип поля
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS BitrixAccountUsers (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ BitrixIDUserID VARCHAR(255) NOT NULL, -- ID пользователя в битриксе
+ Name VARCHAR(255) NOT NULL default '', -- Имя
+ LastName VARCHAR(255) NOT NULL default '',-- Фамилия
+ SecondName VARCHAR(255) NOT NULL default '', -- Отчество
+ Title VARCHAR(255) NOT NULL default '',
+ Email VARCHAR(255) NOT NULL default '', -- Почта
+ UFDepartment INT[], -- Массив департаментов
+ WorkPosition VARCHAR(255) NOT NULL default '', -- Должность
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS BitrixAccounts (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(30) NOT NULL, -- ID аккаунта у нас
+ BitrixID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ Subdomain VARCHAR(50) NOT NULL -- поддомен
+);
+
+CREATE TABLE IF NOT EXISTS PipelineBitrix (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ BitrixID INT NOT NULL, -- Айдишник воронки в битриксе
+ AccountID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ Name VARCHAR(255) NOT NULL, -- Название воронки
+ EntityTypeId INT NOT NULL, -- Тип по номерам
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ CONSTRAINT validEntityType CHECK (EntityTypeId IN (1, 2, 3, 4, 5, 7, 8, 31))
+);
+
+CREATE TABLE IF NOT EXISTS StepBitrix (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ BitrixID VARCHAR(255) NOT NULL, -- Айдишник состояния в битриксе
+ EntityID VARCHAR(255) NOT NULL, -- Тип сущности
+ StatusID VARCHAR(255) NOT NULL, -- Текстовый формат ID статуса
+ Name VARCHAR(255) NOT NULL default '', -- Название
+ NameInit VARCHAR(255) NOT NULL default '', -- возможно это изначальное название
+ Color VARCHAR(50) NOT NULL default '', -- Цвет
+ PipelineID INT NOT NULL, -- ID воронки
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS BitrixRule (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ QuizID INT NOT NULL, -- ID квиза на которое вешается правило
+ PerformerID VARCHAR(255) NOT NULL, -- ID пользователя в битриксе который ответсвенный
+ PipelineID INT NOT NULL, -- ID воронки
+ TypeID VARCHAR(255) NOT NULL, -- шаг сделки только с "ENTITY_ID":"DEAL_TYPE","STATUS_ID":"SALE"
+ StageID VARCHAR(255) NOT NULL, -- стадия сделки, шаг "ENTITY_ID":"DEAL_STAGE","STATUS_ID":"NEW"
+ SourceID VARCHAR(255) NOT NULL, -- тип источника, шаг "ENTITY_ID":"SOURCE","STATUS_ID":"CALL"
+ StatusID VARCHAR(255) NOT NULL, -- тип источника, шаг "ENTITY_ID":"STATUS","STATUS_ID":"IN_PROCESS"
+ FieldsRule JSONB NOT NULL DEFAULT '{}', -- вложенная структура
+ TagsToAdd JSONB NOT NULL DEFAULT '{}', -- структура тегов которые надо дбавлять
+ Deleted BOOLEAN NOT NULL DEFAULT FALSE,
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ LeadFlag BOOLEAN NOT NULL DEFAULT FALSE -- если true то делаем лид а не сделку
+);
+
+CREATE TABLE IF NOT EXISTS bitrixCRMStatuses (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(255) NOT NULL, -- ID портала битрикса
+ DealID INT NOT NULL, -- id сделки или лида в которую добавлялось
+ AnswerID BIGINT NOT NULL REFERENCES answer(id), -- id true result который вызвал действие
+ Status TEXT NOT NULL DEFAULT '', -- запись о ошибке, либо успехе
+ CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE TABLE IF NOT EXISTS bitrixContact (
+ ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY,
+ AccountID VARCHAR(255) NOT NULL, -- ID "компании" в амо
+ BitrixID INT NOT NULL, -- ID контакта в амо
+ Field text NOT NULL DEFAULT '' -- значение чего то email? phone? etc
+);
+
+CREATE UNIQUE INDEX idx_unique_pipeline_bitrix ON PipelineBitrix (BitrixID, AccountID) WHERE Deleted = false;
+CREATE UNIQUE INDEX idx_unique_step_bitrix ON StepBitrix (BitrixID, AccountID) WHERE Deleted = false;
+CREATE UNIQUE INDEX idx_unique_field_bitrix ON BitrixFields (BitrixID, AccountID, EntityID);
+ALTER TABLE answer ADD COLUMN version integer NOT NULL default 0;
diff --git a/dal/schema/000022_init.down.sql b/dal/schema/000022_init.down.sql
new file mode 100644
index 0000000..80de4b6
--- /dev/null
+++ b/dal/schema/000022_init.down.sql
@@ -0,0 +1 @@
+ALTER TABLE question DROP COLUMN IF EXISTS auditory;
\ No newline at end of file
diff --git a/dal/schema/000022_init.up.sql b/dal/schema/000022_init.up.sql
new file mode 100644
index 0000000..dfa9cb5
--- /dev/null
+++ b/dal/schema/000022_init.up.sql
@@ -0,0 +1 @@
+ALTER TABLE question ADD COLUMN auditory BIGINT NOT NULL DEFAULT 0;
diff --git a/dal/schema/000023_init.down.sql b/dal/schema/000023_init.down.sql
new file mode 100644
index 0000000..e69de29
diff --git a/dal/schema/000023_init.up.sql b/dal/schema/000023_init.up.sql
new file mode 100644
index 0000000..94c6a96
--- /dev/null
+++ b/dal/schema/000023_init.up.sql
@@ -0,0 +1,14 @@
+BEGIN;
+ALTER TABLE gigachatAudience RENAME COLUMN Sex TO Sex_old;
+ALTER TABLE gigachatAudience ADD COLUMN Sex INTEGER;
+UPDATE gigachatAudience
+SET Sex = CASE
+ WHEN Sex_old = FALSE THEN 0
+ WHEN Sex_old = TRUE THEN 1
+ ELSE 2
+END;
+ALTER TABLE gigachatAudience DROP COLUMN Sex_old;
+ALTER TABLE gigachatAudience ALTER COLUMN Sex SET NOT NULL;
+ALTER TABLE gigachatAudience
+ ADD CONSTRAINT check_sex_valid_values CHECK (Sex IN (0, 1, 2));
+COMMIT;
diff --git a/dal/schema/000024_init.down.sql b/dal/schema/000024_init.down.sql
new file mode 100644
index 0000000..249ee63
--- /dev/null
+++ b/dal/schema/000024_init.down.sql
@@ -0,0 +1 @@
+ALTER TABLE quiz DROP COLUMN IF EXISTS gigachat;
\ No newline at end of file
diff --git a/dal/schema/000024_init.up.sql b/dal/schema/000024_init.up.sql
new file mode 100644
index 0000000..21deb16
--- /dev/null
+++ b/dal/schema/000024_init.up.sql
@@ -0,0 +1 @@
+ALTER TABLE quiz ADD COLUMN gigachat boolean NOT NULL DEFAULT false;
\ No newline at end of file
diff --git a/dal/schema/000025_init.down.sql b/dal/schema/000025_init.down.sql
new file mode 100644
index 0000000..b1b24af
--- /dev/null
+++ b/dal/schema/000025_init.down.sql
@@ -0,0 +1 @@
+drop table if exists quiz_utm;
\ No newline at end of file
diff --git a/dal/schema/000025_init.up.sql b/dal/schema/000025_init.up.sql
new file mode 100644
index 0000000..a36273f
--- /dev/null
+++ b/dal/schema/000025_init.up.sql
@@ -0,0 +1,7 @@
+CREATE TABLE IF NOT EXISTS quiz_utm (
+ id bigserial UNIQUE NOT NULL PRIMARY KEY,
+ quizID bigint not null,
+ utm text not null default '',
+ deleted boolean not null default false,
+ created_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP
+);
\ No newline at end of file
diff --git a/dal/schema/000026_init.down.sql b/dal/schema/000026_init.down.sql
new file mode 100644
index 0000000..a4c0cfa
--- /dev/null
+++ b/dal/schema/000026_init.down.sql
@@ -0,0 +1,2 @@
+DROP INDEX IF EXISTS idx_quiz_privilege_unique;
+drop table if exists quiz_privilege_usage;
\ No newline at end of file
diff --git a/dal/schema/000026_init.up.sql b/dal/schema/000026_init.up.sql
new file mode 100644
index 0000000..cc91ba5
--- /dev/null
+++ b/dal/schema/000026_init.up.sql
@@ -0,0 +1,10 @@
+CREATE TABLE quiz_privilege_usage (
+ id bigserial UNIQUE NOT NULL PRIMARY KEY,
+ quiz_id BIGINT NOT NULL REFERENCES quiz(id) ON DELETE CASCADE,
+ privilege_id VARCHAR(50) NOT NULL,
+ used_count INT NOT NULL DEFAULT 0,
+ created_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP not null DEFAULT CURRENT_TIMESTAMP
+);
+
+CREATE UNIQUE INDEX idx_quiz_privilege_unique ON quiz_privilege_usage (quiz_id, privilege_id);
\ No newline at end of file
diff --git a/dal/sqlcgen/db.go b/dal/sqlcgen/db.go
new file mode 100644
index 0000000..3bd707e
--- /dev/null
+++ b/dal/sqlcgen/db.go
@@ -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,
+ }
+}
diff --git a/dal/sqlcgen/models.go b/dal/sqlcgen/models.go
new file mode 100644
index 0000000..c266b2c
--- /dev/null
+++ b/dal/sqlcgen/models.go
@@ -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"`
+}
diff --git a/dal/sqlcgen/queries.sql.go b/dal/sqlcgen/queries.sql.go
new file mode 100644
index 0000000..cb87201
--- /dev/null
+++ b/dal/sqlcgen/queries.sql.go
@@ -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
+}
diff --git a/go.mod b/go.mod
index c92debb..f0f45a0 100644
--- a/go.mod
+++ b/go.mod
@@ -5,50 +5,27 @@ go 1.23.2
toolchain go1.23.4
require (
- gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517
- 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/ClickHouse/clickhouse-go v1.5.4
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/skeris/appInit v1.0.2
go.uber.org/zap v1.27.0
)
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/cespare/xxhash/v2 v2.2.0 // 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/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-isatty v0.0.20 // 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/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/fasthttp v1.51.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
- 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/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
)
diff --git a/go.sum b/go.sum
index 108168a..936e917 100644
--- a/go.sum
+++ b/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/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
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/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/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/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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
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/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
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/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.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.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/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.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
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/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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
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=
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.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/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-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.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.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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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-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-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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 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/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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
diff --git a/healthchecks/healthchecks.go b/healthchecks/healthchecks.go
new file mode 100644
index 0000000..ad0a945
--- /dev/null
+++ b/healthchecks/healthchecks.go
@@ -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)
+ }
+}
diff --git a/middleware/middleware.go b/middleware/middleware.go
new file mode 100644
index 0000000..6903804
--- /dev/null
+++ b/middleware/middleware.go
@@ -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()
+ }
+}
diff --git a/model/model.go b/model/model.go
new file mode 100644
index 0000000..b8d5b6e
--- /dev/null
+++ b/model/model.go
@@ -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"` // имя отправителя
+}
diff --git a/service/service.go b/service/service.go
index ac2c510..7585296 100644
--- a/service/service.go
+++ b/service/service.go
@@ -4,9 +4,9 @@ import (
"encoding/json"
"fmt"
"gitea.pena/SQuiz/answerer/clients"
- quizdal "gitea.pena/SQuiz/common/dal"
- "gitea.pena/SQuiz/common/middleware"
- "gitea.pena/SQuiz/common/model"
+ quizdal "gitea.pena/SQuiz/answerer/dal"
+ "gitea.pena/SQuiz/answerer/middleware"
+ "gitea.pena/SQuiz/answerer/model"
"github.com/gofiber/fiber/v2"
"strconv"
"strings"
@@ -147,7 +147,7 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
fp = cfp
}
- quiz, err := s.dal.QuizRepo.GetQuizByQid(c.Context(), quizID[0])
+ quiz, err := s.dal.GetQuizByQid(c.Context(), quizID[0])
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("can not get quiz")
}
@@ -164,7 +164,7 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
final = parsedFinal
}
- question, err := s.dal.QuestionRepo.GetQuestionListByIDs(c.Context(), []int32{int32(ans.QuestionId)})
+ question, err := s.dal.GetQuestionListByIDs(c.Context(), []int32{int32(ans.QuestionId)})
if err != nil {
return c.Status(fiber.StatusInternalServerError).SendString("can not get questions")
}
@@ -178,7 +178,7 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
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{
+ _, err = s.dal.CreateQuestion(c.Context(), &model.Question{
QuizId: quiz.Id,
Title: truncateUTF8(questionText, 512),
Type: model.TypeText,
@@ -234,7 +234,7 @@ func (s *Service) PutAnswersOnePiece(c *fiber.Ctx) error {
return c.Status(fiber.StatusInternalServerError).SendString("can not unmarshal quiz config")
}
- stored, ers := s.dal.AnswerRepo.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id)
+ stored, ers := s.dal.CreateAnswers(c.Context(), answers, cs, fp, quiz.Id)
if len(ers) != 0 {
for _, err := range ers {
if strings.Contains(err.Error(), "duplicate key value") {
diff --git a/sqlc.yaml b/sqlc.yaml
new file mode 100644
index 0000000..103e173
--- /dev/null
+++ b/sqlc.yaml
@@ -0,0 +1,64 @@
+version: "1"
+packages:
+ - name: "sqlcgen"
+ path: "./dal/sqlcgen"
+ queries: "./dal/db_query/queries.sql"
+ schema:
+ - "./dal/schema/000001_init.up.sql"
+ - "./dal/schema/000001_init.down.sql"
+ - "./dal/schema/000002_init.up.sql"
+ - "./dal/schema/000002_init.down.sql"
+ - "./dal/schema/000003_init.up.sql"
+ - "./dal/schema/000003_init.down.sql"
+ - "./dal/schema/000004_init.up.sql"
+ - "./dal/schema/000004_init.down.sql"
+ - "./dal/schema/000005_init.up.sql"
+ - "./dal/schema/000005_init.down.sql"
+ - "./dal/schema/000006_init.up.sql"
+ - "./dal/schema/000006_init.down.sql"
+ - "./dal/schema/000007_init.up.sql"
+ - "./dal/schema/000007_init.down.sql"
+ - "./dal/schema/000008_init.up.sql"
+ - "./dal/schema/000008_init.down.sql"
+ - "./dal/schema/000009_init.up.sql"
+ - "./dal/schema/000009_init.down.sql"
+ - "./dal/schema/000010_init.up.sql"
+ - "./dal/schema/000010_init.down.sql"
+ - "./dal/schema/000011_init.up.sql"
+ - "./dal/schema/000011_init.down.sql"
+ - "./dal/schema/000012_init.up.sql"
+ - "./dal/schema/000012_init.down.sql"
+ - "./dal/schema/000013_init.up.sql"
+ - "./dal/schema/000013_init.down.sql"
+ - "./dal/schema/000014_init.up.sql"
+ - "./dal/schema/000014_init.down.sql"
+ - "./dal/schema/000016_init.up.sql"
+ - "./dal/schema/000016_init.down.sql"
+ - "./dal/schema/000017_init.up.sql"
+ - "./dal/schema/000017_init.down.sql"
+ - "./dal/schema/000018_init.up.sql"
+ - "./dal/schema/000018_init.down.sql"
+ - "./dal/schema/000019_init.up.sql"
+ - "./dal/schema/000019_init.down.sql"
+ - "./dal/schema/000020_init.up.sql"
+ - "./dal/schema/000020_init.down.sql"
+ - "./dal/schema/000021_init.up.sql"
+ - "./dal/schema/000021_init.down.sql"
+ - "./dal/schema/000022_init.up.sql"
+ - "./dal/schema/000022_init.down.sql"
+ - "./dal/schema/000023_init.up.sql"
+ - "./dal/schema/000023_init.down.sql"
+ - "./dal/schema/000024_init.up.sql"
+ - "./dal/schema/000024_init.down.sql"
+ - "./dal/schema/000025_init.up.sql"
+ - "./dal/schema/000025_init.down.sql"
+ - "./dal/schema/000026_init.up.sql"
+ - "./dal/schema/000026_init.down.sql"
+ engine: "postgresql"
+ emit_json_tags: true
+ emit_db_tags: true
+ emit_prepared_queries: false
+ emit_interface: false
+ emit_exact_table_names: false
+ emit_empty_slices: false
+ sql_package: "database/sql"
|