diff --git a/dal/dal.go b/dal/dal.go index 4d0c38d..106e537 100644 --- a/dal/dal.go +++ b/dal/dal.go @@ -5,6 +5,8 @@ import ( "database/sql" _ "embed" "errors" + "fmt" + _ "github.com/ClickHouse/clickhouse-go" _ "github.com/lib/pq" "github.com/minio/minio-go/v7" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal/sqlcgen" @@ -31,6 +33,7 @@ type DAL struct { ResultRepo *result.ResultRepository WorkerRepo *workers.WorkerRepository StatisticsRepo *statistics.StatisticsRepository + WorkerAnsRepo *answer.WorkerAnswerRepository } func New(ctx context.Context, cred string, minioClient *minio.Client) (*DAL, error) { @@ -62,12 +65,17 @@ func New(ctx context.Context, cred string, minioClient *minio.Client) (*DAL, err } } - answerRepo := answer.NewAnswerRepository(answer.Deps{ + workerAnsRepo := answer.NewWorkerAnswerRepo(answer.Deps{ Queries: queries, Pool: pool, AnswerMinio: storerAnswer, }) + answerRepo := answer.NewAnswerRepository(answer.Deps{ + Queries: queries, + Pool: pool, + }) + questionRepo := question.NewQuestionRepository(question.Deps{ Queries: queries, Pool: pool, @@ -102,6 +110,7 @@ func New(ctx context.Context, cred string, minioClient *minio.Client) (*DAL, err ResultRepo: resultRepo, WorkerRepo: workerRepo, StatisticsRepo: statisticsRepo, + WorkerAnsRepo: workerAnsRepo, }, nil } @@ -177,3 +186,38 @@ func (d *AmoDal) Close(ctx context.Context) error { } return nil } + +type ClickHouseDAL struct { + conn *sql.DB + StatisticClickRepo *statistics.StatisticClick +} + +func NewClickHouseDAL(ctx context.Context, cred string) (*ClickHouseDAL, error) { + conn, err := sql.Open("clickhouse", cred) + if err != nil { + return nil, fmt.Errorf("error open database connection: %w", err) + } + + timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + if err := conn.PingContext(timeoutCtx); err != nil { + return nil, fmt.Errorf("error ping database: %w", err) + } + + statsClickRepo, err := statistics.NewClickStatistic(ctx, statistics.DepsClick{ + Conn: conn, + }) + if err != nil { + return nil, err + } + + return &ClickHouseDAL{ + conn: conn, + StatisticClickRepo: statsClickRepo, + }, nil +} + +func (d *ClickHouseDAL) Close(ctx context.Context) error { + return d.conn.Close() +} diff --git a/dal/db_query/queries.sql b/dal/db_query/queries.sql index 2413636..e5868b8 100644 --- a/dal/db_query/queries.sql +++ b/dal/db_query/queries.sql @@ -20,7 +20,7 @@ INSERT INTO quiz (accountid, qid ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19) - RETURNING id, created_at, updated_at, qid; +RETURNING id, created_at, updated_at, qid; -- name: InsertQuestion :one INSERT INTO question ( @@ -35,7 +35,7 @@ INSERT INTO question ( updated_at ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) - RETURNING id, created_at, updated_at; +RETURNING id, created_at, updated_at; -- name: DeleteQuestion :one UPDATE question SET deleted=true WHERE id=$1 RETURNING question.*; @@ -51,7 +51,7 @@ INSERT INTO question( SELECT $1, title, description, questiontype, required, page, content, version, parent_ids FROM question WHERE question.id=$2 - RETURNING question.id, quiz_id, created_at, updated_at; +RETURNING question.id, quiz_id, created_at, updated_at; -- name: DuplicateQuestion :one INSERT INTO question( @@ -61,7 +61,7 @@ INSERT INTO question( SELECT quiz_id, title, description, questiontype, required, page, content, version, parent_ids FROM question WHERE question.id=$1 - RETURNING question.id, quiz_id, created_at, updated_at; +RETURNING question.id, quiz_id, created_at, updated_at; -- name: MoveToHistory :one INSERT INTO question( @@ -71,7 +71,7 @@ INSERT INTO question( SELECT quiz_id, title, description, questiontype, required, page, content, version, parent_ids, true as deleted FROM question WHERE question.id=$1 - RETURNING question.id, quiz_id, parent_ids; +RETURNING question.id, quiz_id, parent_ids; -- name: MoveToHistoryQuiz :one INSERT INTO quiz(deleted, @@ -81,7 +81,7 @@ INSERT INTO quiz(deleted, SELECT true as deleted, accountid, archived,fingerprinting,repeatable,note_prevented,mail_notifications,unique_answers,name,description,config, status,limit_answers,due_to,time_of_passing,pausable,version,version_comment,parent_ids,questions_count,answers_count,average_time_passing, super, group_id FROM quiz WHERE quiz.id=$1 AND quiz.accountid=$2 - RETURNING quiz.id, qid, parent_ids; +RETURNING quiz.id, qid, parent_ids; -- name: CopyQuiz :one INSERT INTO quiz( @@ -91,7 +91,7 @@ INSERT INTO quiz( SELECT accountid, archived,fingerprinting,repeatable,note_prevented,mail_notifications,unique_answers,name,description,config, status,limit_answers,due_to,time_of_passing,pausable,version,version_comment,parent_ids,questions_count, super, group_id FROM quiz WHERE quiz.id=$1 AND quiz.accountId=$2 - RETURNING id, qid,created_at, updated_at; +RETURNING id, qid,created_at, updated_at; -- name: CopyQuizQuestions :exec INSERT INTO question( @@ -134,6 +134,7 @@ SELECT p.amount, p.created_at, a.id, + a.email, qz.config FROM privileges AS p @@ -143,7 +144,7 @@ WHERE qz.id = $1; -- name: CreateAccount :one -INSERT INTO account (id, user_id, created_at, deleted) VALUES ($1, $2, $3, $4) RETURNING *; +INSERT INTO account (id, user_id, email, created_at, deleted) VALUES ($1, $2, $3, $4, $5) RETURNING *;; -- name: DeletePrivilegeByAccID :exec DELETE FROM privileges WHERE account_id = $1; @@ -232,11 +233,11 @@ SET answers_count = COALESCE(aa.unique_true_answers_count, 0), average_time_passing = COALESCE(sta.average_session_time, 0), sessions_count = COALESCE(sta.sess,0) - FROM +FROM (SELECT * FROM quiz WHERE deleted = FALSE AND archived = FALSE) q_sub -LEFT JOIN answer_aggregates aa ON q_sub.id = aa.quiz_id - LEFT JOIN question_aggregates qa ON q_sub.id = qa.quiz_id - LEFT JOIN session_times_aggregates sta ON q_sub.id = sta.quiz_id + LEFT JOIN answer_aggregates aa ON q_sub.id = aa.quiz_id + LEFT JOIN question_aggregates qa ON q_sub.id = qa.quiz_id + LEFT JOIN session_times_aggregates sta ON q_sub.id = sta.quiz_id WHERE q.id = q_sub.id; @@ -247,6 +248,7 @@ UPDATE privileges SET amount = $1 WHERE id = $2; SELECT a.id, a.user_id, + a.email, a.created_at, COALESCE(p.ID,0), coalesce(p.privilegeid,''), @@ -407,13 +409,13 @@ WITH TimeBucket AS ( SELECT date_trunc('hour', timestamp_bucket)::TIMESTAMP AS time_interval_start, COALESCE(LEAD( - date_trunc('hour', timestamp_bucket)::TIMESTAMP - ) OVER (ORDER BY timestamp_bucket), NOW()) AS time_interval_end + date_trunc('hour', timestamp_bucket)::TIMESTAMP + ) OVER (ORDER BY timestamp_bucket), NOW()) AS time_interval_end FROM - generate_series(TO_TIMESTAMP($1), TO_TIMESTAMP($2), CASE - WHEN EXTRACT(epoch FROM TO_TIMESTAMP($2)) - EXTRACT(epoch FROM TO_TIMESTAMP($1)) > 172800 THEN '1 day'::interval - ELSE '1 hour'::interval - END) AS timestamp_bucket + generate_series(TO_TIMESTAMP($1), TO_TIMESTAMP($2), CASE + WHEN EXTRACT(epoch FROM TO_TIMESTAMP($2)) - EXTRACT(epoch FROM TO_TIMESTAMP($1)) > 172800 THEN '1 day'::interval + ELSE '1 hour'::interval + END) AS timestamp_bucket ), OpenStats AS ( SELECT @@ -494,7 +496,7 @@ SELECT CASE WHEN COALESCE(os.open_count, 0) > 0 THEN COALESCE(rs.true_result_count, 0)::float / COALESCE(os.open_count, 0)::float ELSE 0 - END::float AS conversion, + END::float AS conversion, COALESCE(at.avg_time, 0) AS avg_time FROM TimeBucket tb @@ -511,10 +513,10 @@ FROM -- name: QuestionsStatistics :many WITH Funnel AS ( SELECT - COUNT(DISTINCT a.session) FILTER (WHERE a.start = FALSE) AS count_start_false, - COUNT(DISTINCT a.session) FILTER (WHERE a.start = TRUE) AS count_start_true, - COUNT(DISTINCT CASE WHEN a.result = FALSE AND qid_true_result IS NOT NULL THEN a.session END) AS count_f_result_with_t_question, - COUNT(DISTINCT a.session) FILTER (WHERE a.result = TRUE) AS count_t_result + COUNT(DISTINCT a.session) FILTER (WHERE a.start = FALSE) AS count_start_false, + COUNT(DISTINCT a.session) FILTER (WHERE a.start = TRUE) AS count_start_true, + COUNT(DISTINCT CASE WHEN a.result = FALSE AND qid_true_result IS NOT NULL THEN a.session END) AS count_f_result_with_t_question, + COUNT(DISTINCT a.session) FILTER (WHERE a.result = TRUE) AS count_t_result FROM answer a LEFT JOIN ( @@ -529,23 +531,23 @@ WITH Funnel AS ( AND a.created_at <= TO_TIMESTAMP($3) ), Results AS ( - SELECT - COALESCE(q.title, '') AS question_title, - COUNT(*) AS total_answers, - CAST(COUNT(*) * 100.0 / NULLIF(SUM(COUNT(*)) FILTER (WHERE a.result = TRUE) OVER (PARTITION BY a.quiz_id), 0) AS FLOAT8) AS percentage - FROM - question q - JOIN answer a ON q.id = a.question_id - WHERE - a.quiz_id = $1 - AND a.created_at >= TO_TIMESTAMP($2) - AND a.created_at <= TO_TIMESTAMP($3) - AND a.result = TRUE - GROUP BY - q.title, a.quiz_id, a.result - HAVING - COUNT(*) >= 1 - ), + SELECT + COALESCE(q.title, '') AS question_title, + COUNT(*) AS total_answers, + CAST(COUNT(*) * 100.0 / NULLIF(SUM(COUNT(*)) FILTER (WHERE a.result = TRUE) OVER (PARTITION BY a.quiz_id), 0) AS FLOAT8) AS percentage + FROM + question q + JOIN answer a ON q.id = a.question_id + WHERE + a.quiz_id = $1 + AND a.created_at >= TO_TIMESTAMP($2) + AND a.created_at <= TO_TIMESTAMP($3) + AND a.result = TRUE + GROUP BY + q.title, a.quiz_id, a.result + HAVING + COUNT(*) >= 1 + ), LastContent AS ( SELECT a.question_id, @@ -561,35 +563,35 @@ WITH Funnel AS ( answer WHERE quiz_id = $1 - AND start != true + AND start != true AND created_at >= TO_TIMESTAMP($2) AND created_at <= TO_TIMESTAMP($3) GROUP BY - question_id, session + question_id, session ) AS last_created_at_one_session ON a.session = last_created_at_one_session.session AND a.question_id = last_created_at_one_session.question_id AND a.created_at = last_created_at_one_session.last_created_at ), Questions AS ( - SELECT - q.title AS question_title, - q.page AS question_page, - lc.last_answer_content AS answer_content, - CAST( - COUNT(CASE WHEN a.result = FALSE THEN 1 END) * 100.0 / NULLIF(SUM(COUNT(CASE WHEN a.result = FALSE THEN 1 END)) OVER (PARTITION BY q.id), 0) AS FLOAT8 - ) AS percentage - FROM - question q - JOIN LastContent lc ON q.id = lc.question_id - JOIN answer a ON q.id = a.question_id - WHERE - a.quiz_id = $1 - AND a.start != true - AND a.created_at >= TO_TIMESTAMP($2) - AND a.created_at <= TO_TIMESTAMP($3) - GROUP BY - q.id, q.title, lc.last_answer_content - HAVING - COUNT(*) >= 1 -) + SELECT + q.title AS question_title, + q.page AS question_page, + lc.last_answer_content AS answer_content, + CAST( + COUNT(CASE WHEN a.result = FALSE THEN 1 END) * 100.0 / NULLIF(SUM(COUNT(CASE WHEN a.result = FALSE THEN 1 END)) OVER (PARTITION BY q.id), 0) AS FLOAT8 + ) AS percentage + FROM + question q + JOIN LastContent lc ON q.id = lc.question_id + JOIN answer a ON q.id = a.question_id + WHERE + a.quiz_id = $1 + AND a.start != true + AND a.created_at >= TO_TIMESTAMP($2) + AND a.created_at <= TO_TIMESTAMP($3) + GROUP BY + q.id, q.title, lc.last_answer_content + HAVING + COUNT(*) >= 1 + ) SELECT Funnel.count_start_false, Funnel.count_start_true, @@ -603,34 +605,34 @@ SELECT COALESCE(Questions.percentage, 0) AS questions_percentage FROM Funnel - LEFT JOIN Results ON true - LEFT JOIN Questions ON Questions.percentage >= 1; + LEFT JOIN Results ON true + LEFT JOIN Questions ON Questions.percentage >= 1; -- name: QuizCopyQid :one - INSERT INTO quiz ( - accountid, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, name, description, config, - status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, questions_count, answers_count, average_time_passing, super, group_id - ) - SELECT - $2, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, name, description, config, - status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, questions_count, answers_count, average_time_passing, super, group_id - FROM - quiz as q - WHERE - q.qid = $1 - RETURNING (select id from quiz where qid = $1),id, qid; - +INSERT INTO quiz ( + accountid, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, name, description, config, + status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, questions_count, answers_count, average_time_passing, super, group_id +) +SELECT + $2, archived, fingerprinting, repeatable, note_prevented, mail_notifications, unique_answers, name, description, config, + status, limit_answers, due_to, time_of_passing, pausable, version, version_comment, parent_ids, questions_count, answers_count, average_time_passing, super, group_id +FROM + quiz as q +WHERE + q.qid = $1 +RETURNING (select id from quiz where qid = $1),id, qid; + -- name: CopyQuestionQuizID :exec INSERT INTO question ( quiz_id, title, description, questiontype, required, page, content, version, parent_ids, created_at, updated_at ) -SELECT +SELECT $2, title, description, questiontype, required, page, content, version, parent_ids, created_at, updated_at -FROM +FROM question -WHERE +WHERE question.quiz_id = $1 AND deleted = false; -- name: GetQidOwner :one @@ -656,7 +658,7 @@ SELECT (SELECT registration_count FROM Registrations) AS registrations, (SELECT quiz_count FROM Quizes) AS quizes, (SELECT result_count FROM Results) AS results; - + -- name: GetListStartQuiz :many SELECT id FROM quiz WHERE accountid = $1 AND status = 'start'; @@ -684,8 +686,8 @@ RETURNING quiz_id; -- amo methods: -- name: CreateAmoAccount :exec -INSERT INTO users (AccountID, AmoID, Name, Email, Role, "Group", Deleted, CreatedAt, Subdomain, AmoUserID, Country,DriveURL) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12); +INSERT INTO accountsAmo (AccountID, AmoID,Name, Subdomain, Country,DriveURL) +VALUES ($1, $2, $3, $4, $5, $6); -- name: CreateWebHook :exec INSERT INTO tokens (AccountID, RefreshToken, AccessToken, AuthCode, Expiration, CreatedAt) @@ -698,61 +700,80 @@ WHERE accountID = $5; -- name: GetAllTokens :many SELECT * FROM tokens; --- name: CheckExpired :many -SELECT * FROM tokens WHERE Expiration <= TO_TIMESTAMP(EXTRACT(EPOCH FROM NOW()) + (10 * 60)); +-- name: CheckExpiredToken :one +SELECT * FROM tokens WHERE AccountID = $1 AND Expiration <= NOW(); + +-- name: UpdateAmoAccount :exec +UPDATE accountsAmo SET Name = $2, Subdomain = $3, Country = $4, DriveURL = $5 WHERE AccountID = $1 AND Deleted = false; -- name: WebhookDelete :exec -WITH userd AS ( - UPDATE users SET Deleted = true WHERE AmoUserID = $1 RETURNING AccountID -) -DELETE FROM tokens WHERE AccountID IN (SELECT AccountID FROM userd); +WITH companyDel AS ( + UPDATE accountsAmo SET Deleted = true WHERE accountsAmo.AmoID = $1 RETURNING AccountID +), + userDel AS ( + UPDATE usersAmo SET Deleted = true WHERE AmoID = $1 + ) +DELETE FROM tokens WHERE AccountID IN (SELECT AccountID FROM companyDel); -- name: SoftDeleteAccount :exec -WITH userd AS ( - SELECT AmoUserID FROM users WHERE users.AccountID = $1 +WITH amoCompany AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 +),usersDel AS ( + UPDATE usersAmo SET Deleted = true WHERE AmoID = (SELECT AmoID FROM amoCompany) ), - tokend AS ( - UPDATE users SET Deleted = true WHERE AmoUserID IN (SELECT AmoUserID FROM userd) + companyDel AS ( UPDATE accountsAmo SET Deleted = true WHERE AmoID = (SELECT AmoID FROM amoCompany) ) DELETE FROM tokens WHERE tokens.AccountID = $1; --- name: GetCurrentAccount :one -SELECT * FROM users WHERE AccountID = $1 AND Deleted = false; +-- name: GetCurrentCompany :one +SELECT * FROM accountsAmo WHERE AccountID = $1 AND Deleted = false; --- name: CheckMainUser :exec -UPDATE users SET Name = $1, "Group" = $2, Email = $3, Role = $4 WHERE AmoID = $5; +-- name: GetAllCompanyUsers :many +SELECT * FROM usersamo WHERE amoid = $1 AND deleted = false; -- name: GetUsersWithPagination :many WITH user_data AS ( - SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ) -SELECT u.*, COUNT(*) OVER() as total_count -FROM users u -JOIN user_data a ON u.AmoUserID = a.AmoID +SELECT u.*, COUNT(*) OVER() as total_count +FROM usersAmo u + JOIN user_data a ON u.AmoID = a.AmoID WHERE u.Deleted = false ORDER BY u.ID OFFSET ($2 - 1) * $3 LIMIT $3; -- name: GetTagsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT t.*, COUNT(*) OVER() as total_count -FROM tags t JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON t.AccountID = u.AmoID +FROM tags t JOIN user_data u ON t.AccountID = u.AmoID WHERE t.Deleted = false ORDER BY t.ID OFFSET ($2 - 1) * $3 LIMIT $3; -- name: GetStepsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT s.*, COUNT(*) OVER() as total_count -FROM steps s JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON s.AccountID = u.AmoID +FROM steps s JOIN user_data u ON s.AccountID = u.AmoID WHERE s.Deleted = false AND PipelineID = $4 ORDER BY s.ID OFFSET ($2 - 1) * $3 LIMIT $3; -- name: GetPipelinesWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT p.*, COUNT(*) OVER() as total_count -FROM pipelines p JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON p.AccountID = u.AmoID +FROM pipelines p JOIN user_data u ON p.AccountID = u.AmoID WHERE p.Deleted = false ORDER BY p.ID OFFSET ($2 - 1) * $3 LIMIT $3; -- name: GetFieldsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT f.*, COUNT(*) OVER() as total_count -FROM fields f JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON f.AccountID = u.AmoID +FROM fields f JOIN user_data u ON f.AccountID = u.AmoID WHERE f.Deleted = false ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3; @@ -791,12 +812,10 @@ FROM json_array_elements($1::json) AS update_data WHERE f.amoID = (update_data ->> 'AmoID')::INT AND f.accountID = (update_data ->> 'AccountID')::INT AND f.Entity = (update_data ->> 'Entity')::entitytype; - + -- name: CheckTags :many WITH user_data AS ( - SELECT AmoID - FROM users - WHERE users.AccountID = $1 + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ), new_tags AS ( SELECT (tag->>'AmoID')::INT AS amoID, (tag->>'Entity')::entitytype AS Entity, @@ -818,7 +837,7 @@ WITH user_data AS ( ) SELECT nt.*,ud.AmoID FROM new_tags nt - JOIN user_data ud ON true + JOIN user_data ud ON true WHERE NOT EXISTS ( SELECT * FROM inserted_tags ins @@ -836,14 +855,14 @@ WITH new_pipelines AS ( FROM json_array_elements($1::json) AS pipeline ), inserted_pipelines AS( INSERT INTO pipelines (amoID, accountID, name, isArchive, createdAt) - SELECT np.amoID, - np.accountID, - np.name, - np.isArchive, - np.createdAt - FROM new_pipelines np - ON CONFLICT (amoID, accountID) DO NOTHING - RETURNING * + SELECT np.amoID, + np.accountID, + np.name, + np.isArchive, + np.createdAt + FROM new_pipelines np + ON CONFLICT (amoID, accountID) DO NOTHING + RETURNING * ) SELECT np.* FROM new_pipelines np @@ -855,34 +874,32 @@ WHERE NOT EXISTS ( -- name: CheckFields :many WITH user_data AS ( - SELECT AmoID - FROM users - WHERE users.AccountID = $1 + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ), new_fields AS ( SELECT (field->>'AmoID')::INT AS amoID, - COALESCE(field->>'Code', '')::varchar(255) AS code, - COALESCE(field->>'Name', '')::varchar(512) AS name, - CAST(field->>'Entity' AS entitytype) AS Entity, - COALESCE(field->>'Type', '')::fieldtype AS type, - CURRENT_TIMESTAMP AS createdAt + COALESCE(field->>'Code', '')::varchar(255) AS code, + COALESCE(field->>'Name', '')::varchar(512) AS name, + CAST(field->>'Entity' AS entitytype) AS Entity, + COALESCE(field->>'Type', '')::fieldtype AS type, + CURRENT_TIMESTAMP AS createdAt FROM json_array_elements($2::json) AS field ), inserted_fields AS( INSERT INTO fields (amoID, code, accountID, name, Entity, type, createdAt) - SELECT nf.amoID, - nf.code, - ud.AmoID, - nf.name, - nf.Entity, - nf.type, - nf.createdAt - FROM new_fields nf - JOIN user_data ud ON true - ON CONFLICT (amoID, accountID, entity) DO NOTHING - RETURNING * + SELECT nf.amoID, + nf.code, + ud.AmoID, + nf.name, + nf.Entity, + nf.type, + nf.createdAt + FROM new_fields nf + JOIN user_data ud ON true + ON CONFLICT (amoID, accountID, entity) DO NOTHING + RETURNING * ) SELECT nf.*,ud.AmoID FROM new_fields nf - JOIN user_data ud ON true + JOIN user_data ud ON true WHERE NOT EXISTS ( SELECT * FROM inserted_fields ins @@ -901,15 +918,15 @@ WITH new_steps AS ( FROM json_array_elements($1::json) AS step ), inserted_steps AS ( INSERT INTO steps (amoID, pipelineID, accountID, name, color, createdAt) - SELECT ns.amoID, - ns.pipelineID, - ns.accountID, - ns.name, - ns.color, - ns.createdAt - FROM new_steps ns - ON CONFLICT (amoID, accountID, PipelineID) DO NOTHING - RETURNING * + SELECT ns.amoID, + ns.pipelineID, + ns.accountID, + ns.name, + ns.color, + ns.createdAt + FROM new_steps ns + ON CONFLICT (amoID, accountID, PipelineID) DO NOTHING + RETURNING * ) SELECT ns.* FROM new_steps ns @@ -928,13 +945,13 @@ SELECT * FROM rules WHERE QuizID = $1 AND Deleted = false; -- name: SetQuizSettings :one INSERT INTO rules (AccountID, QuizID, PerformerID, PipelineID, StepID, FieldsRule,TagsToAdd) SELECT u.AmoID AS AccountID,$1 AS QuizID,$2 AS PerformerID,$3 AS PipelineID, - $4 AS StepID,$5 AS FieldsRule,$6 AS TagsToAdd FROM users u WHERE u.AccountID = $7 AND u.Deleted = false - RETURNING id; + $4 AS StepID,$5 AS FieldsRule,$6 AS TagsToAdd FROM accountsamo u WHERE u.AccountID = $7 AND u.Deleted = false +RETURNING id; -- name: ChangeQuizSettings :one UPDATE rules SET PerformerID = $1,PipelineID = $2,StepID = $3,FieldsRule = $4, TagsToAdd=$5 -WHERE AccountID = (SELECT AmoID FROM users WHERE users.AccountID = $6 AND users.Deleted = false) AND QuizID = $7 AND Deleted = false +WHERE AccountID = (SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $6 AND accountsAmo.Deleted = false) AND QuizID = $7 AND Deleted = false RETURNING id; -- name: GetQuestionListByIDs :many @@ -942,62 +959,29 @@ SELECT * FROM question WHERE id = ANY($1::int[]) AND deleted = FALSE; -- name: UpdateFieldRules :exec UPDATE rules SET FieldsRule = $1 -WHERE AccountID = (SELECT AmoID FROM users WHERE users.AccountID = $2 AND users.Deleted = false) AND QuizID = $3 AND Deleted = false; +WHERE AccountID = (SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $2 AND accountsAmo.Deleted = false) AND QuizID = $3 AND Deleted = false; --- name: UpdateUsers :exec -UPDATE users AS u -SET Name = (update_data ->> 'Name')::varchar(512), - Email = (update_data ->> 'Email')::varchar(50), - Role = (update_data ->> 'Role')::INT, - "Group" = (update_data ->> 'Group')::INT, - AmoUserID= (update_data ->> 'AmoUserID')::INT -FROM json_array_elements($1::json) AS update_data -WHERE u.AmoID = (update_data ->> 'AmocrmID')::INT; +-- name: UpdateAmoAccountUser :exec +UPDATE usersAmo SET Name = $3, Email = $4, Role = $5, "Group" = $6 +WHERE AmoID = $1 AND AmoUserID = $2 AND deleted = false; --- name: CheckUsers :many -WITH new_users AS ( - SELECT (u->>'AmocrmID')::INT AS AmoID, - (u->>'Name')::VARCHAR(512) AS Name, - (u->>'Group')::INT AS "Group", - (u->>'Role')::INT AS Role, - (u->>'Email')::VARCHAR(50) AS Email, - (u->>'AmoUserID')::INT AS AmoUserID, - CURRENT_TIMESTAMP AS createdAt - FROM json_array_elements($1::json) AS u -), inserted_users AS ( - INSERT INTO users (AmoID, Name, "Group", Role, Email, AmoUserID,createdAt) - SELECT nu.AmoID, - nu.Name, - nu."Group", - nu.Role, - nu.Email, - nu.AmoUserID, - nu.createdAt - FROM new_users nu - ON CONFLICT (amoID) DO NOTHING - RETURNING * -) -SELECT nu.* -FROM new_users nu -WHERE NOT EXISTS ( - SELECT * - FROM inserted_users ins - WHERE ins.amoID = nu.amoID -); +-- name: AddAmoAccountUser :exec +INSERT INTO usersAmo (AmoID, AmoUserID, Name, Email, Role, "Group") +VALUES ($1, $2, $3, $4, $5, $6); -- name: GettingAmoUsersTrueResults :many SELECT a.quiz_id,a.id,a.result,a.question_id,a.content,a.session, - (SELECT a2.utm - FROM answer a2 - WHERE a2.start = true AND a2.session = a.session - LIMIT 1) AS utm -,t.accesstoken,r.accountid,r.fieldsrule,r.tagstoadd,r.performerid,r.stepid,r.pipelineid,(SELECT u.name FROM users u WHERE u.amoid = r.performerid) AS performer_name,u.subdomain,u.accountid,u.driveurl + COALESCE((SELECT a2.utm + FROM answer a2 + WHERE a2.start = true AND a2.session = a.session + LIMIT 1), '{}'::jsonb) AS utm + ,t.accesstoken,r.accountid,r.fieldsrule,r.tagstoadd,r.performerid,r.stepid,r.pipelineid,(SELECT u.name FROM usersAmo u WHERE u.AmoUserID = r.performerid AND u.deleted = false) AS performer_name,u.subdomain,u.accountid,u.driveurl FROM answer a INNER JOIN quiz q ON a.quiz_id = q.id LEFT JOIN amoCRMStatuses s ON a.id = s.AnswerID INNER JOIN rules r ON q.id = r.QuizID INNER JOIN tokens t ON q.accountid = t.AccountID - INNER JOIN users u ON q.accountid = u.accountid AND u.amoid = r.accountid + INNER JOIN accountsAmo u ON q.accountid = u.accountid AND u.amoid = r.accountid WHERE a.result = true AND s.id IS NULL AND a.deleted = false @@ -1009,12 +993,12 @@ WHERE a.result = true INSERT INTO amoCRMStatuses (AccountID, DealID, AnswerID, Status) SELECT u.AmoID, $1, $2, $3 FROM tokens AS t - JOIN users AS u ON t.AccountID = u.AccountID + JOIN accountsAmo AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $4 AND u.Deleted = false; -- name: UpdatingDealAmoStatus :exec UPDATE amoCRMStatuses SET Status = $1 -WHERE DealID = $2 AND AccountID = (SELECT u.AmoID FROM tokens AS t JOIN users AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $3 AND u.Deleted = false); +WHERE DealID = $2 AND AccountID = (SELECT u.AmoID FROM tokens AS t JOIN accountsAmo AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $3 AND u.Deleted = false); -- name: DeleteFields :exec UPDATE fields SET Deleted = true WHERE ID = ANY($1::bigint[]); @@ -1029,7 +1013,7 @@ UPDATE steps SET Deleted = true WHERE ID = ANY($1::bigint[]); UPDATE pipelines SET Deleted = true WHERE ID = ANY($1::bigint[]); -- name: DeleteUsers :exec -UPDATE users SET Deleted = true WHERE ID = ANY($1::bigint[]); +UPDATE usersAmo SET Deleted = true WHERE ID = ANY($1::bigint[]); -- name: GetUserTagsByID :many SELECT ID,AmoID,AccountID,Name,Entity,Color @@ -1047,9 +1031,7 @@ FROM pipelines WHERE AccountID = $1 AND Deleted = false; -- name: GetUserUsersByID :many -SELECT ID,AccountID,AmoID,Name,Email,Role,"Group",Subdomain,AmoUserID,Country -FROM users -WHERE AmoUserID = $1 AND Deleted = false; +SELECT * FROM usersAmo WHERE amoid = $1 AND Deleted = false; -- name: GetFieldByAmoID :one SELECT * FROM fields WHERE AmoID = $1 AND Deleted = false; @@ -1062,7 +1044,19 @@ WHERE AccountID = $1 AND Deleted = false; -- name: DecrementManual :one UPDATE privileges p SET amount = amount - 1 FROM account a WHERE p.account_id = a.id AND a.user_id = $1 AND p.privilegeID = $2 AND p.amount > 0 -RETURNING p.id, p.privilegeID, p.account_id, p.privilege_name, p.amount, p.created_at;; +RETURNING p.id, p.privilegeID, p.account_id, p.privilege_name, p.amount, p.created_at; + +-- name: GetExistingContactAmo :many +WITH getAmoID AS ( + SELECT AmoID FROM amoContact WHERE amoContact.AccountID = $1 AND amoContact.Field = ANY($2::text[]) +) SELECT * FROM amoContact +WHERE amoContact.AccountID = $1 AND amoContact.AmoID IN (SELECT AmoID FROM getAmoID); + +-- name: InsertContactAmo :one +INSERT INTO amoContact (AccountID, AmoID, Field) VALUES ($1, $2, $3) RETURNING AmoID; + +-- name: UpdateAmoContact :exec +UPDATE amoContact SET Field = $1,AmoID=$3 WHERE ID = $2; -- name: CreateLeadTarget :one INSERT INTO leadtarget (accountID,type,quizID,target) VALUES ($1,$2,$3,$4) RETURNING *; diff --git a/dal/schema/000014_init.down.sql b/dal/schema/000014_init.down.sql new file mode 100644 index 0000000..5ddb16f --- /dev/null +++ b/dal/schema/000014_init.down.sql @@ -0,0 +1,20 @@ +DROP TABLE IF EXISTS usersAmo; +DROP INDEX IF EXISTS idx_unique_accountsAmo; +DROP TABLE IF EXISTS accountsAmo; + +CREATE TABLE IF NOT EXISTS users ( + ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY, + AccountID VARCHAR(30) NOT NULL DEFAULT '', -- id квизе из токена + AmoID INT NOT NULL , -- id в амо + Name VARCHAR(512) NOT NULL DEFAULT '', -- имя в амо + Email VARCHAR(50) NOT NULL DEFAULT '', -- почта в амо + Role INT NOT NULL DEFAULT 0, -- роль в амо + "Group" INT NOT NULL DEFAULT 0, -- вложенная структура так как в амо группы хранятся массивом структур + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + Subdomain VARCHAR(50) NOT NULL DEFAULT '', + AmoUserID INT NOT NULL , -- id пользователя который подключал интеграцию + Country VARCHAR(50) NOT NULL DEFAULT '' -- страна в амо +); + +CREATE UNIQUE INDEX idx_unique_users ON users (amoID) WHERE Deleted = false; \ No newline at end of file diff --git a/dal/schema/000014_init.up.sql b/dal/schema/000014_init.up.sql new file mode 100644 index 0000000..9542bbb --- /dev/null +++ b/dal/schema/000014_init.up.sql @@ -0,0 +1,28 @@ +DROP TABLE IF EXISTS users; +DROP INDEX IF EXISTS idx_unique_users; + +CREATE TABLE IF NOT EXISTS accountsAmo ( + ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY, + AccountID VARCHAR(30) NOT NULL DEFAULT '', -- ID аккаунта у нас + AmoID INT NOT NULL, -- ID "компании" в амо + Name VARCHAR(512) NOT NULL DEFAULT '', + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + Subdomain VARCHAR(50) NOT NULL DEFAULT '', -- поддомен - пример https://penadigitaltech.amocrm.ru + Country VARCHAR(50) NOT NULL DEFAULT '', + DriveURL VARCHAR(255) NOT NULL DEFAULT '' -- URL объктного хранилища +); + +CREATE UNIQUE INDEX idx_unique_accountsAmo ON accountsAmo (amoID) WHERE Deleted = false; + +CREATE TABLE IF NOT EXISTS usersAmo ( + ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY, + AmoID INT NOT NULL, -- ID компании в амо (внешний ключ) + AmoUserID INT NOT NULL, -- ID пользователя в амо + Name VARCHAR(512) NOT NULL DEFAULT '', + Email VARCHAR(50) NOT NULL DEFAULT '', + Role INT NOT NULL DEFAULT 0, + "Group" INT NOT NULL DEFAULT 0, + Deleted BOOLEAN NOT NULL DEFAULT FALSE, + CreatedAt TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/dal/schema/000015_init.down.sql b/dal/schema/000015_init.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/dal/schema/000015_init.up.sql b/dal/schema/000015_init.up.sql new file mode 100644 index 0000000..3985517 --- /dev/null +++ b/dal/schema/000015_init.up.sql @@ -0,0 +1,19 @@ +UPDATE answer +SET content = + CASE + WHEN content ~ '|' THEN + regexp_replace(content, '<\/?tr[^>]*>|<\/?td[^>]*>', '', 'g') + WHEN content ~ '[^<]+' THEN + regexp_replace(content, '([^<]+)', '\1', 'g') + WHEN content ~ ']*src="([^"]*)"[^>]*' THEN + regexp_replace(content, ']*src="\s*"[^>]*', '', 'g') + ELSE content + END; + +UPDATE answer +SET content = + CASE + WHEN content ~ ']*src=["'']?([^"''>]+)["'']?[^>]*>)', '\1\3', 'g') + ELSE content + END; diff --git a/dal/schema/000016_init.down.sql b/dal/schema/000016_init.down.sql new file mode 100644 index 0000000..28b2333 --- /dev/null +++ b/dal/schema/000016_init.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS amoContact; \ No newline at end of file diff --git a/dal/schema/000016_init.up.sql b/dal/schema/000016_init.up.sql new file mode 100644 index 0000000..a4866d0 --- /dev/null +++ b/dal/schema/000016_init.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS amoContact ( + ID BIGSERIAL UNIQUE NOT NULL PRIMARY KEY, + AccountID INT NOT NULL, -- ID "компании" в амо + AmoID INT NOT NULL, -- ID контакта в амо + Field text NOT NULL DEFAULT '' -- значение чего то email? phone? etc +) \ No newline at end of file diff --git a/dal/sqlcgen/models.go b/dal/sqlcgen/models.go index 2bf4670..448fa8a 100644 --- a/dal/sqlcgen/models.go +++ b/dal/sqlcgen/models.go @@ -19,6 +19,25 @@ type Account struct { Deleted sql.NullBool `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"` @@ -182,18 +201,14 @@ type Token struct { Createdat sql.NullTime `db:"createdat" json:"createdat"` } -type User 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"` - 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 sql.NullTime `db:"createdat" json:"createdat"` - Subdomain string `db:"subdomain" json:"subdomain"` - Amouserid int32 `db:"amouserid" json:"amouserid"` - Country string `db:"country" json:"country"` - Driveurl string `db:"driveurl" json:"driveurl"` +type Usersamo struct { + ID int64 `db:"id" json:"id"` + Amoid int32 `db:"amoid" json:"amoid"` + Amouserid int32 `db:"amouserid" json:"amouserid"` + Name string `db:"name" json:"name"` + Email string `db:"email" json:"email"` + Role int32 `db:"role" json:"role"` + Group int32 `db:"Group" json:"Group"` + Deleted bool `db:"deleted" json:"deleted"` + Createdat time.Time `db:"createdat" json:"createdat"` } diff --git a/dal/sqlcgen/queries.sql.go b/dal/sqlcgen/queries.sql.go index c2f2ebc..736febf 100644 --- a/dal/sqlcgen/queries.sql.go +++ b/dal/sqlcgen/queries.sql.go @@ -53,6 +53,32 @@ func (q *Queries) AccountPagination(ctx context.Context, arg AccountPaginationPa return items, nil } +const addAmoAccountUser = `-- name: AddAmoAccountUser :exec +INSERT INTO usersAmo (AmoID, AmoUserID, Name, Email, Role, "Group") +VALUES ($1, $2, $3, $4, $5, $6) +` + +type AddAmoAccountUserParams struct { + 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"` +} + +func (q *Queries) AddAmoAccountUser(ctx context.Context, arg AddAmoAccountUserParams) error { + _, err := q.db.ExecContext(ctx, addAmoAccountUser, + arg.Amoid, + arg.Amouserid, + arg.Name, + arg.Email, + arg.Role, + arg.Group, + ) + return err +} + const allServiceStatistics = `-- name: AllServiceStatistics :one WITH Registrations AS ( SELECT COUNT(*) AS registration_count @@ -110,7 +136,7 @@ func (q *Queries) ArchiveQuiz(ctx context.Context, arg ArchiveQuizParams) error const changeQuizSettings = `-- name: ChangeQuizSettings :one UPDATE rules SET PerformerID = $1,PipelineID = $2,StepID = $3,FieldsRule = $4, TagsToAdd=$5 -WHERE AccountID = (SELECT AmoID FROM users WHERE users.AccountID = $6 AND users.Deleted = false) AND QuizID = $7 AND Deleted = false +WHERE AccountID = (SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $6 AND accountsAmo.Deleted = false) AND QuizID = $7 AND Deleted = false RETURNING id ` @@ -157,45 +183,27 @@ func (q *Queries) CheckAndAddDefault(ctx context.Context, arg CheckAndAddDefault return err } -const checkExpired = `-- name: CheckExpired :many -SELECT accountid, refreshtoken, accesstoken, authcode, expiration, createdat FROM tokens WHERE Expiration <= TO_TIMESTAMP(EXTRACT(EPOCH FROM NOW()) + (10 * 60)) +const checkExpiredToken = `-- name: CheckExpiredToken :one +SELECT accountid, refreshtoken, accesstoken, authcode, expiration, createdat FROM tokens WHERE AccountID = $1 AND Expiration <= NOW() ` -func (q *Queries) CheckExpired(ctx context.Context) ([]Token, error) { - rows, err := q.db.QueryContext(ctx, checkExpired) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Token - for rows.Next() { - var i Token - if err := rows.Scan( - &i.Accountid, - &i.Refreshtoken, - &i.Accesstoken, - &i.Authcode, - &i.Expiration, - &i.Createdat, - ); 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 +func (q *Queries) CheckExpiredToken(ctx context.Context, accountid string) (Token, error) { + row := q.db.QueryRowContext(ctx, checkExpiredToken, accountid) + var i Token + err := row.Scan( + &i.Accountid, + &i.Refreshtoken, + &i.Accesstoken, + &i.Authcode, + &i.Expiration, + &i.Createdat, + ) + return i, err } const checkFields = `-- name: CheckFields :many WITH user_data AS ( - SELECT AmoID - FROM users - WHERE users.AccountID = $1 + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ), new_fields AS ( SELECT (field->>'AmoID')::INT AS amoID, COALESCE(field->>'Code', '')::varchar(255) AS code, @@ -275,29 +283,6 @@ func (q *Queries) CheckFields(ctx context.Context, arg CheckFieldsParams) ([]Che return items, nil } -const checkMainUser = `-- name: CheckMainUser :exec -UPDATE users SET Name = $1, "Group" = $2, Email = $3, Role = $4 WHERE AmoID = $5 -` - -type CheckMainUserParams struct { - Name string `db:"name" json:"name"` - Group int32 `db:"Group" json:"Group"` - Email string `db:"email" json:"email"` - Role int32 `db:"role" json:"role"` - Amoid int32 `db:"amoid" json:"amoid"` -} - -func (q *Queries) CheckMainUser(ctx context.Context, arg CheckMainUserParams) error { - _, err := q.db.ExecContext(ctx, checkMainUser, - arg.Name, - arg.Group, - arg.Email, - arg.Role, - arg.Amoid, - ) - return err -} - const checkPipelines = `-- name: CheckPipelines :many WITH new_pipelines AS ( SELECT (pipeline->>'AmoID')::INT AS amoID, @@ -480,9 +465,7 @@ func (q *Queries) CheckSteps(ctx context.Context, dollar_1 json.RawMessage) ([]C const checkTags = `-- name: CheckTags :many WITH user_data AS ( - SELECT AmoID - FROM users - WHERE users.AccountID = $1 + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ), new_tags AS ( SELECT (tag->>'AmoID')::INT AS amoID, (tag->>'Entity')::entitytype AS Entity, @@ -555,79 +538,6 @@ func (q *Queries) CheckTags(ctx context.Context, arg CheckTagsParams) ([]CheckTa return items, nil } -const checkUsers = `-- name: CheckUsers :many -WITH new_users AS ( - SELECT (u->>'AmocrmID')::INT AS AmoID, - (u->>'Name')::VARCHAR(512) AS Name, - (u->>'Group')::INT AS "Group", - (u->>'Role')::INT AS Role, - (u->>'Email')::VARCHAR(50) AS Email, - (u->>'AmoUserID')::INT AS AmoUserID, - CURRENT_TIMESTAMP AS createdAt - FROM json_array_elements($1::json) AS u -), inserted_users AS ( - INSERT INTO users (AmoID, Name, "Group", Role, Email, AmoUserID,createdAt) - SELECT nu.AmoID, - nu.Name, - nu."Group", - nu.Role, - nu.Email, - nu.AmoUserID, - nu.createdAt - FROM new_users nu - ON CONFLICT (amoID) DO NOTHING - RETURNING id, accountid, amoid, name, email, role, "Group", deleted, createdat, subdomain, amouserid, country, driveurl -) -SELECT nu.amoid, nu.name, nu."Group", nu.role, nu.email, nu.amouserid, nu.createdat -FROM new_users nu -WHERE NOT EXISTS ( - SELECT id, accountid, amoid, name, email, role, "Group", deleted, createdat, subdomain, amouserid, country, driveurl - FROM inserted_users ins - WHERE ins.amoID = nu.amoID -) -` - -type CheckUsersRow struct { - Amoid int32 `db:"amoid" json:"amoid"` - Name string `db:"name" json:"name"` - Group int32 `db:"Group" json:"Group"` - Role int32 `db:"role" json:"role"` - Email string `db:"email" json:"email"` - Amouserid int32 `db:"amouserid" json:"amouserid"` - Createdat interface{} `db:"createdat" json:"createdat"` -} - -func (q *Queries) CheckUsers(ctx context.Context, dollar_1 json.RawMessage) ([]CheckUsersRow, error) { - rows, err := q.db.QueryContext(ctx, checkUsers, dollar_1) - if err != nil { - return nil, err - } - defer rows.Close() - var items []CheckUsersRow - for rows.Next() { - var i CheckUsersRow - if err := rows.Scan( - &i.Amoid, - &i.Name, - &i.Group, - &i.Role, - &i.Email, - &i.Amouserid, - &i.Createdat, - ); 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 copyQuestion = `-- name: CopyQuestion :one INSERT INTO question( quiz_id, title, description, questiontype, required, @@ -770,23 +680,17 @@ func (q *Queries) CreateAccount(ctx context.Context, arg CreateAccountParams) (A const createAmoAccount = `-- name: CreateAmoAccount :exec -INSERT INTO users (AccountID, AmoID, Name, Email, Role, "Group", Deleted, CreatedAt, Subdomain, AmoUserID, Country,DriveURL) -VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) +INSERT INTO accountsAmo (AccountID, AmoID,Name, Subdomain, Country,DriveURL) +VALUES ($1, $2, $3, $4, $5, $6) ` type CreateAmoAccountParams struct { - Accountid string `db:"accountid" json:"accountid"` - Amoid int32 `db:"amoid" json:"amoid"` - 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 sql.NullTime `db:"createdat" json:"createdat"` - Subdomain string `db:"subdomain" json:"subdomain"` - Amouserid int32 `db:"amouserid" json:"amouserid"` - Country string `db:"country" json:"country"` - Driveurl string `db:"driveurl" json:"driveurl"` + Accountid string `db:"accountid" json:"accountid"` + Amoid int32 `db:"amoid" json:"amoid"` + Name string `db:"name" json:"name"` + Subdomain string `db:"subdomain" json:"subdomain"` + Country string `db:"country" json:"country"` + Driveurl string `db:"driveurl" json:"driveurl"` } // amo methods: @@ -795,13 +699,7 @@ func (q *Queries) CreateAmoAccount(ctx context.Context, arg CreateAmoAccountPara arg.Accountid, arg.Amoid, arg.Name, - arg.Email, - arg.Role, - arg.Group, - arg.Deleted, - arg.Createdat, arg.Subdomain, - arg.Amouserid, arg.Country, arg.Driveurl, ) @@ -1034,7 +932,7 @@ func (q *Queries) DeleteTags(ctx context.Context, dollar_1 []int64) error { } const deleteUsers = `-- name: DeleteUsers :exec -UPDATE users SET Deleted = true WHERE ID = ANY($1::bigint[]) +UPDATE usersAmo SET Deleted = true WHERE ID = ANY($1::bigint[]) ` func (q *Queries) DeleteUsers(ctx context.Context, dollar_1 []int64) error { @@ -1486,6 +1384,43 @@ func (q *Queries) GetAllAnswersByQuizID(ctx context.Context, session sql.NullStr return items, nil } +const getAllCompanyUsers = `-- name: GetAllCompanyUsers :many +SELECT id, amoid, amouserid, name, email, role, "Group", deleted, createdat FROM usersamo WHERE amoid = $1 AND deleted = false +` + +func (q *Queries) GetAllCompanyUsers(ctx context.Context, amoid int32) ([]Usersamo, error) { + rows, err := q.db.QueryContext(ctx, getAllCompanyUsers, amoid) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Usersamo + for rows.Next() { + var i Usersamo + if err := rows.Scan( + &i.ID, + &i.Amoid, + &i.Amouserid, + &i.Name, + &i.Email, + &i.Role, + &i.Group, + &i.Deleted, + &i.Createdat, + ); 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 getAllTokens = `-- name: GetAllTokens :many SELECT accountid, refreshtoken, accesstoken, authcode, expiration, createdat FROM tokens ` @@ -1520,31 +1455,67 @@ func (q *Queries) GetAllTokens(ctx context.Context) ([]Token, error) { return items, nil } -const getCurrentAccount = `-- name: GetCurrentAccount :one -SELECT id, accountid, amoid, name, email, role, "Group", deleted, createdat, subdomain, amouserid, country, driveurl FROM users WHERE AccountID = $1 AND Deleted = false +const getCurrentCompany = `-- name: GetCurrentCompany :one +SELECT id, accountid, amoid, name, deleted, createdat, subdomain, country, driveurl FROM accountsAmo WHERE AccountID = $1 AND Deleted = false ` -func (q *Queries) GetCurrentAccount(ctx context.Context, accountid string) (User, error) { - row := q.db.QueryRowContext(ctx, getCurrentAccount, accountid) - var i User +func (q *Queries) GetCurrentCompany(ctx context.Context, accountid string) (Accountsamo, error) { + row := q.db.QueryRowContext(ctx, getCurrentCompany, accountid) + var i Accountsamo err := row.Scan( &i.ID, &i.Accountid, &i.Amoid, &i.Name, - &i.Email, - &i.Role, - &i.Group, &i.Deleted, &i.Createdat, &i.Subdomain, - &i.Amouserid, &i.Country, &i.Driveurl, ) return i, err } +const getExistingContactAmo = `-- name: GetExistingContactAmo :many +WITH getAmoID AS ( + SELECT AmoID FROM amoContact WHERE amoContact.AccountID = $1 AND amoContact.Field = ANY($2::text[]) +) SELECT id, accountid, amoid, field FROM amoContact +WHERE amoContact.AccountID = $1 AND amoContact.AmoID IN (SELECT AmoID FROM getAmoID) +` + +type GetExistingContactAmoParams struct { + Accountid int32 `db:"accountid" json:"accountid"` + Column2 []string `db:"column_2" json:"column_2"` +} + +func (q *Queries) GetExistingContactAmo(ctx context.Context, arg GetExistingContactAmoParams) ([]Amocontact, error) { + rows, err := q.db.QueryContext(ctx, getExistingContactAmo, arg.Accountid, pq.Array(arg.Column2)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Amocontact + for rows.Next() { + var i Amocontact + if err := rows.Scan( + &i.ID, + &i.Accountid, + &i.Amoid, + &i.Field, + ); 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 getExpiredCountPrivilege = `-- name: GetExpiredCountPrivilege :many SELECT p.id, p.privilegeID, p.privilege_name, p.amount, p.created_at, a.user_id FROM privileges p @@ -1661,8 +1632,11 @@ func (q *Queries) GetFieldByAmoID(ctx context.Context, amoid int32) (Field, erro } const getFieldsWithPagination = `-- name: GetFieldsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT f.id, f.amoid, f.code, f.accountid, f.name, f.entity, f.type, f.deleted, f.createdat, COUNT(*) OVER() as total_count -FROM fields f JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON f.AccountID = u.AmoID +FROM fields f JOIN user_data u ON f.AccountID = u.AmoID WHERE f.Deleted = false ORDER BY f.ID OFFSET ($2 - 1) * $3 LIMIT $3 ` @@ -1817,8 +1791,11 @@ func (q *Queries) GetListStartQuiz(ctx context.Context, accountid string) ([]int } const getPipelinesWithPagination = `-- name: GetPipelinesWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT p.id, p.amoid, p.accountid, p.name, p.isarchive, p.deleted, p.createdat, COUNT(*) OVER() as total_count -FROM pipelines p JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON p.AccountID = u.AmoID +FROM pipelines p JOIN user_data u ON p.AccountID = u.AmoID WHERE p.Deleted = false ORDER BY p.ID OFFSET ($2 - 1) * $3 LIMIT $3 ` @@ -2432,8 +2409,11 @@ func (q *Queries) GetResultAnswers(ctx context.Context, id int64) ([]GetResultAn } const getStepsWithPagination = `-- name: GetStepsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT s.id, s.amoid, s.pipelineid, s.accountid, s.name, s.color, s.deleted, s.createdat, COUNT(*) OVER() as total_count -FROM steps s JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON s.AccountID = u.AmoID +FROM steps s JOIN user_data u ON s.AccountID = u.AmoID WHERE s.Deleted = false AND PipelineID = $4 ORDER BY s.ID OFFSET ($2 - 1) * $3 LIMIT $3 ` @@ -2496,8 +2476,11 @@ func (q *Queries) GetStepsWithPagination(ctx context.Context, arg GetStepsWithPa } const getTagsWithPagination = `-- name: GetTagsWithPagination :many +WITH user_data AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false +) SELECT t.id, t.amoid, t.accountid, t.entity, t.name, t.color, t.deleted, t.createdat, COUNT(*) OVER() as total_count -FROM tags t JOIN (SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false) u ON t.AccountID = u.AmoID +FROM tags t JOIN user_data u ON t.AccountID = u.AmoID WHERE t.Deleted = false ORDER BY t.ID OFFSET ($2 - 1) * $3 LIMIT $3 ` @@ -2752,44 +2735,28 @@ func (q *Queries) GetUserTagsByID(ctx context.Context, accountid int32) ([]GetUs } const getUserUsersByID = `-- name: GetUserUsersByID :many -SELECT ID,AccountID,AmoID,Name,Email,Role,"Group",Subdomain,AmoUserID,Country -FROM users -WHERE AmoUserID = $1 AND Deleted = false +SELECT id, amoid, amouserid, name, email, role, "Group", deleted, createdat FROM usersAmo WHERE amoid = $1 AND Deleted = false ` -type GetUserUsersByIDRow 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"` - Email string `db:"email" json:"email"` - Role int32 `db:"role" json:"role"` - Group int32 `db:"Group" json:"Group"` - Subdomain string `db:"subdomain" json:"subdomain"` - Amouserid int32 `db:"amouserid" json:"amouserid"` - Country string `db:"country" json:"country"` -} - -func (q *Queries) GetUserUsersByID(ctx context.Context, amouserid int32) ([]GetUserUsersByIDRow, error) { - rows, err := q.db.QueryContext(ctx, getUserUsersByID, amouserid) +func (q *Queries) GetUserUsersByID(ctx context.Context, amoid int32) ([]Usersamo, error) { + rows, err := q.db.QueryContext(ctx, getUserUsersByID, amoid) if err != nil { return nil, err } defer rows.Close() - var items []GetUserUsersByIDRow + var items []Usersamo for rows.Next() { - var i GetUserUsersByIDRow + var i Usersamo if err := rows.Scan( &i.ID, - &i.Accountid, &i.Amoid, + &i.Amouserid, &i.Name, &i.Email, &i.Role, &i.Group, - &i.Subdomain, - &i.Amouserid, - &i.Country, + &i.Deleted, + &i.Createdat, ); err != nil { return nil, err } @@ -2806,11 +2773,11 @@ func (q *Queries) GetUserUsersByID(ctx context.Context, amouserid int32) ([]GetU const getUsersWithPagination = `-- name: GetUsersWithPagination :many WITH user_data AS ( - SELECT AmoID FROM users WHERE users.AccountID = $1 AND Deleted = false + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 AND accountsAmo.Deleted = false ) -SELECT u.id, u.accountid, u.amoid, u.name, u.email, u.role, u."Group", u.deleted, u.createdat, u.subdomain, u.amouserid, u.country, u.driveurl, COUNT(*) OVER() as total_count -FROM users u -JOIN user_data a ON u.AmoUserID = a.AmoID +SELECT u.id, u.amoid, u.amouserid, u.name, u.email, u.role, u."Group", u.deleted, u.createdat, COUNT(*) OVER() as total_count +FROM usersAmo u + JOIN user_data a ON u.AmoID = a.AmoID WHERE u.Deleted = false ORDER BY u.ID OFFSET ($2 - 1) * $3 LIMIT $3 ` @@ -2822,20 +2789,16 @@ type GetUsersWithPaginationParams struct { } type GetUsersWithPaginationRow 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"` - 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 sql.NullTime `db:"createdat" json:"createdat"` - Subdomain string `db:"subdomain" json:"subdomain"` - Amouserid int32 `db:"amouserid" json:"amouserid"` - Country string `db:"country" json:"country"` - Driveurl string `db:"driveurl" json:"driveurl"` - TotalCount int64 `db:"total_count" json:"total_count"` + 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"` + TotalCount int64 `db:"total_count" json:"total_count"` } func (q *Queries) GetUsersWithPagination(ctx context.Context, arg GetUsersWithPaginationParams) ([]GetUsersWithPaginationRow, error) { @@ -2849,18 +2812,14 @@ func (q *Queries) GetUsersWithPagination(ctx context.Context, arg GetUsersWithPa var i GetUsersWithPaginationRow if err := rows.Scan( &i.ID, - &i.Accountid, &i.Amoid, + &i.Amouserid, &i.Name, &i.Email, &i.Role, &i.Group, &i.Deleted, &i.Createdat, - &i.Subdomain, - &i.Amouserid, - &i.Country, - &i.Driveurl, &i.TotalCount, ); err != nil { return nil, err @@ -2878,17 +2837,17 @@ func (q *Queries) GetUsersWithPagination(ctx context.Context, arg GetUsersWithPa const gettingAmoUsersTrueResults = `-- name: GettingAmoUsersTrueResults :many SELECT a.quiz_id,a.id,a.result,a.question_id,a.content,a.session, - (SELECT a2.utm + COALESCE((SELECT a2.utm FROM answer a2 WHERE a2.start = true AND a2.session = a.session - LIMIT 1) AS utm -,t.accesstoken,r.accountid,r.fieldsrule,r.tagstoadd,r.performerid,r.stepid,r.pipelineid,(SELECT u.name FROM users u WHERE u.amoid = r.performerid) AS performer_name,u.subdomain,u.accountid,u.driveurl + LIMIT 1), '{}'::jsonb) AS utm +,t.accesstoken,r.accountid,r.fieldsrule,r.tagstoadd,r.performerid,r.stepid,r.pipelineid,(SELECT u.name FROM usersAmo u WHERE u.AmoUserID = r.performerid AND u.deleted = false) AS performer_name,u.subdomain,u.accountid,u.driveurl FROM answer a INNER JOIN quiz q ON a.quiz_id = q.id LEFT JOIN amoCRMStatuses s ON a.id = s.AnswerID INNER JOIN rules r ON q.id = r.QuizID INNER JOIN tokens t ON q.accountid = t.AccountID - INNER JOIN users u ON q.accountid = u.accountid AND u.amoid = r.accountid + INNER JOIN accountsAmo u ON q.accountid = u.accountid AND u.amoid = r.accountid WHERE a.result = true AND s.id IS NULL AND a.deleted = false @@ -2904,7 +2863,7 @@ type GettingAmoUsersTrueResultsRow struct { QuestionID int64 `db:"question_id" json:"question_id"` Content sql.NullString `db:"content" json:"content"` Session sql.NullString `db:"session" json:"session"` - Utm json.RawMessage `db:"utm" json:"utm"` + Utm interface{} `db:"utm" json:"utm"` Accesstoken string `db:"accesstoken" json:"accesstoken"` Accountid int32 `db:"accountid" json:"accountid"` Fieldsrule json.RawMessage `db:"fieldsrule" json:"fieldsrule"` @@ -3038,6 +2997,23 @@ func (q *Queries) InsertAnswers(ctx context.Context, arg InsertAnswersParams) (A return i, err } +const insertContactAmo = `-- name: InsertContactAmo :one +INSERT INTO amoContact (AccountID, AmoID, Field) VALUES ($1, $2, $3) RETURNING AmoID +` + +type InsertContactAmoParams struct { + Accountid int32 `db:"accountid" json:"accountid"` + Amoid int32 `db:"amoid" json:"amoid"` + Field string `db:"field" json:"field"` +} + +func (q *Queries) InsertContactAmo(ctx context.Context, arg InsertContactAmoParams) (int32, error) { + row := q.db.QueryRowContext(ctx, insertContactAmo, arg.Accountid, arg.Amoid, arg.Field) + var amoid int32 + err := row.Scan(&amoid) + return amoid, err +} + const insertPrivilege = `-- name: InsertPrivilege :exec INSERT INTO privileges (privilegeID, account_id, privilege_name, amount, created_at) VALUES ($1, $2, $3, $4, $5) ` @@ -3439,7 +3415,7 @@ func (q *Queries) QuizCopyQid(ctx context.Context, arg QuizCopyQidParams) (QuizC const setQuizSettings = `-- name: SetQuizSettings :one INSERT INTO rules (AccountID, QuizID, PerformerID, PipelineID, StepID, FieldsRule,TagsToAdd) SELECT u.AmoID AS AccountID,$1 AS QuizID,$2 AS PerformerID,$3 AS PipelineID, - $4 AS StepID,$5 AS FieldsRule,$6 AS TagsToAdd FROM users u WHERE u.AccountID = $7 AND u.Deleted = false + $4 AS StepID,$5 AS FieldsRule,$6 AS TagsToAdd FROM accountsamo u WHERE u.AccountID = $7 AND u.Deleted = false RETURNING id ` @@ -3472,7 +3448,7 @@ const settingDealAmoStatus = `-- name: SettingDealAmoStatus :exec INSERT INTO amoCRMStatuses (AccountID, DealID, AnswerID, Status) SELECT u.AmoID, $1, $2, $3 FROM tokens AS t - JOIN users AS u ON t.AccountID = u.AccountID + JOIN accountsAmo AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $4 AND u.Deleted = false ` @@ -3494,12 +3470,13 @@ func (q *Queries) SettingDealAmoStatus(ctx context.Context, arg SettingDealAmoSt } const softDeleteAccount = `-- name: SoftDeleteAccount :exec -WITH userd AS ( - SELECT AmoUserID FROM users WHERE users.AccountID = $1 -), - tokend AS ( - UPDATE users SET Deleted = true WHERE AmoUserID IN (SELECT AmoUserID FROM userd) - ) +WITH amoCompany AS ( + SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $1 +),usersDel AS ( + UPDATE usersAmo SET Deleted = true WHERE AmoID = (SELECT AmoID FROM amoCompany) + ), + companyDel AS ( UPDATE accountsAmo SET Deleted = true WHERE AmoID = (SELECT AmoID FROM amoCompany) + ) DELETE FROM tokens WHERE tokens.AccountID = $1 ` @@ -3545,9 +3522,73 @@ func (q *Queries) TemplateCopy(ctx context.Context, arg TemplateCopyParams) (int return quiz_id, err } +const updateAmoAccount = `-- name: UpdateAmoAccount :exec +UPDATE accountsAmo SET Name = $2, Subdomain = $3, Country = $4, DriveURL = $5 WHERE AccountID = $1 AND Deleted = false +` + +type UpdateAmoAccountParams struct { + Accountid string `db:"accountid" json:"accountid"` + Name string `db:"name" json:"name"` + Subdomain string `db:"subdomain" json:"subdomain"` + Country string `db:"country" json:"country"` + Driveurl string `db:"driveurl" json:"driveurl"` +} + +func (q *Queries) UpdateAmoAccount(ctx context.Context, arg UpdateAmoAccountParams) error { + _, err := q.db.ExecContext(ctx, updateAmoAccount, + arg.Accountid, + arg.Name, + arg.Subdomain, + arg.Country, + arg.Driveurl, + ) + return err +} + +const updateAmoAccountUser = `-- name: UpdateAmoAccountUser :exec +UPDATE usersAmo SET Name = $3, Email = $4, Role = $5, "Group" = $6 +WHERE AmoID = $1 AND AmoUserID = $2 AND deleted = false +` + +type UpdateAmoAccountUserParams struct { + 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"` +} + +func (q *Queries) UpdateAmoAccountUser(ctx context.Context, arg UpdateAmoAccountUserParams) error { + _, err := q.db.ExecContext(ctx, updateAmoAccountUser, + arg.Amoid, + arg.Amouserid, + arg.Name, + arg.Email, + arg.Role, + arg.Group, + ) + return err +} + +const updateAmoContact = `-- name: UpdateAmoContact :exec +UPDATE amoContact SET Field = $1,AmoID=$3 WHERE ID = $2 +` + +type UpdateAmoContactParams struct { + Field string `db:"field" json:"field"` + ID int64 `db:"id" json:"id"` + Amoid int32 `db:"amoid" json:"amoid"` +} + +func (q *Queries) UpdateAmoContact(ctx context.Context, arg UpdateAmoContactParams) error { + _, err := q.db.ExecContext(ctx, updateAmoContact, arg.Field, arg.ID, arg.Amoid) + return err +} + const updateFieldRules = `-- name: UpdateFieldRules :exec UPDATE rules SET FieldsRule = $1 -WHERE AccountID = (SELECT AmoID FROM users WHERE users.AccountID = $2 AND users.Deleted = false) AND QuizID = $3 AND Deleted = false +WHERE AccountID = (SELECT AmoID FROM accountsAmo WHERE accountsAmo.AccountID = $2 AND accountsAmo.Deleted = false) AND QuizID = $3 AND Deleted = false ` type UpdateFieldRulesParams struct { @@ -3680,25 +3721,9 @@ func (q *Queries) UpdateTags(ctx context.Context, dollar_1 json.RawMessage) erro return err } -const updateUsers = `-- name: UpdateUsers :exec -UPDATE users AS u -SET Name = (update_data ->> 'Name')::varchar(512), - Email = (update_data ->> 'Email')::varchar(50), - Role = (update_data ->> 'Role')::INT, - "Group" = (update_data ->> 'Group')::INT, - AmoUserID= (update_data ->> 'AmoUserID')::INT -FROM json_array_elements($1::json) AS update_data -WHERE u.AmoID = (update_data ->> 'AmocrmID')::INT -` - -func (q *Queries) UpdateUsers(ctx context.Context, dollar_1 json.RawMessage) error { - _, err := q.db.ExecContext(ctx, updateUsers, dollar_1) - return err -} - const updatingDealAmoStatus = `-- name: UpdatingDealAmoStatus :exec UPDATE amoCRMStatuses SET Status = $1 -WHERE DealID = $2 AND AccountID = (SELECT u.AmoID FROM tokens AS t JOIN users AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $3 AND u.Deleted = false) +WHERE DealID = $2 AND AccountID = (SELECT u.AmoID FROM tokens AS t JOIN accountsAmo AS u ON t.AccountID = u.AccountID WHERE t.AccessToken = $3 AND u.Deleted = false) ` type UpdatingDealAmoStatusParams struct { @@ -3713,14 +3738,17 @@ func (q *Queries) UpdatingDealAmoStatus(ctx context.Context, arg UpdatingDealAmo } const webhookDelete = `-- name: WebhookDelete :exec -WITH userd AS ( - UPDATE users SET Deleted = true WHERE AmoUserID = $1 RETURNING AccountID +WITH companyDel AS ( + UPDATE accountsAmo SET Deleted = true WHERE accountsAmo.AmoID = $1 RETURNING AccountID +), +userDel AS ( +UPDATE usersAmo SET Deleted = true WHERE AmoID = $1 ) -DELETE FROM tokens WHERE AccountID IN (SELECT AccountID FROM userd) +DELETE FROM tokens WHERE AccountID IN (SELECT AccountID FROM companyDel) ` -func (q *Queries) WebhookDelete(ctx context.Context, amouserid int32) error { - _, err := q.db.ExecContext(ctx, webhookDelete, amouserid) +func (q *Queries) WebhookDelete(ctx context.Context, amoid int32) error { + _, err := q.db.ExecContext(ctx, webhookDelete, amoid) return err } diff --git a/go.mod b/go.mod index b30afc3..f5c6edf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module penahub.gitlab.yandexcloud.net/backend/quiz/common.git go 1.21.4 require ( + github.com/ClickHouse/clickhouse-go v1.5.4 github.com/gofiber/fiber/v2 v2.52.0 github.com/golang-jwt/jwt/v5 v5.2.0 github.com/golang/protobuf v1.5.3 @@ -19,6 +20,7 @@ require ( require ( github.com/andybalholm/brotli v1.0.5 // indirect + github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/go-cmp v0.6.0 // indirect diff --git a/go.sum b/go.sum index e82a594..d047566 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,19 @@ +github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= +github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= +github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= +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/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/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= @@ -20,6 +27,7 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= @@ -32,6 +40,7 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -41,6 +50,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/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.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0= @@ -52,6 +62,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +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/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= diff --git a/model/amo.go b/model/amo.go index b5bf0ea..5a0dae0 100644 --- a/model/amo.go +++ b/model/amo.go @@ -1,32 +1,30 @@ package model -type User struct { - /* - айдишник в нашей системе Primary Key*/ - ID int64 `json:"ID"` - /* - id пользователя из токена в нашей системе*/ - Accountid string `json:"AccountID"` - /* - айдишник пользователя в амо*/ - AmoID int32 `json:"AmocrmID"` - /* - имя аккаунта в амо*/ - Name string `json:"Name"` - /* - почта пользователя из амо*/ - Email string `json:"Email"` - /* - роль пользователя в амо*/ - Role int32 `json:"Role"` - /* - группы пользователя в амо*/ - Group int32 `json:"Group"` - /* - флаг мягкого удаления*/ - Deleted bool `json:"Deleted"` - /* - таймштамп создания аккаунта*/ - Createdat int64 `json:"CreatedAt"` - /* - поддомен организации в амо*/ - Subdomain string `json:"Subdomain"` - /* - айдишник пользвателя, который подключал интеграцию*/ - Amouserid int32 `json:"AmoUserID"` - /* - страна указанная в настройках амо*/ - Country string `json:"Country"` - // урл объектного хранилища пользователя в амо - DriveURL string `json:"DriveURL"` +import "time" + +type AmoAccountUser struct { + ID int64 `json:"id"` // ID пользователя + AmoID int32 `json:"amoID"` // ID компании в амо, к которой пользователь принадлежит + AmoUserID int32 `json:"amoUserID"` // ID пользователя в амо + Name string `json:"name"` // Имя + Email string `json:"email"` // Email + Role int32 `json:"role"` // Роль + Group int32 `json:"group"` // Группа + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"createdAt"` +} + +type AmoAccount struct { + ID int64 `json:"id"` // ID компании + AccountID string `json:"accountID"` // ID аккаунта нас + AmoID int32 `json:"amoID"` // ID компании в амо + Name string `json:"name"` // Название + Deleted bool `json:"deleted"` + CreatedAt time.Time `json:"createdAt"` + Subdomain string `json:"subdomain"` // поддомен + Country string `json:"country"` // Страна + DriveURL string `json:"driveURL"` // URL объктного хранилища + Stale bool `json:"stale"` // флаг "не свежести" если с токенами все в порядке - false, если просрочились то true } type UserGroups struct { @@ -290,3 +288,11 @@ type AmoUsersTrueResults struct { QuizAccountID string DriveURL string } + +// возможно стоит добавить enum? тип ContactQuizConfig уже есть +type ContactAmo struct { + ID int64 + AccountID int32 // id аккаунта в амо к которому привязан контакт + AmoID int32 // id контакта в амо + Field string // значение поля +} diff --git a/model/amoResp.go b/model/amoResp.go index fd49975..1a65002 100644 --- a/model/amoResp.go +++ b/model/amoResp.go @@ -55,7 +55,7 @@ type UserListResp struct { /* - общее количество юзеров, которые у нас закешированы для этого пользователя*/ Count int64 `json:"count"` /* - список юзеров, которые были закешированы нашим сервисом*/ - Items []User `json:"items"` + Items []AmoAccountUser `json:"items"` } type UserListStepsResp struct { diff --git a/repository/amo/amo.go b/repository/amo/amo.go index 76c0cb6..63e2e3d 100644 --- a/repository/amo/amo.go +++ b/repository/amo/amo.go @@ -6,6 +6,7 @@ import ( "encoding/json" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal/sqlcgen" "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/pj_errors" "time" ) @@ -39,20 +40,18 @@ func (r *AmoRepository) GettingUserWithPagination(ctx context.Context, req *mode return nil, err } var count int64 - var users []model.User + var users []model.AmoAccountUser for _, row := range rows { - user := model.User{ + user := model.AmoAccountUser{ ID: row.ID, - Accountid: row.Accountid, AmoID: row.Amoid, + AmoUserID: row.Amouserid, Name: row.Name, Email: row.Email, Group: row.Group, Role: row.Role, - Createdat: row.Createdat.Time.Unix(), - Subdomain: row.Subdomain, - Amouserid: row.Amouserid, - Country: row.Country, + Deleted: row.Deleted, + CreatedAt: row.Createdat, } count = row.TotalCount @@ -75,41 +74,58 @@ func (r *AmoRepository) SoftDeleteAccount(ctx context.Context, accountID string) return nil } -func (r *AmoRepository) GetCurrentAccount(ctx context.Context, accountID string) (*model.User, error) { - row, err := r.queries.GetCurrentAccount(ctx, accountID) +func (r *AmoRepository) GetCurrentAccount(ctx context.Context, accountID string) (*model.AmoAccount, error) { + row, err := r.queries.GetCurrentCompany(ctx, accountID) if err != nil { return nil, err } - user := model.User{ + user := model.AmoAccount{ ID: row.ID, - Accountid: row.Accountid, + AccountID: row.Accountid, AmoID: row.Amoid, Name: row.Name, - Email: row.Email, - Role: row.Role, - Group: row.Group, - Createdat: row.Createdat.Time.Unix(), + Deleted: row.Deleted, + CreatedAt: row.Createdat, Subdomain: row.Subdomain, - Amouserid: row.Amouserid, Country: row.Country, DriveURL: row.Driveurl, } + _, err = r.queries.CheckExpiredToken(ctx, accountID) + if err != nil { + if err == sql.ErrNoRows { + user.Stale = false + return &user, nil + } + return nil, err + } + + user.Stale = true + return &user, nil } -func (r *AmoRepository) CreateAccount(ctx context.Context, accountID string, userInfo model.User) error { +func (r *AmoRepository) UpdateCurrentAccount(ctx context.Context, user model.AmoAccount) error { + err := r.queries.UpdateAmoAccount(ctx, sqlcgen.UpdateAmoAccountParams{ + Accountid: user.AccountID, + Name: user.Name, + Subdomain: user.Subdomain, + Country: user.Country, + Driveurl: user.DriveURL, + }) + if err != nil { + return err + } + return nil +} + +func (r *AmoRepository) CreateAccount(ctx context.Context, userInfo model.AmoAccount) error { err := r.queries.CreateAmoAccount(ctx, sqlcgen.CreateAmoAccountParams{ - Accountid: accountID, + Accountid: userInfo.AccountID, Amoid: userInfo.AmoID, Name: userInfo.Name, - Email: userInfo.Email, - Role: userInfo.Role, - Group: userInfo.Group, - Createdat: sql.NullTime{Time: time.Now(), Valid: true}, Subdomain: userInfo.Subdomain, - Amouserid: userInfo.Amouserid, Country: userInfo.Country, Driveurl: userInfo.DriveURL, }) @@ -121,15 +137,17 @@ func (r *AmoRepository) CreateAccount(ctx context.Context, accountID string, use return nil } -func (r *AmoRepository) CheckMainUser(ctx context.Context, user model.User) error { - err := r.queries.CheckMainUser(ctx, sqlcgen.CheckMainUserParams{ - Name: user.Name, - Group: user.Group, - Email: user.Email, - Role: user.Role, - Amoid: user.AmoID, - }) +// todo возможно стоит обновлять еще и компанию пока не знаю +func (r *AmoRepository) AddAmoAccountUser(ctx context.Context, user model.AmoAccountUser) error { + err := r.queries.AddAmoAccountUser(ctx, sqlcgen.AddAmoAccountUserParams{ + Amoid: user.AmoID, + Amouserid: user.AmoUserID, + Name: user.Name, + Email: user.Email, + Role: user.Role, + Group: user.Group, + }) if err != nil { return err } @@ -137,39 +155,18 @@ func (r *AmoRepository) CheckMainUser(ctx context.Context, user model.User) erro return nil } -func (r *AmoRepository) CheckAndUpdateUsers(ctx context.Context, users []model.User) error { - dollar1, err := json.Marshal(users) +func (r *AmoRepository) UpdateAmoAccountUser(ctx context.Context, user model.AmoAccountUser) error { + err := r.queries.UpdateAmoAccountUser(ctx, sqlcgen.UpdateAmoAccountUserParams{ + Amoid: user.AmoID, + Amouserid: user.AmoUserID, + Name: user.Name, + Email: user.Email, + Role: user.Role, + Group: user.Group, + }) if err != nil { return err } - rows, err := r.queries.CheckUsers(ctx, dollar1) - if err != nil { - return err - } - - if rows != nil { - var toUpdate []model.User - for _, row := range rows { - to := model.User{ - AmoID: row.Amoid, - Name: row.Name, - Group: row.Group, - Role: row.Role, - Email: row.Email, - Amouserid: row.Amouserid, - } - toUpdate = append(toUpdate, to) - } - dollar1, err := json.Marshal(toUpdate) - if err != nil { - return err - } - - err = r.queries.UpdateUsers(ctx, dollar1) - if err != nil { - return err - } - } return nil } @@ -195,27 +192,25 @@ func (r *AmoRepository) DeleteUsers(ctx context.Context, ids []int64) error { return nil } -func (r *AmoRepository) GetUserUsersByID(ctx context.Context, amoUserID int32) ([]model.User, error) { +func (r *AmoRepository) GetUserUsersByID(ctx context.Context, amoUserID int32) ([]model.AmoAccountUser, error) { rows, err := r.queries.GetUserUsersByID(ctx, amoUserID) if err != nil { return nil, err } - var users []model.User + var users []model.AmoAccountUser for _, row := range rows { - user := model.User{ + user := model.AmoAccountUser{ ID: row.ID, - Accountid: row.Accountid, AmoID: row.Amoid, + AmoUserID: row.Amouserid, Name: row.Name, Email: row.Email, Group: row.Group, Role: row.Role, - Subdomain: row.Subdomain, - Amouserid: row.Amouserid, - Country: row.Country, + Deleted: row.Deleted, + CreatedAt: row.Createdat, } - users = append(users, user) } @@ -256,31 +251,6 @@ func (r *AmoRepository) WebhookUpdate(ctx context.Context, token model.Token) er return nil } -// воркер запускается каждые 5 минут, поэтомму ищем токены котторые исекают менее чем через 10 минут отдаем их на обноление -func (r *AmoRepository) CheckExpired(ctx context.Context) ([]model.Token, error) { - rows, err := r.queries.CheckExpired(ctx) - if err != nil { - return nil, err - } - - var tokens []model.Token - - for _, row := range rows { - token := model.Token{ - AccountID: row.Accountid, - AccessToken: row.Accesstoken, - RefreshToken: row.Refreshtoken, - AuthCode: row.Authcode, - Expiration: row.Expiration.Unix(), - CreatedAt: row.Createdat.Time.Unix(), - } - - tokens = append(tokens, token) - } - - return tokens, nil -} - func (r *AmoRepository) GetAllTokens(ctx context.Context) ([]model.Token, error) { rows, err := r.queries.GetAllTokens(ctx) if err != nil { @@ -1038,7 +1008,7 @@ func (r *AmoRepository) GettingAmoUsersTrueResults(ctx context.Context) ([]model } var utm model.UTMSavingMap - err = json.Unmarshal(row.Utm, &utm) + err = json.Unmarshal(row.Utm.([]byte), &utm) if err != nil { return nil, err } @@ -1111,3 +1081,60 @@ func (r *AmoRepository) UpdatingDealAmoStatus(ctx context.Context, deps SaveDeal return nil } + +// методы для contact в амо + +func (r *AmoRepository) GetExistingContactAmo(ctx context.Context, accountID int32, fields []string) (map[int32][]model.ContactAmo, error) { + rows, err := r.queries.GetExistingContactAmo(ctx, sqlcgen.GetExistingContactAmoParams{ + Accountid: accountID, + Column2: fields, + }) + + if err != nil { + if err == sql.ErrNoRows { + return nil, pj_errors.ErrNotFound + } + return nil, err + } + + result := make(map[int32][]model.ContactAmo) + + for _, row := range rows { + result[row.Amoid] = append(result[row.Amoid], model.ContactAmo{ + ID: row.ID, + AmoID: row.Amoid, + AccountID: row.Accountid, + Field: row.Field, + }) + } + + return result, nil +} + +func (r *AmoRepository) InsertContactAmo(ctx context.Context, val model.ContactAmo) (int32, error) { + amoID, err := r.queries.InsertContactAmo(ctx, sqlcgen.InsertContactAmoParams{ + Accountid: val.AccountID, + Amoid: val.AmoID, + Field: val.Field, + }) + + if err != nil { + return 0, err + } + + return amoID, err +} + +func (r *AmoRepository) UpdateAmoContact(ctx context.Context, id int64, field string, newAmoID int32) error { + err := r.queries.UpdateAmoContact(ctx, sqlcgen.UpdateAmoContactParams{ + Field: field, + ID: id, + Amoid: newAmoID, + }) + + if err != nil { + return err + } + + return nil +} diff --git a/repository/answer/answer.go b/repository/answer/answer.go index c473ce0..893dbb9 100644 --- a/repository/answer/answer.go +++ b/repository/answer/answer.go @@ -15,16 +15,14 @@ type Deps struct { } type AnswerRepository struct { - queries *sqlcgen.Queries - pool *sql.DB - answerMinio *StorerAnswer + queries *sqlcgen.Queries + pool *sql.DB } func NewAnswerRepository(deps Deps) *AnswerRepository { return &AnswerRepository{ - queries: deps.Queries, - pool: deps.Pool, - answerMinio: deps.AnswerMinio, + queries: deps.Queries, + pool: deps.Pool, } } @@ -111,17 +109,6 @@ func (r *AnswerRepository) GetAllAnswersByQuizID(ctx context.Context, session st } for _, row := range rows { - //todo тут забыл добавить проверку на то что minio !=nil - /*if row.Questiontype == model.TypeFile { - fmt.Println("GALL", row.Qid, row.QuestionID, row.Content) - fileURL, err := r.answerMinio.GetAnswerURL(ctx, row.Qid.UUID.String(), row.QuestionID, row.Content.String) - if err != nil { - fmt.Println("GetAnswerURL dal answer minio answer", err) - return nil, err - } - row.Content = sql.NullString{String: fmt.Sprintf("%s|%s", fileURL, row.Content.String), Valid: true} - }*/ - resultAnswer := model.ResultAnswer{ Content: row.Content.String, CreatedAt: row.CreatedAt.Time, diff --git a/repository/answer/worker_answer.go b/repository/answer/worker_answer.go new file mode 100644 index 0000000..3bd1995 --- /dev/null +++ b/repository/answer/worker_answer.go @@ -0,0 +1,55 @@ +package answer + +import ( + "context" + "database/sql" + "fmt" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal/sqlcgen" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model" +) + +type WorkerAnswerRepository struct { + queries *sqlcgen.Queries + pool *sql.DB + answerMinio *StorerAnswer +} + +func NewWorkerAnswerRepo(deps Deps) *WorkerAnswerRepository { + return &WorkerAnswerRepository{ + queries: deps.Queries, + pool: deps.Pool, + answerMinio: deps.AnswerMinio, + } +} + +func (r *WorkerAnswerRepository) GetAllAnswersByQuizID(ctx context.Context, session string) ([]model.ResultAnswer, error) { + var results []model.ResultAnswer + + rows, err := r.queries.GetAllAnswersByQuizID(ctx, sql.NullString{String: session, Valid: true}) + if err != nil { + return nil, err + } + + for _, row := range rows { + if row.Questiontype == model.TypeFile && r.answerMinio != nil { + fmt.Println("GALL", row.Qid, row.QuestionID, row.Content) + fileURL, err := r.answerMinio.GetAnswerURL(ctx, row.Qid.UUID.String(), row.QuestionID, row.Content.String) + if err != nil { + fmt.Println("GetAnswerURL dal answer minio answer", err) + return nil, err + } + row.Content = sql.NullString{String: fmt.Sprintf("%s|%s", fileURL, row.Content.String), Valid: true} + } + + resultAnswer := model.ResultAnswer{ + Content: row.Content.String, + CreatedAt: row.CreatedAt.Time, + QuestionID: uint64(row.QuestionID), + AnswerID: uint64(row.ID), + } + + results = append(results, resultAnswer) + } + + return results, nil +} diff --git a/repository/statistics/click_statistics.go b/repository/statistics/click_statistics.go new file mode 100644 index 0000000..91061c2 --- /dev/null +++ b/repository/statistics/click_statistics.go @@ -0,0 +1,199 @@ +package statistics + +import ( + "context" + "database/sql" + "fmt" + "penahub.gitlab.yandexcloud.net/backend/quiz/common.git/utils" + "sort" + "strings" +) + +type DepsClick struct { + Conn *sql.DB +} + +type StatisticClick struct { + conn *sql.DB +} + +func NewClickStatistic(ctx context.Context, deps DepsClick) (*StatisticClick, error) { + s := &StatisticClick{ + conn: deps.Conn, + } + + err := s.checkMW(ctx) + if err != nil { + fmt.Println("error check material view existing", err) + return nil, err + } + + return s, nil +} + +func (s *StatisticClick) checkMW(ctx context.Context) error { + query := ` +CREATE MATERIALIZED VIEW IF NOT EXISTS mv_last_answers_events +ENGINE = MergeTree() +ORDER BY (ctxsession, event_time) POPULATE AS +SELECT + event_time, ctxsession, ctxquizid, ctxquestionid, ctxidint, message,ctxquiz +FROM statistics +WHERE message IN ('InfoQuizOpen', 'InfoAnswer', 'InfoResult') AND event_level = 'info'; +` + _, err := s.conn.ExecContext(ctx, query) + if err != nil { + return err + } + return nil +} + +type Statistic struct { + Count int64 + QuestionID int64 +} + +type PipeLineStatsResp map[int64][]Statistic + +// пример: +//"[0, 116783, 116810]" +//"[0, 116783, 116798]" +//"[0, 116783, 116798, 116831]" +//"[0, 116783, 116810, 116849]" +//[0] +//"[0, 116783]" +//"[0, 116783, 116810, 116843]" + +//SELECT DISTINCT last_que, reversed +//FROM ( SELECT groupArray(ctxquestionid) AS reversed, arraySlice(arrayReverse(groupArray(ctxquestionid)), 1, 1)[1] AS last_que +//FROM statistics WHERE ctxquizid = 26276 GROUP BY ctxsession ) AS sub; + +func (s *StatisticClick) getFunnel(ctx context.Context, quizID int64, from uint64, to uint64) (map[int64][]int64, error) { + query := ` + SELECT DISTINCT last_que, reversed + FROM ( SELECT groupUniqArray(ctxquestionid) AS reversed, arraySlice(arrayReverse(groupArray(ctxquestionid)), 1, 1)[1] AS last_que + FROM mv_last_answers_events WHERE ctxquizid = ? AND event_time BETWEEN ? AND ? GROUP BY ctxsession ) AS sub; + ` + + rows, err := s.conn.QueryContext(ctx, query, quizID, from, to) + if err != nil { + return nil, err + } + defer rows.Close() + + funnel := make(map[int64][]int64) + for rows.Next() { + var lastQue int64 + var reversed []int64 + if err := rows.Scan(&lastQue, &reversed); err != nil { + return nil, err + } + funnel[lastQue] = reversed + } + + if err := rows.Err(); err != nil { + return nil, err + } + + result := make(map[int64][]int64) + keys := make([]int64, 0, len(funnel)) + for key := range funnel { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, lastQue := range keys { + reversed := funnel[lastQue] + found := false + + for _, otherLastQue := range keys { + if otherLastQue != lastQue { + otherReversed := funnel[otherLastQue] + sort.Slice(otherReversed, func(i, j int) bool { + return otherReversed[i] < otherReversed[j] + }) + index := utils.BinarySearch(lastQue, otherReversed) + if index { + found = true + break + } + } + } + + if !found { + result[lastQue] = reversed + } + } + + return result, nil +} + +func (s *StatisticClick) GetPipelinesStatistics(ctx context.Context, quizID int64, from uint64, to uint64) (PipeLineStatsResp, error) { + pipelines := make(PipeLineStatsResp) + + funnel, err := s.getFunnel(ctx, quizID, from, to) + if err != nil { + return nil, err + } + for lastQue, idS := range funnel { + sesCount, err := s.countSession(ctx, quizID, from, to, idS) + if err != nil { + return nil, err + } + for _, queID := range idS { + if sessionCount, ok := sesCount[queID]; ok { + pipeline := Statistic{ + QuestionID: queID, + Count: sessionCount, + } + pipelines[lastQue] = append(pipelines[lastQue], pipeline) + } + } + } + return pipelines, nil +} + +func (s *StatisticClick) countSession(ctx context.Context, quizID int64, from uint64, to uint64, questionIDs []int64) (map[int64]int64, error) { + placeholders := make([]string, len(questionIDs)) + args := make([]interface{}, len(questionIDs)+3) + args[0] = quizID + for i, id := range questionIDs { + placeholders[i] = "?" + args[i+1] = id + } + args[len(args)-2] = from + args[len(args)-1] = to + + query := fmt.Sprintf(` + SELECT ctxquestionid, COUNT(DISTINCT ctxsession) AS session_count + FROM statistics + WHERE ctxquizid = ? AND ctxquestionid IN (%s) AND event_time BETWEEN ? AND ? + GROUP BY ctxquestionid; + `, strings.Join(placeholders, ",")) + + rows, err := s.conn.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + + counts := make(map[int64]int64) + + for rows.Next() { + var questionID int64 + var count int64 + err := rows.Scan(&questionID, &count) + if err != nil { + return nil, err + } + counts[questionID] = count + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return counts, nil +} diff --git a/sqlc.yaml b/sqlc.yaml index 8aac75c..031ec0e 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -30,6 +30,10 @@ packages: - "./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" engine: "postgresql" diff --git a/utils/binary_search.go b/utils/binary_search.go new file mode 100644 index 0000000..8e91039 --- /dev/null +++ b/utils/binary_search.go @@ -0,0 +1,20 @@ +package utils + +func BinarySearch(target int64, array []int64) bool { + left := 0 + right := len(array) - 1 + + for left <= right { + mid := (left + right) / 2 + + if array[mid] == target { + return true + } else if array[mid] < target { + left = mid + 1 + } else if array[mid] > target { + right = mid - 1 + } + } + + return false +}