diff options
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/repositories/card.test.ts | 1 | ||||
| -rw-r--r-- | src/server/repositories/card.ts | 14 | ||||
| -rw-r--r-- | src/server/repositories/types.ts | 1 | ||||
| -rw-r--r-- | src/server/routes/cards.test.ts | 1 | ||||
| -rw-r--r-- | src/server/routes/decks.test.ts | 59 | ||||
| -rw-r--r-- | src/server/routes/decks.ts | 20 | ||||
| -rw-r--r-- | src/server/routes/study.test.ts | 1 |
7 files changed, 88 insertions, 9 deletions
diff --git a/src/server/repositories/card.test.ts b/src/server/repositories/card.test.ts index 0a46a76..b492fd7 100644 --- a/src/server/repositories/card.test.ts +++ b/src/server/repositories/card.test.ts @@ -111,6 +111,7 @@ function createMockCardRepo(): CardRepository { softDelete: vi.fn(), softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), + countDueCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), diff --git a/src/server/repositories/card.ts b/src/server/repositories/card.ts index 04425a2..ac03bc6 100644 --- a/src/server/repositories/card.ts +++ b/src/server/repositories/card.ts @@ -204,6 +204,20 @@ export const cardRepository: CardRepository = { return result; }, + async countDueCards(deckId: string, now: Date): Promise<number> { + const result = await db + .select({ count: sql<number>`count(*)::int` }) + .from(cards) + .where( + and( + eq(cards.deckId, deckId), + isNull(cards.deletedAt), + lte(cards.due, now), + ), + ); + return result[0]?.count ?? 0; + }, + async findDueCardsWithNoteData( deckId: string, now: Date, diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts index 4768d49..cb3a287 100644 --- a/src/server/repositories/types.ts +++ b/src/server/repositories/types.ts @@ -147,6 +147,7 @@ export interface CardRepository { softDelete(id: string, deckId: string): Promise<boolean>; softDeleteByNoteId(noteId: string): Promise<boolean>; findDueCards(deckId: string, now: Date, limit: number): Promise<Card[]>; + countDueCards(deckId: string, now: Date): Promise<number>; findDueCardsWithNoteData( deckId: string, now: Date, diff --git a/src/server/routes/cards.test.ts b/src/server/routes/cards.test.ts index 66ba601..e5fb0d4 100644 --- a/src/server/routes/cards.test.ts +++ b/src/server/routes/cards.test.ts @@ -25,6 +25,7 @@ function createMockCardRepo(): CardRepository { softDelete: vi.fn(), softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), + countDueCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), diff --git a/src/server/routes/decks.test.ts b/src/server/routes/decks.test.ts index 8f5be9d..55aca2d 100644 --- a/src/server/routes/decks.test.ts +++ b/src/server/routes/decks.test.ts @@ -2,7 +2,11 @@ import { Hono } from "hono"; import { sign } from "hono/jwt"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { errorHandler } from "../middleware/index.js"; -import type { Deck, DeckRepository } from "../repositories/index.js"; +import type { + CardRepository, + Deck, + DeckRepository, +} from "../repositories/index.js"; import { createDecksRouter } from "./decks.js"; function createMockDeckRepo(): DeckRepository { @@ -15,6 +19,24 @@ function createMockDeckRepo(): DeckRepository { }; } +function createMockCardRepo(): CardRepository { + return { + findByDeckId: vi.fn(), + findById: vi.fn(), + findByIdWithNoteData: vi.fn(), + findByNoteId: vi.fn(), + create: vi.fn(), + update: vi.fn(), + softDelete: vi.fn(), + softDeleteByNoteId: vi.fn(), + findDueCards: vi.fn(), + countDueCards: vi.fn().mockResolvedValue(0), + findDueCardsWithNoteData: vi.fn(), + findDueCardsForStudy: vi.fn(), + updateFSRSFields: vi.fn(), + }; +} + const JWT_SECRET = process.env.JWT_SECRET || "test-secret"; async function createTestToken(userId: string): Promise<string> { @@ -57,12 +79,17 @@ interface DeckResponse { describe("GET /api/decks", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; + let mockCardRepo: ReturnType<typeof createMockCardRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); - const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo }); + mockCardRepo = createMockCardRepo(); + const decksRouter = createDecksRouter({ + deckRepo: mockDeckRepo, + cardRepo: mockCardRepo, + }); app = new Hono(); app.onError(errorHandler); app.route("/api/decks", decksRouter); @@ -112,12 +139,17 @@ describe("GET /api/decks", () => { describe("POST /api/decks", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; + let mockCardRepo: ReturnType<typeof createMockCardRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); - const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo }); + mockCardRepo = createMockCardRepo(); + const decksRouter = createDecksRouter({ + deckRepo: mockDeckRepo, + cardRepo: mockCardRepo, + }); app = new Hono(); app.onError(errorHandler); app.route("/api/decks", decksRouter); @@ -220,12 +252,17 @@ describe("POST /api/decks", () => { describe("GET /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; + let mockCardRepo: ReturnType<typeof createMockCardRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); - const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo }); + mockCardRepo = createMockCardRepo(); + const decksRouter = createDecksRouter({ + deckRepo: mockDeckRepo, + cardRepo: mockCardRepo, + }); app = new Hono(); app.onError(errorHandler); app.route("/api/decks", decksRouter); @@ -285,12 +322,17 @@ describe("GET /api/decks/:id", () => { describe("PUT /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; + let mockCardRepo: ReturnType<typeof createMockCardRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); - const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo }); + mockCardRepo = createMockCardRepo(); + const decksRouter = createDecksRouter({ + deckRepo: mockDeckRepo, + cardRepo: mockCardRepo, + }); app = new Hono(); app.onError(errorHandler); app.route("/api/decks", decksRouter); @@ -410,12 +452,17 @@ describe("PUT /api/decks/:id", () => { describe("DELETE /api/decks/:id", () => { let app: Hono; let mockDeckRepo: ReturnType<typeof createMockDeckRepo>; + let mockCardRepo: ReturnType<typeof createMockCardRepo>; let authToken: string; beforeEach(async () => { vi.clearAllMocks(); mockDeckRepo = createMockDeckRepo(); - const decksRouter = createDecksRouter({ deckRepo: mockDeckRepo }); + mockCardRepo = createMockCardRepo(); + const decksRouter = createDecksRouter({ + deckRepo: mockDeckRepo, + cardRepo: mockCardRepo, + }); app = new Hono(); app.onError(errorHandler); app.route("/api/decks", decksRouter); diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts index 2450bcd..2e170db 100644 --- a/src/server/routes/decks.ts +++ b/src/server/routes/decks.ts @@ -2,11 +2,17 @@ import { zValidator } from "@hono/zod-validator"; import { Hono } from "hono"; import { z } from "zod"; import { authMiddleware, Errors, getAuthUser } from "../middleware/index.js"; -import { type DeckRepository, deckRepository } from "../repositories/index.js"; +import { + type CardRepository, + cardRepository, + type DeckRepository, + deckRepository, +} from "../repositories/index.js"; import { createDeckSchema, updateDeckSchema } from "../schemas/index.js"; export interface DeckDependencies { deckRepo: DeckRepository; + cardRepo: CardRepository; } const deckIdParamSchema = z.object({ @@ -14,14 +20,21 @@ const deckIdParamSchema = z.object({ }); export function createDecksRouter(deps: DeckDependencies) { - const { deckRepo } = deps; + const { deckRepo, cardRepo } = deps; return new Hono() .use("*", authMiddleware) .get("/", async (c) => { const user = getAuthUser(c); const decks = await deckRepo.findByUserId(user.id); - return c.json({ decks }, 200); + const now = new Date(); + const decksWithDueCount = await Promise.all( + decks.map(async (deck) => { + const dueCardCount = await cardRepo.countDueCards(deck.id, now); + return { ...deck, dueCardCount }; + }), + ); + return c.json({ decks: decksWithDueCount }, 200); }) .post("/", zValidator("json", createDeckSchema), async (c) => { const user = getAuthUser(c); @@ -79,4 +92,5 @@ export function createDecksRouter(deps: DeckDependencies) { export const decks = createDecksRouter({ deckRepo: deckRepository, + cardRepo: cardRepository, }); diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts index e2fb457..a5ac817 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -25,6 +25,7 @@ function createMockCardRepo(): CardRepository { softDelete: vi.fn(), softDeleteByNoteId: vi.fn(), findDueCards: vi.fn(), + countDueCards: vi.fn(), findDueCardsWithNoteData: vi.fn(), findDueCardsForStudy: vi.fn(), updateFSRSFields: vi.fn(), |
