156 lines
8.1 KiB
JavaScript
156 lines
8.1 KiB
JavaScript
const express = require('express');
|
|
const db = require('../../db');
|
|
const { authenticate } = require('../../middleware/auth');
|
|
const router = express.Router();
|
|
|
|
// ── DB-Migration ──────────────────────────────────────────────────────────────
|
|
(function migrate() {
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS kanban_columns (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
color TEXT NOT NULL DEFAULT '#4ecdc4',
|
|
position INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
);
|
|
CREATE TABLE IF NOT EXISTS kanban_cards (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
column_id INTEGER NOT NULL REFERENCES kanban_columns(id) ON DELETE CASCADE,
|
|
user_id INTEGER NOT NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT NOT NULL DEFAULT '',
|
|
priority TEXT NOT NULL DEFAULT 'none',
|
|
position INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
);
|
|
`);
|
|
// Nachrüsten falls Tabelle schon existiert aber color fehlt
|
|
const cols = db.pragma('table_info(kanban_columns)').map(c => c.name);
|
|
if (!cols.includes('color')) {
|
|
db.exec("ALTER TABLE kanban_columns ADD COLUMN color TEXT NOT NULL DEFAULT '#4ecdc4'");
|
|
}
|
|
})();
|
|
|
|
const uid = req => req.user.id;
|
|
|
|
// ── Alle Spalten + Karten laden ───────────────────────────────────────────────
|
|
router.get('/board', authenticate, (req, res) => {
|
|
const me = uid(req);
|
|
const columns = db.prepare(
|
|
'SELECT * FROM kanban_columns WHERE user_id=? ORDER BY position ASC, id ASC'
|
|
).all(me);
|
|
const cards = db.prepare(
|
|
'SELECT * FROM kanban_cards WHERE user_id=? ORDER BY position ASC, id ASC'
|
|
).all(me);
|
|
const colMap = {};
|
|
for (const c of columns) { c.cards = []; colMap[c.id] = c; }
|
|
for (const card of cards) {
|
|
if (colMap[card.column_id]) colMap[card.column_id].cards.push(card);
|
|
}
|
|
res.json({ columns });
|
|
});
|
|
|
|
// ── Spalte erstellen ──────────────────────────────────────────────────────────
|
|
router.post('/columns', authenticate, (req, res) => {
|
|
const { title, color = '#4ecdc4' } = req.body;
|
|
if (!title?.trim()) return res.status(400).json({ error: 'Titel fehlt' });
|
|
const me = uid(req);
|
|
const maxPos = db.prepare('SELECT MAX(position) as m FROM kanban_columns WHERE user_id=?').get(me);
|
|
const pos = (maxPos?.m ?? -1) + 1;
|
|
const r = db.prepare(
|
|
"INSERT INTO kanban_columns (user_id,title,color,position) VALUES (?,?,?,?)"
|
|
).run(me, title.trim(), color, pos);
|
|
res.json(db.prepare('SELECT * FROM kanban_columns WHERE id=?').get(r.lastInsertRowid));
|
|
});
|
|
|
|
// ── Spalte aktualisieren (Titel + Farbe) ─────────────────────────────────────
|
|
// Feste Route vor :param (Lesson Learned)
|
|
router.post('/columns/reorder', authenticate, (req, res) => {
|
|
const { order } = req.body;
|
|
if (!Array.isArray(order)) return res.status(400).json({ error: 'order fehlt' });
|
|
const me = uid(req);
|
|
const upd = db.prepare('UPDATE kanban_columns SET position=? WHERE id=? AND user_id=?');
|
|
db.transaction(() => { for (let i = 0; i < order.length; i++) upd.run(i, order[i], me); })();
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.patch('/columns/:id', authenticate, (req, res) => {
|
|
const me = uid(req);
|
|
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(req.params.id, me);
|
|
if (!col) return res.status(404).json({ error: 'Spalte nicht gefunden' });
|
|
const title = req.body.title !== undefined ? req.body.title.trim() : col.title;
|
|
const color = req.body.color !== undefined ? req.body.color : col.color;
|
|
if (!title) return res.status(400).json({ error: 'Titel fehlt' });
|
|
db.prepare('UPDATE kanban_columns SET title=?, color=? WHERE id=?').run(title, color, col.id);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.delete('/columns/:id', authenticate, (req, res) => {
|
|
const me = uid(req);
|
|
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(req.params.id, me);
|
|
if (!col) return res.status(404).json({ error: 'Spalte nicht gefunden' });
|
|
db.prepare('DELETE FROM kanban_columns WHERE id=?').run(col.id);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
// ── Karten ────────────────────────────────────────────────────────────────────
|
|
router.post('/cards', authenticate, (req, res) => {
|
|
const { column_id, title, description = '', priority = 'none' } = req.body;
|
|
if (!column_id || !title?.trim()) return res.status(400).json({ error: 'column_id und title erforderlich' });
|
|
const me = uid(req);
|
|
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(column_id, me);
|
|
if (!col) return res.status(404).json({ error: 'Spalte nicht gefunden' });
|
|
const maxPos = db.prepare('SELECT MAX(position) as m FROM kanban_cards WHERE column_id=?').get(column_id);
|
|
const pos = (maxPos?.m ?? -1) + 1;
|
|
const r = db.prepare(
|
|
"INSERT INTO kanban_cards (column_id,user_id,title,description,priority,position) VALUES (?,?,?,?,?,?)"
|
|
).run(column_id, me, title.trim(), description.trim(), priority, pos);
|
|
res.json(db.prepare('SELECT * FROM kanban_cards WHERE id=?').get(r.lastInsertRowid));
|
|
});
|
|
|
|
router.patch('/cards/:id', authenticate, (req, res) => {
|
|
const me = uid(req);
|
|
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
|
if (!card) return res.status(404).json({ error: 'Karte nicht gefunden' });
|
|
const title = req.body.title !== undefined ? req.body.title.trim() : card.title;
|
|
const description = req.body.description !== undefined ? req.body.description.trim() : card.description;
|
|
const priority = req.body.priority !== undefined ? req.body.priority : card.priority;
|
|
if (!title) return res.status(400).json({ error: 'Titel darf nicht leer sein' });
|
|
db.prepare(
|
|
"UPDATE kanban_cards SET title=?,description=?,priority=?,updated_at=datetime('now','localtime') WHERE id=?"
|
|
).run(title, description, priority, card.id);
|
|
res.json(db.prepare('SELECT * FROM kanban_cards WHERE id=?').get(card.id));
|
|
});
|
|
|
|
router.delete('/cards/:id', authenticate, (req, res) => {
|
|
const me = uid(req);
|
|
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
|
if (!card) return res.status(404).json({ error: 'Karte nicht gefunden' });
|
|
db.prepare('DELETE FROM kanban_cards WHERE id=?').run(card.id);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.post('/cards/:id/move', authenticate, (req, res) => {
|
|
const { column_id, position } = req.body;
|
|
const me = uid(req);
|
|
const card = db.prepare('SELECT * FROM kanban_cards WHERE id=? AND user_id=?').get(req.params.id, me);
|
|
if (!card) return res.status(404).json({ error: 'Karte nicht gefunden' });
|
|
const targetColId = column_id !== undefined ? column_id : card.column_id;
|
|
const col = db.prepare('SELECT * FROM kanban_columns WHERE id=? AND user_id=?').get(targetColId, me);
|
|
if (!col) return res.status(404).json({ error: 'Zielspalte nicht gefunden' });
|
|
const siblings = db.prepare(
|
|
'SELECT id FROM kanban_cards WHERE column_id=? AND id!=? ORDER BY position ASC, id ASC'
|
|
).all(targetColId, card.id).map(r => r.id);
|
|
const targetPos = position !== undefined
|
|
? Math.max(0, Math.min(position, siblings.length))
|
|
: siblings.length;
|
|
siblings.splice(targetPos, 0, card.id);
|
|
const upd = db.prepare('UPDATE kanban_cards SET column_id=?, position=? WHERE id=?');
|
|
db.transaction(() => { for (let i = 0; i < siblings.length; i++) upd.run(targetColId, i, siblings[i]); })();
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
module.exports = router;
|