ваше сообщение коммита
This commit is contained in:
@@ -8,6 +8,7 @@ const { requireAuth } = require('../middleware/auth');
|
||||
const crypto = require('crypto');
|
||||
const aiAssistantSettingsService = require('../services/aiAssistantSettingsService');
|
||||
const aiAssistantRulesService = require('../services/aiAssistantRulesService');
|
||||
const { isUserBlocked } = require('../utils/userUtils');
|
||||
|
||||
// Настройка multer для обработки файлов в памяти
|
||||
const storage = multer.memoryStorage();
|
||||
@@ -423,6 +424,11 @@ router.post('/message', requireAuth, upload.array('attachments'), async (req, re
|
||||
const userMessage = userMessageResult.rows[0];
|
||||
logger.info('User message saved', { messageId: userMessage.id, conversationId });
|
||||
|
||||
if (await isUserBlocked(userId)) {
|
||||
logger.info(`[Chat] Пользователь ${userId} заблокирован — ответ ИИ не отправляется.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Новая логика автоответа ИИ по RAG ---
|
||||
let aiMessage = null;
|
||||
let shouldGenerateAiReply = true;
|
||||
|
||||
@@ -4,6 +4,7 @@ const db = require('../db');
|
||||
const { broadcastMessagesUpdate } = require('../wsHub');
|
||||
const telegramBot = require('../services/telegramBot');
|
||||
const emailBot = new (require('../services/emailBot'))();
|
||||
const { isUserBlocked } = require('../utils/userUtils');
|
||||
|
||||
// GET /api/messages?userId=123
|
||||
router.get('/', async (req, res) => {
|
||||
@@ -44,6 +45,10 @@ router.get('/', async (req, res) => {
|
||||
router.post('/', async (req, res) => {
|
||||
const { user_id, sender_type, content, channel, role, direction, attachment_filename, attachment_mimetype, attachment_size, attachment_data, metadata } = req.body;
|
||||
try {
|
||||
// Проверка блокировки пользователя
|
||||
if (await isUserBlocked(user_id)) {
|
||||
return res.status(403).json({ error: 'Пользователь заблокирован. Сообщение не принимается.' });
|
||||
}
|
||||
// Проверка наличия идентификатора для выбранного канала
|
||||
if (channel === 'email') {
|
||||
const emailIdentity = await db.getQuery()(
|
||||
|
||||
@@ -7,9 +7,9 @@ router.get('/health', (req, res) => {
|
||||
});
|
||||
|
||||
router.post('/answer', async (req, res) => {
|
||||
const { tableId, question, userTags, product, systemPrompt, priority, date, rules, history, model, language } = req.body;
|
||||
const { tableId, question, product, systemPrompt, priority, date, rules, history, model, language } = req.body;
|
||||
try {
|
||||
const ragResult = await ragAnswer({ tableId, userQuestion: question, userTags, product });
|
||||
const ragResult = await ragAnswer({ tableId, userQuestion: question, product });
|
||||
const llmResponse = await generateLLMResponse({
|
||||
userQuestion: question,
|
||||
context: ragResult.context,
|
||||
@@ -17,7 +17,6 @@ router.post('/answer', async (req, res) => {
|
||||
objectionAnswer: ragResult.objectionAnswer,
|
||||
answer: ragResult.answer,
|
||||
systemPrompt,
|
||||
userTags: userTags?.join ? userTags.join(', ') : userTags,
|
||||
product,
|
||||
priority: priority || ragResult.priority,
|
||||
date: date || ragResult.date,
|
||||
|
||||
@@ -50,6 +50,30 @@ router.get('/:id', async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Вспомогательная функция для генерации плейсхолдера
|
||||
function generatePlaceholder(name, existingPlaceholders = []) {
|
||||
// Транслитерация (упрощённая)
|
||||
const cyrillicToLatinMap = {
|
||||
а: 'a', б: 'b', в: 'v', г: 'g', д: 'd', е: 'e', ё: 'e', ж: 'zh', з: 'z', и: 'i', й: 'y', к: 'k', л: 'l', м: 'm', н: 'n', о: 'o', п: 'p', р: 'r', с: 's', т: 't', у: 'u', ф: 'f', х: 'h', ц: 'ts', ч: 'ch', ш: 'sh', щ: 'sch', ъ: '', ы: 'y', ь: '', э: 'e', ю: 'yu', я: 'ya'
|
||||
};
|
||||
let translit = name.toLowerCase().split('').map(ch => {
|
||||
if (cyrillicToLatinMap[ch]) return cyrillicToLatinMap[ch];
|
||||
if (/[a-z0-9]/.test(ch)) return ch;
|
||||
if (ch === ' ') return '_';
|
||||
if (ch === '-') return '_';
|
||||
return '';
|
||||
}).join('');
|
||||
translit = translit.replace(/_+/g, '_').replace(/^_+|_+$/g, '');
|
||||
let base = translit;
|
||||
let candidate = base;
|
||||
let i = 1;
|
||||
while (existingPlaceholders.includes(candidate)) {
|
||||
candidate = `${base}_${i}`;
|
||||
i++;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// Добавить столбец (доступно всем)
|
||||
router.post('/:id/columns', async (req, res, next) => {
|
||||
try {
|
||||
@@ -62,9 +86,13 @@ router.post('/:id/columns', async (req, res, next) => {
|
||||
if (purpose) {
|
||||
finalOptions.purpose = purpose;
|
||||
}
|
||||
// Получаем уже существующие плейсхолдеры в таблице
|
||||
const existing = (await db.getQuery()('SELECT placeholder FROM user_columns WHERE table_id = $1', [tableId])).rows;
|
||||
const existingPlaceholders = existing.map(c => c.placeholder).filter(Boolean);
|
||||
const placeholder = generatePlaceholder(name, existingPlaceholders);
|
||||
const result = await db.getQuery()(
|
||||
'INSERT INTO user_columns (table_id, name, type, options, "order") VALUES ($1, $2, $3, $4, $5) RETURNING *',
|
||||
[tableId, name, type, finalOptions ? JSON.stringify(finalOptions) : null, order || 0]
|
||||
'INSERT INTO user_columns (table_id, name, type, options, "order", placeholder) VALUES ($1, $2, $3, $4, $5, $6) RETURNING *',
|
||||
[tableId, name, type, finalOptions ? JSON.stringify(finalOptions) : null, order || 0, placeholder]
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
@@ -80,6 +108,7 @@ router.post('/:id/rows', async (req, res, next) => {
|
||||
'INSERT INTO user_rows (table_id) VALUES ($1) RETURNING *',
|
||||
[tableId]
|
||||
);
|
||||
console.log('[DEBUG][addRow] result.rows[0]:', result.rows[0]);
|
||||
// Получаем все строки и значения для upsert
|
||||
const rows = (await db.getQuery()('SELECT r.id as row_id, c.value as text, c2.value as answer FROM user_rows r LEFT JOIN user_cell_values c ON c.row_id = r.id AND c.column_id = 1 LEFT JOIN user_cell_values c2 ON c2.row_id = r.id AND c2.column_id = 2 WHERE r.table_id = $1', [tableId])).rows;
|
||||
const upsertRows = rows.filter(r => r.row_id && r.text).map(r => ({ row_id: r.row_id, text: r.text, metadata: { answer: r.answer } }));
|
||||
@@ -87,17 +116,18 @@ router.post('/:id/rows', async (req, res, next) => {
|
||||
if (upsertRows.length > 0) {
|
||||
await vectorSearchClient.upsert(tableId, upsertRows);
|
||||
}
|
||||
console.log('[DEBUG][addRow] res.json:', result.rows[0]);
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить строки таблицы с фильтрацией по продукту и тегам
|
||||
// Получить строки таблицы с фильтрацией по продукту, тегам и связям
|
||||
router.get('/:id/rows', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
const { product, tags } = req.query; // tags = "B2B,VIP"
|
||||
const { product, tags, ...relationFilters } = req.query; // tags = "B2B,VIP", relation_{colId}=rowId
|
||||
// Получаем все столбцы, строки и значения ячеек
|
||||
const columns = (await db.getQuery()('SELECT * FROM user_columns WHERE table_id = $1', [tableId])).rows;
|
||||
const rows = (await db.getQuery()('SELECT * FROM user_rows WHERE table_id = $1', [tableId])).rows;
|
||||
@@ -118,7 +148,7 @@ router.get('/:id/rows', async (req, res, next) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Фильтрация на сервере
|
||||
// Фильтрация на сервере (старое)
|
||||
let filtered = data;
|
||||
if (product) {
|
||||
filtered = filtered.filter(r => r.product === product);
|
||||
@@ -130,6 +160,33 @@ router.get('/:id/rows', async (req, res, next) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Новая фильтрация по relation/multiselect/lookup
|
||||
const relationFilterKeys = Object.keys(relationFilters).filter(k => k.startsWith('relation_') || k.startsWith('multiselect_') || k.startsWith('lookup_'));
|
||||
if (relationFilterKeys.length > 0) {
|
||||
// Получаем все связи для строк этой таблицы
|
||||
const rowIds = filtered.map(r => r.id);
|
||||
const rels = (await db.getQuery()(
|
||||
'SELECT * FROM user_table_relations WHERE from_row_id = ANY($1)', [rowIds]
|
||||
)).rows;
|
||||
for (const key of relationFilterKeys) {
|
||||
const [type, colId] = key.split('_');
|
||||
const filterVals = (relationFilters[key] || '').split(',').map(v => v.trim()).filter(Boolean);
|
||||
if (!colId || !filterVals.length) continue;
|
||||
filtered = filtered.filter(r => {
|
||||
const relsForRow = rels.filter(rel => String(rel.from_row_id) === String(r.id) && String(rel.column_id) === colId);
|
||||
if (type === 'relation' || type === 'lookup') {
|
||||
// Обычная связь: хотя бы одна связь с нужным to_row_id
|
||||
return relsForRow.some(rel => filterVals.includes(String(rel.to_row_id)));
|
||||
} else if (type === 'multiselect') {
|
||||
// Мультивыбор: все значения должны быть среди связей
|
||||
const rowVals = relsForRow.map(rel => String(rel.to_row_id));
|
||||
return filterVals.every(val => rowVals.includes(val));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json(filtered);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
@@ -237,13 +294,21 @@ router.delete('/column/:columnId', async (req, res, next) => {
|
||||
router.patch('/column/:columnId', async (req, res, next) => {
|
||||
try {
|
||||
const columnId = req.params.columnId;
|
||||
const { name, type, options, order } = req.body;
|
||||
|
||||
const { name, type, options, order, placeholder } = req.body;
|
||||
// Получаем table_id для проверки уникальности плейсхолдера
|
||||
const colInfo = (await db.getQuery()('SELECT table_id, name FROM user_columns WHERE id = $1', [columnId])).rows[0];
|
||||
if (!colInfo) return res.status(404).json({ error: 'Column not found' });
|
||||
let newPlaceholder = placeholder;
|
||||
if (name !== undefined && !placeholder) {
|
||||
// Если имя меняется и плейсхолдер не передан — генерируем новый
|
||||
const existing = (await db.getQuery()('SELECT placeholder FROM user_columns WHERE table_id = $1 AND id != $2', [colInfo.table_id, columnId])).rows;
|
||||
const existingPlaceholders = existing.map(c => c.placeholder).filter(Boolean);
|
||||
newPlaceholder = generatePlaceholder(name, existingPlaceholders);
|
||||
}
|
||||
// Построение динамического запроса
|
||||
const updates = [];
|
||||
const values = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (name !== undefined) {
|
||||
updates.push(`name = $${paramIndex++}`);
|
||||
values.push(name);
|
||||
@@ -260,21 +325,20 @@ router.patch('/column/:columnId', async (req, res, next) => {
|
||||
updates.push(`"order" = $${paramIndex++}`);
|
||||
values.push(order);
|
||||
}
|
||||
|
||||
if (newPlaceholder !== undefined) {
|
||||
updates.push(`placeholder = $${paramIndex++}`);
|
||||
values.push(newPlaceholder);
|
||||
}
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({ error: 'No fields to update' });
|
||||
}
|
||||
|
||||
updates.push(`updated_at = NOW()`);
|
||||
values.push(columnId);
|
||||
|
||||
const query = `UPDATE user_columns SET ${updates.join(', ')} WHERE id = $${paramIndex} RETURNING *`;
|
||||
const result = await db.getQuery()(query, values);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Column not found' });
|
||||
}
|
||||
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
@@ -383,4 +447,98 @@ router.delete('/:id', requireAuth, async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Получить связи для строки (relation/multiselect/lookup)
|
||||
router.get('/:tableId/row/:rowId/relations', async (req, res, next) => {
|
||||
try {
|
||||
const { tableId, rowId } = req.params;
|
||||
const relations = (await db.getQuery()(
|
||||
'SELECT * FROM user_table_relations WHERE from_row_id = $1',
|
||||
[rowId]
|
||||
)).rows;
|
||||
res.json(relations);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Добавить связь (relation/multiselect/lookup)
|
||||
router.post('/:tableId/row/:rowId/relations', async (req, res, next) => {
|
||||
try {
|
||||
const { tableId, rowId } = req.params;
|
||||
const { column_id, to_table_id, to_row_id } = req.body;
|
||||
const result = await db.getQuery()(
|
||||
`INSERT INTO user_table_relations (from_row_id, column_id, to_table_id, to_row_id)
|
||||
VALUES ($1, $2, $3, $4) RETURNING *`,
|
||||
[rowId, column_id, to_table_id, to_row_id]
|
||||
);
|
||||
res.json(result.rows[0]);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Удалить связь
|
||||
router.delete('/:tableId/row/:rowId/relations/:relationId', async (req, res, next) => {
|
||||
try {
|
||||
const { relationId } = req.params;
|
||||
await db.getQuery()('DELETE FROM user_table_relations WHERE id = $1', [relationId]);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// --- Массовое обновление связей для multiselect-relation ---
|
||||
router.post('/:tableId/row/:rowId/multirelations', async (req, res, next) => {
|
||||
try {
|
||||
const { tableId, rowId } = req.params;
|
||||
const { column_id, to_table_id, to_row_ids } = req.body; // to_row_ids: массив id
|
||||
if (!Array.isArray(to_row_ids)) return res.status(400).json({ error: 'to_row_ids должен быть массивом' });
|
||||
// Удаляем старые связи для этой строки/столбца
|
||||
await db.getQuery()('DELETE FROM user_table_relations WHERE from_row_id = $1 AND column_id = $2', [rowId, column_id]);
|
||||
// Добавляем новые связи
|
||||
for (const to_row_id of to_row_ids) {
|
||||
await db.getQuery()(
|
||||
`INSERT INTO user_table_relations (from_row_id, column_id, to_table_id, to_row_id)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[rowId, column_id, to_table_id, to_row_id]
|
||||
);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить плейсхолдеры для всех столбцов таблицы
|
||||
router.get('/:id/placeholders', async (req, res, next) => {
|
||||
try {
|
||||
const tableId = req.params.id;
|
||||
const columns = (await db.getQuery()('SELECT id, name, placeholder FROM user_columns WHERE table_id = $1', [tableId])).rows;
|
||||
res.json(columns.map(col => ({
|
||||
id: col.id,
|
||||
name: col.name,
|
||||
placeholder: col.placeholder
|
||||
})));
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Получить все плейсхолдеры по всем пользовательским таблицам
|
||||
router.get('/placeholders/all', async (req, res, next) => {
|
||||
try {
|
||||
const result = await db.getQuery()(`
|
||||
SELECT c.id as column_id, c.name as column_name, c.placeholder, t.id as table_id, t.name as table_name
|
||||
FROM user_columns c
|
||||
JOIN user_tables t ON c.table_id = t.id
|
||||
WHERE c.placeholder IS NOT NULL AND c.placeholder != ''
|
||||
ORDER BY t.id, c.id
|
||||
`);
|
||||
res.json(result.rows);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,61 +0,0 @@
|
||||
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;
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
@@ -1,91 +0,0 @@
|
||||
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;
|
||||
@@ -122,10 +122,10 @@ router.get('/', requireAuth, async (req, res, next) => {
|
||||
const tagIdArr = tagIds.split(',').map(Number).filter(Boolean);
|
||||
if (tagIdArr.length > 0) {
|
||||
sql += `
|
||||
JOIN user_tags ut ON ut.user_id = u.id
|
||||
WHERE ut.tag_id = ANY($${idx++})
|
||||
JOIN user_tag_links utl ON utl.user_id = u.id
|
||||
WHERE utl.tag_id = ANY($${idx++})
|
||||
GROUP BY u.id
|
||||
HAVING COUNT(DISTINCT ut.tag_id) = $${idx++}
|
||||
HAVING COUNT(DISTINCT utl.tag_id) = $${idx++}
|
||||
`;
|
||||
params.push(tagIdArr);
|
||||
params.push(tagIdArr.length);
|
||||
@@ -321,7 +321,7 @@ router.get('/:id', async (req, res, next) => {
|
||||
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]);
|
||||
const userResult = await query('SELECT id, first_name, last_name, created_at, preferred_language, is_blocked FROM users WHERE id = $1', [userId]);
|
||||
if (userResult.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'User not found' });
|
||||
}
|
||||
@@ -339,7 +339,8 @@ router.get('/:id', async (req, res, next) => {
|
||||
telegram: identityMap.telegram || null,
|
||||
wallet: identityMap.wallet || null,
|
||||
created_at: user.created_at,
|
||||
preferred_language: user.preferred_language || []
|
||||
preferred_language: user.preferred_language || [],
|
||||
is_blocked: user.is_blocked || false
|
||||
});
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
@@ -432,4 +433,57 @@ router.post('/import', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- Работа с тегами пользователя через user_tag_links ---
|
||||
// PATCH /api/users/:id/tags — установить теги пользователю
|
||||
router.patch('/:id/tags', async (req, res) => {
|
||||
const userId = Number(req.params.id);
|
||||
const { tags } = req.body; // массив tagIds (id строк из таблицы тегов)
|
||||
if (!Array.isArray(tags)) {
|
||||
return res.status(400).json({ error: 'tags должен быть массивом' });
|
||||
}
|
||||
try {
|
||||
// Удаляем старые связи
|
||||
await db.getQuery()('DELETE FROM user_tag_links WHERE user_id = $1', [userId]);
|
||||
// Добавляем новые связи
|
||||
for (const tagId of tags) {
|
||||
await db.getQuery()(
|
||||
'INSERT INTO user_tag_links (user_id, tag_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
|
||||
[userId, tagId]
|
||||
);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/users/:id/tags — получить все теги пользователя
|
||||
router.get('/:id/tags', async (req, res) => {
|
||||
const userId = Number(req.params.id);
|
||||
try {
|
||||
const result = await db.getQuery()(
|
||||
'SELECT tag_id FROM user_tag_links WHERE user_id = $1',
|
||||
[userId]
|
||||
);
|
||||
res.json({ tags: result.rows.map(r => r.tag_id) });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/users/:id/tags/:tagId — удалить тег у пользователя
|
||||
router.delete('/:id/tags/:tagId', async (req, res) => {
|
||||
const userId = Number(req.params.id);
|
||||
const tagId = Number(req.params.tagId);
|
||||
try {
|
||||
await db.getQuery()(
|
||||
'DELETE FROM user_tag_links WHERE user_id = $1 AND tag_id = $2',
|
||||
[userId, tagId]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user