antragsideen-hagen/backend/server.js

283 lines
9.8 KiB
JavaScript
Raw Normal View History

import express from 'express';
import cors from 'cors';
import Database from 'better-sqlite3';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const PORT = process.env.PORT || 3000;
const DB_PATH = process.env.DB_PATH || join(__dirname, '../data/antragsideen.db');
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// DB Connection
const db = new Database(DB_PATH);
db.pragma('foreign_keys = ON');
// Health check
app.get('/health', (req, res) => res.json({ status: 'ok' }));
// ============================================
// ANTRÄGE
// ============================================
// Liste aller Anträge (mit Joins)
app.get('/api/antraege', (req, res) => {
const antraege = db.prepare(`
SELECT
a.*,
p.name as prioritaet,
p.farbe as prioritaet_farbe,
s.name as status,
s.farbe as status_farbe,
b.name as bereich,
b.farbe as bereich_farbe,
b.icon as bereich_icon
FROM antraege a
LEFT JOIN prioritaeten p ON a.prioritaet_id = p.id
LEFT JOIN status_typen s ON a.status_id = s.id
LEFT JOIN bereiche b ON a.bereich_id = b.id
ORDER BY p.reihenfolge, a.id
`).all();
// Ausschüsse und Personen hinzufügen
const ausschuesse = db.prepare(`
SELECT aa.antrag_id, aus.id, aus.name, aus.kuerzel
FROM antrag_ausschuesse aa
JOIN ausschuesse aus ON aa.ausschuss_id = aus.id
`).all();
const personen = db.prepare(`
SELECT ap.antrag_id, p.id, p.name, p.rolle
FROM antrag_personen ap
JOIN personen p ON ap.person_id = p.id
`).all();
const ausschuesseMap = {};
ausschuesse.forEach(a => {
if (!ausschuesseMap[a.antrag_id]) ausschuesseMap[a.antrag_id] = [];
ausschuesseMap[a.antrag_id].push({ id: a.id, name: a.name, kuerzel: a.kuerzel });
});
const personenMap = {};
personen.forEach(p => {
if (!personenMap[p.antrag_id]) personenMap[p.antrag_id] = [];
personenMap[p.antrag_id].push({ id: p.id, name: p.name, rolle: p.rolle });
});
antraege.forEach(a => {
a.ausschuesse = ausschuesseMap[a.id] || [];
a.personen = personenMap[a.id] || [];
});
res.json(antraege);
});
// Einzelner Antrag
app.get('/api/antraege/:id', (req, res) => {
const antrag = db.prepare(`
SELECT
a.*,
p.name as prioritaet,
s.name as status,
b.name as bereich
FROM antraege a
LEFT JOIN prioritaeten p ON a.prioritaet_id = p.id
LEFT JOIN status_typen s ON a.status_id = s.id
LEFT JOIN bereiche b ON a.bereich_id = b.id
WHERE a.id = ?
`).get(req.params.id);
if (!antrag) return res.status(404).json({ error: 'Nicht gefunden' });
antrag.ausschuesse = db.prepare(`
SELECT aus.id, aus.name FROM antrag_ausschuesse aa
JOIN ausschuesse aus ON aa.ausschuss_id = aus.id
WHERE aa.antrag_id = ?
`).all(req.params.id);
antrag.personen = db.prepare(`
SELECT p.id, p.name FROM antrag_personen ap
JOIN personen p ON ap.person_id = p.id
WHERE ap.antrag_id = ?
`).all(req.params.id);
res.json(antrag);
});
// Neuer Antrag
app.post('/api/antraege', (req, res) => {
const { titel, kurzbeschreibung, prioritaet_id, status_id, bereich_id, ausschuesse, personen } = req.body;
const result = db.prepare(`
INSERT INTO antraege (titel, kurzbeschreibung, prioritaet_id, status_id, bereich_id)
VALUES (?, ?, ?, ?, ?)
`).run(titel, kurzbeschreibung, prioritaet_id || 2, status_id || 1, bereich_id || 1);
const antragId = result.lastInsertRowid;
if (ausschuesse?.length) {
const insertAus = db.prepare('INSERT OR IGNORE INTO antrag_ausschuesse (antrag_id, ausschuss_id) VALUES (?, ?)');
ausschuesse.forEach(id => insertAus.run(antragId, id));
}
if (personen?.length) {
const insertPers = db.prepare('INSERT OR IGNORE INTO antrag_personen (antrag_id, person_id) VALUES (?, ?)');
personen.forEach(id => insertPers.run(antragId, id));
}
res.status(201).json({ id: antragId });
});
// Antrag aktualisieren
app.put('/api/antraege/:id', (req, res) => {
const { titel, kurzbeschreibung, prioritaet_id, status_id, bereich_id,
dossier, antragstext, notizen, ausschuesse, personen, position_x, position_y } = req.body;
db.prepare(`
UPDATE antraege SET
titel = COALESCE(?, titel),
kurzbeschreibung = COALESCE(?, kurzbeschreibung),
prioritaet_id = COALESCE(?, prioritaet_id),
status_id = COALESCE(?, status_id),
bereich_id = COALESCE(?, bereich_id),
dossier = COALESCE(?, dossier),
antragstext = COALESCE(?, antragstext),
notizen = COALESCE(?, notizen),
position_x = COALESCE(?, position_x),
position_y = COALESCE(?, position_y),
aktualisiert_am = date('now')
WHERE id = ?
`).run(titel, kurzbeschreibung, prioritaet_id, status_id, bereich_id,
dossier, antragstext, notizen, position_x, position_y, req.params.id);
// Ausschüsse aktualisieren
if (ausschuesse !== undefined) {
db.prepare('DELETE FROM antrag_ausschuesse WHERE antrag_id = ?').run(req.params.id);
const insertAus = db.prepare('INSERT INTO antrag_ausschuesse (antrag_id, ausschuss_id) VALUES (?, ?)');
ausschuesse.forEach(id => insertAus.run(req.params.id, id));
}
// Personen aktualisieren
if (personen !== undefined) {
db.prepare('DELETE FROM antrag_personen WHERE antrag_id = ?').run(req.params.id);
const insertPers = db.prepare('INSERT INTO antrag_personen (antrag_id, person_id) VALUES (?, ?)');
personen.forEach(id => insertPers.run(req.params.id, id));
}
res.json({ success: true });
});
// Antrag löschen
app.delete('/api/antraege/:id', (req, res) => {
db.prepare('DELETE FROM antraege WHERE id = ?').run(req.params.id);
res.json({ success: true });
});
// ============================================
// GRAPH-DATEN
// ============================================
app.get('/api/graph', (req, res) => {
const nodes = db.prepare(`
SELECT
a.id, a.titel, a.kurzbeschreibung, a.position_x, a.position_y,
p.name as prioritaet, p.farbe as prioritaet_farbe, p.reihenfolge as prio_order,
b.name as bereich, b.farbe as bereich_farbe, b.icon as bereich_icon,
CASE WHEN length(a.dossier) > 100 THEN 1 ELSE 0 END as hat_dossier
FROM antraege a
LEFT JOIN prioritaeten p ON a.prioritaet_id = p.id
LEFT JOIN bereiche b ON a.bereich_id = b.id
`).all();
// Kanten basierend auf gemeinsamen Ausschüssen/Personen
const edgesByAusschuss = db.prepare(`
SELECT DISTINCT aa1.antrag_id as source, aa2.antrag_id as target, 'ausschuss' as typ
FROM antrag_ausschuesse aa1
JOIN antrag_ausschuesse aa2 ON aa1.ausschuss_id = aa2.ausschuss_id
WHERE aa1.antrag_id < aa2.antrag_id
`).all();
const edgesByPerson = db.prepare(`
SELECT DISTINCT ap1.antrag_id as source, ap2.antrag_id as target, 'person' as typ
FROM antrag_personen ap1
JOIN antrag_personen ap2 ON ap1.person_id = ap2.person_id
WHERE ap1.antrag_id < ap2.antrag_id
`).all();
const manualEdges = db.prepare(`
SELECT von_antrag_id as source, nach_antrag_id as target, typ, gewicht
FROM verbindungen
`).all();
res.json({
nodes,
edges: {
ausschuss: edgesByAusschuss,
person: edgesByPerson,
manuell: manualEdges
}
});
});
// Positionen speichern
app.put('/api/graph/positions', (req, res) => {
const { positions } = req.body; // { id: { x, y }, ... }
const update = db.prepare('UPDATE antraege SET position_x = ?, position_y = ? WHERE id = ?');
db.exec('BEGIN');
for (const [id, pos] of Object.entries(positions)) {
update.run(pos.x, pos.y, id);
}
db.exec('COMMIT');
res.json({ success: true });
});
// ============================================
// VERBINDUNGEN
// ============================================
app.get('/api/verbindungen', (req, res) => {
const verbindungen = db.prepare('SELECT * FROM verbindungen').all();
res.json(verbindungen);
});
app.post('/api/verbindungen', (req, res) => {
const { von_antrag_id, nach_antrag_id, typ, gewicht, notiz } = req.body;
const result = db.prepare(`
INSERT INTO verbindungen (von_antrag_id, nach_antrag_id, typ, gewicht, notiz)
VALUES (?, ?, ?, ?, ?)
`).run(von_antrag_id, nach_antrag_id, typ || 'manuell', gewicht || 1, notiz);
res.status(201).json({ id: result.lastInsertRowid });
});
app.delete('/api/verbindungen/:id', (req, res) => {
db.prepare('DELETE FROM verbindungen WHERE id = ?').run(req.params.id);
res.json({ success: true });
});
// ============================================
// STAMMDATEN
// ============================================
app.get('/api/stammdaten', (req, res) => {
res.json({
prioritaeten: db.prepare('SELECT * FROM prioritaeten ORDER BY reihenfolge').all(),
status_typen: db.prepare('SELECT * FROM status_typen ORDER BY reihenfolge').all(),
bereiche: db.prepare('SELECT * FROM bereiche').all(),
ausschuesse: db.prepare('SELECT * FROM ausschuesse ORDER BY name').all(),
personen: db.prepare('SELECT * FROM personen ORDER BY name').all()
});
});
// ============================================
// START
// ============================================
app.listen(PORT, () => {
console.log(`🚀 API läuft auf http://localhost:${PORT}`);
});