aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/repositories
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/repositories')
-rw-r--r--src/server/repositories/card.test.ts2
-rw-r--r--src/server/repositories/card.ts163
-rw-r--r--src/server/repositories/review-log.ts21
-rw-r--r--src/server/repositories/types.ts11
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 {