ваше сообщение коммита

This commit is contained in:
2025-05-22 16:24:39 +03:00
parent a91658eb31
commit 9aa842d238
41 changed files with 1621 additions and 507 deletions

View File

@@ -2,12 +2,12 @@ const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const session = require('express-session');
const { sessionMiddleware } = require('./config/session');
const sessionConfig = require('./config/session');
const logger = require('./utils/logger');
// const csurf = require('csurf'); // Закомментировано, так как не используется
const { errorHandler } = require('./middleware/errorHandler');
const errorHandler = require('./middleware/errorHandler');
// const { version } = require('./package.json'); // Закомментировано, так как не используется
const pool = require('./db'); // Добавляем импорт pool
const db = require('./db'); // Добавляем импорт db
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
const fs = require('fs');
const path = require('path');
@@ -48,6 +48,9 @@ const ensureDirectoriesExist = () => {
// Вызываем функцию проверки директорий при запуске сервера
ensureDirectoriesExist();
// Регистрируем коллбек для пересоздания session middleware при смене пула
db.setPoolChangeCallback(sessionConfig.reloadSessionMiddleware);
// Импорт маршрутов
const authRoutes = require('./routes/auth');
const usersRoutes = require('./routes/users');
@@ -79,8 +82,8 @@ app.use(
})
);
// Настройка сессии (ИСПОЛЬЗУЕМ ИМПОРТИРОВАННОЕ MIDDLEWARE)
app.use(sessionMiddleware);
// Настройка сессии (используем геттер, чтобы всегда был актуальный middleware)
app.use((req, res, next) => sessionConfig.sessionMiddleware(req, res, next));
// Добавим middleware для проверки сессии
app.use(async (req, res, next) => {
@@ -89,7 +92,7 @@ app.use(async (req, res, next) => {
// Проверяем сессию в базе данных
if (req.sessionID) {
const result = await pool.query('SELECT sess FROM session WHERE sid = $1', [req.sessionID]);
const result = await db.getQuery()('SELECT sess FROM session WHERE sid = $1', [req.sessionID]);
console.log('Session from DB:', result.rows[0]?.sess);
}
@@ -104,7 +107,7 @@ app.use(async (req, res, next) => {
const token = authHeader.split(' ')[1];
try {
// Находим пользователя по токену
const { rows } = await pool.query(
const { rows } = await db.getQuery(
`
SELECT u.id,
(u.role = 'admin') as is_admin,
@@ -152,7 +155,7 @@ app.use((req, res, next) => {
// Маршруты API
app.use('/api/auth', authRoutes);
app.use('/api/users', usersRoutes);
app.use('/api/identities', identitiesRoutes);
app.use('/api', identitiesRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/tokens', tokensRouter);
@@ -181,6 +184,8 @@ console.log('OPENAI_API_KEY:', redactedValue);
console.log('EMAIL_USER:', process.env.EMAIL_USER);
console.log('EMAIL_PASSWORD:', redactedValue);
console.log('typeof errorHandler:', typeof errorHandler, errorHandler.name);
// Добавляем обработчик ошибок последним
app.use(errorHandler);
@@ -188,7 +193,7 @@ app.use(errorHandler);
app.get('/api/health', async (req, res) => {
try {
// Проверяем подключение к БД
await pool.query('SELECT NOW()');
await db.getQuery('SELECT NOW()');
// Проверяем AI сервис
const aiStatus = await aiAssistant.checkHealth();
@@ -212,7 +217,7 @@ app.get('/api/health', async (req, res) => {
setInterval(
async () => {
try {
await pool.query('DELETE FROM session WHERE expire < NOW()');
await db.getQuery('DELETE FROM session WHERE expire < NOW()');
} catch (error) {
console.error('Error cleaning old sessions:', error);
}

View File

@@ -1,25 +1,46 @@
const session = require('express-session');
const pgSession = require('connect-pg-simple')(session);
const { pool } = require('../db');
const db = require('../db');
const sessionConfig = {
store: new pgSession({
pool,
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
name: 'sessionId',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
},
};
let onPoolChangeCallback = null;
function setPoolChangeCallback(cb) {
onPoolChangeCallback = cb;
}
let sessionMiddleware = createSessionMiddleware();
function createSessionMiddleware() {
return session({
store: new pgSession({
pool: db.getPool(),
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
name: 'sessionId',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
},
});
}
function reloadSessionMiddleware() {
sessionMiddleware = createSessionMiddleware();
if (onPoolChangeCallback) {
onPoolChangeCallback();
}
}
module.exports = {
sessionMiddleware: session(sessionConfig),
get sessionMiddleware() {
return sessionMiddleware;
},
reloadSessionMiddleware,
setPoolChangeCallback,
};

View File

@@ -9,10 +9,14 @@ console.log('DB_PORT:', process.env.DB_PORT);
console.log('DB_NAME:', process.env.DB_NAME);
console.log('DB_USER:', process.env.DB_USER);
// Создаем пул соединений с базой данных
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
// Первичное подключение по дефолтным значениям
let pool = new Pool({
host: process.env.DB_HOST || 'postgres',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'dapp_db',
user: process.env.DB_USER || 'dapp_user',
password: process.env.DB_PASSWORD,
ssl: false,
});
// Проверяем подключение к базе данных
@@ -21,36 +25,59 @@ pool.query('SELECT NOW()')
console.log('Успешное подключение к базе данных:', res.rows[0]);
})
.catch(err => {
console.error('Failed to connect to the database using DATABASE_URL:', err);
console.log('Attempting alternative database connection...');
// Пробуем альтернативное подключение
const altPool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'dapp_db',
user: process.env.DB_USER || 'dapp_user',
password: process.env.DB_PASSWORD,
});
altPool.query('SELECT NOW()')
.then(altRes => {
console.log('Альтернативное подключение успешно:', altRes.rows[0]);
// Заменяем основной пул на альтернативный
module.exports.pool = altPool;
module.exports.query = (text, params) => altPool.query(text, params);
})
.catch(altErr => {
console.error('Альтернативное подключение тоже не удалось:', altErr);
console.log('Переключение на временное хранилище данных в памяти...');
module.exports = createInMemoryStorage();
});
console.error('Ошибка подключения к базе данных:', err);
});
// Функция для выполнения SQL-запросов
const query = (text, params) => {
return pool.query(text, params);
};
console.log('Пул создан:', pool.options || pool);
function getPool() {
return pool;
}
function getQuery() {
return pool.query.bind(pool);
}
let poolChangeCallback = null;
function setPoolChangeCallback(cb) {
poolChangeCallback = cb;
}
// Функция для пересоздания пула из db_settings
async function reinitPoolFromDbSettings() {
try {
const res = await pool.query('SELECT * FROM db_settings ORDER BY id LIMIT 1');
if (!res.rows.length) throw new Error('DB settings not found');
const settings = res.rows[0];
// Закрываем старый пул
await pool.end();
// Создаём новый пул
pool = new Pool({
host: settings.db_host,
port: parseInt(settings.db_port),
database: settings.db_name,
user: settings.db_user,
password: settings.db_password,
ssl: false,
});
// Пересоздаём session middleware
if (poolChangeCallback) {
poolChangeCallback();
}
console.log('Пул пересоздан с новыми параметрами:', settings);
} catch (err) {
console.error('Ошибка пересоздания пула:', err);
throw err;
}
}
// При старте приложения — сразу пробуем инициализировать из db_settings
if (process.env.NODE_ENV !== 'migration') {
reinitPoolFromDbSettings();
}
const query = (text, params) => pool.query(text, params);
// Функция для сохранения гостевого сообщения в базе данных
async function saveGuestMessageToDatabase(message, language, guestId) {
@@ -71,70 +98,9 @@ async function saveGuestMessageToDatabase(message, language, guestId) {
// Экспортируем функции для работы с базой данных
module.exports = {
query,
pool,
getPool,
getQuery,
reinitPoolFromDbSettings,
saveGuestMessageToDatabase,
setPoolChangeCallback,
};
// Функция для создания временного хранилища данных в памяти
function createInMemoryStorage() {
console.log('Используется временное хранилище данных в памяти');
const users = [];
let userId = 1;
// Эмуляция функции query для работы с пользователями
const inMemoryQuery = async (text, params) => {
console.log('SQL query (in-memory):', text, 'Params:', params);
// Эмуляция запроса SELECT * FROM users WHERE address = $1
if (text.includes('SELECT * FROM users WHERE address = $1')) {
const address = params[0];
const user = users.find((u) => u.address === address);
return { rows: user ? [user] : [] };
}
// Эмуляция запроса SELECT * FROM users WHERE email = $1
if (text.includes('SELECT * FROM users WHERE email = $1')) {
const email = params[0];
const user = users.find((u) => u.email === email);
return { rows: user ? [user] : [] };
}
// Эмуляция запроса INSERT INTO users
if (text.includes('INSERT INTO users')) {
let newUser;
if (text.includes('address')) {
newUser = { id: userId++, address: params[0], created_at: new Date(), is_admin: false };
} else if (text.includes('email')) {
newUser = { id: userId++, email: params[0], created_at: new Date(), is_admin: false };
}
if (newUser) {
users.push(newUser);
return { rows: [newUser] };
}
}
return { rows: [] };
};
return {
query: inMemoryQuery,
pool: {
query: (text, params, callback) => {
if (callback) {
try {
const result = inMemoryQuery(text, params);
callback(null, result);
} catch (err) {
callback(err);
}
} else {
return inMemoryQuery(text, params);
}
},
},
};
}

View File

@@ -1,21 +0,0 @@
const { Pool } = require('pg');
const logger = require('../utils/logger');
const pool = new Pool({
user: process.env.DB_USER || 'dapp_user',
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'dapp_db',
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT || 5432,
});
// Проверка подключения
pool.query('SELECT NOW()', (err, res) => {
if (err) {
logger.error('Error connecting to database:', err);
} else {
logger.info('Успешное подключение к базе данных:', res.rows[0]);
}
});
module.exports = { pool };

View File

@@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS email_settings (
id SERIAL PRIMARY KEY,
smtp_host VARCHAR(255) NOT NULL,
smtp_port INTEGER NOT NULL,
smtp_user VARCHAR(255) NOT NULL,
smtp_password VARCHAR(255) NOT NULL,
imap_host VARCHAR(255),
imap_port INTEGER,
from_email VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Для простоты предполагаем, что настройки всегда одни (id=1)
INSERT INTO email_settings (smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email)
VALUES ('smtp.example.com', 465, 'user@example.com', 'password', 'imap.example.com', 993, 'noreply@example.com')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,12 @@
CREATE TABLE IF NOT EXISTS telegram_settings (
id SERIAL PRIMARY KEY,
bot_token VARCHAR(255) NOT NULL,
bot_username VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Для простоты предполагаем, что настройки всегда одни (id=1)
INSERT INTO telegram_settings (bot_token, bot_username)
VALUES ('your-telegram-bot-token', 'your_bot_username')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS db_settings (
id SERIAL PRIMARY KEY,
db_host VARCHAR(255) NOT NULL,
db_port INTEGER NOT NULL,
db_name VARCHAR(255) NOT NULL,
db_user VARCHAR(255) NOT NULL,
db_password VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Для простоты предполагаем, что настройки всегда одни (id=1)
INSERT INTO db_settings (db_host, db_port, db_name, db_user, db_password)
VALUES ('localhost', 5432, 'dapp_db', 'dapp_user', 'dapp_password')
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS ai_providers_settings (
id SERIAL PRIMARY KEY,
provider VARCHAR(32) NOT NULL UNIQUE, -- openai, anthropic, google, ollama
api_key VARCHAR(255),
base_url VARCHAR(255),
selected_model VARCHAR(128),
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Пример заполнения для Ollama (без ключа)
INSERT INTO ai_providers_settings (provider, base_url, selected_model)
VALUES ('ollama', 'http://localhost:11434', 'qwen2.5')
ON CONFLICT (provider) DO NOTHING;

View File

@@ -7,6 +7,11 @@ const { ERROR_CODES } = require('../utils/constants');
*/
// eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, /* next */) => {
console.error('errorHandler: err =', err);
console.error('errorHandler: typeof err =', typeof err);
console.error('errorHandler: stack =', err && err.stack);
console.log('errorHandler called, typeof res:', typeof res, 'res:', res);
console.log('typeof res:', typeof res, 'isFunction:', typeof res === 'function');
// Логируем ошибку
logger.error(`Error: ${err.message}`, {
stack: err.stack,
@@ -65,7 +70,6 @@ function createError(message, status) {
return error;
}
module.exports = {
errorHandler,
createError,
};
module.exports = errorHandler;
// Если нужен createError для других файлов:
// module.exports.createError = createError;

View File

@@ -23,6 +23,8 @@
"fix-duplicates": "node scripts/fix-duplicate-identities.js"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.51.0",
"@google/genai": "^1.0.1",
"@langchain/community": "^0.3.34",
"@langchain/core": "0.3.0",
"@langchain/ollama": "^0.2.0",
@@ -47,6 +49,7 @@
"node-cron": "^3.0.3",
"node-telegram-bot-api": "^0.66.0",
"nodemailer": "^6.10.0",
"openai": "^4.102.0",
"pg": "^8.10.0",
"semver": "^7.7.1",
"session-file-store": "^1.5.0",

View File

@@ -6,49 +6,49 @@ const authService = require('../services/auth-service');
const logger = require('../utils/logger');
// Роли
router.get('/roles', requireAdmin, async (req, res) => {
router.get('/roles', requireAdmin, async (req, res, next) => {
try {
const roles = await authService.getAllRoles();
res.json({ success: true, roles });
} catch (error) {
logger.error('Error getting roles:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
router.post('/roles', requireAdmin, async (req, res) => {
router.post('/roles', requireAdmin, async (req, res, next) => {
try {
const { name, permissions } = req.body;
const role = await authService.createRole(name, permissions);
res.json({ success: true, role });
} catch (error) {
logger.error('Error creating role:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Админ функции
router.get('/users', requireAdmin, async (req, res) => {
router.get('/users', requireAdmin, async (req, res, next) => {
try {
const users = await authService.getAllUsers();
res.json({ success: true, users });
} catch (error) {
logger.error('Error getting users:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Маршрут для получения статистики (защищен middleware requireAdmin)
router.get('/stats', requireAdmin, async (req, res) => {
router.get('/stats', requireAdmin, async (req, res, next) => {
try {
// Получаем количество пользователей
const usersCount = await db.query('SELECT COUNT(*) FROM users');
const usersCount = await db.getQuery()('SELECT COUNT(*) FROM users');
// Получаем количество досок
const boardsCount = await db.query('SELECT COUNT(*) FROM kanban_boards');
const boardsCount = await db.getQuery()('SELECT COUNT(*) FROM kanban_boards');
// Получаем количество задач
const tasksCount = await db.query('SELECT COUNT(*) FROM kanban_tasks');
const tasksCount = await db.getQuery()('SELECT COUNT(*) FROM kanban_tasks');
res.json({
userCount: parseInt(usersCount.rows[0].count),
@@ -57,18 +57,18 @@ router.get('/stats', requireAdmin, async (req, res) => {
});
} catch (error) {
console.error('Ошибка при получении статистики:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Маршрут для получения логов
router.get('/logs', requireAdmin, async (req, res) => {
router.get('/logs', requireAdmin, async (req, res, next) => {
try {
const result = await db.query('SELECT * FROM logs ORDER BY created_at DESC LIMIT 100');
const result = await db.getQuery()('SELECT * FROM logs ORDER BY created_at DESC LIMIT 100');
res.json(result.rows);
} catch (error) {
console.error('Ошибка при получении логов:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});

View File

@@ -34,7 +34,7 @@ router.get('/nonce', async (req, res) => {
const nonce = crypto.randomBytes(16).toString('hex');
// Проверяем, существует ли уже nonce для этого адреса
const existingNonce = await db.query('SELECT id FROM nonces WHERE identity_value = $1', [
const existingNonce = await db.getQuery()('SELECT id FROM nonces WHERE identity_value = $1', [
address.toLowerCase(),
]);

View File

@@ -17,7 +17,7 @@ async function processGuestMessages(userId, guestId) {
logger.info(`Processing guest messages for user ${userId} with guest ID ${guestId}`);
// Проверяем, обрабатывались ли уже эти сообщения
const mappingCheck = await db.query(
const mappingCheck = await db.getQuery()(
'SELECT processed FROM guest_user_mapping WHERE guest_id = $1',
[guestId]
);
@@ -30,7 +30,7 @@ async function processGuestMessages(userId, guestId) {
// Проверяем наличие mapping записи и создаем если нет
if (mappingCheck.rows.length === 0) {
await db.query(
await db.getQuery()(
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
[userId, guestId]
);
@@ -38,7 +38,7 @@ async function processGuestMessages(userId, guestId) {
}
// Получаем все гостевые сообщения со всеми новыми полями
const guestMessagesResult = await db.query(
const guestMessagesResult = await db.getQuery()(
`SELECT
id, guest_id, content, language, is_ai, created_at,
attachment_filename, attachment_mimetype, attachment_size, attachment_data
@@ -48,9 +48,9 @@ async function processGuestMessages(userId, guestId) {
if (guestMessagesResult.rows.length === 0) {
logger.info(`No guest messages found for guest ID ${guestId}`);
const checkResult = await db.query('SELECT 1 FROM guest_user_mapping WHERE guest_id = $1', [guestId]);
const checkResult = await db.getQuery()('SELECT 1 FROM guest_user_mapping WHERE guest_id = $1', [guestId]);
if (checkResult.rows.length > 0) {
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [guestId]);
await db.getQuery()('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [guestId]);
logger.info(`Marked guest mapping as processed (no messages found) for guest ID ${guestId}`);
} else {
logger.warn(`Attempted to mark non-existent guest mapping as processed for guest ID ${guestId}`);
@@ -67,7 +67,7 @@ async function processGuestMessages(userId, guestId) {
? (firstMessage.content.length > 30 ? `${firstMessage.content.substring(0, 30)}...` : firstMessage.content)
: (firstMessage.attachment_filename ? `Файл: ${firstMessage.attachment_filename}` : 'Новый диалог');
const newConversationResult = await db.query(
const newConversationResult = await db.getQuery()(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
@@ -84,7 +84,7 @@ async function processGuestMessages(userId, guestId) {
try {
// Сохраняем сообщение пользователя в таблицу messages, включая данные файла
const userMessageResult = await db.query(
const userMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, created_at, user_id,
attachment_filename, attachment_mimetype, attachment_size, attachment_data)
@@ -118,7 +118,7 @@ async function processGuestMessages(userId, guestId) {
if (aiResponseContent) {
// Сохраняем ответ от ИИ (у него нет вложений)
const aiMessageResult = await db.query(
const aiMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, content, sender_type, role, channel, created_at, user_id)
VALUES
@@ -144,20 +144,20 @@ async function processGuestMessages(userId, guestId) {
// Удаляем только успешно обработанные гостевые сообщения
if (savedMessageIds.length > 0) {
await db.query('DELETE FROM guest_messages WHERE id = ANY($1::int[])', [savedMessageIds]);
await db.getQuery()('DELETE FROM guest_messages WHERE id = ANY($1::int[])', [savedMessageIds]);
logger.info(
`Deleted ${savedMessageIds.length} processed guest messages for guest ID ${guestId}`
);
// Помечаем гостевой ID как обработанный
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
await db.getQuery()('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [
guestId,
]);
logger.info(`Marked guest mapping as processed for guest ID ${guestId}`);
} else {
logger.warn(`No guest messages were successfully processed, skipping deletion for guest ID ${guestId}`);
// Если не было успешных, все равно пометим как обработанные, чтобы не пытаться снова
await db.query('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [guestId]);
await db.getQuery()('UPDATE guest_user_mapping SET processed = true WHERE guest_id = $1', [guestId]);
logger.info(`Marked guest mapping as processed (no successful messages) for guest ID ${guestId}`);
}
@@ -221,7 +221,7 @@ router.post('/guest-message', upload.array('attachments'), async (req, res) => {
});
// Сохраняем сообщение пользователя с текстом или файлом
const result = await db.query(
const result = await db.getQuery()(
`INSERT INTO guest_messages
(guest_id, content, language, is_ai,
attachment_filename, attachment_mimetype, attachment_size, attachment_data)
@@ -293,7 +293,7 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
try {
// Найти или создать диалог
if (conversationId) {
const convResult = await db.query(
const convResult = await db.getQuery()(
'SELECT * FROM conversations WHERE id = $1 AND user_id = $2',
[conversationId, userId]
);
@@ -308,7 +308,7 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
? (message.length > 50 ? `${message.substring(0, 50)}...` : message)
: (file ? `Файл: ${file.originalname}` : 'Новый диалог');
const newConvResult = await db.query(
const newConvResult = await db.getQuery()(
'INSERT INTO conversations (user_id, title) VALUES ($1, $2) RETURNING *',
[userId, title]
);
@@ -325,7 +325,7 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
const attachmentData = file ? file.buffer : null;
// Сохраняем сообщение пользователя
const userMessageResult = await db.query(
const userMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, user_id, content, sender_type, role, channel,
attachment_filename, attachment_mimetype, attachment_size, attachment_data)
@@ -354,7 +354,7 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
logger.info('AI response received' + (aiResponseContent ? '' : ' (empty)'), { conversationId });
if (aiResponseContent) {
const aiMessageResult = await db.query(
const aiMessageResult = await db.getQuery()(
`INSERT INTO messages
(conversation_id, user_id, content, sender_type, role, channel)
VALUES ($1, $2, $3, 'assistant', 'assistant', 'web')
@@ -443,7 +443,7 @@ router.get('/history', requireAuth, async (req, res) => {
countQuery += ' AND conversation_id = $2';
countParams.push(conversationId);
}
const countResult = await db.query(countQuery, countParams);
const countResult = await db.getQuery()(countQuery, countParams);
const totalCount = parseInt(countResult.rows[0].count, 10);
return res.json({ success: true, count: totalCount });
}
@@ -481,7 +481,7 @@ router.get('/history', requireAuth, async (req, res) => {
logger.debug('Executing history query:', { query, params });
const result = await db.query(query, params);
const result = await db.getQuery()(query, params);
// Обрабатываем результаты для фронтенда
const messages = result.rows.map(msg => {
@@ -522,7 +522,7 @@ router.get('/history', requireAuth, async (req, res) => {
totalCountQuery += ' AND conversation_id = $2';
totalCountParams.push(conversationId);
}
const totalCountResult = await db.query(totalCountQuery, totalCountParams);
const totalCountResult = await db.getQuery()(totalCountQuery, totalCountParams);
const totalMessages = parseInt(totalCountResult.rows[0].count, 10);
logger.info(`Returning message history for user ${userId}`, { count: messages.length, offset, limit, total: totalMessages });

View File

@@ -11,7 +11,7 @@ const fs = require('fs');
* @desc Создать новое DLE (Digital Legal Entity)
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const dleParams = req.body;
logger.info('Получен запрос на создание DLE:', dleParams);
@@ -44,11 +44,7 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
});
} catch (error) {
logger.error('Ошибка при создании DLE:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при создании DLE',
error: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
next(error);
}
});
@@ -57,7 +53,7 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
* @desc Получить список всех DLE
* @access Private (только для авторизованных пользователей)
*/
router.get('/', auth.requireAuth, async (req, res) => {
router.get('/', auth.requireAuth, async (req, res, next) => {
try {
const dles = await dleService.getAllDLEs();
res.json({
@@ -66,11 +62,7 @@ router.get('/', auth.requireAuth, async (req, res) => {
});
} catch (error) {
logger.error('Ошибка при получении списка DLE:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при получении списка DLE',
error: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
next(error);
}
});
@@ -99,7 +91,7 @@ router.get('/settings', auth.requireAuth, (req, res) => {
* @desc Удалить DLE по адресу токена
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.delete('/:tokenAddress', auth.requireAuth, auth.requireAdmin, async (req, res) => {
router.delete('/:tokenAddress', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const { tokenAddress } = req.params;
logger.info(`Получен запрос на удаление DLE с адресом токена: ${tokenAddress}`);
@@ -142,11 +134,7 @@ router.delete('/:tokenAddress', auth.requireAuth, auth.requireAdmin, async (req,
});
} catch (error) {
logger.error('Ошибка при удалении DLE:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при удалении DLE',
error: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
next(error);
}
});
@@ -155,7 +143,7 @@ router.delete('/:tokenAddress', auth.requireAuth, auth.requireAdmin, async (req,
* @desc Удалить пустое DLE по имени файла
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.delete('/empty/:fileName', auth.requireAuth, auth.requireAdmin, async (req, res) => {
router.delete('/empty/:fileName', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const { fileName } = req.params;
logger.info(`Получен запрос на удаление пустого DLE с именем файла: ${fileName}`);
@@ -180,11 +168,7 @@ router.delete('/empty/:fileName', auth.requireAuth, auth.requireAdmin, async (re
});
} catch (error) {
logger.error('Ошибка при удалении пустого DLE:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при удалении пустого DLE',
error: process.env.NODE_ENV === 'development' ? error.stack : undefined
});
next(error);
}
});

View File

@@ -6,19 +6,19 @@ const logger = require('../utils/logger');
const db = require('../db');
// Получение всех идентификаторов пользователя
router.get('/', requireAuth, async (req, res) => {
router.get('/', requireAuth, async (req, res, next) => {
try {
const userId = req.session.userId;
const identities = await authService.getUserIdentities(userId);
res.json({ success: true, identities });
} catch (error) {
logger.error('Error getting identities:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Связывание нового идентификатора
router.post('/link', requireAuth, async (req, res) => {
router.post('/link', requireAuth, async (req, res, next) => {
try {
const { type, value } = req.body;
const userId = req.session.userId;
@@ -28,7 +28,7 @@ router.post('/link', requireAuth, async (req, res) => {
const normalizedWallet = value.toLowerCase();
// Проверяем, существует ли уже такой кошелек
const existingCheck = await db.query(
const existingCheck = await db.getQuery()(
`SELECT user_id FROM user_identities
WHERE provider = 'wallet' AND provider_id = $1`,
[normalizedWallet]
@@ -73,12 +73,12 @@ router.post('/link', requireAuth, async (req, res) => {
});
}
res.status(500).json({ error: error.message || 'Internal server error' });
next(error);
}
});
// Получение балансов токенов
router.get('/token-balances', requireAuth, async (req, res) => {
router.get('/token-balances', requireAuth, async (req, res, next) => {
try {
const userId = req.session.userId;
if (!userId) {
@@ -103,12 +103,12 @@ router.get('/token-balances', requireAuth, async (req, res) => {
});
} catch (error) {
logger.error('Error getting token balances:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Удаление идентификатора пользователя
router.delete('/:provider/:providerId', requireAuth, async (req, res) => {
router.delete('/:provider/:providerId', requireAuth, async (req, res, next) => {
try {
const userId = req.session.userId;
const { provider, providerId } = req.params;
@@ -120,7 +120,135 @@ router.delete('/:provider/:providerId', requireAuth, async (req, res) => {
}
} catch (error) {
logger.error('Error deleting identity:', error);
res.status(500).json({ error: 'Internal server error' });
next(error);
}
});
// Получение email-настроек
router.get('/email-settings', requireAuth, async (req, res, next) => {
try {
const { rows } = await db.getQuery()('SELECT * FROM email_settings ORDER BY id LIMIT 1');
if (!rows.length) return res.status(404).json({ success: false, error: 'Not found' });
const settings = rows[0];
delete settings.smtp_password; // не возвращаем пароль
res.json({ success: true, settings });
} catch (error) {
logger.error('Error getting email settings:', error, error && error.stack);
next(error);
}
});
// Обновление email-настроек
router.put('/email-settings', requireAuth, async (req, res, next) => {
try {
const { smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email } = req.body;
if (!smtp_host || !smtp_port || !smtp_user || !from_email) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
const { rows } = await db.getQuery()('SELECT id FROM email_settings ORDER BY id LIMIT 1');
if (rows.length) {
// Обновляем существующую запись
await db.getQuery()(
`UPDATE email_settings SET smtp_host=$1, smtp_port=$2, smtp_user=$3, smtp_password=COALESCE($4, smtp_password), imap_host=$5, imap_port=$6, from_email=$7, updated_at=NOW() WHERE id=$8`,
[smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email, rows[0].id]
);
} else {
// Вставляем новую
await db.getQuery()(
`INSERT INTO email_settings (smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email) VALUES ($1,$2,$3,$4,$5,$6,$7)`,
[smtp_host, smtp_port, smtp_user, smtp_password, imap_host, imap_port, from_email]
);
}
res.json({ success: true });
} catch (error) {
logger.error('Error updating email settings:', error);
next(error);
}
});
// Получение telegram-настроек
router.get('/telegram-settings', requireAuth, async (req, res, next) => {
try {
const { rows } = await db.getQuery()('SELECT * FROM telegram_settings ORDER BY id LIMIT 1');
if (!rows.length) return res.status(404).json({ success: false, error: 'Not found' });
const settings = rows[0];
delete settings.bot_token; // не возвращаем токен
res.json({ success: true, settings });
} catch (error) {
logger.error('Error getting telegram settings:', error, error && error.stack);
next(error);
}
});
// Обновление telegram-настроек
router.put('/telegram-settings', requireAuth, async (req, res, next) => {
try {
const { bot_token, bot_username } = req.body;
if (!bot_token || !bot_username) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
const { rows } = await db.getQuery()('SELECT id FROM telegram_settings ORDER BY id LIMIT 1');
if (rows.length) {
// Обновляем существующую запись
await db.getQuery()(
`UPDATE telegram_settings SET bot_token=$1, bot_username=$2, updated_at=NOW() WHERE id=$3`,
[bot_token, bot_username, rows[0].id]
);
} else {
// Вставляем новую
await db.getQuery()(
`INSERT INTO telegram_settings (bot_token, bot_username) VALUES ($1,$2)` ,
[bot_token, bot_username]
);
}
res.json({ success: true });
} catch (error) {
logger.error('Error updating telegram settings:', error);
next(error);
}
});
// Получение db-настроек
router.get('/db-settings', requireAuth, async (req, res, next) => {
try {
const { rows } = await db.getQuery()('SELECT * FROM db_settings ORDER BY id LIMIT 1');
if (!rows.length) return res.status(404).json({ success: false, error: 'Not found' });
const settings = rows[0];
delete settings.db_password; // не возвращаем пароль
res.json({ success: true, settings });
} catch (error) {
logger.error('Error getting db settings:', error, error && error.stack);
next(error);
}
});
// Обновление db-настроек
router.put('/db-settings', requireAuth, async (req, res, next) => {
try {
const { db_host, db_port, db_name, db_user, db_password } = req.body;
if (!db_host || !db_port || !db_name || !db_user) {
return res.status(400).json({ success: false, error: 'Missing required fields' });
}
const { rows } = await db.getQuery()('SELECT id FROM db_settings ORDER BY id LIMIT 1');
if (rows.length) {
// Обновляем существующую запись
await db.getQuery()(
`UPDATE db_settings SET db_host=$1, db_port=$2, db_name=$3, db_user=$4, db_password=COALESCE($5, db_password), updated_at=NOW() WHERE id=$6`,
[db_host, db_port, db_name, db_user, db_password, rows[0].id]
);
} else {
// Вставляем новую
await db.getQuery()(
`INSERT INTO db_settings (db_host, db_port, db_name, db_user, db_password) VALUES ($1,$2,$3,$4,$5)` ,
[db_host, db_port, db_name, db_user, db_password]
);
}
// Пересоздаём пул соединений с новыми настройками
await db.reinitPoolFromDbSettings();
res.json({ success: true });
} catch (error) {
logger.error('Error updating db settings:', error);
next(error);
}
});

View File

@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
const { pool } = require('../db'); // Убедитесь, что путь к вашему db-коннектору правильный
const db = require('../db');
const logger = require('../utils/logger'); // Если используете логгер
/**
@@ -98,7 +98,7 @@ router.get('/codes', async (req, res) => {
if (parent_code) {
try {
const parentResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]);
const parentResult = await db.getQuery()('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]);
if (parentResult.rows.length > 0) {
const parentLevel = parentResult.rows[0].code_level;
if (parentLevel >= 1 && parentLevel < 6) {
@@ -146,7 +146,7 @@ router.get('/codes', async (req, res) => {
}
if (parent_code) {
// Предполагаем, что parent_code уже добавлен в countQueryParams
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Нужно будет передать parent_code в countQueryParams
const parentLevelResult = await db.getQuery()('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Нужно будет передать parent_code в countQueryParams
if (parentLevelResult.rows.length > 0) {
const parentLevel = parentLevelResult.rows[0].code_level;
if (parentLevel >=1 && parentLevel < 6) {
@@ -174,7 +174,7 @@ router.get('/codes', async (req, res) => {
const queryWhereConditions = [];
if (level) queryWhereConditions.push(`c.code_level = $${currentQueryParamIndex++}`);
if (parent_code) {
const parentLevelResult = await pool.query('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Это дублирование, лучше получить parentLevel один раз
const parentLevelResult = await db.getQuery()('SELECT code_level FROM isic_rev4_codes WHERE code = $1', [parent_code]); // Это дублирование, лучше получить parentLevel один раз
if (parentLevelResult.rows.length > 0) {
const parentLevel = parentLevelResult.rows[0].code_level;
if (parentLevel >=1 && parentLevel < 6) {
@@ -193,12 +193,12 @@ router.get('/codes', async (req, res) => {
try {
logger.debug('Executing count query:', finalCountQuery, 'Params:', countQueryParams);
const totalItemsResult = await pool.query(finalCountQuery, countQueryParams);
const totalItemsResult = await db.getQuery()(finalCountQuery, countQueryParams);
const totalItems = parseInt(totalItemsResult.rows[0].total, 10);
// Параметры для основного запроса - это все, что в queryParams (включая limit и offset)
logger.debug('Executing data query:', finalQuery, 'Params:', queryParams);
const result = await pool.query(finalQuery, queryParams);
const result = await db.getQuery()(finalQuery, queryParams);
res.json({
totalItems,
@@ -253,13 +253,13 @@ router.get('/tree', async (req, res) => {
try {
let items;
if (!root_code) { // Если нет root_code, возвращаем секции (уровень 1)
const result = await pool.query(
const result = await db.getQuery()(
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code_level = 1 ORDER BY sort_order, code"
);
items = result.rows.map(row => ({ ...row, children: [] })); // Добавляем пустой массив children
} else {
// Получаем сам root_code
const rootResult = await pool.query(
const rootResult = await db.getQuery()(
"SELECT code, description, code_level FROM isic_rev4_codes WHERE code = $1",
[root_code]
);
@@ -281,7 +281,7 @@ router.get('/tree', async (req, res) => {
if (childrenQuery) {
const childrenResult = await pool.query(childrenQuery, childrenParams);
const childrenResult = await db.getQuery()(childrenQuery, childrenParams);
rootNode.children = childrenResult.rows.map(row => ({ ...row, children: [] }));
}
items = [rootNode];

View File

@@ -5,23 +5,25 @@ const logger = require('../utils/logger');
const { ethers } = require('ethers');
const rpcProviderService = require('../services/rpcProviderService');
const authTokenService = require('../services/authTokenService');
const aiProviderSettingsService = require('../services/aiProviderSettingsService');
const aiAssistant = require('../services/ai-assistant');
// Логируем версию ethers для отладки
logger.info(`Ethers version: ${ethers.version || 'unknown'}`);
// Получение RPC настроек
router.get('/rpc', requireAdmin, async (req, res) => {
router.get('/rpc', requireAdmin, async (req, res, next) => {
try {
const rpcConfigs = await rpcProviderService.getAllRpcProviders();
res.json({ success: true, data: rpcConfigs });
} catch (error) {
logger.error('Ошибка при получении RPC настроек:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при получении настроек RPC' });
next(error);
}
});
// Добавление/обновление одного или нескольких RPC
router.post('/rpc', requireAdmin, async (req, res) => {
router.post('/rpc', requireAdmin, async (req, res, next) => {
try {
// Если пришёл массив rpcConfigs — bulk-режим
if (Array.isArray(req.body.rpcConfigs)) {
@@ -41,35 +43,35 @@ router.post('/rpc', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'RPC провайдер сохранён' });
} catch (error) {
logger.error('Ошибка при сохранении RPC:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении RPC' });
next(error);
}
});
// Удаление одного RPC
router.delete('/rpc/:networkId', requireAdmin, async (req, res) => {
router.delete('/rpc/:networkId', requireAdmin, async (req, res, next) => {
try {
const { networkId } = req.params;
await rpcProviderService.deleteRpcProvider(networkId);
res.json({ success: true, message: 'RPC провайдер удалён' });
} catch (error) {
logger.error('Ошибка при удалении RPC:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при удалении RPC' });
next(error);
}
});
// Получение токенов для аутентификации
router.get('/auth-tokens', requireAdmin, async (req, res) => {
router.get('/auth-tokens', requireAdmin, async (req, res, next) => {
try {
const authTokens = await authTokenService.getAllAuthTokens();
res.json({ success: true, data: authTokens });
} catch (error) {
logger.error('Ошибка при получении токенов аутентификации:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при получении токенов аутентификации' });
next(error);
}
});
// Сохранение токенов для аутентификации
router.post('/auth-tokens', requireAdmin, async (req, res) => {
router.post('/auth-tokens', requireAdmin, async (req, res, next) => {
try {
const { authTokens } = req.body;
if (!Array.isArray(authTokens)) {
@@ -79,12 +81,12 @@ router.post('/auth-tokens', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'Токены аутентификации успешно сохранены' });
} catch (error) {
logger.error('Ошибка при сохранении токенов аутентификации:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении токенов аутентификации' });
next(error);
}
});
// Добавление/обновление одного токена
router.post('/auth-token', requireAdmin, async (req, res) => {
router.post('/auth-token', requireAdmin, async (req, res, next) => {
try {
const { name, address, network, minBalance } = req.body;
if (!name || !address || !network) {
@@ -94,24 +96,24 @@ router.post('/auth-token', requireAdmin, async (req, res) => {
res.json({ success: true, message: 'Токен аутентификации сохранён' });
} catch (error) {
logger.error('Ошибка при сохранении токена аутентификации:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении токена' });
next(error);
}
});
// Удаление одного токена
router.delete('/auth-token/:address/:network', requireAdmin, async (req, res) => {
router.delete('/auth-token/:address/:network', requireAdmin, async (req, res, next) => {
try {
const { address, network } = req.params;
await authTokenService.deleteAuthToken(address, network);
res.json({ success: true, message: 'Токен аутентификации удалён' });
} catch (error) {
logger.error('Ошибка при удалении токена аутентификации:', error);
res.status(500).json({ success: false, error: 'Ошибка сервера при удалении токена' });
next(error);
}
});
// Тестирование RPC соединения
router.post('/rpc-test', requireAdmin, async (req, res) => {
router.post('/rpc-test', requireAdmin, async (req, res, next) => {
try {
const { rpcUrl, networkId } = req.body;
@@ -164,4 +166,76 @@ router.post('/rpc-test', requireAdmin, async (req, res) => {
}
});
// Получить настройки AI-провайдера
router.get('/ai-settings/:provider', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
const settings = await aiProviderSettingsService.getProviderSettings(provider);
res.json({ success: true, settings });
} catch (error) {
logger.error('Ошибка при получении AI-настроек:', error);
next(error);
}
});
// Сохранить/обновить настройки AI-провайдера
router.put('/ai-settings/:provider', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
const { api_key, base_url, selected_model } = req.body;
const updated = await aiProviderSettingsService.upsertProviderSettings({ provider, api_key, base_url, selected_model });
res.json({ success: true, settings: updated });
} catch (error) {
logger.error('Ошибка при сохранении AI-настроек:', error);
next(error);
}
});
// Удалить настройки AI-провайдера
router.delete('/ai-settings/:provider', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
await aiProviderSettingsService.deleteProviderSettings(provider);
res.json({ success: true });
} catch (error) {
logger.error('Ошибка при удалении AI-настроек:', error);
next(error);
}
});
// Получить список моделей для провайдера
router.get('/ai-settings/:provider/models', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
const settings = await aiProviderSettingsService.getProviderSettings(provider);
let models = [];
if (provider === 'ollama') {
models = await aiAssistant.getAvailableModels();
} else {
models = await aiProviderSettingsService.getProviderModels(provider, settings || {});
}
res.json({ success: true, models });
} catch (error) {
logger.error('Ошибка при получении моделей AI:', error);
res.status(500).json({ success: false, error: error.message });
}
});
// Проверить валидность ключа (verify)
router.post('/ai-settings/:provider/verify', requireAdmin, async (req, res, next) => {
try {
const { provider } = req.params;
const { api_key, base_url } = req.body;
const result = await aiProviderSettingsService.verifyProviderKey(provider, { api_key, base_url });
if (result.success) {
res.json({ success: true });
} else {
res.status(400).json({ success: false, error: result.error });
}
} catch (error) {
logger.error('Ошибка при проверке AI-ключа:', error);
res.status(500).json({ success: false, error: error.message });
}
});
module.exports = router;

View File

@@ -4,7 +4,7 @@ const logger = require('../utils/logger');
const authService = require('../services/auth-service');
// Получение балансов токенов пользователя по токенам из базы
router.get('/balances', async (req, res) => {
router.get('/balances', async (req, res, next) => {
try {
const address = req.query.address;
if (!address) {
@@ -15,7 +15,7 @@ router.get('/balances', async (req, res) => {
res.json({ success: true, data: balances });
} catch (error) {
logger.error('Error fetching token balances:', error);
res.status(500).json({ success: false, error: 'Failed to fetch token balances' });
next(error);
}
});

View File

@@ -20,53 +20,42 @@ router.get('/:address', (req, res) => {
});
// Маршрут для обновления языка пользователя
router.post('/update-language', requireAuth, async (req, res) => {
router.post('/update-language', requireAuth, async (req, res, next) => {
try {
const { language } = req.body;
const userId = req.session.userId;
// Проверка валидности языка
const validLanguages = ['ru', 'en'];
if (!validLanguages.includes(language)) {
return res.status(400).json({ error: 'Неподдерживаемый язык' });
}
// Обновление языка в базе данных
await db.query('UPDATE users SET preferred_language = $1 WHERE id = $2', [language, userId]);
await db.getQuery()('UPDATE users SET preferred_language = $1 WHERE id = $2', [language, userId]);
res.json({ success: true });
} catch (error) {
logger.error('Error updating language:', error);
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
next(error);
}
});
// Маршрут для обновления имени и фамилии пользователя
router.post('/update-profile', requireAuth, async (req, res) => {
router.post('/update-profile', requireAuth, async (req, res, next) => {
try {
const { firstName, lastName } = req.body;
const userId = req.session.userId;
// Проверка валидности данных
if (firstName && firstName.length > 255) {
return res.status(400).json({ error: 'Имя слишком длинное (максимум 255 символов)' });
}
if (lastName && lastName.length > 255) {
return res.status(400).json({ error: 'Фамилия слишком длинная (максимум 255 символов)' });
}
// Обновление имени и фамилии в базе данных
await db.query('UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3', [
await db.getQuery()('UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3', [
firstName || null,
lastName || null,
userId,
]);
res.json({ success: true });
} catch (error) {
logger.error('Error updating user profile:', error);
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
next(error);
}
});

View File

@@ -1,7 +1,8 @@
const fs = require('fs').promises;
const path = require('path');
require('dotenv').config();
const { pool } = require('../db');
const { getPool } = require('../db');
const pool = getPool();
const logger = require('../utils/logger');
async function runMigrations() {

View File

@@ -0,0 +1,105 @@
const db = require('../db');
const OpenAI = require('openai');
const Anthropic = require('@anthropic-ai/sdk');
const { GoogleGenAI } = require('@google/genai');
const TABLE = 'ai_providers_settings';
async function getProviderSettings(provider) {
const { rows } = await db.getQuery()(
`SELECT * FROM ${TABLE} WHERE provider = $1 LIMIT 1`,
[provider]
);
return rows[0] || null;
}
async function upsertProviderSettings({ provider, api_key, base_url, selected_model }) {
const { rows } = await db.getQuery()(
`INSERT INTO ${TABLE} (provider, api_key, base_url, selected_model, updated_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (provider) DO UPDATE SET
api_key = EXCLUDED.api_key,
base_url = EXCLUDED.base_url,
selected_model = EXCLUDED.selected_model,
updated_at = NOW()
RETURNING *`,
[provider, api_key, base_url, selected_model]
);
return rows[0];
}
async function deleteProviderSettings(provider) {
await db.getQuery()(
`DELETE FROM ${TABLE} WHERE provider = $1`,
[provider]
);
}
async function getProviderModels(provider, { api_key, base_url } = {}) {
try {
if (provider === 'openai') {
const client = new OpenAI({ apiKey: api_key, baseURL: base_url });
const res = await client.models.list();
return res.data ? res.data.map(m => ({ id: m.id, ...m })) : [];
}
if (provider === 'anthropic') {
const client = new Anthropic({ apiKey: api_key, baseURL: base_url });
const res = await client.models.list();
return res.data ? res.data.map(m => ({ id: m.id, ...m })) : [];
}
if (provider === 'google') {
const ai = new GoogleGenAI({ apiKey: api_key, baseUrl: base_url });
const pager = await ai.models.list();
const models = [];
for await (const model of pager) {
models.push(model);
}
return models;
}
if (provider === 'ollama') {
// Для Ollama — через ai-assistant.js
return [];
}
return [];
} catch (error) {
return [];
}
}
async function verifyProviderKey(provider, { api_key, base_url } = {}) {
try {
if (provider === 'openai') {
const client = new OpenAI({ apiKey: api_key, baseURL: base_url });
await client.models.list();
return { success: true };
}
if (provider === 'anthropic') {
const client = new Anthropic({ apiKey: api_key, baseURL: base_url });
await client.models.list();
return { success: true };
}
if (provider === 'google') {
const ai = new GoogleGenAI({ apiKey: api_key, baseUrl: base_url });
const pager = await ai.models.list();
for await (const _ of pager) {
break;
}
return { success: true };
}
if (provider === 'ollama') {
// Для Ollama — всегда true (локальный)
return { success: true };
}
return { success: false, error: 'Unknown provider' };
} catch (error) {
return { success: false, error: error.message };
}
}
module.exports = {
getProviderSettings,
upsertProviderSettings,
deleteProviderSettings,
getProviderModels,
verifyProviderKey,
};

View File

@@ -1,7 +1,7 @@
const db = require('../db');
async function getAllAuthTokens() {
const { rows } = await db.query('SELECT * FROM auth_tokens ORDER BY id');
const { rows } = await db.getQuery()('SELECT * FROM auth_tokens ORDER BY id');
return rows;
}

View File

@@ -1,13 +1,13 @@
const { pool } = require('../db');
const verificationService = require('./verification-service');
const logger = require('../utils/logger');
const emailBot = require('./emailBot');
const EmailBotService = require('./emailBot');
const db = require('../db');
const authService = require('./auth-service');
class EmailAuth {
constructor() {
this.emailBot = emailBot;
this.emailBot = new EmailBotService();
}
async initEmailAuth(session, email) {
@@ -17,7 +17,7 @@ class EmailAuth {
}
// Проверяем, существует ли пользователь с таким email
const existingEmailUser = await db.query(
const existingEmailUser = await db.getQuery()(
`SELECT u.id FROM users u
JOIN user_identities i ON u.id = i.user_id
WHERE i.provider = 'email' AND i.provider_id = $1`,

View File

@@ -1,4 +1,4 @@
const { pool } = require('../db');
const db = require('../db');
const nodemailer = require('nodemailer');
const Imap = require('imap');
const simpleParser = require('mailparser').simpleParser;
@@ -6,61 +6,47 @@ const { processMessage } = require('./ai-assistant');
const { inspect } = require('util');
const logger = require('../utils/logger');
// Конфигурация для отправки писем
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_SMTP_HOST || 'smtp.hostland.ru',
port: process.env.EMAIL_SMTP_PORT || 465,
secure: true,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
pool: true,
maxConnections: 3,
maxMessages: 5,
tls: {
rejectUnauthorized: false,
},
});
// Конфигурация для получения писем
const imapConfig = {
user: process.env.EMAIL_USER,
password: process.env.EMAIL_PASSWORD,
host: process.env.EMAIL_IMAP_HOST,
port: process.env.EMAIL_IMAP_PORT,
tls: true,
tlsOptions: { rejectUnauthorized: false },
keepalive: {
interval: 10000,
idleInterval: 300000,
forceNoop: true,
},
};
class EmailBotService {
constructor() {
this.transporter = transporter;
this.imap = new Imap(imapConfig);
this.initialize();
async getSettingsFromDb() {
const { rows } = await db.getQuery()('SELECT * FROM email_settings ORDER BY id LIMIT 1');
if (!rows.length) throw new Error('Email settings not found in DB');
return rows[0];
}
initialize() {
this.imap.once('error', (err) => {
logger.error(`IMAP connection error: ${err.message}`);
setTimeout(() => {
try {
if (this.imap.state !== 'connected') {
this.imap = new Imap(imapConfig);
this.initialize();
}
} catch (e) {
logger.error(`Error reconnecting IMAP: ${e.message}`);
}
}, 60000);
async getTransporter() {
const settings = await this.getSettingsFromDb();
return nodemailer.createTransport({
host: settings.smtp_host,
port: settings.smtp_port,
secure: true,
auth: {
user: settings.smtp_user,
pass: settings.smtp_password,
},
pool: true,
maxConnections: 3,
maxMessages: 5,
tls: { rejectUnauthorized: false },
});
}
async getImapConfig() {
const settings = await this.getSettingsFromDb();
return {
user: settings.smtp_user,
password: settings.smtp_password,
host: settings.imap_host,
port: settings.imap_port,
tls: true,
tlsOptions: { rejectUnauthorized: false },
keepalive: {
interval: 10000,
idleInterval: 300000,
forceNoop: true,
},
};
}
// Метод для инициализации email верификации
async initEmailVerification(email, userId, code) {
try {
@@ -77,24 +63,16 @@ class EmailBotService {
// Отправка кода верификации
async sendVerificationCode(email, code) {
try {
const settings = await this.getSettingsFromDb();
const transporter = await this.getTransporter();
const mailOptions = {
from: process.env.EMAIL_USER,
from: settings.from_email,
to: email,
subject: 'Код подтверждения',
text: `Ваш код подтверждения: ${code}\n\nКод действителен в течение 15 минут.`,
html: `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<h2 style="color: #333;">Код подтверждения</h2>
<p style="font-size: 16px; color: #666;">Ваш код подтверждения:</p>
<div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; margin: 20px 0;">
<span style="font-size: 24px; font-weight: bold; color: #333;">${code}</span>
</div>
<p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p>
</div>
`,
html: `<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;"><h2 style="color: #333;">Код подтверждения</h2><p style="font-size: 16px; color: #666;">Ваш код подтверждения:</p><div style="background-color: #f5f5f5; padding: 15px; border-radius: 5px; text-align: center; margin: 20px 0;"><span style="font-size: 24px; font-weight: bold; color: #333;">${code}</span></div><p style="font-size: 14px; color: #999;">Код действителен в течение 15 минут.</p></div>`,
};
await this.transporter.sendMail(mailOptions);
await transporter.sendMail(mailOptions);
logger.info(`Verification code sent to ${email}`);
} catch (error) {
logger.error('Error sending verification code:', error);
@@ -187,14 +165,15 @@ class EmailBotService {
// Метод для отправки email
async sendEmail(to, subject, text) {
try {
const settings = await this.getSettingsFromDb();
const transporter = await this.getTransporter();
const mailOptions = {
from: process.env.EMAIL_USER,
from: settings.from_email,
to,
subject,
text,
};
await this.transporter.sendMail(mailOptions);
await transporter.sendMail(mailOptions);
logger.info(`Email sent to ${to}`);
return true;
} catch (error) {
@@ -204,5 +183,4 @@ class EmailBotService {
}
}
// Экспортируем singleton instance
module.exports = new EmailBotService();
module.exports = EmailBotService;

View File

@@ -62,7 +62,7 @@ class IdentityService {
);
try {
await db.query(
await db.getQuery()(
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
[userId, normalizedProviderId]
);
@@ -91,7 +91,7 @@ class IdentityService {
);
// Проверяем, существует ли уже такой идентификатор
const existingResult = await db.query(
const existingResult = await db.getQuery()(
`SELECT user_id FROM user_identities WHERE provider = $1 AND provider_id = $2`,
[normalizedProvider, normalizedProviderId]
);
@@ -116,7 +116,7 @@ class IdentityService {
}
} else {
// Создаем новую запись
await db.query(
await db.getQuery()(
`INSERT INTO user_identities (user_id, provider, provider_id)
VALUES ($1, $2, $3)`,
[userId, normalizedProvider, normalizedProviderId]
@@ -148,7 +148,7 @@ class IdentityService {
return [];
}
const result = await db.query(
const result = await db.getQuery()(
`SELECT provider, provider_id FROM user_identities WHERE user_id = $1`,
[userId]
);
@@ -174,7 +174,7 @@ class IdentityService {
return [];
}
const result = await db.query(
const result = await db.getQuery()(
`SELECT provider_id FROM user_identities WHERE user_id = $1 AND provider = $2`,
[userId, provider]
);
@@ -211,7 +211,7 @@ class IdentityService {
const { provider: normalizedProvider, providerId: normalizedProviderId } =
this.normalizeIdentity(provider, providerId);
const result = await db.query(
const result = await db.getQuery()(
`SELECT u.id, u.role FROM users u
JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.provider = $1 AND ui.provider_id = $2`,
@@ -255,7 +255,7 @@ class IdentityService {
// Нормализуем провайдера
const normalizedProvider = provider.toLowerCase();
const result = await db.query(
const result = await db.getQuery()(
`SELECT provider, provider_id, created_at, updated_at
FROM user_identities
WHERE user_id = $1 AND provider = $2
@@ -320,7 +320,7 @@ class IdentityService {
// Сохраняем гостевые идентификаторы в guest_user_mapping
if (session.guestId) {
try {
await db.query(
await db.getQuery()(
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
[userId, session.guestId]
);
@@ -333,7 +333,7 @@ class IdentityService {
if (session.previousGuestId && session.previousGuestId !== session.guestId) {
try {
await db.query(
await db.getQuery()(
'INSERT INTO guest_user_mapping (user_id, guest_id) VALUES ($1, $2) ON CONFLICT (guest_id) DO UPDATE SET user_id = $1',
[userId, session.previousGuestId]
);
@@ -479,7 +479,7 @@ class IdentityService {
for (const [provider, providerId] of Object.entries(identities)) {
if (!providerId) continue;
const result = await db.query(
const result = await db.getQuery()(
`SELECT DISTINCT user_id
FROM user_identities
WHERE provider = $1 AND provider_id = $2`,
@@ -510,7 +510,7 @@ class IdentityService {
return { success: false, error: 'Missing required parameters' };
}
const { provider: normalizedProvider, providerId: normalizedProviderId } = this.normalizeIdentity(provider, providerId);
const result = await db.query(
const result = await db.getQuery()(
`DELETE FROM user_identities WHERE user_id = $1 AND provider = $2 AND provider_id = $3`,
[userId, normalizedProvider, normalizedProviderId]
);

View File

@@ -1,7 +1,7 @@
const db = require('../db');
async function getAllRpcProviders() {
const { rows } = await db.query('SELECT * FROM rpc_providers ORDER BY id');
const { rows } = await db.getQuery()('SELECT * FROM rpc_providers ORDER BY id');
return rows;
}

View File

@@ -51,7 +51,7 @@ class SessionService {
}
// Получаем все гостевые ID для текущего пользователя из новой таблицы
const guestIdsResult = await db.query(
const guestIdsResult = await db.getQuery()(
'SELECT guest_id FROM guest_user_mapping WHERE user_id = $1',
[userId]
);

View File

@@ -6,11 +6,21 @@ const verificationService = require('./verification-service');
const crypto = require('crypto');
let botInstance = null;
let telegramSettingsCache = null;
async function getTelegramSettings() {
if (telegramSettingsCache) return telegramSettingsCache;
const { rows } = await db.getQuery()('SELECT * FROM telegram_settings ORDER BY id LIMIT 1');
if (!rows.length) throw new Error('Telegram settings not found in DB');
telegramSettingsCache = rows[0];
return telegramSettingsCache;
}
// Создание и настройка бота
async function getBot() {
if (!botInstance) {
botInstance = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
const settings = await getTelegramSettings();
botInstance = new Telegraf(settings.bot_token);
// Обработка команды /start
botInstance.command('start', (ctx) => {
@@ -23,7 +33,7 @@ async function getBot() {
try {
// Получаем код верификации для всех активных кодов с провайдером telegram
const codeResult = await db.query(
const codeResult = await db.getQuery()(
`SELECT * FROM verification_codes
WHERE code = $1
AND provider = 'telegram'
@@ -44,14 +54,14 @@ async function getBot() {
let userRole = 'user'; // Роль по умолчанию
// Отмечаем код как использованный
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [
await db.getQuery()('UPDATE verification_codes SET used = true WHERE id = $1', [
verification.id,
]);
logger.info('Starting Telegram auth process for code:', code);
// Проверяем, существует ли уже пользователь с таким Telegram ID
const existingTelegramUser = await db.query(
const existingTelegramUser = await db.getQuery()(
`SELECT ui.user_id
FROM user_identities ui
WHERE ui.provider = 'telegram' AND ui.provider_id = $1`,
@@ -68,7 +78,7 @@ async function getBot() {
// Используем userId из кода верификации
userId = linkedUserId;
// Связываем Telegram с этим пользователем
await db.query(
await db.getQuery()(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())`,
@@ -81,7 +91,7 @@ async function getBot() {
// Проверяем, есть ли пользователь, связанный с гостевым идентификатором
let existingUserWithGuestId = null;
if (providerId) {
const guestUserResult = await db.query(
const guestUserResult = await db.getQuery()(
`SELECT user_id FROM guest_user_mapping WHERE guest_id = $1`,
[providerId]
);
@@ -96,7 +106,7 @@ async function getBot() {
if (existingUserWithGuestId) {
// Используем существующего пользователя и добавляем ему Telegram идентификатор
userId = existingUserWithGuestId;
await db.query(
await db.getQuery()(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())`,
@@ -105,14 +115,14 @@ async function getBot() {
logger.info(`Linked Telegram account ${ctx.from.id} to existing user ${userId}`);
} else {
// Создаем нового пользователя, если не нашли существующего
const userResult = await db.query(
const userResult = await db.getQuery()(
'INSERT INTO users (created_at, role) VALUES (NOW(), $1) RETURNING id',
['user']
);
userId = userResult.rows[0].id;
// Связываем Telegram с новым пользователем
await db.query(
await db.getQuery()(
`INSERT INTO user_identities
(user_id, provider, provider_id, created_at)
VALUES ($1, $2, $3, NOW())`,
@@ -121,7 +131,7 @@ async function getBot() {
// Если был гостевой ID, связываем его с новым пользователем
if (providerId) {
await db.query(
await db.getQuery()(
`INSERT INTO guest_user_mapping
(user_id, guest_id)
VALUES ($1, $2)
@@ -147,15 +157,15 @@ async function getBot() {
logger.info(`[TelegramBot] Role for user ${userId} determined as: ${userRole}`);
// Опционально: Обновить роль в таблице users
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0 && currentUser.rows[0].role !== userRole) {
await db.query('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
await db.getQuery()('UPDATE users SET role = $1 WHERE id = $2', [userRole, userId]);
logger.info(`[TelegramBot] Updated user role in DB to ${userRole}`);
}
} else {
logger.info(`[TelegramBot] No linked wallet found for user ${userId}. Checking current DB role.`);
// Если кошелька нет, берем текущую роль из базы
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) {
userRole = currentUser.rows[0].role;
}
@@ -164,7 +174,7 @@ async function getBot() {
logger.error(`[TelegramBot] Error checking admin role for user ${userId}:`, roleCheckError);
// В случае ошибки берем роль из базы или оставляем 'user'
try {
const currentUser = await db.query('SELECT role FROM users WHERE id = $1', [userId]);
const currentUser = await db.getQuery()('SELECT role FROM users WHERE id = $1', [userId]);
if (currentUser.rows.length > 0) { userRole = currentUser.rows[0].role; }
} catch (dbError) { /* ignore */ }
}
@@ -181,7 +191,7 @@ async function getBot() {
try {
// Ищем сессию, где есть userId и она не истекла (проверка expires_at)
// Сортируем по expires_at DESC чтобы взять самую "свежую", если их несколько
const sessionResult = await db.query(
const sessionResult = await db.getQuery()(
`SELECT sid FROM session
WHERE sess ->> 'userId' = $1
AND expire > NOW()
@@ -195,7 +205,7 @@ async function getBot() {
logger.info(`[telegramBot] Found active session ID ${activeSessionId} for user ${userId}`);
// Обновляем найденную сессию в базе данных, добавляя/перезаписывая данные Telegram
const updateResult = await db.query(
const updateResult = await db.getQuery()(
`UPDATE session
SET sess = (sess::jsonb || $1::jsonb)::json
WHERE sid = $2`,
@@ -275,7 +285,7 @@ async function initTelegramAuth(session) {
const guestId = session.guestId || tempId;
// Связываем гостевой ID с текущим пользователем
await db.query(
await db.getQuery()(
`INSERT INTO guest_user_mapping (user_id, guest_id)
VALUES ($1, $2)
ON CONFLICT (guest_id) DO UPDATE SET user_id = $1`,
@@ -298,9 +308,10 @@ async function initTelegramAuth(session) {
`[initTelegramAuth] Created verification code for guestId: ${session.guestId || tempId}${session.authenticated ? `, userId: ${session.userId}` : ''}`
);
const settings = await getTelegramSettings();
return {
verificationCode: code,
botLink: `https://t.me/${process.env.TELEGRAM_BOT_USERNAME}`,
botLink: `https://t.me/${settings.bot_username}`,
};
} catch (error) {
logger.error('Error initializing Telegram auth:', error);
@@ -308,8 +319,13 @@ async function initTelegramAuth(session) {
}
}
function clearSettingsCache() {
telegramSettingsCache = null;
}
module.exports = {
getBot,
stopBot,
initTelegramAuth,
clearSettingsCache,
};

View File

@@ -29,14 +29,14 @@ class VerificationService {
// Если userId не указан, добавляем запись без ссылки на пользователя
if (userId === null || userId === undefined) {
await db.query(
await db.getQuery()(
`INSERT INTO verification_codes
(code, provider, provider_id, expires_at)
VALUES ($1, $2, $3, $4)`,
[code, provider, providerId, expiresAt]
);
} else {
await db.query(
await db.getQuery()(
`INSERT INTO verification_codes
(code, provider, provider_id, user_id, expires_at)
VALUES ($1, $2, $3, $4, $5)`,
@@ -67,7 +67,7 @@ class VerificationService {
logger.info(`Normalized code: ${normalizedCode}`);
// Проверим, есть ли такой код в базе (для отладки)
const checkResult = await db.query(
const checkResult = await db.getQuery()(
`SELECT code FROM verification_codes
WHERE provider = $1
AND provider_id = $2
@@ -84,7 +84,7 @@ class VerificationService {
logger.warn(`No active codes found for ${provider}:${providerId}`);
}
const result = await db.query(
const result = await db.getQuery()(
`SELECT * FROM verification_codes
WHERE code = $1
AND provider = $2
@@ -104,7 +104,10 @@ class VerificationService {
const verification = result.rows[0];
// Отмечаем код как использованный
await db.query('UPDATE verification_codes SET used = true WHERE id = $1', [verification.id]);
await db.getQuery()(
'UPDATE verification_codes SET used = true WHERE id = $1',
[verification.id]
);
logger.info(`Code verified successfully for ${provider}:${providerId}`);
return {
@@ -126,7 +129,7 @@ class VerificationService {
// Очистка истекших кодов
async cleanupExpiredCodes() {
try {
const result = await db.query(
const result = await db.getQuery()(
'DELETE FROM verification_codes WHERE expires_at <= NOW() RETURNING id'
);
logger.info(`Cleaned up ${result.rowCount} expired verification codes`);

View File

@@ -21,7 +21,7 @@ function generateVerificationCode(length = 6) {
// Проверка существования идентификатора пользователя
async function checkUserIdentity(userId, provider, providerId) {
const result = await db.query(
const result = await db.getQuery()(
'SELECT * FROM user_identities WHERE user_id = $1 AND provider = $2 AND provider_id = $3',
[userId, provider, providerId]
);
@@ -31,7 +31,7 @@ async function checkUserIdentity(userId, provider, providerId) {
// Добавление новой идентификации
async function addUserIdentity(userId, provider, providerId) {
try {
await db.query(
await db.getQuery()(
'INSERT INTO user_identities (user_id, provider, provider_id) VALUES ($1, $2, $3)',
[userId, provider, providerId]
);

View File

@@ -12,6 +12,11 @@
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33"
integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==
"@anthropic-ai/sdk@^0.51.0":
version "0.51.0"
resolved "https://registry.yarnpkg.com/@anthropic-ai/sdk/-/sdk-0.51.0.tgz#2cd022c47e0eb6f4d645d8e8ed9ee0f3c5745ea8"
integrity sha512-fAFC/uHhyzfw7rs65EPVV+scXDytGNm5BjttxHf6rP/YGvaBRKEvp2lwyuMigTwMI95neeG4bzrZigz7KCikjw==
"@colors/colors@1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
@@ -354,6 +359,16 @@
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d"
integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==
"@google/genai@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@google/genai/-/genai-1.0.1.tgz#ccef337f733f27fdc2e5bf741af51889e2847082"
integrity sha512-qf8sq9vpuKUeBKukAn43z2eC1I/Jw63b9wo6O+1x3EIroF3oDouJOtW1AzwvfO+9gzCPfLjuCUONhMKiBC8vkQ==
dependencies:
google-auth-library "^9.14.2"
ws "^8.18.0"
zod "^3.22.4"
zod-to-json-schema "^3.22.4"
"@humanfs/core@^0.19.1":
version "0.19.1"
resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77"
@@ -1223,6 +1238,11 @@ agent-base@6:
dependencies:
debug "4"
agent-base@^7.1.2:
version "7.1.3"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1"
integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
agentkeepalive@^4.2.1:
version "4.6.0"
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a"
@@ -1504,7 +1524,7 @@ base-x@^3.0.2:
dependencies:
safe-buffer "^5.0.1"
base64-js@^1.5.1:
base64-js@^1.3.0, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -1516,6 +1536,11 @@ bcrypt-pbkdf@^1.0.0:
dependencies:
tweetnacl "^0.14.3"
bignumber.js@^9.0.0:
version "9.3.0"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd"
integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==
binary-extensions@^2.0.0, binary-extensions@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
@@ -1671,6 +1696,11 @@ buffer-alloc@^1.2.0:
buffer-alloc-unsafe "^1.1.0"
buffer-fill "^1.0.0"
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
@@ -2309,6 +2339,13 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -2842,7 +2879,7 @@ express@^4.21.2:
utils-merge "1.0.1"
vary "~1.1.2"
extend@~3.0.2:
extend@^3.0.2, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -3117,6 +3154,26 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
gaxios@^6.0.0, gaxios@^6.1.1:
version "6.7.1"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb"
integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==
dependencies:
extend "^3.0.2"
https-proxy-agent "^7.0.1"
is-stream "^2.0.0"
node-fetch "^2.6.9"
uuid "^9.0.1"
gcp-metadata@^6.1.0:
version "6.1.1"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.1.tgz#f65aa69f546bc56e116061d137d3f5f90bdec494"
integrity sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==
dependencies:
gaxios "^6.1.1"
google-logging-utils "^0.0.2"
json-bigint "^1.0.0"
get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -3295,6 +3352,23 @@ globby@^10.0.1:
merge2 "^1.2.3"
slash "^3.0.0"
google-auth-library@^9.14.2:
version "9.15.1"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.15.1.tgz#0c5d84ed1890b2375f1cd74f03ac7b806b392928"
integrity sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==
dependencies:
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
gaxios "^6.1.1"
gcp-metadata "^6.1.0"
gtoken "^7.0.0"
jws "^4.0.0"
google-logging-utils@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-0.0.2.tgz#5fd837e06fa334da450433b9e3e1870c1594466a"
integrity sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==
gopd@^1.0.1, gopd@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1"
@@ -3305,6 +3379,14 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
gtoken@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
dependencies:
gaxios "^6.0.0"
jws "^4.0.0"
handlebars@^4.0.1:
version "4.7.8"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
@@ -3550,6 +3632,14 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
https-proxy-agent@^7.0.1:
version "7.0.6"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
dependencies:
agent-base "^7.1.2"
debug "4"
humanize-ms@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
@@ -3951,6 +4041,13 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -4027,6 +4124,23 @@ jsprim@^2.0.2:
json-schema "0.4.0"
verror "1.10.0"
jwa@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804"
integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==
dependencies:
buffer-equal-constant-time "^1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
keccak@^3.0.0, keccak@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/keccak/-/keccak-3.0.4.tgz#edc09b89e633c0549da444432ecf062ffadee86d"
@@ -4520,7 +4634,7 @@ node-emoji@^1.10.0:
dependencies:
lodash "^4.17.21"
node-fetch@^2.6.7, node-fetch@^2.7.0:
node-fetch@^2.6.7, node-fetch@^2.6.9, node-fetch@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -4663,6 +4777,19 @@ one-time@^1.0.0:
dependencies:
fn.name "1.x.x"
openai@^4.102.0:
version "4.102.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-4.102.0.tgz#fcf09d2ad2b33eb399fe79bb43df1872bf2a15c8"
integrity sha512-CWk15CMhPSHNZnjz+6rwVYV551xaC8CwOd7/zxImrC1btEo37dX/Ii5tBKWfqqxqyzpJ6p3Y4bICzzKhW03WhQ==
dependencies:
"@types/node" "^18.11.18"
"@types/node-fetch" "^2.6.4"
abort-controller "^3.0.0"
agentkeepalive "^4.2.1"
form-data-encoder "1.7.2"
formdata-node "^4.3.2"
node-fetch "^2.6.7"
openai@^4.93.0:
version "4.96.0"
resolved "https://registry.yarnpkg.com/openai/-/openai-4.96.0.tgz#d1a821e99949ac2c55709f4e28e18bb1d9fd8ef9"
@@ -6355,6 +6482,11 @@ uuid@^10.0.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
@@ -6595,7 +6727,7 @@ write-file-atomic@3.0.3:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
ws@8.17.1, ws@8.18.1, ws@^7.4.6, ws@^8.18.1:
ws@8.17.1, ws@8.18.1, ws@^7.4.6, ws@^8.18.0, ws@^8.18.1:
version "8.18.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
@@ -6653,7 +6785,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.5, zod-to-json-schema@^3.24.1:
zod-to-json-schema@^3.22.3, zod-to-json-schema@^3.22.4, zod-to-json-schema@^3.22.5, zod-to-json-schema@^3.24.1:
version "3.24.5"
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz#d1095440b147fb7c2093812a53c54df8d5df50a3"
integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==