import { requireAuth, requireCap } from '../auth.js' import { pool } from '../db.js' import { logActivity } from '../lib/activity.js' function slugify(name) { return String(name) .toLowerCase() .trim() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') || 'calendar' } async function uniqueSlug(base) { let slug = base let n = 2 while (true) { const { rows } = await pool.query('SELECT 1 FROM calendars WHERE slug = $1', [slug]) if (!rows.length) return slug slug = `${base}-${n}` n++ } } export async function calendarRoutes(app) { app.addHook('preHandler', requireAuth) // GET /api/calendars app.get('/api/calendars', { preHandler: requireCap('view') }, async () => { const { rows } = await pool.query( `SELECT * FROM calendars WHERE deleted_at IS NULL ORDER BY is_system ASC, name ASC` ) return rows }) // POST /api/calendars app.post('/api/calendars', { preHandler: requireCap('manage_calendars') }, async (req, reply) => { const { name, color } = req.body || {} if (!name || !color) return reply.status(400).send({ error: 'name and color required' }) const slug = await uniqueSlug(slugify(name)) const { rows } = await pool.query( `INSERT INTO calendars (slug, name, color, is_system, created_by) VALUES ($1, $2, $3, FALSE, $4) RETURNING *`, [slug, name, color, req.user.email] ) const calendar = rows[0] await logActivity(pool, { actorEmail: req.user.email, actorName: req.user.name, action: 'created', entityType: 'calendar', entityId: calendar.id, calendarId: calendar.id, summary: `Created calendar "${calendar.name}"`, }) return reply.status(201).send(calendar) }) // PATCH /api/calendars/:id app.patch('/api/calendars/:id', { preHandler: requireCap('manage_calendars') }, async (req, reply) => { const id = parseInt(req.params.id) const { rows: existing } = await pool.query('SELECT * FROM calendars WHERE id = $1 AND deleted_at IS NULL', [id]) if (!existing.length) return reply.status(404).send({ error: 'Calendar not found' }) if (existing[0].is_system) return reply.status(400).send({ error: 'Cannot edit a system calendar' }) const b = req.body || {} const name = b.name ?? existing[0].name const color = b.color ?? existing[0].color const { rows } = await pool.query( `UPDATE calendars SET name = $1, color = $2 WHERE id = $3 RETURNING *`, [name, color, id] ) await logActivity(pool, { actorEmail: req.user.email, actorName: req.user.name, action: 'updated', entityType: 'calendar', entityId: id, calendarId: id, summary: `Updated calendar "${rows[0].name}"`, }) return rows[0] }) // DELETE /api/calendars/:id — soft delete app.delete('/api/calendars/:id', { preHandler: requireCap('manage_calendars') }, async (req, reply) => { const id = parseInt(req.params.id) const { rows: existing } = await pool.query('SELECT * FROM calendars WHERE id = $1 AND deleted_at IS NULL', [id]) if (!existing.length) return reply.status(404).send({ error: 'Calendar not found' }) if (existing[0].is_system) return reply.status(400).send({ error: 'Cannot delete a system calendar' }) await pool.query('UPDATE calendars SET deleted_at = NOW() WHERE id = $1', [id]) await logActivity(pool, { actorEmail: req.user.email, actorName: req.user.name, action: 'deleted', entityType: 'calendar', entityId: id, calendarId: id, summary: `Deleted calendar "${existing[0].name}"`, }) return { ok: true } }) }