diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 7e01144..cecbb6d 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -4,6 +4,8 @@ import fastifyCors from "@fastify/cors"; import fastifyMultipart from "@fastify/multipart"; import fastifyStatic from "@fastify/static"; import { createYoga } from "graphql-yoga"; +import { eq } from "drizzle-orm"; +import { files } from "./db/schema/index"; import path from "path"; import { schema } from "./graphql/index"; import { buildContext } from "./graphql/context"; @@ -37,10 +39,12 @@ async function main() { }, }); + // fastify-static provides reply.sendFile(); files are stored as // await fastify.register(fastifyStatic, { root: path.resolve(UPLOAD_DIR), prefix: "/assets/", - decorateReply: false, + serve: false, // disable auto-serving; we use a custom route below + decorateReply: true, }); const yoga = createYoga<{ req: FastifyRequest; reply: FastifyReply; db: typeof db; redis: typeof redis }>({ @@ -63,6 +67,25 @@ async function main() { yoga.handleNodeRequestAndResponse(req, reply, { req, reply, db, redis }), }); + // Serve uploaded files: GET /assets/:id + // Files are stored as // — look up filename in DB + fastify.get("/assets/:id", async (request, reply) => { + const { id } = request.params as { id: string }; + + const result = await db + .select({ filename: files.filename, mime_type: files.mime_type }) + .from(files) + .where(eq(files.id, id)) + .limit(1); + + if (!result[0]) return reply.status(404).send({ error: "File not found" }); + + const { filename, mime_type } = result[0]; + reply.header("Content-Type", mime_type); + reply.header("Cache-Control", "public, max-age=31536000, immutable"); + return reply.sendFile(path.join(id, filename)); + }); + fastify.get("/health", async (_request, reply) => { return reply.send({ status: "ok", timestamp: new Date().toISOString() }); });