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

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 path = require('path');
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const authService = require('./services/auth-service'); 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 authRoutes = require('./routes/auth');
const accessRoutes = require('./routes/access');
const usersRoutes = require('./routes/users'); const usersRoutes = require('./routes/users');
const contractsRoutes = require('./routes/contracts');
const rolesRoutes = require('./routes/roles');
const identitiesRoutes = require('./routes/identities'); const identitiesRoutes = require('./routes/identities');
// const conversationsRoutes = require('./routes/conversations');
const messagesRoutes = require('./routes/messages');
const chatRoutes = require('./routes/chat'); const chatRoutes = require('./routes/chat');
const healthRoutes = require('./routes/health'); const adminRoutes = require('./routes/admin');
const debugRoutes = require('./routes/debug');
const app = express(); 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({ app.use(session({
store: new pgSession({ store: new pgSession({
pool, pool,
tableName: 'session', tableName: 'session',
createTableIfMissing: true,
}), }),
secret: process.env.SESSION_SECRET || 'your-secret-key', secret: process.env.SESSION_SECRET || 'hb3atoken',
name: 'sessionId',
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: { cookie: {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней maxAge: 30 * 24 * 60 * 60 * 1000,
httpOnly: true, httpOnly: true,
secure: false, secure: false,
sameSite: 'lax', 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.json());
app.use(express.urlencoded({ extended: true })); 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({ app.use(helmet({
contentSecurityPolicy: false // Отключаем CSP для разработки contentSecurityPolicy: false // Отключаем CSP для разработки
@@ -79,34 +112,51 @@ app.use((req, res, next) => {
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 // Маршруты API
app.use('/api/auth', authRoutes); app.use('/api/auth', authRoutes);
app.use('/api/access', accessRoutes);
app.use('/api/users', usersRoutes); app.use('/api/users', usersRoutes);
app.use('/api/contracts', contractsRoutes);
app.use('/api/roles', rolesRoutes);
app.use('/api/identities', identitiesRoutes); app.use('/api/identities', identitiesRoutes);
// app.use('/api/conversations', conversationsRoutes);
app.use('/api/messages', messagesRoutes);
app.use('/api/chat', chatRoutes); app.use('/api/chat', chatRoutes);
app.use('/api/health', healthRoutes); app.use('/api/admin', adminRoutes);
// Маршруты для отладки (только в режиме разработки)
if (process.env.NODE_ENV !== 'production') {
app.use('/api/debug', debugRoutes);
}
const nonceStore = new Map(); // или любая другая реализация хранилища nonce const nonceStore = new Map(); // или любая другая реализация хранилища nonce
console.log('SESSION_SECRET:', process.env.SESSION_SECRET); 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 }; 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() { const pool = new Pool({
try { 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,
});
// Выполняем миграции // Проверка подключения
await pool.query(createUsersTable); pool.query('SELECT NOW()', (err, res) => {
await pool.query(createSessionTable); if (err) {
await pool.query(createNoncesTable); logger.error('Error connecting to database:', err);
await pool.query(createMessagesTable); } else {
await pool.query(createConversationsTable); logger.info('Успешное подключение к базе данных:', res.rows[0]);
await pool.query(createGuestMessagesTable);
// ... существующий код ...
} catch (error) {
// ... существующий код ...
} }
} });
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 // Инициализация таблицы roles
async function initRoles() { async function initRoles() {
try { try {
// Проверяем, существует ли таблица roles // Проверяем, существует ли таблица roles
const tableExists = await db.query(` const tableExists = await pool.query(`
SELECT EXISTS ( SELECT EXISTS (
SELECT FROM information_schema.tables SELECT FROM information_schema.tables
WHERE table_name = 'roles' WHERE table_name = 'roles'
@@ -11,7 +16,7 @@ async function initRoles() {
if (!tableExists.rows[0].exists) { if (!tableExists.rows[0].exists) {
// Создаем таблицу roles // Создаем таблицу roles
await db.query(` await pool.query(`
CREATE TABLE roles ( CREATE TABLE roles (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE, 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 INSERT INTO roles (id, name, description) VALUES
(3, 'user', 'Обычный пользователь'), (3, 'user', 'Обычный пользователь'),
(4, 'admin', 'Администратор с полным доступом'); (4, 'admin', 'Администратор с полным доступом');
@@ -30,24 +35,24 @@ async function initRoles() {
console.log('Таблица roles создана и заполнена'); console.log('Таблица roles создана и заполнена');
} else { } else {
// Проверяем наличие ролей // Проверяем наличие ролей
const rolesExist = await db.query(` const rolesExist = await pool.query(`
SELECT COUNT(*) FROM roles WHERE id IN (3, 4); SELECT COUNT(*) FROM roles WHERE id IN (3, 4);
`); `);
if (rolesExist.rows[0].count < 2) { if (rolesExist.rows[0].count < 2) {
// Добавляем недостающие роли // Добавляем недостающие роли
const userRoleExists = await db.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`); const userRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'user');`);
const adminRoleExists = await db.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`); const adminRoleExists = await pool.query(`SELECT EXISTS (SELECT FROM roles WHERE name = 'admin');`);
if (!userRoleExists.rows[0].exists) { if (!userRoleExists.rows[0].exists) {
await db.query(` await pool.query(`
INSERT INTO roles (id, name, description) VALUES INSERT INTO roles (id, name, description) VALUES
(3, 'user', 'Обычный пользователь'); (3, 'user', 'Обычный пользователь');
`); `);
} }
if (!adminRoleExists.rows[0].exists) { if (!adminRoleExists.rows[0].exists) {
await db.query(` await pool.query(`
INSERT INTO roles (id, name, description) VALUES INSERT INTO roles (id, name, description) VALUES
(4, 'admin', 'Администратор с полным доступом'); (4, 'admin', 'Администратор с полным доступом');
`); `);
@@ -61,3 +66,54 @@ async function initRoles() {
throw 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) => { const requireAuth = async (req, res, next) => {
try { try {
console.log('Session in requireAuth:', req.session); console.log('Session in requireAuth:', {
console.log('Cookies received:', req.headers.cookie); id: req.sessionID,
console.log('Authorization header:', req.headers.authorization); userId: req.session?.userId,
authenticated: req.session?.authenticated
});
// Проверяем сессию
if (req.session?.authenticated && req.session?.userId) {
// Обновляем время жизни сессии
req.session.touch();
// Проверяем, что пользователь аутентифицирован через сессию
if (req.session && req.session.authenticated && req.session.userId) {
// Добавляем информацию о пользователе в запрос
req.user = { req.user = {
userId: req.session.userId, userId: req.session.userId,
address: req.session.address || null, address: req.session.address,
email: req.session.email || null, isAdmin: req.session.isAdmin,
telegramId: req.session.telegramId || null, authType: req.session.authType
isAdmin: req.session.isAdmin || false,
authType: req.session.authType || 'unknown'
}; };
return next(); return next();
} }
// Проверяем заголовок авторизации // Проверяем Bearer токен
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) { if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.split(' ')[1]; const address = authHeader.split(' ')[1];
// Проверяем, это адрес кошелька или JWT-токен if (address.startsWith('0x')) {
if (token.startsWith('0x')) { const result = await db.query(`
// Это адрес кошелька SELECT u.id, u.is_admin
const address = token; FROM users u
console.log('Found address in Authorization header:', address); JOIN user_identities ui ON u.id = ui.user_id
WHERE ui.identity_type = 'wallet'
AND LOWER(ui.identity_value) = LOWER($1)
`, [address]);
try { if (result.rows.length > 0) {
// Проверяем, существует ли пользователь с таким адресом const user = result.rows[0];
const result = await db.query(`
SELECT u.id, u.is_admin // Создаем новую сессию
FROM users u req.session.regenerate(async (err) => {
JOIN user_identities ui ON u.id = ui.user_id if (err) {
WHERE ui.identity_type = 'wallet' AND LOWER(ui.identity_value) = LOWER($1) console.error('Error regenerating session:', err);
`, [address]); 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));
if (result.rows.length > 0) {
const user = result.rows[0];
req.user = { req.user = {
userId: user.id, userId: user.id,
address: address, address: address,
isAdmin: user.is_admin, isAdmin: user.is_admin,
authType: 'wallet' authType: 'wallet'
}; };
return next(); next();
} });
} catch (error) { return;
console.error('Error finding user by address:', error);
} }
} else {
// Здесь можно добавить логику проверки JWT, если используется
} }
} }
// Если пользователь не аутентифицирован, возвращаем ошибку
return res.status(401).json({ error: 'Unauthorized' }); return res.status(401).json({ error: 'Unauthorized' });
} catch (error) { } 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' }); 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": "eslint .",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix",
"format": "prettier --write \"**/*.{js,vue,json,md}\"", "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": { "dependencies": {
"@langchain/community": "^0.3.34", "@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 express = require('express');
const router = express.Router(); const router = express.Router();
const db = require('../db');
const { requireAdmin } = require('../middleware/auth'); 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 { try {
const result = await db.query('SELECT * FROM users'); const roles = await authService.getAllRoles();
res.json(result.rows); res.json({ success: true, roles });
} catch (error) { } 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' }); res.status(500).json({ error: 'Internal server error' });
} }
}); });

View File

@@ -1,6 +1,5 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { ethers } = require('ethers');
const crypto = require('crypto'); const crypto = require('crypto');
const db = require('../db'); const db = require('../db');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
@@ -8,11 +7,11 @@ const helmet = require('helmet');
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const { checkRole, requireAuth } = require('../middleware/auth'); const { checkRole, requireAuth } = require('../middleware/auth');
const { pool } = require('../db'); const { pool } = require('../db');
const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth');
const authService = require('../services/auth-service'); const authService = require('../services/auth-service');
const { SiweMessage } = require('siwe'); const { SiweMessage } = require('siwe');
const { sendEmail } = require('../services/emailBot'); const { sendEmail } = require('../services/emailBot');
const { verificationCodes } = require('../services/telegramBot'); const { verificationCodes } = require('../services/telegramBot');
const { checkTokensAndUpdateRole } = require('../services/auth-service');
// Создайте лимитер для попыток аутентификации // Создайте лимитер для попыток аутентификации
const authLimiter = rateLimit({ const authLimiter = rateLimit({
@@ -87,85 +86,64 @@ async function checkUserRole(address, req) {
router.post('/verify', async (req, res) => { router.post('/verify', async (req, res) => {
try { try {
const { address, signature, message } = req.body; const { address, signature, message } = req.body;
console.log('Received verification request:', { address, message });
console.log('Signature:', signature);
console.log('Verify request: address=' + address + ', signature=' + signature.substring(0, 10) + '...'); // Проверяем подпись через SIWE
const siwe = new SiweMessage(message);
console.log('Created SIWE message object');
// Получаем nonce из базы данных const fields = await siwe.verify({ signature });
const nonceResult = await db.query(` console.log('SIWE validation result:', fields);
SELECT nonce FROM nonces
WHERE identity_value = $1 AND expires_at > NOW() AND used = false
`, [address.toLowerCase()]);
if (nonceResult.rows.length === 0) { if (!fields || !fields.success) {
console.error(`No valid nonce found for address ${address}`); console.log('SIWE validation failed');
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) {
return res.status(401).json({ error: 'Invalid signature' }); return res.status(401).json({ error: 'Invalid signature' });
} }
// Помечаем nonce как использованный // Проверяем nonce
await db.query(` const nonceResult = await db.query(
UPDATE nonces `SELECT nonce FROM nonces
SET used = true WHERE identity_value = $1
WHERE identity_value = $1 AND expires_at > NOW()`,
`, [address.toLowerCase()]); [address.toLowerCase()]
);
console.log('Nonce check result:', nonceResult.rows);
// Находим или создаем пользователя if (!nonceResult.rows.length) {
console.log('Finding or creating user for address:', address); console.log('Invalid or expired nonce');
const { userId, isAdmin } = await findOrCreateUser(address); return res.status(401).json({ error: 'Invalid or expired nonce' });
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
await new Promise((resolve, reject) => { if (nonceResult.rows[0].nonce !== fields.data.nonce) {
req.session.save(err => { console.log('Nonce mismatch');
if (err) { return res.status(401).json({ error: 'Invalid nonce' });
console.error('Error saving session:', err); }
reject(err);
} else {
console.log('Session saved successfully');
resolve();
}
});
});
console.log('Authentication successful for user:', { // Получаем или создаем пользователя
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, userId,
address, address,
isAdmin, isAdmin,
guestId: currentGuestId authType: 'wallet',
guestId: req.session.guestId
}); });
console.log('Session after save:', req.session);
// Обрабатываем гостевые сообщения, если они есть res.json({
if (currentGuestId) {
console.log(`Processing guest messages for guestId: ${currentGuestId}`);
await authService.processGuestMessages(userId, currentGuestId);
}
return res.json({
authenticated: true, authenticated: true,
userId, userId,
address, address,
@@ -173,7 +151,7 @@ router.post('/verify', async (req, res) => {
authType: 'wallet' authType: 'wallet'
}); });
} catch (error) { } catch (error) {
console.error('Error during wallet verification:', error); console.error('Error:', error);
res.status(500).json({ error: 'Internal server 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 { try {
// Получаем информацию о пользователе const userId = req.session.userId;
const userData = { const address = req.session.address;
address: req.session.address,
isAdmin: req.session.isAdmin || false,
roles: req.session.roles || [],
authenticated: true,
};
// Проверяем доступ к различным разделам if (address) {
const access = { const isAdmin = await checkTokensAndUpdateRole(address);
dashboard: true, // Все аутентифицированные пользователи имеют доступ к панели управления
admin: userData.isAdmin, // Только администраторы имеют доступ к админке
contracts: userData.roles.includes('CONTRACT_MANAGER') || userData.isAdmin,
users: userData.roles.includes('USER_MANAGER') || userData.isAdmin,
};
res.json({ // Обновляем сессию
user: userData, req.session.isAdmin = isAdmin;
access: access,
return res.json({
success: true,
isAdmin,
userId,
address
});
}
return res.json({
success: true,
isAdmin: false,
userId,
address: null
}); });
} catch (error) { } catch (error) {
console.error('Ошибка при проверке прав доступа:', error); logger.error('Error checking access:', error);
res.status(500).json({ error: 'Internal server error' }); return res.status(500).json({ error: 'Internal server error' });
} }
}); });

View File

@@ -1,7 +1,6 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const { ChatOllama } = require('@langchain/ollama'); const aiAssistant = require('../services/ai-assistant');
const { getVectorStore } = require('../services/vectorStore');
const db = require('../db'); const db = require('../db');
const { requireAuth, requireAdmin } = require('../middleware/auth'); const { requireAuth, requireAdmin } = require('../middleware/auth');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
@@ -105,7 +104,7 @@ async function processGuestMessages(userId, guestId) {
// Получаем ответ от AI // Получаем ответ от AI
console.log(`Getting AI response for message ${msg.id} in ${language}`); 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 в ту же беседу // Сохраняем ответ AI в ту же беседу
await db.query( await db.query(
@@ -128,54 +127,44 @@ async function processGuestMessages(userId, guestId) {
// Обработчик для гостевых сообщений // Обработчик для гостевых сообщений
router.post('/guest-message', async (req, res) => { 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 { try {
// Создаем запись в conversations для гостя const { message, language } = req.body;
const conversationResult = await db.query( const guestId = req.session.guestId || crypto.randomBytes(16).toString('hex');
`INSERT INTO conversations (created_at)
VALUES (NOW()) // Сохраняем ID гостя в сессии
RETURNING id` req.session.guestId = guestId;
await req.session.save();
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]
); );
const conversationId = conversationResult.rows[0].id; console.log('Guest message saved:', result.rows[0]);
// Создаем метаданные res.json({
const metadata = { success: true,
guest_id: req.session.guestId, messageId: result.rows[0].id
language: language || 'en' });
};
// Сохраняем только сообщение пользователя
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)
]
);
res.json({ success: true });
} catch (error) { } catch (error) {
console.error('Error processing message:', error); console.error('Error saving guest message:', error);
res.status(500).json({ error: 'Internal server error' }); res.status(500).json({ success: false, error: 'Internal server error' });
} }
}); });
// Маршрут для обычных сообщений (для аутентифицированных пользователей) // Маршрут для обычных сообщений (для аутентифицированных пользователей)
router.post('/message', requireAuth, async (req, res) => { router.post('/message', requireAuth, async (req, res) => {
const { message, language } = req.body;
const userId = req.session.userId;
try { 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( const conversationResult = await db.query(
`INSERT INTO conversations (user_id, created_at) `INSERT INTO conversations (user_id, created_at)
@@ -189,20 +178,25 @@ router.post('/message', requireAuth, async (req, res) => {
// Сохраняем сообщение пользователя // Сохраняем сообщение пользователя
await db.query( await db.query(
`INSERT INTO messages `INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at) (conversation_id, sender_type, content, channel, metadata, created_at)
VALUES ($1, 'user', $2, 'chat', NOW())`, VALUES ($1, 'user', $2, 'chat', $3, NOW())`,
[conversationId, message] [
conversationId,
message,
JSON.stringify({ language: language || 'ru' })
]
); );
// Получаем ответ от AI
const aiResponse = await getAIResponse(message, language);
// Сохраняем ответ AI // Сохраняем ответ AI
await db.query( await db.query(
`INSERT INTO messages `INSERT INTO messages
(conversation_id, sender_type, content, channel, created_at) (conversation_id, sender_type, content, channel, metadata, created_at)
VALUES ($1, 'assistant', $2, 'chat', NOW())`, VALUES ($1, 'assistant', $2, 'chat', $3, NOW())`,
[conversationId, aiResponse] [
conversationId,
aiResponse,
JSON.stringify({ language: language || 'ru' })
]
); );
res.json({ res.json({
@@ -210,7 +204,7 @@ router.post('/message', requireAuth, async (req, res) => {
message: aiResponse message: aiResponse
}); });
} catch (error) { } catch (error) {
console.error('Error processing message:', error); logger.error('Error processing message:', error);
res.status(500).json({ error: 'Internal server 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) => { router.get('/history', async (req, res) => {
const limit = parseInt(req.query.limit) || 2; // По умолчанию только последнее сообщение и ответ
const offset = parseInt(req.query.offset) || 0;
try { 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) { if (!req.session.authenticated || !req.session.userId) {
return res.status(401).json({ error: 'Unauthorized' }); return res.status(401).json({ error: 'Unauthorized' });
} }
// Получаем общее количество сообщений const limit = parseInt(req.query.limit) || 50;
const countResult = await db.query( const offset = parseInt(req.query.offset) || 0;
`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 result = await db.query( const result = await db.query(
`SELECT m.id, m.content, m.sender_type as role, m.created_at, `SELECT
c.user_id, m.metadata m.id,
m.content,
m.sender_type as role,
m.created_at,
c.user_id
FROM messages m FROM messages m
JOIN conversations c ON m.conversation_id = c.id JOIN conversations c ON m.conversation_id = c.id
WHERE c.user_id = $1 WHERE c.user_id = $1
OR (m.metadata->>'guest_id' = $2 AND m.metadata->>'processed' = 'true')
ORDER BY m.created_at DESC ORDER BY m.created_at DESC
LIMIT $3 OFFSET $4`, LIMIT $2 OFFSET $3`,
[req.session.userId, req.session.guestId, limit, offset] [req.session.userId, limit, offset]
); );
return res.json({ return res.json({
success: true, success: true,
messages: result.rows.reverse(), messages: result.rows.reverse()
total
}); });
} catch (error) { } 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' }); 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) => { router.post('/link-guest-messages', requireAuth, async (req, res) => {
try { try {
const userId = req.session.userId; const { userId } = req.session;
const guestId = req.session.guestId; const guestId = req.session.guestId;
console.log('Linking messages:', { userId, guestId });
if (!guestId) { if (!guestId) {
console.log('No guestId in session');
return res.json({ success: true, message: 'No guest messages to link' }); return res.json({ success: true, message: 'No guest messages to link' });
} }
// Связываем гостевые сообщения с пользователем // Проверяем наличие гостевых сообщений
await db.query(` const guestMessages = await db.query(
INSERT INTO messages (user_id, content, role, created_at) 'SELECT EXISTS(SELECT 1 FROM guest_messages WHERE guest_id = $1)',
SELECT $1, content, CASE WHEN is_ai THEN 'assistant' ELSE 'user' END, created_at [guestId]
FROM guest_messages );
WHERE guest_id = $2
ORDER BY created_at
`, [userId, guestId]);
// Удаляем гостевые сообщения console.log('Guest messages check:', guestMessages.rows[0]);
await db.query(`
DELETE FROM guest_messages
WHERE guest_id = $1
`, [guestId]);
// Удаляем временный ID из сессии if (!guestMessages.rows[0].exists) {
delete req.session.guestId; console.log('No guest messages found for guestId:', guestId);
return res.json({ success: true, message: 'No guest messages to link' });
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 {
// ... существующий код верификации ...
// После успешной верификации и создания пользователя
if (req.session.guestId) {
console.log('Found guest messages, processing...');
await processGuestMessages(userId, req.session.guestId);
} }
// Сохраняем данные в сессии // Связываем сообщения
req.session.userId = userId; console.log('Calling link_guest_messages function');
req.session.address = address; await db.query('SELECT link_guest_messages($1, $2)', [userId, guestId]);
req.session.isAdmin = isAdmin;
req.session.authenticated = true;
console.log('Authentication successful for user:', { // Очищаем guestId из сессии после связывания
userId, delete req.session.guestId;
address,
isAdmin,
guestId: req.session.guestId
});
res.json({
authenticated: true,
userId: userId,
address: address,
isAdmin: isAdmin,
authType: 'wallet'
});
console.log('Messages linked successfully');
res.json({ success: true });
} catch (error) { } catch (error) {
console.error('Error during wallet verification:', error); console.error('Error linking guest messages:', error);
res.status(500).json({ error: 'Internal server 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; 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 express = require('express');
const router = express.Router(); const router = express.Router();
const { linkIdentity, getUserIdentities } = require('../utils/identity-linker');
const db = require('../db');
const { requireAuth } = require('../middleware/auth'); const { requireAuth } = require('../middleware/auth');
const authService = require('../services/auth-service');
const logger = require('../utils/logger');
// Получение связанных идентификаторов пользователя // Получение всех идентификаторов пользователя
router.get('/', requireAuth, async (req, res) => { router.get('/', requireAuth, async (req, res) => {
try { try {
// Получаем ID пользователя по Ethereum-адресу const userId = req.session.userId;
const result = await db.query('SELECT id FROM users WHERE address = $1', [ const identities = await authService.getUserIdentities(userId);
req.session.address, res.json({ success: true, identities });
]);
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 });
} catch (error) { } catch (error) {
console.error('Error getting user identities:', error); logger.error('Error getting identities:', error);
res.status(500).json({ error: 'Internal server 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 { try {
const { type, value } = req.params; const { type, value } = req.body;
const userId = req.session.userId;
// Получаем ID пользователя по Ethereum-адресу await authService.linkIdentity(userId, type, value);
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' }); 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; res.json({
success: true,
// Удаляем идентификатор message: 'Identity linked successfully',
await db.query( isAdmin: req.session.isAdmin
'DELETE FROM user_identities WHERE user_id = $1 AND identity_type = $2 AND identity_value = $3', });
[userId, type, value]
);
res.json({ success: true });
} catch (error) { } catch (error) {
console.error('Error deleting user identity:', error); logger.error('Error linking identity:', error);
res.status(500).json({ error: 'Internal server 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').promises;
const fs = require('fs');
const path = require('path'); const path = require('path');
require('dotenv').config(); require('dotenv').config();
const { pool } = require('../db');
// Подключение к БД const logger = require('../utils/logger');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
});
async function runMigrations() { async function runMigrations() {
try { try {
@@ -18,39 +13,64 @@ async function runMigrations() {
CREATE TABLE IF NOT EXISTS migrations ( CREATE TABLE IF NOT EXISTS migrations (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL, 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 { 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 migrationsDir = path.join(__dirname, '../db/migrations');
const migrationFiles = fs const files = await fs.readdir(migrationsDir);
.readdirSync(migrationsDir)
.filter((file) => file.endsWith('.sql'))
.sort(); // Сортируем файлы по имени
// Применяем миграции, которые еще не были применены // Сортируем файлы по номеру
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) { for (const file of migrationFiles) {
if (!appliedMigrations.includes(file)) { if (!executedMigrations.has(file)) {
console.log(`Применение миграции: ${file}`);
// Читаем содержимое файла миграции
const filePath = path.join(migrationsDir, 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('BEGIN');
await pool.query(sql); 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;
}
}
}
// Записываем информацию о примененной миграции // Выполняем SQL-функции
await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]); const functionsDir = path.join(migrationsDir, 'functions');
if (await fs.stat(functionsDir).then(() => true).catch(() => false)) {
const functionFiles = await fs.readdir(functionsDir);
console.log(`Миграция ${file} успешно применена`); for (const file of functionFiles) {
} else { if (file.endsWith('.sql')) {
console.log(`Миграция ${file} уже применена`); 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(); require('dotenv').config();
const express = require('express'); const express = require('express');
const cors = require('cors'); const cors = require('cors');
const { SiweMessage, generateNonce } = require('siwe');
const { ethers } = require('ethers'); const { ethers } = require('ethers');
// const TelegramBotService = require('./services/telegramBot');
const EmailBotService = require('./services/emailBot'); const EmailBotService = require('./services/emailBot');
const { initializeVectorStore } = require('./services/vectorStore');
const session = require('express-session'); const session = require('express-session');
const { app, nonceStore } = require('./app'); const { app, nonceStore } = require('./app');
const usersRouter = require('./routes/users'); const usersRouter = require('./routes/users');
const authRouter = require('./routes/auth'); 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 identitiesRouter = require('./routes/identities');
const { pool } = require('./db'); 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 helmet = require('helmet');
// const csrf = require('csurf'); const TelegramBotService = require('./services/telegramBot');
// const cookieParser = require('cookie-parser'); const pgSession = require('connect-pg-simple')(session);
const messagesRouter = require('./routes/messages'); const authService = require('./services/auth-service');
const sessionMiddleware = require('./middleware/session'); const logger = require('./utils/logger');
// Импорт сервисов
const telegramService = require('./services/telegramBot');
const PORT = process.env.PORT || 8000; const PORT = process.env.PORT || 8000;
@@ -42,83 +22,21 @@ console.log('Переменная окружения PORT:', process.env.PORT);
console.log('Используемый порт:', process.env.PORT || 8000); console.log('Используемый порт:', process.env.PORT || 8000);
// Инициализация сервисов // Инициализация сервисов
let telegramBot; async function initServices() {
let emailBot; try {
console.log('Инициализация сервисов...');
// Проверяем, что библиотека ethers.js правильно импортирована if (process.env.TELEGRAM_BOT_TOKEN) {
console.log('Ethers.js version:', ethers.version); const telegramBot = new TelegramBotService(process.env.TELEGRAM_BOT_TOKEN);
global.telegramBot = telegramBot; // Сохраняем экземпляр глобально
console.log('Telegram бот инициализирован');
}
// Порядок middleware важен! console.log('Все сервисы успешно инициализированы');
// 1. CORS должен быть первым } catch (error) {
app.use( console.error('Ошибка при инициализации сервисов:', error);
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' });
} }
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({ app.use(session({
@@ -136,551 +54,34 @@ app.use(session({
} }
})); }));
async function initServices() { // Маршруты API
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);
}
}
app.use('/api/users', usersRouter); app.use('/api/users', usersRouter);
app.use('/api/auth', authRouter); 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/identities', identitiesRouter);
app.use('/api/messages', messagesRouter);
// Добавьте простой эндпоинт для проверки состояния сервера // Эндпоинт для проверки состояния сервера
app.get('/api/health', (req, res) => { app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() }); res.json({ status: 'ok', timestamp: new Date().toISOString() });
}); });
// Добавьте после настройки маршрутов // Запуск сервера
app.post('/api/verify', async (req, res) => { app.listen(PORT, async () => {
try { try {
// Перенаправляем запрос на /api/auth/verify await initServices();
const { message, signature } = req.body; console.log('Server is running on port', PORT);
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,
});
});
} catch (error) { } catch (error) {
console.error('Ошибка верификации:', error); console.error('Error starting server:', 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);
process.exit(1); process.exit(1);
} }
}
// Обработка сигналов завершения
process.on('SIGINT', () => {
console.log('Получен сигнал SIGINT, завершаем работу...');
server.close(() => {
console.log('Сервер остановлен');
process.exit(0);
});
}); });
process.on('SIGTERM', () => { // Обработка ошибок
console.log('Получен сигнал SIGTERM, завершаем работу...'); process.on('unhandledRejection', (err) => {
server.close(() => { logger.error('Unhandled Rejection:', err);
console.log('Сервер остановлен');
process.exit(0);
});
}); });
// Обработка необработанных исключений process.on('uncaughtException', (err) => {
process.on('uncaughtException', (error) => { logger.error('Uncaught Exception:', err);
console.error('Необработанное исключение:', error);
// Не завершаем процесс, чтобы nodemon мог перезапустить сервер
}); });
process.on('unhandledRejection', (reason, promise) => { module.exports = app;
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);
}
}

View File

@@ -1,158 +1,109 @@
const { ChatOllama } = require('@langchain/ollama'); 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 class AIAssistant {
const model = new ChatOllama({ constructor() {
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434', this.baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
model: process.env.OLLAMA_MODEL || 'llama2', this.defaultModel = process.env.OLLAMA_MODEL || 'mistral';
});
/**
* Обработка сообщения пользователя и получение ответа от ИИ
* @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 'Извините, произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз позже.';
} }
}
/** // Создание экземпляра ChatOllama с нужными параметрами
* Получение информации о пользователе createChat(language = 'ru') {
* @param {number} userId - ID пользователя const systemPrompt = language === 'ru'
* @returns {Promise<Object>} - Информация о пользователе ? 'Вы - полезный ассистент. Отвечайте на русском языке.'
*/ : 'You are a helpful assistant. Respond in English.';
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]
);
if (userResult.rows.length === 0) { return new ChatOllama({
return { id: userId }; 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 };
} }
}
/** // Альтернативный метод запроса через прямой API
* Получение истории диалога async fallbackRequest(message, language) {
* @param {number} userId - ID пользователя try {
* @param {number} limit - Максимальное количество сообщений logger.info('Using fallback request method');
* @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]
);
if (conversationResult.rows.length === 0) { const systemPrompt = language === 'ru'
return ''; ? 'Вы - полезный ассистент. Отвечайте на русском языке.'
: 'You are a helpful assistant. Respond in English.';
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 [];
}
}
// Получение последних сообщений из диалога // Добавляем методы из vectorStore.js
const messagesResult = await pool.query( async initVectorStore() {
`SELECT sender_type, content, created_at // ... код инициализации ...
FROM messages }
WHERE conversation_id = $1
ORDER BY created_at DESC
LIMIT $2`,
[conversationId, limit]
);
// Формирование истории в текстовом формате async findSimilarDocuments(query, k = 3) {
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 '';
} }
} }
module.exports = { // Создаем и экспортируем единственный экземпляр
processMessage, const aiAssistant = new AIAssistant();
getUserInfo, module.exports = aiAssistant;
getConversationHistory,
};

View File

@@ -1,69 +1,54 @@
const db = require('../db'); const db = require('../db');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
const { ethers } = require('ethers'); const { ethers } = require('ethers');
const crypto = require('crypto');
const { processMessage } = require('./ai-assistant'); // Используем AI Assistant 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 { class AuthService {
/** constructor() {
* Проверяет наличие токенов на кошельке и обновляет роль // Инициализация провайдеров для разных сетей
* @param {string} walletAddress - Адрес кошелька this.providers = {
* @returns {Promise<boolean>} - Имеет ли пользователь права администратора eth: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH),
*/ polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON),
async checkTokensAndUpdateRole(walletAddress) { bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC),
try { arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM)
// Получаем 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`); 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 {
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; return false;
} }
const userId = userResult.rows[0].id; try {
// Восстанавливаем адрес из подписи через ethers
// Проверяем наличие токенов на кошельке const recoveredAddress = ethers.verifyMessage(message, signature);
const isAdmin = await this.checkAdminTokens(walletAddress); return ethers.getAddress(recoveredAddress) === ethers.getAddress(address);
} catch (error) {
// Обновляем роль в базе данных logger.error('Error in signature verification:', error);
await this.updateUserRole(userId, isAdmin ? 'admin' : 'user'); return false;
}
logger.info(`User ${userId} with address ${walletAddress}: admin=${isAdmin}`);
return isAdmin;
} catch (error) { } catch (error) {
logger.error(`Error checking tokens: ${error.message}`); logger.error('Error in verifySignature:', error);
return false; return false;
} }
} }
@@ -75,80 +60,195 @@ class AuthService {
*/ */
async checkAdminTokens(walletAddress) { async checkAdminTokens(walletAddress) {
try { try {
const tokenContracts = [ for (const contract of this.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) {
try { try {
const provider = providers[contract.network]; const provider = this.providers[contract.network];
if (!provider) { const tokenContract = new ethers.Contract(
logger.warn(`Provider not found for network: ${contract.network}`); contract.address,
continue; ['function balanceOf(address) view returns (uint256)'],
} provider
);
// Проверка доступности провайдера
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 balance = await tokenContract.balanceOf(walletAddress); const balance = await tokenContract.balanceOf(walletAddress);
logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance.toString()}`); logger.info(`Balance for ${walletAddress} on ${contract.network}: ${balance}`);
if (balance >= MIN_BALANCE) { if (balance >= this.MIN_BALANCE) {
logger.info(`Admin token found on ${contract.network} for ${walletAddress}`); logger.info(`Admin token found on ${contract.network} for ${walletAddress}`);
return true; // Если найден хотя бы один токен, возвращаем true return true;
} }
} catch (error) { } 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}`); logger.info(`No admin tokens found for ${walletAddress}`);
return false; // Если не найдено ни одного токена, возвращаем false return false;
} catch (error) { } catch (error) {
logger.error(`Error in checkAdminTokens: ${error.message}`); logger.error('Error in checkAdminTokens:', error);
return false; return false;
} }
} }
/** /**
* Обновляет роль пользователя в базе данных * Проверяет баланс токенов и обновляет роль пользователя
* @param {number} userId - ID пользователя * @param {string} address - Адрес кошелька
* @param {string} role - Новая роль ('admin' или 'user') * @returns {Promise<boolean>} - Является ли пользователь админом
* @returns {Promise<boolean>} - Успешно ли обновлена роль
*/ */
async updateUserRole(userId, role) { async checkTokensAndUpdateRole(address) {
try { try {
// Получаем ID роли const isAdmin = await this.checkAdminTokens(address);
const roleResult = await db.query('SELECT id FROM roles WHERE name = $1', [role]);
if (roleResult.rows.length === 0) { // Обновляем роль в базе данных
logger.error(`Role ${role} not found`); await this.updateUserRole(address, isAdmin);
return false;
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; // Если пользователь не найден, создаем нового
const result = await db.query(
'INSERT INTO users DEFAULT VALUES RETURNING id',
[]
);
const userId = result.rows[0].id;
// Обновляем роль пользователя // Добавляем wallet identity
await db.query('UPDATE users SET role_id = $1 WHERE id = $2', [roleId, userId]); 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);
}
logger.info(`Updated role for user ${userId} to ${role}`);
return true; return true;
} catch (error) { } 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; 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; 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 TelegramBot = require('node-telegram-bot-api');
const logger = require('../utils/logger'); const logger = require('../utils/logger');
const { pool } = require('../db');
const crypto = require('crypto');
// Создаем бота class TelegramBotService {
const token = process.env.TELEGRAM_BOT_TOKEN; constructor(token) {
let bot = null; this.bot = new TelegramBot(token, {
polling: true,
/**
* Функция для отправки кода подтверждения
*/
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
}
},
request: { request: {
timeout: 30000, // увеличиваем таймаут до 30 секунд timeout: 30000 // 30 секунд таймаут
proxy: process.env.HTTPS_PROXY // используем прокси если есть
} }
}); });
this.verificationCodes = new Map();
this.setupHandlers();
console.log('Telegram bot initialized'); logger.info('TelegramBot service initialized');
}
// Очищаем все предыдущие обработчики setupHandlers() {
bot.removeAllListeners(); this.bot.on('message', this.handleMessage.bind(this));
this.bot.on('callback_query', this.handleCallbackQuery.bind(this));
// Устанавливаем команды бота с обработкой ошибок // Обработка ошибок
bot.setMyCommands([ this.bot.on('polling_error', (error) => {
{ command: '/start', description: 'Получить код подтверждения' }, logger.error('Telegram polling error:', error);
{ command: '/help', description: 'Показать справку' }
]).catch(error => {
console.warn('Error setting bot commands:', error);
// Продолжаем работу даже если не удалось установить команды
}); });
// Обработчик команды /start this.bot.on('error', (error) => {
bot.onText(/\/start/, async (msg) => { logger.error('Telegram bot error:', error);
});
}
async handleMessage(msg) {
try {
const chatId = msg.chat.id; const chatId = msg.chat.id;
try { const text = msg.text;
await sendVerificationCode(chatId);
} catch (error) { logger.info(`Received message from ${chatId}: ${text}`);
console.error('Error handling /start:', error);
await bot.sendMessage(chatId, 'Произошла ошибка. Пожалуйста, попробуйте позже.') if (text.startsWith('/start')) {
.catch(err => console.error('Error sending error message:', err)); await this.handleStart(msg);
} else if (this.verificationCodes.has(chatId)) {
await this.handleVerificationCode(msg);
} }
}); } catch (error) {
logger.error('Error handling message:', error);
}
}
// Обработчик ошибок polling async handleCallbackQuery(query) {
bot.on('polling_error', (error) => { try {
console.error('Telegram bot polling error:', error); const chatId = query.message.chat.id;
// Перезапускаем polling при ошибке await this.bot.answerCallbackQuery(query.id);
setTimeout(() => {
try { logger.info(`Handled callback query from ${chatId}`);
bot.startPolling(); } catch (error) {
} catch (e) { logger.error('Error handling callback query:', error);
console.error('Error restarting polling:', e); }
}
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 секунд }
}); return { success: false, error: 'Неверный код' };
} catch (error) {
// Обработчик остановки polling logger.error('Error verifying code:', error);
bot.on('stop', () => { return { success: false, error: 'Внутренняя ошибка' };
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;
} }
} }
// Экспортируем функции module.exports = TelegramBotService;
module.exports = {
initTelegramBot,
verifyCode,
sendVerificationCode
};

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

View File

@@ -8,34 +8,26 @@
import { onMounted } from 'vue'; import { onMounted } from 'vue';
import { useAuthStore } from './stores/auth'; import { useAuthStore } from './stores/auth';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import axios from 'axios';
console.log('App.vue: Version with auth check loaded'); console.log('App.vue: Version with auth check loaded');
const authStore = useAuthStore(); const authStore = useAuthStore();
const router = useRouter(); const router = useRouter();
onMounted(async () => { async function checkAuth() {
console.log('App.vue: onMounted - checking auth');
try { try {
// Проверяем аутентификацию на сервере const response = await axios.get('/api/auth/check');
const result = await authStore.checkAuth();
console.log('Auth check result:', result.authenticated);
if (result.authenticated) { if (response.data.authenticated) {
// Если пользователь аутентифицирован, восстанавливаем состояние authStore.setAuth(response.data);
console.log('Session restored from server');
// Загружаем историю чата, если мы на странице чата
if (router.currentRoute.value.name === 'home') {
console.log('Loading chat history after session restore');
// Здесь можно вызвать метод для загрузки истории чата
}
} }
} catch (error) { } catch (error) {
console.error('Error checking auth:', error); console.error('Error checking auth:', error);
} }
}); }
onMounted(checkAuth);
</script> </script>
<style> <style>

View File

@@ -2,43 +2,30 @@ import axios from 'axios';
import { useAuthStore } from '../stores/auth'; import { useAuthStore } from '../stores/auth';
// Создаем экземпляр axios с базовым URL // Создаем экземпляр axios с базовым URL
const instance = axios.create({ const api = axios.create({
baseURL: '/', baseURL: '', // Убираем baseURL
withCredentials: true, withCredentials: true,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json'
}, }
}); });
// Добавляем перехватчик для добавления заголовка авторизации // Перехватчик запросов
instance.interceptors.request.use( api.interceptors.request.use(
(config) => { (config) => {
console.log('Axios interceptor running'); config.withCredentials = true; // Важно для каждого запроса
const authStore = useAuthStore(); const authStore = useAuthStore();
// Логируем параметры запроса
console.log('Request parameters:', config);
// Если уже есть заголовок Authorization, не перезаписываем его
if (config.headers.Authorization) {
return config;
}
// Если пользователь аутентифицирован и есть адрес кошелька
if (authStore.isAuthenticated && authStore.address) { if (authStore.isAuthenticated && authStore.address) {
console.log('Adding Authorization header:', `Bearer ${authStore.address}`);
config.headers.Authorization = `Bearer ${authStore.address}`; config.headers.Authorization = `Bearer ${authStore.address}`;
} }
return config; return config;
}, },
(error) => { (error) => Promise.reject(error)
return Promise.reject(error);
}
); );
// Добавляем перехватчик для обработки ответов // Добавляем перехватчик для обработки ответов
instance.interceptors.response.use( api.interceptors.response.use(
(response) => { (response) => {
console.log('Response from server:', response.data); console.log('Response from server:', response.data);
return response; return response;
@@ -69,4 +56,4 @@ const sendGuestMessageToServer = async (messageText) => {
} }
}; };
export default instance; export default api;

View File

@@ -1,141 +0,0 @@
<template>
<div class="telegram-auth">
<button v-if="!showVerification" class="auth-btn telegram-btn" @click="startTelegramAuth">
<span class="auth-icon">📱</span> Подключить Telegram
</button>
<div v-else class="verification-form">
<input
type="text"
v-model="verificationCode"
placeholder="Введите код из Telegram"
/>
<button class="auth-btn verify-btn" @click="verifyCode">Подтвердить</button>
<button class="auth-btn cancel-btn" @click="cancelVerification">Отмена</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useAuthStore } from '../stores/auth';
import axios from '../api/axios';
const auth = useAuthStore();
const showVerification = ref(false);
const verificationCode = ref('');
const startTelegramAuth = () => {
// Открываем Telegram бота в новом окне
window.open('https://t.me/your_bot_username', '_blank');
showVerification.value = true;
};
const verifyCode = async () => {
try {
const response = await axios.post('/api/auth/telegram/verify', {
code: verificationCode.value
});
if (response.data.success) {
auth.setTelegramAuth(response.data);
}
} catch (error) {
console.error('Error verifying Telegram code:', error);
}
};
const cancelVerification = () => {
showVerification.value = false;
verificationCode.value = '';
};
</script>
<style scoped>
.telegram-auth {
margin-bottom: 15px;
}
.telegram-btn {
display: flex;
align-items: center;
justify-content: center;
background-color: #0088cc;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-weight: bold;
cursor: pointer;
width: 100%;
}
.auth-icon {
margin-right: 8px;
}
.auth-progress {
background-color: #f8f8f8;
border-radius: 8px;
padding: 16px;
margin-top: 10px;
}
.auth-actions {
display: flex;
justify-content: space-between;
margin-top: 15px;
}
.cancel-btn {
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
padding: 8px;
cursor: pointer;
}
.check-btn {
background-color: #0088cc;
color: white;
border: none;
border-radius: 4px;
padding: 8px;
cursor: pointer;
font-weight: bold;
}
.error-message {
color: #ff4d4f;
margin-top: 10px;
font-size: 14px;
}
.auth-code {
font-family: monospace;
font-size: 16px;
padding: 12px;
background-color: #f1f1f1;
border-radius: 4px;
margin: 15px 0;
white-space: nowrap;
overflow-x: auto;
}
.copy-btn {
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
font-weight: bold;
display: block;
margin-bottom: 15px;
}
.copy-btn:hover {
background-color: #45a049;
}
</style>

View File

@@ -1,203 +0,0 @@
<template>
<div class="wallet-connection">
<div v-if="error" class="error-message">
{{ error }}
</div>
<div v-if="!authStore.isAuthenticated">
<button @click="connectWallet" class="connect-button" :disabled="loading">
<div v-if="loading" class="spinner"></div>
{{ loading ? 'Подключение...' : 'Подключить кошелек' }}
</button>
</div>
<div v-else class="wallet-info">
<span class="address">{{ formatAddress(authStore.user?.address) }}</span>
<button @click="disconnectWallet" class="disconnect-btn">Выйти</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { connectWithWallet } from '../utils/wallet';
import { useAuthStore } from '../stores/auth';
import { useRouter } from 'vue-router';
const authStore = useAuthStore();
const router = useRouter();
const loading = ref(false);
const error = ref('');
const isConnecting = ref(false);
const props = defineProps({
onWalletAuth: {
type: Function,
required: true
},
isAuthenticated: {
type: Boolean,
required: true
}
});
// Форматирование адреса кошелька
const formatAddress = (address) => {
if (!address) return '';
return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
};
// Функция для подключения кошелька
const connectWallet = async () => {
loading.value = true;
error.value = '';
try {
await props.onWalletAuth();
} catch (err) {
console.error('Ошибка при подключении кошелька:', err);
error.value = 'Ошибка подключения кошелька';
} finally {
loading.value = false;
}
};
// Автоматическое подключение при загрузке компонента
onMounted(async () => {
console.log('WalletConnection mounted, checking auth state...');
// Проверяем аутентификацию на сервере
const authState = await authStore.checkAuth();
console.log('Auth state after check:', authState);
// Если пользователь уже аутентифицирован, не нужно ничего делать
if (authState.authenticated) {
console.log('User is already authenticated, no need to reconnect');
return;
}
// Проверяем, есть ли сохраненный адрес кошелька
const savedAddress = localStorage.getItem('walletAddress');
if (savedAddress && window.ethereum) {
console.log('Found saved wallet address:', savedAddress);
try {
// Проверяем, разблокирован ли MetaMask, но не запрашиваем разрешение
const accounts = await window.ethereum.request({
method: 'eth_accounts' // Используем eth_accounts вместо eth_requestAccounts
});
if (accounts && accounts.length > 0) {
console.log('MetaMask is unlocked, connected accounts:', accounts);
// Если кошелек разблокирован и есть доступные аккаунты, проверяем совпадение адреса
if (accounts[0].toLowerCase() === savedAddress.toLowerCase()) {
console.log('Current account matches saved address');
// Не вызываем handleConnectWallet() автоматически,
// просто показываем пользователю, что он может подключиться
} else {
console.log('Current account does not match saved address');
localStorage.removeItem('walletAddress');
}
} else {
console.log('MetaMask is locked or no accounts available');
}
} catch (error) {
console.error('Error checking MetaMask state:', error);
}
}
});
// Функция для отключения кошелька
const disconnectWallet = async () => {
try {
// Сначала отключаем MetaMask
if (window.ethereum) {
try {
// Просто очищаем слушатели событий
window.ethereum.removeAllListeners();
} catch (error) {
console.error('Error disconnecting MetaMask:', error);
}
}
// Затем выполняем выход из системы
await authStore.disconnect(router);
} catch (error) {
console.error('Error disconnecting wallet:', error);
}
};
</script>
<style scoped>
.wallet-connection {
margin: 20px 0;
}
.connect-button {
background-color: #1976d2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.connect-button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.error-message {
color: #d32f2f;
margin-bottom: 10px;
padding: 10px;
background-color: #ffebee;
border-radius: 4px;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top-color: white;
animation: spin 1s linear infinite;
margin-right: 8px;
}
.wallet-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: #f5f5f5;
border-radius: 4px;
}
.address {
font-family: monospace;
font-weight: bold;
}
.disconnect-btn {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
</style>

View File

@@ -1,195 +1,117 @@
<template> <template>
<div class="email-connect"> <div class="email-connection">
<p>Подключите свой email для быстрой авторизации.</p> <div v-if="!showVerification">
<div class="email-form">
<input <input
type="email"
v-model="email" v-model="email"
placeholder="Введите ваш email" type="email"
:disabled="loading || verificationSent" placeholder="Введите email"
class="email-input"
/> />
<button <button
@click="sendVerification" @click="requestCode"
class="connect-button" :disabled="isLoading || !isValidEmail"
:disabled="!isValidEmail || loading || verificationSent" class="email-btn"
> >
<span class="email-icon"></span> {{ verificationSent ? 'Код отправлен' : 'Отправить код' }} Получить код
</button> </button>
</div> </div>
<div v-else>
<div v-if="verificationSent" class="verification-form">
<input <input
v-model="code"
type="text" type="text"
v-model="verificationCode" placeholder="Введите код"
placeholder="Введите код подтверждения" class="code-input"
:disabled="loading"
/> />
<button <button
@click="verifyEmail" @click="verifyCode"
class="verify-button" :disabled="isLoading"
:disabled="!verificationCode || loading" class="verify-btn"
> >
Подтвердить Подтвердить
</button> </button>
</div> </div>
<div v-if="loading" class="loading">Загрузка...</div>
<div v-if="error" class="error">{{ error }}</div> <div v-if="error" class="error">{{ error }}</div>
<div v-if="success" class="success">{{ success }}</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import axios from 'axios';
const email = ref(''); const props = defineProps({
const verificationCode = ref(''); onEmailAuth: {
const loading = ref(false); type: Function,
const error = ref(''); required: true
const success = ref(''); }
const verificationSent = ref(false);
const isValidEmail = computed(() => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email.value);
}); });
async function sendVerification() { const email = ref('');
if (!isValidEmail.value) return; const code = ref('');
const error = ref('');
const isLoading = ref(false);
const showVerification = ref(false);
const isValidEmail = computed(() => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value);
});
const requestCode = async () => {
try { try {
loading.value = true; isLoading.value = true;
await props.onEmailAuth(email.value);
showVerification.value = true;
error.value = ''; error.value = '';
success.value = '';
// Запрос на отправку кода подтверждения
const response = await axios.post('/api/auth/email', {
email: email.value
}, {
withCredentials: true
});
if (response.data.error) {
error.value = `Ошибка: ${response.data.error}`;
return;
}
verificationSent.value = true;
success.value = `Код подтверждения отправлен на ${email.value}`;
} catch (err) { } catch (err) {
console.error('Error sending verification code:', err); error.value = err.message || 'Ошибка отправки кода';
error.value = 'Ошибка при отправке кода подтверждения';
} finally { } finally {
loading.value = false; isLoading.value = false;
} }
} };
async function verifyEmail() {
if (!verificationCode.value) return;
const verifyCode = async () => {
try { try {
loading.value = true; isLoading.value = true;
await props.onEmailAuth(email.value, code.value);
error.value = ''; error.value = '';
success.value = '';
// Запрос на проверку кода
const response = await axios.post('/api/auth/email/verify', {
email: email.value,
code: verificationCode.value
}, {
withCredentials: true
});
if (response.data.error) {
error.value = `Ошибка: ${response.data.error}`;
return;
}
success.value = 'Email успешно подтвержден';
// Сбрасываем форму
setTimeout(() => {
email.value = '';
verificationCode.value = '';
verificationSent.value = false;
success.value = '';
}, 3000);
} catch (err) { } catch (err) {
console.error('Error verifying email:', err); error.value = err.message || 'Неверный код';
error.value = 'Ошибка при проверке кода подтверждения';
} finally { } finally {
loading.value = false; isLoading.value = false;
} }
} };
</script> </script>
<style scoped> <style scoped>
.email-connect { .email-connection {
display: flex; margin: 10px 0;
flex-direction: column;
gap: 15px;
} }
.email-form, .verification-form { .email-input,
display: flex; .code-input {
gap: 10px; padding: 8px;
} margin-right: 10px;
input {
flex: 1;
padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 4px; border-radius: 4px;
font-size: 16px;
} }
.connect-button, .verify-button { .email-btn,
display: flex; .verify-btn {
align-items: center; padding: 10px 20px;
justify-content: center; background-color: #48bb78;
padding: 10px 15px;
background-color: #4caf50;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 16px; font-size: 14px;
transition: background-color 0.2s;
white-space: nowrap;
}
.connect-button:hover, .verify-button:hover {
background-color: #45a049;
}
.connect-button:disabled, .verify-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.email-icon {
margin-right: 10px;
font-size: 18px;
}
.loading, .error, .success {
padding: 10px;
border-radius: 4px;
}
.loading {
background-color: #f8f9fa;
} }
.error { .error {
background-color: #f8d7da; color: #e53e3e;
color: #721c24; margin-top: 5px;
font-size: 14px;
} }
.success { button:disabled {
background-color: #d4edda; opacity: 0.7;
color: #155724; cursor: not-allowed;
} }
</style> </style>

View File

@@ -1,48 +0,0 @@
<script setup>
import { ref, onMounted } from 'vue';
import { useAuthStore } from '../../stores/auth';
const authStore = useAuthStore();
const identities = ref({});
const newIdentity = ref({ type: 'email', value: '' });
const loading = ref(false);
const error = ref(null);
onMounted(async () => {
try {
loading.value = true;
const response = await fetch('/api/access/tokens', {
credentials: 'include'
});
const data = await response.json();
identities.value = data.identities || {};
} catch (err) {
error.value = 'Ошибка при загрузке идентификаторов';
console.error(err);
} finally {
loading.value = false;
}
});
async function addIdentity() {
try {
loading.value = true;
error.value = null;
const success = await authStore.linkIdentity(
newIdentity.value.type,
newIdentity.value.value
);
if (success) {
identities.value = authStore.identities;
newIdentity.value.value = '';
}
} catch (err) {
error.value = 'Ошибка при добавлении идентификатора';
console.error(err);
} finally {
loading.value = false;
}
}
</script>

View File

@@ -0,0 +1,84 @@
<template>
<div class="wallet-connection">
<button
@click="connectWallet"
:disabled="isLoading"
class="wallet-btn"
>
{{ isAuthenticated ? 'Подключено' : 'Подключить кошелек' }}
</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { connectWithWallet } from '../../services/wallet';
// Определяем props
const props = defineProps({
isAuthenticated: {
type: Boolean,
default: false
}
});
// Определяем состояние
const isLoading = ref(false);
const emit = defineEmits(['connect']);
// Метод подключения кошелька
const connectWallet = async () => {
if (isLoading.value) return;
try {
isLoading.value = true;
// Получаем адрес кошелька
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
// Получаем nonce
const nonceResponse = await api.get(`/api/auth/nonce?address=${address}`);
const nonce = nonceResponse.data.nonce;
// Подписываем сообщение
const message = `${window.location.host} wants you to sign in with your Ethereum account:\n${address.slice(0, 42)}...`;
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address]
});
emit('connect', { address, signature, message });
} catch (error) {
console.error('Error connecting wallet:', error);
} finally {
isLoading.value = false;
}
};
</script>
<style scoped>
.wallet-connection {
margin: 10px 0;
}
.wallet-btn {
padding: 10px 20px;
background-color: #4a5568;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.wallet-btn:hover:not(:disabled) {
background-color: #2d3748;
}
.wallet-btn:disabled {
opacity: 0.7;
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,9 @@
import TelegramConnect from './TelegramConnect.vue';
import WalletConnection from './WalletConnection.vue';
import EmailConnect from './EmailConnect.vue';
export {
TelegramConnect,
WalletConnection,
EmailConnect
};

View File

@@ -1,56 +0,0 @@
import { ref } from 'vue';
import { ethers } from 'ethers';
export function useEthereum() {
const address = ref('');
const isConnected = ref(false);
const provider = ref(null);
const signer = ref(null);
async function connect() {
if (window.ethereum) {
try {
// Запрашиваем доступ к кошельку
await window.ethereum.request({ method: 'eth_requestAccounts' });
// Используем синтаксис ethers.js v6
provider.value = new ethers.BrowserProvider(window.ethereum);
signer.value = await provider.value.getSigner();
address.value = await signer.value.getAddress();
isConnected.value = true;
console.log('Подключение успешно:', address.value);
return { success: true, address: address.value };
} catch (error) {
console.error('Ошибка подключения к кошельку:', error);
return { success: false, error: error.message };
}
} else {
console.error('Ethereum wallet not found. Please install MetaMask.');
return { success: false, error: 'Ethereum wallet not found. Please install MetaMask.' };
}
}
async function getContract(contractAddress, contractABI) {
if (!signer.value) {
console.error('Подключите кошелек перед получением контракта.');
return null;
}
try {
// Используем синтаксис ethers.js v6
const contract = new ethers.Contract(contractAddress, contractABI, signer.value);
console.log('Контракт получен:', contract);
return contract;
} catch (error) {
console.error('Ошибка получения контракта:', error);
return null;
}
}
return {
address,
isConnected,
connect,
getContract,
};
}

View File

@@ -10,7 +10,6 @@ const routes = [
name: 'home', name: 'home',
component: HomeView component: HomeView
} }
// Другие маршруты можно добавить позже, когда будут созданы соответствующие компоненты
]; ];
const router = createRouter({ const router = createRouter({
@@ -32,11 +31,10 @@ router.beforeEach(async (to, from, next) => {
// Проверяем аутентификацию, если маршрут требует авторизации // Проверяем аутентификацию, если маршрут требует авторизации
if (to.matched.some(record => record.meta.requiresAuth)) { if (to.matched.some(record => record.meta.requiresAuth)) {
if (!authStore.isAuthenticated) { if (!authStore.isAuthenticated) {
// Если пользователь не авторизован, перенаправляем на главную
return next({ name: 'home' }); return next({ name: 'home' });
} }
// Проверяем права администратора, если маршрут требует прав администратора // Проверяем права администратора
if (to.matched.some(record => record.meta.requiresAdmin) && !authStore.isAdmin) { if (to.matched.some(record => record.meta.requiresAdmin) && !authStore.isAdmin) {
return next({ name: 'home' }); return next({ name: 'home' });
} }

View File

@@ -0,0 +1,80 @@
import { ethers } from 'ethers';
import api from '../api/axios';
import { useAuthStore } from '../stores/auth';
export async function connectWithWallet() {
try {
console.log('Starting wallet connection...');
// Проверяем наличие MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не установлен. Пожалуйста, установите MetaMask');
}
console.log('MetaMask detected, requesting accounts...');
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
console.log('Got accounts:', accounts);
if (!accounts || accounts.length === 0) {
throw new Error('Нет доступных аккаунтов. Пожалуйста, разблокируйте MetaMask');
}
const address = ethers.getAddress(accounts[0]);
console.log('Normalized address:', address);
console.log('Requesting nonce...');
const { data: { nonce } } = await api.get('/api/auth/nonce', {
params: { address }
});
console.log('Got nonce:', nonce);
// Формируем сообщение в формате SIWE (Sign-In with Ethereum)
const domain = window.location.host;
const origin = window.location.origin;
const statement = "Sign in with Ethereum to the app.";
const message = [
`${domain} wants you to sign in with your Ethereum account:`,
address,
"",
statement,
"",
`URI: ${origin}`,
"Version: 1",
"Chain ID: 1",
`Nonce: ${nonce}`,
`Issued At: ${new Date().toISOString()}`,
"Resources:",
`- ${origin}/api/auth/verify`
].join("\n");
console.log('SIWE message:', message);
console.log('Requesting signature...');
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, address]
});
console.log('Got signature:', signature);
console.log('Sending verification request...');
const response = await api.post('/api/auth/verify', {
address,
signature,
message
});
console.log('Verification response:', response.data);
const authStore = useAuthStore();
if (response.data.authenticated) {
authStore.setAuth(response.data);
}
return response.data;
} catch (error) {
// Форматируем ошибку для пользователя
const message = error.message || 'Ошибка подключения кошелька';
console.error('Error connecting wallet:', message);
throw new Error(message);
}
}

View File

@@ -1,37 +1,22 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import axios from '../api/axios'; import axios from '../api/axios';
const loadAuthState = () => {
const savedAuth = localStorage.getItem('auth');
if (savedAuth) {
try {
return JSON.parse(savedAuth);
} catch (e) {
console.error('Error parsing saved auth state:', e);
}
}
return null;
};
export const useAuthStore = defineStore('auth', { export const useAuthStore = defineStore('auth', {
state: () => { state: () => ({
const savedState = loadAuthState(); user: null,
return { isAuthenticated: false,
user: null, isAdmin: false,
isAuthenticated: savedState?.isAuthenticated || false, authType: null,
isAdmin: savedState?.isAdmin || false, identities: {},
authType: savedState?.authType || null, loading: false,
identities: {}, error: null,
loading: false, messages: [],
error: null, address: null,
messages: [], wallet: null,
address: null, telegramId: null,
wallet: null, email: null,
telegramId: savedState?.telegramId || null, userId: null
email: null, }),
userId: savedState?.userId || null
};
},
actions: { actions: {
async connectWallet(address, signature, message) { async connectWallet(address, signature, message) {
@@ -442,24 +427,22 @@ export const useAuthStore = defineStore('auth', {
} }
}, },
async disconnect(router) { async disconnect() {
// Проверяем, действительно ли нужно выходить
if (!this.isAuthenticated) {
console.log('Already logged out, skipping disconnect');
return;
}
try { try {
// Сначала пробуем очистить сессию на сервере // Очищаем сессию на сервере
await axios.post('/api/auth/clear-session'); await axios.post('/api/auth/clear-session');
await axios.post('/api/auth/logout');
// Очищаем состояние только после успешного выхода // Очищаем состояние
this.clearState(); this.isAuthenticated = false;
this.userId = null;
this.address = null;
this.isAdmin = false;
this.authType = null;
if (router) router.push('/'); // Очищаем локальное хранилище
localStorage.removeItem('auth');
} catch (error) { } catch (error) {
console.error('Error during logout:', error); console.error('Error during disconnect:', error);
} }
}, },
@@ -509,29 +492,23 @@ export const useAuthStore = defineStore('auth', {
setAuth(authData) { setAuth(authData) {
console.log('Setting auth state:', authData); console.log('Setting auth state:', authData);
// Обновляем все поля состояния // Обновляем только состояние в памяти
this.isAuthenticated = authData.authenticated || authData.isAuthenticated; this.isAuthenticated = authData.authenticated || authData.isAuthenticated;
this.user = {
id: authData.userId,
address: authData.address
};
this.userId = authData.userId; this.userId = authData.userId;
this.isAdmin = authData.isAdmin; this.isAdmin = authData.isAdmin;
this.authType = authData.authType; this.authType = authData.authType;
this.address = authData.address; this.address = authData.address;
// Сохраняем состояние в localStorage
const stateToSave = {
isAuthenticated: this.isAuthenticated,
userId: this.userId,
isAdmin: this.isAdmin,
authType: this.authType,
address: this.address
};
localStorage.setItem('auth', JSON.stringify(stateToSave));
console.log('Auth state updated:', { console.log('Auth state updated:', {
isAuthenticated: this.isAuthenticated, isAuthenticated: this.isAuthenticated,
userId: this.userId, userId: this.userId,
authType: this.authType, authType: this.authType,
address: this.address address: this.address,
isAdmin: this.isAdmin
}); });
} }
} }

View File

@@ -1,90 +0,0 @@
import { ethers } from 'ethers';
import axios from '../api/axios';
import { useAuthStore } from '../stores/auth';
// Переименовываем функцию для соответствия импорту
export async function connectWithWallet() {
try {
// Проверяем, доступен ли MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не установлен');
}
// Запрашиваем доступ к кошельку
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const address = await signer.getAddress();
// Получаем nonce для подписи
const nonceResponse = await axios.get(`/api/auth/nonce?address=${address}`);
const nonce = nonceResponse.data.nonce;
// Формируем сообщение для подписи
const message = `Подпишите это сообщение для аутентификации в DApp for Business. Nonce: ${nonce}`;
// Подписываем сообщение
const signature = await signer.signMessage(message);
// Верифицируем подпись на сервере
const response = await axios.post('/api/auth/verify', {
address,
signature,
message: nonce
});
console.log('Wallet verification response:', response.data);
// Обновляем состояние в хранилище auth
const authStore = useAuthStore();
authStore.isAuthenticated = response.data.authenticated;
authStore.user = {
id: response.data.userId,
address: response.data.address
};
authStore.isAdmin = response.data.isAdmin;
authStore.authType = 'wallet';
// Сохраняем адрес кошелька в локальном хранилище
localStorage.setItem('walletAddress', address);
return {
success: true,
authenticated: response.data.authenticated,
userId: response.data.userId,
address: response.data.address,
isAdmin: response.data.isAdmin,
authType: 'wallet'
};
} catch (error) {
console.error('Error connecting wallet:', error);
return { success: false, error: error.message };
}
}
async function disconnectWallet() {
try {
// Отправляем запрос на выход
await axios.post(
'/api/auth/logout',
{},
{
withCredentials: true,
}
);
// Удаляем адрес кошелька из локального хранилища
localStorage.removeItem('walletAddress');
// Обновляем состояние в хранилище auth
const authStore = useAuthStore();
authStore.isAuthenticated = false;
authStore.user = null;
authStore.isAdmin = false;
authStore.authType = null;
return { success: true };
} catch (error) {
console.error('Ошибка при отключении кошелька:', error);
throw error;
}
}

View File

@@ -5,24 +5,34 @@
<div class="auth-section" v-if="!auth.isAuthenticated"> <div class="auth-section" v-if="!auth.isAuthenticated">
<h3>Венчурный фонд и поставщик программного обеспечения</h3> <h3>Венчурный фонд и поставщик программного обеспечения</h3>
</div> </div>
<div class="chat-container"> <div class="chat-container">
<div class="chat-header"> <div class="chat-header">
<WalletConnection <!-- Используем тот же компонент, что и в сообщениях -->
:onWalletAuth="handleWalletAuth" <div v-if="!auth.isAuthenticated" class="auth-buttons">
:isAuthenticated="auth.isAuthenticated" <button class="auth-btn wallet-btn" @click="handleWalletAuth">
/> <span class="auth-icon">👛</span> Подключить кошелек
<div class="user-info" v-if="auth.isAuthenticated"> </button>
</div>
<div v-else class="wallet-info">
<span>{{ truncateAddress(auth.address) }}</span>
<button class="disconnect-btn" @click="disconnectWallet">
Отключить кошелек
</button>
</div> </div>
</div> </div>
<!-- Кнопка загрузки предыдущих сообщений --> <!-- Кнопка загрузки предыдущих сообщений -->
<div v-if="hasMoreMessages" class="load-more-container"> <div v-if="auth.isAuthenticated && hasMoreMessages" class="load-more">
<button @click="loadMoreMessages" class="load-more-btn" :disabled="isLoadingMore"> <button @click="loadMoreMessages" :disabled="isLoadingMore">
{{ isLoadingMore ? 'Загрузка...' : 'Показать предыдущие сообщения' }} {{ isLoadingMore ? 'Загрузка...' : 'Показать предыдущие сообщения' }}
</button> </button>
</div> </div>
<div class="chat-messages" ref="messagesContainer"> <div class="chat-messages" ref="messagesContainer">
<div v-if="isLoadingMore" class="loading">
Загрузка...
</div>
<div v-for="message in messages" :key="message.id" :class="['message', message.role === 'assistant' ? 'ai-message' : 'user-message']"> <div v-for="message in messages" :key="message.id" :class="['message', message.role === 'assistant' ? 'ai-message' : 'user-message']">
<div class="message-content"> <div class="message-content">
{{ message.content }} {{ message.content }}
@@ -39,32 +49,32 @@
<button class="auth-btn email-btn" @click="handleEmailAuth"> <button class="auth-btn email-btn" @click="handleEmailAuth">
<span class="auth-icon"></span> Подключить Email <span class="auth-icon"></span> Подключить Email
</button> </button>
</div> </div>
<!-- Email форма --> <!-- Email форма -->
<div v-if="showEmailForm" class="auth-form"> <div v-if="showEmailForm" class="auth-form">
<input <input
v-model="emailInput" v-model="emailInput"
type="email" type="email"
placeholder="Введите ваш email" placeholder="Введите ваш email"
class="auth-input" class="auth-input"
/> />
<button @click="submitEmail" class="auth-btn"> <button @click="submitEmail" class="auth-btn">
Отправить код Отправить код
</button> </button>
</div> </div>
<!-- Форма верификации email --> <!-- Форма верификации email -->
<div v-if="showEmailVerification" class="auth-form"> <div v-if="showEmailVerification" class="auth-form">
<input <input
v-model="emailCode" v-model="emailCode"
type="text" type="text"
placeholder="Введите код из email" placeholder="Введите код из email"
class="auth-input" class="auth-input"
/> />
<button @click="verifyEmailCode" class="auth-btn"> <button @click="verifyEmailCode" class="auth-btn">
Подтвердить Подтвердить
</button> </button>
</div> </div>
<!-- Telegram верификация --> <!-- Telegram верификация -->
@@ -77,8 +87,8 @@
/> />
<button @click="verifyTelegramCode" class="auth-btn"> <button @click="verifyTelegramCode" class="auth-btn">
Подтвердить Подтвердить
</button> </button>
</div> </div>
<div v-if="emailError" class="error-message"> <div v-if="emailError" class="error-message">
{{ emailError }} {{ emailError }}
@@ -106,17 +116,18 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, watch, nextTick } from 'vue'; import { ref, computed, onMounted, watch, nextTick, onBeforeUnmount } from 'vue';
import { useAuthStore } from '../stores/auth'; import { useAuthStore } from '../stores/auth';
import WalletConnection from '../components/WalletConnection.vue'; import WalletConnection from '../components/identity/WalletConnection.vue';
import TelegramConnect from '../components/TelegramConnect.vue'; import TelegramConnect from '../components/identity/TelegramConnect.vue';
import axios from '../api/axios'; import api from '../api/axios';
import { connectWithWallet } from '../utils/wallet'; import { connectWithWallet } from '../services/wallet';
console.log('HomeView.vue: Version with chat loaded'); console.log('HomeView.vue: Version with chat loaded');
const auth = useAuthStore(); const auth = useAuthStore();
const messages = ref([]); const messages = ref([]);
const guestMessages = ref([]);
const newMessage = ref(''); const newMessage = ref('');
const isLoading = ref(false); const isLoading = ref(false);
const messagesContainer = ref(null); const messagesContainer = ref(null);
@@ -124,7 +135,6 @@ const userLanguage = ref('ru');
const email = ref(''); const email = ref('');
const isValidEmail = ref(true); const isValidEmail = ref(true);
const hasShownAuthMessage = ref(false); const hasShownAuthMessage = ref(false);
const guestMessages = ref([]);
const hasShownAuthOptions = ref(false); const hasShownAuthOptions = ref(false);
// Email аутентификация // Email аутентификация
@@ -144,8 +154,10 @@ const emailError = ref('');
const PAGE_SIZE = 2; // Показываем только последнее сообщение и ответ const PAGE_SIZE = 2; // Показываем только последнее сообщение и ответ
const allMessages = ref([]); // Все загруженные сообщения const allMessages = ref([]); // Все загруженные сообщения
const currentPage = ref(1); // Текущая страница const currentPage = ref(1); // Текущая страница
const hasMoreMessages = ref(false); // Есть ли еще сообщения const hasMoreMessages = ref(true); // Есть ли еще сообщения
const isLoadingMore = ref(false); // Загружаются ли дополнительные сообщения const isLoadingMore = ref(false); // Загружаются ли дополнительные сообщения
const offset = ref(0);
const limit = ref(20);
// Вычисляемое свойство для отображаемых сообщений // Вычисляемое свойство для отображаемых сообщений
const displayedMessages = computed(() => { const displayedMessages = computed(() => {
@@ -153,48 +165,30 @@ const displayedMessages = computed(() => {
return allMessages.value.slice(startIndex); return allMessages.value.slice(startIndex);
}); });
// Функция загрузки истории чата // Функция для сокращения адреса кошелька
const loadChatHistory = async () => { const truncateAddress = (address) => {
try { if (!address) return '';
if (!auth.isAuthenticated || !auth.userId) { return `${address.slice(0, 6)}...${address.slice(-4)}`;
return; };
}
const response = await axios.get('/api/chat/history', { // Функция прокрутки к последнему сообщению
headers: { Authorization: `Bearer ${auth.address}` }, const scrollToBottom = () => {
params: { limit: PAGE_SIZE, offset: 0 } if (messagesContainer.value) {
}); messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight;
if (response.data.success) {
messages.value = response.data.messages.map(msg => ({
id: msg.id,
content: msg.content,
role: msg.role || (msg.sender_type === 'assistant' ? 'assistant' : 'user'),
timestamp: msg.created_at,
showAuthOptions: false
}));
hasMoreMessages.value = response.data.total > PAGE_SIZE;
await nextTick();
scrollToBottom();
}
} catch (error) {
console.error('Error loading chat history:', error);
} }
}; };
// Функция загрузки дополнительных сообщений // Загрузка сообщений
const loadMoreMessages = async () => { const loadMoreMessages = async () => {
if (isLoadingMore.value) return; if (!auth.isAuthenticated) return;
try { try {
isLoadingMore.value = true; isLoadingMore.value = true;
const offset = messages.value.length; const response = await api.get('/api/chat/history', {
params: {
const response = await axios.get('/api/chat/history', { limit: limit.value,
headers: { Authorization: `Bearer ${auth.address}` }, offset: offset.value
params: { limit: PAGE_SIZE, offset } }
}); });
if (response.data.success) { if (response.data.success) {
@@ -206,82 +200,79 @@ const loadMoreMessages = async () => {
showAuthOptions: false showAuthOptions: false
})); }));
messages.value = [...newMessages, ...messages.value]; messages.value = [...messages.value, ...newMessages];
hasMoreMessages.value = response.data.total > messages.value.length; hasMoreMessages.value = response.data.total > messages.value.length;
offset.value += newMessages.length;
} }
} catch (error) { } catch (error) {
console.error('Error loading more messages:', error); console.error('Error loading chat history:', error);
} finally { } finally {
isLoadingMore.value = false; isLoadingMore.value = false;
} }
}; };
// Функция прокрутки к последнему сообщению // Загружаем сообщения при изменении аутентификации
const scrollToBottom = () => { watch(() => auth.isAuthenticated, async (newValue) => {
if (messagesContainer.value) { if (newValue) {
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight; messages.value = [];
} offset.value = 0;
}; hasMoreMessages.value = true;
// Инициализация при монтировании try {
onMounted(async () => { // Сначала загружаем историю из messages
console.log('HomeView.vue: onMounted called'); await loadMoreMessages();
console.log('Auth state:', auth.isAuthenticated);
// Определяем язык // Связываем гостевые сообщения (копируем из guest_messages в messages)
const cyrillicPattern = /[а-яА-ЯёЁ]/; await api.post('/api/chat/link-guest-messages');
userLanguage.value = cyrillicPattern.test(newMessage.value) ? 'ru' : 'en'; console.log('Guest messages linked to authenticated user');
console.log('Detected language:', userLanguage.value);
// Если пользователь уже аутентифицирован, загружаем историю // Перезагружаем сообщения, чтобы получить все, включая перенесенные
if (auth.isAuthenticated && auth.userId) { messages.value = [];
console.log('User authenticated, loading chat history...'); offset.value = 0;
await loadChatHistory(); await loadMoreMessages();
await nextTick();
scrollToBottom();
} catch (linkError) {
console.error('Error linking guest messages:', linkError);
}
} else {
messages.value = [];
offset.value = 0;
hasMoreMessages.value = true;
} }
}); });
// Наблюдатель за изменением состояния аутентификации
watch(() => auth.isAuthenticated, async (newValue, oldValue) => {
console.log('Auth state changed in HomeView:', newValue);
if (newValue && auth.userId) {
// Пользователь только что аутентифицировался
await loadChatHistory();
} else {
// Пользователь вышел из системы
messages.value = []; // Очищаем историю сообщений
hasMoreMessages.value = false; // Сбрасываем флаг наличия дополнительных сообщений
console.log('Chat history cleared after logout');
}
}, { immediate: true });
// Функция для подключения кошелька // Функция для подключения кошелька
const handleWalletAuth = async () => { const handleWalletAuth = async () => {
try { try {
const result = await connectWithWallet(); const result = await connectWithWallet();
if (result.success) { if (result.success) {
console.log('Wallet auth result:', result); // Сохраняем гостевые сообщения перед очисткой
const guestMessages = [...messages.value];
messages.value = [];
offset.value = 0;
hasMoreMessages.value = true;
// Обновляем состояние аутентификации try {
auth.setAuth({ await api.post('/api/chat/link-guest-messages');
authenticated: true, console.log('Guest messages linked to authenticated user');
isAuthenticated: true, await loadMoreMessages();
userId: result.userId,
address: result.address,
isAdmin: result.isAdmin,
authType: 'wallet'
});
// Добавляем задержку для синхронизации сессии const filteredGuestMessages = guestMessages
await new Promise(resolve => setTimeout(resolve, 1000)); .filter(msg => !msg.showAuthButtons)
.reverse();
messages.value = [...messages.value, ...filteredGuestMessages];
// Загружаем историю чата await nextTick();
await loadChatHistory(); scrollToBottom();
} catch (linkError) {
console.error('Error linking guest messages:', linkError);
}
} }
return result;
} catch (error) { } catch (error) {
console.error('Error connecting wallet:', error); console.error('Error connecting wallet:', error);
throw error;
} }
}; };
@@ -295,7 +286,7 @@ const saveGuestMessagesToServer = async () => {
// Отправляем каждое сообщение на сервер // Отправляем каждое сообщение на сервер
for (const msg of userMessages) { for (const msg of userMessages) {
await axios.post('/api/chat/message', { await api.post('/api/chat/message', {
message: msg.content, message: msg.content,
language: userLanguage.value language: userLanguage.value
}); });
@@ -311,7 +302,7 @@ const saveGuestMessagesToServer = async () => {
async function connectTelegram() { async function connectTelegram() {
try { try {
// Отправляем запрос на получение ссылки для авторизации через Telegram // Отправляем запрос на получение ссылки для авторизации через Telegram
const response = await axios.get('/api/auth/telegram', { const response = await api.get('/api/auth/telegram', {
withCredentials: true withCredentials: true
}); });
@@ -376,7 +367,7 @@ async function requestEmailCode() {
// Функция проверки кода // Функция проверки кода
const verifyEmailCode = async () => { const verifyEmailCode = async () => {
try { try {
const response = await axios.post('/api/auth/email/verify-code', { const response = await api.post('/api/auth/email/verify-code', {
email: emailInput.value, email: emailInput.value,
code: emailCode.value code: emailCode.value
}); });
@@ -387,7 +378,7 @@ const verifyEmailCode = async () => {
emailError.value = ''; emailError.value = '';
// Загружаем историю чата после успешной аутентификации // Загружаем историю чата после успешной аутентификации
await loadChatHistory(); await loadMoreMessages();
} else { } else {
emailError.value = response.data.error || 'Неверный код'; emailError.value = response.data.error || 'Неверный код';
} }
@@ -404,12 +395,6 @@ function cancelEmailVerification() {
emailErrorMessage.value = ''; emailErrorMessage.value = '';
} }
// Добавьте эту функцию в <script setup>
const formatAddress = (address) => {
if (!address) return '';
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
};
// Форматирование времени // Форматирование времени
const formatTime = (timestamp) => { const formatTime = (timestamp) => {
if (!timestamp) return ''; if (!timestamp) return '';
@@ -438,101 +423,73 @@ const formatTime = (timestamp) => {
}; };
// Функция для отправки сообщения // Функция для отправки сообщения
const handleMessage = async (messageText) => { const handleMessage = async (text) => {
if (!messageText.trim() || isLoading.value) return;
console.log('Handling message:', messageText);
isLoading.value = true;
try { try {
const messageContent = text.trim();
if (!messageContent) return;
newMessage.value = '';
isLoading.value = true;
if (!auth.isAuthenticated) { if (!auth.isAuthenticated) {
await sendGuestMessage(messageText); // Сохраняем в таблицу guest_messages
const response = await api.post('/api/chat/guest-message', {
message: messageContent,
language: userLanguage.value
});
if (response.data.success) {
const userMessage = {
id: response.data.messageId,
content: messageContent,
role: 'user',
timestamp: new Date().toISOString(),
showAuthButtons: false
};
messages.value.push(userMessage);
messages.value.push({
id: Date.now() + 1,
content: 'Для получения ответа от ассистента, пожалуйста, авторизуйтесь одним из способов:',
role: 'assistant',
timestamp: new Date().toISOString(),
showAuthButtons: true
});
}
} else { } else {
await sendMessage(messageText); // Для авторизованного пользователя сохраняем в messages
const response = await api.post('/api/chat/message', {
message: messageContent,
language: userLanguage.value
});
if (response.data.success) {
const message = {
id: response.data.messageId,
content: messageContent,
role: 'user',
timestamp: new Date().toISOString(),
hasResponse: true
};
messages.value.push(message);
const aiMessage = {
id: response.data.aiMessageId,
content: response.data.message,
role: 'assistant',
timestamp: new Date().toISOString()
};
messages.value.push(aiMessage);
}
} }
} catch (error) { } catch (error) {
console.error('Error handling message:', error); console.error('Error sending message:', error);
messages.value.push({ messages.value.push({
id: Date.now(), id: Date.now(),
content: 'Произошла ошибка при отправке сообщения.', content: 'Произошла ошибка при отправке сообщения.',
role: 'assistant', role: 'assistant',
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} finally {
newMessage.value = '';
isLoading.value = false;
}
};
// Функция для отправки сообщения аутентифицированного пользователя
const sendMessage = async (messageText) => {
try {
const userMessage = {
id: Date.now(),
content: messageText,
role: 'user',
timestamp: new Date().toISOString()
};
messages.value.push(userMessage);
const response = await axios.post('/api/chat/message', {
message: messageText,
language: userLanguage.value
});
if (response.data.success) {
messages.value.push({
id: Date.now() + 1,
content: response.data.message,
role: 'assistant',
timestamp: new Date().toISOString()
});
}
} catch (error) {
console.error('Error sending message:', error);
}
};
// Функция для отправки гостевого сообщения
const sendGuestMessage = async (messageText) => {
try {
// Добавляем сообщение пользователя
const userMessage = {
id: Date.now(),
content: messageText,
role: 'user',
timestamp: new Date().toISOString(),
showAuthButtons: false
};
messages.value.push(userMessage);
// Очищаем поле ввода
newMessage.value = '';
// Сохраняем сообщение на сервере без получения ответа от Ollama
await axios.post('/api/chat/guest-message', {
message: messageText,
language: userLanguage.value
});
// Добавляем сообщение с кнопками аутентификации
messages.value.push({
id: Date.now() + 1,
content: 'Для получения ответа, пожалуйста, авторизуйтесь одним из способов:',
role: 'assistant',
timestamp: new Date().toISOString(),
showAuthButtons: true
});
} catch (error) {
console.error('Error sending guest message:', error);
messages.value.push({
id: Date.now() + 2,
content: 'Произошла ошибка. Пожалуйста, попробуйте позже.',
role: 'assistant',
timestamp: new Date().toISOString(),
showAuthButtons: true
});
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
@@ -554,7 +511,7 @@ const handleEmailAuth = async () => {
// Функция отправки email // Функция отправки email
const submitEmail = async () => { const submitEmail = async () => {
try { try {
const response = await axios.post('/api/auth/email/request', { const response = await api.post('/api/auth/email/request', {
email: emailInput.value email: emailInput.value
}); });
@@ -573,7 +530,7 @@ const submitEmail = async () => {
// Функция верификации кода Telegram // Функция верификации кода Telegram
const verifyTelegramCode = async () => { const verifyTelegramCode = async () => {
try { try {
const response = await axios.post('/api/auth/telegram/verify', { const response = await api.post('/api/auth/telegram/verify', {
code: telegramCode.value code: telegramCode.value
}); });
@@ -602,7 +559,7 @@ const verifyTelegramCode = async () => {
// Загружаем историю чата после небольшой задержки // Загружаем историю чата после небольшой задержки
setTimeout(async () => { setTimeout(async () => {
await loadChatHistory(); await loadMoreMessages();
}, 100); }, 100);
} else { } else {
messages.value.push({ messages.value.push({
@@ -622,6 +579,43 @@ const verifyTelegramCode = async () => {
}); });
} }
}; };
const disconnectWallet = async () => {
try {
await auth.disconnect();
messages.value = [];
offset.value = 0;
hasMoreMessages.value = true;
} catch (error) {
console.error('Error disconnecting wallet:', error);
}
};
// Обработка прокрутки
const handleScroll = async () => {
const element = messagesContainer.value;
if (
!isLoadingMore.value &&
hasMoreMessages.value &&
element.scrollTop === 0
) {
await loadMoreMessages();
}
};
onMounted(() => {
// Добавляем слушатель прокрутки
if (messagesContainer.value) {
messagesContainer.value.addEventListener('scroll', handleScroll);
}
});
onBeforeUnmount(() => {
// Удаляем слушатель
if (messagesContainer.value) {
messagesContainer.value.removeEventListener('scroll', handleScroll);
}
});
</script> </script>
<style scoped> <style scoped>
@@ -665,64 +659,49 @@ h1 {
} }
.chat-header { .chat-header {
padding: 1rem;
border-bottom: 1px solid #ddd;
background-color: #f8f9fa;
display: flex; display: flex;
justify-content: space-between; justify-content: flex-end;
align-items: center; align-items: center;
padding: 10px 20px;
background-color: #f0f0f0;
border-bottom: 1px solid #ccc;
} }
/* Адаптивный заголовок чата */ .wallet-info {
@media (max-width: 768px) {
.chat-header {
padding: 8px 12px;
}
.chat-header h2 {
font-size: 1.2rem;
margin: 0;
}
}
.user-info {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 1rem;
font-size: 0.9rem;
} }
/* Адаптивная информация о пользователе */ .disconnect-btn {
@media (max-width: 768px) { padding: 0.5rem 1rem;
.user-info { background-color: #ff4444;
font-size: 0.7rem;
gap: 5px;
}
.user-info span {
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.logout-btn {
padding: 5px 10px;
background-color: #f44336;
color: white; color: white;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 0.9rem;
} }
/* Адаптивная кнопка выхода */ .disconnect-btn:hover {
@media (max-width: 768px) { background-color: #cc0000;
.logout-btn { }
padding: 4px 8px;
font-size: 0.8rem; .load-more {
} text-align: center;
padding: 1rem;
}
.load-more button {
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.load-more button:hover {
background-color: #0056b3;
} }
.chat-messages { .chat-messages {
@@ -915,32 +894,27 @@ h1 {
} }
.auth-btn { .auth-btn {
display: flex; padding: 8px 16px;
align-items: center; border: none;
justify-content: center;
padding: 0.75rem 1rem;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-size: 1rem; font-size: 14px;
border: none; display: flex;
width: 100%; align-items: center;
font-weight: 500; gap: 8px;
transition: opacity 0.2s;
box-sizing: border-box;
} }
.auth-btn:hover { .wallet-btn {
opacity: 0.9; background-color: #4a5568;
color: white;
} }
.auth-btn:disabled { .wallet-btn:hover {
opacity: 0.7; background-color: #2d3748;
cursor: not-allowed;
} }
.auth-icon { .auth-icon {
margin-right: 0.75rem; font-size: 16px;
font-size: 1.2rem;
} }
.telegram-btn { .telegram-btn {
@@ -1080,4 +1054,48 @@ h1 {
background-color: #cbd5e0; background-color: #cbd5e0;
cursor: not-allowed; cursor: not-allowed;
} }
.wallet-section {
margin-top: 20px;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
background-color: #f9f9f9;
}
.wallet-info {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.disconnect-btn {
padding: 0.5rem 1rem;
background-color: #ff4444;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.disconnect-btn:hover {
background-color: #cc0000;
}
.chat-history {
height: 60vh;
overflow-y: auto;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 1rem;
}
/* Добавим индикатор загрузки */
.loading {
text-align: center;
padding: 1rem;
color: #666;
}
</style> </style>

View File

@@ -1,138 +0,0 @@
<template>
<div class="profile">
<h1>Профиль пользователя</h1>
<div class="profile-info">
<div class="profile-section">
<h2>Основная информация</h2>
<div v-if="loading">Загрузка...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<p><strong>ID:</strong> {{ profile.id }}</p>
<p><strong>Имя пользователя:</strong> {{ profile.username || 'Не указано' }}</p>
<p><strong>Роль:</strong> {{ profile.role === 'admin' ? 'Администратор' : 'Пользователь' }}</p>
<p><strong>Язык интерфейса:</strong>
<select v-model="selectedLanguage" @change="updateLanguage">
<option value="ru">Русский</option>
<option value="en">English</option>
</select>
</p>
</div>
</div>
<div class="profile-section">
<h2>Связанные аккаунты</h2>
<LinkedAccounts />
</div>
<div class="profile-section" v-if="isAdmin">
<h2>Управление ролями</h2>
<RoleManager />
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import LinkedAccounts from '../components/LinkedAccounts.vue';
import RoleManager from '../components/RoleManager.vue';
export default {
components: {
LinkedAccounts,
RoleManager
},
setup() {
const profile = ref({});
const loading = ref(true);
const error = ref(null);
const selectedLanguage = ref('ru');
const isAdmin = ref(false);
// Загрузка профиля пользователя
const loadProfile = async () => {
try {
loading.value = true;
const response = await axios.get('/api/users/profile', {
withCredentials: true
});
profile.value = response.data;
selectedLanguage.value = response.data.preferred_language || 'ru';
isAdmin.value = response.data.role === 'admin';
} catch (err) {
console.error('Error loading profile:', err);
error.value = 'Ошибка при загрузке профиля';
} finally {
loading.value = false;
}
};
// Обновление языка пользователя
const updateLanguage = async () => {
try {
await axios.post('/api/users/update-language', {
language: selectedLanguage.value
}, {
withCredentials: true
});
// Обновляем язык в профиле
profile.value.preferred_language = selectedLanguage.value;
} catch (err) {
console.error('Error updating language:', err);
error.value = 'Ошибка при обновлении языка';
}
};
onMounted(async () => {
await loadProfile();
});
return {
profile,
loading,
error,
selectedLanguage,
isAdmin,
updateLanguage
};
}
};
</script>
<style scoped>
.profile {
padding: 20px;
}
.profile-info {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
@media (min-width: 768px) {
.profile-info {
grid-template-columns: repeat(2, 1fr);
}
}
.profile-section {
background-color: #f5f5f5;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
h1, h2 {
margin-top: 0;
}
select {
padding: 5px;
border-radius: 3px;
border: 1px solid #ccc;
}
</style>

View File

@@ -1,75 +0,0 @@
<template>
<div class="token-access">
<h1>Настройка прав доступа</h1>
<div>
<label for="blockchain">Выберите блокчейн:</label>
<select v-model="selectedBlockchain" id="blockchain">
<option v-for="(blockchain, index) in blockchains" :key="index" :value="blockchain.value">
{{ blockchain.name }}
</option>
</select>
</div>
<form @submit.prevent="checkTokenBalance">
<div>
<label for="contractAddress">Адрес смарт-контракта:</label>
<input v-model="contractAddress" id="contractAddress" placeholder="Введите адрес смарт-контракта" required />
</div>
<div>
<label for="requiredAmount">Объем токенов:</label>
<input v-model="requiredAmount" type="number" id="requiredAmount" placeholder="Введите объем токенов" required />
</div>
<button type="submit">Проверить баланс</button>
</form>
<p v-if="message">{{ message }}</p>
</div>
</template>
<script>
export default {
data() {
return {
selectedBlockchain: 'mainnet', // Значение по умолчанию
blockchains: [
{ name: 'Ethereum', value: 'mainnet' },
{ name: 'Polygon', value: 'polygon' },
{ name: 'Binance Smart Chain', value: 'bsc' },
{ name: 'Arbitrum', value: 'arbitrum' }
],
contractAddress: '',
requiredAmount: '',
message: ''
};
},
methods: {
async checkTokenBalance() {
const response = await fetch(`/api/admin/check-balance`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
walletAddress: this.$store.state.walletAddress, // Предполагается, что адрес кошелька хранится в хранилище
contractAddress: this.contractAddress,
requiredAmount: this.requiredAmount,
blockchain: this.selectedBlockchain
})
});
const data = await response.json();
this.message = data.message;
}
}
};
</script>
<style scoped>
.token-access {
max-width: 400px;
margin: auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
background-color: #f9f9f9;
}
</style>

View File

@@ -38,11 +38,15 @@ export default defineConfig({
}, },
server: { server: {
port: 5173, port: 5173,
host: 'localhost',
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://localhost:8000',
changeOrigin: true changeOrigin: true,
secure: false,
credentials: true,
rewrite: (path) => path
} }
}, }
}, },
}); });