added createTestDataForResults
This commit is contained in:
parent
5a56e13d04
commit
e7086b8137
@ -2,6 +2,7 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gitea.pena/SQuiz/common/model"
|
"gitea.pena/SQuiz/common/model"
|
||||||
@ -22,9 +23,9 @@ import (
|
|||||||
// todo нужно определить из кликхауса на чем будем тестировать статистику
|
// todo нужно определить из кликхауса на чем будем тестировать статистику
|
||||||
|
|
||||||
var PublicKey = `-----BEGIN PUBLIC KEY-----MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=-----END PUBLIC KEY-----`
|
var PublicKey = `-----BEGIN PUBLIC KEY-----MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm6980fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6BdA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y+3GyaOY536H47qyXAgMBAAE=-----END PUBLIC KEY-----`
|
||||||
|
var postgresCred = "postgres://squiz:Redalert2@localhost:35432/squiz?sslmode=disable" //os.Getenv("POSTGRES_CRED")
|
||||||
var baseURL = "http://127.0.0.1:1488" //os.Getenv("API_BASE_URL")
|
var baseURL = "http://127.0.0.1:1488" //os.Getenv("API_BASE_URL")
|
||||||
var validToken = CreateJWT(validUserID) // validUserID
|
var validToken = CreateJWT(validUserID) // validUserID
|
||||||
var expiredToken = CreateExpiredToken(validUserID)
|
var expiredToken = CreateExpiredToken(validUserID)
|
||||||
|
|
||||||
// todo
|
// todo
|
||||||
@ -7250,6 +7251,221 @@ func TestCreateQuizTemplate_SpecialCases(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo перевроверить через дамп
|
||||||
|
type TestData struct {
|
||||||
|
Quiz model.Quiz
|
||||||
|
Questions []model.Question
|
||||||
|
Answers []model.Answer
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestDataForResults(t *testing.T, token string, quizName string) *TestData {
|
||||||
|
quizResp, err := createQuizRequest(token, map[string]interface{}{
|
||||||
|
"name": quizName,
|
||||||
|
"status": "start",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer quizResp.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusCreated, quizResp.StatusCode)
|
||||||
|
|
||||||
|
var quizResult model.Quiz
|
||||||
|
err = json.NewDecoder(quizResp.Body).Decode(&quizResult)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
questions := []model.Question{}
|
||||||
|
questionData := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"title": "Какой основной компонент воздуха?",
|
||||||
|
"type": "variant",
|
||||||
|
"description": "Выберите один правильный ответ.",
|
||||||
|
"required": true,
|
||||||
|
"page": 1,
|
||||||
|
"content": `{"variants": ["Кислород", "Азот", "Углекислый газ", "Водород"], "correct": 1}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Столица России?",
|
||||||
|
"type": "text",
|
||||||
|
"description": "Введите название столицы.",
|
||||||
|
"required": true,
|
||||||
|
"page": 1,
|
||||||
|
"content": `{"maxLength": 50}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Ваш возраст",
|
||||||
|
"type": "number",
|
||||||
|
"description": "Укажите ваш возраст.",
|
||||||
|
"required": false,
|
||||||
|
"page": 2,
|
||||||
|
"content": `{"min": 0, "max": 120}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Форма контактов",
|
||||||
|
"type": "result",
|
||||||
|
"description": "Оставьте свои контактные данные.",
|
||||||
|
"required": true,
|
||||||
|
"page": 2,
|
||||||
|
"content": `{"fields": ["name", "email", "phone"]}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, qData := range questionData {
|
||||||
|
qData["quiz_id"] = quizResult.Id
|
||||||
|
questionResp, err := createQuestionRequest(token, qData)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer questionResp.Body.Close()
|
||||||
|
assert.Equal(t, http.StatusOK, questionResp.StatusCode)
|
||||||
|
|
||||||
|
var questionResult model.Question
|
||||||
|
err = json.NewDecoder(questionResp.Body).Decode(&questionResult)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
questions = append(questions, questionResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
answers := createAnswersInDB(t, quizResult.Id, questions)
|
||||||
|
|
||||||
|
return &TestData{
|
||||||
|
Quiz: quizResult,
|
||||||
|
Questions: questions,
|
||||||
|
Answers: answers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAnswersInDB(t *testing.T, quizID uint64, questions []model.Question) []model.Answer {
|
||||||
|
db, err := sql.Open("postgres", postgresCred)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
answers := []model.Answer{}
|
||||||
|
|
||||||
|
sessions := []string{faker.String(), faker.String(), faker.String()}
|
||||||
|
|
||||||
|
for i, session := range sessions {
|
||||||
|
for j, question := range questions {
|
||||||
|
if question.Type == "result" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
answerContent := ""
|
||||||
|
switch question.Type {
|
||||||
|
case "variant":
|
||||||
|
answerContent = `{"selected": 1, "answer": "Азот"}`
|
||||||
|
case "text":
|
||||||
|
answerContent = `{"answer": "Москва"}`
|
||||||
|
case "number":
|
||||||
|
answerContent = `{"answer": 25}`
|
||||||
|
default:
|
||||||
|
answerContent = `{"answer": "тестовый ответ"}`
|
||||||
|
}
|
||||||
|
|
||||||
|
query := `
|
||||||
|
INSERT INTO answer (content, quiz_id, question_id, fingerprint, session, result, new, deleted, email, device_type, device, browser, ip, os, start, version)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
|
RETURNING id, content, quiz_id, question_id, fingerprint, session, result, new, deleted, email, device_type, device, browser, ip, os, start, version, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
var answer model.Answer
|
||||||
|
err := db.QueryRow(
|
||||||
|
query,
|
||||||
|
answerContent,
|
||||||
|
quizID,
|
||||||
|
question.Id,
|
||||||
|
fmt.Sprintf("fingerprint_%d_%d", i, j),
|
||||||
|
session,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
faker.Email(),
|
||||||
|
"desktop",
|
||||||
|
"Chrome",
|
||||||
|
"Chrome 120.0",
|
||||||
|
"192.168.1.1",
|
||||||
|
"Windows 10",
|
||||||
|
j == 0,
|
||||||
|
1,
|
||||||
|
).Scan(
|
||||||
|
&answer.Id,
|
||||||
|
&answer.Content,
|
||||||
|
&answer.QuizId,
|
||||||
|
&answer.QuestionId,
|
||||||
|
&answer.Fingerprint,
|
||||||
|
&answer.Session,
|
||||||
|
&answer.Result,
|
||||||
|
&answer.New,
|
||||||
|
&answer.Deleted,
|
||||||
|
&answer.Email,
|
||||||
|
&answer.DeviceType,
|
||||||
|
&answer.Device,
|
||||||
|
&answer.Browser,
|
||||||
|
&answer.IP,
|
||||||
|
&answer.OS,
|
||||||
|
&answer.Start,
|
||||||
|
&answer.Version,
|
||||||
|
&answer.CreatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
answers = append(answers, answer)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, question := range questions {
|
||||||
|
if question.Type == "result" {
|
||||||
|
answerContent := `{"name": "Иван Иванов", "email": "ivan@test.com", "phone": "+7-999-123-45-67"}`
|
||||||
|
|
||||||
|
query := `
|
||||||
|
INSERT INTO answer (content, quiz_id, question_id, fingerprint, session, result, new, deleted, email, device_type, device, browser, ip, os, start, version)
|
||||||
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
|
RETURNING id, content, quiz_id, question_id, fingerprint, session, result, new, deleted, email, device_type, device, browser, ip, os, start, version, created_at
|
||||||
|
`
|
||||||
|
|
||||||
|
var answer model.Answer
|
||||||
|
err := db.QueryRow(
|
||||||
|
query,
|
||||||
|
answerContent,
|
||||||
|
quizID,
|
||||||
|
question.Id,
|
||||||
|
fmt.Sprintf("fingerprint_result_%d", i),
|
||||||
|
session,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
faker.Email(),
|
||||||
|
"desktop",
|
||||||
|
"Chrome",
|
||||||
|
"Chrome 120.0",
|
||||||
|
"192.168.1.1",
|
||||||
|
"Windows 10",
|
||||||
|
false,
|
||||||
|
1,
|
||||||
|
).Scan(
|
||||||
|
&answer.Id,
|
||||||
|
&answer.Content,
|
||||||
|
&answer.QuizId,
|
||||||
|
&answer.QuestionId,
|
||||||
|
&answer.Fingerprint,
|
||||||
|
&answer.Session,
|
||||||
|
&answer.Result,
|
||||||
|
&answer.New,
|
||||||
|
&answer.Deleted,
|
||||||
|
&answer.Email,
|
||||||
|
&answer.DeviceType,
|
||||||
|
&answer.Device,
|
||||||
|
&answer.Browser,
|
||||||
|
&answer.IP,
|
||||||
|
&answer.OS,
|
||||||
|
&answer.Start,
|
||||||
|
&answer.Version,
|
||||||
|
&answer.CreatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
answers = append(answers, answer)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answers
|
||||||
|
}
|
||||||
|
|
||||||
func getResultsRequest(token string, quizId string, body map[string]interface{}) (*http.Response, error) {
|
func getResultsRequest(token string, quizId string, body map[string]interface{}) (*http.Response, error) {
|
||||||
payload, err := json.Marshal(body)
|
payload, err := json.Marshal(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -7264,58 +7480,11 @@ func getResultsRequest(token string, quizId string, body map[string]interface{})
|
|||||||
return http.DefaultClient.Do(req)
|
return http.DefaultClient.Do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo для этих тестов нужно заранее заполнять бдшку ответами на вопросы
|
// отсмотрено
|
||||||
func TestGetResults_Success(t *testing.T) {
|
func TestGetResults_Success(t *testing.T) {
|
||||||
quizResp, err := createQuizRequest(validToken, map[string]interface{}{
|
testData := createTestDataForResults(t, validToken, "Квиз для тестирования результатов")
|
||||||
"name": "Квиз для тестирования результатов",
|
|
||||||
"status": "start",
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer quizResp.Body.Close()
|
|
||||||
|
|
||||||
var quizResult model.Quiz
|
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", testData.Quiz.Id), map[string]interface{}{
|
||||||
err = json.NewDecoder(quizResp.Body).Decode(&quizResult)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
quizID := quizResult.Id
|
|
||||||
|
|
||||||
question1Resp, err := createQuestionRequest(validToken, map[string]interface{}{
|
|
||||||
"quiz_id": quizID,
|
|
||||||
"title": "Какой основной компонент воздуха?",
|
|
||||||
"type": "variant",
|
|
||||||
"description": "Выберите один правильный ответ.",
|
|
||||||
"required": true,
|
|
||||||
"page": 1,
|
|
||||||
"content": `{"variants": ["Кислород", "Азот", "Углекислый газ", "Водород"]}`,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer question1Resp.Body.Close()
|
|
||||||
|
|
||||||
var question1Result model.Question
|
|
||||||
err = json.NewDecoder(question1Resp.Body).Decode(&question1Result)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
question2Resp, err := createQuestionRequest(validToken, map[string]interface{}{
|
|
||||||
"quiz_id": quizID,
|
|
||||||
"title": "Столица России?",
|
|
||||||
"type": "text",
|
|
||||||
"description": "Введите название города.",
|
|
||||||
"required": true,
|
|
||||||
"page": 1,
|
|
||||||
"content": `{"placeholder": "Введите ответ"}`,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer question2Resp.Body.Close()
|
|
||||||
|
|
||||||
var question2Result model.Question
|
|
||||||
err = json.NewDecoder(question2Resp.Body).Decode(&question2Result)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// Создаем тестовые ответы (симулируем прохождение квиза)
|
|
||||||
// В реальном приложении ответы создаются через отдельный API
|
|
||||||
// Здесь мы будем тестировать получение результатов без ответов
|
|
||||||
|
|
||||||
// Тестируем получение результатов
|
|
||||||
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
|
|
||||||
"Page": 0,
|
"Page": 0,
|
||||||
"Limit": 10,
|
"Limit": 10,
|
||||||
})
|
})
|
||||||
@ -7331,18 +7500,14 @@ func TestGetResults_Success(t *testing.T) {
|
|||||||
assert.NotNil(t, result.TotalCount)
|
assert.NotNil(t, result.TotalCount)
|
||||||
assert.NotNil(t, result.Results)
|
assert.NotNil(t, result.Results)
|
||||||
|
|
||||||
// Проверяем, что результаты возвращаются в правильном формате
|
|
||||||
assert.IsType(t, uint64(0), result.TotalCount)
|
assert.IsType(t, uint64(0), result.TotalCount)
|
||||||
assert.IsType(t, []model.AnswerExport{}, result.Results)
|
assert.IsType(t, []model.AnswerExport{}, result.Results)
|
||||||
|
|
||||||
// Если есть результаты, проверяем их структуру
|
assert.Greater(t, result.TotalCount, uint64(0))
|
||||||
if len(result.Results) > 0 {
|
assert.Greater(t, len(result.Results), 0)
|
||||||
for _, answer := range result.Results {
|
|
||||||
assert.NotEmpty(t, answer.Id)
|
for _, answer := range result.Results {
|
||||||
assert.NotEmpty(t, answer.CreatedAt)
|
assert.NotEmpty(t, answer.Id)
|
||||||
assert.IsType(t, bool(false), answer.New)
|
|
||||||
assert.IsType(t, int32(0), answer.Version)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7403,40 +7568,11 @@ func TestGetResults_InputValidation(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo для этих тестов нужно заранее заполнять бдшку ответами на вопросы
|
|
||||||
func TestGetResults_Pagination(t *testing.T) {
|
func TestGetResults_Pagination(t *testing.T) {
|
||||||
// Создаем квиз для тестирования пагинации
|
testData := createTestDataForResults(t, validToken, "Квиз для тестирования пагинации результатов")
|
||||||
quizResp, err := createQuizRequest(validToken, map[string]interface{}{
|
|
||||||
"name": "Квиз для тестирования пагинации результатов",
|
|
||||||
"status": "start",
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer quizResp.Body.Close()
|
|
||||||
|
|
||||||
var quizResult model.Quiz
|
|
||||||
err = json.NewDecoder(quizResp.Body).Decode(&quizResult)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
quizID := quizResult.Id
|
|
||||||
|
|
||||||
// Создаем несколько вопросов для квиза
|
|
||||||
questionResp, err := createQuestionRequest(validToken, map[string]interface{}{
|
|
||||||
"quiz_id": quizID,
|
|
||||||
"title": "Тестовый вопрос для пагинации",
|
|
||||||
"type": "text",
|
|
||||||
"description": "Введите ответ.",
|
|
||||||
"required": true,
|
|
||||||
"page": 1,
|
|
||||||
"content": `{"placeholder": "Введите ответ"}`,
|
|
||||||
})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
defer questionResp.Body.Close()
|
|
||||||
|
|
||||||
var questionResult model.Question
|
|
||||||
err = json.NewDecoder(questionResp.Body).Decode(&questionResult)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
t.Run("FirstPage", func(t *testing.T) {
|
t.Run("FirstPage", func(t *testing.T) {
|
||||||
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
|
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", testData.Quiz.Id), map[string]interface{}{
|
||||||
"Page": 0,
|
"Page": 0,
|
||||||
"Limit": 5,
|
"Limit": 5,
|
||||||
})
|
})
|
||||||
@ -7448,13 +7584,12 @@ func TestGetResults_Pagination(t *testing.T) {
|
|||||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Проверяем, что количество результатов не превышает лимит
|
|
||||||
assert.LessOrEqual(t, uint64(len(result.Results)), uint64(5))
|
assert.LessOrEqual(t, uint64(len(result.Results)), uint64(5))
|
||||||
assert.Equal(t, result.TotalCount, uint64(len(result.Results)))
|
assert.Equal(t, result.TotalCount, uint64(len(result.Results)))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SecondPage", func(t *testing.T) {
|
t.Run("SecondPage", func(t *testing.T) {
|
||||||
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
|
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", testData.Quiz.Id), map[string]interface{}{
|
||||||
"Page": 1,
|
"Page": 1,
|
||||||
"Limit": 5,
|
"Limit": 5,
|
||||||
})
|
})
|
||||||
@ -7472,7 +7607,7 @@ func TestGetResults_Pagination(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("EmptyPage", func(t *testing.T) {
|
t.Run("EmptyPage", func(t *testing.T) {
|
||||||
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
|
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", testData.Quiz.Id), map[string]interface{}{
|
||||||
"Page": 100,
|
"Page": 100,
|
||||||
"Limit": 5,
|
"Limit": 5,
|
||||||
})
|
})
|
||||||
@ -7490,7 +7625,7 @@ func TestGetResults_Pagination(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ZeroLimit", func(t *testing.T) {
|
t.Run("ZeroLimit", func(t *testing.T) {
|
||||||
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", quizID), map[string]interface{}{
|
resp, err := getResultsRequest(validToken, fmt.Sprintf("%v", testData.Quiz.Id), map[string]interface{}{
|
||||||
"Page": 0,
|
"Page": 0,
|
||||||
"Limit": 0,
|
"Limit": 0,
|
||||||
})
|
})
|
||||||
|
24
tests/schema/000001_init.down.sql
Normal file
24
tests/schema/000001_init.down.sql
Normal file
@ -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$$;
|
120
tests/schema/000001_init.up.sql
Normal file
120
tests/schema/000001_init.up.sql
Normal file
@ -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;
|
1
tests/schema/000002_init.down.sql
Normal file
1
tests/schema/000002_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE answer DROP COLUMN IF EXISTS result;
|
1
tests/schema/000002_init.up.sql
Normal file
1
tests/schema/000002_init.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE answer ADD COLUMN result BOOLEAN DEFAULT FALSE;
|
1
tests/schema/000003_init.down.sql
Normal file
1
tests/schema/000003_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE quiz DROP COLUMN IF EXISTS sessions_count;
|
1
tests/schema/000003_init.up.sql
Normal file
1
tests/schema/000003_init.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE quiz ADD COLUMN sessions_count integer;
|
2
tests/schema/000004_init.down.sql
Normal file
2
tests/schema/000004_init.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE quiz DROP COLUMN IF EXISTS new;
|
||||||
|
ALTER TABLE quiz DROP COLUMN IF EXISTS deleted;
|
2
tests/schema/000004_init.up.sql
Normal file
2
tests/schema/000004_init.up.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE answer ADD COLUMN new BOOLEAN DEFAULT TRUE;
|
||||||
|
ALTER TABLE answer ADD COLUMN deleted BOOLEAN DEFAULT FALSE;
|
2
tests/schema/000005_init.down.sql
Normal file
2
tests/schema/000005_init.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE answer DROP COLUMN IF EXISTS email;
|
||||||
|
DROP INDEX IF EXISTS answer_email_unique_idx;
|
2
tests/schema/000005_init.up.sql
Normal file
2
tests/schema/000005_init.up.sql
Normal file
@ -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 <> '';
|
6
tests/schema/000006_init.down.sql
Normal file
6
tests/schema/000006_init.down.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE answer
|
||||||
|
DROP COLUMN device_type,
|
||||||
|
DROP COLUMN device,
|
||||||
|
DROP COLUMN os,
|
||||||
|
DROP COLUMN browser,
|
||||||
|
DROP COLUMN ip;
|
6
tests/schema/000006_init.up.sql
Normal file
6
tests/schema/000006_init.up.sql
Normal file
@ -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 '';
|
2
tests/schema/000007_init.down.sql
Normal file
2
tests/schema/000007_init.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE answer
|
||||||
|
DROP COLUMN start;
|
2
tests/schema/000007_init.up.sql
Normal file
2
tests/schema/000007_init.up.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE answer
|
||||||
|
ADD COLUMN start BOOLEAN NOT NULL DEFAULT FALSE;
|
4
tests/schema/000008_init.down.sql
Normal file
4
tests/schema/000008_init.down.sql
Normal file
@ -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 '',
|
4
tests/schema/000008_init.up.sql
Normal file
4
tests/schema/000008_init.up.sql
Normal file
@ -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);
|
4
tests/schema/000009_init.down.sql
Normal file
4
tests/schema/000009_init.down.sql
Normal file
@ -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 '',
|
4
tests/schema/000009_init.up.sql
Normal file
4
tests/schema/000009_init.up.sql
Normal file
@ -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);
|
31
tests/schema/000010_init.down.sql
Normal file
31
tests/schema/000010_init.down.sql
Normal file
@ -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;
|
119
tests/schema/000010_init.up.sql
Normal file
119
tests/schema/000010_init.up.sql
Normal file
@ -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
|
||||||
|
);
|
15
tests/schema/000011_init.down.sql
Normal file
15
tests/schema/000011_init.down.sql
Normal file
@ -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[];
|
5
tests/schema/000011_init.up.sql
Normal file
5
tests/schema/000011_init.up.sql
Normal file
@ -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;
|
4
tests/schema/000012_init.down.sql
Normal file
4
tests/schema/000012_init.down.sql
Normal file
@ -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);
|
4
tests/schema/000012_init.up.sql
Normal file
4
tests/schema/000012_init.up.sql
Normal file
@ -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;
|
4
tests/schema/000013_init.down.sql
Normal file
4
tests/schema/000013_init.down.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE rules
|
||||||
|
DROP COLUMN IF EXISTS TagsToAdd;
|
||||||
|
ALTER TABLE users
|
||||||
|
DROP COLUMN IF EXISTS DriveURL;
|
4
tests/schema/000013_init.up.sql
Normal file
4
tests/schema/000013_init.up.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE rules
|
||||||
|
ADD COLUMN TagsToAdd JSONB NOT NULL DEFAULT '{}';
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN DriveURL text NOT NULL DEFAULT '';
|
20
tests/schema/000014_init.down.sql
Normal file
20
tests/schema/000014_init.down.sql
Normal file
@ -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;
|
28
tests/schema/000014_init.up.sql
Normal file
28
tests/schema/000014_init.up.sql
Normal file
@ -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
tests/schema/000015_init.down.sql
Normal file
0
tests/schema/000015_init.down.sql
Normal file
19
tests/schema/000015_init.up.sql
Normal file
19
tests/schema/000015_init.up.sql
Normal file
@ -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;
|
1
tests/schema/000016_init.down.sql
Normal file
1
tests/schema/000016_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS amoContact;
|
6
tests/schema/000016_init.up.sql
Normal file
6
tests/schema/000016_init.up.sql
Normal file
@ -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
|
||||||
|
)
|
11
tests/schema/000017_init.down.sql
Normal file
11
tests/schema/000017_init.down.sql
Normal file
@ -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;
|
31
tests/schema/000017_init.up.sql
Normal file
31
tests/schema/000017_init.up.sql
Normal file
@ -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
|
||||||
|
);
|
12
tests/schema/000018_init.down.sql
Normal file
12
tests/schema/000018_init.down.sql
Normal file
@ -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;
|
29
tests/schema/000018_init.up.sql
Normal file
29
tests/schema/000018_init.up.sql
Normal file
@ -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;
|
11
tests/schema/000019_init.down.sql
Normal file
11
tests/schema/000019_init.down.sql
Normal file
@ -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;
|
11
tests/schema/000019_init.up.sql
Normal file
11
tests/schema/000019_init.up.sql
Normal file
@ -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;
|
19
tests/schema/000020_init.down.sql
Normal file
19
tests/schema/000020_init.down.sql
Normal file
@ -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 $$;
|
57
tests/schema/000020_init.up.sql
Normal file
57
tests/schema/000020_init.up.sql
Normal file
@ -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
|
||||||
|
)
|
35
tests/schema/000021_init.down.sql
Normal file
35
tests/schema/000021_init.down.sql
Normal file
@ -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;
|
149
tests/schema/000021_init.up.sql
Normal file
149
tests/schema/000021_init.up.sql
Normal file
@ -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;
|
1
tests/schema/000022_init.down.sql
Normal file
1
tests/schema/000022_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE question DROP COLUMN IF EXISTS auditory;
|
1
tests/schema/000022_init.up.sql
Normal file
1
tests/schema/000022_init.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE question ADD COLUMN auditory BIGINT NOT NULL DEFAULT 0;
|
0
tests/schema/000023_init.down.sql
Normal file
0
tests/schema/000023_init.down.sql
Normal file
14
tests/schema/000023_init.up.sql
Normal file
14
tests/schema/000023_init.up.sql
Normal file
@ -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;
|
1
tests/schema/000024_init.down.sql
Normal file
1
tests/schema/000024_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE quiz DROP COLUMN IF EXISTS gigachat;
|
1
tests/schema/000024_init.up.sql
Normal file
1
tests/schema/000024_init.up.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE quiz ADD COLUMN gigachat boolean NOT NULL DEFAULT false;
|
1
tests/schema/000025_init.down.sql
Normal file
1
tests/schema/000025_init.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
drop table if exists quiz_utm;
|
7
tests/schema/000025_init.up.sql
Normal file
7
tests/schema/000025_init.up.sql
Normal file
@ -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
|
||||||
|
);
|
2
tests/schema/000026_init.down.sql
Normal file
2
tests/schema/000026_init.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_quiz_privilege_unique;
|
||||||
|
drop table if exists quiz_privilege_usage;
|
10
tests/schema/000026_init.up.sql
Normal file
10
tests/schema/000026_init.up.sql
Normal file
@ -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);
|
Loading…
Reference in New Issue
Block a user