import { error } from '@sveltejs/kit'; import type { RequestHandler } from './$types.js'; import { withTenant } from '$lib/server/rls.js'; import { requireAuth } from '$lib/server/guards.js'; import { canViewBoard } from '$lib/server/permissions.js'; import { readFile, unlink } from 'fs/promises'; import { json } from '@sveltejs/kit'; import { broadcast } from '$lib/server/realtime.js'; import { logActivity } from '$lib/server/activity.js'; export const GET: RequestHandler = async ({ params, locals }) => { const tenant = locals.tenant; if (!tenant) throw error(400, 'Unknown tenant'); const attachment = await withTenant(tenant.id, async (tx) => { const att = await tx.attachment.findFirst({ where: { id: params.attachmentId }, include: { card: { include: { column: { include: { board: { select: { tenantId: true, visibility: true, members: true } } } } } } } }); if (!att) throw error(404, 'Attachment not found'); if (att.card.column.board.tenantId !== tenant.id) throw error(404, 'Attachment not found'); if (!canViewBoard(locals.user, att.card.column.board.visibility, att.card.column.board.members)) { if (!locals.user) throw error(401, 'Not authenticated'); throw error(403, 'Access denied'); } return att; }); const buffer = await readFile(attachment.filepath); return new Response(buffer, { headers: { 'Content-Type': attachment.mimetype, 'Content-Disposition': `attachment; filename="${attachment.filename}"`, 'Content-Length': String(attachment.size) } }); }; export const DELETE: RequestHandler = async ({ params, request, locals }) => { const tenant = locals.tenant; if (!tenant) throw error(400, 'Unknown tenant'); requireAuth(locals.user); const deleted = await withTenant(tenant.id, async (tx) => { const att = await tx.attachment.findFirst({ where: { id: params.attachmentId }, include: { card: { include: { column: { include: { board: { select: { id: true, tenantId: true, members: true } } } } } } } }); if (!att) throw error(404, 'Attachment not found'); if (att.card.column.board.tenantId !== tenant.id) throw error(404, 'Attachment not found'); // Check board editor permission const { canEditBoard } = await import('$lib/server/permissions.js'); if (!canEditBoard(locals.user, att.card.column.board.members)) { throw error(403, 'Access denied'); } await logActivity(tx, { boardId: att.card.column.board.id, userId: locals.user!.id, action: 'attachment.deleted', cardId: att.cardId, metadata: { filename: att.filename } }); await tx.attachment.delete({ where: { id: params.attachmentId } }); try { await unlink(att.filepath); } catch { // File may already be gone } return { boardId: att.card.column.board.id, cardId: att.cardId, columnId: att.card.columnId }; }); const socketId = request.headers.get('X-Socket-ID'); broadcast(deleted.boardId, 'card:updated', { cardId: deleted.cardId, columnId: deleted.columnId, socketId }); broadcast(deleted.boardId, 'activity:new', { boardId: deleted.boardId, cardId: deleted.cardId, socketId }); return json({ ok: true }); };