added api logic, vitest, minimal testing ui
This commit is contained in:
85
api/src/routes/variable-categories.ts
Normal file
85
api/src/routes/variable-categories.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
|
||||
import { FastifyPluginAsync } from "fastify";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "../prisma.js";
|
||||
|
||||
const NewCat = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
percent: z.number().int().min(0).max(100),
|
||||
isSavings: z.boolean().default(false),
|
||||
priority: z.number().int().min(0).max(10_000),
|
||||
});
|
||||
const PatchCat = NewCat.partial();
|
||||
const IdParam = z.object({ id: z.string().min(1) });
|
||||
|
||||
async function assertPercentTotal100(tx: Prisma.TransactionClient, userId: string) {
|
||||
const g = await tx.variableCategory.groupBy({
|
||||
by: ["userId"],
|
||||
where: { userId },
|
||||
_sum: { percent: true },
|
||||
});
|
||||
const sum = g[0]?._sum.percent ?? 0;
|
||||
if (sum !== 100) {
|
||||
const err: any = new Error(`Percents must sum to 100 (got ${sum}).`);
|
||||
err.statusCode = 400;
|
||||
err.code = "PERCENT_TOTAL_NOT_100";
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin: FastifyPluginAsync = async (app) => {
|
||||
// CREATE
|
||||
app.post("/api/variable-categories", async (req, reply) => {
|
||||
const userId = req.userId;
|
||||
const body = NewCat.safeParse(req.body);
|
||||
if (!body.success) return reply.status(400).send({ error: "INVALID_BODY", details: body.error.flatten() });
|
||||
|
||||
const created = await prisma.$transaction(async (tx) => {
|
||||
const rec = await tx.variableCategory.create({
|
||||
data: { ...body.data, userId },
|
||||
select: { id: true },
|
||||
});
|
||||
await assertPercentTotal100(tx, userId);
|
||||
return rec;
|
||||
});
|
||||
|
||||
return reply.status(201).send(created);
|
||||
});
|
||||
|
||||
// UPDATE
|
||||
app.patch("/api/variable-categories/:id", async (req, reply) => {
|
||||
const userId = req.userId;
|
||||
const pid = IdParam.safeParse(req.params);
|
||||
const patch = PatchCat.safeParse(req.body);
|
||||
if (!pid.success) return reply.status(400).send({ error: "INVALID_ID", details: pid.error.flatten() });
|
||||
if (!patch.success) return reply.status(400).send({ error: "INVALID_BODY", details: patch.error.flatten() });
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const exists = await tx.variableCategory.findFirst({ where: { id: pid.data.id, userId } });
|
||||
if (!exists) return reply.status(404).send({ error: "NOT_FOUND" });
|
||||
await tx.variableCategory.update({ where: { id: pid.data.id }, data: patch.data });
|
||||
await assertPercentTotal100(tx, userId);
|
||||
});
|
||||
|
||||
return reply.send({ ok: true });
|
||||
});
|
||||
|
||||
// DELETE
|
||||
app.delete("/api/variable-categories/:id", async (req, reply) => {
|
||||
const userId = req.userId;
|
||||
const pid = IdParam.safeParse(req.params);
|
||||
if (!pid.success) return reply.status(400).send({ error: "INVALID_ID", details: pid.error.flatten() });
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
const exists = await tx.variableCategory.findFirst({ where: { id: pid.data.id, userId } });
|
||||
if (!exists) return reply.status(404).send({ error: "NOT_FOUND" });
|
||||
await tx.variableCategory.delete({ where: { id: pid.data.id } });
|
||||
await assertPercentTotal100(tx, userId);
|
||||
});
|
||||
|
||||
return reply.send({ ok: true });
|
||||
});
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
Reference in New Issue
Block a user