import type { FastifyPluginAsync } from 'fastify'; import { extname } from 'path'; import { nanoid } from 'nanoid'; import type Database from 'better-sqlite3'; import type { Config } from '../../../config.ts'; import type { Logger } from '../../../middleware/logging.ts'; import type { JwtPayload } from '../../../types.ts'; import { createFile, getFilesByUserId, getFileById, deleteFile } from '../../../db/files.ts'; import { saveFile, deleteStoredFile } from '../../../services/storage.ts'; import { requireAuth } from '../../../middleware/auth.ts'; interface Deps { db: Database.Database; config: Config; logger: Logger; } export const filesApiRoutes: FastifyPluginAsync<{ deps: Deps }> = async (app, { deps }) => { const { db, config } = deps; app.get('/', { preHandler: requireAuth }, async (request, reply) => { const { sub: userId } = request.user as JwtPayload; const files = getFilesByUserId(db, userId); reply.send({ files }); }); app.post('/', { preHandler: requireAuth }, async (request, reply) => { const { sub: userId } = request.user as JwtPayload; const data = await request.file(); if (!data) { return reply.status(400).send({ error: 'No file uploaded' }); } const fileBuffer = await data.toBuffer(); const id = nanoid(); const ext = extname(data.filename); const storedName = `${id}${ext}`; await saveFile(config.uploadDir, storedName, fileBuffer); const file = createFile(db, { id, userId, originalName: data.filename, mimeType: data.mimetype, size: fileBuffer.length, storedName, }); reply.status(201).send({ file, url: `${config.baseUrl}/f/${id}` }); }); app.delete('/:id', { preHandler: requireAuth }, async (request, reply) => { const { sub: userId } = request.user as JwtPayload; const { id } = request.params as { id: string }; const file = getFileById(db, id); if (!file) { return reply.status(404).send({ error: 'File not found' }); } const deleted = deleteFile(db, id, userId); if (!deleted) { return reply.status(403).send({ error: 'Forbidden' }); } await deleteStoredFile(config.uploadDir, file.stored_name); reply.send({ ok: true }); }); };