copy from squiz common

This commit is contained in:
pasha1coil 2025-07-09 15:10:11 +03:00
parent 2dd714b369
commit 18fe7fd62a
65 changed files with 2030 additions and 124 deletions

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

255
dal/dal.go Normal file

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

39
dal/db_query/queries.sql Normal file

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

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

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

@ -0,0 +1 @@
ALTER TABLE answer DROP COLUMN IF EXISTS result;

@ -0,0 +1 @@
ALTER TABLE answer ADD COLUMN result BOOLEAN DEFAULT FALSE;

@ -0,0 +1 @@
ALTER TABLE quiz DROP COLUMN IF EXISTS sessions_count;

@ -0,0 +1 @@
ALTER TABLE quiz ADD COLUMN sessions_count integer;

@ -0,0 +1,2 @@
ALTER TABLE quiz DROP COLUMN IF EXISTS new;
ALTER TABLE quiz DROP COLUMN IF EXISTS deleted;

@ -0,0 +1,2 @@
ALTER TABLE answer ADD COLUMN new BOOLEAN DEFAULT TRUE;
ALTER TABLE answer ADD COLUMN deleted BOOLEAN DEFAULT FALSE;

@ -0,0 +1,2 @@
ALTER TABLE answer DROP COLUMN IF EXISTS email;
DROP INDEX IF EXISTS answer_email_unique_idx;

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

@ -0,0 +1,6 @@
ALTER TABLE answer
DROP COLUMN device_type,
DROP COLUMN device,
DROP COLUMN os,
DROP COLUMN browser,
DROP COLUMN ip;

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

@ -0,0 +1,2 @@
ALTER TABLE answer
DROP COLUMN start;

@ -0,0 +1,2 @@
ALTER TABLE answer
ADD COLUMN start BOOLEAN NOT NULL DEFAULT FALSE;

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

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

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

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

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

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

@ -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[];

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

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

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

@ -0,0 +1,4 @@
ALTER TABLE rules
DROP COLUMN IF EXISTS TagsToAdd;
ALTER TABLE users
DROP COLUMN IF EXISTS DriveURL;

@ -0,0 +1,4 @@
ALTER TABLE rules
ADD COLUMN TagsToAdd JSONB NOT NULL DEFAULT '{}';
ALTER TABLE users
ADD COLUMN DriveURL text NOT NULL DEFAULT '';

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

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

@ -0,0 +1,19 @@
UPDATE answer
SET content =
CASE
WHEN content ~ '<tr>|<td>' THEN
regexp_replace(content, '<\/?tr[^>]*>|<\/?td[^>]*>', '', 'g')
WHEN content ~ '<a download>[^<]+</a>' THEN
regexp_replace(content, '<a download>([^<]+)</a>', '\1', 'g')
WHEN content ~ '<img[^>]*src="([^"]*)"[^>]*' THEN
regexp_replace(content, '<img[^>]*src="\s*"[^>]*', '', 'g')
ELSE content
END;
UPDATE answer
SET content =
CASE
WHEN content ~ '<img' THEN
regexp_replace(content, '(.*?)(<img[^>]*src=["'']?([^"''>]+)["'']?[^>]*>)', '\1\3', 'g')
ELSE content
END;

@ -0,0 +1 @@
DROP TABLE IF EXISTS amoContact;

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1 @@
ALTER TABLE question DROP COLUMN IF EXISTS auditory;

@ -0,0 +1 @@
ALTER TABLE question ADD COLUMN auditory BIGINT NOT NULL DEFAULT 0;

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

@ -0,0 +1 @@
ALTER TABLE quiz DROP COLUMN IF EXISTS gigachat;

@ -0,0 +1 @@
ALTER TABLE quiz ADD COLUMN gigachat boolean NOT NULL DEFAULT false;

@ -0,0 +1 @@
drop table if exists quiz_utm;

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

@ -0,0 +1,2 @@
DROP INDEX IF EXISTS idx_quiz_privilege_unique;
drop table if exists quiz_privilege_usage;

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

31
dal/sqlcgen/db.go Normal file

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

400
dal/sqlcgen/models.go Normal file

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

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

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

31
go.mod

@ -5,50 +5,27 @@ go 1.23.2
toolchain go1.23.4
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
)

86
go.sum

@ -1,17 +1,3 @@
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517 h1:EgBe8VcdPwmxbSzYLndncP+NmR73uYuXxkTeDlEttEE=
gitea.pena/PenaSide/common v0.0.0-20250103085335-91ea31fee517/go.mod h1:91EuBCgcqgJ6mG36n2pds8sPwwfaJytLWOzY3h2YFKU=
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9 h1:tBkXWNIt8icmkMMnq8MA421RWkUy4OZh5P7C3q8uCu4=
gitea.pena/PenaSide/hlog v0.0.0-20241125221102-a54c29c002a9/go.mod h1:sanhSL8aEsfcq21P+eItYiAnKAre+B67nGJmDfk2cf0=
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735 h1:jDVeUhGBTXBibmW5dmtJg2m2+z5z2Rf6J4G0LpjVoJ0=
gitea.pena/PenaSide/linters-golang v0.0.0-20241207122018-933207374735/go.mod h1:gdd+vOT6up9STkEbxa2qESLIMZFjCmRbkcheFQCVgZU=
gitea.pena/PenaSide/trashlog v0.0.0-20250224122049-ddb4d72e9d07 h1:bUIUgzXQt16aBqSccI//BaODpRCTIaqlddSepM98QSc=
gitea.pena/PenaSide/trashlog v0.0.0-20250224122049-ddb4d72e9d07/go.mod h1:GRfWJerTUlgy82CiYAxE4tVYSVV54zEJJQy17Fx46E4=
gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5 h1:C+iCsGMSUJonOTNNk8wWYOfzZ0Jjw+2IQ5FaEGwRVT0=
gitea.pena/SQuiz/common v0.0.0-20250514124515-870e52266ca5/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
gitea.pena/SQuiz/common v0.0.0-20250610192732-9f01faa53ea9 h1:y97VEguCy1Qf/hl/XT9IfX3EgwLgAkn2+1sBDuxjo7Q=
gitea.pena/SQuiz/common v0.0.0-20250610192732-9f01faa53ea9/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
gitea.pena/SQuiz/common v0.0.0-20250610213406-ac5c9d667dc7 h1:LdL8G958qTeDNNsLbqhnlnFbbiSMoqqULnNqxIR4c/w=
gitea.pena/SQuiz/common v0.0.0-20250610213406-ac5c9d667dc7/go.mod h1:zCrUwDh0APpztKk6NUqTZv+zhjVbWpGBJiJ5z9dAH0U=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/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=

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

28
middleware/middleware.go Normal file

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

133
model/model.go Normal file

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

@ -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") {

64
sqlc.yaml Normal file

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