diff options
| author | Claude <noreply@anthropic.com> | 2026-02-12 14:54:18 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-02-12 14:54:18 +0000 |
| commit | 1afb825860cd293b8065d51746f4b23e4e8dab5d (patch) | |
| tree | 1fe3a43f1c7ab469bb0154a1495028cc42b414a0 /src/server | |
| parent | 9a52e7ad3b2d46c523caf079794fdb7757375b91 (diff) | |
| download | kioku-1afb825860cd293b8065d51746f4b23e4e8dab5d.tar.gz kioku-1afb825860cd293b8065d51746f4b23e4e8dab5d.tar.zst kioku-1afb825860cd293b8065d51746f4b23e4e8dab5d.zip | |
feat: 学習カード数の上限を撤廃
REVIEW_CARDS_LIMIT(復習カード80枚制限)とnewCardsPerDay(1日の新規カード制限)
を削除し、期日が来たすべてのカードを制限なく返すように変更。
削除した主な要素:
- REVIEW_CARDS_LIMIT定数とカード取得時のlimitパラメータ
- newCardsPerDayフィールド(DB schema, 型定義, Zod schema, sync, CRDT)
- countDueNewCards, countDueReviewCards, findDueNewCardsForStudy,
findDueReviewCardsForStudy(CardRepository)
- countTodayNewCardReviews(ReviewLogRepository)
- デッキルートからのReviewLogRepository依存
https://claude.ai/code/session_018hrEJ9vg3RPoeAPyEc17gS
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/db/schema.ts | 1 | ||||
| -rw-r--r-- | src/server/repositories/card.test.ts | 4 | ||||
| -rw-r--r-- | src/server/repositories/card.ts | 93 | ||||
| -rw-r--r-- | src/server/repositories/deck.test.ts | 5 | ||||
| -rw-r--r-- | src/server/repositories/deck.ts | 3 | ||||
| -rw-r--r-- | src/server/repositories/review-log.ts | 21 | ||||
| -rw-r--r-- | src/server/repositories/sync.test.ts | 1 | ||||
| -rw-r--r-- | src/server/repositories/sync.ts | 3 | ||||
| -rw-r--r-- | src/server/repositories/types.ts | 25 | ||||
| -rw-r--r-- | src/server/routes/cards.test.ts | 5 | ||||
| -rw-r--r-- | src/server/routes/decks.test.ts | 53 | ||||
| -rw-r--r-- | src/server/routes/decks.ts | 43 | ||||
| -rw-r--r-- | src/server/routes/notes.test.ts | 1 | ||||
| -rw-r--r-- | src/server/routes/study.test.ts | 109 | ||||
| -rw-r--r-- | src/server/routes/study.ts | 15 | ||||
| -rw-r--r-- | src/server/routes/sync.test.ts | 9 | ||||
| -rw-r--r-- | src/server/routes/sync.ts | 1 | ||||
| -rw-r--r-- | src/server/schemas/index.ts | 3 | ||||
| -rw-r--r-- | src/server/types/index.ts | 1 |
19 files changed, 19 insertions, 377 deletions
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 50caf85..55be210 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -101,7 +101,6 @@ export const decks = pgTable("decks", { .references(() => users.id), name: varchar("name", { length: 255 }).notNull(), description: text("description"), - newCardsPerDay: integer("new_cards_per_day").notNull().default(20), createdAt: timestamp("created_at", { withTimezone: true }) .notNull() .defaultNow(), diff --git a/src/server/repositories/card.test.ts b/src/server/repositories/card.test.ts index 21e5485..b492fd7 100644 --- a/src/server/repositories/card.test.ts +++ b/src/server/repositories/card.test.ts @@ -112,12 +112,8 @@ function createMockCardRepo(): CardRepository { softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), countDueCards: vi.fn(), - countDueNewCards: vi.fn(), - countDueReviewCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), - findDueNewCardsForStudy: vi.fn(), - findDueReviewCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), }; } diff --git a/src/server/repositories/card.ts b/src/server/repositories/card.ts index d382f4d..0f1ef79 100644 --- a/src/server/repositories/card.ts +++ b/src/server/repositories/card.ts @@ -1,4 +1,4 @@ -import { and, eq, isNull, lt, ne, sql } from "drizzle-orm"; +import { and, eq, isNull, lt, sql } from "drizzle-orm"; import { getEndOfStudyDayBoundary } from "../../shared/date.js"; import { db } from "../db/index.js"; import { @@ -185,11 +185,7 @@ export const cardRepository: CardRepository = { return result.length > 0; }, - async findDueCards( - deckId: string, - now: Date, - limit: number, - ): Promise<Card[]> { + async findDueCards(deckId: string, now: Date): Promise<Card[]> { const boundary = getEndOfStudyDayBoundary(now); const result = await db .select() @@ -201,8 +197,7 @@ export const cardRepository: CardRepository = { lt(cards.due, boundary), ), ) - .orderBy(cards.due) - .limit(limit); + .orderBy(cards.due); return result; }, @@ -221,44 +216,11 @@ export const cardRepository: CardRepository = { return result[0]?.count ?? 0; }, - async countDueNewCards(deckId: string, now: Date): Promise<number> { - const boundary = getEndOfStudyDayBoundary(now); - const result = await db - .select({ count: sql<number>`count(*)::int` }) - .from(cards) - .where( - and( - eq(cards.deckId, deckId), - isNull(cards.deletedAt), - lt(cards.due, boundary), - eq(cards.state, CardState.New), - ), - ); - return result[0]?.count ?? 0; - }, - - async countDueReviewCards(deckId: string, now: Date): Promise<number> { - const boundary = getEndOfStudyDayBoundary(now); - const result = await db - .select({ count: sql<number>`count(*)::int` }) - .from(cards) - .where( - and( - eq(cards.deckId, deckId), - isNull(cards.deletedAt), - lt(cards.due, boundary), - ne(cards.state, CardState.New), - ), - ); - return result[0]?.count ?? 0; - }, - async findDueCardsWithNoteData( deckId: string, now: Date, - limit: number, ): Promise<CardWithNoteData[]> { - const dueCards = await this.findDueCards(deckId, now, limit); + const dueCards = await this.findDueCards(deckId, now); const cardsWithNoteData: CardWithNoteData[] = []; @@ -292,56 +254,11 @@ export const cardRepository: CardRepository = { async findDueCardsForStudy( deckId: string, now: Date, - limit: number, ): Promise<CardForStudy[]> { - const dueCards = await this.findDueCards(deckId, now, limit); + const dueCards = await this.findDueCards(deckId, now); return enrichCardsForStudy(dueCards); }, - async findDueNewCardsForStudy( - deckId: string, - now: Date, - limit: number, - ): Promise<CardForStudy[]> { - const boundary = getEndOfStudyDayBoundary(now); - const result = await db - .select() - .from(cards) - .where( - and( - eq(cards.deckId, deckId), - isNull(cards.deletedAt), - lt(cards.due, boundary), - eq(cards.state, CardState.New), - ), - ) - .orderBy(cards.due) - .limit(limit); - return enrichCardsForStudy(result); - }, - - async findDueReviewCardsForStudy( - deckId: string, - now: Date, - limit: number, - ): Promise<CardForStudy[]> { - const boundary = getEndOfStudyDayBoundary(now); - const result = await db - .select() - .from(cards) - .where( - and( - eq(cards.deckId, deckId), - isNull(cards.deletedAt), - lt(cards.due, boundary), - ne(cards.state, CardState.New), - ), - ) - .orderBy(cards.due) - .limit(limit); - return enrichCardsForStudy(result); - }, - async updateFSRSFields( id: string, deckId: string, diff --git a/src/server/repositories/deck.test.ts b/src/server/repositories/deck.test.ts index 945f844..ab6e2fc 100644 --- a/src/server/repositories/deck.test.ts +++ b/src/server/repositories/deck.test.ts @@ -7,7 +7,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, @@ -35,7 +34,6 @@ describe("DeckRepository mock factory", () => { expect(deck.userId).toBe("user-uuid-123"); expect(deck.name).toBe("Test Deck"); expect(deck.description).toBeNull(); - expect(deck.newCardsPerDay).toBe(20); expect(deck.deletedAt).toBeNull(); expect(deck.syncVersion).toBe(0); }); @@ -45,13 +43,11 @@ describe("DeckRepository mock factory", () => { id: "custom-id", name: "Custom Deck", description: "A description", - newCardsPerDay: 50, }); expect(deck.id).toBe("custom-id"); expect(deck.name).toBe("Custom Deck"); expect(deck.description).toBe("A description"); - expect(deck.newCardsPerDay).toBe(50); }); }); @@ -130,7 +126,6 @@ describe("Deck interface contracts", () => { expect(deck).toHaveProperty("name"); expect(deck).toHaveProperty("description"); - expect(deck).toHaveProperty("newCardsPerDay"); }); }); diff --git a/src/server/repositories/deck.ts b/src/server/repositories/deck.ts index 647c5cb..97af5f7 100644 --- a/src/server/repositories/deck.ts +++ b/src/server/repositories/deck.ts @@ -31,7 +31,6 @@ export const deckRepository: DeckRepository = { userId: string; name: string; description?: string | null; - newCardsPerDay?: number; }): Promise<Deck> { const [deck] = await db .insert(decks) @@ -39,7 +38,6 @@ export const deckRepository: DeckRepository = { userId: data.userId, name: data.name, description: data.description ?? null, - newCardsPerDay: data.newCardsPerDay ?? 20, }) .returning(); if (!deck) { @@ -54,7 +52,6 @@ export const deckRepository: DeckRepository = { data: { name?: string; description?: string | null; - newCardsPerDay?: number; }, ): Promise<Deck | undefined> { const result = await db diff --git a/src/server/repositories/review-log.ts b/src/server/repositories/review-log.ts index 97488d2..c8950d6 100644 --- a/src/server/repositories/review-log.ts +++ b/src/server/repositories/review-log.ts @@ -1,7 +1,5 @@ -import { and, eq, gte, sql } from "drizzle-orm"; -import { getStartOfStudyDayBoundary } from "../../shared/date.js"; import { db } from "../db/index.js"; -import { CardState, cards, reviewLogs } from "../db/schema.js"; +import { reviewLogs } from "../db/schema.js"; import type { ReviewLog, ReviewLogRepository } from "./types.js"; export const reviewLogRepository: ReviewLogRepository = { @@ -31,21 +29,4 @@ export const reviewLogRepository: ReviewLogRepository = { } return reviewLog; }, - - async countTodayNewCardReviews(deckId: string, now: Date): Promise<number> { - const startOfDay = getStartOfStudyDayBoundary(now); - - const result = await db - .select({ count: sql<number>`count(distinct ${reviewLogs.cardId})::int` }) - .from(reviewLogs) - .innerJoin(cards, eq(reviewLogs.cardId, cards.id)) - .where( - and( - eq(cards.deckId, deckId), - eq(reviewLogs.state, CardState.New), - gte(reviewLogs.reviewedAt, startOfDay), - ), - ); - return result[0]?.count ?? 0; - }, }; diff --git a/src/server/repositories/sync.test.ts b/src/server/repositories/sync.test.ts index ce59cb5..8425839 100644 --- a/src/server/repositories/sync.test.ts +++ b/src/server/repositories/sync.test.ts @@ -16,7 +16,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/repositories/sync.ts b/src/server/repositories/sync.ts index ca4c208..e197d37 100644 --- a/src/server/repositories/sync.ts +++ b/src/server/repositories/sync.ts @@ -53,7 +53,6 @@ export interface SyncDeckData { id: string; name: string; description: string | null; - newCardsPerDay: number; createdAt: string; updatedAt: string; deletedAt: string | null; @@ -224,7 +223,6 @@ export const syncRepository: SyncRepository = { userId, name: deckData.name, description: deckData.description, - newCardsPerDay: deckData.newCardsPerDay, createdAt: new Date(deckData.createdAt), updatedAt: clientUpdatedAt, deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) : null, @@ -248,7 +246,6 @@ export const syncRepository: SyncRepository = { .set({ name: deckData.name, description: deckData.description, - newCardsPerDay: deckData.newCardsPerDay, updatedAt: clientUpdatedAt, deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts index 4042daf..71cb811 100644 --- a/src/server/repositories/types.ts +++ b/src/server/repositories/types.ts @@ -50,7 +50,6 @@ export interface Deck { userId: string; name: string; description: string | null; - newCardsPerDay: number; createdAt: Date; updatedAt: Date; deletedAt: Date | null; @@ -64,7 +63,6 @@ export interface DeckRepository { userId: string; name: string; description?: string | null; - newCardsPerDay?: number; }): Promise<Deck>; update( id: string, @@ -72,7 +70,6 @@ export interface DeckRepository { data: { name?: string; description?: string | null; - newCardsPerDay?: number; }, ): Promise<Deck | undefined>; softDelete(id: string, userId: string): Promise<boolean>; @@ -146,30 +143,13 @@ export interface CardRepository { ): Promise<Card | undefined>; softDelete(id: string, deckId: string): Promise<boolean>; softDeleteByNoteId(noteId: string): Promise<boolean>; - findDueCards(deckId: string, now: Date, limit: number): Promise<Card[]>; + findDueCards(deckId: string, now: Date): Promise<Card[]>; countDueCards(deckId: string, now: Date): Promise<number>; findDueCardsWithNoteData( deckId: string, now: Date, - limit: number, ): Promise<CardWithNoteData[]>; - findDueCardsForStudy( - deckId: string, - now: Date, - limit: number, - ): Promise<CardForStudy[]>; - findDueNewCardsForStudy( - deckId: string, - now: Date, - limit: number, - ): Promise<CardForStudy[]>; - findDueReviewCardsForStudy( - deckId: string, - now: Date, - limit: number, - ): Promise<CardForStudy[]>; - countDueNewCards(deckId: string, now: Date): Promise<number>; - countDueReviewCards(deckId: string, now: Date): Promise<number>; + findDueCardsForStudy(deckId: string, now: Date): Promise<CardForStudy[]>; updateFSRSFields( id: string, deckId: string, @@ -210,7 +190,6 @@ export interface ReviewLogRepository { elapsedDays: number; durationMs?: number | null; }): Promise<ReviewLog>; - countTodayNewCardReviews(deckId: string, now: Date): Promise<number>; } export interface NoteType { diff --git a/src/server/routes/cards.test.ts b/src/server/routes/cards.test.ts index 4595e28..a063c95 100644 --- a/src/server/routes/cards.test.ts +++ b/src/server/routes/cards.test.ts @@ -26,12 +26,8 @@ function createMockCardRepo(): CardRepository { softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), countDueCards: vi.fn(), - countDueNewCards: vi.fn(), - countDueReviewCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), - findDueNewCardsForStudy: vi.fn(), - findDueReviewCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), }; } @@ -66,7 +62,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/routes/decks.test.ts b/src/server/routes/decks.test.ts index d0854c1..f686024 100644 --- a/src/server/routes/decks.test.ts +++ b/src/server/routes/decks.test.ts @@ -6,7 +6,6 @@ import type { CardRepository, Deck, DeckRepository, - ReviewLogRepository, } from "../repositories/index.js"; import { createDecksRouter } from "./decks.js"; @@ -32,23 +31,12 @@ function createMockCardRepo(): CardRepository { softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), countDueCards: vi.fn().mockResolvedValue(0), - countDueNewCards: vi.fn().mockResolvedValue(0), - countDueReviewCards: vi.fn().mockResolvedValue(0), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), - findDueNewCardsForStudy: vi.fn(), - findDueReviewCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), }; } -function createMockReviewLogRepo(): ReviewLogRepository { - return { - create: vi.fn(), - countTodayNewCardReviews: vi.fn().mockResolvedValue(0), - }; -} - const JWT_SECRET = process.env.JWT_SECRET || "test-secret"; async function createTestToken(userId: string): Promise<string> { @@ -69,7 +57,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, @@ -92,18 +79,15 @@ describe("GET /api/decks", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; let mockCardRepo: ReturnType<typeof createMockCardRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); mockCardRepo = createMockCardRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo, cardRepo: mockCardRepo, - reviewLogRepo: mockReviewLogRepo, }); app = new Hono(); app.onError(errorHandler); @@ -155,18 +139,15 @@ describe("POST /api/decks", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; let mockCardRepo: ReturnType<typeof createMockCardRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); mockCardRepo = createMockCardRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo, cardRepo: mockCardRepo, - reviewLogRepo: mockReviewLogRepo, }); app = new Hono(); app.onError(errorHandler); @@ -194,7 +175,6 @@ describe("POST /api/decks", () => { userId: "user-uuid-123", name: "New Deck", description: undefined, - newCardsPerDay: 20, }); }); @@ -202,7 +182,6 @@ describe("POST /api/decks", () => { const newDeck = createMockDeck({ name: "Full Deck", description: "Full description", - newCardsPerDay: 30, }); vi.mocked(mockDeckRepo.create).mockResolvedValue(newDeck); @@ -215,7 +194,6 @@ describe("POST /api/decks", () => { body: JSON.stringify({ name: "Full Deck", description: "Full description", - newCardsPerDay: 30, }), }); @@ -226,7 +204,6 @@ describe("POST /api/decks", () => { userId: "user-uuid-123", name: "Full Deck", description: "Full description", - newCardsPerDay: 30, }); }); @@ -271,18 +248,15 @@ describe("GET /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; let mockCardRepo: ReturnType<typeof createMockCardRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); mockCardRepo = createMockCardRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo, cardRepo: mockCardRepo, - reviewLogRepo: mockReviewLogRepo, }); app = new Hono(); app.onError(errorHandler); @@ -344,18 +318,15 @@ describe("PUT /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; let mockCardRepo: ReturnType<typeof createMockCardRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); mockCardRepo = createMockCardRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo, cardRepo: mockCardRepo, - reviewLogRepo: mockReviewLogRepo, }); app = new Hono(); app.onError(errorHandler); @@ -405,27 +376,6 @@ describe("PUT /api/decks/:id", () => { expect(body.deck?.description).toBe("New description"); }); - it("updates newCardsPerDay", async () => { - const updatedDeck = createMockDeck({ newCardsPerDay: 50 }); - vi.mocked(mockDeckRepo.update).mockResolvedValue(updatedDeck); - - const res = await app.request( - "/api/decks/00000000-0000-0000-0000-000000000000", - { - method: "PUT", - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ newCardsPerDay: 50 }), - }, - ); - - expect(res.status).toBe(200); - const body = (await res.json()) as DeckResponse; - expect(body.deck?.newCardsPerDay).toBe(50); - }); - it("returns 404 for non-existent deck", async () => { vi.mocked(mockDeckRepo.update).mockResolvedValue(undefined); @@ -477,18 +427,15 @@ describe("DELETE /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; let mockCardRepo: ReturnType<typeof createMockCardRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); mockCardRepo = createMockCardRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo, cardRepo: mockCardRepo, - reviewLogRepo: mockReviewLogRepo, }); app = new Hono(); app.onError(errorHandler); diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts index d73aa0c..ed7077e 100644 --- a/src/server/routes/decks.ts +++ b/src/server/routes/decks.ts @@ -7,25 +7,20 @@ import { cardRepository, type DeckRepository, deckRepository, - type ReviewLogRepository, - reviewLogRepository, } from "../repositories/index.js"; import { createDeckSchema, updateDeckSchema } from "../schemas/index.js"; export interface DeckDependencies { deckRepo: DeckRepository; cardRepo: CardRepository; - reviewLogRepo: ReviewLogRepository; } const deckIdParamSchema = z.object({ id: z.uuid(), }); -const REVIEW_CARDS_LIMIT = 80; - export function createDecksRouter(deps: DeckDependencies) { - const { deckRepo, cardRepo, reviewLogRepo } = deps; + const { deckRepo, cardRepo } = deps; return new Hono() .use("*", authMiddleware) @@ -35,26 +30,7 @@ export function createDecksRouter(deps: DeckDependencies) { const now = new Date(); const decksWithDueCount = await Promise.all( decks.map(async (deck) => { - const [dueNewCards, dueReviewCards, reviewedNewCards] = - await Promise.all([ - cardRepo.countDueNewCards(deck.id, now), - cardRepo.countDueReviewCards(deck.id, now), - reviewLogRepo.countTodayNewCardReviews(deck.id, now), - ]); - - // Apply the same limits as the study screen - const newCardBudget = Math.max( - 0, - deck.newCardsPerDay - reviewedNewCards, - ); - const newCardsToStudy = Math.min(dueNewCards, newCardBudget); - const reviewCardsToStudy = Math.min( - dueReviewCards, - REVIEW_CARDS_LIMIT, - ); - - const dueCardCount = newCardsToStudy + reviewCardsToStudy; - + const dueCardCount = await cardRepo.countDueCards(deck.id, now); return { ...deck, dueCardCount }; }), ); @@ -68,7 +44,6 @@ export function createDecksRouter(deps: DeckDependencies) { userId: user.id, name: data.name, description: data.description, - newCardsPerDay: data.newCardsPerDay, }); return c.json({ deck }, 201); @@ -83,18 +58,7 @@ export function createDecksRouter(deps: DeckDependencies) { } const now = new Date(); - const [dueNewCards, dueReviewCards, reviewedNewCards] = await Promise.all( - [ - cardRepo.countDueNewCards(deck.id, now), - cardRepo.countDueReviewCards(deck.id, now), - reviewLogRepo.countTodayNewCardReviews(deck.id, now), - ], - ); - - const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards); - const newCardsToStudy = Math.min(dueNewCards, newCardBudget); - const reviewCardsToStudy = Math.min(dueReviewCards, REVIEW_CARDS_LIMIT); - const dueCardCount = newCardsToStudy + reviewCardsToStudy; + const dueCardCount = await cardRepo.countDueCards(deck.id, now); return c.json({ deck: { ...deck, dueCardCount } }, 200); }) @@ -131,5 +95,4 @@ export function createDecksRouter(deps: DeckDependencies) { export const decks = createDecksRouter({ deckRepo: deckRepository, cardRepo: cardRepository, - reviewLogRepo: reviewLogRepository, }); diff --git a/src/server/routes/notes.test.ts b/src/server/routes/notes.test.ts index e354fa6..116a57f 100644 --- a/src/server/routes/notes.test.ts +++ b/src/server/routes/notes.test.ts @@ -57,7 +57,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts index 514d966..119b25d 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -26,12 +26,8 @@ function createMockCardRepo(): CardRepository { softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), countDueCards: vi.fn(), - countDueNewCards: vi.fn(), - countDueReviewCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), - findDueNewCardsForStudy: vi.fn(), - findDueReviewCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), }; } @@ -49,7 +45,6 @@ function createMockDeckRepo(): DeckRepository { function createMockReviewLogRepo(): ReviewLogRepository { return { create: vi.fn(), - countTodayNewCardReviews: vi.fn().mockResolvedValue(0), }; } @@ -73,7 +68,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", - newCardsPerDay: 20, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, @@ -152,14 +146,13 @@ describe("GET /api/decks/:deckId/study", () => { let app: Hono; let mockCardRepo: ReturnType<typeof createMockCardRepo>; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; - let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockCardRepo = createMockCardRepo(); mockDeckRepo = createMockDeckRepo(); - mockReviewLogRepo = createMockReviewLogRepo(); + const mockReviewLogRepo = createMockReviewLogRepo(); const studyRouter = createStudyRouter({ cardRepo: mockCardRepo, deckRepo: mockDeckRepo, @@ -175,8 +168,7 @@ describe("GET /api/decks/:deckId/study", () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue( createMockDeck({ id: DECK_ID }), ); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]); + vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue([]); const res = await app.request(`/api/decks/${DECK_ID}/study`, { method: "GET", @@ -190,20 +182,14 @@ describe("GET /api/decks/:deckId/study", () => { DECK_ID, "user-uuid-123", ); - expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith( - DECK_ID, - expect.any(Date), - 20, - ); - expect(mockCardRepo.findDueReviewCardsForStudy).toHaveBeenCalledWith( + expect(mockCardRepo.findDueCardsForStudy).toHaveBeenCalledWith( DECK_ID, expect.any(Date), - 80, ); }); it("returns due cards", async () => { - const newCards = [ + const mockCards = [ createMockCardForStudy({ id: "card-1", front: "Q1", @@ -211,8 +197,6 @@ describe("GET /api/decks/:deckId/study", () => { state: CardState.New, fieldValuesMap: {}, }), - ]; - const reviewCards = [ createMockCardForStudy({ id: "card-2", front: "Q2", @@ -224,10 +208,7 @@ describe("GET /api/decks/:deckId/study", () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue( createMockDeck({ id: DECK_ID }), ); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue(newCards); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue( - reviewCards, - ); + vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue(mockCards); const res = await app.request(`/api/decks/${DECK_ID}/study`, { method: "GET", @@ -259,10 +240,7 @@ describe("GET /api/decks/:deckId/study", () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue( createMockDeck({ id: DECK_ID }), ); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue( - mockCards, - ); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]); + vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue(mockCards); const res = await app.request(`/api/decks/${DECK_ID}/study`, { method: "GET", @@ -276,81 +254,6 @@ describe("GET /api/decks/:deckId/study", () => { expect(body.cards?.[0]?.fieldValuesMap?.Front).toBe("Question"); }); - it("limits new cards based on newCardsPerDay", async () => { - vi.mocked(mockDeckRepo.findById).mockResolvedValue( - createMockDeck({ id: DECK_ID, newCardsPerDay: 5 }), - ); - vi.mocked(mockReviewLogRepo.countTodayNewCardReviews).mockResolvedValue(3); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]); - - await app.request(`/api/decks/${DECK_ID}/study`, { - method: "GET", - headers: { Authorization: `Bearer ${authToken}` }, - }); - - expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith( - DECK_ID, - expect.any(Date), - 2, - ); - }); - - it("returns 0 new cards when daily limit is reached", async () => { - vi.mocked(mockDeckRepo.findById).mockResolvedValue( - createMockDeck({ id: DECK_ID, newCardsPerDay: 5 }), - ); - vi.mocked(mockReviewLogRepo.countTodayNewCardReviews).mockResolvedValue(5); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]); - - await app.request(`/api/decks/${DECK_ID}/study`, { - method: "GET", - headers: { Authorization: `Bearer ${authToken}` }, - }); - - expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith( - DECK_ID, - expect.any(Date), - 0, - ); - }); - - it("places new cards before review cards in response", async () => { - const newCards = [ - createMockCardForStudy({ - id: "new-1", - state: CardState.New, - fieldValuesMap: {}, - }), - ]; - const reviewCards = [ - createMockCardForStudy({ - id: "review-1", - state: CardState.Review, - fieldValuesMap: {}, - }), - ]; - vi.mocked(mockDeckRepo.findById).mockResolvedValue( - createMockDeck({ id: DECK_ID }), - ); - vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue(newCards); - vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue( - reviewCards, - ); - - const res = await app.request(`/api/decks/${DECK_ID}/study`, { - method: "GET", - headers: { Authorization: `Bearer ${authToken}` }, - }); - - expect(res.status).toBe(200); - const body = (await res.json()) as StudyResponse; - expect(body.cards).toHaveLength(2); - expect(body.cards?.[0]?.id).toBe("new-1"); - expect(body.cards?.[1]?.id).toBe("review-1"); - }); - it("returns 404 for non-existent deck", async () => { vi.mocked(mockDeckRepo.findById).mockResolvedValue(undefined); diff --git a/src/server/routes/study.ts b/src/server/routes/study.ts index d05f3ca..0f42f93 100644 --- a/src/server/routes/study.ts +++ b/src/server/routes/study.ts @@ -52,20 +52,9 @@ export function createStudyRouter(deps: StudyDependencies) { const now = new Date(); - // Calculate new card budget based on today's already-reviewed new cards - const reviewedNewCards = await reviewLogRepo.countTodayNewCardReviews( - deckId, - now, - ); - const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards); + const cards = await cardRepo.findDueCardsForStudy(deckId, now); - // Fetch new cards (limited) and review cards separately - const [newCards, reviewCards] = await Promise.all([ - cardRepo.findDueNewCardsForStudy(deckId, now, newCardBudget), - cardRepo.findDueReviewCardsForStudy(deckId, now, 80), - ]); - - return c.json({ cards: [...newCards, ...reviewCards] }, 200); + return c.json({ cards }, 200); }) .post( "/:cardId", diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts index 8ea2ce3..4c0d8d8 100644 --- a/src/server/routes/sync.test.ts +++ b/src/server/routes/sync.test.ts @@ -143,7 +143,6 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440000", name: "Test Deck", description: "A test deck", - newCardsPerDay: 20, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: null, @@ -302,7 +301,6 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440003", name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: null, @@ -349,7 +347,6 @@ describe("POST /api/sync/push", () => { id: "not-a-uuid", name: "", description: null, - newCardsPerDay: -1, createdAt: "invalid-date", updatedAt: "2024-01-01T00:00:00.000Z", deletedAt: null, @@ -432,7 +429,6 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440004", name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-01T00:00:00.000Z", deletedAt: null, @@ -634,7 +630,6 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440007", name: "Deleted Deck", description: null, - newCardsPerDay: 20, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: "2024-01-02T00:00:00.000Z", @@ -830,7 +825,6 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-02T00:00:00.000Z"), deletedAt: null, @@ -906,7 +900,6 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: "A test description", - newCardsPerDay: 20, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-02T00:00:00.000Z"), deletedAt: null, @@ -1041,7 +1034,6 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: null, - newCardsPerDay: 20, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-01T00:00:00.000Z"), deletedAt: null, @@ -1215,7 +1207,6 @@ describe("GET /api/sync/pull", () => { userId, name: "Deleted Deck", description: null, - newCardsPerDay: 20, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-02T00:00:00.000Z"), deletedAt: new Date("2024-01-02T00:00:00.000Z"), diff --git a/src/server/routes/sync.ts b/src/server/routes/sync.ts index c571f8a..a9ea3b3 100644 --- a/src/server/routes/sync.ts +++ b/src/server/routes/sync.ts @@ -18,7 +18,6 @@ const syncDeckSchema = z.object({ id: z.uuid(), name: z.string().min(1).max(255), description: z.string().nullable(), - newCardsPerDay: z.number().int().min(0).max(1000), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), deletedAt: z.string().datetime().nullable(), diff --git a/src/server/schemas/index.ts b/src/server/schemas/index.ts index fc1bd77..aa9ceea 100644 --- a/src/server/schemas/index.ts +++ b/src/server/schemas/index.ts @@ -48,7 +48,6 @@ export const deckSchema = z.object({ userId: z.uuid(), name: z.string().min(1).max(255), description: z.string().max(1000).nullable(), - newCardsPerDay: z.number().int().min(0).default(20), createdAt: z.coerce.date(), updatedAt: z.coerce.date(), deletedAt: z.coerce.date().nullable(), @@ -59,14 +58,12 @@ export const deckSchema = z.object({ export const createDeckSchema = z.object({ name: z.string().min(1).max(255), description: z.string().max(1000).nullable().optional(), - newCardsPerDay: z.number().int().min(0).default(20), }); // Deck update input schema export const updateDeckSchema = z.object({ name: z.string().min(1).max(255).optional(), description: z.string().max(1000).nullable().optional(), - newCardsPerDay: z.number().int().min(0).optional(), }); // Card schema diff --git a/src/server/types/index.ts b/src/server/types/index.ts index bfba06f..08f8379 100644 --- a/src/server/types/index.ts +++ b/src/server/types/index.ts @@ -33,7 +33,6 @@ export interface Deck { userId: string; name: string; description: string | null; - newCardsPerDay: number; createdAt: Date; updatedAt: Date; deletedAt: Date | null; |
