import { unlink } from "node:fs/promises"; import path from "node:path"; import express from "express"; import multer from "multer"; import ViteExpress from "vite-express"; import z from "zod"; import { db } from "./db/database.ts"; const baseUploadsFolder = "uploads/"; const upload = multer({ dest: baseUploadsFolder, }); const app = express(); const PostInput = z.object({ file: z.object({ filename: z.string() }), body: z.object({ publication_date: z.coerce .date() .transform((x) => new Date(x).toISOString()), title: z.string().nonempty(), text: z.string().nonempty(), }), }); const PatchInput = z.object({ file: z.object({ filename: z.string().optional() }).optional(), body: z.object({ publication_date: z.coerce .date() .optional() .transform((x) => (x ? new Date(x).toISOString() : x)), title: z.string().nonempty().optional(), text: z.string().nonempty().optional(), }), }); const AnnouncementIdInput = z.object({ announcementId: z.coerce.number().positive(), }); const GetFromGame = z.object({ gameName: z.string().nonempty(), }); app.get("/api/announcements", async (_, res) => { const announcements = await db .selectFrom("announcements") .select(["id", "title", "text", "publication_date", "image"]) .orderBy("announcements.publication_date", "desc") .execute(); res.json(announcements); }); app.get("/api/announcements/from-game", async (req, res) => { const fromGameData = GetFromGame.parse(req.query); console.log(`Request from game ${fromGameData.gameName}`); const announcements = await db .selectFrom("announcements") .select(["id", "title", "text", "publication_date", "image"]) .orderBy("announcements.publication_date", "desc") .where("publication_date", "<=", new Date().toISOString()) .limit(3) .execute(); res.json(announcements); }); app.get("/api/announcements/:announcementId", async (req, res) => { const { announcementId } = AnnouncementIdInput.parse(req.params); const announcement = await db .selectFrom("announcements") .select(["id", "title", "text", "publication_date", "image"]) .where("id", "=", announcementId) .executeTakeFirstOrThrow(); res.json(announcement); }); app.post("/api/announcements", upload.single("image"), async (req, res) => { const postInput = PostInput.parse(req); const announcement = { title: postInput.body.title, text: postInput.body.text, publication_date: postInput.body.publication_date, image: postInput.file.filename, }; await db.insertInto("announcements").values(announcement).execute(); res.redirect("/"); }); app.post( "/api/announcements/:announcementId", upload.single("image"), async (req, res) => { const patchInput = PatchInput.parse(req); const { announcementId } = AnnouncementIdInput.parse(req.params); await db .updateTable("announcements") .set({ title: patchInput.body.title, text: patchInput.body.text, publication_date: patchInput.body.publication_date, image: patchInput.file?.filename, }) .where("id", "=", announcementId) .execute(); res.redirect("/"); }, ); app.delete("/api/announcements/:announcementId", async (req, res) => { const { announcementId } = AnnouncementIdInput.parse(req.params); const { image } = await db .selectFrom("announcements") .select("image") .where("id", "=", announcementId) .executeTakeFirstOrThrow(); await db .deleteFrom("announcements") .where("id", "=", announcementId) .execute(); await unlink(path.join(baseUploadsFolder, image)); res.status(204).send(); }); app.use("/images", express.static(baseUploadsFolder)); const httpPort = parseInt(process.env.HTTP_PORT ?? "3000", 10); ViteExpress.listen(app, httpPort, () => console.log(`Server is listening on port ${httpPort}...`), );