feat(01-02): add Hono API routes with validation, image upload, and integration tests

- Item routes: GET, POST, PUT, DELETE with Zod validation and image cleanup
- Category routes: GET, POST, PUT, DELETE with Uncategorized protection
- Totals route: per-category and global aggregates
- Image upload: multipart file handling with type/size validation
- Routes use DI via Hono context variables for testability
- Integration tests: 10 tests covering all endpoints and edge cases
- All 30 tests passing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 22:40:49 +01:00
parent 22757a8aef
commit 029adf4dca
7 changed files with 412 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
import { Hono } from "hono";
import { randomUUID } from "node:crypto";
import { join } from "node:path";
import { mkdir } from "node:fs/promises";
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"];
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const app = new Hono();
app.post("/", async (c) => {
const body = await c.req.parseBody();
const file = body["image"];
if (!file || typeof file === "string") {
return c.json({ error: "No image file provided" }, 400);
}
// Validate file type
if (!ALLOWED_TYPES.includes(file.type)) {
return c.json(
{ error: "Invalid file type. Accepted: jpeg, png, webp" },
400,
);
}
// Validate file size
if (file.size > MAX_SIZE) {
return c.json({ error: "File too large. Maximum size is 5MB" }, 400);
}
// Generate unique filename
const ext = file.type.split("/")[1] === "jpeg" ? "jpg" : file.type.split("/")[1];
const filename = `${Date.now()}-${randomUUID()}.${ext}`;
// Ensure uploads directory exists
await mkdir("uploads", { recursive: true });
// Write file
const buffer = await file.arrayBuffer();
await Bun.write(join("uploads", filename), buffer);
return c.json({ filename }, 201);
});
export { app as imageRoutes };