import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox' import * as fs from 'node:fs' import * as path from 'node:path' import { Type } from 'typebox' import { GetAttachmentParamsSchema } from '../plugins/schemas/attachment.ts' import { TypeboxRef } from '../utils/typebox-ref.ts' const plugin: FastifyPluginAsyncTypebox = async (fastify) => { const uploadDir = path.join(process.cwd(), 'uploads') if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }) } fastify.post( '/attachment/upload', { schema: { summary: 'Upload attachment', tags: ['Attachment'], operationId: 'attachment.upload', description: 'Pass file to multipart/form-data', consumes: ['multipart/form-data'], response: { 200: Type.String({ format: 'uuid', description: 'Attachment UUID' }), }, }, }, async (req, reply) => { const data = await req.file() if (!data) { return reply.notAcceptable() } const meta = await fastify.prisma.attachment.create({ data: { name: data.filename, mimetype: data.mimetype, size: 0, }, }) if (!meta) { return reply.notAcceptable() } const filePath = path.join(process.cwd(), 'uploads', meta.id) await new Promise((resolve, reject) => { const writeStream = fs.createWriteStream(filePath) data.file.pipe(writeStream) data.file.on('end', resolve) data.file.on('error', reject) }) return meta.id }, ) fastify.get( '/attachment/:id', { schema: { summary: 'Get attachment', tags: ['Attachment'], operationId: 'attachment.get', params: TypeboxRef(GetAttachmentParamsSchema), response: { 200: Type.Any({ description: 'Attachment content' }), }, }, config: { skipAuth: true, }, }, async (req, reply) => { const meta = await fastify.prisma.attachment.findFirst({ where: { id: req.params.id }, }) if (!meta) { return reply.notFound('Attachment not found') } const filePath = path.join(process.cwd(), 'uploads', meta.id) reply.type(meta.mimetype) reply.header('Cache-Control', 'public, max-age=31536000') reply.header('Content-Disposition', `inline; filename="${meta.name}"`) return fs.createReadStream(filePath) }, ) } export default plugin