aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server/repositories/card.ts
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-31 13:25:42 +0900
committernsfisis <nsfisis@gmail.com>2025-12-31 13:25:42 +0900
commitce9011bf351d9666bb2e81c92ae06a0eb1716d12 (patch)
treebeea42ad8c4b755d1c36705c6c052d5b0588eaac /src/server/repositories/card.ts
parent5e42032146de07d4ab53598e9311efd145f9dfc3 (diff)
downloadkioku-ce9011bf351d9666bb2e81c92ae06a0eb1716d12.tar.gz
kioku-ce9011bf351d9666bb2e81c92ae06a0eb1716d12.tar.zst
kioku-ce9011bf351d9666bb2e81c92ae06a0eb1716d12.zip
feat(study): render note-based cards using template system
Update StudyPage to support both legacy cards (direct front/back) and note-based cards (template rendering with field values). Add new CardForStudy type that includes note type templates and field values as a name-value map for efficient client-side rendering. 🤖 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/card.ts')
-rw-r--r--src/server/repositories/card.ts107
1 files changed, 105 insertions, 2 deletions
diff --git a/src/server/repositories/card.ts b/src/server/repositories/card.ts
index 92811d4..7116642 100644
--- a/src/server/repositories/card.ts
+++ b/src/server/repositories/card.ts
@@ -1,7 +1,19 @@
import { and, eq, isNull, lte, sql } from "drizzle-orm";
import { db } from "../db/index.js";
-import { CardState, cards, noteFieldValues, notes } from "../db/schema.js";
-import type { Card, CardRepository, CardWithNoteData } from "./types.js";
+import {
+ CardState,
+ cards,
+ noteFieldTypes,
+ noteFieldValues,
+ notes,
+ noteTypes,
+} from "../db/schema.js";
+import type {
+ Card,
+ CardForStudy,
+ CardRepository,
+ CardWithNoteData,
+} from "./types.js";
export const cardRepository: CardRepository = {
async findByDeckId(deckId: string): Promise<Card[]> {
@@ -219,6 +231,97 @@ export const cardRepository: CardRepository = {
return cardsWithNoteData;
},
+ async findDueCardsForStudy(
+ deckId: string,
+ now: Date,
+ limit: number,
+ ): Promise<CardForStudy[]> {
+ const dueCards = await this.findDueCards(deckId, now, limit);
+
+ const cardsForStudy: CardForStudy[] = [];
+
+ for (const card of dueCards) {
+ // Legacy card (no note association)
+ if (!card.noteId) {
+ cardsForStudy.push({
+ ...card,
+ noteType: null,
+ fieldValuesMap: {},
+ });
+ continue;
+ }
+
+ // 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, treat as legacy card
+ cardsForStudy.push({
+ ...card,
+ noteType: null,
+ fieldValuesMap: {},
+ });
+ 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, treat as legacy card
+ cardsForStudy.push({
+ ...card,
+ noteType: null,
+ fieldValuesMap: {},
+ });
+ 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,
+ });
+ }
+
+ return cardsForStudy;
+ },
+
async updateFSRSFields(
id: string,
deckId: string,