From 797ef2fcfaa7ac63355c13809a644401a76250bc Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 7 Dec 2025 17:37:08 +0900 Subject: feat(server): add Deck CRUD endpoints with tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/server/repositories/deck.ts | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/server/repositories/deck.ts (limited to 'src/server/repositories/deck.ts') diff --git a/src/server/repositories/deck.ts b/src/server/repositories/deck.ts new file mode 100644 index 0000000..77985a7 --- /dev/null +++ b/src/server/repositories/deck.ts @@ -0,0 +1,95 @@ +import { and, eq, isNull, sql } from "drizzle-orm"; +import { db } from "../db/index.js"; +import { decks } from "../db/schema.js"; +import type { Deck, DeckRepository } from "./types.js"; + +export const deckRepository: DeckRepository = { + async findByUserId(userId: string): Promise { + const result = await db + .select() + .from(decks) + .where(and(eq(decks.userId, userId), isNull(decks.deletedAt))); + return result; + }, + + async findById(id: string, userId: string): Promise { + const result = await db + .select() + .from(decks) + .where( + and( + eq(decks.id, id), + eq(decks.userId, userId), + isNull(decks.deletedAt), + ), + ); + return result[0]; + }, + + async create(data: { + userId: string; + name: string; + description?: string | null; + newCardsPerDay?: number; + }): Promise { + const [deck] = await db + .insert(decks) + .values({ + userId: data.userId, + name: data.name, + description: data.description ?? null, + newCardsPerDay: data.newCardsPerDay ?? 20, + }) + .returning(); + if (!deck) { + throw new Error("Failed to create deck"); + } + return deck; + }, + + async update( + id: string, + userId: string, + data: { + name?: string; + description?: string | null; + newCardsPerDay?: number; + }, + ): Promise { + const result = await db + .update(decks) + .set({ + ...data, + updatedAt: new Date(), + syncVersion: sql`${decks.syncVersion} + 1`, + }) + .where( + and( + eq(decks.id, id), + eq(decks.userId, userId), + isNull(decks.deletedAt), + ), + ) + .returning(); + return result[0]; + }, + + async softDelete(id: string, userId: string): Promise { + const result = await db + .update(decks) + .set({ + deletedAt: new Date(), + updatedAt: new Date(), + syncVersion: sql`${decks.syncVersion} + 1`, + }) + .where( + and( + eq(decks.id, id), + eq(decks.userId, userId), + isNull(decks.deletedAt), + ), + ) + .returning({ id: decks.id }); + return result.length > 0; + }, +}; -- cgit v1.2.3-70-g09d2