Files
Momento/memento-note/lib/flashcards/deck-queries.ts
Antigravity 36336e6b0d
Some checks failed
CI / Lint, Test & Build (push) Failing after 32s
CI / Deploy production (on server) (push) Has been skipped
feat(flashcards): révision SM-2, génération IA et page /revision
Livre US-FLASHCARDS avec decks, session de révision, stats et migration Prisma. Finalise le Web Clipper (i18n 15 langues) et corrige les erreurs ESLint bloquant la CI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 19:22:20 +00:00

91 lines
2.3 KiB
TypeScript

import prisma from '@/lib/prisma'
import { isCardMastered } from '@/lib/flashcards/sm2'
export interface DeckSummary {
id: string
name: string
notebookId: string | null
totalCards: number
dueCount: number
masteredCount: number
lastReviewedAt: string | null
createdAt: string
}
export async function listDeckSummaries(userId: string): Promise<DeckSummary[]> {
const now = new Date()
const decks = await prisma.flashcardDeck.findMany({
where: { userId },
include: {
flashcards: {
select: {
id: true,
interval: true,
nextReviewAt: true,
reviews: {
orderBy: { reviewedAt: 'desc' },
take: 1,
select: { reviewedAt: true },
},
},
},
},
orderBy: { updatedAt: 'desc' },
})
return decks.map((deck) => {
const totalCards = deck.flashcards.length
const dueCount = deck.flashcards.filter((c) => c.nextReviewAt <= now).length
const masteredCount = deck.flashcards.filter((c) => isCardMastered(c.interval)).length
const lastReview = deck.flashcards
.flatMap((c) => c.reviews.map((r) => r.reviewedAt))
.sort((a, b) => b.getTime() - a.getTime())[0]
return {
id: deck.id,
name: deck.name,
notebookId: deck.notebookId,
totalCards,
dueCount,
masteredCount,
lastReviewedAt: lastReview ? lastReview.toISOString() : null,
createdAt: deck.createdAt.toISOString(),
}
})
}
export async function getDeckDetail(userId: string, deckId: string) {
const deck = await prisma.flashcardDeck.findFirst({
where: { id: deckId, userId },
include: {
flashcards: {
orderBy: { nextReviewAt: 'asc' },
include: {
note: { select: { id: true, title: true } },
},
},
},
})
if (!deck) return null
const now = new Date()
return {
id: deck.id,
name: deck.name,
notebookId: deck.notebookId,
cards: deck.flashcards.map((c) => ({
id: c.id,
front: c.front,
back: c.back,
type: c.type,
interval: c.interval,
easinessFactor: c.easinessFactor,
nextReviewAt: c.nextReviewAt.toISOString(),
noteId: c.noteId,
noteTitle: c.note?.title ?? null,
due: c.nextReviewAt <= now,
mastered: isCardMastered(c.interval),
})),
}
}