92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import { FastifyInstance } from "fastify";
|
|
import { z } from "zod";
|
|
import { prisma } from "../../lib/prisma.js";
|
|
|
|
const createBody = z.object({
|
|
title: z.string().min(1).max(200).trim(),
|
|
body: z.string().min(1).max(10000).trim(),
|
|
boardId: z.string().optional().nullable(),
|
|
publishedAt: z.coerce.date().optional(),
|
|
});
|
|
|
|
const updateBody = z.object({
|
|
title: z.string().min(1).max(200).trim().optional(),
|
|
body: z.string().min(1).max(10000).trim().optional(),
|
|
boardId: z.string().optional().nullable(),
|
|
publishedAt: z.coerce.date().optional(),
|
|
});
|
|
|
|
export default async function adminChangelogRoutes(app: FastifyInstance) {
|
|
app.get(
|
|
"/admin/changelog",
|
|
{ preHandler: [app.requireAdmin, app.requireRole("SUPER_ADMIN", "ADMIN")], config: { rateLimit: { max: 60, timeWindow: "1 minute" } } },
|
|
async (_req, reply) => {
|
|
const entries = await prisma.changelogEntry.findMany({
|
|
include: { board: { select: { id: true, slug: true, name: true } } },
|
|
orderBy: { publishedAt: "desc" },
|
|
take: 200,
|
|
});
|
|
reply.send({ entries });
|
|
}
|
|
);
|
|
|
|
app.post<{ Body: z.infer<typeof createBody> }>(
|
|
"/admin/changelog",
|
|
{ preHandler: [app.requireAdmin, app.requireRole("SUPER_ADMIN", "ADMIN")], config: { rateLimit: { max: 20, timeWindow: "1 minute" } } },
|
|
async (req, reply) => {
|
|
const body = createBody.parse(req.body);
|
|
const entry = await prisma.changelogEntry.create({
|
|
data: {
|
|
title: body.title,
|
|
body: body.body,
|
|
boardId: body.boardId || null,
|
|
publishedAt: body.publishedAt ?? new Date(),
|
|
},
|
|
});
|
|
req.log.info({ adminId: req.adminId, entryId: entry.id }, "changelog entry created");
|
|
reply.status(201).send(entry);
|
|
}
|
|
);
|
|
|
|
app.put<{ Params: { id: string }; Body: z.infer<typeof updateBody> }>(
|
|
"/admin/changelog/:id",
|
|
{ preHandler: [app.requireAdmin, app.requireRole("SUPER_ADMIN", "ADMIN")], config: { rateLimit: { max: 20, timeWindow: "1 minute" } } },
|
|
async (req, reply) => {
|
|
const entry = await prisma.changelogEntry.findUnique({ where: { id: req.params.id } });
|
|
if (!entry) {
|
|
reply.status(404).send({ error: "Entry not found" });
|
|
return;
|
|
}
|
|
|
|
const body = updateBody.parse(req.body);
|
|
const updated = await prisma.changelogEntry.update({
|
|
where: { id: entry.id },
|
|
data: {
|
|
...(body.title !== undefined && { title: body.title }),
|
|
...(body.body !== undefined && { body: body.body }),
|
|
...(body.boardId !== undefined && { boardId: body.boardId || null }),
|
|
...(body.publishedAt !== undefined && { publishedAt: body.publishedAt }),
|
|
},
|
|
});
|
|
req.log.info({ adminId: req.adminId, entryId: entry.id }, "changelog entry updated");
|
|
reply.send(updated);
|
|
}
|
|
);
|
|
|
|
app.delete<{ Params: { id: string } }>(
|
|
"/admin/changelog/:id",
|
|
{ preHandler: [app.requireAdmin, app.requireRole("SUPER_ADMIN", "ADMIN")], config: { rateLimit: { max: 20, timeWindow: "1 minute" } } },
|
|
async (req, reply) => {
|
|
const entry = await prisma.changelogEntry.findUnique({ where: { id: req.params.id } });
|
|
if (!entry) {
|
|
reply.status(404).send({ error: "Entry not found" });
|
|
return;
|
|
}
|
|
|
|
await prisma.changelogEntry.delete({ where: { id: entry.id } });
|
|
req.log.info({ adminId: req.adminId, entryId: entry.id }, "changelog entry deleted");
|
|
reply.status(204).send();
|
|
}
|
|
);
|
|
}
|