aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/routes/decks.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-07 17:37:08 +0900
committernsfisis <nsfisis@gmail.com>2025-12-07 17:37:08 +0900
commit797ef2fcfaa7ac63355c13809a644401a76250bc (patch)
tree70814083131572b31b72fdbaeea74b2d7aa60d91 /src/server/routes/decks.ts
parent943674471d062ea4494727ce308c8c429afd6f98 (diff)
downloadkioku-797ef2fcfaa7ac63355c13809a644401a76250bc.tar.gz
kioku-797ef2fcfaa7ac63355c13809a644401a76250bc.tar.zst
kioku-797ef2fcfaa7ac63355c13809a644401a76250bc.zip
feat(server): add Deck CRUD endpoints with tests
Implement complete Deck management API: - GET /api/decks - List user's decks - POST /api/decks - Create new deck - GET /api/decks/:id - Get deck by ID - PUT /api/decks/:id - Update deck - DELETE /api/decks/:id - Soft delete deck All endpoints require authentication and scope data to the authenticated user. Includes 22 unit tests covering success and error cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/server/routes/decks.ts')
-rw-r--r--src/server/routes/decks.ts82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts
new file mode 100644
index 0000000..4604ea9
--- /dev/null
+++ b/src/server/routes/decks.ts
@@ -0,0 +1,82 @@
+import { zValidator } from "@hono/zod-validator";
+import { Hono } from "hono";
+import { z } from "zod";
+import { authMiddleware, Errors, getAuthUser } from "../middleware/index.js";
+import { type DeckRepository, deckRepository } from "../repositories/index.js";
+import { createDeckSchema, updateDeckSchema } from "../schemas/index.js";
+
+export interface DeckDependencies {
+ deckRepo: DeckRepository;
+}
+
+const deckIdParamSchema = z.object({
+ id: z.string().uuid(),
+});
+
+export function createDecksRouter(deps: DeckDependencies) {
+ const { deckRepo } = deps;
+
+ return new Hono()
+ .use("*", authMiddleware)
+ .get("/", async (c) => {
+ const user = getAuthUser(c);
+ const decks = await deckRepo.findByUserId(user.id);
+ return c.json({ decks }, 200);
+ })
+ .post("/", zValidator("json", createDeckSchema), async (c) => {
+ const user = getAuthUser(c);
+ const data = c.req.valid("json");
+
+ const deck = await deckRepo.create({
+ userId: user.id,
+ name: data.name,
+ description: data.description,
+ newCardsPerDay: data.newCardsPerDay,
+ });
+
+ return c.json({ deck }, 201);
+ })
+ .get("/:id", zValidator("param", deckIdParamSchema), async (c) => {
+ const user = getAuthUser(c);
+ const { id } = c.req.valid("param");
+
+ const deck = await deckRepo.findById(id, user.id);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ return c.json({ deck }, 200);
+ })
+ .put(
+ "/:id",
+ zValidator("param", deckIdParamSchema),
+ zValidator("json", updateDeckSchema),
+ async (c) => {
+ const user = getAuthUser(c);
+ const { id } = c.req.valid("param");
+ const data = c.req.valid("json");
+
+ const deck = await deckRepo.update(id, user.id, data);
+ if (!deck) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ return c.json({ deck }, 200);
+ },
+ )
+ .delete("/:id", zValidator("param", deckIdParamSchema), async (c) => {
+ const user = getAuthUser(c);
+ const { id } = c.req.valid("param");
+
+ const deleted = await deckRepo.softDelete(id, user.id);
+ if (!deleted) {
+ throw Errors.notFound("Deck not found", "DECK_NOT_FOUND");
+ }
+
+ return c.json({ success: true }, 200);
+ });
+}
+
+export const decks = createDecksRouter({
+ deckRepo: deckRepository,
+});