diff options
Diffstat (limited to 'src/server/repositories')
| -rw-r--r-- | src/server/repositories/card.test.ts | 2 | ||||
| -rw-r--r-- | src/server/repositories/card.ts | 163 | ||||
| -rw-r--r-- | src/server/repositories/review-log.ts | 21 | ||||
| -rw-r--r-- | src/server/repositories/types.ts | 11 |
4 files changed, 134 insertions, 63 deletions
diff --git a/src/server/repositories/card.test.ts b/src/server/repositories/card.test.ts index b492fd7..1ed31c7 100644 --- a/src/server/repositories/card.test.ts +++ b/src/server/repositories/card.test.ts @@ -114,6 +114,8 @@ function createMockCardRepo(): CardRepository { countDueCards: 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 2f14149..6202267 100644 --- a/src/server/repositories/card.ts +++ b/src/server/repositories/card.ts @@ -1,4 +1,4 @@ -import { and, eq, isNull, lt, sql } from "drizzle-orm"; +import { and, eq, isNull, lt, lte, ne, sql } from "drizzle-orm"; import { getEndOfStudyDayBoundary } from "../../shared/date.js"; import { db } from "../db/index.js"; import { @@ -263,69 +263,49 @@ export const cardRepository: CardRepository = { limit: number, ): Promise<CardForStudy[]> { const dueCards = await this.findDueCards(deckId, now, limit); + return enrichCardsForStudy(dueCards); + }, - const cardsForStudy: CardForStudy[] = []; - - for (const card of dueCards) { - // Fetch note to get noteTypeId - const noteResult = await db - .select() - .from(notes) - .where(and(eq(notes.id, card.noteId), isNull(notes.deletedAt))); - - const note = noteResult[0]; - if (!note) { - // Note was deleted, skip this card - continue; - } - - // Fetch note type for templates - const noteTypeResult = await db - .select({ - frontTemplate: noteTypes.frontTemplate, - backTemplate: noteTypes.backTemplate, - }) - .from(noteTypes) - .where( - and(eq(noteTypes.id, note.noteTypeId), isNull(noteTypes.deletedAt)), - ); - - const noteType = noteTypeResult[0]; - if (!noteType) { - // Note type was deleted, skip this card - continue; - } - - // Fetch field values with their field names - const fieldValuesWithNames = await db - .select({ - fieldName: noteFieldTypes.name, - value: noteFieldValues.value, - }) - .from(noteFieldValues) - .innerJoin( - noteFieldTypes, - eq(noteFieldValues.noteFieldTypeId, noteFieldTypes.id), - ) - .where(eq(noteFieldValues.noteId, card.noteId)); - - // Convert to name-value map - const fieldValuesMap: Record<string, string> = {}; - for (const fv of fieldValuesWithNames) { - fieldValuesMap[fv.fieldName] = fv.value; - } - - cardsForStudy.push({ - ...card, - noteType: { - frontTemplate: noteType.frontTemplate, - backTemplate: noteType.backTemplate, - }, - fieldValuesMap, - }); - } + async findDueNewCardsForStudy( + deckId: string, + now: Date, + limit: number, + ): Promise<CardForStudy[]> { + const result = await db + .select() + .from(cards) + .where( + and( + eq(cards.deckId, deckId), + isNull(cards.deletedAt), + lte(cards.due, now), + eq(cards.state, CardState.New), + ), + ) + .orderBy(cards.due) + .limit(limit); + return enrichCardsForStudy(result); + }, - return cardsForStudy; + async findDueReviewCardsForStudy( + deckId: string, + now: Date, + limit: number, + ): Promise<CardForStudy[]> { + const result = await db + .select() + .from(cards) + .where( + and( + eq(cards.deckId, deckId), + isNull(cards.deletedAt), + lte(cards.due, now), + ne(cards.state, CardState.New), + ), + ) + .orderBy(cards.due) + .limit(limit); + return enrichCardsForStudy(result); }, async updateFSRSFields( @@ -369,3 +349,62 @@ export const cardRepository: CardRepository = { return result[0]; }, }; + +async function enrichCardsForStudy(dueCards: Card[]): Promise<CardForStudy[]> { + const cardsForStudy: CardForStudy[] = []; + + for (const card of dueCards) { + const noteResult = await db + .select() + .from(notes) + .where(and(eq(notes.id, card.noteId), isNull(notes.deletedAt))); + + const note = noteResult[0]; + if (!note) { + continue; + } + + const noteTypeResult = await db + .select({ + frontTemplate: noteTypes.frontTemplate, + backTemplate: noteTypes.backTemplate, + }) + .from(noteTypes) + .where( + and(eq(noteTypes.id, note.noteTypeId), isNull(noteTypes.deletedAt)), + ); + + const noteType = noteTypeResult[0]; + if (!noteType) { + continue; + } + + const fieldValuesWithNames = await db + .select({ + fieldName: noteFieldTypes.name, + value: noteFieldValues.value, + }) + .from(noteFieldValues) + .innerJoin( + noteFieldTypes, + eq(noteFieldValues.noteFieldTypeId, noteFieldTypes.id), + ) + .where(eq(noteFieldValues.noteId, card.noteId)); + + const fieldValuesMap: Record<string, string> = {}; + for (const fv of fieldValuesWithNames) { + fieldValuesMap[fv.fieldName] = fv.value; + } + + cardsForStudy.push({ + ...card, + noteType: { + frontTemplate: noteType.frontTemplate, + backTemplate: noteType.backTemplate, + }, + fieldValuesMap, + }); + } + + return cardsForStudy; +} diff --git a/src/server/repositories/review-log.ts b/src/server/repositories/review-log.ts index c8950d6..591c647 100644 --- a/src/server/repositories/review-log.ts +++ b/src/server/repositories/review-log.ts @@ -1,5 +1,6 @@ +import { and, eq, gte, sql } from "drizzle-orm"; import { db } from "../db/index.js"; -import { reviewLogs } from "../db/schema.js"; +import { CardState, cards, reviewLogs } from "../db/schema.js"; import type { ReviewLog, ReviewLogRepository } from "./types.js"; export const reviewLogRepository: ReviewLogRepository = { @@ -29,4 +30,22 @@ export const reviewLogRepository: ReviewLogRepository = { } return reviewLog; }, + + async countTodayNewCardReviews(deckId: string, now: Date): Promise<number> { + const startOfDay = new Date(now); + startOfDay.setHours(0, 0, 0, 0); + + 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/types.ts b/src/server/repositories/types.ts index cb3a287..47fb68f 100644 --- a/src/server/repositories/types.ts +++ b/src/server/repositories/types.ts @@ -158,6 +158,16 @@ export interface CardRepository { now: Date, limit: number, ): Promise<CardForStudy[]>; + findDueNewCardsForStudy( + deckId: string, + now: Date, + limit: number, + ): Promise<CardForStudy[]>; + findDueReviewCardsForStudy( + deckId: string, + now: Date, + limit: number, + ): Promise<CardForStudy[]>; updateFSRSFields( id: string, deckId: string, @@ -198,6 +208,7 @@ export interface ReviewLogRepository { elapsedDays: number; durationMs?: number | null; }): Promise<ReviewLog>; + countTodayNewCardReviews(deckId: string, now: Date): Promise<number>; } export interface NoteType { |
