Files
DLE/backend/routes/kanban.js

340 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;