Тестовый коммит после удаления husky
This commit is contained in:
7
backend/.prettierrc
Normal file
7
backend/.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100
|
||||
}
|
||||
251
backend/app.js
251
backend/app.js
@@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const session = require('express-session');
|
||||
const { verifySignature } = require('./utils/auth');
|
||||
const { verifySignature, findOrCreateUser } = require('./utils/auth');
|
||||
const pgSession = require('connect-pg-simple')(session);
|
||||
const { requireRole } = require('./middleware/auth');
|
||||
const crypto = require('crypto');
|
||||
@@ -9,6 +9,13 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { router: authRouter } = require('./routes/auth');
|
||||
const { pool } = require('./db');
|
||||
const FileStore = require('session-file-store')(session);
|
||||
const helmet = require('helmet');
|
||||
const sessionMiddleware = require('./middleware/session');
|
||||
const chatRouter = require('./routes/chat');
|
||||
const usersRoutes = require('./routes/users');
|
||||
const contractsRoutes = require('./routes/contracts');
|
||||
const rolesRoutes = require('./routes/roles');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -20,74 +27,88 @@ function generateNonce() {
|
||||
// Парсинг JSON - должен быть до всех роутов
|
||||
app.use(express.json());
|
||||
|
||||
// Настройка CORS - должна быть первой после парсинга JSON
|
||||
app.use(cors({
|
||||
origin: ['http://127.0.0.1:5173', 'http://localhost:5173'],
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'OPTIONS', 'DELETE', 'PUT', 'HEAD'],
|
||||
allowedHeaders: [
|
||||
'Content-Type',
|
||||
'X-Wallet-Address',
|
||||
'X-Wallet-Signature',
|
||||
'Cookie',
|
||||
'Authorization'
|
||||
],
|
||||
exposedHeaders: ['Set-Cookie']
|
||||
}));
|
||||
// Настройка CORS
|
||||
app.use(
|
||||
cors({
|
||||
origin: function(origin, callback) {
|
||||
// Разрешаем запросы с любого источника в режиме разработки
|
||||
const allowedOrigins = ['http://localhost:3000', 'http://127.0.0.1:5173', 'http://localhost:5173'];
|
||||
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Not allowed by CORS'));
|
||||
}
|
||||
},
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Auth-Nonce'],
|
||||
})
|
||||
);
|
||||
|
||||
// Настройка сессий
|
||||
app.use(session({
|
||||
store: new pgSession({
|
||||
pool: pool,
|
||||
tableName: 'session',
|
||||
createTableIfMissing: true
|
||||
}),
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 часа
|
||||
}
|
||||
}));
|
||||
app.use(
|
||||
session({
|
||||
store: new pgSession({
|
||||
pool: pool,
|
||||
tableName: 'sessions',
|
||||
createTableIfMissing: false,
|
||||
}),
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
name: 'dapp.sid',
|
||||
cookie: {
|
||||
secure: false,
|
||||
httpOnly: true,
|
||||
maxAge: 30 * 24 * 60 * 60 * 1000,
|
||||
sameSite: 'lax',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Middleware для логирования сессий
|
||||
app.use((req, res, next) => {
|
||||
// Восстанавливаем сессию из store если есть sessionID
|
||||
if (req.sessionID && !req.session.authenticated) {
|
||||
req.sessionStore.get(req.sessionID, (err, session) => {
|
||||
if (err) {
|
||||
console.error('Session restore error:', err);
|
||||
} else if (session) {
|
||||
req.session.authenticated = session.authenticated;
|
||||
req.session.address = session.address;
|
||||
req.session.lastSignature = session.lastSignature;
|
||||
}
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
// Middleware для безопасности
|
||||
app.use(
|
||||
helmet({
|
||||
contentSecurityPolicy: false, // Отключаем CSP для разработки
|
||||
})
|
||||
);
|
||||
|
||||
// Middleware для логирования
|
||||
app.use((req, res, next) => {
|
||||
console.log(`${req.method} ${req.path}`, {
|
||||
headers: req.headers,
|
||||
body: req.body,
|
||||
session: req.session
|
||||
session: req.session,
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
||||
// Middleware для сохранения сессии после каждого запроса
|
||||
app.use((req, res, next) => {
|
||||
const originalEnd = res.end;
|
||||
|
||||
res.end = function () {
|
||||
if (req.session && req.session.save) {
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при сохранении сессии:', err);
|
||||
}
|
||||
originalEnd.apply(res, arguments);
|
||||
});
|
||||
} else {
|
||||
originalEnd.apply(res, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Middleware для проверки авторизации
|
||||
const requireAuth = (req, res, next) => {
|
||||
console.log('Auth check:', {
|
||||
session: req.session,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address
|
||||
address: req.session.address,
|
||||
});
|
||||
|
||||
if (!req.session.authenticated || !req.session.address) {
|
||||
@@ -96,9 +117,68 @@ const requireAuth = (req, res, next) => {
|
||||
next();
|
||||
};
|
||||
|
||||
// Миддлвар для обновления дополнительных полей в таблице sessions
|
||||
app.use(async (req, res, next) => {
|
||||
try {
|
||||
// Если есть адрес, но нет userId, найдем или создадим пользователя
|
||||
if (req.session && req.session.authenticated && req.session.address && !req.session.userId) {
|
||||
try {
|
||||
const user = await findOrCreateUser(req.session.address, 'wallet');
|
||||
req.session.userId = user.id;
|
||||
req.session.role = user.role;
|
||||
req.session.isAdmin = user.is_admin;
|
||||
|
||||
// Стандартизируем данные сессии
|
||||
standardizeSessionData(req);
|
||||
|
||||
// Сохраняем обновленную сессию
|
||||
await new Promise((resolve, reject) => {
|
||||
req.session.save(err => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Ошибка при обновлении userId в сессии:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем поля в таблице sessions
|
||||
if (req.session && (req.session.userId || req.session.address)) {
|
||||
db.query(
|
||||
'UPDATE sessions SET last_activity = NOW(), user_id = $1, auth_channel = $2, language = $3 WHERE sid = $4',
|
||||
[
|
||||
req.session.userId || null,
|
||||
req.session.authChannel || 'web',
|
||||
req.session.language || 'en',
|
||||
req.sessionID
|
||||
]
|
||||
).catch(err => console.error('Error updating session data:', err));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка в middleware сессий:', err);
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
// Функция для стандартизации данных сессии
|
||||
function standardizeSessionData(req) {
|
||||
if (req.session.authenticated) {
|
||||
// Убедимся, что все необходимые поля присутствуют
|
||||
req.session.authType = req.session.authType || 'wallet';
|
||||
req.session.role = req.session.role || 'USER';
|
||||
req.session.isAdmin = !!req.session.isAdmin;
|
||||
req.session.authChannel = req.session.authChannel || 'web';
|
||||
req.session.language = req.session.language || 'en';
|
||||
}
|
||||
}
|
||||
|
||||
// API роуты
|
||||
const apiRouter = express.Router();
|
||||
|
||||
// Удалите или закомментируйте этот блок кода
|
||||
/*
|
||||
apiRouter.post('/refresh-session', async (req, res) => {
|
||||
try {
|
||||
const { address, signature } = req.body;
|
||||
@@ -143,17 +223,18 @@ apiRouter.post('/refresh-session', async (req, res) => {
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
apiRouter.get('/session', (req, res) => {
|
||||
console.log('Session check:', {
|
||||
session: req.session,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address
|
||||
address: req.session.address,
|
||||
});
|
||||
|
||||
|
||||
res.json({
|
||||
authenticated: !!req.session.authenticated,
|
||||
address: req.session.address || null
|
||||
address: req.session.address || null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -180,12 +261,8 @@ apiRouter.get('/admin/check', async (req, res) => {
|
||||
const ethers = require('ethers');
|
||||
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||||
const contractABI = require('./artifacts/contracts/MyContract.sol/MyContract.json').abi;
|
||||
const contract = new ethers.Contract(
|
||||
process.env.CONTRACT_ADDRESS,
|
||||
contractABI,
|
||||
provider
|
||||
);
|
||||
|
||||
const contract = new ethers.Contract(process.env.CONTRACT_ADDRESS, contractABI, provider);
|
||||
|
||||
const contractOwner = await contract.owner();
|
||||
const isAdmin = req.session.address.toLowerCase() === contractOwner.toLowerCase();
|
||||
|
||||
@@ -216,48 +293,51 @@ apiRouter.post('/verify', async (req, res) => {
|
||||
try {
|
||||
console.log('Получен запрос на верификацию в app.js:', req.body);
|
||||
const { message, signature } = req.body;
|
||||
|
||||
|
||||
if (!message || !signature) {
|
||||
console.error('Отсутствуют необходимые поля:', { message: !!message, signature: !!signature });
|
||||
console.error('Отсутствуют необходимые поля:', {
|
||||
message: !!message,
|
||||
signature: !!signature,
|
||||
});
|
||||
return res.status(400).json({ success: false, error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что message содержит адрес
|
||||
if (!message.address) {
|
||||
console.error('Отсутствует адрес в сообщении');
|
||||
return res.status(400).json({ success: false, error: 'Missing address in message' });
|
||||
}
|
||||
|
||||
|
||||
// Получаем адрес из сообщения напрямую
|
||||
const address = message.address;
|
||||
console.log('Адрес из сообщения:', address);
|
||||
|
||||
|
||||
// Устанавливаем сессию без проверки подписи
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address;
|
||||
req.session.lastSignature = signature;
|
||||
|
||||
|
||||
// Сохраняем сессию
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при сохранении сессии:', err);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Session save failed',
|
||||
message: err.message
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'Session save failed',
|
||||
message: err.message,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log('Сессия сохранена успешно:', req.sessionID);
|
||||
return res.json({ success: true, address, isAdmin: true });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Подробная ошибка при верификации:', error.stack);
|
||||
console.error('Verification error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Verification failed',
|
||||
message: error.message || 'Unknown error'
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Verification failed',
|
||||
message: error.message || 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -267,27 +347,34 @@ app.use('/api', apiRouter);
|
||||
|
||||
// Подключаем маршруты аутентификации
|
||||
app.use('/api/auth', authRouter);
|
||||
app.use('/api/users', usersRoutes);
|
||||
app.use('/api/contracts', contractsRoutes);
|
||||
app.use('/api/chat', chatRouter);
|
||||
app.use('/api/roles', rolesRoutes);
|
||||
|
||||
apiRouter.get('/nonce', (req, res) => {
|
||||
const nonce = generateNonce();
|
||||
console.log('Generated new nonce:', nonce);
|
||||
|
||||
|
||||
// Сохраняем nonce в сессии
|
||||
if (!req.session.nonces) {
|
||||
req.session.nonces = [];
|
||||
}
|
||||
req.session.nonces.push(nonce);
|
||||
|
||||
|
||||
// Ограничиваем количество nonce в сессии
|
||||
if (req.session.nonces.length > 5) {
|
||||
req.session.nonces.shift();
|
||||
}
|
||||
|
||||
|
||||
console.log('Nonces in session:', req.session.nonces);
|
||||
|
||||
|
||||
res.json({ nonce });
|
||||
});
|
||||
|
||||
// Добавьте после настройки сессий
|
||||
app.use(sessionMiddleware);
|
||||
|
||||
// Обработка ошибок сессий
|
||||
app.use((err, req, res, next) => {
|
||||
if (err.code === 'ENOENT' && err.message.includes('sessions')) {
|
||||
@@ -311,4 +398,8 @@ app.use((err, req, res, next) => {
|
||||
res.status(500).json({ error: 'Something broke!' });
|
||||
});
|
||||
|
||||
module.exports = { app };
|
||||
// Подключаем маршруты для отладки
|
||||
const debugRouter = require('./routes/debug');
|
||||
app.use('/api/debug', debugRouter);
|
||||
|
||||
module.exports = { app };
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Пример документа для RAG
|
||||
|
||||
Это пример документа, который будет использоваться в RAG системе.
|
||||
|
||||
## Блокчейн
|
||||
|
||||
Блокчейн - это распределенная база данных, которая хранит информацию о всех транзакциях участников системы в виде "цепочки блоков". Каждый блок содержит набор транзакций и ссылку на предыдущий блок.
|
||||
|
||||
## Смарт-контракты
|
||||
|
||||
Смарт-контракты - это программы, которые автоматически выполняются при соблюдении определенных условий. Они работают на блокчейне и могут автоматизировать выполнение соглашений.
|
||||
|
||||
## Web3
|
||||
|
||||
Web3 - это новое поколение интернета, основанное на блокчейне и децентрализованных технологиях. Оно позволяет пользователям контролировать свои данные и взаимодействовать без посредников.
|
||||
|
||||
@@ -4,7 +4,7 @@ require('dotenv').config();
|
||||
// Создаем пул соединений с базой данных
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
// Проверяем подключение к базе данных
|
||||
@@ -12,7 +12,7 @@ pool.query('SELECT NOW()', (err, res) => {
|
||||
if (err) {
|
||||
console.error('Ошибка подключения к базе данных:', err);
|
||||
console.log('Переключение на временное хранилище данных в памяти...');
|
||||
|
||||
|
||||
// Если не удалось подключиться к базе данных, используем временное хранилище
|
||||
module.exports = createInMemoryStorage();
|
||||
} else {
|
||||
@@ -28,53 +28,53 @@ const query = (text, params) => {
|
||||
// Экспортируем функции для работы с базой данных
|
||||
module.exports = {
|
||||
query,
|
||||
pool
|
||||
pool,
|
||||
};
|
||||
|
||||
// Функция для создания временного хранилища данных в памяти
|
||||
function createInMemoryStorage() {
|
||||
console.log('Используется временное хранилище данных в памяти');
|
||||
|
||||
|
||||
const users = [];
|
||||
let userId = 1;
|
||||
|
||||
|
||||
// Эмуляция функции query для работы с пользователями
|
||||
const inMemoryQuery = async (text, params) => {
|
||||
console.log('SQL query (in-memory):', text, 'Params:', params);
|
||||
|
||||
|
||||
// Эмуляция запроса SELECT * FROM users WHERE address = $1
|
||||
if (text.includes('SELECT * FROM users WHERE address = $1')) {
|
||||
const address = params[0];
|
||||
const user = users.find(u => u.address === address);
|
||||
const user = users.find((u) => u.address === address);
|
||||
return { rows: user ? [user] : [] };
|
||||
}
|
||||
|
||||
|
||||
// Эмуляция запроса SELECT * FROM users WHERE email = $1
|
||||
if (text.includes('SELECT * FROM users WHERE email = $1')) {
|
||||
const email = params[0];
|
||||
const user = users.find(u => u.email === email);
|
||||
const user = users.find((u) => u.email === email);
|
||||
return { rows: user ? [user] : [] };
|
||||
}
|
||||
|
||||
|
||||
// Эмуляция запроса INSERT INTO users
|
||||
if (text.includes('INSERT INTO users')) {
|
||||
let newUser;
|
||||
|
||||
|
||||
if (text.includes('address')) {
|
||||
newUser = { id: userId++, address: params[0], created_at: new Date(), is_admin: false };
|
||||
} else if (text.includes('email')) {
|
||||
newUser = { id: userId++, email: params[0], created_at: new Date(), is_admin: false };
|
||||
}
|
||||
|
||||
|
||||
if (newUser) {
|
||||
users.push(newUser);
|
||||
return { rows: [newUser] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return { rows: [] };
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
query: inMemoryQuery,
|
||||
pool: {
|
||||
@@ -89,7 +89,42 @@ function createInMemoryStorage() {
|
||||
} else {
|
||||
return inMemoryQuery(text, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка и создание таблицы session, если она не существует
|
||||
async function checkSessionTable() {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'session'
|
||||
);
|
||||
`);
|
||||
|
||||
const tableExists = result.rows[0].exists;
|
||||
|
||||
if (!tableExists) {
|
||||
console.log('Таблица session не существует, создаем...');
|
||||
|
||||
await pool.query(`
|
||||
CREATE TABLE "session" (
|
||||
"sid" varchar NOT NULL COLLATE "default",
|
||||
"sess" json NOT NULL,
|
||||
"expire" timestamp(6) NOT NULL,
|
||||
CONSTRAINT "session_pkey" PRIMARY KEY ("sid")
|
||||
);
|
||||
CREATE INDEX "IDX_session_expire" ON "session" ("expire");
|
||||
`);
|
||||
|
||||
console.log('Таблица session успешно создана');
|
||||
} else {
|
||||
console.log('Таблица session уже существует');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке/создании таблицы session:', error);
|
||||
}
|
||||
}
|
||||
|
||||
9
backend/docs/api.md
Normal file
9
backend/docs/api.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# API Documentation
|
||||
|
||||
## Authentication
|
||||
|
||||
### POST /api/auth/refresh-session
|
||||
|
||||
Refreshes the user session.
|
||||
|
||||
**Request:**
|
||||
30
backend/eslint.config.js
Normal file
30
backend/eslint.config.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import globals from 'globals';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['node_modules/**', 'artifacts/**', 'sessions/**', 'logs/**', 'data/**'],
|
||||
},
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
globals: {
|
||||
...globals.node,
|
||||
...globals.es2021,
|
||||
describe: 'readonly',
|
||||
it: 'readonly',
|
||||
beforeEach: 'readonly',
|
||||
before: 'readonly',
|
||||
after: 'readonly',
|
||||
afterEach: 'readonly',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'no-console': 'off',
|
||||
'no-undef': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -1,12 +1,12 @@
|
||||
require("@nomicfoundation/hardhat-toolbox");
|
||||
require('@nomicfoundation/hardhat-toolbox');
|
||||
require('dotenv').config();
|
||||
|
||||
module.exports = {
|
||||
solidity: "0.8.20",
|
||||
solidity: '0.8.20',
|
||||
networks: {
|
||||
sepolia: {
|
||||
url: process.env.ETHEREUM_NETWORK_URL,
|
||||
accounts: [process.env.PRIVATE_KEY]
|
||||
}
|
||||
}
|
||||
};
|
||||
accounts: [process.env.PRIVATE_KEY],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{"level":"info","message":"Running scheduled token balance check","timestamp":"2025-03-04T20:30:00.970Z"}
|
||||
{"level":"info","message":"Checking token balances for 1 users","timestamp":"2025-03-04T20:30:00.982Z"}
|
||||
{"level":"error","message":"Ошибка при получении контракта AccessToken: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T20:30:00.983Z"}
|
||||
{"level":"error","message":"Error checking token balance for 0x0000000000000000000000000000000000000000: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T20:30:00.983Z"}
|
||||
{"level":"info","message":"User 10 with address 0x0000000000000000000000000000000000000000: admin=false","timestamp":"2025-03-04T20:30:00.984Z"}
|
||||
{"level":"info","message":"Running scheduled token balance check","timestamp":"2025-03-04T21:30:00.777Z"}
|
||||
{"level":"info","message":"Checking token balances for 1 users","timestamp":"2025-03-04T21:30:00.793Z"}
|
||||
{"level":"error","message":"Ошибка при получении контракта AccessToken: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T21:30:00.794Z"}
|
||||
{"level":"error","message":"Error checking token balance for 0x0000000000000000000000000000000000000000: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T21:30:00.795Z"}
|
||||
{"level":"info","message":"User 10 with address 0x0000000000000000000000000000000000000000: admin=false","timestamp":"2025-03-04T21:30:00.795Z"}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{"level":"error","message":"Ошибка при получении контракта AccessToken: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T20:30:00.983Z"}
|
||||
{"level":"error","message":"Error checking token balance for 0x0000000000000000000000000000000000000000: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T20:30:00.983Z"}
|
||||
{"level":"error","message":"Ошибка при получении контракта AccessToken: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T21:30:00.794Z"}
|
||||
{"level":"error","message":"Error checking token balance for 0x0000000000000000000000000000000000000000: Адрес контракта AccessToken не найден в переменных окружения","timestamp":"2025-03-04T21:30:00.795Z"}
|
||||
|
||||
@@ -1,32 +1,130 @@
|
||||
const { checkAccess } = require('../utils/access-check');
|
||||
const logger = require('../utils/logger');
|
||||
const { getUserInfo } = require('../utils/access-check');
|
||||
|
||||
// Добавьте в начало файла
|
||||
const isMiddleware = true;
|
||||
|
||||
// Middleware для проверки роли
|
||||
const requireRole = (requiredRole) => async (req, res, next) => {
|
||||
const requireRole = (allowedRoles) => async (req, res, next) => {
|
||||
if (!req.session || !req.session.authenticated || !req.session.userId) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
|
||||
try {
|
||||
const address = req.headers['x-wallet-address'];
|
||||
if (!address) {
|
||||
return res.status(401).json({ error: 'No wallet address' });
|
||||
}
|
||||
|
||||
const { hasAccess, role } = await checkAccess(address);
|
||||
// Получение информации о пользователе
|
||||
const userInfo = await getUserInfo(req.session.userId);
|
||||
|
||||
if (!hasAccess) {
|
||||
return res.status(403).json({ error: 'No access token' });
|
||||
if (!userInfo) {
|
||||
return res.status(401).json({ error: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
if (requiredRole && role !== requiredRole) {
|
||||
return res.status(403).json({ error: 'Insufficient permissions' });
|
||||
|
||||
// Проверка роли
|
||||
if (!allowedRoles.includes(userInfo.role)) {
|
||||
return res.status(403).json({ error: 'Недостаточно прав' });
|
||||
}
|
||||
|
||||
// Добавляем информацию о роли в request
|
||||
req.userRole = role;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({ error: 'Auth check failed' });
|
||||
logger.error('Error checking user role:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
};
|
||||
|
||||
// Проверка роли пользователя
|
||||
const checkRole = async (req, res, next) => {
|
||||
try {
|
||||
// Если функция вызвана как middleware
|
||||
const isMiddleware = typeof next === 'function';
|
||||
|
||||
if (!req.session.authenticated) {
|
||||
return isMiddleware ? res.status(401).json({ error: 'Не авторизован' }) : false;
|
||||
}
|
||||
|
||||
// Если роль администратора уже проверена в сессии
|
||||
if (req.session.isAdmin === true) {
|
||||
return isMiddleware ? next() : true;
|
||||
}
|
||||
|
||||
const db = require('../db');
|
||||
|
||||
// Проверка наличия токенов доступа в смарт-контракте
|
||||
if (req.session.address) {
|
||||
const address = req.session.address.toLowerCase();
|
||||
|
||||
// Проверка в базе данных
|
||||
const userRole = await db.query(
|
||||
'SELECT r.name FROM users u JOIN roles r ON u.role_id = r.id WHERE LOWER(u.address) = $1',
|
||||
[address]
|
||||
);
|
||||
|
||||
if (userRole.rows.length > 0 && userRole.rows[0].name === 'admin') {
|
||||
req.session.isAdmin = true;
|
||||
return isMiddleware ? next() : true;
|
||||
}
|
||||
|
||||
// Проверка токенов в смарт-контракте через сервис
|
||||
const { ethers } = require('ethers');
|
||||
const provider = new ethers.JsonRpcProvider(process.env.PROVIDER_URL);
|
||||
const accessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
|
||||
const accessTokenContract = new ethers.Contract(
|
||||
process.env.ACCESS_TOKEN_ADDRESS,
|
||||
accessTokenABI,
|
||||
provider
|
||||
);
|
||||
|
||||
try {
|
||||
const hasAdminRole = await accessTokenContract.hasRole(
|
||||
ethers.keccak256(ethers.toUtf8Bytes('ADMIN_ROLE')),
|
||||
address
|
||||
);
|
||||
|
||||
if (hasAdminRole) {
|
||||
// Обновляем роль в базе данных
|
||||
await db.query(
|
||||
'UPDATE users SET role_id = (SELECT id FROM roles WHERE name = $1) WHERE LOWER(address) = $2',
|
||||
['admin', address]
|
||||
);
|
||||
req.session.isAdmin = true;
|
||||
return isMiddleware ? next() : true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке роли в контракте:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Если пользователь не администратор
|
||||
req.session.isAdmin = false;
|
||||
return isMiddleware ? res.status(403).json({ error: 'Недостаточно прав' }) : false;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке роли:', error);
|
||||
return isMiddleware ? res.status(500).json({ error: 'Внутренняя ошибка сервера' }) : false;
|
||||
}
|
||||
};
|
||||
|
||||
// Middleware для проверки аутентификации
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session || !req.session.authenticated) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Middleware для проверки прав администратора
|
||||
const requireAdmin = (req, res, next) => {
|
||||
if (!req.session || !req.session.authenticated) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
|
||||
if (!req.session.isAdmin) {
|
||||
return res.status(403).json({ error: 'Требуются права администратора' });
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
requireRole
|
||||
};
|
||||
requireRole,
|
||||
requireAuth,
|
||||
requireAdmin,
|
||||
checkRole,
|
||||
};
|
||||
|
||||
12
backend/middleware/logger.js
Normal file
12
backend/middleware/logger.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const logger = (req, res, next) => {
|
||||
const start = Date.now();
|
||||
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
console.log(`${req.method} ${req.originalUrl} - ${res.statusCode} - ${duration}ms`);
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = logger;
|
||||
22
backend/middleware/session.js
Normal file
22
backend/middleware/session.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const session = require('express-session');
|
||||
const pgSession = require('connect-pg-simple')(session);
|
||||
const { pool } = require('../db');
|
||||
|
||||
const sessionMiddleware = session({
|
||||
store: new pgSession({
|
||||
pool: pool,
|
||||
tableName: 'session',
|
||||
createTableIfMissing: true,
|
||||
}),
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: process.env.NODE_ENV === 'production', // В production должно быть true
|
||||
httpOnly: true,
|
||||
maxAge: 24 * 60 * 60 * 1000, // 24 часа
|
||||
sameSite: 'none', // Для работы между разными доменами
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = sessionMiddleware;
|
||||
@@ -1,44 +0,0 @@
|
||||
-- Создание таблицы для связи идентификаторов пользователей
|
||||
CREATE TABLE IF NOT EXISTS user_identities (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
identity_type VARCHAR(20) NOT NULL, -- 'ethereum', 'telegram', 'email'
|
||||
identity_value VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(identity_type, identity_value)
|
||||
);
|
||||
|
||||
-- Создание таблицы для предпочтений пользователей
|
||||
CREATE TABLE IF NOT EXISTS user_preferences (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
preference_key VARCHAR(50) NOT NULL,
|
||||
preference_value TEXT,
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(user_id, preference_key)
|
||||
);
|
||||
|
||||
-- Создание таблицы для взаимодействий пользователей
|
||||
CREATE TABLE IF NOT EXISTS user_interactions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
interaction_type VARCHAR(50) NOT NULL,
|
||||
interaction_data JSONB,
|
||||
created_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Создание таблицы для тем пользователей
|
||||
CREATE TABLE IF NOT EXISTS user_topics (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
topic VARCHAR(100) NOT NULL,
|
||||
relevance_score FLOAT DEFAULT 1.0,
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(user_id, topic)
|
||||
);
|
||||
|
||||
-- Индексы для оптимизации запросов
|
||||
CREATE INDEX IF NOT EXISTS idx_user_identities_user_id ON user_identities(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_preferences_user_id ON user_preferences(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_interactions_user_id ON user_interactions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_topics_user_id ON user_topics(user_id);
|
||||
@@ -1,64 +0,0 @@
|
||||
-- Таблица для Канбан-досок
|
||||
CREATE TABLE IF NOT EXISTS kanban_boards (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(100) 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(100) NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
wip_limit INTEGER DEFAULT NULL, -- Лимит задач в работе (Work In Progress)
|
||||
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(200) NOT NULL,
|
||||
description TEXT,
|
||||
position INTEGER NOT NULL,
|
||||
assigned_to INTEGER REFERENCES users(id),
|
||||
due_date TIMESTAMP,
|
||||
labels JSONB DEFAULT '[]',
|
||||
created_by INTEGER REFERENCES users(id),
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Таблица для комментариев к карточкам
|
||||
CREATE TABLE IF NOT EXISTS kanban_comments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
card_id INTEGER REFERENCES kanban_cards(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Таблица для доступа к доскам
|
||||
CREATE TABLE IF NOT EXISTS kanban_board_access (
|
||||
id SERIAL PRIMARY KEY,
|
||||
board_id INTEGER REFERENCES kanban_boards(id) ON DELETE CASCADE,
|
||||
user_id INTEGER REFERENCES users(id),
|
||||
access_level VARCHAR(20) NOT NULL, -- 'read', 'write', 'admin'
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
UNIQUE(board_id, user_id)
|
||||
);
|
||||
|
||||
-- Индексы для оптимизации запросов
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_columns_board_id ON kanban_columns(board_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_cards_column_id ON kanban_cards(column_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_comments_card_id ON kanban_comments(card_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_board_access_board_id ON kanban_board_access(board_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_kanban_board_access_user_id ON kanban_board_access(user_id);
|
||||
26
backend/migrations/006_role_management.sql
Normal file
26
backend/migrations/006_role_management.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
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);
|
||||
40
backend/migrations/007_user_identities_conversations.sql
Normal file
40
backend/migrations/007_user_identities_conversations.sql
Normal file
@@ -0,0 +1,40 @@
|
||||
-- Таблица идентификаторов пользователей
|
||||
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;
|
||||
|
||||
14
backend/migrations/008_chat_history.sql
Normal file
14
backend/migrations/008_chat_history.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Создание таблицы для хранения истории диалогов
|
||||
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);
|
||||
@@ -1,20 +1,9 @@
|
||||
{
|
||||
"verbose": true,
|
||||
"ignore": [
|
||||
".git",
|
||||
"node_modules/**/node_modules",
|
||||
"sessions",
|
||||
"data/vector_store"
|
||||
],
|
||||
"watch": [
|
||||
"*.js",
|
||||
"routes/",
|
||||
"services/",
|
||||
"utils/",
|
||||
"middleware/"
|
||||
],
|
||||
"ignore": [".git", "node_modules/**/node_modules", "sessions", "data/vector_store"],
|
||||
"watch": ["*.js", "routes/", "services/", "utils/", "middleware/"],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"ext": "js,json,env"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,34 +12,46 @@
|
||||
"server": "nodemon server.js --signal SIGUSR2",
|
||||
"migrate": "node scripts/run-migrations.js",
|
||||
"prod": "NODE_ENV=production node server.js",
|
||||
"test": "mocha test/**/*.test.js"
|
||||
"test": "mocha test/**/*.test.js",
|
||||
"check-ollama": "node scripts/check-ollama-models.js",
|
||||
"check-ethers": "node scripts/check-ethers-v6-compatibility.js",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"**/*.{js,vue,json,md}\"",
|
||||
"format:check": "prettier --check \"**/*.{js,vue,json,md}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@langchain/community": "^0.0.32",
|
||||
"@langchain/community": "^0.3.34",
|
||||
"@langchain/core": "0.3.0",
|
||||
"@langchain/ollama": "^0.2.0",
|
||||
"axios": "^1.6.7",
|
||||
"connect-pg-simple": "^10.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^4.1.0",
|
||||
"csurf": "^1.11.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"ethers": "^6.7.1",
|
||||
"ethers": "6.13.5",
|
||||
"express": "^4.18.2",
|
||||
"express-rate-limit": "^7.5.0",
|
||||
"express-session": "^1.17.3",
|
||||
"helmet": "^8.0.0",
|
||||
"hnswlib-node": "^3.0.0",
|
||||
"imap": "^0.8.19",
|
||||
"langchain": "^0.1.21",
|
||||
"langchain": "0.0.200",
|
||||
"mailparser": "^3.7.2",
|
||||
"node-telegram-bot-api": "^0.64.0",
|
||||
"nodemailer": "^6.9.9",
|
||||
"node-cron": "^3.0.3",
|
||||
"node-telegram-bot-api": "^0.66.0",
|
||||
"nodemailer": "^6.10.0",
|
||||
"pg": "^8.10.0",
|
||||
"session-file-store": "^1.5.0",
|
||||
"siwe": "^2.1.4",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-prettier": "^10.0.2",
|
||||
"globals": "^16.0.0",
|
||||
"nodemon": "^3.1.9",
|
||||
"prettier": "^3.5.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
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 pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
// Проверка доступа
|
||||
router.get('/check', async (req, res) => {
|
||||
const walletAddress = req.headers['x-wallet-address'];
|
||||
|
||||
|
||||
if (!walletAddress) {
|
||||
return res.status(400).json({ error: 'No wallet address provided' });
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
// Проверяем наличие активного токена для адреса
|
||||
const result = await pool.query(
|
||||
'SELECT * FROM access_tokens WHERE wallet_address = $1 AND expires_at > NOW()',
|
||||
[walletAddress.toLowerCase()]
|
||||
);
|
||||
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.json({ hasAccess: false });
|
||||
}
|
||||
|
||||
|
||||
const token = result.rows[0];
|
||||
|
||||
|
||||
res.json({
|
||||
hasAccess: true,
|
||||
tokenId: token.id,
|
||||
role: token.role,
|
||||
expiresAt: token.expires_at
|
||||
expiresAt: token.expires_at,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Access check error:', error);
|
||||
@@ -44,15 +47,15 @@ router.get('/check', async (req, res) => {
|
||||
// Проверка прав администратора
|
||||
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);
|
||||
@@ -63,34 +66,34 @@ router.get('/admin-only', async (req, res) => {
|
||||
// Получение списка токенов
|
||||
router.get('/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 adminCheck = await pool.query(
|
||||
'SELECT * FROM access_tokens WHERE wallet_address = $1 AND role = $2 AND expires_at > NOW()',
|
||||
[walletAddress.toLowerCase(), 'ADMIN']
|
||||
);
|
||||
|
||||
|
||||
if (adminCheck.rows.length === 0) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
|
||||
|
||||
// Получаем список всех токенов
|
||||
const result = await pool.query(
|
||||
'SELECT * FROM access_tokens ORDER BY created_at DESC'
|
||||
const result = await pool.query('SELECT * FROM access_tokens ORDER BY created_at DESC');
|
||||
|
||||
res.json(
|
||||
result.rows.map((token) => ({
|
||||
id: token.id,
|
||||
walletAddress: token.wallet_address,
|
||||
role: token.role,
|
||||
createdAt: token.created_at,
|
||||
expiresAt: token.expires_at,
|
||||
}))
|
||||
);
|
||||
|
||||
res.json(result.rows.map(token => ({
|
||||
id: token.id,
|
||||
walletAddress: token.wallet_address,
|
||||
role: token.role,
|
||||
createdAt: token.created_at,
|
||||
expiresAt: token.expires_at
|
||||
})));
|
||||
} catch (error) {
|
||||
console.error('Tokens list error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
@@ -100,44 +103,44 @@ router.get('/tokens', async (req, res) => {
|
||||
// Создание токена
|
||||
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 adminCheck = await pool.query(
|
||||
'SELECT * FROM access_tokens WHERE wallet_address = $1 AND role = $2 AND expires_at > NOW()',
|
||||
[walletAddress.toLowerCase(), 'ADMIN']
|
||||
);
|
||||
|
||||
|
||||
if (adminCheck.rows.length === 0) {
|
||||
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 pool.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
|
||||
expiresAt: result.rows[0].expires_at,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Token creation error:', error);
|
||||
@@ -148,30 +151,27 @@ router.post('/tokens', async (req, res) => {
|
||||
// Отзыв токена
|
||||
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 adminCheck = await pool.query(
|
||||
'SELECT * FROM access_tokens WHERE wallet_address = $1 AND role = $2 AND expires_at > NOW()',
|
||||
[walletAddress.toLowerCase(), 'ADMIN']
|
||||
);
|
||||
|
||||
|
||||
if (adminCheck.rows.length === 0) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
|
||||
|
||||
const { id } = req.params;
|
||||
|
||||
|
||||
// Удаляем токен
|
||||
await pool.query(
|
||||
'DELETE FROM access_tokens WHERE id = $1',
|
||||
[id]
|
||||
);
|
||||
|
||||
await pool.query('DELETE FROM access_tokens WHERE id = $1', [id]);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Token revocation error:', error);
|
||||
@@ -179,4 +179,123 @@ router.delete('/tokens/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// Получение информации о роли текущего пользователя
|
||||
router.get('/role', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const address = req.session.address.toLowerCase();
|
||||
|
||||
const result = await db.query(
|
||||
'SELECT r.name as role FROM users u JOIN roles r ON u.role_id = r.id WHERE LOWER(u.address) = $1',
|
||||
[address]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
return res.json({ role: result.rows[0].role });
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении роли:', error);
|
||||
return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получение списка всех пользователей (только для администраторов)
|
||||
router.get('/users', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(
|
||||
'SELECT u.id, u.wallet_address, r.name as role, u.created_at FROM users u JOIN roles r ON u.role_id = r.id'
|
||||
);
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении списка пользователей:', error);
|
||||
return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Изменение роли пользователя (только для администраторов)
|
||||
router.post('/users/:userId/role', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { userId } = req.params;
|
||||
const { role } = req.body;
|
||||
|
||||
if (!role || !['admin', 'user'].includes(role)) {
|
||||
return res.status(400).json({ error: 'Некорректная роль' });
|
||||
}
|
||||
|
||||
await db.query(
|
||||
'UPDATE users SET role_id = (SELECT id FROM roles WHERE name = $1) WHERE id = $2',
|
||||
[role, userId]
|
||||
);
|
||||
|
||||
return res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Ошибка при изменении роли пользователя:', error);
|
||||
return res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получение информации о токенах доступа текущего пользователя
|
||||
router.get('/tokens', requireAuth, async (req, res) => {
|
||||
try {
|
||||
// Логирование для отладки
|
||||
console.log('GET /api/access/tokens запрос получен');
|
||||
console.log('Сессия пользователя:', req.session);
|
||||
|
||||
// Получаем адрес из сессии, а не из заголовков
|
||||
if (!req.session || !req.session.address) {
|
||||
return res.status(400).json({ error: 'No wallet address in session' });
|
||||
}
|
||||
|
||||
const address = req.session.address.toLowerCase();
|
||||
|
||||
// Используем правильное имя таблицы и полей
|
||||
const result = await db.query(
|
||||
'SELECT id, wallet_address, role, created_at, expires_at FROM access_tokens WHERE LOWER(wallet_address) = $1',
|
||||
[address]
|
||||
);
|
||||
|
||||
return res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении токенов:', error);
|
||||
res.status(500).json({ error: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/mint', requireAuth, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
// Логирование для отладки
|
||||
console.log('POST /api/access/mint запрос получен');
|
||||
console.log('Данные запроса:', req.body);
|
||||
|
||||
const { walletAddress, role, expiresInDays } = req.body;
|
||||
|
||||
if (!walletAddress || !role || !expiresInDays) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// Вычисляем дату истечения
|
||||
const expiresAt = new Date();
|
||||
expiresAt.setDate(expiresAt.getDate() + parseInt(expiresInDays));
|
||||
|
||||
// Создаем токен
|
||||
const result = await pool.query(
|
||||
'INSERT INTO access_tokens (wallet_address, role, expires_at) VALUES ($1, $2, $3) RETURNING *',
|
||||
[walletAddress.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('Ошибка при создании токена:', error);
|
||||
res.status(500).json({ error: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
123
backend/routes/admin.js
Normal file
123
backend/routes/admin.js
Normal file
@@ -0,0 +1,123 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const { checkIfAdmin } = require('../utils/access-check');
|
||||
|
||||
// Middleware для проверки прав администратора
|
||||
const requireAdmin = async (req, res, next) => {
|
||||
console.log('Проверка прав администратора:', {
|
||||
session: req.session
|
||||
? {
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin,
|
||||
}
|
||||
: null,
|
||||
headers: {
|
||||
authorization: req.headers.authorization,
|
||||
},
|
||||
});
|
||||
|
||||
// Проверка аутентификации через сессию
|
||||
if (req.session && req.session.authenticated && req.session.isAdmin) {
|
||||
console.log('Пользователь авторизован как администратор через сессию');
|
||||
return next();
|
||||
}
|
||||
|
||||
// Проверка через заголовок авторизации
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
console.log('Отсутствует заголовок авторизации');
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const address = authHeader.split(' ')[1];
|
||||
console.log('Проверка адреса из заголовка:', address);
|
||||
|
||||
try {
|
||||
// Проверяем напрямую в базе данных
|
||||
const userResult = await db.query('SELECT is_admin FROM users WHERE address = $1', [
|
||||
address.toLowerCase(),
|
||||
]);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
console.log(`Пользователь с адресом ${address} не найден`);
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const isAdmin = userResult.rows[0].is_admin;
|
||||
console.log(`Пользователь с адресом ${address} имеет статус администратора:`, isAdmin);
|
||||
|
||||
if (!isAdmin) {
|
||||
console.log(`Пользователь с адресом ${address} не является администратором`);
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
// Обновляем сессию
|
||||
if (req.session) {
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address;
|
||||
req.session.isAdmin = true;
|
||||
|
||||
console.log('Сессия обновлена из middleware:', {
|
||||
address,
|
||||
isAdmin: true,
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке прав администратора:', error);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
};
|
||||
|
||||
// Применяем middleware ко всем маршрутам
|
||||
router.use(requireAdmin);
|
||||
|
||||
// Маршрут для получения списка пользователей
|
||||
router.get('/users', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM users');
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении списка пользователей:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения статистики
|
||||
router.get('/stats', async (req, res) => {
|
||||
try {
|
||||
// Получаем количество пользователей
|
||||
const usersCount = await db.query('SELECT COUNT(*) FROM users');
|
||||
|
||||
// Получаем количество досок
|
||||
const boardsCount = await db.query('SELECT COUNT(*) FROM kanban_boards');
|
||||
|
||||
// Получаем количество задач
|
||||
const tasksCount = await db.query('SELECT COUNT(*) FROM kanban_tasks');
|
||||
|
||||
res.json({
|
||||
userCount: parseInt(usersCount.rows[0].count),
|
||||
boardCount: parseInt(boardsCount.rows[0].count),
|
||||
taskCount: parseInt(tasksCount.rows[0].count),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении статистики:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения логов
|
||||
router.get('/logs', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM logs ORDER BY created_at DESC LIMIT 100');
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении логов:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -6,6 +6,10 @@ const db = require('../db');
|
||||
const logger = require('../utils/logger');
|
||||
const helmet = require('helmet');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
const { checkIfAdmin } = require('../utils/access-check');
|
||||
const { checkRole, requireAuth } = require('../middleware/auth');
|
||||
const { pool } = require('../db');
|
||||
const { verifySignature, checkAccess, findOrCreateUser } = require('../utils/auth');
|
||||
|
||||
// Создайте лимитер для попыток аутентификации
|
||||
const authLimiter = rateLimit({
|
||||
@@ -13,208 +17,221 @@ const authLimiter = rateLimit({
|
||||
max: 20, // Увеличьте лимит с 5 до 20
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: 'Слишком много попыток аутентификации. Попробуйте позже.' }
|
||||
message: { error: 'Слишком много попыток аутентификации. Попробуйте позже.' },
|
||||
});
|
||||
|
||||
// Маршрут для получения nonce для подписи
|
||||
// Получение nonce для аутентификации
|
||||
router.get('/nonce', async (req, res) => {
|
||||
try {
|
||||
const { address } = req.query;
|
||||
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.log('Nonce request:', {
|
||||
// address,
|
||||
// sessionID: req.sessionID,
|
||||
// session: req.session
|
||||
// });
|
||||
|
||||
|
||||
if (!address) {
|
||||
return res.status(400).json({ error: 'Address is required' });
|
||||
}
|
||||
|
||||
// Генерируем случайный nonce
|
||||
const nonce = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
// Создаем сообщение для подписи
|
||||
const message = `Sign this message to authenticate with DApp for Business. Nonce: ${nonce}`;
|
||||
|
||||
|
||||
// Генерируем nonce
|
||||
const nonce = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
// Сохраняем nonce в сессии
|
||||
req.session.nonce = nonce;
|
||||
req.session.pendingAddress = address;
|
||||
|
||||
// Получаем IP-адрес клиента
|
||||
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
|
||||
// Сохраняем IP-адрес в сессии при генерации nonce
|
||||
req.session.clientIP = clientIP;
|
||||
|
||||
// Явно сохраняем сессию
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.error('Error saving session:', err);
|
||||
return res.status(500).json({ error: 'Failed to save session' });
|
||||
}
|
||||
|
||||
// Удалите или закомментируйте
|
||||
// console.log('Nonce saved in session:', {
|
||||
// nonce,
|
||||
// pendingAddress: address,
|
||||
// sessionID: req.sessionID
|
||||
// });
|
||||
|
||||
res.json({ message });
|
||||
req.session.authNonce = nonce;
|
||||
req.session.pendingAddress = address.toLowerCase();
|
||||
|
||||
console.log('Сгенерирован nonce для адреса:', address);
|
||||
console.log('Сессия после генерации nonce:', req.session);
|
||||
|
||||
// Сохраняем сессию и ждем завершения
|
||||
await new Promise((resolve, reject) => {
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при сохранении сессии:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Проверяем, что nonce сохранился
|
||||
console.log('Сессия после сохранения:', req.session);
|
||||
|
||||
return res.json({ nonce });
|
||||
} catch (error) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.error('Error generating nonce:', error);
|
||||
logger.error('Error generating nonce:', error);
|
||||
res.status(500).json({ error: 'Failed to generate nonce' });
|
||||
console.error('Ошибка при генерации nonce:', error);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для верификации подписи
|
||||
router.post('/verify', authLimiter, async (req, res) => {
|
||||
// Функция для проверки роли пользователя
|
||||
async function checkUserRole(address, req) {
|
||||
try {
|
||||
const { address, signature } = req.body;
|
||||
|
||||
if (!address || !signature) {
|
||||
return res.status(400).json({ error: 'Address and signature are required' });
|
||||
const lowerCaseAddress = address.toLowerCase();
|
||||
|
||||
// Проверяем наличие токена доступа в базе данных
|
||||
const result = await db.query(
|
||||
'SELECT role FROM access_tokens WHERE LOWER(wallet_address) = $1 AND expires_at > NOW()',
|
||||
[lowerCaseAddress]
|
||||
);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
// Если есть активный токен, проверяем роль
|
||||
const role = result.rows[0].role;
|
||||
return role === 'ADMIN';
|
||||
}
|
||||
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.log('Verify request:', {
|
||||
// address,
|
||||
// signature,
|
||||
// sessionID: req.sessionID,
|
||||
// session: {
|
||||
// nonce: req.session.nonce,
|
||||
// pendingAddress: req.session.pendingAddress
|
||||
// }
|
||||
// });
|
||||
|
||||
// Получаем nonce из сессии
|
||||
const nonce = req.session.nonce;
|
||||
const pendingAddress = req.session.pendingAddress;
|
||||
|
||||
if (!nonce || !pendingAddress) {
|
||||
return res.status(400).json({ error: 'No pending authentication request' });
|
||||
}
|
||||
|
||||
// Получаем IP-адрес клиента
|
||||
const clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
|
||||
// Проверяем, что IP-адрес совпадает
|
||||
if (req.session.clientIP !== clientIP) {
|
||||
return res.status(400).json({ error: 'IP address mismatch' });
|
||||
}
|
||||
|
||||
// Проверяем, что адрес совпадает с тем, для которого был сгенерирован nonce
|
||||
if (pendingAddress.toLowerCase() !== address.toLowerCase()) {
|
||||
return res.status(400).json({ error: 'Address mismatch' });
|
||||
}
|
||||
|
||||
// Создаем сообщение для проверки подписи
|
||||
const message = `Sign this message to authenticate with DApp for Business. Nonce: ${nonce}`;
|
||||
|
||||
// Восстанавливаем адрес из подписи
|
||||
const recoveredAddress = ethers.verifyMessage(message, signature);
|
||||
|
||||
// Проверяем, что восстановленный адрес совпадает с предоставленным
|
||||
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
|
||||
return res.status(400).json({ error: 'Invalid signature' });
|
||||
}
|
||||
|
||||
// Проверяем, существует ли пользователь в базе данных
|
||||
const user = await db.query('SELECT * FROM users WHERE address = $1', [address]);
|
||||
|
||||
let userId;
|
||||
let isAdmin = false;
|
||||
|
||||
if (user.rows.length === 0) {
|
||||
// Если пользователь не существует, создаем его
|
||||
const newUser = await db.query(
|
||||
'INSERT INTO users (address, created_at) VALUES ($1, NOW()) RETURNING id',
|
||||
[address]
|
||||
);
|
||||
userId = newUser.rows[0].id;
|
||||
} else {
|
||||
userId = user.rows[0].id;
|
||||
isAdmin = user.rows[0].is_admin || false;
|
||||
}
|
||||
|
||||
// Устанавливаем состояние аутентификации в сессии
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address;
|
||||
req.session.isAdmin = isAdmin;
|
||||
req.session.authType = 'wallet';
|
||||
req.session.userId = userId;
|
||||
|
||||
// Удаляем nonce из сессии
|
||||
delete req.session.nonce;
|
||||
delete req.session.pendingAddress;
|
||||
|
||||
// Явно сохраняем сессию
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.error('Error saving session:', err);
|
||||
return res.status(500).json({ error: 'Failed to save session' });
|
||||
}
|
||||
|
||||
// Удалите или закомментируйте
|
||||
// console.log('Authentication successful:', {
|
||||
// address,
|
||||
// isAdmin,
|
||||
// sessionID: req.sessionID
|
||||
// });
|
||||
|
||||
res.json({
|
||||
authenticated: true,
|
||||
address,
|
||||
isAdmin,
|
||||
authType: 'wallet'
|
||||
});
|
||||
});
|
||||
|
||||
// Если нет токена, проверяем адрес администратора из переменных окружения
|
||||
const adminAddresses = (process.env.ADMIN_ADDRESSES || '')
|
||||
.split(',')
|
||||
.map((a) => a.toLowerCase());
|
||||
return adminAddresses.includes(lowerCaseAddress);
|
||||
} catch (error) {
|
||||
console.error('Error verifying signature:', error);
|
||||
console.error('Ошибка при проверке роли пользователя:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Верификация подписи
|
||||
router.post('/verify', async (req, res) => {
|
||||
try {
|
||||
const { address, signature, message, nonce } = req.body;
|
||||
|
||||
// Более подробная обработка ошибок
|
||||
if (error.message.includes('invalid signature')) {
|
||||
return res.status(400).json({
|
||||
error: 'Недействительная подпись',
|
||||
message: 'Подпись не соответствует адресу. Пожалуйста, попробуйте снова.'
|
||||
});
|
||||
console.log('Верификация подписи:', { address, signature, message });
|
||||
console.log('Сессия при верификации:', req.session);
|
||||
|
||||
if (!address || !signature || !message) {
|
||||
return res.status(400).json({ error: 'Address, signature and message are required' });
|
||||
}
|
||||
|
||||
if (error.message.includes('invalid address')) {
|
||||
return res.status(400).json({
|
||||
error: 'Недействительный адрес',
|
||||
message: 'Указанный адрес имеет неверный формат.'
|
||||
});
|
||||
|
||||
// Проверяем наличие nonce в сессии
|
||||
if (!req.session.authNonce || !req.session.pendingAddress) {
|
||||
console.error('Сессия не содержит nonce или pendingAddress:', req.session);
|
||||
|
||||
// Проверяем наличие nonce в заголовке
|
||||
const headerNonce = req.headers['x-auth-nonce'];
|
||||
if (headerNonce) {
|
||||
console.log('Найден nonce в заголовке:', headerNonce);
|
||||
req.session.authNonce = headerNonce;
|
||||
req.session.pendingAddress = address.toLowerCase();
|
||||
}
|
||||
|
||||
// Если в запросе есть nonce в сообщении, извлекаем его
|
||||
let extractedNonce = null;
|
||||
if (message) {
|
||||
const match = message.match(/nonce: ([a-f0-9]+)/);
|
||||
if (match && match[1]) {
|
||||
extractedNonce = match[1];
|
||||
console.log('Извлечен nonce из сообщения:', extractedNonce);
|
||||
|
||||
// Устанавливаем nonce в сессию
|
||||
req.session.authNonce = extractedNonce;
|
||||
req.session.pendingAddress = address.toLowerCase();
|
||||
|
||||
// Сохраняем сессию
|
||||
await new Promise((resolve) => {
|
||||
req.session.save((err) => {
|
||||
if (err) console.error('Ошибка при сохранении сессии:', err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
error: 'Ошибка верификации подписи',
|
||||
message: 'Не удалось проверить подпись. Пожалуйста, попробуйте снова позже.'
|
||||
});
|
||||
|
||||
// Формируем ожидаемое сообщение
|
||||
const expectedMessage = `Подтвердите вход в DApp for Business с nonce: ${req.session.authNonce}`;
|
||||
|
||||
// Проверяем, что адрес совпадает с ожидаемым
|
||||
if (req.session.pendingAddress && req.session.pendingAddress.toLowerCase() !== address.toLowerCase()) {
|
||||
console.error('Адрес не совпадает с ожидаемым:', {
|
||||
expected: req.session.pendingAddress,
|
||||
received: address,
|
||||
});
|
||||
return res.status(400).json({ error: 'Invalid address' });
|
||||
}
|
||||
|
||||
let verified = false;
|
||||
try {
|
||||
// Проверяем подпись с использованием ethers.js
|
||||
const recoveredAddress = ethers.verifyMessage(expectedMessage, signature);
|
||||
|
||||
if (recoveredAddress.toLowerCase() !== address.toLowerCase()) {
|
||||
console.error('Неверная подпись:', {
|
||||
expected: address.toLowerCase(),
|
||||
recovered: recoveredAddress.toLowerCase(),
|
||||
});
|
||||
return res.status(400).json({ error: 'Invalid signature' });
|
||||
}
|
||||
|
||||
verified = true;
|
||||
console.log('Подпись успешно проверена');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке подписи:', error);
|
||||
return res.status(400).json({ error: 'Invalid signature format' });
|
||||
}
|
||||
|
||||
// Если подпись верна, аутентифицируем пользователя
|
||||
if (verified) {
|
||||
// Найдем или создадим пользователя
|
||||
const user = await findOrCreateUser(address, 'wallet');
|
||||
|
||||
// Обновляем сессию
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address;
|
||||
req.session.userId = user.id;
|
||||
req.session.authType = 'wallet';
|
||||
req.session.isAdmin = user.is_admin;
|
||||
req.session.role = user.role;
|
||||
req.session.authChannel = 'web';
|
||||
req.session.language = req.body.language || 'en';
|
||||
|
||||
// Удаляем временные данные
|
||||
delete req.session.authNonce;
|
||||
delete req.session.pendingAddress;
|
||||
|
||||
// Сохраняем сессию
|
||||
await new Promise((resolve, reject) => {
|
||||
req.session.save(err => {
|
||||
if (err) {
|
||||
console.error('Ошибка при сохранении сессии:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Аутентификация успешна:', {
|
||||
address,
|
||||
isAdmin: user.is_admin,
|
||||
userId: user.id,
|
||||
role: user.role
|
||||
});
|
||||
|
||||
res.json({
|
||||
authenticated: true,
|
||||
address,
|
||||
isAdmin: user.is_admin,
|
||||
role: user.role
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({ error: 'Invalid signature' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Authentication error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для проверки состояния аутентификации
|
||||
// Проверка текущей сессии
|
||||
router.get('/check', (req, res) => {
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.log('Session check:', {
|
||||
// session: req.session,
|
||||
// authenticated: req.session.authenticated
|
||||
// });
|
||||
console.log('Сессия при проверке:', req.session);
|
||||
|
||||
if (req.session.authenticated) {
|
||||
// Если сессия существует и пользователь аутентифицирован
|
||||
if (req.session && req.session.authenticated) {
|
||||
res.json({
|
||||
authenticated: true,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin,
|
||||
authType: req.session.authType
|
||||
isAdmin: req.session.isAdmin || false,
|
||||
role: req.session.role || 'USER'
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
@@ -226,31 +243,49 @@ router.get('/check', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для выхода из системы
|
||||
// Обработчик выхода из системы
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.error('Error destroying session:', err);
|
||||
return res.status(500).json({ error: 'Failed to logout' });
|
||||
}
|
||||
try {
|
||||
// Сохраняем sessionID перед удалением сессии
|
||||
const sessionID = req.sessionID;
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
// Удаляем сессию из хранилища
|
||||
req.session.destroy(async (err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при удалении сессии:', err);
|
||||
return res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
|
||||
try {
|
||||
// Удаляем запись из базы данных
|
||||
await db.query('DELETE FROM sessions WHERE sid = $1', [sessionID]);
|
||||
console.log(`Сессия ${sessionID} удалена из базы данных`);
|
||||
} catch (dbErr) {
|
||||
console.error('Ошибка при удалении сессии из базы данных:', dbErr);
|
||||
}
|
||||
|
||||
// Очищаем cookie
|
||||
res.clearCookie('dapp.sid');
|
||||
res.json({ success: true });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для авторизации через Telegram
|
||||
router.get('/telegram', (req, res) => {
|
||||
// Генерируем случайный токен для авторизации
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
|
||||
|
||||
// Сохраняем токен в сессии
|
||||
req.session.telegramToken = token;
|
||||
|
||||
|
||||
// Создаем URL для авторизации через Telegram
|
||||
const botName = process.env.TELEGRAM_BOT_NAME || 'YourBotName';
|
||||
const authUrl = `https://t.me/${botName}?start=${token}`;
|
||||
|
||||
|
||||
res.json({ authUrl });
|
||||
});
|
||||
|
||||
@@ -258,22 +293,22 @@ router.get('/telegram', (req, res) => {
|
||||
router.post('/email', async (req, res) => {
|
||||
try {
|
||||
const { email } = req.body;
|
||||
|
||||
|
||||
if (!email) {
|
||||
return res.status(400).json({ error: 'Email is required' });
|
||||
}
|
||||
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
|
||||
|
||||
// Сохраняем код в сессии
|
||||
req.session.emailVerificationCode = verificationCode;
|
||||
req.session.pendingEmail = email;
|
||||
|
||||
|
||||
// В реальном приложении здесь нужно отправить email с кодом подтверждения
|
||||
// Удалите или закомментируйте эти логи
|
||||
// console.log(`Verification code for ${email}: ${verificationCode}`);
|
||||
|
||||
|
||||
res.json({ success: true, message: 'Verification code sent' });
|
||||
} catch (error) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
@@ -287,35 +322,35 @@ router.post('/email', async (req, res) => {
|
||||
router.post('/email/verify', async (req, res) => {
|
||||
try {
|
||||
const { email, code } = req.body;
|
||||
|
||||
|
||||
if (!email || !code) {
|
||||
return res.status(400).json({ error: 'Email and code are required' });
|
||||
}
|
||||
|
||||
|
||||
// Получаем код из сессии
|
||||
const verificationCode = req.session.emailVerificationCode;
|
||||
const pendingEmail = req.session.pendingEmail;
|
||||
|
||||
|
||||
if (!verificationCode || !pendingEmail) {
|
||||
return res.status(400).json({ error: 'No pending verification' });
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, что email совпадает с тем, для которого был сгенерирован код
|
||||
if (pendingEmail !== email) {
|
||||
return res.status(400).json({ error: 'Email mismatch' });
|
||||
}
|
||||
|
||||
|
||||
// Проверяем код
|
||||
if (verificationCode !== code) {
|
||||
return res.status(400).json({ error: 'Invalid verification code' });
|
||||
}
|
||||
|
||||
|
||||
// Проверяем, существует ли пользователь в базе данных
|
||||
const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
|
||||
|
||||
|
||||
let userId;
|
||||
let isAdmin = false;
|
||||
|
||||
|
||||
if (user.rows.length === 0) {
|
||||
// Если пользователь не существует, создаем его
|
||||
const newUser = await db.query(
|
||||
@@ -327,7 +362,7 @@ router.post('/email/verify', async (req, res) => {
|
||||
userId = user.rows[0].id;
|
||||
isAdmin = user.rows[0].is_admin || false;
|
||||
}
|
||||
|
||||
|
||||
// Устанавливаем состояние аутентификации в сессии
|
||||
req.session.isAuthenticated = true;
|
||||
req.session.authenticated = true;
|
||||
@@ -335,16 +370,16 @@ router.post('/email/verify', async (req, res) => {
|
||||
req.session.userId = userId;
|
||||
req.session.isAdmin = isAdmin;
|
||||
req.session.authType = 'email';
|
||||
|
||||
|
||||
// Удаляем код из сессии
|
||||
delete req.session.emailVerificationCode;
|
||||
delete req.session.pendingEmail;
|
||||
|
||||
|
||||
res.json({
|
||||
authenticated: true,
|
||||
address: email,
|
||||
isAdmin,
|
||||
authType: 'email'
|
||||
authType: 'email',
|
||||
});
|
||||
} catch (error) {
|
||||
// Удалите или закомментируйте эти логи
|
||||
@@ -354,4 +389,176 @@ router.post('/email/verify', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = { router };
|
||||
// Добавляем маршрут для проверки прав доступа
|
||||
router.get('/check-access', requireAuth, (req, res) => {
|
||||
try {
|
||||
// Получаем информацию о пользователе
|
||||
const userData = {
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin || false,
|
||||
roles: req.session.roles || [],
|
||||
authenticated: true,
|
||||
};
|
||||
|
||||
// Проверяем доступ к различным разделам
|
||||
const access = {
|
||||
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,
|
||||
access: access,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке прав доступа:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Упрощенный маршрут для обновления сессии
|
||||
router.post('/refresh-session', async (req, res) => {
|
||||
try {
|
||||
const { address } = req.body;
|
||||
|
||||
if (!address) {
|
||||
return res.status(400).json({ success: false, message: 'Адрес не указан' });
|
||||
}
|
||||
|
||||
console.log(`Получен запрос на обновление сессии для адреса: ${address}`);
|
||||
|
||||
// Проверяем, существует ли пользователь в базе данных
|
||||
const userResult = await pool.query('SELECT * FROM users WHERE address = $1', [
|
||||
address.toLowerCase(),
|
||||
]);
|
||||
|
||||
let user = null;
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
user = userResult.rows[0];
|
||||
console.log(`Найден пользователь: ${user.id}`);
|
||||
} else {
|
||||
console.log(`Пользователь с адресом ${address} не найден`);
|
||||
}
|
||||
|
||||
// Обновляем сессию
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address.toLowerCase();
|
||||
|
||||
if (user) {
|
||||
req.session.userId = user.id;
|
||||
req.session.isAdmin = user.is_admin || false;
|
||||
req.session.role = user.is_admin ? 'ADMIN' : 'USER';
|
||||
} else {
|
||||
// Если пользователь не найден в базе, проверяем через переменные окружения
|
||||
const adminAddresses = (process.env.ADMIN_ADDRESSES || '')
|
||||
.split(',')
|
||||
.map((a) => a.toLowerCase());
|
||||
const isAdmin = adminAddresses.includes(address.toLowerCase());
|
||||
req.session.isAdmin = isAdmin;
|
||||
req.session.role = isAdmin ? 'ADMIN' : 'USER';
|
||||
}
|
||||
|
||||
// Сохраняем сессию
|
||||
await new Promise((resolve, reject) => {
|
||||
req.session.save((err) => {
|
||||
if (err) {
|
||||
console.error('Ошибка при сохранении сессии:', err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Сессия обновлена:', req.session);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'Сессия обновлена',
|
||||
user: {
|
||||
id: user ? user.id : null,
|
||||
address: address.toLowerCase(),
|
||||
isAdmin: req.session.isAdmin,
|
||||
role: req.session.role,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении сессии:', error);
|
||||
return res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для обновления статуса администратора
|
||||
router.post('/update-admin-status', async (req, res) => {
|
||||
try {
|
||||
const { address, isAdmin } = req.body;
|
||||
|
||||
if (!address) {
|
||||
return res.status(400).json({ error: 'Address is required' });
|
||||
}
|
||||
|
||||
console.log(`Запрос на обновление статуса администратора для адреса ${address} на ${isAdmin}`);
|
||||
|
||||
// Проверяем, существует ли пользователь
|
||||
const userResult = await db.query('SELECT * FROM users WHERE address = $1', [
|
||||
address.toLowerCase(),
|
||||
]);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
// Если пользователь не найден, создаем его
|
||||
await db.query('INSERT INTO users (address, is_admin, created_at) VALUES ($1, $2, NOW())', [
|
||||
address.toLowerCase(),
|
||||
isAdmin,
|
||||
]);
|
||||
|
||||
console.log(
|
||||
`Создан новый пользователь с адресом ${address} и статусом администратора ${isAdmin}`
|
||||
);
|
||||
} else {
|
||||
// Если пользователь найден, обновляем его статус
|
||||
await db.query('UPDATE users SET is_admin = $1 WHERE address = $2', [
|
||||
isAdmin,
|
||||
address.toLowerCase(),
|
||||
]);
|
||||
|
||||
console.log(
|
||||
`Обновлен статус администратора для пользователя с адресом ${address} на ${isAdmin}`
|
||||
);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении статуса администратора:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для проверки структуры таблицы users
|
||||
router.get('/check-db-structure', async (req, res) => {
|
||||
try {
|
||||
// Получаем информацию о таблице users
|
||||
const tableInfo = await pool.query(`
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'users'
|
||||
`);
|
||||
|
||||
res.json({
|
||||
tableStructure: tableInfo.rows,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении структуры базы данных:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавьте обработку ошибок
|
||||
router.use((err, req, res, next) => {
|
||||
console.error('Auth route error:', err);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
});
|
||||
|
||||
module.exports = { router };
|
||||
|
||||
@@ -1,185 +1,200 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { checkAccess } = require('../utils/access-check');
|
||||
const { createOllamaChain, directOllamaQuery, checkOllamaAvailability, ChatOllama } = require('../services/ollama');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { getVectorStore } = require('../services/vectorStore');
|
||||
const db = require('../db');
|
||||
const { requireAuth, requireAdmin } = require('../middleware/auth');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Хранилище истории чатов
|
||||
const chatHistory = {};
|
||||
|
||||
// Обработка чат-сообщений с проверкой сессии
|
||||
router.post('/', async (req, res) => {
|
||||
// Обработчик сообщений чата
|
||||
router.post('/message', requireAuth, async (req, res) => {
|
||||
try {
|
||||
console.log('Получен запрос в chat.js:', {
|
||||
body: req.body,
|
||||
session: req.session ? {
|
||||
id: req.sessionID,
|
||||
address: req.session.address,
|
||||
isAuthenticated: req.session.isAuthenticated,
|
||||
authenticated: req.session.authenticated
|
||||
} : null,
|
||||
cookies: req.cookies,
|
||||
headers: {
|
||||
cookie: req.headers.cookie,
|
||||
origin: req.headers.origin,
|
||||
referer: req.headers.referer,
|
||||
'content-type': req.headers['content-type']
|
||||
}
|
||||
});
|
||||
const { message, language = 'ru' } = req.body;
|
||||
|
||||
// Проверяем, что тело запроса правильно парсится
|
||||
if (req.headers['content-type'] === 'application/json') {
|
||||
console.log('JSON body:', JSON.stringify(req.body));
|
||||
} else {
|
||||
console.log('Non-JSON body:', req.body);
|
||||
// Проверка аутентификации
|
||||
if (!req.session || !req.session.authenticated) {
|
||||
return res.status(401).json({ error: 'Требуется аутентификация' });
|
||||
}
|
||||
|
||||
// ВАЖНО: Принимаем любой адрес из запроса без проверки сессии
|
||||
const userAddress = req.body.address || '0xdefault';
|
||||
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
console.log(`Получено сообщение: ${message}, язык: ${language}`);
|
||||
|
||||
// Определяем язык сообщения, если не указан явно
|
||||
let detectedLanguage = language;
|
||||
if (!language || language === 'auto') {
|
||||
// Простая эвристика для определения языка
|
||||
const cyrillicPattern = /[а-яА-ЯёЁ]/;
|
||||
detectedLanguage = cyrillicPattern.test(message) ? 'ru' : 'en';
|
||||
}
|
||||
|
||||
console.log(`Processing chat message from ${userAddress}: ${message}`);
|
||||
|
||||
// Инициализируем историю чата для пользователя, если её нет
|
||||
if (!chatHistory[userAddress]) {
|
||||
chatHistory[userAddress] = [];
|
||||
// Формируем системный промпт в зависимости от языка
|
||||
let systemPrompt = '';
|
||||
if (detectedLanguage === 'ru') {
|
||||
systemPrompt = 'Вы - полезный ассистент. Отвечайте на русском языке.';
|
||||
} else {
|
||||
systemPrompt = 'You are a helpful assistant. Respond in English.';
|
||||
}
|
||||
|
||||
// Отправляем запрос к Ollama с указанием языка
|
||||
console.log(`Отправка запроса к Ollama (модель: ${process.env.OLLAMA_MODEL || 'mistral'}, язык: ${detectedLanguage}): ${message}`);
|
||||
|
||||
// Временно возвращаем тестовый ответ для отладки
|
||||
const responseText = `Тестовый ответ на сообщение: ${message}`;
|
||||
|
||||
// Сохраняем историю чата
|
||||
chatHistory[userAddress].push({
|
||||
type: 'human',
|
||||
text: message
|
||||
// Проверяем доступность Ollama
|
||||
console.log('Проверка доступности Ollama...');
|
||||
try {
|
||||
const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/tags`);
|
||||
const data = await response.json();
|
||||
console.log('Ollama доступен. Доступные модели:');
|
||||
data.models.forEach(model => {
|
||||
console.log(`- ${model.name}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке доступности Ollama:', error);
|
||||
return res.status(500).json({ error: 'Сервис Ollama недоступен' });
|
||||
}
|
||||
|
||||
// Создаем экземпляр ChatOllama
|
||||
const chat = new ChatOllama({
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
||||
model: process.env.OLLAMA_MODEL || 'mistral',
|
||||
system: systemPrompt
|
||||
});
|
||||
|
||||
console.log('Отправка запроса к Ollama...');
|
||||
|
||||
chatHistory[userAddress].push({
|
||||
type: 'ai',
|
||||
text: responseText
|
||||
// Получаем ответ от модели
|
||||
let aiResponse;
|
||||
try {
|
||||
const response = await chat.invoke(message);
|
||||
aiResponse = response.content;
|
||||
console.log('Ответ AI:', aiResponse);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при вызове ChatOllama:', error);
|
||||
|
||||
// Альтернативный метод запроса через прямой API
|
||||
try {
|
||||
console.log('Пробуем альтернативный метод запроса...');
|
||||
const response = await fetch(`${process.env.OLLAMA_BASE_URL || 'http://localhost:11434'}/api/generate`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: process.env.OLLAMA_MODEL || 'mistral',
|
||||
prompt: message,
|
||||
system: systemPrompt,
|
||||
stream: false
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
aiResponse = data.response;
|
||||
console.log('Ответ AI (альтернативный метод):', aiResponse);
|
||||
} catch (fallbackError) {
|
||||
console.error('Ошибка при использовании альтернативного метода:', fallbackError);
|
||||
throw error; // Выбрасываем исходную ошибку
|
||||
}
|
||||
}
|
||||
|
||||
// Отправляем ответ клиенту
|
||||
res.json({
|
||||
reply: aiResponse,
|
||||
language: detectedLanguage
|
||||
});
|
||||
|
||||
return res.json({ response: responseText });
|
||||
} catch (error) {
|
||||
console.error('Подробная ошибка:', error.stack);
|
||||
console.error('Chat error:', error);
|
||||
res.status(500).json({
|
||||
error: "Извините, произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже."
|
||||
});
|
||||
logger.error('Error processing message:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавьте новый эндпоинт для проверки сессии
|
||||
router.get('/check-session', (req, res) => {
|
||||
// Добавьте этот маршрут для проверки доступных моделей
|
||||
router.get('/models', async (req, res) => {
|
||||
try {
|
||||
console.log('Проверка сессии в chat.js:', {
|
||||
sessionID: req.sessionID,
|
||||
session: req.session ? {
|
||||
isAuthenticated: req.session.isAuthenticated,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address
|
||||
} : null,
|
||||
cookies: req.cookies,
|
||||
headers: {
|
||||
cookie: req.headers.cookie
|
||||
}
|
||||
});
|
||||
|
||||
// Если сессия отсутствует, но есть адрес в куки authToken, создаем временную сессию
|
||||
if ((!req.session || (!req.session.isAuthenticated && !req.session.authenticated)) && req.cookies.authToken) {
|
||||
console.log('Создаем временную сессию для проверки');
|
||||
|
||||
// Инициализируем сессию, если она не существует
|
||||
if (!req.session) {
|
||||
req.session = {};
|
||||
}
|
||||
|
||||
req.session.isAuthenticated = true;
|
||||
req.session.authenticated = true;
|
||||
req.session.isAdmin = true;
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: 'Temporary session created',
|
||||
isAdmin: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!req.session) {
|
||||
return res.status(401).json({ error: 'No session' });
|
||||
}
|
||||
|
||||
if (!req.session.isAuthenticated && !req.session.authenticated) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
const ollama = new Ollama();
|
||||
const models = await ollama.list();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
models: models.models.map((model) => model.name),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке сессии:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
console.error('Ошибка при получении списка моделей:', error);
|
||||
res.status(500).json({ success: false, message: 'Ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавьте новый эндпоинт для прямой отправки сообщений в Ollama
|
||||
router.post('/ollama', async (req, res) => {
|
||||
// Маршрут для получения истории диалогов (доступен пользователю для своих диалогов)
|
||||
router.get('/history', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message, model = 'mistral' } = req.body;
|
||||
const userId = req.session.userId;
|
||||
const { limit = 50, offset = 0 } = req.query;
|
||||
|
||||
console.log(`Отправка сообщения в Ollama (${model}):`, message);
|
||||
const result = await db.query(`
|
||||
SELECT id, channel, sender_type, content, metadata, created_at
|
||||
FROM chat_history
|
||||
WHERE user_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`, [userId, limit, offset]);
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching chat history:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения всех диалогов (только для админов)
|
||||
router.get('/admin/history', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { limit = 50, offset = 0, userId } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT ch.id, ch.user_id, u.username, ch.channel,
|
||||
ch.sender_type, ch.content, ch.metadata, ch.created_at
|
||||
FROM chat_history ch
|
||||
LEFT JOIN users u ON ch.user_id = u.id
|
||||
`;
|
||||
|
||||
const params = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (userId) {
|
||||
query += ` WHERE ch.user_id = $${paramIndex}`;
|
||||
params.push(userId);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
// Используем функцию directOllamaQuery вместо создания нового экземпляра ChatOllama
|
||||
const result = await directOllamaQuery(message, model);
|
||||
query += ` ORDER BY ch.created_at DESC LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
|
||||
params.push(limit, offset);
|
||||
|
||||
console.log('Ответ от Ollama:', result);
|
||||
const result = await db.query(query, params);
|
||||
|
||||
// Возвращаем ответ клиенту
|
||||
res.json({
|
||||
response: result,
|
||||
model: model
|
||||
});
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отправке сообщения в Ollama:', error);
|
||||
res.status(500).json({
|
||||
error: "Ошибка при отправке сообщения в Ollama. Убедитесь, что сервер Ollama запущен."
|
||||
});
|
||||
logger.error('Error fetching admin chat history:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Проверьте, что маршрут правильно настроен
|
||||
router.post('/message', async (req, res) => {
|
||||
// Сохранение сообщения в историю чата
|
||||
router.post('/message', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
const { content, channel = 'web', metadata = {} } = req.body;
|
||||
const userId = req.session.userId;
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
}
|
||||
// Сохранение сообщения пользователя
|
||||
const userMessageResult = await db.query(`
|
||||
INSERT INTO chat_history (user_id, channel, sender_type, content, metadata)
|
||||
VALUES ($1, $2, 'user', $3, $4)
|
||||
RETURNING id
|
||||
`, [userId, channel, content, metadata]);
|
||||
|
||||
console.log('Получено сообщение:', message);
|
||||
const messageId = userMessageResult.rows[0].id;
|
||||
|
||||
// Здесь ваш код обработки сообщения
|
||||
// ...
|
||||
|
||||
// Временный ответ для тестирования
|
||||
res.json({
|
||||
response: `Это тестовый ответ на ваше сообщение: "${message}". Сервер работает.`
|
||||
});
|
||||
res.json({ success: true, messageId });
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
logger.error('Error saving chat message:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
@@ -4,29 +4,29 @@ const { requireRole } = require('../middleware/auth');
|
||||
|
||||
// Получение информации о контрактах
|
||||
router.get('/', (req, res) => {
|
||||
res.json({
|
||||
res.json({
|
||||
message: 'Contracts API endpoint',
|
||||
contracts: [
|
||||
{
|
||||
name: 'AccessToken',
|
||||
address: process.env.ACCESS_TOKEN_ADDRESS
|
||||
}
|
||||
]
|
||||
address: process.env.ACCESS_TOKEN_ADDRESS,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
// Защищенный эндпоинт для получения детальной информации о контрактах
|
||||
router.get('/details', requireRole('ADMIN'), (req, res) => {
|
||||
res.json({
|
||||
res.json({
|
||||
message: 'Contract details endpoint',
|
||||
contracts: [
|
||||
{
|
||||
name: 'AccessToken',
|
||||
address: process.env.ACCESS_TOKEN_ADDRESS,
|
||||
network: process.env.ETHEREUM_NETWORK_URL.includes('sepolia') ? 'Sepolia' : 'Unknown'
|
||||
}
|
||||
]
|
||||
network: process.env.ETHEREUM_NETWORK_URL.includes('sepolia') ? 'Sepolia' : 'Unknown',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
0
backend/routes/conversations.js
Normal file
0
backend/routes/conversations.js
Normal file
@@ -1,189 +1,33 @@
|
||||
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 {
|
||||
console.log('Отладка сессии:', {
|
||||
sessionID: req.sessionID,
|
||||
session: req.session ? {
|
||||
isAuthenticated: req.session.isAuthenticated,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
} : null,
|
||||
cookies: req.cookies,
|
||||
headers: {
|
||||
cookie: req.headers.cookie
|
||||
}
|
||||
});
|
||||
|
||||
res.json({
|
||||
sessionID: req.sessionID,
|
||||
session: req.session ? {
|
||||
isAuthenticated: req.session.isAuthenticated,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
} : null,
|
||||
cookies: req.cookies
|
||||
});
|
||||
const result = await db.query('SELECT * FROM session');
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при отладке сессии:', error);
|
||||
console.error('Ошибка при получении данных из таблицы session:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Эндпоинт для создания тестовой сессии
|
||||
router.post('/create-session', (req, res) => {
|
||||
const { address } = req.body;
|
||||
|
||||
if (!address) {
|
||||
return res.status(400).json({ error: 'Address is required' });
|
||||
}
|
||||
|
||||
// Инициализируем сессию, если она не существует
|
||||
if (!req.session) {
|
||||
req.session = {};
|
||||
}
|
||||
|
||||
req.session.isAuthenticated = true;
|
||||
req.session.authenticated = true;
|
||||
req.session.address = address.toLowerCase();
|
||||
req.session.isAdmin = true;
|
||||
|
||||
// Сохраняем сессию
|
||||
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,
|
||||
sessionID: req.sessionID,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Тестовый эндпоинт для отправки сообщений без проверки сессии
|
||||
router.post('/test-chat', (req, res) => {
|
||||
try {
|
||||
const { message, address } = req.body;
|
||||
|
||||
console.log('Тестовый чат-запрос:', {
|
||||
message,
|
||||
address,
|
||||
headers: {
|
||||
cookie: req.headers.cookie,
|
||||
'content-type': req.headers['content-type']
|
||||
},
|
||||
cookies: req.cookies,
|
||||
session: req.session ? {
|
||||
isAuthenticated: req.session.isAuthenticated,
|
||||
authenticated: req.session.authenticated,
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
} : null
|
||||
});
|
||||
|
||||
if (!message) {
|
||||
return res.status(400).json({ error: 'Message is required' });
|
||||
}
|
||||
|
||||
// Возвращаем тестовый ответ
|
||||
res.json({
|
||||
response: `Тестовый ответ на сообщение: ${message}`,
|
||||
receivedAddress: address,
|
||||
sessionAddress: req.session?.address
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка в тестовом чате:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Тестовый эндпоинт для проверки соединения
|
||||
router.get('/ping', (req, res) => {
|
||||
res.json({
|
||||
message: 'pong',
|
||||
timestamp: new Date().toISOString(),
|
||||
server: {
|
||||
port: process.env.PORT || 8080,
|
||||
address: req.socket.localAddress,
|
||||
hostname: require('os').hostname()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Тестовый эндпоинт для проверки Ollama
|
||||
router.get('/ollama-test', async (req, res) => {
|
||||
try {
|
||||
const { directOllamaQuery } = require('../services/ollama');
|
||||
|
||||
// Тестовый запрос к Ollama
|
||||
const result = await directOllamaQuery('Привет, как дела?', 'mistral');
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
response: result,
|
||||
model: 'mistral'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при тестировании Ollama:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Ошибка при тестировании Ollama'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Тестовый эндпоинт для проверки доступности Ollama
|
||||
router.get('/ollama-status', async (req, res) => {
|
||||
try {
|
||||
const { checkOllamaAvailability } = require('../services/ollama');
|
||||
|
||||
// Проверяем доступность Ollama
|
||||
const isAvailable = await checkOllamaAvailability();
|
||||
|
||||
if (isAvailable) {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
message: 'Ollama доступен'
|
||||
});
|
||||
} else {
|
||||
res.status(503).json({
|
||||
status: 'error',
|
||||
message: 'Ollama недоступен'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке доступности Ollama:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: error.message || 'Ошибка при проверке доступности Ollama'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
@@ -6,11 +6,11 @@ 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(),
|
||||
@@ -18,19 +18,19 @@ router.get('/', async (req, res) => {
|
||||
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'
|
||||
heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB',
|
||||
},
|
||||
database: {
|
||||
connected: true,
|
||||
timestamp: dbResult.rows[0].now
|
||||
}
|
||||
timestamp: dbResult.rows[0].now,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
error: error.message
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
@@ -6,7 +6,7 @@ const { Pool } = require('pg');
|
||||
// Подключение к БД
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
// Middleware для проверки аутентификации
|
||||
@@ -21,20 +21,19 @@ function requireAuth(req, res, next) {
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
try {
|
||||
// Получаем ID пользователя по Ethereum-адресу
|
||||
const result = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[req.session.address]
|
||||
);
|
||||
|
||||
const result = await pool.query('SELECT id FROM users WHERE address = $1', [
|
||||
req.session.address,
|
||||
]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
|
||||
const userId = result.rows[0].id;
|
||||
|
||||
|
||||
// Получаем все идентификаторы пользователя
|
||||
const identities = await getUserIdentities(userId);
|
||||
|
||||
|
||||
res.json({ identities });
|
||||
} catch (error) {
|
||||
console.error('Error getting user identities:', error);
|
||||
@@ -46,25 +45,24 @@ router.get('/', requireAuth, async (req, res) => {
|
||||
router.delete('/:type/:value', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { type, value } = req.params;
|
||||
|
||||
|
||||
// Получаем ID пользователя по Ethereum-адресу
|
||||
const result = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[req.session.address]
|
||||
);
|
||||
|
||||
const result = await pool.query('SELECT id FROM users WHERE address = $1', [
|
||||
req.session.address,
|
||||
]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
|
||||
|
||||
const userId = result.rows[0].id;
|
||||
|
||||
|
||||
// Удаляем идентификатор
|
||||
await pool.query(
|
||||
'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) {
|
||||
console.error('Error deleting user identity:', error);
|
||||
@@ -72,4 +70,4 @@ router.delete('/:type/:value', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,340 +0,0 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Pool } = require('pg');
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
});
|
||||
|
||||
// Middleware для проверки аутентификации
|
||||
function requireAuth(req, res, next) {
|
||||
if (!req.session || (!req.session.isAuthenticated && !req.session.authenticated)) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
// Получение всех досок пользователя
|
||||
router.get('/boards', async (req, res) => {
|
||||
try {
|
||||
// Для разработки: если сессия не содержит адрес, используем тестовый
|
||||
const userAddress = (req.session.address || '0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b').toLowerCase();
|
||||
|
||||
console.log('Запрос досок для адреса:', userAddress);
|
||||
|
||||
// Проверяем, существует ли пользователь
|
||||
const userResult = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[userAddress]
|
||||
);
|
||||
|
||||
console.log('Результат запроса пользователя:', userResult.rows);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
console.log('Пользователь не найден, создаем нового');
|
||||
// Если пользователь не найден, создаем его
|
||||
const newUserResult = await pool.query(
|
||||
'INSERT INTO users (address, created_at) VALUES ($1, NOW()) RETURNING id',
|
||||
[userAddress]
|
||||
);
|
||||
console.log('Создан новый пользователь:', newUserResult.rows);
|
||||
}
|
||||
|
||||
// Получаем доски пользователя
|
||||
const ownBoardsQuery = 'SELECT kb.* FROM kanban_boards kb ' +
|
||||
'JOIN users u ON kb.owner_id = u.id ' +
|
||||
'WHERE u.address = $1 ' +
|
||||
'ORDER BY kb.updated_at DESC';
|
||||
|
||||
console.log('Запрос досок пользователя:', ownBoardsQuery);
|
||||
|
||||
const ownBoardsResult = await pool.query(ownBoardsQuery, [userAddress]);
|
||||
|
||||
console.log('Результат запроса досок пользователя:', ownBoardsResult.rows);
|
||||
|
||||
// Получаем доски, к которым у пользователя есть доступ
|
||||
const sharedBoardsResult = await pool.query(
|
||||
'SELECT kb.* FROM kanban_boards kb ' +
|
||||
'JOIN kanban_board_access kba ON kb.id = kba.board_id ' +
|
||||
'JOIN users u1 ON kba.user_id = u1.id ' +
|
||||
'JOIN users u2 ON kb.owner_id = u2.id ' +
|
||||
'WHERE u1.address = $1 AND u2.address != $1 ' +
|
||||
'ORDER BY kb.updated_at DESC',
|
||||
[userAddress]
|
||||
);
|
||||
|
||||
// Получаем публичные доски
|
||||
const publicBoardsResult = await pool.query(
|
||||
'SELECT kb.* FROM kanban_boards kb ' +
|
||||
'JOIN users u ON kb.owner_id = u.id ' +
|
||||
'WHERE kb.is_public = true AND u.address != $1 ' +
|
||||
'AND NOT EXISTS (' +
|
||||
' SELECT 1 FROM kanban_board_access kba ' +
|
||||
' JOIN users u2 ON kba.user_id = u2.id ' +
|
||||
' WHERE kba.board_id = kb.id AND u2.address = $1' +
|
||||
') ' +
|
||||
'ORDER BY kb.updated_at DESC',
|
||||
[userAddress]
|
||||
);
|
||||
|
||||
res.json({
|
||||
ownBoards: ownBoardsResult.rows,
|
||||
sharedBoards: sharedBoardsResult.rows,
|
||||
publicBoards: publicBoardsResult.rows
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching boards:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Создание новой доски
|
||||
router.post('/boards', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, description, isPublic } = req.body;
|
||||
|
||||
// Получаем ID пользователя
|
||||
let userResult = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[req.session.address]
|
||||
);
|
||||
|
||||
let userId;
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
// Если пользователь не найден, создаем его
|
||||
const newUserResult = await pool.query(
|
||||
'INSERT INTO users (address, created_at, preferred_language) VALUES ($1, NOW(), $2) RETURNING id',
|
||||
[req.session.address, 'ru']
|
||||
);
|
||||
|
||||
userId = newUserResult.rows[0].id;
|
||||
} else {
|
||||
userId = userResult.rows[0].id;
|
||||
}
|
||||
|
||||
// Создаем новую доску
|
||||
const result = await pool.query(
|
||||
`INSERT INTO kanban_boards (title, description, owner_id, is_public, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[title, description, userId, isPublic]
|
||||
);
|
||||
|
||||
// Создаем стандартные колонки
|
||||
const columns = ['Backlog', 'In Progress', 'Review', 'Done'];
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
await pool.query(
|
||||
`INSERT INTO kanban_columns (board_id, title, position, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, NOW(), NOW())`,
|
||||
[result.rows[0].id, columns[i], i]
|
||||
);
|
||||
}
|
||||
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (error) {
|
||||
console.error('Error creating kanban board:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получение конкретной доски со всеми колонками и карточками
|
||||
router.get('/boards/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const boardId = req.params.id;
|
||||
|
||||
// Получаем ID пользователя
|
||||
let userResult = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[req.session.address]
|
||||
);
|
||||
|
||||
let userId;
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
// Если пользователь не найден, создаем его
|
||||
const newUserResult = await pool.query(
|
||||
'INSERT INTO users (address, created_at, preferred_language) VALUES ($1, NOW(), $2) RETURNING id',
|
||||
[req.session.address, 'ru']
|
||||
);
|
||||
|
||||
userId = newUserResult.rows[0].id;
|
||||
} else {
|
||||
userId = userResult.rows[0].id;
|
||||
}
|
||||
|
||||
// Проверяем доступ к доске
|
||||
const boardResult = await pool.query(
|
||||
'SELECT * FROM kanban_boards WHERE id = $1',
|
||||
[boardId]
|
||||
);
|
||||
|
||||
if (boardResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Board not found' });
|
||||
}
|
||||
|
||||
const board = boardResult.rows[0];
|
||||
|
||||
// Проверяем, имеет ли пользователь доступ к доске
|
||||
if (board.owner_id !== userId && !board.is_public) {
|
||||
const accessResult = await pool.query(
|
||||
'SELECT * FROM kanban_board_access WHERE board_id = $1 AND user_id = $2',
|
||||
[boardId, userId]
|
||||
);
|
||||
|
||||
if (accessResult.rows.length === 0) {
|
||||
return res.status(403).json({ error: 'Access denied' });
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем колонки доски
|
||||
const columnsResult = await pool.query(
|
||||
'SELECT * FROM kanban_columns WHERE board_id = $1 ORDER BY position',
|
||||
[boardId]
|
||||
);
|
||||
|
||||
// Получаем карточки для всех колонок
|
||||
const cardsResult = await pool.query(
|
||||
`SELECT kc.*, u.address as assigned_address
|
||||
FROM kanban_cards kc
|
||||
LEFT JOIN users u ON kc.assigned_to = u.id
|
||||
WHERE kc.column_id IN (
|
||||
SELECT id FROM kanban_columns WHERE board_id = $1
|
||||
)
|
||||
ORDER BY kc.position`,
|
||||
[boardId]
|
||||
);
|
||||
|
||||
// Группируем карточки по колонкам
|
||||
const columns = columnsResult.rows.map(column => {
|
||||
const cards = cardsResult.rows.filter(card => card.column_id === column.id);
|
||||
return {
|
||||
...column,
|
||||
cards
|
||||
};
|
||||
});
|
||||
|
||||
res.json({
|
||||
...board,
|
||||
columns
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting kanban board:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавление колонки к доске
|
||||
router.post('/boards/:boardId/columns', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { boardId } = req.params;
|
||||
const { title, wipLimit } = req.body;
|
||||
|
||||
// Проверяем, существует ли доска
|
||||
const boardResult = await pool.query(
|
||||
'SELECT * FROM kanban_boards WHERE id = $1',
|
||||
[boardId]
|
||||
);
|
||||
|
||||
if (boardResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Board not found' });
|
||||
}
|
||||
|
||||
// Получаем максимальную позицию колонок
|
||||
const positionResult = await pool.query(
|
||||
'SELECT MAX(position) as max_position FROM kanban_columns WHERE board_id = $1',
|
||||
[boardId]
|
||||
);
|
||||
|
||||
const position = positionResult.rows[0].max_position ? positionResult.rows[0].max_position + 1 : 0;
|
||||
|
||||
// Создаем новую колонку
|
||||
const result = await pool.query(
|
||||
`INSERT INTO kanban_columns (board_id, title, position, wip_limit, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[boardId, title, position, wipLimit]
|
||||
);
|
||||
|
||||
res.status(201).json(result.rows[0]);
|
||||
} catch (error) {
|
||||
console.error('Error creating column:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Получение колонок доски
|
||||
router.get('/boards/:boardId/columns', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { boardId } = req.params;
|
||||
|
||||
const result = await pool.query(
|
||||
'SELECT * FROM kanban_columns WHERE board_id = $1 ORDER BY position',
|
||||
[boardId]
|
||||
);
|
||||
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
console.error('Error getting columns:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Создание карточки
|
||||
router.post('/cards', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { title, description, columnId, dueDate } = req.body;
|
||||
|
||||
// Получаем ID пользователя
|
||||
let userResult = await pool.query(
|
||||
'SELECT id FROM users WHERE address = $1',
|
||||
[req.session.address]
|
||||
);
|
||||
|
||||
let userId;
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
// Если пользователь не найден, создаем его
|
||||
const newUserResult = await pool.query(
|
||||
'INSERT INTO users (address, created_at, preferred_language) VALUES ($1, NOW(), $2) RETURNING id',
|
||||
[req.session.address, 'ru']
|
||||
);
|
||||
|
||||
userId = newUserResult.rows[0].id;
|
||||
} else {
|
||||
userId = userResult.rows[0].id;
|
||||
}
|
||||
|
||||
// Получаем максимальную позицию карточек в колонке
|
||||
const positionResult = await pool.query(
|
||||
'SELECT MAX(position) as max_position FROM kanban_cards WHERE column_id = $1',
|
||||
[columnId]
|
||||
);
|
||||
|
||||
const position = positionResult.rows[0].max_position ? positionResult.rows[0].max_position + 1 : 0;
|
||||
|
||||
// Создаем новую карточку
|
||||
const result = await pool.query(
|
||||
`INSERT INTO kanban_cards (column_id, title, description, position, due_date, created_by, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
|
||||
RETURNING *`,
|
||||
[columnId, title, description, position, dueDate, userId]
|
||||
);
|
||||
|
||||
// Получаем информацию о пользователе для отображения
|
||||
const cardWithUser = {
|
||||
...result.rows[0],
|
||||
assigned_address: null
|
||||
};
|
||||
|
||||
res.status(201).json(cardWithUser);
|
||||
} catch (error) {
|
||||
console.error('Error creating card:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем остальные маршруты для работы с колонками, карточками и т.д.
|
||||
// ...
|
||||
|
||||
module.exports = router;
|
||||
246
backend/routes/messages.js
Normal file
246
backend/routes/messages.js
Normal file
@@ -0,0 +1,246 @@
|
||||
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;
|
||||
56
backend/routes/roles.js
Normal file
56
backend/routes/roles.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const { requireAuth, requireAdmin } = require('../middleware/auth');
|
||||
const { checkTokenBalanceAndUpdateRole } = require('../utils/access-check');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
// Маршрут для проверки и обновления роли пользователя
|
||||
router.post('/check-role', requireAuth, async (req, res) => {
|
||||
try {
|
||||
if (!req.session.address) {
|
||||
return res.status(400).json({ error: 'В сессии отсутствует адрес кошелька' });
|
||||
}
|
||||
|
||||
const isAdmin = await checkTokenBalanceAndUpdateRole(req.session.address);
|
||||
|
||||
// Обновление сессии
|
||||
req.session.isAdmin = isAdmin;
|
||||
|
||||
res.json({ isAdmin });
|
||||
} catch (error) {
|
||||
logger.error('Error checking role:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения всех ролей (только для админов)
|
||||
router.get('/', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const result = await db.query('SELECT * FROM roles ORDER BY id');
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching roles:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
// Маршрут для получения пользователей с их ролями (только для админов)
|
||||
router.get('/users', requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT u.id, u.username, u.preferred_language, r.name as role,
|
||||
u.created_at, u.last_token_check
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
ORDER BY u.created_at DESC
|
||||
`);
|
||||
|
||||
res.json(result.rows);
|
||||
} catch (error) {
|
||||
logger.error('Error fetching users with roles:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../db');
|
||||
const logger = require('../utils/logger');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
|
||||
// Получение списка пользователей
|
||||
router.get('/', (req, res) => {
|
||||
@@ -9,10 +12,35 @@ router.get('/', (req, res) => {
|
||||
// Получение информации о пользователе
|
||||
router.get('/:address', (req, res) => {
|
||||
const { address } = req.params;
|
||||
res.json({
|
||||
res.json({
|
||||
address,
|
||||
message: 'User details endpoint'
|
||||
message: 'User details endpoint',
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
// Маршрут для обновления языка пользователя
|
||||
router.post('/update-language', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { language } = req.body;
|
||||
const userId = req.session.userId;
|
||||
|
||||
// Проверка валидности языка
|
||||
const validLanguages = ['ru', 'en'];
|
||||
if (!validLanguages.includes(language)) {
|
||||
return res.status(400).json({ error: 'Неподдерживаемый язык' });
|
||||
}
|
||||
|
||||
// Обновление языка в базе данных
|
||||
await db.query(
|
||||
'UPDATE users SET preferred_language = $1 WHERE id = $2',
|
||||
[language, userId]
|
||||
);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Error updating language:', error);
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -4,14 +4,9 @@ const path = require('path');
|
||||
const packageJson = require('../package.json');
|
||||
const dependencies = packageJson.dependencies || {};
|
||||
|
||||
const requiredDependencies = [
|
||||
'express-rate-limit',
|
||||
'winston',
|
||||
'helmet',
|
||||
'csurf'
|
||||
];
|
||||
const requiredDependencies = ['express-rate-limit', 'winston', 'helmet', 'csurf'];
|
||||
|
||||
const missingDependencies = requiredDependencies.filter(dep => !dependencies[dep]);
|
||||
const missingDependencies = requiredDependencies.filter((dep) => !dependencies[dep]);
|
||||
|
||||
if (missingDependencies.length > 0) {
|
||||
console.error('Missing dependencies:', missingDependencies);
|
||||
@@ -19,4 +14,4 @@ if (missingDependencies.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('All required dependencies are installed.');
|
||||
console.log('All required dependencies are installed.');
|
||||
|
||||
125
backend/scripts/check-ethers-v6-compatibility.js
Normal file
125
backend/scripts/check-ethers-v6-compatibility.js
Normal file
@@ -0,0 +1,125 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { promisify } = require('util');
|
||||
const readFile = promisify(fs.readFile);
|
||||
const readdir = promisify(fs.readdir);
|
||||
const stat = promisify(fs.stat);
|
||||
|
||||
// Паттерны для поиска несовместимых конструкций ethers.js v5
|
||||
const patterns = [
|
||||
'ethers.providers.JsonRpcProvider',
|
||||
'ethers.providers.Web3Provider',
|
||||
'ethers.utils.parseEther',
|
||||
'ethers.utils.formatEther',
|
||||
'ethers.utils.formatUnits',
|
||||
'ethers.utils.parseUnits',
|
||||
'ethers.utils.verifyMessage',
|
||||
'ethers.utils.keccak256',
|
||||
'ethers.utils.toUtf8Bytes',
|
||||
'ethers.utils.arrayify',
|
||||
'ethers.utils.hexlify',
|
||||
'ethers.BigNumber.from',
|
||||
'ethers.constants.Zero',
|
||||
'ethers.constants.One',
|
||||
'ethers.constants.Two',
|
||||
'ethers.constants.MaxUint256',
|
||||
'ethers.constants.AddressZero',
|
||||
'ethers.constants.HashZero',
|
||||
];
|
||||
|
||||
// Соответствующие замены для ethers.js v6.x
|
||||
const replacements = [
|
||||
'ethers.JsonRpcProvider',
|
||||
'ethers.BrowserProvider',
|
||||
'ethers.parseEther',
|
||||
'ethers.formatEther',
|
||||
'ethers.formatUnits',
|
||||
'ethers.parseUnits',
|
||||
'ethers.verifyMessage',
|
||||
'ethers.keccak256',
|
||||
'ethers.toUtf8Bytes',
|
||||
'ethers.getBytes',
|
||||
'ethers.hexlify',
|
||||
'ethers.getBigInt',
|
||||
'ethers.ZeroAddress',
|
||||
'ethers.ZeroAddress',
|
||||
'ethers.ZeroAddress',
|
||||
'ethers.MaxUint256',
|
||||
'ethers.ZeroAddress',
|
||||
'ethers.ZeroHash',
|
||||
];
|
||||
|
||||
// Функция для рекурсивного обхода директории
|
||||
async function walkDir(dir, fileList = []) {
|
||||
const files = await readdir(dir);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(dir, file);
|
||||
const fileStat = await stat(filePath);
|
||||
|
||||
if (fileStat.isDirectory()) {
|
||||
// Пропускаем node_modules и .git
|
||||
if (file !== 'node_modules' && file !== '.git') {
|
||||
fileList = await walkDir(filePath, fileList);
|
||||
}
|
||||
} else if (file.endsWith('.js')) {
|
||||
fileList.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
// Функция для проверки файла
|
||||
async function checkFile(filePath) {
|
||||
try {
|
||||
if (filePath.includes('check-ethers-v6-compatibility.js')) {
|
||||
return false; // Пропускаем проверку самого скрипта
|
||||
}
|
||||
|
||||
const content = await readFile(filePath, 'utf8');
|
||||
let hasIssues = false;
|
||||
|
||||
for (let i = 0; i < patterns.length; i++) {
|
||||
if (content.includes(patterns[i])) {
|
||||
console.log(`\x1b[33mПроблема в файле ${filePath}:\x1b[0m`);
|
||||
console.log(` Найдено: \x1b[31m${patterns[i]}\x1b[0m`);
|
||||
console.log(` Заменить на: \x1b[32m${replacements[i]}\x1b[0m`);
|
||||
hasIssues = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasIssues;
|
||||
} catch (error) {
|
||||
console.error(`Ошибка при проверке файла ${filePath}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Основная функция
|
||||
async function main() {
|
||||
try {
|
||||
console.log('Проверка совместимости с ethers.js v6.x...');
|
||||
|
||||
const files = await walkDir(path.resolve(__dirname, '..'));
|
||||
let issuesFound = false;
|
||||
|
||||
for (const file of files) {
|
||||
const hasIssues = await checkFile(file);
|
||||
if (hasIssues) {
|
||||
issuesFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!issuesFound) {
|
||||
console.log('\x1b[32mПроблем не найдено. Код совместим с ethers.js v6.x\x1b[0m');
|
||||
} else {
|
||||
console.log('\n\x1b[33mНайдены проблемы совместимости с ethers.js v6.x\x1b[0m');
|
||||
console.log('Пожалуйста, обновите код в соответствии с рекомендациями выше.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке совместимости:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
34
backend/scripts/check-ollama-models.js
Normal file
34
backend/scripts/check-ollama-models.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const axios = require('axios');
|
||||
|
||||
async function checkOllamaModels() {
|
||||
try {
|
||||
console.log('Проверка доступных моделей Ollama...');
|
||||
|
||||
const baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
|
||||
const response = await axios.get(`${baseUrl}/api/tags`, {
|
||||
timeout: 5000, // 5 секунд таймаут
|
||||
});
|
||||
|
||||
if (response.status === 200 && response.data && response.data.models) {
|
||||
console.log('\nДоступные модели Ollama:');
|
||||
console.log('------------------------');
|
||||
|
||||
response.data.models.forEach((model) => {
|
||||
console.log(`- ${model.name}`);
|
||||
});
|
||||
|
||||
console.log('\nДля использования конкретной модели, укажите ее в .env файле:');
|
||||
console.log('OLLAMA_EMBEDDINGS_MODEL=mistral');
|
||||
console.log('OLLAMA_MODEL=mistral');
|
||||
} else {
|
||||
console.log('Не удалось получить список моделей');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке моделей Ollama:', error.message);
|
||||
console.log('\nУбедитесь, что Ollama запущен. Вы можете запустить его командой:');
|
||||
console.log('ollama serve');
|
||||
}
|
||||
}
|
||||
|
||||
// Запускаем проверку
|
||||
checkOllamaModels();
|
||||
@@ -1,34 +1,31 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const accessToken = await hre.ethers.getContractAt(
|
||||
"AccessToken",
|
||||
"0xF352c498cF0857F472dC473E4Dd39551E79B1063"
|
||||
'AccessToken',
|
||||
'0xF352c498cF0857F472dC473E4Dd39551E79B1063'
|
||||
);
|
||||
|
||||
const owner = await accessToken.owner();
|
||||
console.log("Contract owner:", owner);
|
||||
console.log('Contract owner:', owner);
|
||||
|
||||
// Проверяем все токены и их владельцев
|
||||
console.log("\nAll tokens:");
|
||||
console.log('\nAll tokens:');
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
try {
|
||||
const tokenOwner = await accessToken.ownerOf(i);
|
||||
console.log(`Token ${i} owner: ${tokenOwner}`);
|
||||
} catch (error) {
|
||||
if (!error.message.includes("invalid token ID")) {
|
||||
if (!error.message.includes('invalid token ID')) {
|
||||
console.log(`Token ${i} error:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем активные токены для всех известных адресов
|
||||
const addresses = [
|
||||
owner,
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||
];
|
||||
const addresses = [owner, '0x70997970C51812dc3A010C7d01b50e0d17dc79C8'];
|
||||
|
||||
console.log("\nActive tokens:");
|
||||
console.log('\nActive tokens:');
|
||||
for (const address of addresses) {
|
||||
const activeToken = await accessToken.activeTokens(address);
|
||||
console.log(`${address}: Token ${activeToken.toString()}`);
|
||||
@@ -40,4 +37,4 @@ main()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
21
backend/scripts/check-tokens.js
Normal file
21
backend/scripts/check-tokens.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,42 +1,41 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const accessToken = await hre.ethers.getContractAt(
|
||||
"AccessToken",
|
||||
"0xF352c498cF0857F472dC473E4Dd39551E79B1063"
|
||||
'AccessToken',
|
||||
'0xF352c498cF0857F472dC473E4Dd39551E79B1063'
|
||||
);
|
||||
|
||||
const moderatorAddress = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
|
||||
|
||||
const moderatorAddress = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
|
||||
|
||||
try {
|
||||
console.log("\nMinting moderator token...");
|
||||
console.log('\nMinting moderator token...');
|
||||
const mintTx = await accessToken.mintAccessToken(moderatorAddress, 1); // MODERATOR
|
||||
console.log("Waiting for transaction:", mintTx.hash);
|
||||
console.log('Waiting for transaction:', mintTx.hash);
|
||||
await mintTx.wait();
|
||||
console.log("Moderator token minted");
|
||||
console.log('Moderator token minted');
|
||||
|
||||
// Проверяем результат
|
||||
const activeToken = await accessToken.activeTokens(moderatorAddress);
|
||||
console.log(`Moderator's active token: ${activeToken}`);
|
||||
|
||||
const role = await accessToken.checkRole(moderatorAddress);
|
||||
console.log(`Moderator role: ${["ADMIN", "MODERATOR", "SUPPORT"][role]}`);
|
||||
console.log(`Moderator role: ${['ADMIN', 'MODERATOR', 'SUPPORT'][role]}`);
|
||||
} catch (error) {
|
||||
console.log("Moderator token minting error:", error.message);
|
||||
console.log('Moderator token minting error:', error.message);
|
||||
}
|
||||
|
||||
// Проверяем все активные токены
|
||||
console.log("\nAll active tokens:");
|
||||
const addresses = [
|
||||
await accessToken.owner(),
|
||||
moderatorAddress
|
||||
];
|
||||
console.log('\nAll active tokens:');
|
||||
const addresses = [await accessToken.owner(), moderatorAddress];
|
||||
|
||||
for (const address of addresses) {
|
||||
try {
|
||||
const activeToken = await accessToken.activeTokens(address);
|
||||
const role = await accessToken.checkRole(address);
|
||||
console.log(`${address}: Token ${activeToken}, Role: ${["ADMIN", "MODERATOR", "SUPPORT"][role]}`);
|
||||
console.log(
|
||||
`${address}: Token ${activeToken}, Role: ${['ADMIN', 'MODERATOR', 'SUPPORT'][role]}`
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`${address}: ${error.message}`);
|
||||
}
|
||||
@@ -46,6 +45,6 @@ async function main() {
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error("Script error:", error);
|
||||
console.error('Script error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const AccessToken = await hre.ethers.getContractFactory("AccessToken");
|
||||
const AccessToken = await hre.ethers.getContractFactory('AccessToken');
|
||||
const accessToken = await AccessToken.deploy();
|
||||
await accessToken.waitForDeployment();
|
||||
|
||||
const address = await accessToken.getAddress();
|
||||
console.log("AccessToken deployed to:", address);
|
||||
console.log('AccessToken deployed to:', address);
|
||||
|
||||
// Создаем первый админский токен для владельца контракта
|
||||
const [owner] = await hre.ethers.getSigners();
|
||||
const tx = await accessToken.mintAccessToken(owner.address, 0); // 0 = ADMIN
|
||||
await tx.wait();
|
||||
console.log("Admin token minted for:", owner.address);
|
||||
console.log('Admin token minted for:', owner.address);
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -20,4 +20,4 @@ main()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
console.log("Начинаем деплой контракта...");
|
||||
console.log('Начинаем деплой контракта...');
|
||||
|
||||
// Получаем контракт
|
||||
const MyContract = await hre.ethers.getContractFactory("MyContract");
|
||||
|
||||
const MyContract = await hre.ethers.getContractFactory('MyContract');
|
||||
|
||||
// Деплоим контракт
|
||||
const myContract = await MyContract.deploy();
|
||||
await myContract.waitForDeployment();
|
||||
|
||||
const address = await myContract.getAddress();
|
||||
console.log("Контракт развернут по адресу:", address);
|
||||
console.log('Контракт развернут по адресу:', address);
|
||||
}
|
||||
|
||||
main()
|
||||
@@ -19,4 +19,4 @@ main()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,20 +5,20 @@ dotenv.config();
|
||||
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
async function initDb() {
|
||||
try {
|
||||
console.log('Инициализация базы данных...');
|
||||
|
||||
|
||||
// Добавляем тестового пользователя
|
||||
await pool.query(`
|
||||
INSERT INTO users (address, is_admin)
|
||||
VALUES ('0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b', TRUE)
|
||||
ON CONFLICT (address) DO NOTHING
|
||||
`);
|
||||
|
||||
|
||||
// Добавляем тестовую доску
|
||||
await pool.query(`
|
||||
INSERT INTO kanban_boards (title, description, owner_id, is_public)
|
||||
@@ -30,17 +30,18 @@ async function initDb() {
|
||||
)
|
||||
ON CONFLICT DO NOTHING
|
||||
`);
|
||||
|
||||
|
||||
// Получаем ID доски
|
||||
const boardResult = await pool.query(`
|
||||
SELECT id FROM kanban_boards WHERE title = 'Тестовая доска' LIMIT 1
|
||||
`);
|
||||
|
||||
|
||||
if (boardResult.rows.length > 0) {
|
||||
const boardId = boardResult.rows[0].id;
|
||||
|
||||
|
||||
// Добавляем тестовые колонки
|
||||
await pool.query(`
|
||||
await pool.query(
|
||||
`
|
||||
INSERT INTO kanban_columns (board_id, title, position)
|
||||
VALUES
|
||||
($1, 'Backlog', 0),
|
||||
@@ -48,9 +49,11 @@ async function initDb() {
|
||||
($1, 'Review', 2),
|
||||
($1, 'Done', 3)
|
||||
ON CONFLICT DO NOTHING
|
||||
`, [boardId]);
|
||||
`,
|
||||
[boardId]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
console.log('База данных инициализирована успешно');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при инициализации базы данных:', error);
|
||||
@@ -59,4 +62,4 @@ async function initDb() {
|
||||
}
|
||||
}
|
||||
|
||||
initDb();
|
||||
initDb();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const accessToken = await hre.ethers.getContractAt(
|
||||
"AccessToken",
|
||||
"0xF352c498cF0857F472dC473E4Dd39551E79B1063"
|
||||
'AccessToken',
|
||||
'0xF352c498cF0857F472dC473E4Dd39551E79B1063'
|
||||
);
|
||||
|
||||
const owner = await accessToken.owner();
|
||||
console.log("Contract owner:", owner);
|
||||
console.log('Contract owner:', owner);
|
||||
|
||||
// Создаем админский токен для владельца
|
||||
try {
|
||||
@@ -16,34 +16,34 @@ async function main() {
|
||||
console.log(`Admin token minted for ${owner}`);
|
||||
|
||||
const role = await accessToken.checkRole(owner);
|
||||
console.log("Owner role:", ["ADMIN", "MODERATOR", "SUPPORT"][role]);
|
||||
console.log('Owner role:', ['ADMIN', 'MODERATOR', 'SUPPORT'][role]);
|
||||
} catch (error) {
|
||||
console.log("Admin token minting error:", error.message);
|
||||
console.log('Admin token minting error:', error.message);
|
||||
}
|
||||
|
||||
// Создаем тестовый токен модератора
|
||||
const moderatorAddress = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"; // Тестовый адрес модератора
|
||||
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]);
|
||||
console.log('Moderator role:', ['ADMIN', 'MODERATOR', 'SUPPORT'][role]);
|
||||
} catch (error) {
|
||||
console.log("Moderator token minting error:", error.message);
|
||||
console.log('Moderator token minting error:', error.message);
|
||||
}
|
||||
|
||||
// Проверяем все токены
|
||||
console.log("\nChecking all tokens:");
|
||||
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]}`);
|
||||
console.log(`Token ${i}: Owner ${owner}, Role: ${['ADMIN', 'MODERATOR', 'SUPPORT'][role]}`);
|
||||
} catch (error) {
|
||||
// Пропускаем несуществующие токены
|
||||
if (!error.message.includes("nonexistent token")) {
|
||||
if (!error.message.includes('nonexistent token')) {
|
||||
console.log(`Token ${i} error:`, error.message);
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,4 @@ main()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,72 +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 ownerRole = await accessToken.checkRole(owner);
|
||||
console.log("Owner role:", ["ADMIN", "MODERATOR", "SUPPORT"][ownerRole]);
|
||||
} catch (error) {
|
||||
console.log("Owner role check error:", error.message);
|
||||
}
|
||||
|
||||
// Создадим токен модератора для тестового адреса
|
||||
const moderatorAddress = "0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B";
|
||||
try {
|
||||
const tx = await accessToken.mintAccessToken(moderatorAddress, 1); // 1 = MODERATOR
|
||||
await tx.wait();
|
||||
console.log(`Moderator token minted for ${moderatorAddress}`);
|
||||
|
||||
// Проверим роль модератора
|
||||
const modRole = await accessToken.checkRole(moderatorAddress);
|
||||
console.log("Moderator role:", ["ADMIN", "MODERATOR", "SUPPORT"][modRole]);
|
||||
} catch (error) {
|
||||
console.log("Moderator token minting error:", error.message);
|
||||
}
|
||||
|
||||
// Получим все активные токены (с ограничением по блокам)
|
||||
const currentBlock = await hre.ethers.provider.getBlockNumber();
|
||||
const fromBlock = currentBlock - 1000; // Последние 1000 блоков
|
||||
|
||||
const filter = accessToken.filters.Transfer(null, null, null);
|
||||
const events = await accessToken.queryFilter(filter, fromBlock);
|
||||
console.log("\nActive tokens (last 1000 blocks):");
|
||||
for (let event of events) {
|
||||
if (event.args.from === "0x0000000000000000000000000000000000000000") {
|
||||
console.log(`Token ID: ${event.args.tokenId}, Owner: ${event.args.to}`);
|
||||
try {
|
||||
const role = await accessToken.checkRole(event.args.to);
|
||||
console.log(`Role: ${["ADMIN", "MODERATOR", "SUPPORT"][role]}`);
|
||||
} catch (error) {
|
||||
console.log("Role check error:", error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Альтернативный способ - проверить конкретный токен
|
||||
console.log("\nChecking specific tokens:");
|
||||
for (let i = 1; i <= 2; 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) {
|
||||
console.log(`Token ${i} not found or error:`, error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => process.exit(0))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
const hre = require("hardhat");
|
||||
const hre = require('hardhat');
|
||||
|
||||
async function main() {
|
||||
const accessToken = await hre.ethers.getContractAt(
|
||||
"AccessToken",
|
||||
"0xF352c498cF0857F472dC473E4Dd39551E79B1063"
|
||||
'AccessToken',
|
||||
'0xF352c498cF0857F472dC473E4Dd39551E79B1063'
|
||||
);
|
||||
|
||||
// Отзываем все токены от 1 до 3
|
||||
@@ -23,4 +23,4 @@ main()
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,13 +6,13 @@ require('dotenv').config();
|
||||
// Подключение к БД
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
async function runMigrations() {
|
||||
try {
|
||||
console.log('Запуск миграций...');
|
||||
|
||||
|
||||
// Создаем таблицу для отслеживания миграций, если её нет
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
@@ -21,41 +21,39 @@ async function runMigrations() {
|
||||
applied_at TIMESTAMP DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
|
||||
// Получаем список уже примененных миграций
|
||||
const { rows } = await pool.query('SELECT name FROM migrations');
|
||||
const appliedMigrations = rows.map(row => row.name);
|
||||
|
||||
const appliedMigrations = rows.map((row) => row.name);
|
||||
|
||||
// Получаем список файлов миграций
|
||||
const migrationsDir = path.join(__dirname, '../migrations');
|
||||
const migrationFiles = fs.readdirSync(migrationsDir)
|
||||
.filter(file => file.endsWith('.sql'))
|
||||
const migrationFiles = fs
|
||||
.readdirSync(migrationsDir)
|
||||
.filter((file) => file.endsWith('.sql'))
|
||||
.sort(); // Сортируем файлы по имени
|
||||
|
||||
|
||||
// Применяем миграции, которые еще не были применены
|
||||
for (const file of migrationFiles) {
|
||||
if (!appliedMigrations.includes(file)) {
|
||||
console.log(`Применение миграции: ${file}`);
|
||||
|
||||
|
||||
// Читаем содержимое файла миграции
|
||||
const filePath = path.join(migrationsDir, file);
|
||||
const sql = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
|
||||
// Выполняем SQL-запросы из файла
|
||||
await pool.query(sql);
|
||||
|
||||
|
||||
// Записываем информацию о примененной миграции
|
||||
await pool.query(
|
||||
'INSERT INTO migrations (name) VALUES ($1)',
|
||||
[file]
|
||||
);
|
||||
|
||||
await pool.query('INSERT INTO migrations (name) VALUES ($1)', [file]);
|
||||
|
||||
console.log(`Миграция ${file} успешно применена`);
|
||||
} else {
|
||||
console.log(`Миграция ${file} уже применена`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log('Все миграции успешно применены');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при выполнении миграций:', error);
|
||||
@@ -65,4 +63,4 @@ async function runMigrations() {
|
||||
}
|
||||
}
|
||||
|
||||
runMigrations();
|
||||
runMigrations();
|
||||
|
||||
53
backend/scripts/update-user-roles.js
Normal file
53
backend/scripts/update-user-roles.js
Normal file
@@ -0,0 +1,53 @@
|
||||
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);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ const express = require('express');
|
||||
const cors = require('cors');
|
||||
const { SiweMessage, generateNonce } = require('siwe');
|
||||
const { ethers } = require('ethers');
|
||||
const TelegramBotService = require('./services/telegramBot');
|
||||
// const TelegramBotService = require('./services/telegramBot');
|
||||
const EmailBotService = require('./services/emailBot');
|
||||
const { initializeVectorStore } = require('./services/vectorStore');
|
||||
const session = require('express-session');
|
||||
@@ -12,24 +12,27 @@ const usersRouter = require('./routes/users');
|
||||
const { router: authRouter } = require('./routes/auth');
|
||||
const contractsRouter = require('./routes/contracts');
|
||||
const accessRouter = require('./routes/access');
|
||||
const chatRouter = require('./routes/chat');
|
||||
const path = require('path');
|
||||
const axios = require('axios');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { getVectorStore } = require('./services/vectorStore');
|
||||
const debugRouter = require('./routes/debug');
|
||||
// const debugRoutes = require('./routes/debug');
|
||||
const identitiesRouter = require('./routes/identities');
|
||||
const kanbanRouter = require('./routes/kanban');
|
||||
const { pool } = require('./db');
|
||||
const fs = require('fs');
|
||||
const pgSession = require('connect-pg-simple')(session);
|
||||
const sessionStore = new pgSession({
|
||||
pool: pool,
|
||||
tableName: 'session',
|
||||
createTableIfMissing: true
|
||||
createTableIfMissing: true,
|
||||
});
|
||||
const helmet = require('helmet');
|
||||
const csrf = require('csurf');
|
||||
// const csrf = require('csurf');
|
||||
// const cookieParser = require('cookie-parser');
|
||||
const messagesRouter = require('./routes/messages');
|
||||
|
||||
// Импорт сервисов
|
||||
const { initTelegramBot } = require('./services/telegram-service');
|
||||
|
||||
const PORT = process.env.PORT || 8000;
|
||||
|
||||
@@ -50,24 +53,21 @@ const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||||
console.log('Provider URL:', process.env.ETHEREUM_NETWORK_URL);
|
||||
console.log('Contract address:', process.env.CONTRACT_ADDRESS);
|
||||
|
||||
const contract = new ethers.Contract(
|
||||
process.env.CONTRACT_ADDRESS,
|
||||
contractABI,
|
||||
provider
|
||||
);
|
||||
const contract = new ethers.Contract(process.env.CONTRACT_ADDRESS, contractABI, provider);
|
||||
|
||||
// Проверяем, что библиотека ethers.js правильно импортирована
|
||||
console.log('Ethers.js version:', ethers.version);
|
||||
|
||||
// Порядок middleware важен!
|
||||
// 1. CORS должен быть первым
|
||||
app.use(cors({
|
||||
origin: ['http://127.0.0.1:5173', 'http://localhost:5173'],
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||
exposedHeaders: ['Set-Cookie']
|
||||
}));
|
||||
app.use(
|
||||
cors({
|
||||
origin: ['http://localhost:5173', 'http://127.0.0.1:5173'],
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Auth-Nonce'],
|
||||
})
|
||||
);
|
||||
|
||||
// Добавьте после настройки CORS
|
||||
app.use(helmet());
|
||||
@@ -77,18 +77,23 @@ app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 3. Затем сессии
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
maxAge: 24 * 60 * 60 * 1000
|
||||
},
|
||||
store: sessionStore
|
||||
}));
|
||||
app.use(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET || 'your-secret-key',
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production', // В разработке можно установить false
|
||||
sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
|
||||
maxAge: 24 * 60 * 60 * 1000, // 1 день
|
||||
},
|
||||
store: new pgSession({
|
||||
pool: pool,
|
||||
tableName: 'session',
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// Добавьте после настройки сессий
|
||||
app.use((req, res, next) => {
|
||||
@@ -164,76 +169,67 @@ app.use((req, res, next) => {
|
||||
|
||||
// Добавляем middleware для отладки сессий
|
||||
app.use((req, res, next) => {
|
||||
// console.log('Session debug:', {
|
||||
// url: req.url,
|
||||
// method: req.method,
|
||||
// sessionID: req.sessionID,
|
||||
// cookies: req.headers.cookie,
|
||||
// session: req.session ? {
|
||||
// isAuthenticated: req.session.isAuthenticated,
|
||||
// authenticated: req.session.authenticated,
|
||||
// address: req.session.address,
|
||||
// isAdmin: req.session.isAdmin,
|
||||
// nonce: req.session.nonce ? '[REDACTED]' : undefined,
|
||||
// pendingAddress: req.session.pendingAddress
|
||||
// } : null
|
||||
// });
|
||||
console.log('Сессия:', req.session);
|
||||
console.log('Куки:', req.headers.cookie);
|
||||
next();
|
||||
});
|
||||
|
||||
// Настройка CSRF-защиты
|
||||
const csrfProtection = csrf({
|
||||
cookie: {
|
||||
key: '_csrf',
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production', // true в production, false в development
|
||||
sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax'
|
||||
}
|
||||
});
|
||||
// Добавьте cookie-парсер перед CSRF-защитой
|
||||
// app.use(cookieParser());
|
||||
|
||||
// Затем настройте CSRF-защиту
|
||||
// const csrfProtection = csrf({
|
||||
// cookie: {
|
||||
// key: '_csrf',
|
||||
// path: '/',
|
||||
// httpOnly: true,
|
||||
// secure: process.env.NODE_ENV === 'production',
|
||||
// sameSite: 'lax'
|
||||
// }
|
||||
// });
|
||||
|
||||
// Добавьте маршрут для получения CSRF-токена
|
||||
// app.get('/api/csrf-token', csrfProtection, (req, res) => {
|
||||
// res.json({ csrfToken: req.csrfToken() });
|
||||
// });
|
||||
|
||||
// Применяем CSRF-защиту только к определенным маршрутам
|
||||
app.use('/api/protected', csrfProtection);
|
||||
app.use('/api/admin', csrfProtection);
|
||||
app.use('/api/kanban', csrfProtection);
|
||||
|
||||
// Маршрут для получения CSRF-токена
|
||||
app.get('/api/csrf-token', csrfProtection, (req, res) => {
|
||||
res.json({ csrfToken: req.csrfToken() });
|
||||
});
|
||||
// app.use('/api/protected', csrfProtection);
|
||||
// app.use('/api/admin', csrfProtection);
|
||||
// app.use('/api/kanban', csrfProtection);
|
||||
|
||||
// Обработчик ошибок CSRF
|
||||
app.use((err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN') {
|
||||
console.error('CSRF error:', {
|
||||
url: req.url,
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body: req.body
|
||||
});
|
||||
return res.status(403).json({
|
||||
error: 'CSRF token validation failed',
|
||||
message: 'Your session may have expired. Please refresh the page and try again.'
|
||||
});
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
// app.use((err, req, res, next) => {
|
||||
// if (err.code === 'EBADCSRFTOKEN') {
|
||||
// console.error('CSRF error:', {
|
||||
// url: req.url,
|
||||
// method: req.method,
|
||||
// headers: req.headers,
|
||||
// body: req.body
|
||||
// });
|
||||
// return res.status(403).json({
|
||||
// error: 'CSRF token validation failed',
|
||||
// message: 'Your session may have expired. Please refresh the page and try again.'
|
||||
// });
|
||||
// }
|
||||
// next(err);
|
||||
// });
|
||||
|
||||
async function initServices() {
|
||||
try {
|
||||
console.log('Инициализация сервисов...');
|
||||
|
||||
|
||||
// Инициализируем ботов, если они нужны
|
||||
if (process.env.TELEGRAM_BOT_TOKEN) {
|
||||
telegramBot = new TelegramBotService(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);
|
||||
@@ -244,10 +240,10 @@ app.use('/api/users', usersRouter);
|
||||
app.use('/api/auth', authRouter);
|
||||
app.use('/api/contracts', contractsRouter);
|
||||
app.use('/api/access', accessRouter);
|
||||
app.use('/api/chat', chatRouter);
|
||||
app.use('/api/debug', debugRouter);
|
||||
// app.use('/api/chat', chatRouter);
|
||||
// app.use('/api/debug', debugRoutes);
|
||||
app.use('/api/identities', identitiesRouter);
|
||||
app.use('/api/kanban', kanbanRouter);
|
||||
app.use('/api/messages', messagesRouter);
|
||||
|
||||
// Добавьте простой эндпоинт для проверки состояния сервера
|
||||
app.get('/api/health', (req, res) => {
|
||||
@@ -259,81 +255,81 @@ app.post('/api/verify', async (req, res) => {
|
||||
try {
|
||||
// Перенаправляем запрос на /api/auth/verify
|
||||
const { message, signature } = req.body;
|
||||
console.log("Перенаправление запроса на /api/auth/verify:", { message, signature });
|
||||
|
||||
console.log('Перенаправление запроса на /api/auth/verify:', { message, signature });
|
||||
|
||||
// Проверяем наличие необходимых данных
|
||||
if (!message || !message.address || !signature) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Отсутствуют необходимые данные для верификации'
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Отсутствуют необходимые данные для верификации',
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const address = message.address.toLowerCase();
|
||||
console.log("Адрес из сообщения:", address);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
// Сохраняем сессию
|
||||
req.session.save();
|
||||
} 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
|
||||
}
|
||||
isAdmin: req.session.isAdmin,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
res.cookie('authToken', 'true', {
|
||||
maxAge: 86400000,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: 'lax',
|
||||
path: '/'
|
||||
path: '/',
|
||||
});
|
||||
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
address: address,
|
||||
isAdmin: isAdmin
|
||||
isAdmin: isAdmin,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Ошибка верификации:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Внутренняя ошибка сервера'
|
||||
console.error('Ошибка верификации:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message || 'Внутренняя ошибка сервера',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -345,22 +341,22 @@ app.get('/api/session', (req, res) => {
|
||||
sessionID: req.sessionID,
|
||||
isAuthenticated: req.session?.isAuthenticated,
|
||||
authenticated: req.session?.authenticated,
|
||||
address: req.session?.address
|
||||
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
|
||||
isAdmin: req.session.isAdmin,
|
||||
});
|
||||
} else {
|
||||
res.json({
|
||||
isAuthenticated: false,
|
||||
authenticated: false,
|
||||
address: null,
|
||||
isAdmin: false
|
||||
isAdmin: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -380,89 +376,45 @@ app.get('/api/public', (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/api/protected', (req, res) => {
|
||||
res.json({
|
||||
res.json({
|
||||
message: 'This is a protected API endpoint',
|
||||
user: {
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
}
|
||||
isAdmin: req.session.isAdmin,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/admin', (req, res) => {
|
||||
res.json({
|
||||
res.json({
|
||||
message: 'This is an admin API endpoint',
|
||||
user: {
|
||||
address: req.session.address,
|
||||
isAdmin: req.session.isAdmin
|
||||
}
|
||||
isAdmin: req.session.isAdmin,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Добавьте обработчик ошибок
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Глобальный обработчик ошибок:', err);
|
||||
|
||||
// Обработка ошибок CSRF
|
||||
if (err.code === 'EBADCSRFTOKEN') {
|
||||
return res.status(403).json({
|
||||
error: 'Недействительный CSRF-токен',
|
||||
message: 'Возможно, ваша сессия истекла. Пожалуйста, обновите страницу и попробуйте снова.'
|
||||
});
|
||||
console.error('Глобальная ошибка:', err.stack);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: 'Внутренняя ошибка сервера' });
|
||||
}
|
||||
|
||||
// Обработка ошибок валидации
|
||||
if (err.name === 'ValidationError') {
|
||||
return res.status(400).json({
|
||||
error: 'Ошибка валидации',
|
||||
details: err.details || err.message
|
||||
});
|
||||
}
|
||||
|
||||
// Обработка ошибок базы данных
|
||||
if (err.code === '23505') { // Postgres unique violation
|
||||
return res.status(409).json({
|
||||
error: 'Конфликт данных',
|
||||
message: 'Запись с такими данными уже существует.'
|
||||
});
|
||||
}
|
||||
|
||||
// Общая обработка ошибок
|
||||
res.status(err.status || 500).json({
|
||||
error: 'Внутренняя ошибка сервера',
|
||||
message: process.env.NODE_ENV === 'production' ? 'Что-то пошло не так' : err.message
|
||||
});
|
||||
});
|
||||
|
||||
// Перед запуском сервера
|
||||
console.log('Перед запуском сервера на порту:', PORT);
|
||||
|
||||
// Запуск сервера и инициализация сервисов
|
||||
const server = app.listen(PORT, '0.0.0.0', async () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
console.log('Server address:', server.address());
|
||||
|
||||
// Инициализируем сервисы без блокировки запуска сервера
|
||||
initServices().catch(err => {
|
||||
console.error('Ошибка при инициализации сервисов:', err);
|
||||
let server;
|
||||
|
||||
checkDatabaseStructure().then(() => {
|
||||
// Запускаем сервер
|
||||
server = app.listen(PORT, () => {
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
console.log('Server address:', server.address());
|
||||
});
|
||||
|
||||
// Проверяем доступность Ollama в фоновом режиме
|
||||
try {
|
||||
const { checkOllamaAvailability } = require('./services/ollama');
|
||||
checkOllamaAvailability().catch(err => {
|
||||
console.error('Ошибка при проверке Ollama:', err);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при импорте модуля Ollama:', error);
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`Port ${PORT} is already in use. Please try another port.`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.error('Server error:', err);
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем graceful shutdown
|
||||
@@ -480,7 +432,7 @@ async function checkOllamaServer() {
|
||||
const response = await axios.get('http://localhost:11434/api/tags');
|
||||
if (response.status === 200) {
|
||||
console.log('Ollama сервер доступен');
|
||||
|
||||
|
||||
// Тестируем прямой запрос к Ollama
|
||||
try {
|
||||
console.log('Тестируем прямой запрос к Ollama...');
|
||||
@@ -489,13 +441,13 @@ async function checkOllamaServer() {
|
||||
model: 'llama3',
|
||||
temperature: 0.2,
|
||||
});
|
||||
|
||||
|
||||
const result = await model.invoke('Привет, как дела?');
|
||||
console.log('Ответ от Ollama:', result);
|
||||
} catch (testError) {
|
||||
console.error('Ошибка при тестировании Ollama:', testError);
|
||||
}
|
||||
|
||||
|
||||
// Инициализируем векторное хранилище
|
||||
try {
|
||||
console.log('Инициализируем векторное хранилище...');
|
||||
@@ -504,7 +456,7 @@ async function checkOllamaServer() {
|
||||
} catch (vectorError) {
|
||||
console.error('Ошибка при инициализации векторного хранилища:', vectorError);
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -515,15 +467,16 @@ async function checkOllamaServer() {
|
||||
}
|
||||
|
||||
// Настройка периодической очистки устаревших сессий
|
||||
const pgSessionCleanup = setInterval(function() {
|
||||
const pgSessionCleanup = setInterval(function () {
|
||||
console.log('Cleaning up expired sessions...');
|
||||
pool.query('DELETE FROM session WHERE expire < NOW()')
|
||||
.then(result => {
|
||||
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));
|
||||
.catch((err) => console.error('Error cleaning up sessions:', err));
|
||||
}, 3600000); // Очистка каждый час
|
||||
|
||||
// Очистка интервала при завершении работы
|
||||
@@ -547,11 +500,11 @@ async function ensureTablesExist() {
|
||||
AND table_name = 'users'
|
||||
);
|
||||
`);
|
||||
|
||||
|
||||
// Если таблица не существует, создаем все таблицы
|
||||
if (!result.rows[0].exists) {
|
||||
console.log('Таблицы не найдены, создаем...');
|
||||
|
||||
|
||||
// SQL-запросы для создания таблиц
|
||||
const createTablesSql = `
|
||||
-- Таблица пользователей
|
||||
@@ -631,7 +584,7 @@ async function ensureTablesExist() {
|
||||
-- Индекс для таблицы сообщений
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_messages_user ON chat_messages(user_id);
|
||||
`;
|
||||
|
||||
|
||||
await pool.query(createTablesSql);
|
||||
console.log('Таблицы успешно созданы');
|
||||
} else {
|
||||
@@ -643,7 +596,7 @@ async function ensureTablesExist() {
|
||||
}
|
||||
|
||||
// Вызываем функцию при запуске сервера
|
||||
ensureTablesExist();
|
||||
ensureTablesExist();
|
||||
|
||||
// Добавляем middleware для проверки аутентификации
|
||||
app.use('/api/protected', (req, res, next) => {
|
||||
@@ -652,11 +605,11 @@ app.use('/api/protected', (req, res, next) => {
|
||||
// authenticated: req.session.authenticated,
|
||||
// address: req.session.address
|
||||
// });
|
||||
|
||||
|
||||
if (!req.session.authenticated) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
@@ -667,10 +620,117 @@ app.use('/api/admin', (req, res, next) => {
|
||||
// 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.on('SIGINT', () => {
|
||||
console.log('Получен сигнал SIGINT, завершаем работу...');
|
||||
server.close(() => {
|
||||
console.log('Сервер остановлен');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
console.log('Получен сигнал SIGTERM, завершаем работу...');
|
||||
server.close(() => {
|
||||
console.log('Сервер остановлен');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
// Обработка необработанных исключений
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Необработанное исключение:', error);
|
||||
// Не завершаем процесс, чтобы nodemon мог перезапустить сервер
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Необработанное отклонение промиса:', reason);
|
||||
// Не завершаем процесс, чтобы nodemon мог перезапустить сервер
|
||||
});
|
||||
|
||||
// Инициализация Telegram бота
|
||||
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 sessions 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 sessions WHERE expire < NOW()');
|
||||
console.log(`Первоначальная очистка: удалено ${result.rowCount} устаревших сессий`);
|
||||
} catch (err) {
|
||||
console.error('Ошибка при первоначальной очистке сессий:', err);
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
158
backend/services/ai-assistant.js
Normal file
158
backend/services/ai-assistant.js
Normal file
@@ -0,0 +1,158 @@
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { pool } = require('../db');
|
||||
|
||||
// Инициализация модели Ollama
|
||||
const model = new ChatOllama({
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
||||
model: process.env.OLLAMA_MODEL || 'llama2',
|
||||
});
|
||||
|
||||
/**
|
||||
* Обработка сообщения пользователя и получение ответа от ИИ
|
||||
* @param {number} userId - ID пользователя
|
||||
* @param {string} message - Текст сообщения
|
||||
* @param {string} language - Язык пользователя
|
||||
* @returns {Promise<string>} - Ответ ИИ
|
||||
*/
|
||||
async function processMessage(userId, message, language = 'ru') {
|
||||
try {
|
||||
// Получение информации о пользователе
|
||||
const userInfo = await getUserInfo(userId);
|
||||
|
||||
// Получение истории диалога (последние 10 сообщений)
|
||||
const history = await getConversationHistory(userId);
|
||||
|
||||
// Формирование контекста для ИИ
|
||||
const context = `
|
||||
Пользователь: ${userInfo.username || 'Пользователь'} (ID: ${userId})
|
||||
Язык: ${language}
|
||||
Роль: ${userInfo.is_admin ? 'Администратор' : 'Пользователь'}
|
||||
История диалога:
|
||||
${history}
|
||||
|
||||
Текущее сообщение: ${message}
|
||||
`;
|
||||
|
||||
// Временная заглушка для ответа ИИ
|
||||
// В будущем здесь будет интеграция с реальной моделью ИИ
|
||||
const responses = {
|
||||
ru: [
|
||||
'Спасибо за ваше сообщение! Чем я могу помочь?',
|
||||
'Я понимаю ваш запрос. Давайте разберемся с этим вопросом.',
|
||||
'Интересный вопрос! Вот что я могу предложить...',
|
||||
'Я обработал вашу информацию. Есть ли у вас дополнительные вопросы?',
|
||||
'Я готов помочь вам с этим запросом. Нужны ли дополнительные детали?',
|
||||
],
|
||||
en: [
|
||||
'Thank you for your message! How can I help you?',
|
||||
"I understand your request. Let's figure this out.",
|
||||
"Interesting question! Here's what I can suggest...",
|
||||
"I've processed your information. Do you have any additional questions?",
|
||||
"I'm ready to help you with this request. Do you need any additional details?",
|
||||
],
|
||||
};
|
||||
|
||||
const langResponses = responses[language] || responses['ru'];
|
||||
const randomIndex = Math.floor(Math.random() * langResponses.length);
|
||||
|
||||
// Имитация задержки ответа ИИ
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return langResponses[randomIndex];
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
return 'Извините, произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте еще раз позже.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение информации о пользователе
|
||||
* @param {number} userId - ID пользователя
|
||||
* @returns {Promise<Object>} - Информация о пользователе
|
||||
*/
|
||||
async function getUserInfo(userId) {
|
||||
try {
|
||||
const userResult = await pool.query(
|
||||
`SELECT u.id, u.username, u.address, u.is_admin, u.language, r.name as role
|
||||
FROM users u
|
||||
JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.id = $1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
return { id: userId };
|
||||
}
|
||||
|
||||
// Получение идентификаторов пользователя
|
||||
const identitiesResult = await pool.query(
|
||||
`SELECT identity_type, identity_value, verified
|
||||
FROM user_identities
|
||||
WHERE user_id = $1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
const user = userResult.rows[0];
|
||||
user.identities = identitiesResult.rows;
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error getting user info:', error);
|
||||
return { id: userId };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение истории диалога
|
||||
* @param {number} userId - ID пользователя
|
||||
* @param {number} limit - Максимальное количество сообщений
|
||||
* @returns {Promise<string>} - История диалога в текстовом формате
|
||||
*/
|
||||
async function getConversationHistory(userId, limit = 10) {
|
||||
try {
|
||||
// Получение последнего активного диалога пользователя
|
||||
const conversationResult = await pool.query(
|
||||
`SELECT id FROM conversations
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const conversationId = conversationResult.rows[0].id;
|
||||
|
||||
// Получение последних сообщений из диалога
|
||||
const messagesResult = await pool.query(
|
||||
`SELECT sender_type, content, created_at
|
||||
FROM messages
|
||||
WHERE conversation_id = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2`,
|
||||
[conversationId, limit]
|
||||
);
|
||||
|
||||
// Формирование истории в текстовом формате
|
||||
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,
|
||||
getUserInfo,
|
||||
getConversationHistory,
|
||||
};
|
||||
@@ -1,47 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { Document } = require('langchain/document');
|
||||
const { RecursiveCharacterTextSplitter } = require('langchain/text_splitter');
|
||||
|
||||
// Функция для загрузки документов из файлов
|
||||
async function loadDocumentsFromFiles(directory) {
|
||||
const documents = [];
|
||||
|
||||
try {
|
||||
const files = fs.readdirSync(directory);
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(directory, file);
|
||||
const stat = fs.statSync(filePath);
|
||||
|
||||
if (stat.isFile() && (file.endsWith('.txt') || file.endsWith('.md'))) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
|
||||
documents.push(
|
||||
new Document({
|
||||
pageContent: content,
|
||||
metadata: {
|
||||
source: filePath,
|
||||
filename: file,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Разделяем документы на чанки
|
||||
const textSplitter = new RecursiveCharacterTextSplitter({
|
||||
chunkSize: 1000,
|
||||
chunkOverlap: 200,
|
||||
});
|
||||
|
||||
const splitDocs = await textSplitter.splitDocuments(documents);
|
||||
|
||||
return splitDocs;
|
||||
} catch (error) {
|
||||
console.error('Error loading documents:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { loadDocumentsFromFiles };
|
||||
@@ -1,68 +1,246 @@
|
||||
const { pool } = require('../db');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { PGVectorStore } = require('@langchain/community/vectorstores/pgvector');
|
||||
const { Pool } = require('pg');
|
||||
const Imap = require('imap');
|
||||
const { simpleParser } = require('mailparser');
|
||||
const { checkMailServer } = require('../utils/checkMail');
|
||||
const { sleep, isValidEmail } = require('../utils/helpers');
|
||||
const { linkIdentity, getUserIdByIdentity } = require('../utils/identity-linker');
|
||||
require('dotenv').config();
|
||||
const simpleParser = require('mailparser').simpleParser;
|
||||
const { processMessage } = require('./ai-assistant');
|
||||
|
||||
class EmailBotService {
|
||||
constructor() {
|
||||
this.enabled = false;
|
||||
console.log('EmailBotService: Сервис отключен (заглушка)');
|
||||
// Конфигурация для отправки писем
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.EMAIL_SMTP_HOST,
|
||||
port: process.env.EMAIL_SMTP_PORT,
|
||||
secure: process.env.EMAIL_SMTP_PORT === '465', // true для 465, false для других портов
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD,
|
||||
},
|
||||
});
|
||||
|
||||
// Конфигурация для получения писем
|
||||
const imapConfig = {
|
||||
user: process.env.EMAIL_USER,
|
||||
password: process.env.EMAIL_PASSWORD,
|
||||
host: process.env.EMAIL_IMAP_HOST,
|
||||
port: process.env.EMAIL_IMAP_PORT,
|
||||
tls: true,
|
||||
tlsOptions: { rejectUnauthorized: false },
|
||||
};
|
||||
|
||||
/**
|
||||
* Инициализация сервиса электронной почты
|
||||
*/
|
||||
function initEmailBot() {
|
||||
if (!process.env.EMAIL_USER || !process.env.EMAIL_PASSWORD) {
|
||||
console.warn('EMAIL_USER or EMAIL_PASSWORD not set, Email integration disabled');
|
||||
return null;
|
||||
}
|
||||
|
||||
async start() {
|
||||
console.log('EmailBotService: Запуск сервиса отключен (заглушка)');
|
||||
return false;
|
||||
}
|
||||
console.log('Email bot initialized');
|
||||
|
||||
async stop() {
|
||||
console.log('EmailBotService: Остановка сервиса отключена (заглушка)');
|
||||
return true;
|
||||
}
|
||||
// Запуск проверки почты каждые 5 минут
|
||||
const checkInterval = 5 * 60 * 1000; // 5 минут
|
||||
setInterval(checkEmails, checkInterval);
|
||||
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
// Первая проверка при запуске
|
||||
checkEmails();
|
||||
|
||||
return {
|
||||
sendEmail,
|
||||
checkEmails,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка новых писем
|
||||
*/
|
||||
function checkEmails() {
|
||||
const imap = new Imap(imapConfig);
|
||||
|
||||
imap.once('ready', () => {
|
||||
imap.openBox('INBOX', false, (err, box) => {
|
||||
if (err) {
|
||||
console.error('Error opening inbox:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Поиск непрочитанных писем
|
||||
imap.search(['UNSEEN'], (err, results) => {
|
||||
if (err) {
|
||||
console.error('Error searching emails:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
if (results.length === 0) {
|
||||
console.log('No new emails');
|
||||
imap.end();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${results.length} new emails`);
|
||||
|
||||
const f = imap.fetch(results, { bodies: '' });
|
||||
|
||||
f.on('message', (msg, seqno) => {
|
||||
msg.on('body', (stream, info) => {
|
||||
simpleParser(stream, async (err, parsed) => {
|
||||
if (err) {
|
||||
console.error('Error parsing email:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Обработка письма
|
||||
await processEmail(parsed);
|
||||
|
||||
// Пометить как прочитанное
|
||||
imap.setFlags(results, ['\\Seen'], (err) => {
|
||||
if (err) {
|
||||
console.error('Error marking email as read:', err);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error processing email:', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
f.once('error', (err) => {
|
||||
console.error('Fetch error:', err);
|
||||
});
|
||||
|
||||
f.once('end', () => {
|
||||
imap.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
imap.once('error', (err) => {
|
||||
console.error('IMAP error:', err);
|
||||
});
|
||||
|
||||
imap.connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка полученного письма
|
||||
* @param {Object} email - Распарсенное письмо
|
||||
*/
|
||||
async function processEmail(email) {
|
||||
try {
|
||||
const from = email.from.value[0].address;
|
||||
const subject = email.subject;
|
||||
const text = email.text || '';
|
||||
|
||||
console.log(`Processing email from ${from}, subject: ${subject}`);
|
||||
|
||||
// Поиск пользователя по email
|
||||
const userResult = await pool.query(
|
||||
`SELECT u.* FROM users u
|
||||
JOIN user_identities ui ON u.id = ui.user_id
|
||||
WHERE ui.identity_type = 'email' AND ui.identity_value = $1 AND ui.verified = TRUE`,
|
||||
[from]
|
||||
);
|
||||
|
||||
if (userResult.rows.length === 0) {
|
||||
console.log(`No verified user found for email ${from}`);
|
||||
// Отправка ответа о необходимости регистрации
|
||||
await sendEmail(
|
||||
from,
|
||||
'Регистрация в системе',
|
||||
'Для использования ИИ-ассистента через email, пожалуйста, зарегистрируйтесь на нашем сайте и подтвердите свой email.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const user = userResult.rows[0];
|
||||
|
||||
// Получение или создание диалога
|
||||
const conversationResult = await pool.query(
|
||||
`SELECT * FROM conversations
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1`,
|
||||
[user.id]
|
||||
);
|
||||
|
||||
let conversationId;
|
||||
|
||||
if (conversationResult.rows.length === 0) {
|
||||
// Создание нового диалога
|
||||
const newConversationResult = await pool.query(
|
||||
`INSERT INTO conversations (user_id, title)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id`,
|
||||
[user.id, subject || 'Email диалог']
|
||||
);
|
||||
|
||||
conversationId = newConversationResult.rows[0].id;
|
||||
} else {
|
||||
conversationId = conversationResult.rows[0].id;
|
||||
}
|
||||
|
||||
// Сохранение сообщения пользователя
|
||||
await pool.query(
|
||||
`INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
[conversationId, 'user', user.id, text, 'email']
|
||||
);
|
||||
|
||||
// Обработка сообщения ИИ-ассистентом
|
||||
const aiResponse = await processMessage(user.id, text, user.language || 'ru');
|
||||
|
||||
// Сохранение ответа ИИ
|
||||
await pool.query(
|
||||
`INSERT INTO messages (conversation_id, sender_type, sender_id, content, channel)
|
||||
VALUES ($1, $2, $3, $4, $5)`,
|
||||
[conversationId, 'ai', null, aiResponse, 'email']
|
||||
);
|
||||
|
||||
// Обновление времени последнего обновления диалога
|
||||
await pool.query(
|
||||
`UPDATE conversations
|
||||
SET updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
[conversationId]
|
||||
);
|
||||
|
||||
// Отправка ответа пользователю
|
||||
await sendEmail(from, `Re: ${subject}`, aiResponse);
|
||||
|
||||
console.log(`Sent response to ${from}`);
|
||||
} catch (error) {
|
||||
console.error('Error processing email:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// В обработчике команд добавьте код для связывания аккаунтов
|
||||
async function processCommand(email, command, args) {
|
||||
if (command === 'link' && args.length > 0) {
|
||||
const ethAddress = args[0];
|
||||
|
||||
// Проверяем формат Ethereum-адреса
|
||||
if (!/^0x[a-fA-F0-9]{40}$/.test(ethAddress)) {
|
||||
return 'Неверный формат Ethereum-адреса. Используйте формат 0x...';
|
||||
}
|
||||
|
||||
try {
|
||||
// Получаем ID пользователя по Ethereum-адресу
|
||||
const userId = await getUserIdByIdentity('ethereum', ethAddress);
|
||||
|
||||
if (!userId) {
|
||||
return 'Пользователь с таким Ethereum-адресом не найден. Сначала войдите через веб-интерфейс.';
|
||||
}
|
||||
|
||||
// Связываем Email-аккаунт с пользователем
|
||||
const success = await linkIdentity(userId, 'email', email);
|
||||
|
||||
if (success) {
|
||||
return `Ваш Email-аккаунт успешно связан с Ethereum-адресом ${ethAddress}`;
|
||||
} else {
|
||||
return 'Не удалось связать аккаунты. Возможно, этот Email-аккаунт уже связан с другим пользователем.';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при связывании аккаунтов:', error);
|
||||
return 'Произошла ошибка при связывании аккаунтов. Попробуйте позже.';
|
||||
}
|
||||
/**
|
||||
* Отправка email
|
||||
* @param {string} to - Адрес получателя
|
||||
* @param {string} subject - Тема письма
|
||||
* @param {string} text - Текст письма
|
||||
* @returns {Promise<Object>} - Результат отправки
|
||||
*/
|
||||
async function sendEmail(to, subject, text) {
|
||||
try {
|
||||
const info = await transporter.sendMail({
|
||||
from: process.env.EMAIL_USER,
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
});
|
||||
|
||||
console.log('Email sent:', info.messageId);
|
||||
return info;
|
||||
} catch (error) {
|
||||
console.error('Error sending email:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Обработка других команд...
|
||||
}
|
||||
|
||||
module.exports = EmailBotService;
|
||||
module.exports = {
|
||||
initEmailBot,
|
||||
sendEmail,
|
||||
checkEmails,
|
||||
};
|
||||
|
||||
32
backend/services/index.js
Normal file
32
backend/services/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const { initTelegramBot } = require('./telegram-service');
|
||||
const { initEmailBot, sendEmail, checkEmails } = require('./emailBot');
|
||||
const {
|
||||
initializeVectorStore,
|
||||
getVectorStore,
|
||||
similaritySearch,
|
||||
addDocument,
|
||||
} = require('./vectorStore');
|
||||
const { processMessage, getUserInfo, getConversationHistory } = require('./ai-assistant');
|
||||
// ... другие импорты
|
||||
|
||||
module.exports = {
|
||||
// Telegram
|
||||
initTelegramBot,
|
||||
|
||||
// Email
|
||||
initEmailBot,
|
||||
sendEmail,
|
||||
checkEmails,
|
||||
|
||||
// Vector Store
|
||||
initializeVectorStore,
|
||||
getVectorStore,
|
||||
similaritySearch,
|
||||
addDocument,
|
||||
|
||||
// AI Assistant
|
||||
processMessage,
|
||||
getUserInfo,
|
||||
getConversationHistory,
|
||||
// ... другие экспорты
|
||||
};
|
||||
@@ -1,7 +1,9 @@
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const { RetrievalQAChain } = require("langchain/chains");
|
||||
const { PromptTemplate } = require("@langchain/core/prompts");
|
||||
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 = `
|
||||
@@ -19,17 +21,17 @@ const PROMPT_TEMPLATE = `
|
||||
// Функция для проверки доступности Ollama
|
||||
async function checkOllamaAvailability() {
|
||||
console.log('Проверка доступности Ollama...');
|
||||
|
||||
|
||||
try {
|
||||
// Добавляем таймаут для запроса
|
||||
const response = await axios.get('http://localhost:11434/api/tags', {
|
||||
timeout: 5000 // 5 секунд таймаут
|
||||
timeout: 5000, // 5 секунд таймаут
|
||||
});
|
||||
|
||||
|
||||
if (response.status === 200) {
|
||||
console.log('Ollama доступен. Доступные модели:');
|
||||
if (response.data && response.data.models) {
|
||||
response.data.models.forEach(model => {
|
||||
response.data.models.forEach((model) => {
|
||||
console.log(`- ${model.name}`);
|
||||
});
|
||||
}
|
||||
@@ -42,32 +44,45 @@ async function checkOllamaAvailability() {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для прямого запроса к Ollama API
|
||||
async function directOllamaQuery(message, model = 'mistral') {
|
||||
// Функция для прямого запроса к Ollama
|
||||
async function directOllamaQuery(message, language = 'en') {
|
||||
try {
|
||||
console.log(`Отправка запроса к Ollama (модель: ${model}):`, message);
|
||||
|
||||
// Проверяем доступность Ollama перед отправкой запроса
|
||||
const isAvailable = await checkOllamaAvailability();
|
||||
if (!isAvailable) {
|
||||
throw new Error('Сервер Ollama недоступен');
|
||||
// Всегда используем модель 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 недоступен');
|
||||
}
|
||||
|
||||
// Создаем экземпляр ChatOllama
|
||||
const ollama = new ChatOllama({
|
||||
|
||||
console.log('Отправка запроса к Ollama...');
|
||||
|
||||
const chatModel = new ChatOllama({
|
||||
baseUrl: 'http://localhost:11434',
|
||||
model: model,
|
||||
model: modelName,
|
||||
temperature: 0.7,
|
||||
});
|
||||
|
||||
console.log('Отправка запроса к Ollama...');
|
||||
const result = await ollama.invoke(message);
|
||||
console.log('Получен ответ от Ollama');
|
||||
|
||||
return result.content;
|
||||
|
||||
const response = await chatModel.invoke([new HumanMessage(message)]);
|
||||
|
||||
return response.content;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при запросе к Ollama:', error);
|
||||
throw error;
|
||||
|
||||
// Возвращаем сообщение об ошибке
|
||||
return 'Извините, произошла ошибка при обработке вашего запроса. Пожалуйста, попробуйте позже.';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,27 +113,23 @@ async function createOllamaChain(vectorStore) {
|
||||
// Создаем шаблон запроса
|
||||
const prompt = new PromptTemplate({
|
||||
template: PROMPT_TEMPLATE,
|
||||
inputVariables: ["context", "query"],
|
||||
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
|
||||
}
|
||||
);
|
||||
const chain = RetrievalQAChain.fromLLM(model, retriever, {
|
||||
returnSourceDocuments: true,
|
||||
prompt: prompt,
|
||||
inputKey: 'query',
|
||||
outputKey: 'text',
|
||||
verbose: true,
|
||||
});
|
||||
console.log('Цепочка для поиска и ответа создана');
|
||||
|
||||
return chain;
|
||||
@@ -146,4 +157,4 @@ async function getOllamaModel() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery };
|
||||
module.exports = { getOllamaModel, createOllamaChain, checkOllamaAvailability, directOllamaQuery };
|
||||
|
||||
262
backend/services/telegram-service.js
Normal file
262
backend/services/telegram-service.js
Normal file
@@ -0,0 +1,262 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const { pool } = require('../db');
|
||||
const { processMessage } = require('./ai-assistant');
|
||||
|
||||
// Инициализация бота
|
||||
const token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
let bot = null;
|
||||
|
||||
if (token) {
|
||||
bot = new TelegramBot(token, { polling: true });
|
||||
console.log('Telegram bot initialized');
|
||||
} else {
|
||||
console.warn('TELEGRAM_BOT_TOKEN not set, Telegram integration disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация Telegram бота
|
||||
*/
|
||||
function initTelegramBot() {
|
||||
if (!bot) return;
|
||||
|
||||
// Обработка команды /start
|
||||
bot.onText(/\/start/, async (msg) => {
|
||||
const chatId = msg.chat.id;
|
||||
const userId = msg.from.id;
|
||||
const username =
|
||||
msg.from.username || `${msg.from.first_name} ${msg.from.last_name || ''}`.trim();
|
||||
|
||||
try {
|
||||
// Проверка существования пользователя
|
||||
const user = await findOrCreateUser(userId, username, chatId);
|
||||
|
||||
// Приветственное сообщение
|
||||
bot.sendMessage(chatId, `Привет, ${username}! Я ИИ-ассистент. Чем могу помочь?`);
|
||||
} catch (error) {
|
||||
console.error('Error handling /start command:', error);
|
||||
bot.sendMessage(
|
||||
chatId,
|
||||
'Произошла ошибка при обработке команды. Пожалуйста, попробуйте позже.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка текстовых сообщений
|
||||
bot.on('message', async (msg) => {
|
||||
if (!msg.text || msg.text.startsWith('/')) return;
|
||||
|
||||
const chatId = msg.chat.id;
|
||||
const userId = msg.from.id;
|
||||
const username =
|
||||
msg.from.username || `${msg.from.first_name} ${msg.from.last_name || ''}`.trim();
|
||||
|
||||
try {
|
||||
// Проверка существования пользователя
|
||||
const user = await findOrCreateUser(userId, username, chatId);
|
||||
|
||||
// Получение или создание диалога
|
||||
const conversation = await getOrCreateConversation(user.id);
|
||||
|
||||
// Сохранение сообщения пользователя
|
||||
await saveMessage(conversation.id, 'user', user.id, msg.text, 'telegram');
|
||||
|
||||
// Обработка сообщения ИИ-ассистентом
|
||||
const aiResponse = await processMessage(user.id, msg.text, user.language || 'ru');
|
||||
|
||||
// Сохранение ответа ИИ
|
||||
await saveMessage(conversation.id, 'ai', null, aiResponse, 'telegram');
|
||||
|
||||
// Отправка ответа
|
||||
bot.sendMessage(chatId, aiResponse);
|
||||
} catch (error) {
|
||||
console.error('Error processing message:', error);
|
||||
bot.sendMessage(
|
||||
chatId,
|
||||
'Произошла ошибка при обработке сообщения. Пожалуйста, попробуйте позже.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Telegram bot handlers registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Поиск или создание пользователя по Telegram ID
|
||||
* @param {number} telegramId - Telegram ID пользователя
|
||||
* @param {string} username - Имя пользователя
|
||||
* @param {number} chatId - ID чата
|
||||
* @returns {Promise<Object>} - Информация о пользователе
|
||||
*/
|
||||
async function findOrCreateUser(telegramId, username, chatId) {
|
||||
try {
|
||||
// Поиск пользователя по Telegram ID
|
||||
const userIdResult = await pool.query(
|
||||
`SELECT user_id FROM user_identities
|
||||
WHERE identity_type = 'telegram' AND identity_value = $1`,
|
||||
[telegramId.toString()]
|
||||
);
|
||||
|
||||
if (userIdResult.rows.length > 0) {
|
||||
// Пользователь найден
|
||||
const userId = userIdResult.rows[0].user_id;
|
||||
|
||||
// Получение информации о пользователе
|
||||
const userResult = await pool.query('SELECT * FROM users WHERE id = $1', [userId]);
|
||||
|
||||
return userResult.rows[0];
|
||||
} else {
|
||||
// Создание нового пользователя
|
||||
const userResult = await pool.query(
|
||||
`INSERT INTO users (
|
||||
username,
|
||||
role_id,
|
||||
is_admin,
|
||||
language,
|
||||
address
|
||||
) VALUES (
|
||||
$1,
|
||||
(SELECT id FROM roles WHERE name = 'user'),
|
||||
FALSE,
|
||||
'ru',
|
||||
'0x' || encode(gen_random_bytes(20), 'hex')
|
||||
) RETURNING *`,
|
||||
[username]
|
||||
);
|
||||
|
||||
const newUser = userResult.rows[0];
|
||||
|
||||
// Добавление идентификатора Telegram
|
||||
await pool.query(
|
||||
`INSERT INTO user_identities (
|
||||
user_id,
|
||||
identity_type,
|
||||
identity_value,
|
||||
verified
|
||||
) VALUES ($1, 'telegram', $2, TRUE)`,
|
||||
[newUser.id, telegramId.toString()]
|
||||
);
|
||||
|
||||
// Сохранение метаданных Telegram
|
||||
await pool.query(
|
||||
`INSERT INTO user_preferences (
|
||||
user_id,
|
||||
preference_key,
|
||||
preference_value
|
||||
) VALUES ($1, 'telegram_chat_id', $2)`,
|
||||
[newUser.id, chatId.toString()]
|
||||
);
|
||||
|
||||
return newUser;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error finding or creating user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получение или создание диалога для пользователя
|
||||
* @param {number} userId - ID пользователя
|
||||
* @returns {Promise<Object>} - Информация о диалоге
|
||||
*/
|
||||
async function getOrCreateConversation(userId) {
|
||||
try {
|
||||
// Поиск активного диалога
|
||||
const conversationResult = await pool.query(
|
||||
`SELECT * FROM conversations
|
||||
WHERE user_id = $1
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (conversationResult.rows.length > 0) {
|
||||
// Обновление времени последней активности
|
||||
await pool.query('UPDATE conversations SET updated_at = NOW() WHERE id = $1', [
|
||||
conversationResult.rows[0].id,
|
||||
]);
|
||||
|
||||
return conversationResult.rows[0];
|
||||
} else {
|
||||
// Создание нового диалога
|
||||
const newConversationResult = await pool.query(
|
||||
`INSERT INTO conversations (user_id, title)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *`,
|
||||
[userId, 'Диалог в Telegram']
|
||||
);
|
||||
|
||||
return newConversationResult.rows[0];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting or creating conversation:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранение сообщения
|
||||
* @param {number} conversationId - ID диалога
|
||||
* @param {string} senderType - Тип отправителя ('user', 'ai')
|
||||
* @param {number|null} senderId - ID отправителя
|
||||
* @param {string} content - Текст сообщения
|
||||
* @param {string} channel - Канал ('telegram')
|
||||
* @returns {Promise<Object>} - Информация о сообщении
|
||||
*/
|
||||
async function saveMessage(conversationId, senderType, senderId, content, channel) {
|
||||
try {
|
||||
const messageResult = await pool.query(
|
||||
`INSERT INTO messages (
|
||||
conversation_id,
|
||||
sender_type,
|
||||
sender_id,
|
||||
content,
|
||||
channel
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[conversationId, senderType, senderId, content, channel]
|
||||
);
|
||||
|
||||
return messageResult.rows[0];
|
||||
} catch (error) {
|
||||
console.error('Error saving message:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отправка сообщения пользователю через Telegram
|
||||
* @param {number} userId - ID пользователя
|
||||
* @param {string} message - Текст сообщения
|
||||
* @returns {Promise<boolean>} - Успешность отправки
|
||||
*/
|
||||
async function sendMessageToUser(userId, message) {
|
||||
if (!bot) return false;
|
||||
|
||||
try {
|
||||
// Получение Telegram chat ID пользователя
|
||||
const chatIdResult = await pool.query(
|
||||
`SELECT preference_value FROM user_preferences
|
||||
WHERE user_id = $1 AND preference_key = 'telegram_chat_id'`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (chatIdResult.rows.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const chatId = chatIdResult.rows[0].preference_value;
|
||||
|
||||
// Отправка сообщения
|
||||
await bot.sendMessage(chatId, message);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error sending message to user:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initTelegramBot,
|
||||
sendMessageToUser,
|
||||
};
|
||||
@@ -1,402 +0,0 @@
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const { ChatOllama } = require('@langchain/ollama');
|
||||
const axios = require('axios');
|
||||
const dns = require('dns').promises;
|
||||
require('dotenv').config();
|
||||
const { sleep } = require('../utils/helpers');
|
||||
const util = require('util');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
const { linkIdentity, getUserIdByIdentity } = require('../utils/identity-linker');
|
||||
|
||||
class TelegramBotService {
|
||||
constructor() {
|
||||
// Проверяем наличие токена
|
||||
if (!process.env.TELEGRAM_BOT_TOKEN) {
|
||||
throw new Error('Token is required');
|
||||
}
|
||||
|
||||
this.isRunning = false;
|
||||
this.maxRetries = 3;
|
||||
this.retryDelay = 5000; // 5 секунд между попытками
|
||||
|
||||
// Создаем бота без polling
|
||||
this.bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, {
|
||||
polling: false,
|
||||
request: {
|
||||
proxy: null,
|
||||
agentOptions: {
|
||||
rejectUnauthorized: true,
|
||||
minVersion: 'TLSv1.2'
|
||||
},
|
||||
timeout: 30000
|
||||
}
|
||||
});
|
||||
|
||||
this.token = process.env.TELEGRAM_BOT_TOKEN;
|
||||
this.chat = new ChatOllama({
|
||||
model: 'mistral',
|
||||
baseUrl: 'http://localhost:11434'
|
||||
});
|
||||
|
||||
// Добавляем настройки прокси для axios
|
||||
this.axiosConfig = {
|
||||
timeout: 5000,
|
||||
proxy: false,
|
||||
httpsAgent: new (require('https').Agent)({
|
||||
rejectUnauthorized: true,
|
||||
minVersion: 'TLSv1.2'
|
||||
})
|
||||
};
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
this.bot.onText(/.*/, async (msg) => {
|
||||
try {
|
||||
const chatId = msg.chat.id;
|
||||
const userQuestion = msg.text;
|
||||
|
||||
// Пропускаем команды
|
||||
if (userQuestion.startsWith('/')) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Получен вопрос:', userQuestion);
|
||||
|
||||
// Используем локальную модель
|
||||
const result = await this.chat.invoke(userQuestion);
|
||||
const assistantResponse = result.content;
|
||||
|
||||
await this.bot.sendMessage(chatId, assistantResponse);
|
||||
} catch (error) {
|
||||
console.error('Telegram bot error:', error);
|
||||
await this.bot.sendMessage(msg.chat.id,
|
||||
'Извините, произошла ошибка при обработке вашего запроса. ' +
|
||||
'Попробуйте повторить позже или обратитесь к администратору.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.bot.onText(/\/link (.+)/, async (msg, match) => {
|
||||
const chatId = msg.chat.id;
|
||||
const ethAddress = match[1];
|
||||
|
||||
// Проверяем формат Ethereum-адреса
|
||||
if (!/^0x[a-fA-F0-9]{40}$/.test(ethAddress)) {
|
||||
this.bot.sendMessage(chatId, 'Неверный формат Ethereum-адреса. Используйте формат 0x...');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Получаем ID пользователя по Ethereum-адресу
|
||||
const userId = await getUserIdByIdentity('ethereum', ethAddress);
|
||||
|
||||
if (!userId) {
|
||||
this.bot.sendMessage(chatId, 'Пользователь с таким Ethereum-адресом не найден. Сначала войдите через веб-интерфейс.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Связываем Telegram-аккаунт с пользователем
|
||||
const success = await linkIdentity(userId, 'telegram', chatId.toString());
|
||||
|
||||
if (success) {
|
||||
this.bot.sendMessage(chatId, `Ваш Telegram-аккаунт успешно связан с Ethereum-адресом ${ethAddress}`);
|
||||
} else {
|
||||
this.bot.sendMessage(chatId, 'Не удалось связать аккаунты. Возможно, этот Telegram-аккаунт уже связан с другим пользователем.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при связывании аккаунтов:', error);
|
||||
this.bot.sendMessage(chatId, 'Произошла ошибка при связывании аккаунтов. Попробуйте позже.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupCommands() {
|
||||
this.bot.onText(/\/start/, async (msg) => {
|
||||
const welcomeMessage = `
|
||||
👋 Здравствуйте! Я - ассистент DApp for Business.
|
||||
|
||||
Я готов помочь вам с вопросами о:
|
||||
• Разработке dApps
|
||||
• Блокчейн-технологиях
|
||||
• Web3 и криптовалютах
|
||||
|
||||
Просто задавайте вопросы, а если нужна помощь -
|
||||
используйте команду /help
|
||||
`;
|
||||
await this.bot.sendMessage(msg.chat.id, welcomeMessage);
|
||||
});
|
||||
|
||||
this.bot.onText(/\/help/, async (msg) => {
|
||||
const helpMessage = `
|
||||
🤖 Я - ассистент DApp for Business
|
||||
|
||||
Я могу помочь вам с:
|
||||
• Разработкой децентрализованных приложений
|
||||
• Интеграцией блокчейн-технологий в бизнес
|
||||
• Консультациями по Web3 и криптовалютам
|
||||
|
||||
Команды:
|
||||
/start - начать работу с ботом
|
||||
/help - показать это сообщение
|
||||
/status - проверить состояние бота
|
||||
|
||||
Просто задавайте вопросы на русском или английском языке!
|
||||
`;
|
||||
await this.bot.sendMessage(msg.chat.id, helpMessage);
|
||||
});
|
||||
|
||||
this.bot.onText(/\/status/, async (msg) => {
|
||||
try {
|
||||
const status = {
|
||||
isRunning: this.isRunning,
|
||||
uptime: process.uptime(),
|
||||
memoryUsage: process.memoryUsage(),
|
||||
connections: {
|
||||
telegram: Boolean(this.bot),
|
||||
ollama: Boolean(this.chat)
|
||||
}
|
||||
};
|
||||
|
||||
const statusMessage = `
|
||||
📊 Статус бота:
|
||||
|
||||
🟢 Статус: ${status.isRunning ? 'Работает' : 'Остановлен'}
|
||||
⏱ Время работы: ${Math.floor(status.uptime / 60)} мин
|
||||
|
||||
🔗 Подключения:
|
||||
• Telegram API: ${status.connections.telegram ? '✅' : '❌'}
|
||||
• Ollama: ${status.connections.ollama ? '✅' : '❌'}
|
||||
|
||||
💾 Использование памяти:
|
||||
• Heap: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)}MB
|
||||
• RSS: ${Math.round(status.memoryUsage.rss / 1024 / 1024)}MB
|
||||
`;
|
||||
|
||||
await this.bot.sendMessage(msg.chat.id, statusMessage);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении статуса:', error);
|
||||
await this.bot.sendMessage(msg.chat.id, 'Ошибка при получении статуса бота');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
let retries = 0;
|
||||
|
||||
while (retries < this.maxRetries) {
|
||||
try {
|
||||
console.log(`Попытка инициализации Telegram бота (${retries + 1}/${this.maxRetries})...`);
|
||||
|
||||
// Сначала проверяем DNS и доступность
|
||||
try {
|
||||
console.log('Проверка DNS для api.telegram.org...');
|
||||
const addresses = await dns.resolve4('api.telegram.org');
|
||||
console.log('IP адреса api.telegram.org:', addresses);
|
||||
|
||||
// Пинг для проверки доступности (теперь ждем результат)
|
||||
try {
|
||||
const { stdout } = await exec('ping -c 1 api.telegram.org');
|
||||
console.log('Результат ping:', stdout);
|
||||
} catch (pingError) {
|
||||
console.error('Ошибка при выполнении ping:', pingError);
|
||||
throw new Error('Сервер Telegram недоступен');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка сетевой проверки:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Затем проверяем API
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/getMe`,
|
||||
this.axiosConfig
|
||||
);
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
console.log('Успешное подключение к API Telegram:', {
|
||||
botInfo: response.data.result
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке API Telegram:', {
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
response: error.response?.data,
|
||||
config: {
|
||||
url: error.config?.url,
|
||||
method: error.config?.method,
|
||||
timeout: error.config?.timeout
|
||||
}
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Основная инициализация бота
|
||||
await this.initBot();
|
||||
console.log('Telegram bot service initialized');
|
||||
return;
|
||||
|
||||
} catch (error) {
|
||||
retries++;
|
||||
console.error('Ошибка при инициализации Telegram бота:', {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
response: error.response?.data,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
if (retries < this.maxRetries) {
|
||||
console.log(`Повторная попытка через ${this.retryDelay/1000} секунд...`);
|
||||
await sleep(this.retryDelay);
|
||||
} else {
|
||||
console.error('Превышено максимальное количество попыток подключения к Telegram');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async initBot() {
|
||||
try {
|
||||
// Проверяем, не запущен ли уже бот
|
||||
const webhookInfo = await this.bot.getWebHookInfo();
|
||||
|
||||
// Если есть webhook или активный polling, пробуем остановить
|
||||
if (webhookInfo.url || webhookInfo.has_custom_certificate) {
|
||||
console.log('Удаляем существующий webhook...');
|
||||
await this.bot.deleteWebHook();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
// Пробуем получить обновления с большим таймаутом
|
||||
try {
|
||||
console.log('Проверяем наличие других экземпляров бота...');
|
||||
const updates = await this.bot.getUpdates({
|
||||
offset: -1,
|
||||
limit: 1,
|
||||
timeout: 0
|
||||
});
|
||||
console.log('Проверка существующих подключений:', updates);
|
||||
} catch (error) {
|
||||
if (error.code === 409) {
|
||||
console.log('Обнаружен активный бот, пробуем остановить...');
|
||||
await this.stop();
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
// Повторная попытка получить обновления
|
||||
await this.bot.getUpdates({ offset: -1, limit: 1, timeout: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
// Небольшая пауза перед запуском поллинга
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Запускаем polling
|
||||
console.log('Запускаем polling...');
|
||||
await this.bot.startPolling({
|
||||
interval: 2000,
|
||||
params: {
|
||||
timeout: 10
|
||||
}
|
||||
});
|
||||
|
||||
this.isRunning = true;
|
||||
this.setupHandlers();
|
||||
this.setupErrorHandlers();
|
||||
this.setupCommands();
|
||||
} catch (error) {
|
||||
if (error.code === 409) {
|
||||
console.log('Бот уже запущен в другом процессе');
|
||||
this.isRunning = false;
|
||||
} else {
|
||||
console.error('Ошибка при инициализации Telegram бота:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupErrorHandlers() {
|
||||
this.bot.on('polling_error', (error) => {
|
||||
console.error('Telegram polling error:', {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
// Обработка различных ошибок
|
||||
if (this.isRunning && (error.code === 'EFATAL' || error.code === 'ETELEGRAM')) {
|
||||
console.log('Переподключение к Telegram через 5 секунд...');
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await this.stop();
|
||||
await this.initBot();
|
||||
} catch (err) {
|
||||
console.error('Ошибка при перезапуске бота:', err);
|
||||
}
|
||||
}, 5000);
|
||||
} else if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED') {
|
||||
// Для ошибок соединения пробуем сразу переподключиться
|
||||
this.bot.startPolling();
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка других ошибок
|
||||
this.bot.on('error', (error) => {
|
||||
console.error('Telegram bot error:', error);
|
||||
// Пробуем переподключиться при любой ошибке
|
||||
setTimeout(() => this.bot.startPolling(), 5000);
|
||||
});
|
||||
|
||||
// Обработка webhook ошибок
|
||||
this.bot.on('webhook_error', (error) => {
|
||||
console.error('Telegram webhook error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
async stop() {
|
||||
if (this.isRunning) {
|
||||
console.log('Останавливаем Telegram бота...');
|
||||
try {
|
||||
// Сначала отключаем обработчики
|
||||
this.bot.removeAllListeners();
|
||||
|
||||
// Останавливаем поллинг
|
||||
await this.bot.stopPolling();
|
||||
|
||||
// Очищаем очередь обновлений
|
||||
await this.bot.getUpdates({
|
||||
offset: -1,
|
||||
limit: 1,
|
||||
timeout: 1
|
||||
});
|
||||
|
||||
this.isRunning = false;
|
||||
console.log('Telegram бот остановлен');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при остановке бота:', error);
|
||||
// Принудительно отмечаем как остановленный
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkTelegramAvailability() {
|
||||
const { stdout } = await exec('ping -c 1 api.telegram.org');
|
||||
const match = stdout.match(/time=(\d+(\.\d+)?)/);
|
||||
if (match) {
|
||||
const pingTime = parseFloat(match[1]);
|
||||
console.log(`Время отклика Telegram API: ${pingTime}ms`);
|
||||
if (pingTime > 1000) { // Если пинг больше секунды
|
||||
console.warn('Внимание: высокая задержка при подключении к Telegram API');
|
||||
}
|
||||
}
|
||||
return stdout;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TelegramBotService;
|
||||
@@ -1,134 +1,212 @@
|
||||
const { HNSWLib } = require("@langchain/community/vectorstores/hnswlib");
|
||||
const { OllamaEmbeddings } = require("@langchain/ollama");
|
||||
const { DirectoryLoader } = require("langchain/document_loaders/fs/directory");
|
||||
const { TextLoader } = require("langchain/document_loaders/fs/text");
|
||||
const { RecursiveCharacterTextSplitter } = require("langchain/text_splitter");
|
||||
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 DOCS_DIR = path.join(__dirname, '../data/documents');
|
||||
// Путь к директории для хранения векторного индекса
|
||||
const VECTOR_STORE_DIR = path.join(__dirname, '../data/vector_store');
|
||||
// Путь к директории для хранения векторной базы данных
|
||||
const VECTOR_STORE_PATH = path.join(__dirname, '../data/vector_store');
|
||||
|
||||
// Создаем директории, если они не существуют
|
||||
if (!fs.existsSync(DOCS_DIR)) {
|
||||
fs.mkdirSync(DOCS_DIR, { recursive: true });
|
||||
console.log(`Создана директория для документов: ${DOCS_DIR}`);
|
||||
}
|
||||
// Инициализация embeddings с использованием локальной модели Ollama
|
||||
const embeddings = new OllamaEmbeddings({
|
||||
model: process.env.OLLAMA_EMBEDDINGS_MODEL || 'mistral',
|
||||
baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
|
||||
});
|
||||
|
||||
if (!fs.existsSync(VECTOR_STORE_DIR)) {
|
||||
fs.mkdirSync(VECTOR_STORE_DIR, { recursive: true });
|
||||
console.log(`Создана директория для векторного хранилища: ${VECTOR_STORE_DIR}`);
|
||||
}
|
||||
|
||||
// Глобальная переменная для хранения экземпляра векторного хранилища
|
||||
let vectorStore = null;
|
||||
|
||||
// Функция для инициализации векторного хранилища
|
||||
/**
|
||||
* Инициализация векторного хранилища
|
||||
*/
|
||||
async function initializeVectorStore() {
|
||||
try {
|
||||
console.log('Инициализация векторного хранилища...');
|
||||
|
||||
// Проверяем, существует ли директория с документами
|
||||
if (!fs.existsSync(DOCS_DIR)) {
|
||||
console.warn(`Директория с документами не найдена: ${DOCS_DIR}`);
|
||||
return null;
|
||||
// Создание директории, если она не существует
|
||||
if (!fs.existsSync(VECTOR_STORE_PATH)) {
|
||||
fs.mkdirSync(VECTOR_STORE_PATH, { recursive: true });
|
||||
console.log(`Created vector store directory at ${VECTOR_STORE_PATH}`);
|
||||
}
|
||||
|
||||
// Проверяем, есть ли документы в директории
|
||||
const files = fs.readdirSync(DOCS_DIR);
|
||||
if (files.length === 0) {
|
||||
console.warn(`В директории с документами нет файлов: ${DOCS_DIR}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Найдено ${files.length} файлов в директории с документами`);
|
||||
|
||||
// Загружаем документы из директории
|
||||
const loader = new DirectoryLoader(
|
||||
DOCS_DIR,
|
||||
{
|
||||
".txt": (path) => new TextLoader(path),
|
||||
".md": (path) => new TextLoader(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();
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Загрузка документов...');
|
||||
const docs = await loader.load();
|
||||
console.log(`Загружено ${docs.length} документов`);
|
||||
|
||||
if (docs.length === 0) {
|
||||
console.warn('Не удалось загрузить документы');
|
||||
return null;
|
||||
} 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,
|
||||
});
|
||||
|
||||
console.log('Разбиение документов на чанки...');
|
||||
const splitDocs = await textSplitter.splitDocuments(docs);
|
||||
console.log(`Документы разбиты на ${splitDocs.length} чанков`);
|
||||
|
||||
// Создаем эмбеддинги с помощью Ollama
|
||||
console.log('Создание эмбеддингов...');
|
||||
const embeddings = new OllamaEmbeddings({
|
||||
model: "mistral",
|
||||
baseUrl: "http://localhost:11434",
|
||||
});
|
||||
|
||||
// Проверяем, существует ли уже векторное хранилище
|
||||
if (fs.existsSync(path.join(VECTOR_STORE_DIR, 'hnswlib.index'))) {
|
||||
console.log('Загрузка существующего векторного хранилища...');
|
||||
try {
|
||||
vectorStore = await HNSWLib.load(
|
||||
VECTOR_STORE_DIR,
|
||||
embeddings
|
||||
);
|
||||
console.log('Векторное хранилище успешно загружено');
|
||||
return vectorStore;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке векторного хранилища:', error);
|
||||
console.log('Создание нового векторного хранилища...');
|
||||
}
|
||||
}
|
||||
|
||||
// Создаем новое векторное хранилище
|
||||
console.log('Создание нового векторного хранилища...');
|
||||
vectorStore = await HNSWLib.fromDocuments(
|
||||
splitDocs,
|
||||
embeddings
|
||||
);
|
||||
|
||||
// Сохраняем векторное хранилище
|
||||
console.log('Сохранение векторного хранилища...');
|
||||
await vectorStore.save(VECTOR_STORE_DIR);
|
||||
console.log('Векторное хранилище успешно сохранено');
|
||||
|
||||
return vectorStore;
|
||||
|
||||
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);
|
||||
console.log('Приложение продолжит работу без векторного хранилища');
|
||||
// Возвращаем заглушку вместо реального хранилища
|
||||
return {
|
||||
addDocuments: async () => console.log('Векторное хранилище недоступно: addDocuments'),
|
||||
similaritySearch: async () => {
|
||||
console.log('Векторное хранилище недоступно: similaritySearch');
|
||||
return [];
|
||||
}
|
||||
};
|
||||
console.error('Error adding document to vector store:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для получения экземпляра векторного хранилища
|
||||
async function getVectorStore() {
|
||||
if (!vectorStore) {
|
||||
vectorStore = await initializeVectorStore();
|
||||
}
|
||||
return vectorStore;
|
||||
}
|
||||
|
||||
module.exports = { initializeVectorStore, getVectorStore };
|
||||
module.exports = {
|
||||
initializeVectorStore,
|
||||
getVectorStore,
|
||||
similaritySearch,
|
||||
addDocument,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
const { expect } = require('chai');
|
||||
const { ethers } = require('hardhat');
|
||||
|
||||
describe("AccessToken", function () {
|
||||
describe('AccessToken', function () {
|
||||
let AccessToken;
|
||||
let accessToken;
|
||||
let owner;
|
||||
@@ -10,36 +10,32 @@ describe("AccessToken", function () {
|
||||
|
||||
beforeEach(async function () {
|
||||
[owner, addr1, addr2] = await ethers.getSigners();
|
||||
AccessToken = await ethers.getContractFactory("AccessToken");
|
||||
AccessToken = await ethers.getContractFactory('AccessToken');
|
||||
accessToken = await AccessToken.deploy();
|
||||
});
|
||||
|
||||
describe("Minting", function () {
|
||||
it("Should mint admin token", async function () {
|
||||
describe('Minting', function () {
|
||||
it('Should mint admin token', async function () {
|
||||
await accessToken.mintAccessToken(addr1.address, 0);
|
||||
expect(await accessToken.checkRole(addr1.address)).to.equal(0);
|
||||
});
|
||||
|
||||
it("Should mint moderator token", async function () {
|
||||
it('Should mint moderator token', async function () {
|
||||
await accessToken.mintAccessToken(addr1.address, 1);
|
||||
expect(await accessToken.checkRole(addr1.address)).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Access Control", function () {
|
||||
it("Should fail for non-token holders", async function () {
|
||||
await expect(
|
||||
accessToken.checkRole(addr1.address)
|
||||
).to.be.revertedWith("No active token");
|
||||
describe('Access Control', function () {
|
||||
it('Should fail for non-token holders', async function () {
|
||||
await expect(accessToken.checkRole(addr1.address)).to.be.revertedWith('No active token');
|
||||
});
|
||||
|
||||
it("Should revoke access", async function () {
|
||||
it('Should revoke access', async function () {
|
||||
await accessToken.mintAccessToken(addr1.address, 0);
|
||||
const tokenId = await accessToken.activeTokens(addr1.address);
|
||||
await accessToken.revokeToken(tokenId);
|
||||
await expect(
|
||||
accessToken.checkRole(addr1.address)
|
||||
).to.be.revertedWith("No active token");
|
||||
await expect(accessToken.checkRole(addr1.address)).to.be.revertedWith('No active token');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const { ethers } = require('ethers');
|
||||
require('dotenv').config();
|
||||
const db = require('../db');
|
||||
const contractArtifact = require('../artifacts/contracts/MyContract.sol/MyContract.json');
|
||||
const contractABI = contractArtifact.abi;
|
||||
const logger = require('./logger');
|
||||
const { getContract } = require('./contracts');
|
||||
|
||||
// Проверяем наличие необходимых переменных окружения
|
||||
if (!process.env.ACCESS_TOKEN_ADDRESS) {
|
||||
@@ -18,11 +21,7 @@ let accessToken;
|
||||
|
||||
try {
|
||||
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
|
||||
accessToken = new ethers.Contract(
|
||||
process.env.ACCESS_TOKEN_ADDRESS,
|
||||
AccessTokenABI,
|
||||
provider
|
||||
);
|
||||
accessToken = new ethers.Contract(process.env.ACCESS_TOKEN_ADDRESS, AccessTokenABI, provider);
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициализации контракта AccessToken:', error);
|
||||
}
|
||||
@@ -49,10 +48,10 @@ async function checkAccess(address) {
|
||||
const roles = ['ADMIN', 'MODERATOR', 'SUPPORT'];
|
||||
const role = roles[roleId];
|
||||
|
||||
return {
|
||||
hasAccess: true,
|
||||
return {
|
||||
hasAccess: true,
|
||||
role,
|
||||
tokenId: activeTokenId.toString()
|
||||
tokenId: activeTokenId.toString(),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Access check error:', error);
|
||||
@@ -60,30 +59,138 @@ async function checkAccess(address) {
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAdmin(address) {
|
||||
// Функция для проверки, является ли пользователь администратором
|
||||
async function checkIfAdmin(address) {
|
||||
try {
|
||||
console.log('Проверка прав администратора для адреса:', address);
|
||||
// Проверяем, является ли пользователь администратором через смарт-контракт
|
||||
const contract = new ethers.Contract(
|
||||
process.env.CONTRACT_ADDRESS,
|
||||
contractABI,
|
||||
provider
|
||||
);
|
||||
|
||||
console.log('Контракт инициализирован:', {
|
||||
address: process.env.CONTRACT_ADDRESS,
|
||||
provider: provider.connection.url
|
||||
});
|
||||
|
||||
const isAdmin = await contract.isAdmin(address);
|
||||
console.log('Результат проверки из контракта:', isAdmin);
|
||||
|
||||
|
||||
// Проверяем в базе данных
|
||||
const result = await db.query('SELECT is_admin FROM users WHERE address = $1', [address]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
console.log(`Пользователь с адресом ${address} не найден в базе данных`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const isAdmin = result.rows[0].is_admin;
|
||||
console.log(`Пользователь с адресом ${address} имеет статус администратора:`, isAdmin);
|
||||
|
||||
return isAdmin;
|
||||
} catch (error) {
|
||||
console.error('Ошибка при проверке прав администратора:', error);
|
||||
// В случае ошибки возвращаем false вместо выброса исключения
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkAccess, checkAdmin };
|
||||
/**
|
||||
* Проверяет баланс токенов пользователя и обновляет его роль
|
||||
* @param {string} address - Адрес кошелька пользователя
|
||||
* @returns {Promise<boolean>} - Имеет ли пользователь права администратора
|
||||
*/
|
||||
async function checkTokenBalanceAndUpdateRole(address) {
|
||||
try {
|
||||
// Получение контракта токенов
|
||||
const accessTokenContract = await getContract('AccessToken');
|
||||
|
||||
// Проверка баланса
|
||||
const balance = await accessTokenContract.balanceOf(address);
|
||||
|
||||
// Минимальное количество токенов для прав администратора
|
||||
const minTokens = ethers.utils.parseUnits(process.env.MIN_ADMIN_TOKENS || "1", 18);
|
||||
|
||||
const isAdmin = balance.gte(minTokens);
|
||||
|
||||
// Получение 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
|
||||
`, [address.toLowerCase()]);
|
||||
|
||||
if (userResult.rows.length > 0) {
|
||||
const userId = userResult.rows[0].id;
|
||||
|
||||
// Получение ID роли
|
||||
const roleResult = await db.query(
|
||||
'SELECT id FROM roles WHERE name = $1',
|
||||
[isAdmin ? 'admin' : 'user']
|
||||
);
|
||||
|
||||
if (roleResult.rows.length > 0) {
|
||||
const roleId = roleResult.rows[0].id;
|
||||
|
||||
// Обновление роли пользователя
|
||||
await db.query(
|
||||
'UPDATE users SET role_id = $1, last_token_check = NOW() WHERE id = $2',
|
||||
[roleId, userId]
|
||||
);
|
||||
|
||||
logger.info(`Updated user ${userId} role to ${isAdmin ? 'admin' : 'user'} based on token balance`);
|
||||
}
|
||||
}
|
||||
|
||||
return isAdmin;
|
||||
} catch (error) {
|
||||
logger.error(`Error checking token balance for ${address}: ${error.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает информацию о пользователе, включая его роль
|
||||
* @param {number} userId - ID пользователя
|
||||
* @returns {Promise<Object>} - Информация о пользователе
|
||||
*/
|
||||
async function getUserInfo(userId) {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT u.id, u.username, u.preferred_language, r.name as role
|
||||
FROM users u
|
||||
LEFT JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.id = $1
|
||||
`, [userId]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logger.error(`Error getting user info for ${userId}: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запускает проверку токенов для всех пользователей
|
||||
*/
|
||||
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 token balances for ${walletUsers.rows.length} users`);
|
||||
|
||||
for (const user of walletUsers.rows) {
|
||||
// Проверка баланса токенов
|
||||
const hasTokens = await checkTokenBalanceAndUpdateRole(user.address);
|
||||
|
||||
logger.info(`User ${user.id} with address ${user.address}: admin=${hasTokens}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error checking token balances: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
checkAccess,
|
||||
checkIfAdmin,
|
||||
checkTokenBalanceAndUpdateRole,
|
||||
getUserInfo,
|
||||
checkAllUsersTokens
|
||||
};
|
||||
|
||||
@@ -2,13 +2,11 @@ const { SiweMessage } = require('siwe');
|
||||
const { ethers } = require('ethers');
|
||||
const AccessTokenABI = require('../artifacts/contracts/AccessToken.sol/AccessToken.json').abi;
|
||||
require('dotenv').config();
|
||||
const { pool } = require('../db');
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||||
const accessToken = new ethers.Contract(
|
||||
process.env.ACCESS_TOKEN_ADDRESS,
|
||||
AccessTokenABI,
|
||||
provider
|
||||
);
|
||||
// В ethers.js v6.x используется JsonRpcProvider напрямую
|
||||
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
|
||||
const accessToken = new ethers.Contract(process.env.ACCESS_TOKEN_ADDRESS, AccessTokenABI, provider);
|
||||
|
||||
// Проверяем наличие адреса контракта
|
||||
if (!process.env.ACCESS_TOKEN_ADDRESS) {
|
||||
@@ -25,13 +23,13 @@ if (!process.env.ACCESS_TOKEN_ADDRESS) {
|
||||
async function verifySignature(message, signature, address) {
|
||||
try {
|
||||
// Формируем сообщение для проверки
|
||||
const domain = message.domain || window.location.host;
|
||||
const domain = message.domain || 'localhost';
|
||||
const statement = message.statement || 'Sign in with Ethereum to the app.';
|
||||
const uri = message.uri || window.location.origin;
|
||||
const uri = message.uri || 'http://localhost:8000';
|
||||
const version = message.version || '1';
|
||||
const chainId = message.chainId || '1';
|
||||
const nonce = message.nonce;
|
||||
|
||||
|
||||
const messageToVerify = `${domain} wants you to sign in with your Ethereum account:
|
||||
${address}
|
||||
|
||||
@@ -42,10 +40,10 @@ Version: ${version}
|
||||
Chain ID: ${chainId}
|
||||
Nonce: ${nonce}
|
||||
`;
|
||||
|
||||
// Восстанавливаем адрес из подписи
|
||||
|
||||
// В ethers.js v6.x используется verifyMessage напрямую
|
||||
const recoveredAddress = ethers.verifyMessage(messageToVerify, signature);
|
||||
|
||||
|
||||
return recoveredAddress.toLowerCase() === address.toLowerCase();
|
||||
} catch (error) {
|
||||
console.error('Signature verification error:', error);
|
||||
@@ -62,23 +60,125 @@ async function verifyAndCheckAccess(message, signature, address) {
|
||||
// Проверяем подпись
|
||||
const verified = await verifySignature(message, signature, address);
|
||||
if (!verified) {
|
||||
return {
|
||||
return {
|
||||
verified: false,
|
||||
access: { hasAccess: false }
|
||||
access: { hasAccess: false },
|
||||
};
|
||||
}
|
||||
|
||||
// Проверяем доступ
|
||||
const access = await checkAccess(address);
|
||||
|
||||
|
||||
return {
|
||||
verified: true,
|
||||
access
|
||||
access,
|
||||
};
|
||||
}
|
||||
|
||||
// Функция для поиска или создания пользователя
|
||||
async function findOrCreateUser(identifier, identityType = 'wallet') {
|
||||
try {
|
||||
// Проверяем, является ли адрес адресом администратора
|
||||
const isAdmin = identityType === 'wallet' &&
|
||||
identifier.toLowerCase() === process.env.ADMIN_WALLET_ADDRESS.toLowerCase();
|
||||
|
||||
console.log(`Проверка на администратора: ${identifier.toLowerCase()} === ${process.env.ADMIN_WALLET_ADDRESS.toLowerCase()} = ${isAdmin}`);
|
||||
|
||||
// Проверяем, существует ли пользователь с таким идентификатором
|
||||
const identityResult = await pool.query(
|
||||
'SELECT user_id FROM user_identities WHERE identity_type = $1 AND identity_value = $2',
|
||||
[identityType, identifier.toLowerCase()]
|
||||
);
|
||||
|
||||
let userId;
|
||||
let isNewUser = false;
|
||||
|
||||
if (identityResult.rows.length > 0) {
|
||||
// Пользователь найден
|
||||
userId = identityResult.rows[0].user_id;
|
||||
console.log(`Найден существующий пользователь с ID: ${userId}`);
|
||||
|
||||
// Обновляем статус администратора, если это необходимо
|
||||
if (isAdmin) {
|
||||
await pool.query(
|
||||
'UPDATE users SET is_admin = true WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
console.log(`Обновлен статус администратора для пользователя ${userId}`);
|
||||
}
|
||||
} else {
|
||||
// Создаем нового пользователя с явным указанием всех необходимых полей
|
||||
const username = `user_${Date.now()}`;
|
||||
|
||||
// Проверяем существование роли USER или ADMIN
|
||||
const roleName = isAdmin ? 'ADMIN' : 'USER';
|
||||
const roleCheck = await pool.query('SELECT id FROM roles WHERE name = $1', [roleName]);
|
||||
let roleId;
|
||||
|
||||
if (roleCheck.rows.length > 0) {
|
||||
roleId = roleCheck.rows[0].id;
|
||||
} else {
|
||||
// Если роли нет, создаем её
|
||||
const newRole = await pool.query(
|
||||
'INSERT INTO roles (name, description) VALUES ($1, $2) RETURNING id',
|
||||
[roleName, isAdmin ? 'Administrator role' : 'Regular user role']
|
||||
);
|
||||
roleId = newRole.rows[0].id;
|
||||
}
|
||||
|
||||
// Создаем пользователя с обязательными полями
|
||||
const userResult = await pool.query(
|
||||
`INSERT INTO users (username, role_id, address, created_at, is_admin)
|
||||
VALUES ($1, $2, $3, NOW(), $4)
|
||||
RETURNING id`,
|
||||
[username, roleId, identifier.toLowerCase(), isAdmin]
|
||||
);
|
||||
|
||||
userId = userResult.rows[0].id;
|
||||
isNewUser = true;
|
||||
|
||||
// Создаем запись в таблице идентификаторов
|
||||
await pool.query(
|
||||
'INSERT INTO user_identities (user_id, identity_type, identity_value, verified, created_at) VALUES ($1, $2, $3, true, NOW())',
|
||||
[userId, identityType, identifier.toLowerCase()]
|
||||
);
|
||||
|
||||
console.log(`Создан новый пользователь с ID: ${userId}, isAdmin: ${isAdmin}`);
|
||||
}
|
||||
|
||||
// Получаем информацию о пользователе
|
||||
try {
|
||||
const userInfo = await pool.query(
|
||||
`SELECT u.id, u.username, u.is_admin, r.name as role
|
||||
FROM users u
|
||||
JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.id = $1`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
if (userInfo.rows.length > 0) {
|
||||
return userInfo.rows[0];
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка при получении информации о пользователе:', err);
|
||||
}
|
||||
|
||||
// Если не удалось получить полную информацию, возвращаем базовую
|
||||
return {
|
||||
id: userId,
|
||||
username: isNewUser ? `user_${Date.now()}` : 'unknown',
|
||||
is_admin: isAdmin,
|
||||
role: isAdmin ? 'ADMIN' : 'USER'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Ошибка при поиске/создании пользователя:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
verifyAndCheckAccess,
|
||||
verifySignature,
|
||||
checkAccess
|
||||
};
|
||||
checkAccess,
|
||||
findOrCreateUser
|
||||
};
|
||||
|
||||
@@ -14,4 +14,4 @@ async function checkMailServer(domain) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkMailServer };
|
||||
module.exports = { checkMailServer };
|
||||
|
||||
46
backend/utils/contracts.js
Normal file
46
backend/utils/contracts.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { ethers } = require('ethers');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const logger = require('./logger');
|
||||
|
||||
/**
|
||||
* Получает экземпляр контракта по его имени
|
||||
* @param {string} contractName - Имя контракта (например, 'AccessToken')
|
||||
* @returns {Promise<ethers.Contract>} - Экземпляр контракта
|
||||
*/
|
||||
async function getContract(contractName) {
|
||||
try {
|
||||
// Путь к артефакту контракта
|
||||
const artifactPath = path.join(__dirname, '..', 'artifacts', 'contracts', `${contractName}.sol`, `${contractName}.json`);
|
||||
|
||||
// Проверка существования файла
|
||||
if (!fs.existsSync(artifactPath)) {
|
||||
throw new Error(`Артефакт контракта ${contractName} не найден по пути ${artifactPath}`);
|
||||
}
|
||||
|
||||
// Загрузка ABI из артефакта
|
||||
const contractArtifact = require(artifactPath);
|
||||
const contractABI = contractArtifact.abi;
|
||||
|
||||
// Получение адреса контракта из переменных окружения
|
||||
const contractAddress = process.env[`${contractName.toUpperCase()}_ADDRESS`];
|
||||
if (!contractAddress) {
|
||||
throw new Error(`Адрес контракта ${contractName} не найден в переменных окружения`);
|
||||
}
|
||||
|
||||
// Подключение к провайдеру
|
||||
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||||
|
||||
// Создание экземпляра контракта
|
||||
const contract = new ethers.Contract(contractAddress, contractABI, provider);
|
||||
|
||||
return contract;
|
||||
} catch (error) {
|
||||
logger.error(`Ошибка при получении контракта ${contractName}: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getContract
|
||||
};
|
||||
2
backend/utils/db.js
Normal file
2
backend/utils/db.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Реэкспорт основного модуля db
|
||||
module.exports = require('../db');
|
||||
@@ -1,6 +1,6 @@
|
||||
// Функция для создания задержки
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// Функция для валидации email адреса
|
||||
@@ -11,5 +11,5 @@ function isValidEmail(email) {
|
||||
|
||||
module.exports = {
|
||||
sleep,
|
||||
isValidEmail
|
||||
};
|
||||
isValidEmail,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ const { Pool } = require('pg');
|
||||
// Подключение к БД
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false
|
||||
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ async function linkIdentity(userId, identityType, identityValue) {
|
||||
'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) {
|
||||
@@ -30,13 +30,13 @@ async function linkIdentity(userId, identityType, identityValue) {
|
||||
// Если идентификатор уже связан с этим пользователем, ничего не делаем
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Добавляем новую связь
|
||||
await pool.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) {
|
||||
@@ -57,11 +57,11 @@ async function getUserIdByIdentity(identityType, identityValue) {
|
||||
'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);
|
||||
@@ -80,7 +80,7 @@ async function getUserIdentities(userId) {
|
||||
'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);
|
||||
@@ -91,5 +91,5 @@ async function getUserIdentities(userId) {
|
||||
module.exports = {
|
||||
linkIdentity,
|
||||
getUserIdByIdentity,
|
||||
getUserIdentities
|
||||
};
|
||||
getUserIdentities,
|
||||
};
|
||||
|
||||
@@ -3,25 +3,19 @@ const path = require('path');
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: process.env.LOG_LEVEL || 'info',
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
),
|
||||
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
format: winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.simple()
|
||||
)
|
||||
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/error.log'),
|
||||
level: 'error'
|
||||
level: 'error',
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: path.join(__dirname, '../logs/combined.log')
|
||||
})
|
||||
]
|
||||
filename: path.join(__dirname, '../logs/combined.log'),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
module.exports = logger;
|
||||
module.exports = logger;
|
||||
|
||||
4
backend/utils/wallet.js
Normal file
4
backend/utils/wallet.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(process.env.ETHEREUM_NETWORK_URL);
|
||||
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user