import { Router } from 'express' import { config } from '../../config.js' import { getDocument } from '../../services/appwriteAdmin.js' import { withLocks } from '../../services/keyedLock.js' import { adminActivate, assertOwnProject, disable, getFeatureDoc, listCustomerFeatures } from '../../services/siteFeatures.js' /** * Funktionen (News-Pinnwand) je Kundenprojekt im Ticketsystem (Kunden-Reiter "Funktionen"). * Eingehaengt in routes/team/index.js unter /api/team/funktionen, dort hinter requireStaffJwt. */ export const funktionenAdminRouter = Router() const NOTE_MAX = 300 const adminName = (req) => req.session.adminName || req.session.name || 'Admin' function sendError(res, err) { const status = err.status >= 400 && err.status < 600 ? err.status : 500 if (status >= 500) console.error('[funktionen-admin]', err.message) return res.status(status).json({ error: err.message || 'Interner Serverfehler' }) } /** Neuester Eintrag zuerst; aeltere Eintraege fallen vorn heraus, sobald 300 Zeichen voll sind. */ export function composeAdminNote(previous, { by, action, note }, now = new Date()) { const entry = `${now.toISOString()} ${by}: ${action}${note ? ` – ${note}` : ''}`.slice(0, NOTE_MAX) const older = String(previous || '') return older ? `${entry} | ${older}`.slice(0, NOTE_MAX) : entry } async function ticketExists(ticketId) { if (!ticketId) return false try { await getDocument(config.collections.workorders, ticketId) return true } catch (err) { if (err.status === 404) return false throw err } } async function adminView(customerId) { const projects = await listCustomerFeatures(customerId) for (const p of projects) { const news = p.features.news const doc = await getFeatureDoc(customerId, p.projectId, 'news') news.adminNote = doc?.adminNote || '' news.ticketMissing = news.state === 'setup' && !(doc?.ticketId && (await ticketExists(doc.ticketId))) } return projects } funktionenAdminRouter.get('/:customerId', async (req, res) => { try { res.json({ projects: await adminView(req.params.customerId) }) } catch (err) { sendError(res, err) } }) funktionenAdminRouter.post('/:customerId/news', async (req, res) => { try { const { customerId } = req.params const { projectId, action } = req.body || {} const note = typeof req.body?.note === 'string' ? req.body.note.trim().slice(0, 120) : '' if (action !== 'activate' && action !== 'disable') { return res.status(400).json({ error: 'Unbekannte Aktion' }) } if (typeof projectId !== 'string' || !projectId) { return res.status(400).json({ error: 'Projekt fehlt' }) } await assertOwnProject(customerId, projectId) const by = adminName(req) await withLocks([`feature-admin:${projectId}`], async () => { const doc = await getFeatureDoc(customerId, projectId, 'news') const adminNote = composeAdminNote(doc?.adminNote, { by, action, note }) if (action === 'activate') { await adminActivate(customerId, projectId, 'news', { by, adminNote }) } else { if (!doc) throw Object.assign(new Error('Für dieses Projekt gibt es noch keine News-Pinnwand'), { status: 409 }) await disable(doc.$id, { by, adminNote }) } }) res.json({ projects: (await adminView(customerId)).filter((p) => p.projectId === projectId) }) } catch (err) { sendError(res, err) } })