Описание изменений

This commit is contained in:
2025-03-19 17:18:03 +03:00
parent 2831527544
commit 87bad93eac
75 changed files with 2103 additions and 4861 deletions

View File

@@ -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 };

25
backend/config/session.js Normal file
View File

@@ -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)
};

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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]);
}
}
});
module.exports = { pool };

View File

@@ -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;
}
}
}
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 };

View File

@@ -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");

View File

@@ -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
);

View File

@@ -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);

View File

@@ -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 $$;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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 $$;

View File

@@ -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 $$;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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' });
}
};

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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",

View File

@@ -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;

View File

@@ -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' });
}
});

View File

@@ -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' });
}
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -1 +0,0 @@

View File

@@ -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;

View File

@@ -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;

View File

@@ -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' });
}
});

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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);
});

View File

@@ -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;
}
}
}
}

View File

@@ -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);
});

View File

@@ -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;

View File

@@ -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<string>} - Ответ ИИ
*/
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<Object>} - Информация о пользователе
*/
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<string>} - История диалога в текстовом формате
*/
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;

View File

@@ -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<boolean>} - Имеет ли пользователь права администратора
*/
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<boolean>} - Успешно ли обновлена роль
* Проверяет баланс токенов и обновляет роль пользователя
* @param {string} address - Адрес кошелька
* @returns {Promise<boolean>} - Является ли пользователь админом
*/
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<Array>} - Список идентификаторов
*/
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<boolean>} - Является ли пользователь администратором
*/
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();
// Создаем и экспортируем единственный экземпляр
const authService = new AuthService();
module.exports = authService;

View File

@@ -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 };

View File

@@ -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>${code}</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
};
module.exports = TelegramBotService;

View File

@@ -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<Array>} - Массив похожих документов
*/
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<boolean>} - Успешность добавления
*/
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,
};

View File

@@ -1,39 +0,0 @@
const db = require('../db');
const logger = require('../utils/logger');
const authService = require('../services/auth-service');
/**
* Проверяет токены всех пользователей и обновляет их роли
* @returns {Promise<void>}
*/
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
};

View File

@@ -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<boolean>} - Результат проверки
*/
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<boolean>} - Является ли пользователь администратором
*/
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<Object>} - Информация о доступе
*/
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<Object>} - 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
};

View File

@@ -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<ethers.Contract>} - Экземпляр контракта
*/
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;
}
}

View File

@@ -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<boolean>} - Результат операции
*/
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<number|null>} - 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<Array|null>} - Массив идентификаторов или 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,
};