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

This commit is contained in:
2025-06-01 15:13:52 +03:00
parent 03ea1cf726
commit 2507d776e0
32 changed files with 1832 additions and 445 deletions

View File

@@ -0,0 +1,36 @@
-- Миграция для динамических пользовательских таблиц (аналог Notion)
CREATE TABLE IF NOT EXISTS user_tables (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_columns (
id SERIAL PRIMARY KEY,
table_id INTEGER NOT NULL REFERENCES user_tables(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL, -- text, number, select, multiselect, date, etc.
options JSONB DEFAULT NULL, -- для select/multiselect
"order" INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_rows (
id SERIAL PRIMARY KEY,
table_id INTEGER NOT NULL REFERENCES user_tables(id) ON DELETE CASCADE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS user_cell_values (
id SERIAL PRIMARY KEY,
row_id INTEGER NOT NULL REFERENCES user_rows(id) ON DELETE CASCADE,
column_id INTEGER NOT NULL REFERENCES user_columns(id) ON DELETE CASCADE,
value TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(row_id, column_id)
);

View File

@@ -5,13 +5,12 @@ const { ERROR_CODES } = require('../utils/constants');
/**
* Middleware для обработки ошибок
*/
// eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, /* next */) => {
const errorHandler = (err, req, res, next) => {
console.log('errorHandler called, arguments:', arguments);
console.log('typeof res:', typeof res, 'isFunction:', typeof res === 'function');
console.error('errorHandler: err =', err);
console.error('errorHandler: typeof err =', typeof err);
console.error('errorHandler: stack =', err && err.stack);
console.log('errorHandler called, typeof res:', typeof res, 'res:', res);
console.log('typeof res:', typeof res, 'isFunction:', typeof res === 'function');
// Логируем ошибку
logger.error(`Error: ${err.message}`, {
stack: err.stack,

View File

@@ -5,6 +5,5 @@
"env": {
"NODE_ENV": "development"
},
"ext": "js,json,env",
"exec": "node server.js"
"ext": "js,json,env"
}

220
backend/routes/tables.js Normal file
View File

@@ -0,0 +1,220 @@
const express = require('express');
const router = express.Router();
const db = require('../db');
const { requireAuth } = require('../middleware/auth');
router.use((req, res, next) => {
console.log('Tables router received:', req.method, req.originalUrl);
next();
});
// Получить список всех таблиц (доступно всем)
router.get('/', async (req, res, next) => {
try {
const result = await db.getQuery()('SELECT * FROM user_tables ORDER BY id');
res.json(result.rows);
} catch (err) {
next(err);
}
});
// Создать новую таблицу (доступно всем)
router.post('/', async (req, res, next) => {
try {
const { name, description } = req.body;
const result = await db.getQuery()(
'INSERT INTO user_tables (name, description) VALUES ($1, $2) RETURNING *',
[name, description || null]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Получить структуру и данные таблицы (доступно всем)
router.get('/:id', async (req, res, next) => {
try {
const tableId = req.params.id;
const columns = (await db.getQuery()('SELECT * FROM user_columns WHERE table_id = $1 ORDER BY "order" ASC, id ASC', [tableId])).rows;
const rows = (await db.getQuery()('SELECT * FROM user_rows WHERE table_id = $1 ORDER BY id', [tableId])).rows;
const cellValues = (await db.getQuery()('SELECT * FROM user_cell_values WHERE row_id IN (SELECT id FROM user_rows WHERE table_id = $1)', [tableId])).rows;
res.json({ columns, rows, cellValues });
} catch (err) {
next(err);
}
});
// Добавить столбец (доступно всем)
router.post('/:id/columns', async (req, res, next) => {
try {
const tableId = req.params.id;
const { name, type, options, order } = req.body;
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, options ? JSON.stringify(options) : null, order || 0]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Добавить строку (доступно всем)
router.post('/:id/rows', async (req, res, next) => {
try {
const tableId = req.params.id;
const result = await db.getQuery()(
'INSERT INTO user_rows (table_id) VALUES ($1) RETURNING *',
[tableId]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Изменить значение ячейки (доступно всем)
router.patch('/cell/:cellId', async (req, res, next) => {
try {
const cellId = req.params.cellId;
const { value } = req.body;
const result = await db.getQuery()(
'UPDATE user_cell_values SET value = $1, updated_at = NOW() WHERE id = $2 RETURNING *',
[value, cellId]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Создать/обновить значение ячейки (upsert) (доступно всем)
router.post('/cell', async (req, res, next) => {
try {
const { row_id, column_id, value } = req.body;
const result = await db.getQuery()(
`INSERT INTO user_cell_values (row_id, column_id, value) VALUES ($1, $2, $3)
ON CONFLICT (row_id, column_id) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()
RETURNING *`,
[row_id, column_id, value]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// Удалить строку (доступно всем)
router.delete('/row/:rowId', async (req, res, next) => {
try {
const rowId = req.params.rowId;
await db.getQuery()('DELETE FROM user_rows WHERE id = $1', [rowId]);
res.json({ success: true });
} catch (err) {
next(err);
}
});
// Удалить столбец (доступно всем)
router.delete('/column/:columnId', async (req, res, next) => {
try {
const columnId = req.params.columnId;
await db.getQuery()('DELETE FROM user_columns WHERE id = $1', [columnId]);
res.json({ success: true });
} catch (err) {
next(err);
}
});
// PATCH для обновления столбца (доступно всем)
router.patch('/column/:columnId', async (req, res, next) => {
try {
const columnId = req.params.columnId;
const { name, type, options, order } = req.body;
// Построение динамического запроса
const updates = [];
const values = [];
let paramIndex = 1;
if (name !== undefined) {
updates.push(`name = $${paramIndex++}`);
values.push(name);
}
if (type !== undefined) {
updates.push(`type = $${paramIndex++}`);
values.push(type);
}
if (options !== undefined) {
updates.push(`options = $${paramIndex++}`);
values.push(options ? JSON.stringify(options) : null);
}
if (order !== undefined) {
updates.push(`"order" = $${paramIndex++}`);
values.push(order);
}
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);
}
});
// PATCH: обновить название/описание таблицы (доступно всем)
router.patch('/:id', async (req, res, next) => {
try {
const tableId = req.params.id;
const { name, description } = req.body;
const result = await db.getQuery()(
`UPDATE user_tables SET
name = COALESCE($1, name),
description = COALESCE($2, description),
updated_at = NOW()
WHERE id = $3 RETURNING *`,
[name, description, tableId]
);
res.json(result.rows[0]);
} catch (err) {
next(err);
}
});
// DELETE: удалить таблицу и каскадно все связанные строки/столбцы/ячейки (доступно всем)
router.delete('/:id', requireAuth, async (req, res, next) => {
try {
const tableId = Number(req.params.id);
console.log('Backend: typeof tableId:', typeof tableId, 'value:', tableId);
// Проверяем, существует ли таблица
const checkResult = await db.getQuery()('SELECT id, name FROM user_tables WHERE id = $1', [tableId]);
console.log('Backend: Table check result:', checkResult.rows);
if (checkResult.rows.length === 0) {
console.log('Backend: Table not found');
return res.status(404).json({ error: 'Table not found' });
}
// Удаляем только основную таблицу - каскадное удаление сработает автоматически
console.log('Backend: Executing DELETE query for table_id:', tableId);
const result = await db.getQuery()('DELETE FROM user_tables WHERE id = $1', [tableId]);
console.log('Backend: Delete result - rowCount:', result.rowCount);
res.json({ success: true, deleted: result.rowCount });
} catch (err) {
console.error('Backend: Error deleting table:', err);
next(err);
}
});
module.exports = router;

View File

@@ -15,6 +15,8 @@ const pgSession = require('connect-pg-simple')(session);
const authService = require('./services/auth-service');
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;
@@ -94,6 +96,7 @@ 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) => {
@@ -122,4 +125,6 @@ process.on('uncaughtException', (err) => {
logger.error('Uncaught Exception:', err);
});
app.use(errorHandler);
module.exports = app;