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

This commit is contained in:
2025-06-06 21:56:17 +03:00
parent 80e0cb5272
commit 75e845b5af
22 changed files with 1123 additions and 488 deletions

View File

@@ -12,6 +12,9 @@ const aiAssistant = require('./services/ai-assistant'); // Добавляем и
const fs = require('fs');
const path = require('path');
const messagesRoutes = require('./routes/messages');
const userTagsRoutes = require('./routes/userTags');
const tagsInitRoutes = require('./routes/tagsInit');
const tagsRoutes = require('./routes/tags');
// Проверка и создание директорий для хранения данных контрактов
const ensureDirectoriesExist = () => {
@@ -100,7 +103,8 @@ app.use(async (req, res, next) => {
// Если сессия уже есть, используем её
if (req.session.authenticated) {
return next();
next();
return;
}
// Проверяем заголовок авторизации
@@ -150,14 +154,16 @@ app.use(
// Логирование запросов
app.use((req, res, next) => {
console.log('[APP] Глобальный лог:', req.method, req.originalUrl);
logger.info(`${req.method} ${req.url}`);
next();
});
// Маршруты API
app.use('/api/tables', tablesRoutes); // ДОЛЖНО БЫТЬ ВЫШЕ!
app.use('/api', identitiesRoutes);
// app.use('/api', identitiesRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/users/:userId/tags', userTagsRoutes);
app.use('/api/users', usersRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/admin', adminRoutes);
@@ -167,6 +173,9 @@ app.use('/api/geocoding', geocodingRoutes); // Добавленное испол
app.use('/api/dle', dleRoutes); // Добавляем маршрут DLE
app.use('/api/settings', settingsRoutes); // Добавляем маршрут настроек
app.use('/api/messages', messagesRoutes);
app.use('/api/tags', tagsInitRoutes);
app.use('/api/tags', tagsRoutes);
app.use('/api/identities', identitiesRoutes);
const nonceStore = new Map(); // или любая другая реализация хранилища nonce

View File

@@ -97,4 +97,4 @@ async function saveGuestMessageToDatabase(message, language, guestId) {
}
// Экспортируем функции для работы с базой данных
module.exports = { getQuery, pool, getPool, setPoolChangeCallback };
module.exports = { query: pool.query.bind(pool), getQuery, pool, getPool, setPoolChangeCallback };

View File

@@ -0,0 +1,13 @@
-- Создание справочника тегов
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
description TEXT
);
-- Создание связующей таблицы "пользователь — тег"
CREATE TABLE IF NOT EXISTS user_tags (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, tag_id)
);

61
backend/routes/tags.js Normal file
View File

@@ -0,0 +1,61 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
// Получить все теги
router.get('/', async (req, res) => {
console.log('GET /api/tags');
try {
const query = db.getQuery();
const { rows } = await query('SELECT * FROM tags ORDER BY name');
res.json(rows);
} catch (e) {
console.error('Ошибка в /api/tags:', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
// Создать тег
router.post('/', async (req, res) => {
console.log('POST /api/tags', req.body);
try {
const { name, description } = req.body;
const query = db.getQuery();
const result = await query(
'INSERT INTO tags (name, description) VALUES ($1, $2) RETURNING *',
[name, description]
);
const row = result && result.rows && result.rows[0] ? result.rows[0] : null;
if (row) {
res.json(row);
} else {
res.status(500).json({ error: 'Не удалось создать тег', result });
}
} catch (e) {
console.error('Ошибка в /api/tags (POST):', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
// Удалить тег и все его связи с пользователями
router.delete('/:tagId', async (req, res) => {
console.log('DELETE /api/tags/:id', req.params.tagId);
try {
const tagId = req.params.tagId;
const query = db.getQuery();
// Сначала удаляем связи user_tags
await query('DELETE FROM user_tags WHERE tag_id = $1', [tagId]);
// Затем удаляем сам тег
const result = await query('DELETE FROM tags WHERE id = $1 RETURNING *', [tagId]);
if (result.rowCount > 0) {
res.json({ success: true });
} else {
res.status(404).json({ error: 'Тег не найден' });
}
} catch (e) {
console.error('Ошибка в /api/tags/:id (DELETE):', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
module.exports = router;

View File

@@ -0,0 +1,29 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
// Инициализация таблиц тегов
router.post('/init', async (req, res) => {
console.log('POST /api/tags/init');
try {
const query = db.getQuery();
await query(`
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE IF NOT EXISTS user_tags (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, tag_id)
);
`);
res.json({ ok: true });
} catch (e) {
console.error('Ошибка в /api/tags/init:', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
module.exports = router;

View File

@@ -0,0 +1,91 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
// Инициализация таблиц тегов (если нужно)
router.post('/init', async (req, res) => {
console.log('POST /api/users/tags/init');
try {
const query = db.getQuery();
await query(`
CREATE TABLE IF NOT EXISTS tags (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE,
description TEXT
);
CREATE TABLE IF NOT EXISTS user_tags (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
tag_id INTEGER NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, tag_id)
);
`);
res.json({ ok: true });
} catch (e) {
console.error('Ошибка в /api/users/tags/init:', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
// --- Работа с тегами пользователя ---
// Получить теги пользователя
router.get('/:userId/tags', async (req, res) => {
console.log('GET /api/users/:id/tags', req.params.userId);
try {
const userId = req.params.userId;
const query = db.getQuery();
const result = await query(
`SELECT t.* FROM tags t
JOIN user_tags ut ON ut.tag_id = t.id
WHERE ut.user_id = $1`,
[userId]
);
const rows = result && result.rows ? result.rows : [];
res.json(rows);
} catch (e) {
console.error('Ошибка в /api/users/:id/tags (GET):', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
// Добавить тег пользователю
router.post('/:userId/tags', async (req, res) => {
console.log('POST /api/users/:id/tags', req.params.userId, req.body);
try {
const userId = req.params.userId;
const { tag_id } = req.body;
const query = db.getQuery();
await query(
'INSERT INTO user_tags (user_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
[userId, tag_id]
);
res.json({ ok: true });
} catch (e) {
console.error('Ошибка в /api/users/:id/tags (POST):', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
// Удалить тег у пользователя
router.delete('/:userId/tags/:tagId', async (req, res) => {
console.log('DELETE /api/users/:id/tags/:tagId', req.params.userId, req.params.tagId);
try {
const userId = req.params.userId;
const tagId = req.params.tagId;
const query = db.getQuery();
const result = await query(
'DELETE FROM user_tags WHERE user_id = $1 AND tag_id = $2 RETURNING *',
[userId, tagId]
);
if (result.rowCount > 0) {
res.json({ success: true });
} else {
res.status(404).json({ error: 'Связь не найдена' });
}
} catch (e) {
console.error('Ошибка в /api/users/:id/tags/:tagId (DELETE):', e);
res.status(500).json({ error: e.message, stack: e.stack });
}
});
module.exports = router;

View File

@@ -3,62 +3,22 @@ const router = express.Router();
const db = require('../db');
const logger = require('../utils/logger');
const { requireAuth } = require('../middleware/auth');
const { deleteUserById } = require('../services/userDeleteService');
const { broadcastContactsUpdate } = require('../wsHub');
// const userService = require('../services/userService');
console.log('[users.js] ROUTER LOADED');
router.use((req, res, next) => {
console.log('[users.js] ROUTER REQUEST:', req.method, req.originalUrl);
next();
});
// Получение списка пользователей
// router.get('/', (req, res) => {
// res.json({ message: 'Users API endpoint' });
// });
// Получение информации о пользователе
router.get('/:address', (req, res) => {
const { address } = req.params;
res.json({
address,
message: 'User details endpoint',
});
});
// Маршрут для обновления языка пользователя
router.post('/update-language', requireAuth, async (req, res, next) => {
try {
const { language } = req.body;
const userId = req.session.userId;
const validLanguages = ['ru', 'en'];
if (!validLanguages.includes(language)) {
return res.status(400).json({ error: 'Неподдерживаемый язык' });
}
await db.getQuery()('UPDATE users SET preferred_language = $1 WHERE id = $2', [language, userId]);
res.json({ success: true });
} catch (error) {
logger.error('Error updating language:', error);
next(error);
}
});
// Маршрут для обновления имени и фамилии пользователя
router.post('/update-profile', requireAuth, async (req, res, next) => {
try {
const { firstName, lastName } = req.body;
const userId = req.session.userId;
if (firstName && firstName.length > 255) {
return res.status(400).json({ error: 'Имя слишком длинное (максимум 255 символов)' });
}
if (lastName && lastName.length > 255) {
return res.status(400).json({ error: 'Фамилия слишком длинная (максимум 255 символов)' });
}
await db.getQuery()('UPDATE users SET first_name = $1, last_name = $2 WHERE id = $3', [
firstName || null,
lastName || null,
userId,
]);
res.json({ success: true });
} catch (error) {
logger.error('Error updating user profile:', error);
next(error);
}
});
// Получить профиль текущего пользователя
/*
router.get('/profile', requireAuth, async (req, res) => {
@@ -172,21 +132,51 @@ router.patch('/:id', async (req, res) => {
// DELETE /api/users/:id — удалить контакт и все связанные данные
router.delete('/:id', async (req, res) => {
const userId = req.params.id;
const client = await db.getPool().connect();
console.log('[users.js] DELETE HANDLER', req.params.id);
const userId = Number(req.params.id);
console.log('[ROUTER] Перед вызовом deleteUserById для userId:', userId);
try {
await client.query('BEGIN');
await client.query('DELETE FROM user_identities WHERE user_id = $1', [userId]);
await client.query('DELETE FROM messages WHERE user_id = $1', [userId]);
// Добавьте другие связанные таблицы, если нужно
await client.query('DELETE FROM users WHERE id = $1', [userId]);
await client.query('COMMIT');
res.json({ success: true });
const deletedCount = await deleteUserById(userId);
console.log('[ROUTER] deleteUserById вернул:', deletedCount);
if (deletedCount === 0) {
return res.status(404).json({ success: false, deleted: 0, error: 'User not found' });
}
broadcastContactsUpdate();
res.json({ success: true, deleted: deletedCount });
} catch (e) {
await client.query('ROLLBACK');
console.error('[DELETE] Ошибка при удалении пользователя:', e);
res.status(500).json({ error: 'DB error', details: e.message });
} finally {
client.release();
}
});
// Получить пользователя по id
router.get('/:id', async (req, res, next) => {
const userId = req.params.id;
try {
const query = db.getQuery();
// Получаем пользователя
const userResult = await query('SELECT id, first_name, last_name, created_at, preferred_language FROM users WHERE id = $1', [userId]);
if (userResult.rows.length === 0) {
return res.status(404).json({ error: 'User not found' });
}
const user = userResult.rows[0];
// Получаем идентификаторы
const identitiesResult = await query('SELECT provider, provider_id FROM user_identities WHERE user_id = $1', [userId]);
const identityMap = {};
for (const id of identitiesResult.rows) {
identityMap[id.provider] = id.provider_id;
}
res.json({
id: user.id,
name: [user.first_name, user.last_name].filter(Boolean).join(' ') || null,
email: identityMap.email || null,
telegram: identityMap.telegram || null,
wallet: identityMap.wallet || null,
created_at: user.created_at,
preferred_language: user.preferred_language || []
});
} catch (e) {
res.status(500).json({ error: e.message });
}
});

View File

@@ -1,22 +1,8 @@
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { ethers } = require('ethers');
const session = require('express-session');
const { app, nonceStore } = require('./app');
const usersRouter = require('./routes/users');
const authRouter = require('./routes/auth');
const identitiesRouter = require('./routes/identities');
const chatRouter = require('./routes/chat');
const { pool } = require('./db');
const helmet = require('helmet');
const { getBot, stopBot } = require('./services/telegramBot');
const pgSession = require('connect-pg-simple')(session);
const authService = require('./services/auth-service');
const http = require('http');
const { initWSS } = require('./wsHub');
const logger = require('./utils/logger');
const EmailBotService = require('./services/emailBot.js');
const tablesRouter = require('./routes/tables');
const errorHandler = require('./middleware/errorHandler');
const PORT = process.env.PORT || 8000;
@@ -28,85 +14,18 @@ console.log('Используемый порт:', process.env.PORT || 8000);
async function initServices() {
try {
console.log('Инициализация сервисов...');
// Останавливаем предыдущий экземпляр бота
console.log('Перед stopBot');
await stopBot();
console.log('После stopBot, перед getBot');
getBot();
console.log('После getBot, перед созданием EmailBotService');
// Добавляем обработку ошибок при запуске бота
try {
console.log('Пробуем создать экземпляр EmailBotService');
// Запуск email-бота
console.log('Создаём экземпляр EmailBotService');
const emailBot = new EmailBotService();
await emailBot.start();
// Добавляем graceful shutdown
process.once('SIGINT', async () => {
await stopBot();
process.exit(0);
});
process.once('SIGTERM', async () => {
await stopBot();
process.exit(0);
});
} catch (error) {
if (error.code === 409) {
logger.warn(
'Another instance of Telegram bot is running. This is normal during development with nodemon'
);
// Просто логируем ошибку и продолжаем работу
// Бот будет запущен при следующем перезапуске
} else {
logger.error('Error launching Telegram bot:', error);
console.error('Ошибка при запуске Telegram-бота:', error);
}
}
// Здесь может быть инициализация ботов, email-сервисов и т.д.
// ...
console.log('Все сервисы успешно инициализированы');
} catch (error) {
console.error('Ошибка при инициализации сервисов:', error);
}
}
// Настройка сессий
app.use(
session({
store: new pgSession({
pool: pool,
tableName: 'session',
}),
secret: process.env.SESSION_SECRET || 'hb3atoken',
resave: false,
saveUninitialized: true,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней
},
})
);
const server = http.createServer(app);
initWSS(server);
// Маршруты API
app.use('/api/users', usersRouter);
app.use('/api/auth', authRouter);
app.use('/api/identities', identitiesRouter);
app.use('/api/chat', chatRouter);
app.use('/api/tables', tablesRouter);
// Эндпоинт для проверки состояния сервера
app.get('/api/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Для отладки
// const host = app.get('host');
// console.log('host:', host);
app.listen(PORT, async () => {
server.listen(PORT, async () => {
try {
await initServices();
console.log(`Server is running on port ${PORT}`);
@@ -125,6 +44,4 @@ process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception:', err);
});
app.use(errorHandler);
module.exports = app;

View File

@@ -0,0 +1,26 @@
const db = require('../db');
async function deleteUserById(userId) {
console.log('[DELETE] Вызван deleteUserById для userId:', userId);
const query = db.getQuery();
try {
await query('BEGIN');
console.log('[DELETE] Начинаем удаление user_identities для userId:', userId);
const resIdentities = await query('DELETE FROM user_identities WHERE user_id = $1', [userId]);
console.log('[DELETE] Удалено user_identities:', resIdentities.rowCount);
console.log('[DELETE] Начинаем удаление messages для userId:', userId);
const resMessages = await query('DELETE FROM messages WHERE user_id = $1', [userId]);
console.log('[DELETE] Удалено messages:', resMessages.rowCount);
console.log('[DELETE] Начинаем удаление пользователя из users:', userId);
const result = await query('DELETE FROM users WHERE id = $1 RETURNING *', [userId]);
console.log('[DELETE] Результат удаления пользователя:', result.rowCount, result.rows);
await query('COMMIT');
return result.rowCount;
} catch (e) {
await query('ROLLBACK');
console.error('[DELETE] Ошибка при удалении пользователя:', e);
throw e;
}
}
module.exports = { deleteUserById };

22
backend/wsHub.js Normal file
View File

@@ -0,0 +1,22 @@
const WebSocket = require('ws');
let wss = null;
const wsClients = new Set();
function initWSS(server) {
wss = new WebSocket.Server({ server });
wss.on('connection', (ws) => {
wsClients.add(ws);
ws.on('close', () => wsClients.delete(ws));
});
}
function broadcastContactsUpdate() {
for (const ws of wsClients) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'contacts-updated' }));
}
}
}
module.exports = { initWSS, broadcastContactsUpdate };