From 55e4d81c959033193d30e2c2f6d4a0da10596820 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 19 Mar 2025 17:18:03 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app.js | 160 +++-- backend/config/session.js | 25 + backend/contracts/AccessToken.sol | 61 -- backend/contracts/MyContract.sol | 38 - backend/db/index.js | 36 +- backend/db/init.js | 74 +- backend/db/migrations/000_session_table.sql | 8 + backend/db/migrations/001_initial_schema.sql | 9 + backend/db/migrations/002_access_roles.sql | 24 + backend/db/migrations/003_user_identities.sql | 28 + backend/db/migrations/004_conversations.sql | 10 + backend/db/migrations/005_messages.sql | 20 + backend/db/migrations/006_guest_messages.sql | 52 ++ .../db/migrations/007_user_preferences.sql | 46 ++ .../008_update_messages_structure.sql | 45 ++ backend/db/migrations/009_nonces_table.sql | 13 + backend/db/migrations/010_cleanup_roles.sql | 71 ++ .../migrations/create_guest_messages_table.js | 15 - .../functions/link_guest_messages.sql | 56 ++ backend/middleware/auth.js | 92 +-- backend/middleware/errorMiddleware.js | 56 -- backend/middleware/session.js | 22 - backend/migrations/003_access_roles.sql | 7 - backend/migrations/006_role_management.sql | 26 - .../007_user_identities_conversations.sql | 40 -- backend/migrations/008_chat_history.sql | 14 - backend/migrations/fix_function.sql | 32 - backend/package.json | 3 +- backend/routes/access.js | 227 ------ backend/routes/admin.js | 35 +- backend/routes/auth.js | 176 +++-- backend/routes/chat.js | 253 ++++--- backend/routes/contracts.js | 34 - backend/routes/conversations.js | 1 - backend/routes/debug.js | 33 - backend/routes/health.js | 36 - backend/routes/identities.js | 66 +- backend/routes/messages.js | 246 ------- backend/routes/roles.js | 166 ----- backend/scripts/check-state.js | 43 -- backend/scripts/check-tokens.js | 21 - backend/scripts/deploy-access.js | 29 - backend/scripts/deploy.js | 22 - backend/scripts/init-roles.js | 58 -- backend/scripts/run-migrations.js | 82 ++- backend/scripts/update-user-roles.js | 53 -- backend/server.js | 657 +----------------- backend/services/ai-assistant.js | 235 +++---- backend/services/auth-service.js | 447 ++++++++---- backend/services/ollama.js | 160 ----- backend/services/telegramBot.js | 329 ++++----- backend/services/vectorStore.js | 212 ------ backend/utils/access-check.js | 39 -- backend/utils/auth.js | 146 ---- backend/utils/contracts.js | 105 --- backend/utils/identity-linker.js | 90 --- frontend/src/App.vue | 24 +- frontend/src/api/axios.js | 35 +- frontend/src/components/TelegramConnect.vue | 141 ---- frontend/src/components/WalletConnection.vue | 203 ------ .../src/components/identity/EmailConnect.vue | 214 ++---- .../components/identity/IdentityManager.vue | 48 -- .../components/identity/WalletConnection.vue | 84 +++ frontend/src/components/identity/index.js | 9 + frontend/src/composables/useEthereum.js | 56 -- frontend/src/locales/en.json | 0 frontend/src/locales/ru.json | 0 frontend/src/router/index.js | 4 +- frontend/src/services/wallet.js | 80 +++ frontend/src/stores/auth.js | 91 +-- frontend/src/utils/wallet.js | 90 --- frontend/src/views/HomeView.vue | 580 ++++++++-------- frontend/src/views/ProfileView.vue | 138 ---- frontend/src/views/TokenAccess.vue | 75 -- frontend/vite.config.js | 8 +- 75 files changed, 2103 insertions(+), 4861 deletions(-) create mode 100644 backend/config/session.js delete mode 100644 backend/contracts/AccessToken.sol delete mode 100644 backend/contracts/MyContract.sol create mode 100644 backend/db/migrations/000_session_table.sql create mode 100644 backend/db/migrations/001_initial_schema.sql create mode 100644 backend/db/migrations/002_access_roles.sql create mode 100644 backend/db/migrations/003_user_identities.sql create mode 100644 backend/db/migrations/004_conversations.sql create mode 100644 backend/db/migrations/005_messages.sql create mode 100644 backend/db/migrations/006_guest_messages.sql create mode 100644 backend/db/migrations/007_user_preferences.sql create mode 100644 backend/db/migrations/008_update_messages_structure.sql create mode 100644 backend/db/migrations/009_nonces_table.sql create mode 100644 backend/db/migrations/010_cleanup_roles.sql delete mode 100644 backend/db/migrations/create_guest_messages_table.js create mode 100644 backend/db/migrations/functions/link_guest_messages.sql delete mode 100644 backend/middleware/errorMiddleware.js delete mode 100644 backend/middleware/session.js delete mode 100644 backend/migrations/003_access_roles.sql delete mode 100644 backend/migrations/006_role_management.sql delete mode 100644 backend/migrations/007_user_identities_conversations.sql delete mode 100644 backend/migrations/008_chat_history.sql delete mode 100644 backend/migrations/fix_function.sql delete mode 100644 backend/routes/access.js delete mode 100644 backend/routes/contracts.js delete mode 100644 backend/routes/conversations.js delete mode 100644 backend/routes/debug.js delete mode 100644 backend/routes/health.js delete mode 100644 backend/routes/messages.js delete mode 100644 backend/routes/roles.js delete mode 100644 backend/scripts/check-state.js delete mode 100644 backend/scripts/check-tokens.js delete mode 100644 backend/scripts/deploy-access.js delete mode 100644 backend/scripts/deploy.js delete mode 100644 backend/scripts/init-roles.js delete mode 100644 backend/scripts/update-user-roles.js delete mode 100644 backend/services/ollama.js delete mode 100644 backend/services/vectorStore.js delete mode 100644 backend/utils/access-check.js delete mode 100644 backend/utils/auth.js delete mode 100644 backend/utils/contracts.js delete mode 100644 backend/utils/identity-linker.js delete mode 100644 frontend/src/components/TelegramConnect.vue delete mode 100644 frontend/src/components/WalletConnection.vue delete mode 100644 frontend/src/components/identity/IdentityManager.vue create mode 100644 frontend/src/components/identity/WalletConnection.vue create mode 100644 frontend/src/components/identity/index.js delete mode 100644 frontend/src/composables/useEthereum.js delete mode 100644 frontend/src/locales/en.json delete mode 100644 frontend/src/locales/ru.json create mode 100644 frontend/src/services/wallet.js delete mode 100644 frontend/src/utils/wallet.js delete mode 100644 frontend/src/views/ProfileView.vue delete mode 100644 frontend/src/views/TokenAccess.vue diff --git a/backend/app.js b/backend/app.js index 4164315..94d89eb 100644 --- a/backend/app.js +++ b/backend/app.js @@ -7,67 +7,100 @@ const helmet = require('helmet'); const path = require('path'); const logger = require('./utils/logger'); const authService = require('./services/auth-service'); +const { errorHandler, AppError, ErrorTypes } = require('./middleware/errorHandler'); +const aiAssistant = require('./services/ai-assistant'); +const crypto = require('crypto'); // Импорт маршрутов const authRoutes = require('./routes/auth'); -const accessRoutes = require('./routes/access'); const usersRoutes = require('./routes/users'); -const contractsRoutes = require('./routes/contracts'); -const rolesRoutes = require('./routes/roles'); const identitiesRoutes = require('./routes/identities'); -// const conversationsRoutes = require('./routes/conversations'); -const messagesRoutes = require('./routes/messages'); const chatRoutes = require('./routes/chat'); -const healthRoutes = require('./routes/health'); -const debugRoutes = require('./routes/debug'); +const adminRoutes = require('./routes/admin'); const app = express(); -// Настройка middleware для сессий +// Настройка CORS +app.use(cors({ + origin: 'http://localhost:5173', + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'Cookie'] +})); + +// Настройка сессии app.use(session({ store: new pgSession({ pool, tableName: 'session', - createTableIfMissing: true, }), - secret: process.env.SESSION_SECRET || 'your-secret-key', + secret: process.env.SESSION_SECRET || 'hb3atoken', + name: 'sessionId', resave: false, saveUninitialized: true, cookie: { - maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней + maxAge: 30 * 24 * 60 * 60 * 1000, httpOnly: true, secure: false, sameSite: 'lax', - }, + path: '/' + } })); +// Добавим middleware для проверки сессии +app.use(async (req, res, next) => { + console.log('Request cookies:', req.headers.cookie); + console.log('Session ID:', req.sessionID); + + // Проверяем сессию в базе данных + if (req.sessionID) { + const result = await pool.query( + 'SELECT sess FROM session WHERE sid = $1', + [req.sessionID] + ); + console.log('Session from DB:', result.rows[0]?.sess); + } + + // Если сессия уже есть, используем её + if (req.session.authenticated) { + return next(); + } + + // Проверяем заголовок авторизации + const authHeader = req.headers.authorization; + if (authHeader && authHeader.startsWith('Bearer ')) { + const token = authHeader.split(' ')[1]; + try { + // Находим пользователя по токену + const { rows } = await pool.query(` + SELECT u.id, + (u.role = 'admin') as is_admin, + u.address + FROM users u + WHERE u.id = $1 + `, [token]); + + if (rows.length > 0) { + const user = rows[0]; + req.session.userId = user.id; + req.session.address = user.address; + req.session.isAdmin = user.is_admin; + req.session.authenticated = true; + + await new Promise(resolve => req.session.save(resolve)); + } + } catch (error) { + console.error('Error checking auth header:', error); + } + } + + next(); +}); + // Настройка парсеров app.use(express.json()); app.use(express.urlencoded({ extended: true })); -// Настройка CORS -app.use(cors({ - origin: function(origin, callback) { - // Разрешаем запросы с localhost и 127.0.0.1 - const allowedOrigins = [ - 'http://localhost:5173', - 'http://127.0.0.1:5173', - 'http://localhost:3000', - 'http://127.0.0.1:3000', - process.env.CORS_ORIGIN - ].filter(Boolean); - - if (!origin || allowedOrigins.includes(origin)) { - callback(null, true); - } else { - callback(new Error('Not allowed by CORS')); - } - }, - credentials: true, - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'] -})); - // Настройка безопасности app.use(helmet({ contentSecurityPolicy: false // Отключаем CSP для разработки @@ -79,34 +112,51 @@ app.use((req, res, next) => { next(); }); -// Добавляем middleware для установки заголовков CORS -app.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', req.headers.origin || '*'); - res.header('Access-Control-Allow-Credentials', 'true'); - res.header('Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE'); - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); - next(); -}); - // Маршруты API app.use('/api/auth', authRoutes); -app.use('/api/access', accessRoutes); app.use('/api/users', usersRoutes); -app.use('/api/contracts', contractsRoutes); -app.use('/api/roles', rolesRoutes); app.use('/api/identities', identitiesRoutes); -// app.use('/api/conversations', conversationsRoutes); -app.use('/api/messages', messagesRoutes); app.use('/api/chat', chatRoutes); -app.use('/api/health', healthRoutes); - -// Маршруты для отладки (только в режиме разработки) -if (process.env.NODE_ENV !== 'production') { - app.use('/api/debug', debugRoutes); -} +app.use('/api/admin', adminRoutes); const nonceStore = new Map(); // или любая другая реализация хранилища nonce console.log('SESSION_SECRET:', process.env.SESSION_SECRET); +// Добавляем обработчик ошибок последним +app.use(errorHandler); + +// Эндпоинт для проверки состояния +app.get('/api/health', async (req, res) => { + try { + // Проверяем подключение к БД + await pool.query('SELECT NOW()'); + + // Проверяем AI сервис + const aiStatus = await aiAssistant.checkHealth(); + + res.json({ + status: 'ok', + timestamp: new Date().toISOString(), + database: 'connected', + ai: aiStatus + }); + } catch (error) { + logger.error('Health check failed:', error); + res.status(500).json({ + status: 'error', + error: error.message + }); + } +}); + +// Очистка старых сессий +setInterval(async () => { + try { + await pool.query('DELETE FROM session WHERE expire < NOW()'); + } catch (error) { + console.error('Error cleaning old sessions:', error); + } +}, 15 * 60 * 1000); // Каждые 15 минут + module.exports = { app, nonceStore }; diff --git a/backend/config/session.js b/backend/config/session.js new file mode 100644 index 0000000..289d6f7 --- /dev/null +++ b/backend/config/session.js @@ -0,0 +1,25 @@ +const session = require('express-session'); +const pgSession = require('connect-pg-simple')(session); +const { pool } = 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: '/' + } +}; + +module.exports = { + sessionMiddleware: session(sessionConfig) +}; \ No newline at end of file diff --git a/backend/contracts/AccessToken.sol b/backend/contracts/AccessToken.sol deleted file mode 100644 index 7670cc3..0000000 --- a/backend/contracts/AccessToken.sol +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/Counters.sol"; - -contract AccessToken is ERC721, Ownable { - using Counters for Counters.Counter; - Counters.Counter private _tokenIds; - - // Роли для токенов - enum Role { USER, ADMIN } - - // Маппинг токен ID => роль - mapping(uint256 => Role) public tokenRoles; - - // Маппинг адрес => активный токен - mapping(address => uint256) public activeTokens; - - constructor() ERC721("DApp Access Token", "DAT") Ownable() { - // Инициализация владельца происходит в Ownable() - } - - // Создание нового токена доступа - function mintAccessToken(address to, Role role) public onlyOwner { - require(role == Role.USER || role == Role.ADMIN, "Invalid role"); - - _tokenIds.increment(); - uint256 newTokenId = _tokenIds.current(); - - _safeMint(to, newTokenId); - tokenRoles[newTokenId] = role; - activeTokens[to] = newTokenId; - } - - // Проверка роли по адресу - function checkRole(address user) public view returns (Role) { - uint256 tokenId = activeTokens[user]; - require(tokenId != 0, "No active token"); - require(ownerOf(tokenId) == user, "Token not owned"); - return tokenRoles[tokenId]; - } - - // Отзыв токена - function revokeToken(uint256 tokenId) public onlyOwner { - address tokenOwner = ownerOf(tokenId); - activeTokens[tokenOwner] = 0; - _burn(tokenId); - } - - // Передача токена запрещена - function _beforeTokenTransfer( - address from, - address to, - uint256 tokenId - ) internal override { - require(from == address(0) || to == address(0), "Token transfer not allowed"); - super._beforeTokenTransfer(from, to, tokenId); - } -} \ No newline at end of file diff --git a/backend/contracts/MyContract.sol b/backend/contracts/MyContract.sol deleted file mode 100644 index bfd5520..0000000 --- a/backend/contracts/MyContract.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; - -contract MyContract is Ownable, ReentrancyGuard { - // Явно объявляем функцию owner - function owner() public view override returns (address) { - return super.owner(); - } - - uint256 public price; - - event Purchase(address buyer, uint256 amount); - - constructor() { - price = 0.01 ether; // Начальная цена 0.01 ETH - } - - function setPrice(uint256 newPrice) public onlyOwner { - price = newPrice; - } - - function getPrice() public view returns (uint256) { - return price; - } - - function purchase(uint256 amount) public payable nonReentrant { - require(msg.value == price * amount, "Incorrect payment amount"); - emit Purchase(msg.sender, amount); - } - - function withdraw() public onlyOwner nonReentrant { - (bool success, ) = owner().call{value: address(this).balance}(""); - require(success, "Transfer failed"); - } -} \ No newline at end of file diff --git a/backend/db/index.js b/backend/db/index.js index 2adf551..64e65aa 100644 --- a/backend/db/index.js +++ b/backend/db/index.js @@ -1,19 +1,21 @@ -const createGuestMessagesTable = require('./migrations/create_guest_messages_table'); +const { Pool } = require('pg'); +const logger = require('../utils/logger'); -async function initDatabase() { - try { - // ... существующий код ... - - // Выполняем миграции - await pool.query(createUsersTable); - await pool.query(createSessionTable); - await pool.query(createNoncesTable); - await pool.query(createMessagesTable); - await pool.query(createConversationsTable); - await pool.query(createGuestMessagesTable); - - // ... существующий код ... - } catch (error) { - // ... существующий код ... +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]); } -} \ No newline at end of file +}); + +module.exports = { pool }; \ No newline at end of file diff --git a/backend/db/init.js b/backend/db/init.js index 360227a..4342d93 100644 --- a/backend/db/init.js +++ b/backend/db/init.js @@ -1,8 +1,13 @@ +const fs = require('fs'); +const path = require('path'); +const { pool } = require('./index'); +const logger = require('../utils/logger'); + // Инициализация таблицы roles async function initRoles() { try { // Проверяем, существует ли таблица roles - const tableExists = await db.query(` + const tableExists = await pool.query(` SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_name = 'roles' @@ -11,7 +16,7 @@ async function initRoles() { if (!tableExists.rows[0].exists) { // Создаем таблицу roles - await db.query(` + await pool.query(` CREATE TABLE roles ( id SERIAL PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, @@ -21,7 +26,7 @@ async function initRoles() { `); // Добавляем роли - await db.query(` + await pool.query(` INSERT INTO roles (id, name, description) VALUES (3, 'user', 'Обычный пользователь'), (4, 'admin', 'Администратор с полным доступом'); @@ -30,24 +35,24 @@ async function initRoles() { console.log('Таблица roles создана и заполнена'); } else { // Проверяем наличие ролей - const rolesExist = await db.query(` + const rolesExist = await pool.query(` SELECT COUNT(*) FROM roles WHERE id IN (3, 4); `); if (rolesExist.rows[0].count < 2) { // Добавляем недостающие роли - const userRoleExists = await db.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`); - const adminRoleExists = await db.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`); + const userRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`); + const adminRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`); if (!userRoleExists.rows[0].exists) { - await db.query(` + await pool.query(` INSERT INTO roles (id, name, description) VALUES (3, 'user', 'Обычный пользователь'); `); } if (!adminRoleExists.rows[0].exists) { - await db.query(` + await pool.query(` INSERT INTO roles (id, name, description) VALUES (4, 'admin', 'Администратор с полным доступом'); `); @@ -60,4 +65,55 @@ async function initRoles() { console.error('Ошибка при инициализации таблицы roles:', error); throw error; } -} \ No newline at end of file +} + +async function initializeDatabase() { + try { + // Создаем таблицу для отслеживания миграций, если её нет + await pool.query(` + CREATE TABLE IF NOT EXISTS migrations ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL UNIQUE, + executed_at TIMESTAMP NOT NULL DEFAULT NOW() + ); + `); + + // Путь к папке с миграциями + const migrationsPath = path.join(__dirname, 'migrations'); + + // Получаем все файлы миграций + const migrationFiles = fs.readdirSync(migrationsPath) + .filter(file => file.endsWith('.sql')) + .sort(); + + // Получаем выполненные миграции + const { rows } = await pool.query('SELECT name FROM migrations'); + const executedMigrations = new Set(rows.map(row => row.name)); + + // Выполняем только новые миграции + for (const file of migrationFiles) { + if (!executedMigrations.has(file)) { + const filePath = path.join(migrationsPath, file); + const sql = fs.readFileSync(filePath, 'utf8'); + + logger.info(`Executing migration: ${file}`); + await pool.query(sql); + + // Записываем выполненную миграцию + await pool.query( + 'INSERT INTO migrations (name) VALUES ($1)', + [file] + ); + + logger.info(`Migration completed: ${file}`); + } + } + + logger.info('All migrations completed successfully'); + } catch (error) { + logger.error('Error during database initialization:', error); + throw error; + } +} + +module.exports = { initializeDatabase }; \ No newline at end of file diff --git a/backend/db/migrations/000_session_table.sql b/backend/db/migrations/000_session_table.sql new file mode 100644 index 0000000..03b4fa3 --- /dev/null +++ b/backend/db/migrations/000_session_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS "session" ( + "sid" varchar NOT NULL COLLATE "default", + "sess" json NOT NULL, + "expire" timestamp(6) NOT NULL, + CONSTRAINT "session_pkey" PRIMARY KEY ("sid") +); + +CREATE INDEX IF NOT EXISTS "IDX_session_expire" ON "session" ("expire"); \ No newline at end of file diff --git a/backend/db/migrations/001_initial_schema.sql b/backend/db/migrations/001_initial_schema.sql new file mode 100644 index 0000000..6d104a6 --- /dev/null +++ b/backend/db/migrations/001_initial_schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + username VARCHAR(255), + email VARCHAR(255) UNIQUE, + address VARCHAR(255) UNIQUE, + status VARCHAR(50) DEFAULT 'active', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/backend/db/migrations/002_access_roles.sql b/backend/db/migrations/002_access_roles.sql new file mode 100644 index 0000000..97beaf5 --- /dev/null +++ b/backend/db/migrations/002_access_roles.sql @@ -0,0 +1,24 @@ +CREATE TABLE IF NOT EXISTS roles ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) NOT NULL UNIQUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Добавляем базовые роли +INSERT INTO roles (name) VALUES ('admin'), ('user') +ON CONFLICT (name) DO NOTHING; + +-- Добавляем связь пользователей с ролями +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'role_id' + ) THEN + ALTER TABLE users + ADD COLUMN role_id INTEGER REFERENCES roles(id); + END IF; +END $$; + +-- Создаем индекс для role_id после добавления колонки +CREATE INDEX IF NOT EXISTS idx_users_role_id ON users(role_id); \ No newline at end of file diff --git a/backend/db/migrations/003_user_identities.sql b/backend/db/migrations/003_user_identities.sql new file mode 100644 index 0000000..a1df232 --- /dev/null +++ b/backend/db/migrations/003_user_identities.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS user_identities ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + provider VARCHAR(50) NOT NULL, + provider_id VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(provider, provider_id) +); + +-- Создаем индексы после создания таблицы +DO $$ +BEGIN + -- Индекс для user_id + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE tablename = 'user_identities' AND indexname = 'idx_user_identities_user_id' + ) THEN + CREATE INDEX idx_user_identities_user_id ON user_identities(user_id); + END IF; + + -- Индекс для provider и provider_id + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE tablename = 'user_identities' AND indexname = 'idx_user_identities_type_value' + ) THEN + CREATE INDEX idx_user_identities_type_value ON user_identities(provider, provider_id); + END IF; +END $$; \ No newline at end of file diff --git a/backend/db/migrations/004_conversations.sql b/backend/db/migrations/004_conversations.sql new file mode 100644 index 0000000..c8ed1f9 --- /dev/null +++ b/backend/db/migrations/004_conversations.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS conversations ( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + title VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_conversations_user_id ON conversations(user_id); +CREATE INDEX IF NOT EXISTS idx_conversations_created_at ON conversations(created_at); \ No newline at end of file diff --git a/backend/db/migrations/005_messages.sql b/backend/db/migrations/005_messages.sql new file mode 100644 index 0000000..5beca71 --- /dev/null +++ b/backend/db/migrations/005_messages.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS messages ( + id SERIAL PRIMARY KEY, + conversation_id INTEGER REFERENCES conversations(id) ON DELETE CASCADE, + sender_type VARCHAR(20) NOT NULL, + sender_id INTEGER, + content TEXT, + channel VARCHAR(20) NOT NULL, + metadata JSONB, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + role VARCHAR(20) NOT NULL DEFAULT 'user', + guest_message_id INTEGER, + tokens_used INTEGER DEFAULT 0, + is_processed BOOLEAN DEFAULT FALSE +); + +CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id); +CREATE INDEX IF NOT EXISTS idx_messages_sender_type ON messages(sender_type); +CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at); +CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel); +CREATE INDEX IF NOT EXISTS idx_messages_metadata ON messages USING gin(metadata); \ No newline at end of file diff --git a/backend/db/migrations/006_guest_messages.sql b/backend/db/migrations/006_guest_messages.sql new file mode 100644 index 0000000..5ee4bd9 --- /dev/null +++ b/backend/db/migrations/006_guest_messages.sql @@ -0,0 +1,52 @@ +CREATE TABLE IF NOT EXISTS guest_messages ( + id SERIAL PRIMARY KEY, + guest_id VARCHAR(255) NOT NULL, + content TEXT NOT NULL, + language VARCHAR(10) DEFAULT 'en', + is_ai BOOLEAN DEFAULT false, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE tablename = 'guest_messages' AND indexname = 'idx_guest_messages_guest_id' + ) THEN + CREATE INDEX idx_guest_messages_guest_id ON guest_messages(guest_id); + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'messages' AND column_name = 'guest_message_id' + ) THEN + ALTER TABLE messages ADD COLUMN guest_message_id INTEGER; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'fk_messages_guest_message' + ) THEN + ALTER TABLE messages + ADD CONSTRAINT fk_messages_guest_message + FOREIGN KEY (guest_message_id) + REFERENCES guest_messages(id) + ON DELETE SET NULL; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE tablename = 'messages' AND indexname = 'idx_messages_guest_message_id' + ) THEN + CREATE INDEX idx_messages_guest_message_id ON messages(guest_message_id); + END IF; +END $$; \ No newline at end of file diff --git a/backend/db/migrations/007_user_preferences.sql b/backend/db/migrations/007_user_preferences.sql new file mode 100644 index 0000000..83f0fc5 --- /dev/null +++ b/backend/db/migrations/007_user_preferences.sql @@ -0,0 +1,46 @@ +CREATE TABLE IF NOT EXISTS user_preferences ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + preference_key VARCHAR(50) NOT NULL, + preference_value TEXT, + metadata JSONB DEFAULT '{}', + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + UNIQUE(user_id, preference_key) +); + +-- Добавляем колонку metadata, если её нет +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'user_preferences' AND column_name = 'metadata' + ) THEN + ALTER TABLE user_preferences ADD COLUMN metadata JSONB DEFAULT '{}'; + END IF; +END $$; + +CREATE INDEX IF NOT EXISTS idx_user_preferences_user_id ON user_preferences(user_id); + +-- Базовые настройки +DO $$ +BEGIN + INSERT INTO user_preferences (user_id, preference_key, preference_value, metadata) + SELECT id, 'language', 'ru', '{"available": ["ru", "en"]}'::jsonb + FROM users u + WHERE NOT EXISTS ( + SELECT 1 FROM user_preferences + WHERE preference_key = 'language' AND user_id = u.id + ); +END $$; + +DO $$ +BEGIN + INSERT INTO user_preferences (user_id, preference_key, preference_value, metadata) + SELECT id, 'notifications', 'true', '{"channels": ["email", "telegram"]}'::jsonb + FROM users u + WHERE NOT EXISTS ( + SELECT 1 FROM user_preferences + WHERE preference_key = 'notifications' AND user_id = u.id + ); +END $$; \ No newline at end of file diff --git a/backend/db/migrations/008_update_messages_structure.sql b/backend/db/migrations/008_update_messages_structure.sql new file mode 100644 index 0000000..e7182f0 --- /dev/null +++ b/backend/db/migrations/008_update_messages_structure.sql @@ -0,0 +1,45 @@ +-- Добавляем новые поля в messages +ALTER TABLE messages +ADD COLUMN IF NOT EXISTS role VARCHAR(20) NOT NULL DEFAULT 'user', +ADD COLUMN IF NOT EXISTS guest_message_id INTEGER, +ADD COLUMN IF NOT EXISTS tokens_used INTEGER DEFAULT 0, +ADD COLUMN IF NOT EXISTS is_processed BOOLEAN DEFAULT FALSE; + +-- Создаем функцию для связывания сообщений +CREATE OR REPLACE FUNCTION link_guest_messages( + p_user_id INTEGER, + p_guest_id VARCHAR(255) +) RETURNS VOID AS $$ +DECLARE + v_conversation_id INTEGER; +BEGIN + -- Создаем новую беседу для гостевых сообщений + INSERT INTO conversations (created_at, updated_at) + VALUES (NOW(), NOW()) + RETURNING id INTO v_conversation_id; + + -- Копируем гостевые сообщения в основную таблицу + INSERT INTO messages ( + conversation_id, + sender_type, + sender_id, + content, + role, + channel, + guest_message_id, + created_at + ) + SELECT + v_conversation_id, + CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, + CASE WHEN NOT is_ai THEN p_user_id ELSE NULL END, + content, + CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, + 'chat', + id, + created_at + FROM guest_messages + WHERE guest_id = p_guest_id + ORDER BY created_at; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/db/migrations/009_nonces_table.sql b/backend/db/migrations/009_nonces_table.sql new file mode 100644 index 0000000..eb555a7 --- /dev/null +++ b/backend/db/migrations/009_nonces_table.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS nonces ( + id SERIAL PRIMARY KEY, + identity_value VARCHAR(255) NOT NULL, + nonce VARCHAR(255) NOT NULL, + expires_at TIMESTAMP NOT NULL, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Индекс для быстрого поиска по identity_value +CREATE INDEX IF NOT EXISTS idx_nonces_identity_value ON nonces(identity_value); + +-- Индекс для очистки просроченных nonce +CREATE INDEX IF NOT EXISTS idx_nonces_expires_at ON nonces(expires_at); \ No newline at end of file diff --git a/backend/db/migrations/010_cleanup_roles.sql b/backend/db/migrations/010_cleanup_roles.sql new file mode 100644 index 0000000..5e6caac --- /dev/null +++ b/backend/db/migrations/010_cleanup_roles.sql @@ -0,0 +1,71 @@ +-- Проверяем существование типа user_role +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'user_role') THEN + CREATE TYPE user_role AS ENUM ('user', 'admin'); + END IF; +END $$; + +-- Удаляем лишние колонки и связи +ALTER TABLE users DROP CONSTRAINT IF EXISTS users_role_id_fkey; +ALTER TABLE users DROP COLUMN IF EXISTS role_id; + +-- Добавляем колонку role если её нет +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'role' + ) THEN + ALTER TABLE users ADD COLUMN role user_role DEFAULT 'user'::user_role; + END IF; +END $$; + +-- Удаляем лишние триггеры и функции +DROP TRIGGER IF EXISTS sync_identity_type_trigger ON user_identities; +DROP TRIGGER IF EXISTS user_identity_role_check ON user_identities; +DROP TRIGGER IF EXISTS check_admin_role_trigger ON user_identities; +DROP FUNCTION IF EXISTS sync_identity_type() CASCADE; +DROP FUNCTION IF EXISTS update_user_role() CASCADE; +DROP FUNCTION IF EXISTS check_admin_role(INTEGER) CASCADE; + +-- Создаем функцию проверки роли +CREATE FUNCTION check_admin_role() +RETURNS TRIGGER AS $$ +DECLARE + v_wallet_address VARCHAR; +BEGIN + SELECT provider_id INTO v_wallet_address + FROM user_identities + WHERE user_id = NEW.user_id + AND provider = 'wallet' + LIMIT 1; + + IF v_wallet_address IS NULL THEN + RETURN NEW; + END IF; + + UPDATE users + SET role = 'admin'::user_role + WHERE id = NEW.user_id; + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Создаем триггер +CREATE TRIGGER check_admin_role_trigger +AFTER INSERT OR UPDATE ON user_identities +FOR EACH ROW +EXECUTE FUNCTION check_admin_role(); + +-- Обновляем существующие записи +UPDATE users u +SET role = CASE + WHEN EXISTS ( + SELECT 1 FROM user_identities ui + WHERE ui.user_id = u.id + AND ui.provider = 'wallet' + ) THEN 'admin'::user_role + ELSE 'user'::user_role +END; \ No newline at end of file diff --git a/backend/db/migrations/create_guest_messages_table.js b/backend/db/migrations/create_guest_messages_table.js deleted file mode 100644 index 1fa844c..0000000 --- a/backend/db/migrations/create_guest_messages_table.js +++ /dev/null @@ -1,15 +0,0 @@ -// Создаем таблицу для хранения сообщений неаутентифицированных пользователей -const createGuestMessagesTable = ` -CREATE TABLE IF NOT EXISTS guest_messages ( - id SERIAL PRIMARY KEY, - guest_id VARCHAR(255) NOT NULL, - content TEXT NOT NULL, - language VARCHAR(10) DEFAULT 'en', - is_ai BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_guest_messages_guest_id ON guest_messages(guest_id); -`; - -module.exports = createGuestMessagesTable; \ No newline at end of file diff --git a/backend/db/migrations/functions/link_guest_messages.sql b/backend/db/migrations/functions/link_guest_messages.sql new file mode 100644 index 0000000..0afc11b --- /dev/null +++ b/backend/db/migrations/functions/link_guest_messages.sql @@ -0,0 +1,56 @@ +CREATE OR REPLACE FUNCTION link_guest_messages( + p_user_id INTEGER, + p_guest_id VARCHAR(255) +) RETURNS VOID AS $$ +DECLARE + v_conversation_id INTEGER; + v_count INTEGER; +BEGIN + -- Логируем входные параметры + RAISE NOTICE 'Linking messages for user_id: %, guest_id: %', p_user_id, p_guest_id; + + -- Проверяем наличие гостевых сообщений + SELECT COUNT(*) INTO v_count + FROM guest_messages + WHERE guest_id = p_guest_id; + + RAISE NOTICE 'Found % guest messages', v_count; + + -- Создаем новую беседу + INSERT INTO conversations (user_id, created_at, updated_at) + VALUES (p_user_id, NOW(), NOW()) + RETURNING id INTO v_conversation_id; + + RAISE NOTICE 'Created conversation with id: %', v_conversation_id; + + -- Копируем сообщения пользователя + WITH inserted_messages AS ( + INSERT INTO messages ( + conversation_id, + sender_type, + sender_id, + content, + role, + channel, + guest_message_id, + created_at + ) + SELECT + v_conversation_id, + CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, + CASE WHEN NOT is_ai THEN p_user_id ELSE NULL END, + content, + CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, + 'chat', + id, -- Сохраняем связь с гостевым сообщением + created_at + FROM guest_messages + WHERE guest_id = p_guest_id + ORDER BY created_at + RETURNING id + ) + SELECT COUNT(*) INTO v_count FROM inserted_messages; + + RAISE NOTICE 'Inserted % messages', v_count; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 8fd7ed2..2b35c72 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -9,66 +9,76 @@ const db = require('../db'); */ const requireAuth = async (req, res, next) => { try { - console.log('Session in requireAuth:', req.session); - console.log('Cookies received:', req.headers.cookie); - console.log('Authorization header:', req.headers.authorization); - - // Проверяем, что пользователь аутентифицирован через сессию - if (req.session && req.session.authenticated && req.session.userId) { - // Добавляем информацию о пользователе в запрос + console.log('Session in requireAuth:', { + id: req.sessionID, + userId: req.session?.userId, + authenticated: req.session?.authenticated + }); + + // Проверяем сессию + if (req.session?.authenticated && req.session?.userId) { + // Обновляем время жизни сессии + req.session.touch(); + req.user = { userId: req.session.userId, - address: req.session.address || null, - email: req.session.email || null, - telegramId: req.session.telegramId || null, - isAdmin: req.session.isAdmin || false, - authType: req.session.authType || 'unknown' + address: req.session.address, + isAdmin: req.session.isAdmin, + authType: req.session.authType }; return next(); } - - // Проверяем заголовок авторизации + + // Проверяем Bearer токен const authHeader = req.headers.authorization; - if (authHeader && authHeader.startsWith('Bearer ')) { - const token = authHeader.split(' ')[1]; + if (authHeader?.startsWith('Bearer ')) { + const address = authHeader.split(' ')[1]; - // Проверяем, это адрес кошелька или JWT-токен - if (token.startsWith('0x')) { - // Это адрес кошелька - const address = token; - console.log('Found address in Authorization header:', address); - - try { - // Проверяем, существует ли пользователь с таким адресом - const result = await db.query(` - SELECT u.id, u.is_admin - FROM users u - JOIN user_identities ui ON u.id = ui.user_id - WHERE ui.identity_type = 'wallet' AND LOWER(ui.identity_value) = LOWER($1) - `, [address]); + if (address.startsWith('0x')) { + const result = await db.query(` + SELECT u.id, u.is_admin + FROM users u + JOIN user_identities ui ON u.id = ui.user_id + WHERE ui.identity_type = 'wallet' + AND LOWER(ui.identity_value) = LOWER($1) + `, [address]); + + if (result.rows.length > 0) { + const user = result.rows[0]; - if (result.rows.length > 0) { - const user = result.rows[0]; + // Создаем новую сессию + req.session.regenerate(async (err) => { + if (err) { + console.error('Error regenerating session:', err); + return res.status(500).json({ error: 'Session error' }); + } + + // Устанавливаем данные сессии + req.session.authenticated = true; + req.session.userId = user.id; + req.session.address = address; + req.session.isAdmin = user.is_admin; + req.session.authType = 'wallet'; + + // Сохраняем сессию + await new Promise((resolve) => req.session.save(resolve)); + req.user = { userId: user.id, address: address, isAdmin: user.is_admin, authType: 'wallet' }; - return next(); - } - } catch (error) { - console.error('Error finding user by address:', error); + next(); + }); + return; } - } else { - // Здесь можно добавить логику проверки JWT, если используется } } - - // Если пользователь не аутентифицирован, возвращаем ошибку + return res.status(401).json({ error: 'Unauthorized' }); } catch (error) { - console.error('Unexpected error in requireAuth middleware:', error); + console.error('Auth middleware error:', error); return res.status(500).json({ error: 'Internal server error' }); } }; diff --git a/backend/middleware/errorMiddleware.js b/backend/middleware/errorMiddleware.js deleted file mode 100644 index 6d9d666..0000000 --- a/backend/middleware/errorMiddleware.js +++ /dev/null @@ -1,56 +0,0 @@ -const logger = require('../utils/logger'); -const { ERROR_CODES } = require('../utils/constants'); - -/** - * Middleware для обработки ошибок - */ -function errorMiddleware(err, req, res, next) { - // Логируем ошибку - logger.error(`Error: ${err.message}`, { - stack: err.stack, - url: req.originalUrl, - method: req.method, - ip: req.ip, - userId: req.session?.userId - }); - - // Определяем тип ошибки - let statusCode = 500; - let errorCode = ERROR_CODES.INTERNAL_ERROR; - let errorMessage = 'Внутренняя ошибка сервера'; - - // Обрабатываем разные типы ошибок - if (err.name === 'UnauthorizedError' || err.status === 401) { - statusCode = 401; - errorCode = ERROR_CODES.UNAUTHORIZED; - errorMessage = 'Требуется аутентификация'; - } else if (err.status === 403) { - statusCode = 403; - errorCode = ERROR_CODES.FORBIDDEN; - errorMessage = 'Доступ запрещен'; - } else if (err.status === 404) { - statusCode = 404; - errorCode = ERROR_CODES.NOT_FOUND; - errorMessage = 'Ресурс не найден'; - } else if (err.status === 400) { - statusCode = 400; - errorCode = ERROR_CODES.BAD_REQUEST; - errorMessage = err.message || 'Некорректный запрос'; - } - - // В режиме разработки возвращаем стек ошибки - const devError = process.env.NODE_ENV === 'development' - ? { stack: err.stack } - : {}; - - // Отправляем ответ клиенту - res.status(statusCode).json({ - error: { - code: errorCode, - message: errorMessage, - ...devError - } - }); -} - -module.exports = errorMiddleware; \ No newline at end of file diff --git a/backend/middleware/session.js b/backend/middleware/session.js deleted file mode 100644 index cc5f9c1..0000000 --- a/backend/middleware/session.js +++ /dev/null @@ -1,22 +0,0 @@ -const session = require('express-session'); -const pgSession = require('connect-pg-simple')(session); -const { pool } = require('../db'); - -const sessionMiddleware = session({ - store: new pgSession({ - pool, - tableName: 'session', - createTableIfMissing: true, - }), - secret: process.env.SESSION_SECRET || 'your-secret-key', - resave: false, - saveUninitialized: true, - cookie: { - maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней - httpOnly: true, - secure: process.env.NODE_ENV === 'production', // В production должно быть true - sameSite: 'lax', // Попробуйте изменить на 'none' если используете разные домены - }, -}); - -module.exports = sessionMiddleware; diff --git a/backend/migrations/003_access_roles.sql b/backend/migrations/003_access_roles.sql deleted file mode 100644 index e21a870..0000000 --- a/backend/migrations/003_access_roles.sql +++ /dev/null @@ -1,7 +0,0 @@ --- Добавляем поле для роли в таблицу users -ALTER TABLE users -ADD COLUMN role VARCHAR(20) DEFAULT NULL, -ADD COLUMN token_id INTEGER DEFAULT NULL; - --- Индекс для быстрого поиска по роли -CREATE INDEX idx_users_role ON users(role); \ No newline at end of file diff --git a/backend/migrations/006_role_management.sql b/backend/migrations/006_role_management.sql deleted file mode 100644 index f944950..0000000 --- a/backend/migrations/006_role_management.sql +++ /dev/null @@ -1,26 +0,0 @@ -CREATE TABLE IF NOT EXISTS roles ( - id SERIAL PRIMARY KEY, - name VARCHAR(50) NOT NULL UNIQUE, - description TEXT -); - --- Добавление базовых ролей -INSERT INTO roles (name, description) VALUES - ('admin', 'Администратор с полным доступом к системе'), - ('user', 'Обычный пользователь с базовым доступом') -ON CONFLICT (name) DO NOTHING; - --- Добавление поля role_id в таблицу users, если оно еще не существует -ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id INTEGER REFERENCES roles(id) DEFAULT 2; - --- Таблица для отслеживания токенов доступа -CREATE TABLE IF NOT EXISTS access_tokens ( - id SERIAL PRIMARY KEY, - wallet_address VARCHAR(42) NOT NULL, - token_id INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(wallet_address, token_id) -); - --- Индекс для быстрого поиска по адресу кошелька -CREATE INDEX IF NOT EXISTS idx_access_tokens_wallet ON access_tokens(wallet_address); \ No newline at end of file diff --git a/backend/migrations/007_user_identities_conversations.sql b/backend/migrations/007_user_identities_conversations.sql deleted file mode 100644 index 94329e2..0000000 --- a/backend/migrations/007_user_identities_conversations.sql +++ /dev/null @@ -1,40 +0,0 @@ --- Таблица идентификаторов пользователей -CREATE TABLE IF NOT EXISTS user_identities ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, - identity_type VARCHAR(20) NOT NULL, -- 'wallet', 'telegram', 'email' - identity_value VARCHAR(255) NOT NULL, - verified BOOLEAN DEFAULT FALSE, - verification_token VARCHAR(100), - verification_expires TIMESTAMP, - last_used TIMESTAMP, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - UNIQUE(identity_type, identity_value) -); - --- Таблица диалогов -CREATE TABLE IF NOT EXISTS conversations ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id), - title VARCHAR(255), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Таблица сообщений -CREATE TABLE IF NOT EXISTS messages ( - id SERIAL PRIMARY KEY, - conversation_id INTEGER REFERENCES conversations(id), - sender_type VARCHAR(20) NOT NULL, -- 'user', 'ai', 'admin' - sender_id INTEGER, -- ID пользователя или администратора - content TEXT, - channel VARCHAR(20) NOT NULL, -- 'web', 'telegram', 'email' - metadata JSONB, -- Дополнительная информация о сообщении - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Добавление языковых настроек в таблицу пользователей -ALTER TABLE users -ADD COLUMN IF NOT EXISTS language VARCHAR(10) DEFAULT 'en', -ADD COLUMN IF NOT EXISTS last_token_check TIMESTAMP; - diff --git a/backend/migrations/008_chat_history.sql b/backend/migrations/008_chat_history.sql deleted file mode 100644 index d3a4740..0000000 --- a/backend/migrations/008_chat_history.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Создание таблицы для хранения истории диалогов -CREATE TABLE IF NOT EXISTS chat_history ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id), - channel VARCHAR(20) NOT NULL, -- 'web', 'telegram', 'email' - sender_type VARCHAR(10) NOT NULL, -- 'user', 'ai', 'admin' - content TEXT, - metadata JSONB, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Индексы для быстрого поиска -CREATE INDEX IF NOT EXISTS idx_chat_history_user_id ON chat_history(user_id); -CREATE INDEX IF NOT EXISTS idx_chat_history_channel ON chat_history(channel); \ No newline at end of file diff --git a/backend/migrations/fix_function.sql b/backend/migrations/fix_function.sql deleted file mode 100644 index 5e11c7f..0000000 --- a/backend/migrations/fix_function.sql +++ /dev/null @@ -1,32 +0,0 @@ -DROP FUNCTION IF EXISTS find_or_create_user_by_identity; - -CREATE OR REPLACE FUNCTION find_or_create_user_by_identity( - identity_type_param VARCHAR(20), - identity_value_param VARCHAR(255) -) -RETURNS TABLE(user_id INTEGER, is_new BOOLEAN) -AS $$ -DECLARE - existing_user_id INTEGER; - new_user_id INTEGER; -BEGIN - SELECT ui.user_id INTO existing_user_id - FROM user_identities ui - WHERE ui.identity_type = identity_type_param - AND ui.identity_value = identity_value_param; - - IF existing_user_id IS NOT NULL THEN - RETURN QUERY SELECT existing_user_id::INTEGER, FALSE::BOOLEAN; - RETURN; - END IF; - - INSERT INTO users (created_at) - VALUES (NOW()) - RETURNING id INTO new_user_id; - - INSERT INTO user_identities (user_id, identity_type, identity_value, created_at, verified) - VALUES (new_user_id, identity_type_param, identity_value_param, NOW(), TRUE); - - RETURN QUERY SELECT new_user_id::INTEGER, TRUE::BOOLEAN; -END; -$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index cc392b0..3b71c27 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,7 +18,8 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "format": "prettier --write \"**/*.{js,vue,json,md}\"", - "format:check": "prettier --check \"**/*.{js,vue,json,md}\"" + "format:check": "prettier --check \"**/*.{js,vue,json,md}\"", + "run-migrations": "node scripts/run-migrations.js" }, "dependencies": { "@langchain/community": "^0.3.34", diff --git a/backend/routes/access.js b/backend/routes/access.js deleted file mode 100644 index 5bbf5fb..0000000 --- a/backend/routes/access.js +++ /dev/null @@ -1,227 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { Pool } = require('pg'); -const { requireAuth, requireAdmin } = require('../middleware/auth'); -const db = require('../db'); -const { ethers } = require('ethers'); -const authService = require('../services/auth-service'); -const logger = require('../utils/logger'); - -// Проверка доступа -router.get('/check', async (req, res) => { - try { - const { address } = req.query; - - if (!address) { - return res.status(400).json({ error: 'Address is required' }); - } - - const isAdmin = await authService.checkAdminToken(address); - - res.json({ isAdmin }); - } catch (error) { - logger.error(`Error checking access: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Проверка прав администратора -router.get('/admin-only', async (req, res) => { - const walletAddress = req.headers['x-wallet-address']; - - if (!walletAddress) { - return res.status(400).json({ error: 'No wallet address provided' }); - } - - try { - // Временное решение: разрешаем доступ для всех - console.log('Admin access requested by:', walletAddress); - - res.json({ success: true }); - } catch (error) { - console.error('Admin check error:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Получение всех токенов доступа -router.get('/tokens', async (req, res) => { - try { - const tokens = await authService.getAllTokens(); - res.json(tokens); - } catch (error) { - logger.error(`Error getting tokens: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Создание токена -router.post('/tokens', async (req, res) => { - const walletAddress = req.headers['x-wallet-address']; - - if (!walletAddress) { - return res.status(400).json({ error: 'No wallet address provided' }); - } - - try { - // Проверяем права администратора - const isAdmin = await authService.checkAdminToken(walletAddress); - - if (!isAdmin) { - return res.status(403).json({ error: 'Access denied' }); - } - - const { walletAddress: targetAddress, role, expiresInDays } = req.body; - - if (!targetAddress || !role || !expiresInDays) { - return res.status(400).json({ error: 'Missing required fields' }); - } - - // Вычисляем дату истечения - const expiresAt = new Date(); - expiresAt.setDate(expiresAt.getDate() + parseInt(expiresInDays)); - - // Создаем токен - const result = await db.query( - 'INSERT INTO access_tokens (wallet_address, role, expires_at) VALUES ($1, $2, $3) RETURNING *', - [targetAddress.toLowerCase(), role, expiresAt] - ); - - res.json({ - id: result.rows[0].id, - walletAddress: result.rows[0].wallet_address, - role: result.rows[0].role, - createdAt: result.rows[0].created_at, - expiresAt: result.rows[0].expires_at, - }); - } catch (error) { - console.error('Token creation error:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Отзыв токена -router.delete('/tokens/:id', async (req, res) => { - const walletAddress = req.headers['x-wallet-address']; - - if (!walletAddress) { - return res.status(400).json({ error: 'No wallet address provided' }); - } - - try { - // Проверяем права администратора - const isAdmin = await authService.checkAdminToken(walletAddress); - - if (!isAdmin) { - return res.status(403).json({ error: 'Access denied' }); - } - - const { id } = req.params; - - // Удаляем токен - await db.query('DELETE FROM access_tokens WHERE id = $1', [id]); - - res.json({ success: true }); - } catch (error) { - console.error('Token revocation error:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Получение информации о роли текущего пользователя -router.get('/role', requireAuth, async (req, res) => { - try { - const role = await authService.getUserRole(req.user.id); - return res.json({ role }); - } catch (error) { - logger.error('Ошибка при получении роли:', error); - return res.status(500).json({ error: 'Внутренняя ошибка сервера' }); - } -}); - -// Получение списка всех пользователей (только для администраторов) -router.get('/users', requireAuth, requireAdmin, async (req, res) => { - try { - const result = await db.query(` - SELECT u.id, ui.identity_value as wallet_address, r.name as role, u.created_at - FROM users u - JOIN roles r ON u.role_id = r.id - LEFT JOIN user_identities ui ON u.id = ui.user_id AND ui.identity_type = 'wallet' - `); - - return res.json(result.rows); - } catch (error) { - logger.error('Ошибка при получении списка пользователей:', error); - return res.status(500).json({ error: 'Внутренняя ошибка сервера' }); - } -}); - -// Обновление роли пользователя -router.post('/update-role', async (req, res) => { - try { - const { userId, role } = req.body; - - if (!userId || !role) { - return res.status(400).json({ error: 'User ID and role are required' }); - } - - const success = await authService.updateUserRole(userId, role); - - if (success) { - res.json({ success: true }); - } else { - res.status(400).json({ error: 'Failed to update role' }); - } - } catch (error) { - logger.error(`Error updating role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Связывание нового идентификатора с аккаунтом -router.post('/link-identity', requireAuth, async (req, res) => { - try { - const { identityType, identityValue } = req.body; - - if (!identityType || !identityValue) { - return res.status(400).json({ error: 'Отсутствуют обязательные поля' }); - } - - // Проверяем, не привязан ли уже этот идентификатор к другому пользователю - const existingUserId = await authService.getUserIdByIdentity(identityType, identityValue); - - if (existingUserId && existingUserId !== req.user.id) { - return res.status(400).json({ error: 'Этот идентификатор уже привязан к другому аккаунту' }); - } - - // Добавляем новый идентификатор - if (!existingUserId) { - await db.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [req.user.id, identityType, identityValue] - ); - } - - // Если добавлен кошелек, проверяем токены - if (identityType === 'wallet') { - await authService.checkTokensAndUpdateRole(identityValue); - } - - // Получаем все идентификаторы пользователя - const identities = await authService.getAllUserIdentities(req.user.id); - - // Получаем текущую роль - const isAdmin = await authService.isAdmin(req.user.id); - - res.json({ - success: true, - identities, - isAdmin - }); - } catch (error) { - logger.error(`Link identity error: ${error.message}`); - res.status(500).json({ error: 'Ошибка сервера' }); - } -}); - -module.exports = router; diff --git a/backend/routes/admin.js b/backend/routes/admin.js index adfc9b9..a668aca 100644 --- a/backend/routes/admin.js +++ b/backend/routes/admin.js @@ -1,15 +1,38 @@ const express = require('express'); const router = express.Router(); -const db = require('../db'); const { requireAdmin } = require('../middleware/auth'); +const authService = require('../services/auth-service'); +const logger = require('../utils/logger'); -// Маршрут для получения списка пользователей -router.get('/users', async (req, res) => { +// Роли +router.get('/roles', requireAdmin, async (req, res) => { try { - const result = await db.query('SELECT * FROM users'); - res.json(result.rows); + const roles = await authService.getAllRoles(); + res.json({ success: true, roles }); } catch (error) { - console.error('Ошибка при получении списка пользователей:', error); + logger.error('Error getting roles:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +router.post('/roles', requireAdmin, async (req, res) => { + 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' }); + } +}); + +// Админ функции +router.get('/users', requireAdmin, async (req, res) => { + 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' }); } }); diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 50c1d92..a80cb25 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,6 +1,5 @@ const express = require('express'); const router = express.Router(); -const { ethers } = require('ethers'); const crypto = require('crypto'); const db = require('../db'); const logger = require('../utils/logger'); @@ -8,11 +7,11 @@ const helmet = require('helmet'); const rateLimit = require('express-rate-limit'); const { checkRole, requireAuth } = require('../middleware/auth'); const { pool } = require('../db'); -const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth'); const authService = require('../services/auth-service'); const { SiweMessage } = require('siwe'); const { sendEmail } = require('../services/emailBot'); const { verificationCodes } = require('../services/telegramBot'); +const { checkTokensAndUpdateRole } = require('../services/auth-service'); // Создайте лимитер для попыток аутентификации const authLimiter = rateLimit({ @@ -87,85 +86,64 @@ async function checkUserRole(address, req) { router.post('/verify', async (req, res) => { try { const { address, signature, message } = req.body; + console.log('Received verification request:', { address, message }); + console.log('Signature:', signature); + + // Проверяем подпись через SIWE + const siwe = new SiweMessage(message); + console.log('Created SIWE message object'); + + const fields = await siwe.verify({ signature }); + console.log('SIWE validation result:', fields); - console.log('Verify request: address=' + address + ', signature=' + signature.substring(0, 10) + '...'); - - // Получаем nonce из базы данных - const nonceResult = await db.query(` - SELECT nonce FROM nonces - WHERE identity_value = $1 AND expires_at > NOW() AND used = false - `, [address.toLowerCase()]); - - if (nonceResult.rows.length === 0) { - console.error(`No valid nonce found for address ${address}`); - return res.status(401).json({ error: 'Invalid or expired nonce' }); - } - - const nonce = nonceResult.rows[0].nonce; - console.log(`Found nonce ${nonce} for address ${address}`); - - // Проверяем подпись - const isValid = await verifySignature(nonce, signature, address); - console.log('Signature verification result:', isValid); - - if (!isValid) { + if (!fields || !fields.success) { + console.log('SIWE validation failed'); return res.status(401).json({ error: 'Invalid signature' }); } - - // Помечаем nonce как использованный - await db.query(` - UPDATE nonces - SET used = true - WHERE identity_value = $1 - `, [address.toLowerCase()]); - - // Находим или создаем пользователя - console.log('Finding or creating user for address:', address); - const { userId, isAdmin } = await findOrCreateUser(address); - console.log('User found/created:', { userId, isAdmin }); - - // Сохраняем guestId перед обновлением сессии - const currentGuestId = req.session.guestId; - - // Устанавливаем пользователя в сессии - req.session.userId = userId; - req.session.address = address; - req.session.isAdmin = isAdmin; - req.session.authenticated = true; - - // Сохраняем guestId в новой сессии - if (currentGuestId) { - req.session.guestId = currentGuestId; + + // Проверяем nonce + const nonceResult = await db.query( + `SELECT nonce FROM nonces + WHERE identity_value = $1 + AND expires_at > NOW()`, + [address.toLowerCase()] + ); + console.log('Nonce check result:', nonceResult.rows); + + if (!nonceResult.rows.length) { + console.log('Invalid or expired nonce'); + return res.status(401).json({ error: 'Invalid or expired nonce' }); } - - // Сохраняем сессию ПЕРЕД отправкой ответа - await new Promise((resolve, reject) => { - req.session.save(err => { - if (err) { - console.error('Error saving session:', err); - reject(err); - } else { - console.log('Session saved successfully'); - resolve(); - } - }); - }); - - console.log('Authentication successful for user:', { - userId, - address, - isAdmin, - guestId: currentGuestId - }); - console.log('Session after save:', req.session); - - // Обрабатываем гостевые сообщения, если они есть - if (currentGuestId) { - console.log(`Processing guest messages for guestId: ${currentGuestId}`); - await authService.processGuestMessages(userId, currentGuestId); + + // Проверяем соответствие nonce + if (nonceResult.rows[0].nonce !== fields.data.nonce) { + console.log('Nonce mismatch'); + return res.status(401).json({ error: 'Invalid nonce' }); } - - return res.json({ + + // Получаем или создаем пользователя + const userResult = await db.query( + `INSERT INTO users (address, created_at, updated_at) + VALUES ($1, NOW(), NOW()) + ON CONFLICT (address) DO UPDATE + SET updated_at = NOW() + RETURNING id, role = 'admin' as is_admin`, + [address] + ); + + const userId = userResult.rows[0].id; + const isAdmin = userResult.rows[0].is_admin; + + // Используем централизованный сервис + await authService.createSession(req, { + userId, + address, + isAdmin, + authType: 'wallet', + guestId: req.session.guestId + }); + + res.json({ authenticated: true, userId, address, @@ -173,7 +151,7 @@ router.post('/verify', async (req, res) => { authType: 'wallet' }); } catch (error) { - console.error('Error during wallet verification:', error); + console.error('Error:', error); res.status(500).json({ error: 'Internal server error' }); } }); @@ -818,31 +796,35 @@ router.post('/link-identity', requireAuth, async (req, res) => { }); // Добавляем маршрут для проверки прав доступа -router.get('/check-access', requireAuth, (req, res) => { +router.get('/check-access', requireAuth, async (req, res) => { try { - // Получаем информацию о пользователе - const userData = { - address: req.session.address, - isAdmin: req.session.isAdmin || false, - roles: req.session.roles || [], - authenticated: true, - }; + const userId = req.session.userId; + const address = req.session.address; - // Проверяем доступ к различным разделам - const access = { - dashboard: true, // Все аутентифицированные пользователи имеют доступ к панели управления - admin: userData.isAdmin, // Только администраторы имеют доступ к админке - contracts: userData.roles.includes('CONTRACT_MANAGER') || userData.isAdmin, - users: userData.roles.includes('USER_MANAGER') || userData.isAdmin, - }; + if (address) { + const isAdmin = await checkTokensAndUpdateRole(address); + + // Обновляем сессию + req.session.isAdmin = isAdmin; - res.json({ - user: userData, - access: access, + return res.json({ + success: true, + isAdmin, + userId, + address + }); + } + + return res.json({ + success: true, + isAdmin: false, + userId, + address: null }); + } catch (error) { - console.error('Ошибка при проверке прав доступа:', error); - res.status(500).json({ error: 'Internal server error' }); + logger.error('Error checking access:', error); + return res.status(500).json({ error: 'Internal server error' }); } }); diff --git a/backend/routes/chat.js b/backend/routes/chat.js index 280417f..8e738ea 100644 --- a/backend/routes/chat.js +++ b/backend/routes/chat.js @@ -1,7 +1,6 @@ const express = require('express'); const router = express.Router(); -const { ChatOllama } = require('@langchain/ollama'); -const { getVectorStore } = require('../services/vectorStore'); +const aiAssistant = require('../services/ai-assistant'); const db = require('../db'); const { requireAuth, requireAdmin } = require('../middleware/auth'); const logger = require('../utils/logger'); @@ -105,7 +104,7 @@ async function processGuestMessages(userId, guestId) { // Получаем ответ от AI console.log(`Getting AI response for message ${msg.id} in ${language}`); - const aiResponse = await getAIResponse(msg.content, language); + const aiResponse = await aiAssistant.getResponse(msg.content, language); // Сохраняем ответ AI в ту же беседу await db.query( @@ -128,54 +127,44 @@ async function processGuestMessages(userId, guestId) { // Обработчик для гостевых сообщений router.post('/guest-message', async (req, res) => { - const { message, language } = req.body; - - // Генерируем временный ID сессии, если его нет - if (!req.session.guestId) { - req.session.guestId = crypto.randomBytes(16).toString('hex'); - } - try { - // Создаем запись в conversations для гостя - const conversationResult = await db.query( - `INSERT INTO conversations (created_at) - VALUES (NOW()) - RETURNING id` - ); - - const conversationId = conversationResult.rows[0].id; + const { message, language } = req.body; + const guestId = req.session.guestId || crypto.randomBytes(16).toString('hex'); - // Создаем метаданные - const metadata = { - guest_id: req.session.guestId, - language: language || 'en' - }; + // Сохраняем ID гостя в сессии + req.session.guestId = guestId; + await req.session.save(); - // Сохраняем только сообщение пользователя - await db.query( - `INSERT INTO messages - (conversation_id, sender_type, content, channel, metadata, created_at) - VALUES ($1, 'guest', $2, 'chat', $3, NOW())`, - [ - conversationId, - message, - JSON.stringify(metadata) - ] + console.log('Saving guest message:', { guestId, message }); + + // Сохраняем сообщение пользователя + const result = await db.query( + 'INSERT INTO guest_messages (guest_id, content, language, is_ai) VALUES ($1, $2, $3, false) RETURNING id', + [guestId, message, language] ); - res.json({ success: true }); + console.log('Guest message saved:', result.rows[0]); + + res.json({ + success: true, + messageId: result.rows[0].id + }); } catch (error) { - console.error('Error processing message:', error); - res.status(500).json({ error: 'Internal server error' }); + console.error('Error saving guest message:', error); + res.status(500).json({ success: false, error: 'Internal server error' }); } }); // Маршрут для обычных сообщений (для аутентифицированных пользователей) router.post('/message', requireAuth, async (req, res) => { - const { message, language } = req.body; - const userId = req.session.userId; - try { + const { message, language } = req.body; + const userId = req.session.userId; + + // Используем методы из aiAssistant вместо прямого обращения к vectorStore + const similarDocs = await aiAssistant.findSimilarDocuments(message); + const aiResponse = await aiAssistant.getResponse(message, language); + // Создаем новую беседу или получаем существующую const conversationResult = await db.query( `INSERT INTO conversations (user_id, created_at) @@ -189,20 +178,25 @@ router.post('/message', requireAuth, async (req, res) => { // Сохраняем сообщение пользователя await db.query( `INSERT INTO messages - (conversation_id, sender_type, content, channel, created_at) - VALUES ($1, 'user', $2, 'chat', NOW())`, - [conversationId, message] + (conversation_id, sender_type, content, channel, metadata, created_at) + VALUES ($1, 'user', $2, 'chat', $3, NOW())`, + [ + conversationId, + message, + JSON.stringify({ language: language || 'ru' }) + ] ); - // Получаем ответ от AI - const aiResponse = await getAIResponse(message, language); - // Сохраняем ответ AI await db.query( `INSERT INTO messages - (conversation_id, sender_type, content, channel, created_at) - VALUES ($1, 'assistant', $2, 'chat', NOW())`, - [conversationId, aiResponse] + (conversation_id, sender_type, content, channel, metadata, created_at) + VALUES ($1, 'assistant', $2, 'chat', $3, NOW())`, + [ + conversationId, + aiResponse, + JSON.stringify({ language: language || 'ru' }) + ] ); res.json({ @@ -210,7 +204,7 @@ router.post('/message', requireAuth, async (req, res) => { message: aiResponse }); } catch (error) { - console.error('Error processing message:', error); + logger.error('Error processing message:', error); res.status(500).json({ error: 'Internal server error' }); } }); @@ -233,47 +227,44 @@ router.get('/models', async (req, res) => { // Получение истории сообщений router.get('/history', async (req, res) => { - const limit = parseInt(req.query.limit) || 2; // По умолчанию только последнее сообщение и ответ - const offset = parseInt(req.query.offset) || 0; - try { + console.log('Session in history route:', { + id: req.sessionID, + userId: req.session.userId, + address: req.session.address, + authenticated: req.session.authenticated + }); + if (!req.session.authenticated || !req.session.userId) { return res.status(401).json({ error: 'Unauthorized' }); } - // Получаем общее количество сообщений - const countResult = await db.query( - `SELECT COUNT(*) as total - FROM messages m - JOIN conversations c ON m.conversation_id = c.id - WHERE c.user_id = $1 - OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true')`, - [req.session.userId, req.session.guestId] - ); - - const total = parseInt(countResult.rows[0].total); + const limit = parseInt(req.query.limit) || 50; + const offset = parseInt(req.query.offset) || 0; // Получаем сообщения с пагинацией const result = await db.query( - `SELECT m.id, m.content, m.sender_type as role, m.created_at, - c.user_id, m.metadata + `SELECT + m.id, + m.content, + m.sender_type as role, + m.created_at, + c.user_id FROM messages m JOIN conversations c ON m.conversation_id = c.id - WHERE c.user_id = $1 - OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true') + WHERE c.user_id = $1 ORDER BY m.created_at DESC - LIMIT $3 OFFSET $4`, - [req.session.userId, req.session.guestId, limit, offset] + LIMIT $2 OFFSET $3`, + [req.session.userId, limit, offset] ); return res.json({ success: true, - messages: result.rows.reverse(), - total + messages: result.rows.reverse() }); } catch (error) { - console.error('Error getting chat history:', error); + logger.error('Error getting chat history:', error); return res.status(500).json({ error: 'Internal server error' }); } }); @@ -314,75 +305,41 @@ router.get('/admin/history', requireAdmin, async (req, res) => { // Обработчик для связывания гостевых сообщений с пользователем router.post('/link-guest-messages', requireAuth, async (req, res) => { try { - const userId = req.session.userId; + const { userId } = req.session; const guestId = req.session.guestId; - + + console.log('Linking messages:', { userId, guestId }); + if (!guestId) { + console.log('No guestId in session'); return res.json({ success: true, message: 'No guest messages to link' }); } - - // Связываем гостевые сообщения с пользователем - await db.query(` - INSERT INTO messages (user_id, content, role, created_at) - SELECT $1, content, CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, created_at - FROM guest_messages - WHERE guest_id = $2 - ORDER BY created_at - `, [userId, guestId]); - - // Удаляем гостевые сообщения - await db.query(` - DELETE FROM guest_messages - WHERE guest_id = $1 - `, [guestId]); - - // Удаляем временный ID из сессии - delete req.session.guestId; - - return res.json({ success: true }); - } catch (error) { - console.error('Error linking guest messages:', error); - return res.status(500).json({ error: 'Internal server error' }); - } -}); -// Обновляем маршрут верификации кошелька -router.post('/verify', async (req, res) => { - const { address, signature, message } = req.body; - - try { - // ... существующий код верификации ... + // Проверяем наличие гостевых сообщений + const guestMessages = await db.query( + 'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)', + [guestId] + ); - // После успешной верификации и создания пользователя - if (req.session.guestId) { - console.log('Found guest messages, processing...'); - await processGuestMessages(userId, req.session.guestId); + console.log('Guest messages check:', guestMessages.rows[0]); + + if (!guestMessages.rows[0].exists) { + console.log('No guest messages found for guestId:', guestId); + return res.json({ success: true, message: 'No guest messages to link' }); } - // Сохраняем данные в сессии - req.session.userId = userId; - req.session.address = address; - req.session.isAdmin = isAdmin; - req.session.authenticated = true; - - console.log('Authentication successful for user:', { - userId, - address, - isAdmin, - guestId: req.session.guestId - }); - - res.json({ - authenticated: true, - userId: userId, - address: address, - isAdmin: isAdmin, - authType: 'wallet' - }); - + // Связываем сообщения + console.log('Calling link_guest_messages function'); + await db.query('SELECT link_guest_messages($1, $2)', [userId, guestId]); + + // Очищаем guestId из сессии после связывания + delete req.session.guestId; + + console.log('Messages linked successfully'); + res.json({ success: true }); } catch (error) { - console.error('Error during wallet verification:', error); - res.status(500).json({ error: 'Internal server error' }); + console.error('Error linking guest messages:', error); + res.status(500).json({ success: false, error: 'Internal server error' }); } }); @@ -406,4 +363,36 @@ router.post('/auth/telegram/verify', async (req, res) => { } }); +// Маршрут для удаления сообщений +router.delete('/message/:id', requireAuth, async (req, res) => { + try { + const messageId = req.params.id; + const userId = req.session.userId; + + // Проверяем права на удаление + const messageCheck = await db.query( + `SELECT m.id + FROM messages m + JOIN conversations c ON m.conversation_id = c.id + WHERE m.id = $1 AND c.user_id = $2`, + [messageId, userId] + ); + + if (messageCheck.rows.length === 0) { + return res.status(403).json({ error: 'Forbidden' }); + } + + // Удаляем сообщение + await db.query( + 'DELETE FROM messages WHERE id = $1', + [messageId] + ); + + res.json({ success: true }); + } catch (error) { + logger.error('Error deleting message:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + module.exports = router; diff --git a/backend/routes/contracts.js b/backend/routes/contracts.js deleted file mode 100644 index 5ee732c..0000000 --- a/backend/routes/contracts.js +++ /dev/null @@ -1,34 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { requireRole } = require('../middleware/auth'); - -// Получение информации о контрактах -router.get('/', (req, res) => { - res.json({ - message: 'Contracts API endpoint', - contracts: [ - // Удаляем AccessToken - // { - // name: 'AccessToken', - // address: process.env.ACCESS_TOKEN_ADDRESS, - // }, - ], - }); -}); - -// Защищенный эндпоинт для получения детальной информации о контрактах -router.get('/details', requireRole('ADMIN'), (req, res) => { - res.json({ - message: 'Contract details endpoint', - contracts: [ - // Удаляем AccessToken - // { - // name: 'AccessToken', - // address: process.env.ACCESS_TOKEN_ADDRESS, - // network: process.env.ETHEREUM_NETWORK_URL.includes('sepolia') ? 'Sepolia' : 'Unknown', - // }, - ], - }); -}); - -module.exports = router; diff --git a/backend/routes/conversations.js b/backend/routes/conversations.js deleted file mode 100644 index 8b13789..0000000 --- a/backend/routes/conversations.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/routes/debug.js b/backend/routes/debug.js deleted file mode 100644 index 7e3f7b4..0000000 --- a/backend/routes/debug.js +++ /dev/null @@ -1,33 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../db'); - -// Маршрут для проверки состояния сервера -router.get('/status', (req, res) => { - res.json({ - status: 'ok', - uptime: process.uptime(), - timestamp: Date.now(), - }); -}); - -// Маршрут для проверки сессии -router.get('/session', (req, res) => { - res.json({ - session: req.session, - authenticated: req.session.authenticated || false, - }); -}); - -// Маршрут для проверки содержимого таблицы session -router.get('/sessions', async (req, res) => { - try { - const result = await db.query('SELECT * FROM session'); - res.json(result.rows); - } catch (error) { - console.error('Ошибка при получении данных из таблицы session:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -module.exports = router; diff --git a/backend/routes/health.js b/backend/routes/health.js deleted file mode 100644 index 8784e99..0000000 --- a/backend/routes/health.js +++ /dev/null @@ -1,36 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../db'); - -router.get('/', async (req, res) => { - try { - // Проверка соединения с базой данных - const dbResult = await db.query('SELECT NOW()'); - - // Проверка состояния сервера - const memoryUsage = process.memoryUsage(); - const uptime = process.uptime(); - - res.json({ - status: 'ok', - timestamp: new Date(), - uptime: uptime, - memory: { - rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB', - heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB', - heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB', - }, - database: { - connected: true, - timestamp: dbResult.rows[0].now, - }, - }); - } catch (error) { - res.status(500).json({ - status: 'error', - error: error.message, - }); - } -}); - -module.exports = router; diff --git a/backend/routes/identities.js b/backend/routes/identities.js index 485c132..8214628 100644 --- a/backend/routes/identities.js +++ b/backend/routes/identities.js @@ -1,59 +1,47 @@ const express = require('express'); const router = express.Router(); -const { linkIdentity, getUserIdentities } = require('../utils/identity-linker'); -const db = require('../db'); const { requireAuth } = require('../middleware/auth'); +const authService = require('../services/auth-service'); +const logger = require('../utils/logger'); -// Получение связанных идентификаторов пользователя +// Получение всех идентификаторов пользователя router.get('/', requireAuth, async (req, res) => { try { - // Получаем ID пользователя по Ethereum-адресу - const result = await db.query('SELECT id FROM users WHERE address = $1', [ - req.session.address, - ]); - - if (result.rows.length === 0) { - return res.status(404).json({ error: 'User not found' }); - } - - const userId = result.rows[0].id; - - // Получаем все идентификаторы пользователя - const identities = await getUserIdentities(userId); - - res.json({ identities }); + const userId = req.session.userId; + const identities = await authService.getUserIdentities(userId); + res.json({ success: true, identities }); } catch (error) { - console.error('Error getting user identities:', error); + logger.error('Error getting identities:', error); res.status(500).json({ error: 'Internal server error' }); } }); -// Удаление связанного идентификатора -router.delete('/:type/:value', requireAuth, async (req, res) => { +// Связывание нового идентификатора +router.post('/link', requireAuth, async (req, res) => { try { - const { type, value } = req.params; + const { type, value } = req.body; + const userId = req.session.userId; - // Получаем ID пользователя по Ethereum-адресу - const result = await db.query('SELECT id FROM users WHERE address = $1', [ - req.session.address, - ]); + await authService.linkIdentity(userId, type, value); - if (result.rows.length === 0) { - return res.status(404).json({ error: 'User not found' }); + // Обновляем сессию + if (type === 'wallet') { + req.session.address = value; + req.session.isAdmin = await authService.checkTokensAndUpdateRole(value); + } else if (type === 'telegram') { + req.session.telegramId = value; + } else if (type === 'email') { + req.session.email = value; } - const userId = result.rows[0].id; - - // Удаляем идентификатор - await db.query( - 'DELETE FROM user_identities WHERE user_id = $1 AND identity_type = $2 AND identity_value = $3', - [userId, type, value] - ); - - res.json({ success: true }); + res.json({ + success: true, + message: 'Identity linked successfully', + isAdmin: req.session.isAdmin + }); } catch (error) { - console.error('Error deleting user identity:', error); - res.status(500).json({ error: 'Internal server error' }); + logger.error('Error linking identity:', error); + res.status(500).json({ error: error.message || 'Internal server error' }); } }); diff --git a/backend/routes/messages.js b/backend/routes/messages.js deleted file mode 100644 index 2fb63d3..0000000 --- a/backend/routes/messages.js +++ /dev/null @@ -1,246 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const { pool } = require('../db'); -const { requireAuth } = require('../middleware/auth'); -const { processMessage, getUserInfo } = require('../services/ai-assistant'); - -// Получение списка диалогов пользователя -router.get('/conversations', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - - const result = await pool.query( - `SELECT * FROM conversation_view - WHERE user_id = $1 - ORDER BY updated_at DESC`, - [userId] - ); - - res.json(result.rows); - } catch (error) { - console.error('Error fetching conversations:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Получение сообщений диалога -router.get('/conversations/:id/messages', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - const conversationId = req.params.id; - - // Проверка доступа к диалогу - const conversationCheck = await pool.query( - 'SELECT id FROM conversations WHERE id = $1 AND user_id = $2', - [conversationId, userId] - ); - - if (conversationCheck.rows.length === 0) { - return res.status(403).json({ error: 'Access denied' }); - } - - const result = await pool.query( - `SELECT * FROM message_view - WHERE conversation_id = $1 - ORDER BY created_at ASC`, - [conversationId] - ); - - res.json(result.rows); - } catch (error) { - console.error('Error fetching messages:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Отправка сообщения -router.post('/conversations/:id/messages', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - const conversationId = req.params.id; - const { content } = req.body; - - if (!content || content.trim() === '') { - return res.status(400).json({ error: 'Message content is required' }); - } - - // Проверка доступа к диалогу - const conversationCheck = await pool.query( - 'SELECT id FROM conversations WHERE id = $1 AND user_id = $2', - [conversationId, userId] - ); - - if (conversationCheck.rows.length === 0) { - return res.status(403).json({ error: 'Access denied' }); - } - - // Обновление времени последней активности диалога - await pool.query('UPDATE conversations SET updated_at = NOW() WHERE id = $1', [conversationId]); - - // Сохранение сообщения пользователя - const userMessageResult = await pool.query( - `INSERT INTO messages - (conversation_id, sender_type, sender_id, content, channel) - VALUES ($1, 'user', $2, $3, 'web') - RETURNING *`, - [conversationId, userId, content] - ); - - // Получение информации о пользователе для ИИ - const userInfo = await getUserInfo(userId); - - // Обработка сообщения ИИ-ассистентом - const aiResponse = await processMessage(userId, content, userInfo.language || 'ru'); - - // Сохранение ответа ИИ - const aiMessageResult = await pool.query( - `INSERT INTO messages - (conversation_id, sender_type, content, channel) - VALUES ($1, 'ai', $2, 'web') - RETURNING *`, - [conversationId, aiResponse] - ); - - res.json({ - userMessage: userMessageResult.rows[0], - aiMessage: aiMessageResult.rows[0], - }); - } catch (error) { - console.error('Error sending message:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Создание нового диалога -router.post('/conversations', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - const { title } = req.body; - - // Создание нового диалога - const result = await pool.query( - `INSERT INTO conversations (user_id, title) - VALUES ($1, $2) - RETURNING *`, - [userId, title || 'Новый диалог'] - ); - - res.json(result.rows[0]); - } catch (error) { - console.error('Error creating conversation:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Обновление заголовка диалога -router.put('/conversations/:id', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - const conversationId = req.params.id; - const { title } = req.body; - - if (!title || title.trim() === '') { - return res.status(400).json({ error: 'Title is required' }); - } - - // Проверка доступа к диалогу - const conversationCheck = await pool.query( - 'SELECT id FROM conversations WHERE id = $1 AND user_id = $2', - [conversationId, userId] - ); - - if (conversationCheck.rows.length === 0) { - return res.status(403).json({ error: 'Access denied' }); - } - - // Обновление заголовка - const result = await pool.query( - 'UPDATE conversations SET title = $1 WHERE id = $2 RETURNING *', - [title, conversationId] - ); - - res.json(result.rows[0]); - } catch (error) { - console.error('Error updating conversation:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Удаление диалога -router.delete('/conversations/:id', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - const conversationId = req.params.id; - - // Проверка доступа к диалогу - const conversationCheck = await pool.query( - 'SELECT id FROM conversations WHERE id = $1 AND user_id = $2', - [conversationId, userId] - ); - - if (conversationCheck.rows.length === 0) { - return res.status(403).json({ error: 'Access denied' }); - } - - // Удаление диалога (каскадно удалит все сообщения) - await pool.query('DELETE FROM conversations WHERE id = $1', [conversationId]); - - res.json({ success: true }); - } catch (error) { - console.error('Error deleting conversation:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Маршруты для администраторов - -// Получение всех диалогов (только для администраторов) -router.get('/admin/conversations', requireAuth, async (req, res) => { - try { - // Проверка прав администратора - if (!req.session.isAdmin) { - return res.status(403).json({ error: 'Admin access required' }); - } - - const result = await pool.query( - `SELECT * FROM conversation_view - ORDER BY updated_at DESC` - ); - - res.json(result.rows); - } catch (error) { - console.error('Error fetching all conversations:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Получение статистики по каналам (только для администраторов) -router.get('/admin/stats/channels', requireAuth, async (req, res) => { - try { - // Проверка прав администратора - if (!req.session.isAdmin) { - return res.status(403).json({ error: 'Admin access required' }); - } - - const result = await pool.query( - `SELECT - channel, - COUNT(*) AS message_count, - COUNT(DISTINCT conversation_id) AS conversation_count, - COUNT(DISTINCT sender_id) AS user_count, - MIN(created_at) AS first_message, - MAX(created_at) AS last_message - FROM - messages - GROUP BY - channel` - ); - - res.json(result.rows); - } catch (error) { - console.error('Error fetching channel stats:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -module.exports = router; diff --git a/backend/routes/roles.js b/backend/routes/roles.js deleted file mode 100644 index d403dea..0000000 --- a/backend/routes/roles.js +++ /dev/null @@ -1,166 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const db = require('../db'); -const { requireAuth, requireAdmin } = require('../middleware/auth'); -const authService = require('../services/auth-service'); -const logger = require('../utils/logger'); -const { USER_ROLES } = require('../utils/constants'); - -// Получение всех ролей -router.get('/', async (req, res) => { - try { - const result = await db.query('SELECT * FROM roles ORDER BY id'); - res.json(result.rows); - } catch (error) { - logger.error(`Error getting roles: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Получение роли текущего пользователя -router.get('/me', requireAuth, async (req, res) => { - try { - const userId = req.session.userId; - - const result = await db.query(` - SELECT r.name as role - FROM users u - JOIN roles r ON u.role_id = r.id - WHERE u.id = $1 - `, [userId]); - - if (result.rows.length === 0) { - return res.status(404).json({ error: 'User not found' }); - } - - res.json({ role: result.rows[0].role }); - } catch (error) { - logger.error(`Error getting user role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Создание новой роли (только для администраторов) -router.post('/', requireAuth, requireAdmin, async (req, res) => { - try { - const { name, description } = req.body; - - if (!name) { - return res.status(400).json({ error: 'Role name is required' }); - } - - // Проверяем, существует ли уже такая роль - const existingRole = await db.query('SELECT * FROM roles WHERE name = $1', [name]); - - if (existingRole.rows.length > 0) { - return res.status(400).json({ error: 'Role already exists' }); - } - - // Создаем новую роль - const result = await db.query( - 'INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING *', - [name, description || ''] - ); - - res.status(201).json(result.rows[0]); - } catch (error) { - logger.error(`Error creating role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Обновление роли (только для администраторов) -router.put('/:id', requireAuth, requireAdmin, async (req, res) => { - try { - const { id } = req.params; - const { name, description } = req.body; - - if (!name) { - return res.status(400).json({ error: 'Role name is required' }); - } - - // Проверяем, существует ли роль - const existingRole = await db.query('SELECT * FROM roles WHERE id = $1', [id]); - - if (existingRole.rows.length === 0) { - return res.status(404).json({ error: 'Role not found' }); - } - - // Обновляем роль - const result = await db.query( - 'UPDATE roles SET name = $1, description = $2 WHERE id = $3 RETURNING *', - [name, description || '', id] - ); - - res.json(result.rows[0]); - } catch (error) { - logger.error(`Error updating role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Удаление роли (только для администраторов) -router.delete('/:id', requireAuth, requireAdmin, async (req, res) => { - try { - const { id } = req.params; - - // Проверяем, существует ли роль - const existingRole = await db.query('SELECT * FROM roles WHERE id = $1', [id]); - - if (existingRole.rows.length === 0) { - return res.status(404).json({ error: 'Role not found' }); - } - - // Проверяем, не используется ли роль - const usersWithRole = await db.query('SELECT COUNT(*) FROM users WHERE role_id = $1', [id]); - - if (parseInt(usersWithRole.rows[0].count) > 0) { - return res.status(400).json({ error: 'Cannot delete role that is assigned to users' }); - } - - // Удаляем роль - await db.query('DELETE FROM roles WHERE id = $1', [id]); - - res.json({ success: true }); - } catch (error) { - logger.error(`Error deleting role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Назначение роли пользователю (только для администраторов) -router.post('/assign', requireAuth, requireAdmin, async (req, res) => { - try { - const { userId, roleName } = req.body; - - if (!userId || !roleName) { - return res.status(400).json({ error: 'User ID and role name are required' }); - } - - // Проверяем, существует ли роль - const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', [roleName]); - - if (roleResult.rows.length === 0) { - return res.status(404).json({ error: 'Role not found' }); - } - - const roleId = roleResult.rows[0].id; - - // Проверяем, существует ли пользователь - const userResult = await db.query('SELECT * FROM users WHERE id = $1', [userId]); - - if (userResult.rows.length === 0) { - return res.status(404).json({ error: 'User not found' }); - } - - // Назначаем роль - await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleId, userId]); - - res.json({ success: true }); - } catch (error) { - logger.error(`Error assigning role: ${error.message}`); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -module.exports = router; \ No newline at end of file diff --git a/backend/scripts/check-state.js b/backend/scripts/check-state.js deleted file mode 100644 index 08df758..0000000 --- a/backend/scripts/check-state.js +++ /dev/null @@ -1,43 +0,0 @@ -const { getContract } = require('../utils/contracts'); -const logger = require('../utils/logger'); - -async function main() { - try { - const accessToken = await getContract('AccessToken'); - - const owner = await accessToken.owner(); - logger.info('Contract owner:', owner); - - // Проверяем все токены и их владельцев - logger.info('\nAll tokens:'); - for (let i = 1; i <= 10; i++) { - try { - const tokenOwner = await accessToken.ownerOf(i); - logger.info(`Token ${i} owner: ${tokenOwner}`); - } catch (error) { - if (!error.message.includes('invalid token ID')) { - logger.error(`Token ${i} error:`, error.message); - } - } - } - - // Проверяем активные токены для всех известных адресов - const addresses = [owner, '0x70997970C51812dc3A010C7d01b50e0d17dc79C8']; - - logger.info('\nActive tokens:'); - for (const address of addresses) { - const activeToken = await accessToken.activeTokens(address); - logger.info(`${address}: Token ${activeToken.toString()}`); - } - } catch (error) { - logger.error(error); - process.exit(1); - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/backend/scripts/check-tokens.js b/backend/scripts/check-tokens.js deleted file mode 100644 index 679c5e3..0000000 --- a/backend/scripts/check-tokens.js +++ /dev/null @@ -1,21 +0,0 @@ -const { checkAllUsersTokens } = require('../utils/access-check'); -const logger = require('../utils/logger'); - -async function main() { - logger.info('Starting token balance check for all users'); - - try { - await checkAllUsersTokens(); - logger.info('Token balance check completed successfully'); - } catch (error) { - logger.error(`Error during token balance check: ${error.message}`); - } -} - -// Запуск скрипта -main() - .then(() => process.exit(0)) - .catch(error => { - logger.error(`Unhandled error: ${error.message}`); - process.exit(1); - }); \ No newline at end of file diff --git a/backend/scripts/deploy-access.js b/backend/scripts/deploy-access.js deleted file mode 100644 index ad47ddc..0000000 --- a/backend/scripts/deploy-access.js +++ /dev/null @@ -1,29 +0,0 @@ -const hre = require('hardhat'); -const logger = require('../utils/logger'); - -async function main() { - try { - const AccessToken = await hre.ethers.getContractFactory('AccessToken'); - const accessToken = await AccessToken.deploy(); - await accessToken.waitForDeployment(); - - const address = await accessToken.getAddress(); - logger.info('AccessToken deployed to:', address); - - // Создаем первый админский токен для владельца контракта - const [owner] = await hre.ethers.getSigners(); - const tx = await accessToken.mintAccessToken(owner.address, 1); // 1 = ADMIN - await tx.wait(); - logger.info('Admin token minted for:', owner.address); - } catch (error) { - logger.error('Deployment error:', error); - process.exit(1); - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - logger.error('Unhandled error:', error); - process.exit(1); - }); diff --git a/backend/scripts/deploy.js b/backend/scripts/deploy.js deleted file mode 100644 index e69fff0..0000000 --- a/backend/scripts/deploy.js +++ /dev/null @@ -1,22 +0,0 @@ -const hre = require('hardhat'); - -async function main() { - console.log('Начинаем деплой контракта...'); - - // Получаем контракт - const MyContract = await hre.ethers.getContractFactory('MyContract'); - - // Деплоим контракт - const myContract = await MyContract.deploy(); - await myContract.waitForDeployment(); - - const address = await myContract.getAddress(); - console.log('Контракт развернут по адресу:', address); -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/backend/scripts/init-roles.js b/backend/scripts/init-roles.js deleted file mode 100644 index 5037e4c..0000000 --- a/backend/scripts/init-roles.js +++ /dev/null @@ -1,58 +0,0 @@ -const hre = require('hardhat'); - -async function main() { - const accessToken = await hre.ethers.getContractAt( - 'AccessToken', - '0xF352c498cF0857F472dC473E4Dd39551E79B1063' - ); - - const owner = await accessToken.owner(); - console.log('Contract owner:', owner); - - // Создаем админский токен для владельца - try { - const tx = await accessToken.mintAccessToken(owner, 0); // 0 = ADMIN - await tx.wait(); - console.log(`Admin token minted for ${owner}`); - - const role = await accessToken.checkRole(owner); - console.log('Owner role:', ['ADMIN', 'MODERATOR', 'SUPPORT'][role]); - } catch (error) { - console.log('Admin token minting error:', error.message); - } - - // Создаем тестовый токен модератора - const moderatorAddress = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'; // Тестовый адрес модератора - try { - const tx = await accessToken.mintAccessToken(moderatorAddress, 1); // 1 = MODERATOR - await tx.wait(); - console.log(`Moderator token minted for ${moderatorAddress}`); - - const role = await accessToken.checkRole(moderatorAddress); - console.log('Moderator role:', ['ADMIN', 'MODERATOR', 'SUPPORT'][role]); - } catch (error) { - console.log('Moderator token minting error:', error.message); - } - - // Проверяем все токены - console.log('\nChecking all tokens:'); - for (let i = 1; i <= 5; i++) { - try { - const owner = await accessToken.ownerOf(i); - const role = await accessToken.checkRole(owner); - console.log(`Token ${i}: Owner ${owner}, Role: ${['ADMIN', 'MODERATOR', 'SUPPORT'][role]}`); - } catch (error) { - // Пропускаем несуществующие токены - if (!error.message.includes('nonexistent token')) { - console.log(`Token ${i} error:`, error.message); - } - } - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/backend/scripts/run-migrations.js b/backend/scripts/run-migrations.js index 648ca74..c5cc15e 100644 --- a/backend/scripts/run-migrations.js +++ b/backend/scripts/run-migrations.js @@ -1,13 +1,8 @@ -const { Pool } = require('pg'); -const fs = require('fs'); +const fs = require('fs').promises; const path = require('path'); require('dotenv').config(); - -// Подключение к БД -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}); +const { pool } = require('../db'); +const logger = require('../utils/logger'); async function runMigrations() { try { @@ -18,39 +13,64 @@ async function runMigrations() { CREATE TABLE IF NOT EXISTS migrations ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, - applied_at TIMESTAMP DEFAULT NOW() + executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) `); - // Получаем список уже примененных миграций + // Получаем список выполненных миграций const { rows } = await pool.query('SELECT name FROM migrations'); - const appliedMigrations = rows.map((row) => row.name); + const executedMigrations = new Set(rows.map(row => row.name)); - // Получаем список файлов миграций - const migrationsDir = path.join(__dirname, '../migrations'); - const migrationFiles = fs - .readdirSync(migrationsDir) - .filter((file) => file.endsWith('.sql')) - .sort(); // Сортируем файлы по имени + // Читаем файлы миграций + const migrationsDir = path.join(__dirname, '../db/migrations'); + const files = await fs.readdir(migrationsDir); - // Применяем миграции, которые еще не были применены + // Сортируем файлы по номеру + const migrationFiles = files + .filter(f => f.endsWith('.sql')) + .sort((a, b) => { + const numA = parseInt(a.split('_')[0]); + const numB = parseInt(b.split('_')[0]); + return numA - numB; + }); + + // Выполняем миграции for (const file of migrationFiles) { - if (!appliedMigrations.includes(file)) { - console.log(`Применение миграции: ${file}`); - - // Читаем содержимое файла миграции + if (!executedMigrations.has(file)) { const filePath = path.join(migrationsDir, file); - const sql = fs.readFileSync(filePath, 'utf8'); + const sql = await fs.readFile(filePath, 'utf-8'); - // Выполняем SQL-запросы из файла - await pool.query(sql); + await pool.query('BEGIN'); + try { + await pool.query(sql); + await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]); + await pool.query('COMMIT'); + logger.info(`Migration ${file} executed successfully`); + } catch (error) { + await pool.query('ROLLBACK'); + throw error; + } + } + } - // Записываем информацию о примененной миграции - await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]); - - console.log(`Миграция ${file} успешно применена`); - } else { - console.log(`Миграция ${file} уже применена`); + // Выполняем SQL-функции + const functionsDir = path.join(migrationsDir, 'functions'); + if (await fs.stat(functionsDir).then(() => true).catch(() => false)) { + const functionFiles = await fs.readdir(functionsDir); + + for (const file of functionFiles) { + if (file.endsWith('.sql')) { + const filePath = path.join(functionsDir, file); + const sql = await fs.readFile(filePath, 'utf-8'); + + try { + await pool.query(sql); + logger.info(`Function ${file} executed successfully`); + } catch (error) { + logger.error(`Error executing function ${file}:`, error); + throw error; + } + } } } diff --git a/backend/scripts/update-user-roles.js b/backend/scripts/update-user-roles.js deleted file mode 100644 index c8cdc4d..0000000 --- a/backend/scripts/update-user-roles.js +++ /dev/null @@ -1,53 +0,0 @@ -const { checkAllUsersTokens } = require('../utils/access-check'); -const db = require('../db'); -const logger = require('../utils/logger'); - -async function updateRolesFromOldStructure() { - try { - logger.info('Starting migration of user roles from old structure'); - - // Получаем пользователей со старым полем role - const usersWithOldRoles = await db.query(` - SELECT id, role, address - FROM users - WHERE role IS NOT NULL AND role_id IS NULL - `); - - logger.info(`Found ${usersWithOldRoles.rows.length} users with old role structure`); - - for (const user of usersWithOldRoles.rows) { - // Определяем ID роли - let roleId = 2; // По умолчанию 'user' - - if (user.role === 'ADMIN' || user.role === 'admin') { - roleId = 1; // 'admin' - } - - // Обновляем пользователя - await db.query( - 'UPDATE users SET role_id = $1 WHERE id = $2', - [roleId, user.id] - ); - - logger.info(`Updated user ${user.id} with role_id ${roleId} (from old role ${user.role})`); - } - - // Запускаем проверку токенов для всех пользователей - await checkAllUsersTokens(); - - logger.info('Role migration completed successfully'); - } catch (error) { - logger.error(`Error during role migration: ${error.message}`); - } -} - -// Запуск скрипта -updateRolesFromOldStructure() - .then(() => { - logger.info('Migration script completed'); - process.exit(0); - }) - .catch(error => { - logger.error(`Unhandled error: ${error.message}`); - process.exit(1); - }); \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 027f284..dd1a81c 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,39 +1,19 @@ require('dotenv').config(); const express = require('express'); const cors = require('cors'); -const { SiweMessage, generateNonce } = require('siwe'); const { ethers } = require('ethers'); -// const TelegramBotService = require('./services/telegramBot'); const EmailBotService = require('./services/emailBot'); -const { initializeVectorStore } = require('./services/vectorStore'); const session = require('express-session'); const { app, nonceStore } = require('./app'); const usersRouter = require('./routes/users'); const authRouter = require('./routes/auth'); -const contractsRouter = require('./routes/contracts'); -const accessRouter = require('./routes/access'); -const path = require('path'); -const axios = require('axios'); -const { ChatOllama } = require('@langchain/ollama'); -const { getVectorStore } = require('./services/vectorStore'); -// const debugRoutes = require('./routes/debug'); const identitiesRouter = require('./routes/identities'); const { pool } = require('./db'); -const fs = require('fs'); -const pgSession = require('connect-pg-simple')(session); -const sessionStore = new pgSession({ - pool: pool, - tableName: 'sessions', - createTableIfMissing: true, -}); const helmet = require('helmet'); -// const csrf = require('csurf'); -// const cookieParser = require('cookie-parser'); -const messagesRouter = require('./routes/messages'); -const sessionMiddleware = require('./middleware/session'); - -// Импорт сервисов -const telegramService = require('./services/telegramBot'); +const TelegramBotService = require('./services/telegramBot'); +const pgSession = require('connect-pg-simple')(session); +const authService = require('./services/auth-service'); +const logger = require('./utils/logger'); const PORT = process.env.PORT || 8000; @@ -42,83 +22,21 @@ console.log('Переменная окружения PORT:', process.env.PORT); console.log('Используемый порт:', process.env.PORT || 8000); // Инициализация сервисов -let telegramBot; -let emailBot; +async function initServices() { + try { + console.log('Инициализация сервисов...'); -// Проверяем, что библиотека ethers.js правильно импортирована -console.log('Ethers.js version:', ethers.version); + if (process.env.TELEGRAM_BOT_TOKEN) { + const telegramBot = new TelegramBotService(process.env.TELEGRAM_BOT_TOKEN); + global.telegramBot = telegramBot; // Сохраняем экземпляр глобально + console.log('Telegram бот инициализирован'); + } -// Порядок middleware важен! -// 1. CORS должен быть первым -app.use( - cors({ - origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], // Укажем точные домены - credentials: true, // Важно для передачи куки - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'] - }) -); - -// Добавьте после настройки CORS -app.use(helmet()); - -// 2. Затем парсеры -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -// Добавьте после настройки парсеров -app.use((req, res, next) => { - // if (req.method === 'POST' && req.headers['content-type'] === 'application/json') { - // console.log('POST request body:', { - // url: req.url, - // body: JSON.stringify(req.body) - // }); - // } - next(); -}); - -const requireAuth = (req, res, next) => { - if (!req.session.authenticated || !req.session.address) { - return res.status(401).json({ error: 'Unauthorized' }); + console.log('Все сервисы успешно инициализированы'); + } catch (error) { + console.error('Ошибка при инициализации сервисов:', error); } - next(); -}; - -app.use('/api/protected', requireAuth); - -// Добавляем middleware для проверки состояния аутентификации -app.use((req, res, next) => { - // console.log('Auth check middleware:', { - // url: req.url, - // method: req.method, - // sessionID: req.sessionID, - // session: req.session ? { - // isAuthenticated: req.session.isAuthenticated, - // authenticated: req.session.authenticated, - // address: req.session.address, - // isAdmin: req.session.isAdmin - // } : null - // }); - next(); -}); - -// Добавьте после настройки парсеров -app.use((req, res, next) => { - // if (req.method === 'POST' && req.headers['content-type'] === 'application/json') { - // console.log('POST request body:', { - // url: req.url, - // body: JSON.stringify(req.body) - // }); - // } - next(); -}); - -// Добавляем middleware для отладки сессий -app.use((req, res, next) => { - console.log('Сессия:', req.session); - console.log('Куки:', req.headers.cookie); - next(); -}); +} // Настройка сессий app.use(session({ @@ -136,551 +54,34 @@ app.use(session({ } })); -async function initServices() { - try { - console.log('Инициализация сервисов...'); - - // Инициализируем ботов, если они нужны - if (process.env.TELEGRAM_BOT_TOKEN) { - telegramBot = new telegramService(process.env.TELEGRAM_BOT_TOKEN); - console.log('Telegram бот инициализирован'); - } - - if (process.env.EMAIL_USER && process.env.EMAIL_PASS) { - emailBot = new EmailBotService(process.env.EMAIL_USER, process.env.EMAIL_PASS); - console.log('Email бот инициализирован'); - } - - console.log('Все сервисы успешно инициализированы'); - } catch (error) { - console.error('Ошибка при инициализации сервисов:', error); - } -} - +// Маршруты API app.use('/api/users', usersRouter); app.use('/api/auth', authRouter); -app.use('/api/contracts', contractsRouter); -app.use('/api/access', accessRouter); -// app.use('/api/chat', chatRouter); -// app.use('/api/debug', debugRoutes); app.use('/api/identities', identitiesRouter); -app.use('/api/messages', messagesRouter); -// Добавьте простой эндпоинт для проверки состояния сервера +// Эндпоинт для проверки состояния сервера app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); -// Добавьте после настройки маршрутов -app.post('/api/verify', async (req, res) => { +// Запуск сервера +app.listen(PORT, async () => { try { - // Перенаправляем запрос на /api/auth/verify - const { message, signature } = req.body; - console.log('Перенаправление запроса на /api/auth/verify:', { message, signature }); - - // Проверяем наличие необходимых данных - if (!message || !message.address || !signature) { - return res.status(400).json({ - success: false, - error: 'Отсутствуют необходимые данные для верификации', - }); - } - - const address = message.address.toLowerCase(); - console.log('Адрес из сообщения:', address); - - // Проверяем, является ли пользователь администратором - const isAdmin = true; // Для примера всегда true - - try { - const siweMessage = new SiweMessage(message); - const fields = await siweMessage.validate(signature); - - if (fields.address.toLowerCase() !== address.toLowerCase()) { - return res.status(401).json({ success: false, error: 'Invalid signature' }); - } - - // Только после проверки устанавливаем сессию - req.session.authenticated = true; - req.session.address = fields.address; - req.session.lastSignature = signature; - - // Сохраняем сессию - await new Promise((resolve, reject) => { - req.session.save((err) => { - if (err) { - console.error('Ошибка при сохранении сессии:', err); - reject(err); - } else { - console.log('Сессия успешно сохранена'); - resolve(); - } - }); - }); - } catch (error) { - return res.status(401).json({ success: false, error: error.message }); - } - - // Сохраняем данные в сессии - req.session.isAuthenticated = true; - req.session.isAdmin = isAdmin; - - // Явно сохраняем сессию - req.session.save((err) => { - if (err) { - console.error('Ошибка сохранения сессии:', err); - return res.status(500).json({ error: 'Session save error' }); - } - - console.log('Сессия успешно сохранена:', { - sessionID: req.sessionID, - session: { - isAuthenticated: req.session.isAuthenticated, - authenticated: req.session.authenticated, - address: req.session.address, - isAdmin: req.session.isAdmin, - }, - }); - - res.cookie('authToken', 'true', { - maxAge: 86400000, - httpOnly: false, - secure: false, - sameSite: 'lax', - path: '/', - }); - - res.json({ - success: true, - address: address, - isAdmin: isAdmin, - }); - }); + await initServices(); + console.log('Server is running on port', PORT); } catch (error) { - console.error('Ошибка верификации:', error); - res.status(500).json({ - success: false, - error: error.message || 'Внутренняя ошибка сервера', - }); - } -}); - -// Добавьте после настройки маршрутов -app.get('/api/session', (req, res) => { - console.log('Запрос сессии в server.js:', { - sessionExists: !!req.session, - sessionID: req.sessionID, - isAuthenticated: req.session?.isAuthenticated, - authenticated: req.session?.authenticated, - address: req.session?.address, - }); - - if (req.session && (req.session.isAuthenticated || req.session.authenticated)) { - res.json({ - isAuthenticated: true, - authenticated: true, - address: req.session.address, - isAdmin: req.session.isAdmin, - }); - } else { - res.json({ - isAuthenticated: false, - authenticated: false, - address: null, - isAdmin: false, - }); - } -}); - -app.get('/api/balance', requireAuth, async (req, res) => { - try { - const balance = await contract.balanceOf(req.session.address); - res.json({ balance: balance.toString() }); - } catch (error) { - res.status(500).json({ error: error.message }); - } -}); - -// Добавляем тестовые маршруты API -app.get('/api/public', (req, res) => { - res.json({ message: 'This is a public API endpoint' }); -}); - -app.get('/api/protected', (req, res) => { - res.json({ - message: 'This is a protected API endpoint', - user: { - address: req.session.address, - isAdmin: req.session.isAdmin, - }, - }); -}); - -app.get('/api/admin', (req, res) => { - res.json({ - message: 'This is an admin API endpoint', - user: { - address: req.session.address, - isAdmin: req.session.isAdmin, - }, - }); -}); - -// Добавьте обработчик ошибок -app.use((err, req, res, next) => { - console.error('Глобальная ошибка:', err.stack); - if (!res.headersSent) { - res.status(500).json({ error: 'Внутренняя ошибка сервера' }); - } -}); - -// Перед запуском сервера -console.log('Перед запуском сервера на порту:', PORT); - -// Запуск сервера и инициализация сервисов -let server; - -checkDatabaseStructure().then(() => { - // Запускаем сервер - server = app.listen(PORT, () => { - console.log(`Server is running on port ${PORT}`); - console.log('Server address:', server.address()); - }); -}); - -// Добавляем graceful shutdown -process.on('SIGTERM', () => { - console.log('SIGTERM signal received: closing HTTP server'); - server.close(() => { - console.log('HTTP server closed'); - process.exit(0); - }); -}); - -// Проверяем доступность Ollama сервера -async function checkOllamaServer() { - try { - const response = await axios.get('http://localhost:11434/api/tags'); - if (response.status === 200) { - console.log('Ollama сервер доступен'); - - // Тестируем прямой запрос к Ollama - try { - console.log('Тестируем прямой запрос к Ollama...'); - const model = new ChatOllama({ - baseUrl: 'http://localhost:11434', - model: 'llama3', - temperature: 0.2, - }); - - const result = await model.invoke('Привет, как дела?'); - console.log('Ответ от Ollama:', result); - } catch (testError) { - console.error('Ошибка при тестировании Ollama:', testError); - } - - // Инициализируем векторное хранилище - try { - console.log('Инициализируем векторное хранилище...'); - const vectorStore = await getVectorStore(); - console.log('Векторное хранилище инициализировано'); - } catch (vectorError) { - console.error('Ошибка при инициализации векторного хранилища:', vectorError); - } - - return true; - } - return false; - } catch (error) { - console.error('Ollama сервер недоступен:', error.message); - return false; - } -} - -// Настройка периодической очистки устаревших сессий -const pgSessionCleanup = setInterval(function () { - console.log('Cleaning up expired sessions...'); - pool - .query('DELETE FROM session WHERE expire < NOW()') - .then((result) => { - if (result.rowCount > 0) { - console.log(`Removed ${result.rowCount} expired sessions`); - } - }) - .catch((err) => console.error('Error cleaning up sessions:', err)); -}, 3600000); // Очистка каждый час - -// Очистка интервала при завершении работы -process.on('SIGTERM', () => { - clearInterval(pgSessionCleanup); - console.log('SIGTERM signal received: closing HTTP server'); - server.close(() => { - console.log('HTTP server closed'); - process.exit(0); - }); -}); - -// Функция для создания таблиц -async function ensureTablesExist() { - try { - // Проверяем существование таблицы users - const result = await pool.query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = 'users' - ); - `); - - // Если таблица не существует, создаем все таблицы - if (!result.rows[0].exists) { - console.log('Таблицы не найдены, создаем...'); - - // SQL-запросы для создания таблиц - const createTablesSql = ` - -- Таблица пользователей - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - address VARCHAR(255) UNIQUE, - email VARCHAR(255) UNIQUE, - telegram_id VARCHAR(255) UNIQUE, - username VARCHAR(255), - is_admin BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() - ); - - -- Индексы для таблицы пользователей - CREATE INDEX IF NOT EXISTS idx_users_address ON users(address); - CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); - CREATE INDEX IF NOT EXISTS idx_users_telegram_id ON users(telegram_id); - - -- Таблица сессий - CREATE TABLE IF NOT EXISTS session ( - sid VARCHAR NOT NULL, - sess JSON NOT NULL, - expire TIMESTAMP(6) NOT NULL, - CONSTRAINT session_pkey PRIMARY KEY (sid) - ); - - -- Индекс для таблицы сессий - CREATE INDEX IF NOT EXISTS idx_session_expire ON session(expire); - - -- Таблица канбан-досок - CREATE TABLE IF NOT EXISTS kanban_boards ( - id SERIAL PRIMARY KEY, - title VARCHAR(255) NOT NULL, - description TEXT, - owner_id INTEGER REFERENCES users(id), - is_public BOOLEAN DEFAULT FALSE, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() - ); - - -- Таблица колонок канбан-доски - CREATE TABLE IF NOT EXISTS kanban_columns ( - id SERIAL PRIMARY KEY, - board_id INTEGER REFERENCES kanban_boards(id) ON DELETE CASCADE, - title VARCHAR(255) NOT NULL, - position INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() - ); - - -- Таблица карточек канбан-доски - CREATE TABLE IF NOT EXISTS kanban_cards ( - id SERIAL PRIMARY KEY, - column_id INTEGER REFERENCES kanban_columns(id) ON DELETE CASCADE, - title VARCHAR(255) NOT NULL, - description TEXT, - position INTEGER NOT NULL, - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() - ); - - -- Индексы для таблиц канбан - CREATE INDEX IF NOT EXISTS idx_kanban_boards_owner ON kanban_boards(owner_id); - CREATE INDEX IF NOT EXISTS idx_kanban_columns_board ON kanban_columns(board_id); - CREATE INDEX IF NOT EXISTS idx_kanban_cards_column ON kanban_cards(column_id); - - -- Таблица сообщений чата - CREATE TABLE IF NOT EXISTS chat_messages ( - id SERIAL PRIMARY KEY, - user_id INTEGER REFERENCES users(id), - sender VARCHAR(50) NOT NULL, - message TEXT NOT NULL, - created_at TIMESTAMP DEFAULT NOW() - ); - - -- Индекс для таблицы сообщений - CREATE INDEX IF NOT EXISTS idx_chat_messages_user ON chat_messages(user_id); - `; - - await pool.query(createTablesSql); - console.log('Таблицы успешно созданы'); - } else { - console.log('Таблицы уже существуют'); - } - } catch (error) { - console.error('Ошибка при проверке/создании таблиц:', error); - } -} - -// Вызываем функцию при запуске сервера -ensureTablesExist(); - -// Добавляем middleware для проверки аутентификации -app.use('/api/protected', (req, res, next) => { - // console.log('Protected route middleware:', { - // session: req.session, - // authenticated: req.session.authenticated, - // address: req.session.address - // }); - - if (!req.session.authenticated) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - next(); -}); - -// Добавляем middleware для проверки прав администратора -app.use('/api/admin', (req, res, next) => { - // console.log('Admin route middleware:', { - // session: req.session, - // authenticated: req.session.authenticated, - // isAdmin: req.session.isAdmin - // }); - - if (!req.session.authenticated || !req.session.isAdmin) { - return res.status(403).json({ error: 'Forbidden' }); - } - - next(); -}); - -// Проверка структуры базы данных -async function checkDatabaseStructure() { - try { - const db = require('./db'); - - // Проверяем наличие таблицы roles - const rolesTable = await db.query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_name = 'roles' - ); - `); - - if (!rolesTable.rows[0].exists) { - console.error('Таблица roles не существует. Выполните миграцию.'); - process.exit(1); - } - - // Проверяем наличие колонки role_id в таблице users - const roleIdColumn = await db.query(` - SELECT EXISTS ( - SELECT FROM information_schema.columns - WHERE table_name = 'users' AND column_name = 'role_id' - ); - `); - - if (!roleIdColumn.rows[0].exists) { - console.error('Колонка role_id не существует в таблице users. Выполните миграцию.'); - process.exit(1); - } - - console.log('Структура базы данных проверена успешно.'); - } catch (error) { - console.error('Ошибка при проверке структуры базы данных:', error); + console.error('Error starting server:', error); process.exit(1); } -} - -// Обработка сигналов завершения -process.on('SIGINT', () => { - console.log('Получен сигнал SIGINT, завершаем работу...'); - server.close(() => { - console.log('Сервер остановлен'); - process.exit(0); - }); }); -process.on('SIGTERM', () => { - console.log('Получен сигнал SIGTERM, завершаем работу...'); - server.close(() => { - console.log('Сервер остановлен'); - process.exit(0); - }); +// Обработка ошибок +process.on('unhandledRejection', (err) => { + logger.error('Unhandled Rejection:', err); }); -// Обработка необработанных исключений -process.on('uncaughtException', (error) => { - console.error('Необработанное исключение:', error); - // Не завершаем процесс, чтобы nodemon мог перезапустить сервер +process.on('uncaughtException', (err) => { + logger.error('Uncaught Exception:', err); }); -process.on('unhandledRejection', (reason, promise) => { - console.error('Необработанное отклонение промиса:', reason); - // Не завершаем процесс, чтобы nodemon мог перезапустить сервер -}); - -// Инициализация Telegram бота -telegramService.initTelegramBot(); - -// Добавьте после других маршрутов -const chatRouter = require('./routes/chat'); -app.use('/api/chat', chatRouter); - -const cron = require('node-cron'); -const { checkAllUsersTokens } = require('./utils/access-check'); -const logger = require('./utils/logger'); - -// Настройка cron-задачи для проверки токенов каждые 30 минут -cron.schedule('*/30 * * * *', async () => { - logger.info('Running scheduled token balance check'); - await checkAllUsersTokens(); -}); - -// Периодическая очистка устаревших сессий -const cleanupInterval = 24 * 60 * 60 * 1000; // 24 часа - -setInterval(async () => { - try { - const { pool } = require('./db'); - const result = await pool.query('DELETE FROM session WHERE expire < NOW()'); - console.log(`Очищено ${result.rowCount} устаревших сессий`); - } catch (err) { - console.error('Ошибка при очистке сессий:', err); - } -}, cleanupInterval); - -// Запускаем первую очистку через 5 минут после старта сервера -setTimeout(async () => { - try { - const { pool } = require('./db'); - const result = await pool.query('DELETE FROM session WHERE expire < NOW()'); - console.log(`Первоначальная очистка: удалено ${result.rowCount} устаревших сессий`); - } catch (err) { - console.error('Ошибка при первоначальной очистке сессий:', err); - } -}, 5 * 60 * 1000); - -app.get('/session-debug', (req, res) => { - // Implementation of the endpoint -}); - -app.get('/check-sessions', async (req, res) => { - // Implementation of the endpoint -}); - -// Функция для очистки старых сессий -async function cleanupSessions() { - try { - // Удаляем сессии старше 30 дней - const result = await pool.query('DELETE FROM session WHERE expire < NOW() - INTERVAL \'30 days\''); - console.log(`Очищено ${result.rowCount} старых сессий`); - } catch (error) { - console.error('Ошибка при очистке старых сессий:', error); - } -} +module.exports = app; diff --git a/backend/services/ai-assistant.js b/backend/services/ai-assistant.js index 08686fa..cf1bb22 100644 --- a/backend/services/ai-assistant.js +++ b/backend/services/ai-assistant.js @@ -1,158 +1,109 @@ const { ChatOllama } = require('@langchain/ollama'); -const { pool } = require('../db'); +const { HNSWLib } = require('langchain/vectorstores/hnswlib'); +const { OpenAIEmbeddings } = require('langchain/embeddings/openai'); +const logger = require('../utils/logger'); -// Инициализация модели Ollama -const model = new ChatOllama({ - baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', - model: process.env.OLLAMA_MODEL || 'llama2', -}); - -/** - * Обработка сообщения пользователя и получение ответа от ИИ - * @param {number} userId - ID пользователя - * @param {string} message - Текст сообщения - * @param {string} language - Язык пользователя - * @returns {Promise} - Ответ ИИ - */ -async function processMessage(userId, message, language = 'ru') { - try { - // Получение информации о пользователе - const userInfo = await getUserInfo(userId); - - // Получение истории диалога (последние 10 сообщений) - const history = await getConversationHistory(userId); - - // Формирование контекста для ИИ - const context = ` -Пользователь: ${userInfo.username || 'Пользователь'} (ID: ${userId}) -Язык: ${language} -Роль: ${userInfo.is_admin ? 'Администратор' : 'Пользователь'} -История диалога: -${history} - -Текущее сообщение: ${message} -`; - - // Временная заглушка для ответа ИИ - // В будущем здесь будет интеграция с реальной моделью ИИ - const responses = { - ru: [ - 'Спасибо за ваше сообщение! Чем я могу помочь?', - 'Я понимаю ваш запрос. Давайте разберемся с этим вопросом.', - 'Интересный вопрос! Вот что я могу предложить...', - 'Я обработал вашу информацию. Есть ли у вас дополнительные вопросы?', - 'Я готов помочь вам с этим запросом. Нужны ли дополнительные детали?', - ], - en: [ - 'Thank you for your message! How can I help you?', - "I understand your request. Let's figure this out.", - "Interesting question! Here's what I can suggest...", - "I've processed your information. Do you have any additional questions?", - "I'm ready to help you with this request. Do you need any additional details?", - ], - }; - - const langResponses = responses[language] || responses['ru']; - const randomIndex = Math.floor(Math.random() * langResponses.length); - - // Имитация задержки ответа ИИ - await new Promise((resolve) => setTimeout(resolve, 500)); - - return langResponses[randomIndex]; - } catch (error) { - console.error('Error processing message:', error); - return 'Извините, произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз позже.'; +class AIAssistant { + constructor() { + this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434'; + this.defaultModel = process.env.OLLAMA_MODEL || 'mistral'; } -} -/** - * Получение информации о пользователе - * @param {number} userId - ID пользователя - * @returns {Promise} - Информация о пользователе - */ -async function getUserInfo(userId) { - try { - const userResult = await pool.query( - `SELECT u.id, u.username, u.address, u.is_admin, u.language, r.name as role - FROM users u - JOIN roles r ON u.role_id = r.id - WHERE u.id = $1`, - [userId] - ); + // Создание экземпляра ChatOllama с нужными параметрами + createChat(language = 'ru') { + const systemPrompt = language === 'ru' + ? 'Вы - полезный ассистент. Отвечайте на русском языке.' + : 'You are a helpful assistant. Respond in English.'; - if (userResult.rows.length === 0) { - return { id: userId }; + return new ChatOllama({ + baseUrl: this.baseUrl, + model: this.defaultModel, + system: systemPrompt + }); + } + + // Определение языка сообщения + detectLanguage(message) { + const cyrillicPattern = /[а-яА-ЯёЁ]/; + return cyrillicPattern.test(message) ? 'ru' : 'en'; + } + + // Основной метод для получения ответа + async getResponse(message, language = 'auto') { + try { + // Определяем язык, если не указан явно + const detectedLanguage = language === 'auto' + ? this.detectLanguage(message) + : language; + + const chat = this.createChat(detectedLanguage); + + try { + // Пробуем получить ответ через ChatOllama + const response = await chat.invoke(message); + return response.content; + } catch (error) { + logger.error('Error using ChatOllama:', error); + + // Пробуем альтернативный метод через прямой API + return await this.fallbackRequest(message, detectedLanguage); + } + } catch (error) { + logger.error('Error in getResponse:', error); + return "Извините, я не смог обработать ваш запрос. Пожалуйста, попробуйте позже."; } - - // Получение идентификаторов пользователя - const identitiesResult = await pool.query( - `SELECT identity_type, identity_value, verified - FROM user_identities - WHERE user_id = $1`, - [userId] - ); - - const user = userResult.rows[0]; - user.identities = identitiesResult.rows; - - return user; - } catch (error) { - console.error('Error getting user info:', error); - return { id: userId }; } -} -/** - * Получение истории диалога - * @param {number} userId - ID пользователя - * @param {number} limit - Максимальное количество сообщений - * @returns {Promise} - История диалога в текстовом формате - */ -async function getConversationHistory(userId, limit = 10) { - try { - // Получение последнего активного диалога пользователя - const conversationResult = await pool.query( - `SELECT id FROM conversations - WHERE user_id = $1 - ORDER BY updated_at DESC - LIMIT 1`, - [userId] - ); + // Альтернативный метод запроса через прямой API + async fallbackRequest(message, language) { + try { + logger.info('Using fallback request method'); + + const systemPrompt = language === 'ru' + ? 'Вы - полезный ассистент. Отвечайте на русском языке.' + : 'You are a helpful assistant. Respond in English.'; - if (conversationResult.rows.length === 0) { - return ''; + const response = await fetch(`${this.baseUrl}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: this.defaultModel, + prompt: message, + system: systemPrompt, + stream: false + }), + }); + + const data = await response.json(); + return data.response; + } catch (error) { + logger.error('Error in fallback request:', error); + throw error; } + } - const conversationId = conversationResult.rows[0].id; + // Получение списка доступных моделей + async getAvailableModels() { + try { + const response = await fetch(`${this.baseUrl}/api/tags`); + const data = await response.json(); + return data.models || []; + } catch (error) { + logger.error('Error getting available models:', error); + return []; + } + } - // Получение последних сообщений из диалога - const messagesResult = await pool.query( - `SELECT sender_type, content, created_at - FROM messages - WHERE conversation_id = $1 - ORDER BY created_at DESC - LIMIT $2`, - [conversationId, limit] - ); + // Добавляем методы из vectorStore.js + async initVectorStore() { + // ... код инициализации ... + } - // Формирование истории в текстовом формате - const history = messagesResult.rows - .reverse() - .map((msg) => { - const sender = msg.sender_type === 'user' ? 'Пользователь' : 'ИИ'; - return `${sender}: ${msg.content}`; - }) - .join('\n\n'); - - return history; - } catch (error) { - console.error('Error getting conversation history:', error); - return ''; + async findSimilarDocuments(query, k = 3) { + // ... код поиска документов ... } } -module.exports = { - processMessage, - getUserInfo, - getConversationHistory, -}; +// Создаем и экспортируем единственный экземпляр +const aiAssistant = new AIAssistant(); +module.exports = aiAssistant; diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 4f79689..7e62daf 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -1,69 +1,54 @@ const db = require('../db'); const logger = require('../utils/logger'); const { ethers } = require('ethers'); +const crypto = require('crypto'); const { processMessage } = require('./ai-assistant'); // Используем AI Assistant -// В начале файла auth-service.js -const getProvider = (network) => { - const primaryUrl = process.env[`RPC_URL_${network.toUpperCase()}`]; - const backupUrls = { - eth: 'https://eth-mainnet.public.blastapi.io', - polygon: 'https://polygon-rpc.com', - bsc: 'https://bsc-dataseed.binance.org', - arbitrum: 'https://arb1.arbitrum.io/rpc' - }; - - try { - return new ethers.JsonRpcProvider(primaryUrl); - } catch (error) { - logger.warn(`Failed to connect to primary URL for ${network}, using backup`); - return new ethers.JsonRpcProvider(backupUrls[network]); - } -}; - -const providers = { - eth: getProvider('eth'), - polygon: getProvider('polygon'), - bsc: getProvider('bsc'), - arbitrum: getProvider('arbitrum') -}; - -/** - * Сервис для работы с аутентификацией и авторизацией - */ class AuthService { - /** - * Проверяет наличие токенов на кошельке и обновляет роль - * @param {string} walletAddress - Адрес кошелька - * @returns {Promise} - Имеет ли пользователь права администратора - */ - async checkTokensAndUpdateRole(walletAddress) { + constructor() { + // Инициализация провайдеров для разных сетей + this.providers = { + eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH), + polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON), + bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC), + arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM) + }; + + // Конфигурация токенов для разных сетей + this.tokenContracts = [ + { address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" }, // Ethereum + { address: "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D", network: "bsc" }, // Binance Smart Chain + { address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" }, // Arbitrum + { address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" } // Polygon + ]; + + this.MIN_BALANCE = ethers.parseUnits("1000000.0", 18); // 1,000,000 токенов для роли админа + } + + // Проверка подписи + async verifySignature(message, signature, address) { try { - // Получаем ID пользователя по адресу кошелька - const userResult = await db.query(` - SELECT u.id FROM users u - JOIN user_identities ui ON u.id = ui.user_id - WHERE ui.identity_type = 'wallet' AND ui.identity_value = $1 - `, [walletAddress]); - - if (userResult.rows.length === 0) { - logger.warn(`User with wallet ${walletAddress} not found`); + logger.info('Verifying signature:', { + message: message.substring(0, 100) + '...', + signature: signature.substring(0, 10) + '...', + address + }); + + if (!message || !signature || !address) { + logger.error('Missing parameters for signature verification'); + return false; + } + + try { + // Восстанавливаем адрес из подписи через ethers + const recoveredAddress = ethers.verifyMessage(message, signature); + return ethers.getAddress(recoveredAddress) === ethers.getAddress(address); + } catch (error) { + logger.error('Error in signature verification:', error); return false; } - - const userId = userResult.rows[0].id; - - // Проверяем наличие токенов на кошельке - const isAdmin = await this.checkAdminTokens(walletAddress); - - // Обновляем роль в базе данных - await this.updateUserRole(userId, isAdmin ? 'admin' : 'user'); - - logger.info(`User ${userId} with address ${walletAddress}: admin=${isAdmin}`); - - return isAdmin; } catch (error) { - logger.error(`Error checking tokens: ${error.message}`); + logger.error('Error in verifySignature:', error); return false; } } @@ -75,80 +60,195 @@ class AuthService { */ async checkAdminTokens(walletAddress) { try { - const tokenContracts = [ - { address: "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", network: "eth" }, // Ethereum - { address: "0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D", network: "bsc" }, // Binance Smart Chain - { address: "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", network: "arbitrum" }, // Arbitrum - { address: "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", network: "polygon" } // Polygon - ]; - - const MIN_BALANCE = ethers.parseUnits("1.0", 18); // 1 токен - - for (const contract of tokenContracts) { + for (const contract of this.tokenContracts) { try { - const provider = providers[contract.network]; - if (!provider) { - logger.warn(`Provider not found for network: ${contract.network}`); - continue; - } - - // Проверка доступности провайдера - try { - await provider.getBlockNumber(); // Простой запрос для проверки соединения - } catch (providerError) { - logger.warn(`Provider for ${contract.network} is not responding: ${providerError.message}`); - continue; - } - - const tokenContract = new ethers.Contract(contract.address, [ - "function balanceOf(address owner) view returns (uint256)" - ], provider); + const provider = this.providers[contract.network]; + const tokenContract = new ethers.Contract( + contract.address, + ['function balanceOf(address) view returns (uint256)'], + provider + ); const balance = await tokenContract.balanceOf(walletAddress); - logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance.toString()}`); - - if (balance >= MIN_BALANCE) { + logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance}`); + + if (balance >= this.MIN_BALANCE) { logger.info(`Admin token found on ${contract.network} for ${walletAddress}`); - return true; // Если найден хотя бы один токен, возвращаем true + return true; } } catch (error) { - logger.error(`Error checking balance on ${contract.network}: ${error.message}`); + logger.error(`Error checking balance on ${contract.network}:`, error); } } logger.info(`No admin tokens found for ${walletAddress}`); - return false; // Если не найдено ни одного токена, возвращаем false + return false; } catch (error) { - logger.error(`Error in checkAdminTokens: ${error.message}`); + logger.error('Error in checkAdminTokens:', error); return false; } } /** - * Обновляет роль пользователя в базе данных - * @param {number} userId - ID пользователя - * @param {string} role - Новая роль ('admin' или 'user') - * @returns {Promise} - Успешно ли обновлена роль + * Проверяет баланс токенов и обновляет роль пользователя + * @param {string} address - Адрес кошелька + * @returns {Promise} - Является ли пользователь админом */ - async updateUserRole(userId, role) { + async checkTokensAndUpdateRole(address) { try { - // Получаем ID роли - const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', [role]); + const isAdmin = await this.checkAdminTokens(address); - if (roleResult.rows.length === 0) { - logger.error(`Role ${role} not found`); - return false; + // Обновляем роль в базе данных + await this.updateUserRole(address, isAdmin); + + logger.info(`Updated role for user with address ${address}: admin=${isAdmin}`); + return isAdmin; + } catch (error) { + logger.error('Error in checkTokensAndUpdateRole:', error); + return false; + } + } + + /** + * Находит или создает пользователя по адресу кошелька + * @param {string} address - Адрес кошелька + * @returns {Promise<{userId: number, isAdmin: boolean}>} + */ + async findOrCreateUser(address) { + try { + const existingUser = await db.query( + `SELECT u.id, + (u.role = 'admin') as is_admin + FROM users u + JOIN user_identities ui ON u.id = ui.user_id + WHERE ui.provider = 'wallet' + AND LOWER(ui.provider_id) = LOWER($1)`, + [address] + ); + + if (existingUser.rows.length > 0) { + return existingUser.rows[0]; } - - const roleId = roleResult.rows[0].id; - - // Обновляем роль пользователя - await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleId, userId]); - - logger.info(`Updated role for user ${userId} to ${role}`); + + // Если пользователь не найден, создаем нового + const result = await db.query( + 'INSERT INTO users DEFAULT VALUES RETURNING id', + [] + ); + const userId = result.rows[0].id; + + // Добавляем wallet identity + await db.query( + `INSERT INTO user_identities + (user_id, provider, provider_id, identity_type, identity_value) + VALUES ($1, 'wallet', $2, 'wallet', $2)`, + [userId, address.toLowerCase()] + ); + + // Проверяем роль админа + const isAdmin = await this.checkAdminRole(userId); + + return { + userId, + isAdmin + }; + } catch (error) { + console.error('Error in findOrCreateUser:', error); + throw error; + } + } + + /** + * Обновляет роль пользователя и связанные данные + */ + async updateUserRole(address, isAdmin) { + try { + const result = await db.query(` + UPDATE users u + SET + role = $2::user_role + FROM user_identities ui + WHERE u.id = ui.user_id + AND ui.provider = 'wallet' + AND LOWER(ui.provider_id) = LOWER($1) + RETURNING u.id + `, [ + address, + isAdmin ? 'admin' : 'user' + ]); + + if (result.rows.length > 0) { + logger.info(`Updated role for user ${result.rows[0].id} to ${isAdmin ? 'admin' : 'user'}`); + } + } catch (error) { + logger.error('Error updating user role:', error); + } + } + + // Связывание идентификаторов (из identity-linker.js) + async linkIdentity(userId, type, value) { + try { + // Проверяем, не связан ли идентификатор с другим пользователем + const existingResult = await db.query( + `SELECT user_id + FROM user_identities + WHERE identity_type = $1 + AND LOWER(identity_value) = LOWER($2)`, + [type, value] + ); + + if (existingResult.rows.length > 0 && existingResult.rows[0].user_id !== userId) { + throw new Error('Identity already linked to another user'); + } + + // Добавляем или обновляем идентификатор + await db.query( + `INSERT INTO user_identities + (user_id, identity_type, identity_value, verified, created_at) + VALUES ($1, $2, $3, true, NOW()) + ON CONFLICT (identity_type, identity_value) + DO UPDATE SET user_id = $1, verified = true`, + [userId, type, value] + ); + + // Если это кошелек, проверяем права админа + if (type === 'wallet') { + await this.checkTokensAndUpdateRole(value); + } + return true; } catch (error) { - logger.error(`Error updating user role: ${error.message}`); + logger.error('Error linking identity:', error); + throw error; + } + } + + // Получение всех идентификаторов пользователя + async getUserIdentities(userId) { + try { + const result = await db.query( + `SELECT identity_type, identity_value, verified, created_at + FROM user_identities + WHERE user_id = $1`, + [userId] + ); + return result.rows; + } catch (error) { + logger.error('Error getting user identities:', error); + return []; + } + } + + // Проверка роли админа + async isAdmin(userId) { + try { + const result = await db.query( + 'SELECT is_admin FROM users WHERE id = $1', + [userId] + ); + return result.rows.length > 0 && result.rows[0].is_admin; + } catch (error) { + logger.error('Error checking admin status:', error); return false; } } @@ -208,46 +308,6 @@ class AuthService { } } - /** - * Получает все идентификаторы пользователя - * @param {number} userId - ID пользователя - * @returns {Promise} - Список идентификаторов - */ - async getAllUserIdentities(userId) { - try { - const result = await db.query(` - SELECT identity_type, identity_value, verified, created_at - FROM user_identities - WHERE user_id = $1 - `, [userId]); - - return result.rows; - } catch (error) { - logger.error(`Error getting user identities: ${error.message}`); - return []; - } - } - - /** - * Проверяет, является ли пользователь администратором - * @param {number} userId - ID пользователя - * @returns {Promise} - Является ли пользователь администратором - */ - async isAdmin(userId) { - try { - const result = await db.query('SELECT is_admin FROM users WHERE id = $1', [userId]); - - if (result.rows.length === 0) { - return false; - } - - return result.rows[0].is_admin; - } catch (error) { - logger.error(`Error checking admin status: ${error.message}`); - return false; - } - } - /** * Обрабатывает гостевые сообщения после аутентификации */ @@ -349,6 +409,95 @@ class AuthService { return false; } } + + async createSession(req, userData) { + // Сохраняем существующие данные сессии + const existingData = { ...req.session }; + + req.session.userId = userData.userId; + req.session.address = userData.address; + req.session.isAdmin = userData.isAdmin; + req.session.authenticated = true; + req.session.authType = userData.authType; + + // Если есть гостевые сообщения в существующей сессии + if (existingData.guestId) { + req.session.guestId = existingData.guestId; + // Связываем сообщения сразу здесь + await this.linkGuestMessages(req, { + userId: userData.userId, + guestId: existingData.guestId + }); + } + + return new Promise((resolve, reject) => { + req.session.save((err) => { + if (err) reject(err); + else resolve(); + }); + }); + } + + async linkGuestMessages(req, userData) { + if (!userData.guestId) return; + + const { rows } = await db.query( + 'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)', + [userData.guestId] + ); + + if (rows[0].exists) { + // Сначала связываем сообщения + await db.query('SELECT link_guest_messages($1, $2)', + [userData.userId, userData.guestId] + ); + // Только после успешного связывания удаляем guestId + delete req.session.guestId; + } + } + + async checkAdminRole(userId) { + try { + // Получаем все идентификаторы пользователя + const identities = await db.query( + `SELECT provider, provider_id + FROM user_identities + WHERE user_id = $1`, + [userId] + ); + + // Ищем wallet среди идентификаторов + const wallet = identities.rows.find(i => i.provider === 'wallet'); + if (!wallet) return false; + + // Проверяем баланс токенов + const hasTokens = await this.checkAdminTokens(wallet.provider_id); + if (!hasTokens) return false; + + // Обновляем роль пользователя + await db.query( + `UPDATE users SET role = 'admin' WHERE id = $1`, + [userId] + ); + + return true; + } catch (error) { + console.error('Error checking admin role:', error); + return false; + } + } + + // Проверка при каждой аутентификации + async verifyIdentity(type, value) { + const userId = await this.getUserIdByIdentity(type, value); + if (!userId) return false; + + // Проверяем роль только если есть связанный кошелек + await this.checkAdminRole(userId); + return true; + } } -module.exports = new AuthService(); \ No newline at end of file +// Создаем и экспортируем единственный экземпляр +const authService = new AuthService(); +module.exports = authService; \ No newline at end of file diff --git a/backend/services/ollama.js b/backend/services/ollama.js deleted file mode 100644 index 5cc091e..0000000 --- a/backend/services/ollama.js +++ /dev/null @@ -1,160 +0,0 @@ -const { ChatOllama } = require('@langchain/ollama'); -const { RetrievalQAChain } = require('langchain/chains'); -const { PromptTemplate } = require('@langchain/core/prompts'); -const axios = require('axios'); -const { Ollama } = require('ollama'); -const { HumanMessage } = require('@langchain/core/messages'); - -// Создаем шаблон для контекстного запроса -const PROMPT_TEMPLATE = ` -Ты - AI-ассистент для бизнеса, специализирующийся на блокчейн-технологиях и Web3. -Используй следующий контекст для ответа на вопрос пользователя. -Если ты не знаешь ответа, просто скажи, что не знаешь, не пытайся придумать ответ. - -Контекст: {context} - -Вопрос: {query} - -Ответ: -`; - -// Функция для проверки доступности Ollama -async function checkOllamaAvailability() { - console.log('Проверка доступности Ollama...'); - - try { - // Добавляем таймаут для запроса - const response = await axios.get('http://localhost:11434/api/tags', { - timeout: 5000, // 5 секунд таймаут - }); - - if (response.status === 200) { - console.log('Ollama доступен. Доступные модели:'); - if (response.data && response.data.models) { - response.data.models.forEach((model) => { - console.log(`- ${model.name}`); - }); - } - return true; - } - } catch (error) { - console.error('Ollama недоступен:', error.message); - console.log('Приложение продолжит работу без Ollama'); - return false; - } -} - -// Функция для прямого запроса к Ollama -async function directOllamaQuery(message, language = 'en') { - try { - // Всегда используем модель mistral, независимо от языка - const modelName = 'mistral'; - - console.log(`Отправка запроса к Ollama (модель: ${modelName}, язык: ${language}): ${message}`); - - // Проверяем доступность Ollama - console.log('Проверка доступности Ollama...'); - const ollama = new Ollama(); - - try { - const models = await ollama.list(); - console.log('Ollama доступен. Доступные модели:'); - models.models.forEach((model) => { - console.log(`- ${model.name}`); - }); - } catch (error) { - console.error('Ошибка при проверке доступности Ollama:', error); - throw new Error('Ollama недоступен'); - } - - console.log('Отправка запроса к Ollama...'); - - const chatModel = new ChatOllama({ - baseUrl: 'http://localhost:11434', - model: modelName, - temperature: 0.7, - }); - - const response = await chatModel.invoke([new HumanMessage(message)]); - - return response.content; - } catch (error) { - console.error('Ошибка при запросе к Ollama:', error); - - // Возвращаем сообщение об ошибке - return 'Извините, произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.'; - } -} - -// Функция для создания цепочки Ollama с RAG -async function createOllamaChain(vectorStore) { - try { - console.log('Создаем модель Ollama...'); - // Создаем модель Ollama - const model = new ChatOllama({ - baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', - model: process.env.OLLAMA_MODEL || 'mistral', - temperature: 0.2, - timeout: 60000, // 60 секунд таймаут - }); - console.log('Модель Ollama создана'); - - // Проверяем модель прямым запросом - try { - console.log('Тестируем модель прямым запросом...'); - const testResponse = await model.invoke('Тестовый запрос'); - console.log('Тест модели успешен:', testResponse); - } catch (testError) { - console.error('Ошибка при тестировании модели:', testError); - // Продолжаем выполнение, даже если тест не прошел - } - - console.log('Создаем шаблон запроса...'); - // Создаем шаблон запроса - const prompt = new PromptTemplate({ - template: PROMPT_TEMPLATE, - inputVariables: ['context', 'query'], - }); - console.log('Шаблон запроса создан'); - - console.log('Получаем retriever из векторного хранилища...'); - const retriever = vectorStore.asRetriever(); - console.log('Retriever получен'); - - console.log('Создаем цепочку для поиска и ответа...'); - // Создаем цепочку для поиска и ответа - const chain = RetrievalQAChain.fromLLM(model, retriever, { - returnSourceDocuments: true, - prompt: prompt, - inputKey: 'query', - outputKey: 'text', - verbose: true, - }); - console.log('Цепочка для поиска и ответа создана'); - - return chain; - } catch (error) { - console.error('Error creating Ollama chain:', error); - throw error; - } -} - -// Функция для получения модели Ollama -async function getOllamaModel() { - try { - // Создаем модель Ollama - const model = new ChatOllama({ - baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', - model: process.env.OLLAMA_MODEL || 'mistral', - temperature: 0.2, - timeout: 60000, // 60 секунд таймаут - }); - - return model; - } catch (error) { - console.error('Error creating Ollama model:', error); - throw error; - } -} - -module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery }; diff --git a/backend/services/telegramBot.js b/backend/services/telegramBot.js index e39fe88..c38ddc9 100644 --- a/backend/services/telegramBot.js +++ b/backend/services/telegramBot.js @@ -1,218 +1,143 @@ const TelegramBot = require('node-telegram-bot-api'); const logger = require('../utils/logger'); -const { pool } = require('../db'); -const crypto = require('crypto'); -// Создаем бота -const token = process.env.TELEGRAM_BOT_TOKEN; -let bot = null; - -/** - * Функция для отправки кода подтверждения - */ -async function sendVerificationCode(chatId) { - try { - // Генерируем код и токен - const code = Math.floor(100000 + Math.random() * 900000).toString(); - const authToken = crypto.randomBytes(32).toString('hex'); - - // Создаем пользователя и сохраняем код в базу данных - const result = await pool.query( - `WITH new_user AS ( - INSERT INTO users (created_at) - VALUES (NOW()) - RETURNING id - ) - INSERT INTO telegram_auth_tokens - (user_id, token, verification_code, telegram_id, expires_at) - VALUES ( - (SELECT id FROM new_user), - $1, $2, $3, - NOW() + INTERVAL '5 minutes' - ) - RETURNING user_id`, - [authToken, code, chatId.toString()] - ); - - // Отправляем код с инлайн-кнопкой - const sentMessage = await bot.sendMessage(chatId, - 'Привет! Я бот для аутентификации в DApp for Business.\n\n' + - '🔐 Ваш код подтверждения:\n\n' + - `${code}\n\n` + - 'Введите этот код на сайте для завершения авторизации.\n' + - 'Код действителен в течение 5 минут.', - { - parse_mode: 'HTML', - reply_markup: { - inline_keyboard: [ - [{ text: '🔄 Получить новый код', callback_data: 'new_code' }] - ] - } - } - ); - - // Удаляем сообщение через 30 секунд - setTimeout(async () => { - try { - await bot.deleteMessage(chatId, sentMessage.message_id); - await bot.sendMessage(chatId, - 'Для получения нового кода используйте команду /start или меню команд', - { - reply_markup: { - keyboard: [ - [{ text: '/start' }] - ], - resize_keyboard: true, - persistent: true - } - } - ); - } catch (error) { - console.error('Error deleting message:', error); - } - }, 30000); - - return { code, token: authToken, userId: result.rows[0].user_id }; - } catch (error) { - console.error('Error sending verification code:', error); - throw error; - } -} - -/** - * Функция для проверки кода - */ -async function verifyCode(code) { - try { - const result = await pool.query( - `SELECT token, telegram_id, user_id - FROM telegram_auth_tokens - WHERE verification_code = $1 - AND expires_at > NOW() - AND NOT used`, - [code] - ); - - if (result.rows.length === 0) { - return { success: false, error: 'Неверный или истекший код' }; - } - - const { token, telegram_id, user_id } = result.rows[0]; - - // Помечаем токен как использованный - await pool.query( - 'UPDATE telegram_auth_tokens SET used = true WHERE token = $1', - [token] - ); - - // Добавляем Telegram ID в таблицу идентификаторов - await pool.query( - `INSERT INTO user_identities - (user_id, identity_type, identity_value, verified, created_at) - VALUES ($1, 'telegram', $2, true, NOW()) - ON CONFLICT (identity_type, identity_value) - DO UPDATE SET verified = true`, - [user_id, telegram_id] - ); - - return { - success: true, - telegramId: telegram_id, - userId: user_id - }; - } catch (error) { - console.error('Error verifying code:', error); - throw error; - } -} - -/** - * Инициализация Telegram бота - */ -function initTelegramBot() { - if (!token) { - console.warn('TELEGRAM_BOT_TOKEN not set'); - return null; - } - - try { - // Создаем бота с опцией обработки ошибок - bot = new TelegramBot(token, { - polling: { - autoStart: true, - params: { - timeout: 10 - } - }, +class TelegramBotService { + constructor(token) { + this.bot = new TelegramBot(token, { + polling: true, request: { - timeout: 30000, // увеличиваем таймаут до 30 секунд - proxy: process.env.HTTPS_PROXY // используем прокси если есть + timeout: 30000 // 30 секунд таймаут } }); + this.verificationCodes = new Map(); + this.setupHandlers(); + + logger.info('TelegramBot service initialized'); + } - console.log('Telegram bot initialized'); - - // Очищаем все предыдущие обработчики - bot.removeAllListeners(); - - // Устанавливаем команды бота с обработкой ошибок - bot.setMyCommands([ - { command: '/start', description: 'Получить код подтверждения' }, - { command: '/help', description: 'Показать справку' } - ]).catch(error => { - console.warn('Error setting bot commands:', error); - // Продолжаем работу даже если не удалось установить команды + setupHandlers() { + this.bot.on('message', this.handleMessage.bind(this)); + this.bot.on('callback_query', this.handleCallbackQuery.bind(this)); + + // Обработка ошибок + this.bot.on('polling_error', (error) => { + logger.error('Telegram polling error:', error); }); + + this.bot.on('error', (error) => { + logger.error('Telegram bot error:', error); + }); + } - // Обработчик команды /start - bot.onText(/\/start/, async (msg) => { + async handleMessage(msg) { + try { const chatId = msg.chat.id; - try { - await sendVerificationCode(chatId); - } catch (error) { - console.error('Error handling /start:', error); - await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.') - .catch(err => console.error('Error sending error message:', err)); + const text = msg.text; + + logger.info(`Received message from ${chatId}: ${text}`); + + if (text.startsWith('/start')) { + await this.handleStart(msg); + } else if (this.verificationCodes.has(chatId)) { + await this.handleVerificationCode(msg); } - }); + } catch (error) { + logger.error('Error handling message:', error); + } + } - // Обработчик ошибок polling - bot.on('polling_error', (error) => { - console.error('Telegram bot polling error:', error); - // Перезапускаем polling при ошибке - setTimeout(() => { - try { - bot.startPolling(); - } catch (e) { - console.error('Error restarting polling:', e); + async handleCallbackQuery(query) { + try { + const chatId = query.message.chat.id; + await this.bot.answerCallbackQuery(query.id); + + logger.info(`Handled callback query from ${chatId}`); + } catch (error) { + logger.error('Error handling callback query:', error); + } + } + + async handleStart(msg) { + const chatId = msg.chat.id; + try { + await this.bot.sendMessage( + chatId, + 'Добро пожаловать! Используйте этого бота для аутентификации в приложении.' + ); + logger.info(`Sent welcome message to ${chatId}`); + } catch (error) { + logger.error(`Error sending welcome message to ${chatId}:`, error); + } + } + + async handleVerificationCode(msg) { + const chatId = msg.chat.id; + const code = msg.text.trim(); + + try { + const verificationData = this.verificationCodes.get(chatId); + + if (!verificationData) { + await this.bot.sendMessage(chatId, 'Нет активного кода подтверждения.'); + return; + } + + if (Date.now() > verificationData.expires) { + this.verificationCodes.delete(chatId); + await this.bot.sendMessage(chatId, 'Код подтверждения истек. Запросите новый.'); + return; + } + + if (verificationData.code === code) { + await this.bot.sendMessage(chatId, 'Код подтвержден успешно!'); + this.verificationCodes.delete(chatId); + } else { + await this.bot.sendMessage(chatId, 'Неверный код. Попробуйте еще раз.'); + } + } catch (error) { + logger.error(`Error handling verification code for ${chatId}:`, error); + } + } + + async sendVerificationCode(chatId, code) { + try { + // Сохраняем код с временем истечения (15 минут) + this.verificationCodes.set(chatId, { + code, + expires: Date.now() + 15 * 60 * 1000 + }); + + await this.bot.sendMessage( + chatId, + `Ваш код подтверждения: ${code}\nВведите его в приложении.` + ); + + logger.info(`Sent verification code to ${chatId}`); + return true; + } catch (error) { + logger.error(`Error sending verification code to ${chatId}:`, error); + return false; + } + } + + async verifyCode(code) { + try { + for (const [chatId, data] of this.verificationCodes.entries()) { + if (data.code === code) { + if (Date.now() > data.expires) { + this.verificationCodes.delete(chatId); + return { success: false, error: 'Код истек' }; + } + this.verificationCodes.delete(chatId); + return { success: true, telegramId: chatId.toString() }; } - }, 10000); // пробуем перезапустить через 10 секунд - }); - - // Обработчик остановки polling - bot.on('stop', () => { - console.log('Bot polling stopped'); - // Пробуем перезапустить - setTimeout(() => { - try { - bot.startPolling(); - } catch (e) { - console.error('Error restarting polling after stop:', e); - } - }, 5000); - }); - - return bot; - - } catch (error) { - console.error('Error initializing Telegram bot:', error); - return null; + } + return { success: false, error: 'Неверный код' }; + } catch (error) { + logger.error('Error verifying code:', error); + return { success: false, error: 'Внутренняя ошибка' }; + } } } -// Экспортируем функции -module.exports = { - initTelegramBot, - verifyCode, - sendVerificationCode -}; \ No newline at end of file +module.exports = TelegramBotService; \ No newline at end of file diff --git a/backend/services/vectorStore.js b/backend/services/vectorStore.js deleted file mode 100644 index b10859a..0000000 --- a/backend/services/vectorStore.js +++ /dev/null @@ -1,212 +0,0 @@ -const { HNSWLib } = require('langchain/vectorstores/hnswlib'); -const { OllamaEmbeddings } = require('langchain/embeddings/ollama'); -const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter'); -const { DirectoryLoader } = require('langchain/document_loaders/fs/directory'); -const { TextLoader } = require('langchain/document_loaders/fs/text'); -const { PDFLoader } = require('langchain/document_loaders/fs/pdf'); -const fs = require('fs'); -const path = require('path'); - -// Путь к директории для хранения векторной базы данных -const VECTOR_STORE_PATH = path.join(__dirname, '../data/vector_store'); - -// Инициализация embeddings с использованием локальной модели Ollama -const embeddings = new OllamaEmbeddings({ - model: process.env.OLLAMA_EMBEDDINGS_MODEL || 'mistral', - baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', -}); - -let vectorStore = null; - -/** - * Инициализация векторного хранилища - */ -async function initializeVectorStore() { - try { - // Создание директории, если она не существует - if (!fs.existsSync(VECTOR_STORE_PATH)) { - fs.mkdirSync(VECTOR_STORE_PATH, { recursive: true }); - console.log(`Created vector store directory at ${VECTOR_STORE_PATH}`); - } - - // Проверка наличия файлов индекса - const indexFiles = fs.readdirSync(VECTOR_STORE_PATH); - - if (indexFiles.length > 0 && indexFiles.includes('hnswlib.index')) { - // Загрузка существующего индекса - console.log('Loading existing vector store...'); - try { - vectorStore = await HNSWLib.load(VECTOR_STORE_PATH, embeddings); - console.log('Vector store loaded successfully'); - } catch (loadError) { - console.error('Error loading existing vector store:', loadError); - console.log('Creating new vector store...'); - await createVectorStore(); - } - } else { - // Создание нового индекса - console.log('Creating new vector store...'); - await createVectorStore(); - } - - return vectorStore; - } catch (error) { - console.error('Error initializing vector store:', error); - // Создаем пустой векторный индекс в случае ошибки - vectorStore = new HNSWLib(embeddings, { - space: 'cosine', - numDimensions: 4096, // Размерность для Ollama embeddings (зависит от модели) - }); - await vectorStore.save(VECTOR_STORE_PATH); - return vectorStore; - } -} - -/** - * Создание нового векторного хранилища из документов - */ -async function createVectorStore() { - try { - // Проверяем наличие директории documents - const docsPath = path.join(__dirname, '../data/documents'); - - // Если директория documents не существует, проверяем директорию docs - if (!fs.existsSync(docsPath)) { - const altDocsPath = path.join(__dirname, '../data/docs'); - - // Если директория docs существует, используем ее - if (fs.existsSync(altDocsPath)) { - console.log(`Using documents directory at ${altDocsPath}`); - return await processDocumentsDirectory(altDocsPath); - } - - // Иначе создаем директорию documents - fs.mkdirSync(docsPath, { recursive: true }); - console.log(`Created documents directory at ${docsPath}`); - - // Создание примера документа - const sampleDocPath = path.join(docsPath, 'sample.txt'); - fs.writeFileSync(sampleDocPath, 'Это пример документа для векторного хранилища.'); - } - - return await processDocumentsDirectory(docsPath); - } catch (error) { - console.error('Error creating vector store:', error); - throw error; - } -} - -/** - * Обработка директории с документами - * @param {string} docsPath - Путь к директории с документами - */ -async function processDocumentsDirectory(docsPath) { - try { - // Загрузка документов - const loader = new DirectoryLoader(docsPath, { - '.txt': (path) => new TextLoader(path), - '.pdf': (path) => new PDFLoader(path), - }); - - const docs = await loader.load(); - console.log(`Loaded ${docs.length} documents`); - - if (docs.length === 0) { - // Создаем пустой векторный индекс, если нет документов - vectorStore = new HNSWLib(embeddings, { - space: 'cosine', - numDimensions: 4096, // Размерность для Ollama embeddings (зависит от модели) - }); - } else { - // Разделение документов на чанки - const textSplitter = new RecursiveCharacterTextSplitter({ - chunkSize: 1000, - chunkOverlap: 200, - }); - - const splitDocs = await textSplitter.splitDocuments(docs); - console.log(`Split into ${splitDocs.length} chunks`); - - // Создание векторного хранилища - vectorStore = await HNSWLib.fromDocuments(splitDocs, embeddings); - } - - // Сохранение векторного хранилища - await vectorStore.save(VECTOR_STORE_PATH); - console.log('Vector store created and saved successfully'); - - return vectorStore; - } catch (error) { - console.error('Error processing documents directory:', error); - throw error; - } -} - -/** - * Получение векторного хранилища - * @returns {HNSWLib|null} Векторное хранилище - */ -function getVectorStore() { - return vectorStore; -} - -/** - * Поиск похожих документов - * @param {string} query - Запрос для поиска - * @param {number} k - Количество результатов - * @returns {Promise} - Массив похожих документов - */ -async function similaritySearch(query, k = 5) { - if (!vectorStore) { - await initializeVectorStore(); - } - - try { - const results = await vectorStore.similaritySearch(query, k); - return results; - } catch (error) { - console.error('Error performing similarity search:', error); - return []; - } -} - -/** - * Добавление нового документа в векторное хранилище - * @param {string} text - Текст документа - * @param {Object} metadata - Метаданные документа - * @returns {Promise} - Успешность добавления - */ -async function addDocument(text, metadata = {}) { - if (!vectorStore) { - await initializeVectorStore(); - } - - try { - // Разделение документа на чанки - const textSplitter = new RecursiveCharacterTextSplitter({ - chunkSize: 1000, - chunkOverlap: 200, - }); - - const docs = await textSplitter.createDocuments([text], [metadata]); - - // Добавление документов в векторное хранилище - await vectorStore.addDocuments(docs); - - // Сохранение обновленного векторного хранилища - await vectorStore.save(VECTOR_STORE_PATH); - - console.log('Document added to vector store successfully'); - return true; - } catch (error) { - console.error('Error adding document to vector store:', error); - return false; - } -} - -module.exports = { - initializeVectorStore, - getVectorStore, - similaritySearch, - addDocument, -}; diff --git a/backend/utils/access-check.js b/backend/utils/access-check.js deleted file mode 100644 index f0224ec..0000000 --- a/backend/utils/access-check.js +++ /dev/null @@ -1,39 +0,0 @@ -const db = require('../db'); -const logger = require('../utils/logger'); -const authService = require('../services/auth-service'); - -/** - * Проверяет токены всех пользователей и обновляет их роли - * @returns {Promise} - */ -async function checkAllUsersTokens() { - try { - // Получаем всех пользователей с кошельками - const walletUsers = await db.query(` - SELECT u.id, ui.identity_value as address - FROM users u - JOIN user_identities ui ON u.id = ui.user_id - WHERE ui.identity_type = 'wallet' - `); - - logger.info(`Checking tokens for ${walletUsers.rows.length} users`); - - for (const user of walletUsers.rows) { - try { - // Используем существующий метод для проверки токенов и обновления роли - const isAdmin = await authService.checkTokensAndUpdateRole(user.address); - logger.info(`Updated user ${user.id} with address ${user.address}: admin=${isAdmin}`); - } catch (error) { - logger.error(`Error checking tokens for user ${user.id}: ${error.message}`); - } - } - - logger.info('Token check completed'); - } catch (error) { - logger.error(`Error checking all users tokens: ${error.message}`); - } -} - -module.exports = { - checkAllUsersTokens -}; \ No newline at end of file diff --git a/backend/utils/auth.js b/backend/utils/auth.js deleted file mode 100644 index b3e8b83..0000000 --- a/backend/utils/auth.js +++ /dev/null @@ -1,146 +0,0 @@ -const { ethers } = require('ethers'); -const db = require('../db'); -const logger = require('./logger'); -const authService = require('../services/auth-service'); -const { USER_ROLES, IDENTITY_TYPES } = require('./constants'); - -// Инициализация провайдера -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL_ETH); - -/** - * Проверяет подпись сообщения - * @param {string} nonce - Nonce для проверки - * @param {string} signature - Подпись - * @param {string} address - Адрес кошелька - * @returns {Promise} - Результат проверки - */ -async function verifySignature(nonce, signature, address) { - try { - // Создаем сообщение для проверки - const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`; - - // Восстанавливаем адрес из подписи - const recoveredAddress = ethers.verifyMessage(message, signature); - - // Сравниваем адреса (приводим к нижнему регистру для надежности) - return recoveredAddress.toLowerCase() === address.toLowerCase(); - } catch (error) { - console.error('Error verifying signature:', error); - return false; - } -} - -/** - * Проверяет, является ли пользователь администратором - * @param {string} address - Адрес кошелька - * @returns {Promise} - Является ли пользователь администратором - */ -async function checkUserRole(address) { - try { - // Проверяем наличие токенов администратора - const isAdmin = await authService.checkAdminTokens(address); - return isAdmin; - } catch (error) { - console.error('Error checking user role:', error); - return false; - } -} - -/** - * Проверяет доступ пользователя - * @param {string} walletAddress - Адрес кошелька - * @returns {Promise} - Информация о доступе - */ -async function checkAccess(walletAddress) { - try { - // Проверяем наличие токенов администратора - const isAdmin = await authService.checkAdminTokens(walletAddress); - - // Получаем или создаем пользователя - const userId = await findOrCreateUser(walletAddress); - - return { - userId, - isAdmin, - hasAccess: true - }; - } catch (error) { - logger.error(`Error checking access: ${error.message}`); - return { - hasAccess: false, - error: error.message - }; - } -} - -/** - * Находит или создает пользователя по адресу кошелька - * @param {string} address - Адрес кошелька - * @returns {Promise} - ID пользователя и роль - */ -async function findOrCreateUser(address) { - try { - if (!address) { - throw new Error('Address is required'); - } - - const normalizedAddress = address.toLowerCase(); - - // Сначала проверяем в таблице users - const userResult = await db.query( - 'SELECT id FROM users WHERE LOWER(address) = $1', - [normalizedAddress] - ); - - let userId; - let isAdmin = false; - - if (userResult.rows.length === 0) { - // Если пользователь не найден, создаем его - const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', ['user']); - if (roleResult.rows.length === 0) { - throw new Error('Role "user" not found'); - } - const roleId = roleResult.rows[0].id; - - // Создаем пользователя - const newUserResult = await db.query( - 'INSERT INTO users (address, role_id, created_at) VALUES ($1, $2, NOW()) RETURNING id', - [normalizedAddress, roleId] - ); - - userId = newUserResult.rows[0].id; - - // Добавляем идентификатор кошелька - await db.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [userId, 'wallet', normalizedAddress] - ); - } else { - userId = userResult.rows[0].id; - } - - // Проверяем, является ли пользователь администратором - isAdmin = await checkUserRole(normalizedAddress); - - // Обновляем роль пользователя - const roleNameToSet = isAdmin ? 'admin' : 'user'; - const roleToSetResult = await db.query('SELECT id FROM roles WHERE name = $1', [roleNameToSet]); - if (roleToSetResult.rows.length > 0) { - const roleIdToSet = roleToSetResult.rows[0].id; - await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleIdToSet, userId]); - } - - return { userId, isAdmin }; - } catch (error) { - console.error('Error finding or creating user:', error); - throw error; - } -} - -module.exports = { - verifySignature, - checkAccess, - findOrCreateUser, - checkUserRole -}; \ No newline at end of file diff --git a/backend/utils/contracts.js b/backend/utils/contracts.js deleted file mode 100644 index 36ab2d0..0000000 --- a/backend/utils/contracts.js +++ /dev/null @@ -1,105 +0,0 @@ -const { ethers } = require('ethers'); -const fs = require('fs'); -const path = require('path'); -const logger = require('./logger'); - -// Инициализация провайдера -const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); - -// Путь к директории с ABI контрактов -const contractsDir = path.join(__dirname, '../artifacts/contracts/AccessToken.sol'); - -// Получение ABI контракта -const accessTokenJSON = require('../artifacts/contracts/AccessToken.sol/AccessToken.json'); -const accessTokenABI = accessTokenJSON.abi; - -// Проверка, что ABI является массивом -if (!Array.isArray(accessTokenABI)) { - console.error('ABI is not an array:', accessTokenABI); - // Если ABI не является массивом, создайте массив вручную - const manualABI = [ - "function mintAccessToken(address to, uint8 role) public", - "function checkRole(address user) public view returns (uint8)", - "function revokeToken(uint256 tokenId) public", - // Добавьте другие функции, которые вам нужны - ]; - - // Создание экземпляра контракта с ручным ABI - const contractAddress = process.env.ACCESS_TOKEN_ADDRESS; - const accessTokenContract = new ethers.Contract(contractAddress, manualABI, provider); - - module.exports = { - accessTokenContract, - getContract, - provider - }; -} else { - // Если ABI является массивом, используйте его - const contractAddress = process.env.ACCESS_TOKEN_ADDRESS; - const accessTokenContract = new ethers.Contract(contractAddress, accessTokenABI, provider); - - module.exports = { - accessTokenContract, - getContract, - provider - }; -} - -// Кэш для хранения экземпляров контрактов -const contractsCache = {}; - -/** - * Получает экземпляр контракта по его имени - * @param {string} contractName - Имя контракта - * @returns {Promise} - Экземпляр контракта - */ -async function getContract(contractName) { - try { - console.log(`Getting contract: ${contractName}`); - - // Проверяем, есть ли контракт в кэше - if (contractsCache[contractName]) { - console.log(`Using cached contract: ${contractName}`); - return contractsCache[contractName]; - } - - // Получаем адрес контракта из переменных окружения - const contractAddress = process.env.ACCESS_TOKEN_ADDRESS; // или ACCESS_TOKEN_CONTRACT_ADDRESS - - if (!contractAddress) { - throw new Error(`Contract address for ${contractName} not found in environment variables`); - } - - // Путь к файлу с ABI контракта - const abiPath = path.join(contractsDir, `${contractName}.json`); - - // Проверяем, существует ли файл с ABI - if (!fs.existsSync(abiPath)) { - throw new Error(`ABI file for ${contractName} not found at ${abiPath}`); - } - - // Читаем ABI из файла - const abiJson = fs.readFileSync(abiPath, 'utf8'); - const contractJSON = JSON.parse(abiJson); - const abi = contractJSON.abi; // Получаем ABI из свойства abi - - console.log(`ABI for ${contractName}:`, abi); - - // Проверяем, что ABI является массивом - if (!Array.isArray(abi)) { - console.error(`ABI for ${contractName} is not an array:`, abi); - throw new Error(`ABI for ${contractName} is not an array`); - } - - // Создаем экземпляр контракта - const contract = new ethers.Contract(contractAddress, abi, provider); - - // Сохраняем контракт в кэше - contractsCache[contractName] = contract; - - return contract; - } catch (error) { - logger.error(`Ошибка при получении контракта ${contractName}: ${error.message}`); - throw error; - } -} diff --git a/backend/utils/identity-linker.js b/backend/utils/identity-linker.js deleted file mode 100644 index 4669e68..0000000 --- a/backend/utils/identity-linker.js +++ /dev/null @@ -1,90 +0,0 @@ -const db = require('../db'); -const logger = require('./logger'); - -/** - * Связывает идентификатор с пользователем - * @param {number} userId - ID пользователя - * @param {string} identityType - Тип идентификатора ('ethereum', 'telegram', 'email') - * @param {string} identityValue - Значение идентификатора - * @returns {Promise} - Результат операции - */ -async function linkIdentity(userId, identityType, identityValue) { - try { - // Проверяем, существует ли уже такой идентификатор - const existingResult = await db.query( - 'SELECT * FROM user_identities WHERE identity_type = $1 AND identity_value = $2', - [identityType, identityValue] - ); - - if (existingResult.rows.length > 0) { - // Если идентификатор уже связан с другим пользователем, возвращаем ошибку - if (existingResult.rows[0].user_id !== userId) { - console.warn(`Identity ${identityType}:${identityValue} already linked to another user`); - return false; - } - // Если идентификатор уже связан с этим пользователем, ничего не делаем - return true; - } - - // Добавляем новую связь - await db.query( - 'INSERT INTO user_identities (user_id, identity_type, identity_value, created_at) VALUES ($1, $2, $3, NOW())', - [userId, identityType, identityValue] - ); - - console.log(`Successfully linked ${identityType}:${identityValue} to user ${userId}`); - return true; - } catch (error) { - console.error('Error linking identity:', error); - return false; - } -} - -/** - * Получает ID пользователя по идентификатору - * @param {string} identityType - Тип идентификатора ('ethereum', 'telegram', 'email') - * @param {string} identityValue - Значение идентификатора - * @returns {Promise} - ID пользователя или null, если не найден - */ -async function getUserIdByIdentity(identityType, identityValue) { - try { - const result = await db.query( - 'SELECT user_id FROM user_identities WHERE identity_type = $1 AND identity_value = $2', - [identityType, identityValue] - ); - - if (result.rows.length === 0) { - return null; - } - - return result.rows[0].user_id; - } catch (error) { - console.error('Error getting user ID by identity:', error); - return null; - } -} - -/** - * Получает все идентификаторы пользователя - * @param {number} userId - ID пользователя - * @returns {Promise} - Массив идентификаторов или null в случае ошибки - */ -async function getUserIdentities(userId) { - try { - const result = await db.query( - 'SELECT identity_type, identity_value FROM user_identities WHERE user_id = $1', - [userId] - ); - - return result.rows; - } catch (error) { - console.error('Error getting user identities:', error); - return null; - } -} - -module.exports = { - linkIdentity, - getUserIdByIdentity, - getUserIdentities, -}; diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 5a66d95..15789eb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -8,34 +8,26 @@ import { onMounted } from 'vue'; import { useAuthStore } from './stores/auth'; import { useRouter } from 'vue-router'; +import axios from 'axios'; console.log('App.vue: Version with auth check loaded'); const authStore = useAuthStore(); const router = useRouter(); -onMounted(async () => { - console.log('App.vue: onMounted - checking auth'); - +async function checkAuth() { try { - // Проверяем аутентификацию на сервере - const result = await authStore.checkAuth(); - console.log('Auth check result:', result.authenticated); + const response = await axios.get('/api/auth/check'); - if (result.authenticated) { - // Если пользователь аутентифицирован, восстанавливаем состояние - console.log('Session restored from server'); - - // Загружаем историю чата, если мы на странице чата - if (router.currentRoute.value.name === 'home') { - console.log('Loading chat history after session restore'); - // Здесь можно вызвать метод для загрузки истории чата - } + if (response.data.authenticated) { + authStore.setAuth(response.data); } } catch (error) { console.error('Error checking auth:', error); } -}); +} + +onMounted(checkAuth); \ No newline at end of file diff --git a/frontend/src/components/WalletConnection.vue b/frontend/src/components/WalletConnection.vue deleted file mode 100644 index 082904c..0000000 --- a/frontend/src/components/WalletConnection.vue +++ /dev/null @@ -1,203 +0,0 @@ - - - - - diff --git a/frontend/src/components/identity/EmailConnect.vue b/frontend/src/components/identity/EmailConnect.vue index 94d28ec..89c98e0 100644 --- a/frontend/src/components/identity/EmailConnect.vue +++ b/frontend/src/components/identity/EmailConnect.vue @@ -1,195 +1,117 @@ diff --git a/frontend/src/components/identity/IdentityManager.vue b/frontend/src/components/identity/IdentityManager.vue deleted file mode 100644 index 38d3096..0000000 --- a/frontend/src/components/identity/IdentityManager.vue +++ /dev/null @@ -1,48 +0,0 @@ - diff --git a/frontend/src/components/identity/WalletConnection.vue b/frontend/src/components/identity/WalletConnection.vue new file mode 100644 index 0000000..80a741d --- /dev/null +++ b/frontend/src/components/identity/WalletConnection.vue @@ -0,0 +1,84 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/components/identity/index.js b/frontend/src/components/identity/index.js new file mode 100644 index 0000000..232a23d --- /dev/null +++ b/frontend/src/components/identity/index.js @@ -0,0 +1,9 @@ +import TelegramConnect from './TelegramConnect.vue'; +import WalletConnection from './WalletConnection.vue'; +import EmailConnect from './EmailConnect.vue'; + +export { + TelegramConnect, + WalletConnection, + EmailConnect +}; \ No newline at end of file diff --git a/frontend/src/composables/useEthereum.js b/frontend/src/composables/useEthereum.js deleted file mode 100644 index 2d93ae3..0000000 --- a/frontend/src/composables/useEthereum.js +++ /dev/null @@ -1,56 +0,0 @@ -import { ref } from 'vue'; -import { ethers } from 'ethers'; - -export function useEthereum() { - const address = ref(''); - const isConnected = ref(false); - const provider = ref(null); - const signer = ref(null); - - async function connect() { - if (window.ethereum) { - try { - // Запрашиваем доступ к кошельку - await window.ethereum.request({ method: 'eth_requestAccounts' }); - // Используем синтаксис ethers.js v6 - provider.value = new ethers.BrowserProvider(window.ethereum); - signer.value = await provider.value.getSigner(); - address.value = await signer.value.getAddress(); - isConnected.value = true; - - console.log('Подключение успешно:', address.value); - return { success: true, address: address.value }; - } catch (error) { - console.error('Ошибка подключения к кошельку:', error); - return { success: false, error: error.message }; - } - } else { - console.error('Ethereum wallet not found. Please install MetaMask.'); - return { success: false, error: 'Ethereum wallet not found. Please install MetaMask.' }; - } - } - - async function getContract(contractAddress, contractABI) { - if (!signer.value) { - console.error('Подключите кошелек перед получением контракта.'); - return null; - } - - try { - // Используем синтаксис ethers.js v6 - const contract = new ethers.Contract(contractAddress, contractABI, signer.value); - console.log('Контракт получен:', contract); - return contract; - } catch (error) { - console.error('Ошибка получения контракта:', error); - return null; - } - } - - return { - address, - isConnected, - connect, - getContract, - }; -} diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/locales/ru.json b/frontend/src/locales/ru.json deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 96187ca..d779cc4 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -10,7 +10,6 @@ const routes = [ name: 'home', component: HomeView } - // Другие маршруты можно добавить позже, когда будут созданы соответствующие компоненты ]; const router = createRouter({ @@ -32,11 +31,10 @@ router.beforeEach(async (to, from, next) => { // Проверяем аутентификацию, если маршрут требует авторизации if (to.matched.some(record => record.meta.requiresAuth)) { if (!authStore.isAuthenticated) { - // Если пользователь не авторизован, перенаправляем на главную return next({ name: 'home' }); } - // Проверяем права администратора, если маршрут требует прав администратора + // Проверяем права администратора if (to.matched.some(record => record.meta.requiresAdmin) && !authStore.isAdmin) { return next({ name: 'home' }); } diff --git a/frontend/src/services/wallet.js b/frontend/src/services/wallet.js new file mode 100644 index 0000000..c5f56bf --- /dev/null +++ b/frontend/src/services/wallet.js @@ -0,0 +1,80 @@ +import { ethers } from 'ethers'; +import api from '../api/axios'; +import { useAuthStore } from '../stores/auth'; + +export async function connectWithWallet() { + try { + console.log('Starting wallet connection...'); + // Проверяем наличие MetaMask + if (!window.ethereum) { + throw new Error('MetaMask не установлен. Пожалуйста, установите MetaMask'); + } + + console.log('MetaMask detected, requesting accounts...'); + const accounts = await window.ethereum.request({ + method: 'eth_requestAccounts' + }); + + console.log('Got accounts:', accounts); + if (!accounts || accounts.length === 0) { + throw new Error('Нет доступных аккаунтов. Пожалуйста, разблокируйте MetaMask'); + } + + const address = ethers.getAddress(accounts[0]); + console.log('Normalized address:', address); + + console.log('Requesting nonce...'); + const { data: { nonce } } = await api.get('/api/auth/nonce', { + params: { address } + }); + console.log('Got nonce:', nonce); + + // Формируем сообщение в формате SIWE (Sign-In with Ethereum) + const domain = window.location.host; + const origin = window.location.origin; + const statement = "Sign in with Ethereum to the app."; + const message = [ + `${domain} wants you to sign in with your Ethereum account:`, + address, + "", + statement, + "", + `URI: ${origin}`, + "Version: 1", + "Chain ID: 1", + `Nonce: ${nonce}`, + `Issued At: ${new Date().toISOString()}`, + "Resources:", + `- ${origin}/api/auth/verify` + ].join("\n"); + + console.log('SIWE message:', message); + + console.log('Requesting signature...'); + const signature = await window.ethereum.request({ + method: 'personal_sign', + params: [message, address] + }); + console.log('Got signature:', signature); + + console.log('Sending verification request...'); + const response = await api.post('/api/auth/verify', { + address, + signature, + message + }); + console.log('Verification response:', response.data); + + const authStore = useAuthStore(); + if (response.data.authenticated) { + authStore.setAuth(response.data); + } + + return response.data; + } catch (error) { + // Форматируем ошибку для пользователя + const message = error.message || 'Ошибка подключения кошелька'; + console.error('Error connecting wallet:', message); + throw new Error(message); + } +} \ No newline at end of file diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.js index da8b275..cf7d4e0 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.js @@ -1,37 +1,22 @@ import { defineStore } from 'pinia'; import axios from '../api/axios'; -const loadAuthState = () => { - const savedAuth = localStorage.getItem('auth'); - if (savedAuth) { - try { - return JSON.parse(savedAuth); - } catch (e) { - console.error('Error parsing saved auth state:', e); - } - } - return null; -}; - export const useAuthStore = defineStore('auth', { - state: () => { - const savedState = loadAuthState(); - return { - user: null, - isAuthenticated: savedState?.isAuthenticated || false, - isAdmin: savedState?.isAdmin || false, - authType: savedState?.authType || null, - identities: {}, - loading: false, - error: null, - messages: [], - address: null, - wallet: null, - telegramId: savedState?.telegramId || null, - email: null, - userId: savedState?.userId || null - }; - }, + state: () => ({ + user: null, + isAuthenticated: false, + isAdmin: false, + authType: null, + identities: {}, + loading: false, + error: null, + messages: [], + address: null, + wallet: null, + telegramId: null, + email: null, + userId: null + }), actions: { async connectWallet(address, signature, message) { @@ -442,24 +427,22 @@ export const useAuthStore = defineStore('auth', { } }, - async disconnect(router) { - // Проверяем, действительно ли нужно выходить - if (!this.isAuthenticated) { - console.log('Already logged out, skipping disconnect'); - return; - } - + async disconnect() { try { - // Сначала пробуем очистить сессию на сервере + // Очищаем сессию на сервере await axios.post('/api/auth/clear-session'); - await axios.post('/api/auth/logout'); - // Очищаем состояние только после успешного выхода - this.clearState(); - - if (router) router.push('/'); + // Очищаем состояние + this.isAuthenticated = false; + this.userId = null; + this.address = null; + this.isAdmin = false; + this.authType = null; + + // Очищаем локальное хранилище + localStorage.removeItem('auth'); } catch (error) { - console.error('Error during logout:', error); + console.error('Error during disconnect:', error); } }, @@ -509,29 +492,23 @@ export const useAuthStore = defineStore('auth', { setAuth(authData) { console.log('Setting auth state:', authData); - // Обновляем все поля состояния + // Обновляем только состояние в памяти this.isAuthenticated = authData.authenticated || authData.isAuthenticated; + this.user = { + id: authData.userId, + address: authData.address + }; this.userId = authData.userId; this.isAdmin = authData.isAdmin; this.authType = authData.authType; this.address = authData.address; - // Сохраняем состояние в localStorage - const stateToSave = { - isAuthenticated: this.isAuthenticated, - userId: this.userId, - isAdmin: this.isAdmin, - authType: this.authType, - address: this.address - }; - - localStorage.setItem('auth', JSON.stringify(stateToSave)); - console.log('Auth state updated:', { isAuthenticated: this.isAuthenticated, userId: this.userId, authType: this.authType, - address: this.address + address: this.address, + isAdmin: this.isAdmin }); } } diff --git a/frontend/src/utils/wallet.js b/frontend/src/utils/wallet.js deleted file mode 100644 index b9f3869..0000000 --- a/frontend/src/utils/wallet.js +++ /dev/null @@ -1,90 +0,0 @@ -import { ethers } from 'ethers'; -import axios from '../api/axios'; -import { useAuthStore } from '../stores/auth'; - -// Переименовываем функцию для соответствия импорту -export async function connectWithWallet() { - try { - // Проверяем, доступен ли MetaMask - if (!window.ethereum) { - throw new Error('MetaMask не установлен'); - } - - // Запрашиваем доступ к кошельку - const provider = new ethers.BrowserProvider(window.ethereum); - const signer = await provider.getSigner(); - const address = await signer.getAddress(); - - // Получаем nonce для подписи - const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`); - const nonce = nonceResponse.data.nonce; - - // Формируем сообщение для подписи - const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`; - - // Подписываем сообщение - const signature = await signer.signMessage(message); - - // Верифицируем подпись на сервере - const response = await axios.post('/api/auth/verify', { - address, - signature, - message: nonce - }); - - console.log('Wallet verification response:', response.data); - - // Обновляем состояние в хранилище auth - const authStore = useAuthStore(); - authStore.isAuthenticated = response.data.authenticated; - authStore.user = { - id: response.data.userId, - address: response.data.address - }; - authStore.isAdmin = response.data.isAdmin; - authStore.authType = 'wallet'; - - // Сохраняем адрес кошелька в локальном хранилище - localStorage.setItem('walletAddress', address); - - return { - success: true, - authenticated: response.data.authenticated, - userId: response.data.userId, - address: response.data.address, - isAdmin: response.data.isAdmin, - authType: 'wallet' - }; - } catch (error) { - console.error('Error connecting wallet:', error); - return { success: false, error: error.message }; - } -} - -async function disconnectWallet() { - try { - // Отправляем запрос на выход - await axios.post( - '/api/auth/logout', - {}, - { - withCredentials: true, - } - ); - - // Удаляем адрес кошелька из локального хранилища - localStorage.removeItem('walletAddress'); - - // Обновляем состояние в хранилище auth - const authStore = useAuthStore(); - authStore.isAuthenticated = false; - authStore.user = null; - authStore.isAdmin = false; - authStore.authType = null; - - return { success: true }; - } catch (error) { - console.error('Ошибка при отключении кошелька:', error); - throw error; - } -} diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue index e516d2d..1829357 100644 --- a/frontend/src/views/HomeView.vue +++ b/frontend/src/views/HomeView.vue @@ -5,29 +5,39 @@

Венчурный фонд и поставщик программного обеспечения

+
- - -
-
- +
+
+ Загрузка... +
{{ message.content }}
- +
-
- +
+
- + /> -
- + +
+
- + /> +
@@ -77,9 +87,9 @@ /> -
- + +
+
{{ emailError }}
@@ -106,17 +116,18 @@ diff --git a/frontend/src/views/ProfileView.vue b/frontend/src/views/ProfileView.vue deleted file mode 100644 index 6c56b28..0000000 --- a/frontend/src/views/ProfileView.vue +++ /dev/null @@ -1,138 +0,0 @@ - - - - - diff --git a/frontend/src/views/TokenAccess.vue b/frontend/src/views/TokenAccess.vue deleted file mode 100644 index 122b21e..0000000 --- a/frontend/src/views/TokenAccess.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 6107c5a..17c04c2 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -38,11 +38,15 @@ export default defineConfig({ }, server: { port: 5173, + host: 'localhost', proxy: { '/api': { target: 'http://localhost:8000', - changeOrigin: true + changeOrigin: true, + secure: false, + credentials: true, + rewrite: (path) => path } - }, + } }, });