initial commit
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
import { pool } from '../db.js';
|
||||
|
||||
/**
|
||||
* Проверка прав доступа пользователя к сценарию
|
||||
*/
|
||||
export async function getUserScenarioPermission(userId, scenarioId) {
|
||||
const query = `
|
||||
SELECT can_start, can_execute, can_view_results
|
||||
FROM uno_bff.scenario_permissions
|
||||
WHERE user_id = $1 AND scenario_id = $2
|
||||
`;
|
||||
const result = await pool.query(query, [userId, scenarioId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить сценарий по ID
|
||||
*/
|
||||
export async function getScenarioById(scenarioId) {
|
||||
const query = `
|
||||
SELECT scenario_id, name, description, status, is_public, start_workflow_key, created_at, updated_at
|
||||
FROM uno_bff.scenarios
|
||||
WHERE scenario_id = $1
|
||||
`;
|
||||
const result = await pool.query(query, [scenarioId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить шаг сценария по ID
|
||||
*/
|
||||
export async function getScenarioStep(scenarioId, stepId) {
|
||||
const query = `
|
||||
SELECT scenario_id, step_id, name, description, step_order, status, step_workflow_key, is_terminal, input_schema, output_schema, created_at, updated_at
|
||||
FROM uno_bff.scenario_steps
|
||||
WHERE scenario_id = $1 AND step_id = $2
|
||||
`;
|
||||
const result = await pool.query(query, [scenarioId, stepId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить первый шаг сценария
|
||||
*/
|
||||
export async function getFirstScenarioStep(scenarioId) {
|
||||
const query = `
|
||||
SELECT scenario_id, step_id, name, description, step_order, status, step_workflow_key, is_terminal, input_schema, output_schema
|
||||
FROM uno_bff.scenario_steps
|
||||
WHERE scenario_id = $1 AND status = 'active'
|
||||
ORDER BY step_order ASC
|
||||
LIMIT 1
|
||||
`;
|
||||
const result = await pool.query(query, [scenarioId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать запись generation
|
||||
*/
|
||||
export async function createGeneration({ userId, scenarioId, authSessionId, requestPayload, currentStepId }) {
|
||||
const query = `
|
||||
INSERT INTO uno_bff.generations (user_id, scenario_id, auth_session_id, status, request_payload, current_step_id)
|
||||
VALUES ($1, $2, $3, 'running', $4, $5)
|
||||
RETURNING generation_uuid, user_id, scenario_id, auth_session_id, status, current_step_id, request_payload, started_at
|
||||
`;
|
||||
const result = await pool.query(query, [userId, scenarioId, authSessionId, JSON.stringify(requestPayload), currentStepId]);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить generation по UUID
|
||||
*/
|
||||
export async function getGenerationByUuid(generationUuid) {
|
||||
const query = `
|
||||
SELECT generation_uuid, user_id, scenario_id, auth_session_id, status, current_step_id, request_payload, result_payload, last_error_payload, external_run_id, started_at, finished_at
|
||||
FROM uno_bff.generations
|
||||
WHERE generation_uuid = $1
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить generation (после выполнения шага)
|
||||
*/
|
||||
export async function updateGeneration(generationUuid, updates) {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let idx = 1;
|
||||
|
||||
if (updates.status !== undefined) {
|
||||
fields.push(`status = $${idx++}`);
|
||||
values.push(updates.status);
|
||||
}
|
||||
if (updates.currentStepId !== undefined) {
|
||||
fields.push(`current_step_id = $${idx++}`);
|
||||
values.push(updates.currentStepId);
|
||||
}
|
||||
if (updates.resultPayload !== undefined) {
|
||||
fields.push(`result_payload = $${idx++}`);
|
||||
values.push(JSON.stringify(updates.resultPayload));
|
||||
}
|
||||
if (updates.lastErrorPayload !== undefined) {
|
||||
fields.push(`last_error_payload = $${idx++}`);
|
||||
values.push(JSON.stringify(updates.lastErrorPayload));
|
||||
}
|
||||
if (updates.externalRunId !== undefined) {
|
||||
fields.push(`external_run_id = $${idx++}`);
|
||||
values.push(updates.externalRunId);
|
||||
}
|
||||
if (updates.finishedAt !== undefined) {
|
||||
fields.push(`finished_at = $${idx++}`);
|
||||
values.push(updates.finishedAt);
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return getGenerationByUuid(generationUuid);
|
||||
}
|
||||
|
||||
values.push(generationUuid);
|
||||
const query = `
|
||||
UPDATE uno_bff.generations
|
||||
SET ${fields.join(', ')}, updated_at = now()
|
||||
WHERE generation_uuid = $${idx}
|
||||
RETURNING generation_uuid, user_id, scenario_id, status, current_step_id, request_payload, result_payload, external_run_id
|
||||
`;
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить generation_step
|
||||
* Используется n8n workflow для обновления статуса шага
|
||||
*/
|
||||
export async function updateGenerationStepByUuid(generationUuid, stepId, updates) {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let idx = 1;
|
||||
|
||||
if (updates.status !== undefined) {
|
||||
fields.push(`status = $${idx++}`);
|
||||
values.push(updates.status);
|
||||
}
|
||||
if (updates.responsePayload !== undefined) {
|
||||
fields.push(`response_payload = $${idx++}`);
|
||||
values.push(JSON.stringify(updates.responsePayload));
|
||||
}
|
||||
if (updates.errorPayload !== undefined) {
|
||||
fields.push(`error_payload = $${idx++}`);
|
||||
values.push(JSON.stringify(updates.errorPayload));
|
||||
}
|
||||
if (updates.startedAt !== undefined) {
|
||||
fields.push(`started_at = $${idx++}`);
|
||||
values.push(updates.startedAt);
|
||||
}
|
||||
if (updates.finishedAt !== undefined) {
|
||||
fields.push(`finished_at = $${idx++}`);
|
||||
values.push(updates.finishedAt);
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return getGenerationStep(generationUuid, stepId);
|
||||
}
|
||||
|
||||
values.push(generationUuid, stepId);
|
||||
const query = `
|
||||
UPDATE uno_bff.generation_steps
|
||||
SET ${fields.join(', ')}, updated_at = now()
|
||||
WHERE generation_uuid = $${idx} AND step_id = $${idx + 1}
|
||||
RETURNING id, generation_uuid, scenario_id, step_id, status, request_payload, response_payload
|
||||
`;
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить generation_step
|
||||
*/
|
||||
export async function getGenerationStep(generationUuid, stepId) {
|
||||
const query = `
|
||||
SELECT id, generation_uuid, scenario_id, step_id, step_order, status, request_payload, response_payload, error_payload
|
||||
FROM uno_bff.generation_steps
|
||||
WHERE generation_uuid = $1 AND step_id = $2
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid, stepId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить generation по UUID с проверкой принадлежности пользователю
|
||||
*/
|
||||
export async function getGenerationByUuidForUser(generationUuid, userId) {
|
||||
const query = `
|
||||
SELECT g.generation_uuid, g.user_id, g.scenario_id, g.auth_session_id, g.status,
|
||||
g.current_step_id, g.request_payload, g.result_payload, g.last_error_payload,
|
||||
g.external_run_id, g.started_at, g.finished_at,
|
||||
s.name as scenario_name
|
||||
FROM uno_bff.generations g
|
||||
LEFT JOIN uno_bff.scenarios s ON g.scenario_id = s.scenario_id
|
||||
WHERE g.generation_uuid = $1 AND g.user_id = $2
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid, userId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить метаданные generation по UUID с проверкой принадлежности пользователю
|
||||
*/
|
||||
export async function getGenerationMetaByUuid(generationUuid, userId) {
|
||||
const query = `
|
||||
SELECT g.scenario_id, s.name as scenario_name
|
||||
FROM uno_bff.generations g
|
||||
LEFT JOIN uno_bff.scenarios s ON g.scenario_id = s.scenario_id
|
||||
WHERE g.generation_uuid = $1 AND g.user_id = $2
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid, userId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить generation result_payload и статус
|
||||
*/
|
||||
export async function updateGenerationResult(generationUuid, resultPayload, status = 'completed') {
|
||||
const query = `
|
||||
UPDATE uno_bff.generations
|
||||
SET result_payload = $2, status = $3, finished_at = COALESCE(finished_at, now()), updated_at = now()
|
||||
WHERE generation_uuid = $1
|
||||
RETURNING generation_uuid, user_id, scenario_id, status, result_payload
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid, JSON.stringify(resultPayload), status]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все шаги для generation
|
||||
*/
|
||||
export async function getGenerationSteps(generationUuid) {
|
||||
const query = `
|
||||
SELECT step_id, step_order, status, request_payload, response_payload, error_payload, started_at, finished_at
|
||||
FROM uno_bff.generation_steps
|
||||
WHERE generation_uuid = $1
|
||||
ORDER BY step_order ASC
|
||||
`;
|
||||
const result = await pool.query(query, [generationUuid]);
|
||||
return result.rows;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { pool } from '../db.js';
|
||||
|
||||
export async function createAuthSession({
|
||||
userId,
|
||||
refreshTokenHash,
|
||||
csrfTokenHash,
|
||||
userAgent,
|
||||
ipAddress,
|
||||
expiresAt,
|
||||
}) {
|
||||
const sql = `
|
||||
INSERT INTO uno_bff.auth_sessions
|
||||
(
|
||||
user_id,
|
||||
refresh_token_hash,
|
||||
csrf_token_hash,
|
||||
status,
|
||||
user_agent,
|
||||
ip_address,
|
||||
expires_at,
|
||||
last_seen_at
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
$1, $2, $3, 'active', $4, $5, $6, now()
|
||||
)
|
||||
RETURNING
|
||||
id,
|
||||
user_id,
|
||||
refresh_token_hash,
|
||||
csrf_token_hash,
|
||||
status,
|
||||
user_agent,
|
||||
ip_address,
|
||||
expires_at,
|
||||
last_seen_at,
|
||||
revoked_at,
|
||||
created_at,
|
||||
updated_at
|
||||
`;
|
||||
|
||||
const params = [
|
||||
userId,
|
||||
refreshTokenHash,
|
||||
csrfTokenHash,
|
||||
userAgent || null,
|
||||
ipAddress || null,
|
||||
expiresAt,
|
||||
];
|
||||
|
||||
const { rows } = await pool.query(sql, params);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function revokeSession(sessionId) {
|
||||
const sql = `
|
||||
UPDATE uno_bff.auth_sessions
|
||||
SET
|
||||
status = 'revoked',
|
||||
revoked_at = now(),
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
AND status = 'active'
|
||||
`;
|
||||
|
||||
await pool.query(sql, [sessionId]);
|
||||
}
|
||||
|
||||
export async function getAuthSession(sessionId) {
|
||||
const sql = `
|
||||
SELECT id, user_id, refresh_token_hash, csrf_token_hash, status, expires_at
|
||||
FROM uno_bff.auth_sessions
|
||||
WHERE id = $1 AND status = 'active' AND expires_at > now()
|
||||
`;
|
||||
const result = await pool.query(sql, [sessionId]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function rotateSessionTokens(sessionId, newRefreshTokenHash, newCsrfTokenHash) {
|
||||
const sql = `
|
||||
UPDATE uno_bff.auth_sessions
|
||||
SET
|
||||
refresh_token_hash = $2,
|
||||
csrf_token_hash = $3,
|
||||
updated_at = now()
|
||||
WHERE id = $1 AND status = 'active'
|
||||
RETURNING id, user_id
|
||||
`;
|
||||
const result = await pool.query(sql, [sessionId, newRefreshTokenHash, newCsrfTokenHash]);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { pool } from "../db.js";
|
||||
|
||||
const TOKEN_TTL_SEC = 600; // 10 минут
|
||||
|
||||
export async function createTgLoginToken({ token, intent, userId, ipAddress, userAgent }) {
|
||||
const expires = new Date(Date.now() + TOKEN_TTL_SEC * 1000);
|
||||
await pool.query(
|
||||
`INSERT INTO uno_bff.tg_login_tokens
|
||||
(token, intent, status, user_id, ip_address, user_agent, expires_at)
|
||||
VALUES ($1, $2, 'pending', $3, $4, $5, $6)`,
|
||||
[token, intent, userId || null, ipAddress, userAgent, expires]
|
||||
);
|
||||
return { token, expiresAt: expires, ttlSec: TOKEN_TTL_SEC };
|
||||
}
|
||||
|
||||
export async function findTgLoginToken(token) {
|
||||
const r = await pool.query(
|
||||
`SELECT * FROM uno_bff.tg_login_tokens WHERE token = $1`, [token]
|
||||
);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function markTgTokenConfirmed(token, { telegramId, telegramUsername, telegramFirstName, telegramLastName, userId }) {
|
||||
await pool.query(
|
||||
`UPDATE uno_bff.tg_login_tokens
|
||||
SET status = 'confirmed',
|
||||
telegram_id = $2,
|
||||
telegram_username = $3,
|
||||
telegram_first_name = $4,
|
||||
telegram_last_name = $5,
|
||||
user_id = COALESCE($6, user_id),
|
||||
confirmed_at = now()
|
||||
WHERE token = $1 AND status = 'pending' AND expires_at > now()`,
|
||||
[token, telegramId, telegramUsername, telegramFirstName, telegramLastName, userId || null]
|
||||
);
|
||||
}
|
||||
|
||||
export async function markTgTokenError(token, errorCode) {
|
||||
await pool.query(
|
||||
`UPDATE uno_bff.tg_login_tokens
|
||||
SET status = 'error', error_code = $2
|
||||
WHERE token = $1 AND status = 'pending'`,
|
||||
[token, errorCode]
|
||||
);
|
||||
}
|
||||
|
||||
export async function consumeTgToken(token) {
|
||||
const r = await pool.query(
|
||||
`UPDATE uno_bff.tg_login_tokens
|
||||
SET status = 'consumed', consumed_at = now()
|
||||
WHERE token = $1 AND status = 'confirmed'
|
||||
RETURNING *`,
|
||||
[token]
|
||||
);
|
||||
return r.rows[0] || null;
|
||||
}
|
||||
|
||||
export async function countTgStartsByIpLastMinutes(ipAddress, minutes = 10) {
|
||||
if (!ipAddress) return 0;
|
||||
const r = await pool.query(
|
||||
`SELECT count(*)::int AS c FROM uno_bff.tg_login_tokens
|
||||
WHERE ip_address = $1 AND created_at > now() - ($2 || ' minutes')::interval`,
|
||||
[ipAddress, String(minutes)]
|
||||
);
|
||||
return r.rows[0]?.c || 0;
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import { pool } from '../db.js';
|
||||
|
||||
/**
|
||||
* Repository для работы с метаданными файлов пользователей
|
||||
*/
|
||||
export const userFileRepository = {
|
||||
/**
|
||||
* Создать запись о файле
|
||||
*/
|
||||
async create({ userId, s3Key, originalFilename, fileSize, contentType, fileType = 'image', folder = 'images_input', generationUuid = null, generationStepId = null, status = 'uploaded', uploadId = null }) {
|
||||
const query = `
|
||||
INSERT INTO uno_bff.user_files (user_id, s3_key, original_filename, file_size, content_type, file_type, folder, generation_uuid, generation_step_id, status, upload_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
||||
RETURNING id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, generation_uuid, generation_step_id, status, upload_id, created_at, updated_at
|
||||
`;
|
||||
const values = [userId, s3Key, originalFilename, fileSize, contentType, fileType, folder, generationUuid, generationStepId, status, uploadId];
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Обновить файл
|
||||
*/
|
||||
async update(fileId, updates) {
|
||||
const allowedFields = ['status', 'upload_id', 'file_size', 'updated_at'];
|
||||
const setClauses = [];
|
||||
const values = [fileId];
|
||||
let paramIndex = 2;
|
||||
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (allowedFields.includes(key)) {
|
||||
setClauses.push(`${key} = $${paramIndex}`);
|
||||
values.push(value);
|
||||
paramIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
if (setClauses.length === 0) {
|
||||
throw new Error('No valid fields to update');
|
||||
}
|
||||
|
||||
const query = `
|
||||
UPDATE uno_bff.user_files
|
||||
SET ${setClauses.join(', ')}
|
||||
WHERE id = $1
|
||||
RETURNING id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, status, upload_id, created_at, updated_at
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Удалить файл по ID
|
||||
*/
|
||||
async delete(fileId) {
|
||||
const query = `
|
||||
DELETE FROM uno_bff.user_files
|
||||
WHERE id = $1
|
||||
RETURNING id
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [fileId]);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Найти файл по ID и проверить владение
|
||||
*/
|
||||
async findByIdAndOwner(fileId, userId) {
|
||||
const query = `
|
||||
SELECT id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, created_at
|
||||
FROM uno_bff.user_files
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [fileId, userId]);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Найти файл по S3 ключу и проверить владение
|
||||
*/
|
||||
async findByKeyAndOwner(s3Key, userId) {
|
||||
const query = `
|
||||
SELECT id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, created_at
|
||||
FROM uno_bff.user_files
|
||||
WHERE s3_key = $1 AND user_id = $2
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [s3Key, userId]);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Получить список файлов пользователя
|
||||
*/
|
||||
async findByUser(userId, options = {}) {
|
||||
const {
|
||||
fileType = 'image',
|
||||
folder = 'images_input',
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
orderBy = 'created_at',
|
||||
order = 'DESC',
|
||||
} = options;
|
||||
|
||||
const validOrderColumns = ['created_at', 'original_filename', 'file_size'];
|
||||
const validOrders = ['ASC', 'DESC'];
|
||||
const safeOrderBy = validOrderColumns.includes(orderBy) ? orderBy : 'created_at';
|
||||
const safeOrder = validOrders.includes(order?.toUpperCase()) ? order.toUpperCase() : 'DESC';
|
||||
|
||||
const query = `
|
||||
SELECT id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, created_at
|
||||
FROM uno_bff.user_files
|
||||
WHERE user_id = $1
|
||||
AND ($2::VARCHAR IS NULL OR file_type = $2)
|
||||
AND ($3::VARCHAR IS NULL OR folder = $3)
|
||||
ORDER BY ${safeOrderBy} ${safeOrder}
|
||||
LIMIT $4 OFFSET $5
|
||||
`;
|
||||
|
||||
const values = [
|
||||
userId,
|
||||
fileType || null,
|
||||
folder || null,
|
||||
limit,
|
||||
offset,
|
||||
];
|
||||
|
||||
const result = await pool.query(query, values);
|
||||
return result.rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* Получить общее количество файлов пользователя
|
||||
*/
|
||||
async countByUser(userId, options = {}) {
|
||||
const { fileType = 'image', folder = 'images_input' } = options;
|
||||
|
||||
const query = `
|
||||
SELECT COUNT(*) as total
|
||||
FROM uno_bff.user_files
|
||||
WHERE user_id = $1
|
||||
AND ($2::VARCHAR IS NULL OR file_type = $2)
|
||||
AND ($3::VARCHAR IS NULL OR folder = $3)
|
||||
`;
|
||||
|
||||
const values = [userId, fileType || null, folder || null];
|
||||
const result = await pool.query(query, values);
|
||||
return parseInt(result.rows[0].total, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* Удалить запись о файле (по ID и владельцу)
|
||||
*/
|
||||
async deleteByIdAndOwner(fileId, userId) {
|
||||
const query = `
|
||||
DELETE FROM uno_bff.user_files
|
||||
WHERE id = $1 AND user_id = $2
|
||||
RETURNING s3_key
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [fileId, userId]);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Удалить запись о файле по S3 ключу
|
||||
*/
|
||||
async deleteByKeyAndOwner(s3Key, userId) {
|
||||
const query = `
|
||||
DELETE FROM uno_bff.user_files
|
||||
WHERE s3_key = $1 AND user_id = $2
|
||||
RETURNING s3_key
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [s3Key, userId]);
|
||||
return result.rows[0];
|
||||
},
|
||||
|
||||
/**
|
||||
* Получить файлы генерации
|
||||
*/
|
||||
async findByGeneration(generationUuid, userId) {
|
||||
const query = `
|
||||
SELECT id, user_id, s3_key, original_filename, file_size, content_type, file_type, folder, generation_uuid, generation_step_id, created_at
|
||||
FROM uno_bff.user_files
|
||||
WHERE generation_uuid = $1 AND user_id = $2
|
||||
ORDER BY created_at ASC
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [generationUuid, userId]);
|
||||
return result.rows;
|
||||
},
|
||||
|
||||
/**
|
||||
* Получить файлы шага генерации
|
||||
*/
|
||||
async findByGenerationStep(generationUuid, stepId, userId) {
|
||||
const query = `
|
||||
SELECT f.id, f.user_id, f.s3_key, f.original_filename, f.file_size, f.content_type, f.file_type, f.folder, f.generation_uuid, f.generation_step_id, f.created_at
|
||||
FROM uno_bff.user_files f
|
||||
INNER JOIN uno_bff.generation_steps gs ON f.generation_step_id = gs.id
|
||||
WHERE f.generation_uuid = $1 AND gs.step_id = $2 AND f.user_id = $3
|
||||
ORDER BY f.created_at ASC
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [generationUuid, stepId, userId]);
|
||||
return result.rows;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { pool } from "../db.js";
|
||||
|
||||
export async function findUserByEmail(email) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1
|
||||
`, [email]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserByEmailAndPassword(email, password) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users
|
||||
WHERE lower(email) = lower($1) AND status = 'active'
|
||||
AND password_hash::text = sites.crypt($2::text, password_hash::text)
|
||||
LIMIT 1
|
||||
`, [email, password]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserById(userId) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, display_name, role, status, balance, created_at
|
||||
FROM uno_bff.users WHERE id = $1 AND status = 'active'
|
||||
`, [userId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function updateUserBalance(userId, delta) {
|
||||
const { rows } = await pool.query(`
|
||||
UPDATE uno_bff.users
|
||||
SET balance = balance + $2, updated_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING balance
|
||||
`, [userId, delta]);
|
||||
return rows[0]?.balance ?? null;
|
||||
}
|
||||
|
||||
export async function touchLastLogin(userId) {
|
||||
await pool.query(`UPDATE uno_bff.users SET last_login_at = now(), updated_at = now() WHERE id = $1`, [userId]);
|
||||
}
|
||||
|
||||
export async function createUser({ email, password, displayName }) {
|
||||
const { rows } = await pool.query(`
|
||||
INSERT INTO uno_bff.users (email, password_hash, display_name, role, status)
|
||||
VALUES (lower($1), sites.crypt($2::text, sites.gen_salt('bf', 10)), $3, 'user', 'active')
|
||||
RETURNING id, email, display_name, role, status, balance, created_at
|
||||
`, [email, password, displayName || null]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function userExistsByEmail(email) {
|
||||
const { rows } = await pool.query(`SELECT 1 FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1`, [email]);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function findUserByTelegramId(telegramId) {
|
||||
const { rows } = await pool.query(`SELECT * FROM uno_bff.users WHERE telegram_id = $1 AND status = 'active' LIMIT 1`, [telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function linkTelegramToUser(userId, telegramId) {
|
||||
const { rows } = await pool.query(`UPDATE uno_bff.users SET telegram_id = $2 WHERE id = $1 RETURNING *`, [userId, telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function createUserFromTelegram({ telegramId, displayName, email }) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO uno_bff.users (email, password_hash, display_name, telegram_id, role, status)
|
||||
VALUES (lower($1), '!tg!' || sites.gen_salt('bf', 10), $2, $3, 'user', 'active')
|
||||
RETURNING id, email, display_name, role, status, balance, telegram_id, created_at`,
|
||||
[email, displayName || null, telegramId]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function countTelegramSignupsByIpLast24h(ipAddress) {
|
||||
if (!ipAddress) return 0;
|
||||
const { rows } = await pool.query(
|
||||
`SELECT COUNT(*)::int AS c FROM uno_bff.signup_attempts
|
||||
WHERE ip_address = $1 AND provider = 'telegram' AND created_at > now() - interval '24 hours'`,
|
||||
[ipAddress]
|
||||
);
|
||||
return rows[0]?.c ?? 0;
|
||||
}
|
||||
|
||||
export async function recordSignupAttempt({ ipAddress, provider, telegramId, userAgent }) {
|
||||
await pool.query(
|
||||
`INSERT INTO uno_bff.signup_attempts (ip_address, provider, telegram_id, user_agent)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[ipAddress || null, provider, telegramId || null, userAgent || null]
|
||||
);
|
||||
}
|
||||
|
||||
export async function findTelegramOwner(telegramId) {
|
||||
const { rows } = await pool.query(`SELECT id FROM uno_bff.users WHERE telegram_id = $1 LIMIT 1`, [telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function unlinkTelegramFromUser(userId) {
|
||||
const { rows } = await pool.query(`UPDATE uno_bff.users SET telegram_id = NULL, updated_at = now() WHERE id = $1 RETURNING id`, [userId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { pool } from "../db.js";
|
||||
|
||||
export async function findUserByEmail(email) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1
|
||||
`, [email]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserByEmailAndPassword(email, password) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users
|
||||
WHERE lower(email) = lower($1) AND status = 'active'
|
||||
AND password_hash::text = sites.crypt($2::text, password_hash::text)
|
||||
LIMIT 1
|
||||
`, [email, password]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserById(userId) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, display_name, role, status, balance, created_at
|
||||
FROM uno_bff.users WHERE id = $1 AND status = 'active'
|
||||
`, [userId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function updateUserBalance(userId, delta) {
|
||||
const { rows } = await pool.query(`
|
||||
UPDATE uno_bff.users
|
||||
SET balance = balance + $2, updated_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING balance
|
||||
`, [userId, delta]);
|
||||
return rows[0]?.balance ?? null;
|
||||
}
|
||||
|
||||
export async function touchLastLogin(userId) {
|
||||
await pool.query(`UPDATE uno_bff.users SET last_login_at = now(), updated_at = now() WHERE id = $1`, [userId]);
|
||||
}
|
||||
|
||||
export async function createUser({ email, password, displayName }) {
|
||||
const { rows } = await pool.query(`
|
||||
INSERT INTO uno_bff.users (email, password_hash, display_name, role, status)
|
||||
VALUES (lower($1), sites.crypt($2::text, sites.gen_salt('bf', 10)), $3, 'user', 'active')
|
||||
RETURNING id, email, display_name, role, status, balance, created_at
|
||||
`, [email, password, displayName || null]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function userExistsByEmail(email) {
|
||||
const { rows } = await pool.query(`SELECT 1 FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1`, [email]);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function findUserByTelegramId(telegramId) {
|
||||
const { rows } = await pool.query(`SELECT * FROM uno_bff.users WHERE telegram_id = $1 AND status = 'active' LIMIT 1`, [telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function linkTelegramToUser(userId, telegramId) {
|
||||
const { rows } = await pool.query(`UPDATE uno_bff.users SET telegram_id = $2 WHERE id = $1 RETURNING *`, [userId, telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { pool } from "../db.js";
|
||||
|
||||
export async function findUserByEmail(email) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1
|
||||
`, [email]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserByEmailAndPassword(email, password) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, password_hash, display_name, role, status, balance, last_login_at, created_at, updated_at
|
||||
FROM uno_bff.users
|
||||
WHERE lower(email) = lower($1) AND status = 'active'
|
||||
AND password_hash::text = sites.crypt($2::text, password_hash::text)
|
||||
LIMIT 1
|
||||
`, [email, password]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function findUserById(userId) {
|
||||
const { rows } = await pool.query(`
|
||||
SELECT id, email, display_name, role, status, balance, created_at
|
||||
FROM uno_bff.users WHERE id = $1 AND status = 'active'
|
||||
`, [userId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function updateUserBalance(userId, delta) {
|
||||
const { rows } = await pool.query(`
|
||||
UPDATE uno_bff.users
|
||||
SET balance = balance + $2, updated_at = NOW()
|
||||
WHERE id = $1
|
||||
RETURNING balance
|
||||
`, [userId, delta]);
|
||||
return rows[0]?.balance ?? null;
|
||||
}
|
||||
|
||||
export async function touchLastLogin(userId) {
|
||||
await pool.query(`UPDATE uno_bff.users SET last_login_at = now(), updated_at = now() WHERE id = $1`, [userId]);
|
||||
}
|
||||
|
||||
export async function createUser({ email, password, displayName }) {
|
||||
const { rows } = await pool.query(`
|
||||
INSERT INTO uno_bff.users (email, password_hash, display_name, role, status)
|
||||
VALUES (lower($1), sites.crypt($2::text, sites.gen_salt('bf', 10)), $3, 'user', 'active')
|
||||
RETURNING id, email, display_name, role, status, balance, created_at
|
||||
`, [email, password, displayName || null]);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function userExistsByEmail(email) {
|
||||
const { rows } = await pool.query(`SELECT 1 FROM uno_bff.users WHERE lower(email) = lower($1) LIMIT 1`, [email]);
|
||||
return rows.length > 0;
|
||||
}
|
||||
|
||||
export async function findUserByTelegramId(telegramId) {
|
||||
const { rows } = await pool.query(`SELECT * FROM uno_bff.users WHERE telegram_id = $1 AND status = 'active' LIMIT 1`, [telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function linkTelegramToUser(userId, telegramId) {
|
||||
const { rows } = await pool.query(`UPDATE uno_bff.users SET telegram_id = $2 WHERE id = $1 RETURNING *`, [userId, telegramId]);
|
||||
return rows[0] || null;
|
||||
}
|
||||
|
||||
export async function createUserFromTelegram({ telegramId, displayName, email }) {
|
||||
const { rows } = await pool.query(
|
||||
`INSERT INTO uno_bff.users (email, password_hash, display_name, telegram_id, role, status)
|
||||
VALUES (lower($1), '!tg!' || sites.gen_salt('bf', 10), $2, $3, 'user', 'active')
|
||||
RETURNING id, email, display_name, role, status, balance, telegram_id, created_at`,
|
||||
[email, displayName || null, telegramId]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
export async function countTelegramSignupsByIpLast24h(ipAddress) {
|
||||
if (!ipAddress) return 0;
|
||||
const { rows } = await pool.query(
|
||||
`SELECT COUNT(*)::int AS c FROM uno_bff.signup_attempts
|
||||
WHERE ip_address = $1 AND provider = 'telegram' AND created_at > now() - interval '24 hours'`,
|
||||
[ipAddress]
|
||||
);
|
||||
return rows[0]?.c ?? 0;
|
||||
}
|
||||
|
||||
export async function recordSignupAttempt({ ipAddress, provider, telegramId, userAgent }) {
|
||||
await pool.query(
|
||||
`INSERT INTO uno_bff.signup_attempts (ip_address, provider, telegram_id, user_agent)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[ipAddress || null, provider, telegramId || null, userAgent || null]
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user