diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-07 17:37:08 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-07 17:37:08 +0900 |
| commit | 797ef2fcfaa7ac63355c13809a644401a76250bc (patch) | |
| tree | 70814083131572b31b72fdbaeea74b2d7aa60d91 /src/server/repositories | |
| parent | 943674471d062ea4494727ce308c8c429afd6f98 (diff) | |
| download | kioku-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/repositories')
| -rw-r--r-- | src/server/repositories/deck.ts | 95 | ||||
| -rw-r--r-- | src/server/repositories/index.ts | 1 | ||||
| -rw-r--r-- | src/server/repositories/types.ts | 33 |
3 files changed, 129 insertions, 0 deletions
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<Deck[]> { + 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<Deck | undefined> { + 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<Deck> { + 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<Deck | undefined> { + 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<boolean> { + 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<void>; deleteById(id: string): Promise<void>; } + +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<Deck[]>; + findById(id: string, userId: string): Promise<Deck | undefined>; + create(data: { + userId: string; + name: string; + description?: string | null; + newCardsPerDay?: number; + }): Promise<Deck>; + update( + id: string, + userId: string, + data: { + name?: string; + description?: string | null; + newCardsPerDay?: number; + }, + ): Promise<Deck | undefined>; + softDelete(id: string, userId: string): Promise<boolean>; +} |
