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 ++++++++++++++++++++++++++++++++++++++++ src/server/repositories/index.ts | 1 + src/server/repositories/types.ts | 33 ++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 src/server/repositories/deck.ts (limited to 'src/server/repositories') 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; + }, +}; diff --git a/src/server/repositories/index.ts b/src/server/repositories/index.ts index 04b1f35..9a703ab 100644 --- a/src/server/repositories/index.ts +++ b/src/server/repositories/index.ts @@ -1,3 +1,4 @@ +export { deckRepository } from "./deck.js"; export { refreshTokenRepository } from "./refresh-token.js"; export * from "./types.js"; export { userRepository } from "./user.js"; diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts index 1ab4bdc..740fa29 100644 --- a/src/server/repositories/types.ts +++ b/src/server/repositories/types.ts @@ -44,3 +44,36 @@ export interface RefreshTokenRepository { }): Promise; deleteById(id: string): Promise; } + +export interface Deck { + id: string; + userId: string; + name: string; + description: string | null; + newCardsPerDay: number; + createdAt: Date; + updatedAt: Date; + deletedAt: Date | null; + syncVersion: number; +} + +export interface DeckRepository { + findByUserId(userId: string): Promise; + findById(id: string, userId: string): Promise; + create(data: { + userId: string; + name: string; + description?: string | null; + newCardsPerDay?: number; + }): Promise; + update( + id: string, + userId: string, + data: { + name?: string; + description?: string | null; + newCardsPerDay?: number; + }, + ): Promise; + softDelete(id: string, userId: string): Promise; +} -- cgit v1.2.3-70-g09d2