import { requireAuth, requireCap } from '../auth.js' import { pool } from '../db.js' import { logActivity } from '../lib/activity.js' import { mkdir, unlink, writeFile } from 'fs/promises' import { randomUUID } from 'crypto' import { join, extname } from 'path' export async function attachmentRoutes(app, opts) { const UPLOADS_DIR = opts.uploadsDir app.addHook('preHandler', requireAuth) // POST /api/events/:id/attachments — multipart, field "file" app.post('/api/events/:id/attachments', { preHandler: requireCap('edit') }, async (req, reply) => { const eventId = parseInt(req.params.id) const { rows } = await pool.query('SELECT id, calendar_id FROM events WHERE id = $1 AND deleted_at IS NULL', [eventId]) if (!rows.length) return reply.status(404).send({ error: 'Event not found' }) const event = rows[0] const part = await req.file() if (!part) return reply.status(400).send({ error: 'No file uploaded' }) const chunks = [] for await (const chunk of part.file) chunks.push(chunk) const buffer = Buffer.concat(chunks) const ext = extname(part.filename || '') || '' const storedName = `${randomUUID()}${ext}` const storedPath = `events/${eventId}/${storedName}` const dir = join(UPLOADS_DIR, 'events', String(eventId)) await mkdir(dir, { recursive: true }) await writeFile(join(dir, storedName), buffer) const { rows: ins } = await pool.query( `INSERT INTO event_attachments (event_id, filename, stored_path, mime_type, size_bytes, uploaded_by) VALUES ($1,$2,$3,$4,$5,$6) RETURNING *`, [eventId, part.filename || storedName, storedPath, part.mimetype || null, buffer.length, req.user.email] ) const attachment = ins[0] await logActivity(pool, { actorEmail: req.user.email, actorName: req.user.name, action: 'created', entityType: 'attachment', entityId: attachment.id, calendarId: event.calendar_id, summary: `Added attachment "${attachment.filename}"`, }) return reply.status(201).send({ ...attachment, url: `/api/uploads/${attachment.stored_path}` }) }) // DELETE /api/attachments/:id app.delete('/api/attachments/:id', { preHandler: requireCap('edit') }, async (req, reply) => { const { rows } = await pool.query( `SELECT a.*, e.calendar_id FROM event_attachments a JOIN events e ON e.id = a.event_id WHERE a.id = $1`, [req.params.id] ) if (!rows.length) return reply.status(404).send({ error: 'Attachment not found' }) const attachment = rows[0] await unlink(join(UPLOADS_DIR, attachment.stored_path)).catch(() => {}) await pool.query('DELETE FROM event_attachments WHERE id = $1', [attachment.id]) await logActivity(pool, { actorEmail: req.user.email, actorName: req.user.name, action: 'deleted', entityType: 'attachment', entityId: attachment.id, calendarId: attachment.calendar_id, summary: `Deleted attachment "${attachment.filename}"`, }) return { ok: true } }) }