aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/server
diff options
context:
space:
mode:
authornsfisis <54318333+nsfisis@users.noreply.github.com>2026-02-13 20:23:31 +0900
committerGitHub <noreply@github.com>2026-02-13 20:23:31 +0900
commitce6bbcea37d014b9575299d2d079fda7ccb5628d (patch)
tree1fe3a43f1c7ab469bb0154a1495028cc42b414a0 /src/server
parent9a52e7ad3b2d46c523caf079794fdb7757375b91 (diff)
parent1afb825860cd293b8065d51746f4b23e4e8dab5d (diff)
downloadkioku-ce6bbcea37d014b9575299d2d079fda7ccb5628d.tar.gz
kioku-ce6bbcea37d014b9575299d2d079fda7ccb5628d.tar.zst
kioku-ce6bbcea37d014b9575299d2d079fda7ccb5628d.zip
Merge pull request #15 from nsfisis/claude/remove-card-limit-10fVw
Remove newCardsPerDay limit and simplify card study logic
Diffstat (limited to 'src/server')
-rw-r--r--src/server/db/schema.ts1
-rw-r--r--src/server/repositories/card.test.ts4
-rw-r--r--src/server/repositories/card.ts93
-rw-r--r--src/server/repositories/deck.test.ts5
-rw-r--r--src/server/repositories/deck.ts3
-rw-r--r--src/server/repositories/review-log.ts21
-rw-r--r--src/server/repositories/sync.test.ts1
-rw-r--r--src/server/repositories/sync.ts3
-rw-r--r--src/server/repositories/types.ts25
-rw-r--r--src/server/routes/cards.test.ts5
-rw-r--r--src/server/routes/decks.test.ts53
-rw-r--r--src/server/routes/decks.ts43
-rw-r--r--src/server/routes/notes.test.ts1
-rw-r--r--src/server/routes/study.test.ts109
-rw-r--r--src/server/routes/study.ts15
-rw-r--r--src/server/routes/sync.test.ts9
-rw-r--r--src/server/routes/sync.ts1
-rw-r--r--src/server/schemas/index.ts3
-rw-r--r--src/server/types/index.ts1
19 files changed, 19 insertions, 377 deletions
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts
index 50caf85..55be210 100644
--- a/src/server/db/schema.ts
+++ b/src/server/db/schema.ts
@@ -101,7 +101,6 @@ export const decks = pgTable("decks", {
.references(() => users.id),
name: varchar("name", { length: 255 }).notNull(),
description: text("description"),
- newCardsPerDay: integer("new_cards_per_day").notNull().default(20),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
diff --git a/src/server/repositories/card.test.ts b/src/server/repositories/card.test.ts
index 21e5485..b492fd7 100644
--- a/src/server/repositories/card.test.ts
+++ b/src/server/repositories/card.test.ts
@@ -112,12 +112,8 @@ function createMockCardRepo(): CardRepository {
softDeleteByNoteId: vi.fn(),
findDueCards: vi.fn(),
countDueCards: vi.fn(),
- countDueNewCards: vi.fn(),
- countDueReviewCards: 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 d382f4d..0f1ef79 100644
--- a/src/server/repositories/card.ts
+++ b/src/server/repositories/card.ts
@@ -1,4 +1,4 @@
-import { and, eq, isNull, lt, ne, sql } from "drizzle-orm";
+import { and, eq, isNull, lt, sql } from "drizzle-orm";
import { getEndOfStudyDayBoundary } from "../../shared/date.js";
import { db } from "../db/index.js";
import {
@@ -185,11 +185,7 @@ export const cardRepository: CardRepository = {
return result.length > 0;
},
- async findDueCards(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<Card[]> {
+ async findDueCards(deckId: string, now: Date): Promise<Card[]> {
const boundary = getEndOfStudyDayBoundary(now);
const result = await db
.select()
@@ -201,8 +197,7 @@ export const cardRepository: CardRepository = {
lt(cards.due, boundary),
),
)
- .orderBy(cards.due)
- .limit(limit);
+ .orderBy(cards.due);
return result;
},
@@ -221,44 +216,11 @@ export const cardRepository: CardRepository = {
return result[0]?.count ?? 0;
},
- async countDueNewCards(deckId: string, now: Date): Promise<number> {
- const boundary = getEndOfStudyDayBoundary(now);
- const result = await db
- .select({ count: sql<number>`count(*)::int` })
- .from(cards)
- .where(
- and(
- eq(cards.deckId, deckId),
- isNull(cards.deletedAt),
- lt(cards.due, boundary),
- eq(cards.state, CardState.New),
- ),
- );
- return result[0]?.count ?? 0;
- },
-
- async countDueReviewCards(deckId: string, now: Date): Promise<number> {
- const boundary = getEndOfStudyDayBoundary(now);
- const result = await db
- .select({ count: sql<number>`count(*)::int` })
- .from(cards)
- .where(
- and(
- eq(cards.deckId, deckId),
- isNull(cards.deletedAt),
- lt(cards.due, boundary),
- ne(cards.state, CardState.New),
- ),
- );
- return result[0]?.count ?? 0;
- },
-
async findDueCardsWithNoteData(
deckId: string,
now: Date,
- limit: number,
): Promise<CardWithNoteData[]> {
- const dueCards = await this.findDueCards(deckId, now, limit);
+ const dueCards = await this.findDueCards(deckId, now);
const cardsWithNoteData: CardWithNoteData[] = [];
@@ -292,56 +254,11 @@ export const cardRepository: CardRepository = {
async findDueCardsForStudy(
deckId: string,
now: Date,
- limit: number,
): Promise<CardForStudy[]> {
- const dueCards = await this.findDueCards(deckId, now, limit);
+ const dueCards = await this.findDueCards(deckId, now);
return enrichCardsForStudy(dueCards);
},
- async findDueNewCardsForStudy(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<CardForStudy[]> {
- const boundary = getEndOfStudyDayBoundary(now);
- const result = await db
- .select()
- .from(cards)
- .where(
- and(
- eq(cards.deckId, deckId),
- isNull(cards.deletedAt),
- lt(cards.due, boundary),
- eq(cards.state, CardState.New),
- ),
- )
- .orderBy(cards.due)
- .limit(limit);
- return enrichCardsForStudy(result);
- },
-
- async findDueReviewCardsForStudy(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<CardForStudy[]> {
- const boundary = getEndOfStudyDayBoundary(now);
- const result = await db
- .select()
- .from(cards)
- .where(
- and(
- eq(cards.deckId, deckId),
- isNull(cards.deletedAt),
- lt(cards.due, boundary),
- ne(cards.state, CardState.New),
- ),
- )
- .orderBy(cards.due)
- .limit(limit);
- return enrichCardsForStudy(result);
- },
-
async updateFSRSFields(
id: string,
deckId: string,
diff --git a/src/server/repositories/deck.test.ts b/src/server/repositories/deck.test.ts
index 945f844..ab6e2fc 100644
--- a/src/server/repositories/deck.test.ts
+++ b/src/server/repositories/deck.test.ts
@@ -7,7 +7,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
@@ -35,7 +34,6 @@ describe("DeckRepository mock factory", () => {
expect(deck.userId).toBe("user-uuid-123");
expect(deck.name).toBe("Test Deck");
expect(deck.description).toBeNull();
- expect(deck.newCardsPerDay).toBe(20);
expect(deck.deletedAt).toBeNull();
expect(deck.syncVersion).toBe(0);
});
@@ -45,13 +43,11 @@ describe("DeckRepository mock factory", () => {
id: "custom-id",
name: "Custom Deck",
description: "A description",
- newCardsPerDay: 50,
});
expect(deck.id).toBe("custom-id");
expect(deck.name).toBe("Custom Deck");
expect(deck.description).toBe("A description");
- expect(deck.newCardsPerDay).toBe(50);
});
});
@@ -130,7 +126,6 @@ describe("Deck interface contracts", () => {
expect(deck).toHaveProperty("name");
expect(deck).toHaveProperty("description");
- expect(deck).toHaveProperty("newCardsPerDay");
});
});
diff --git a/src/server/repositories/deck.ts b/src/server/repositories/deck.ts
index 647c5cb..97af5f7 100644
--- a/src/server/repositories/deck.ts
+++ b/src/server/repositories/deck.ts
@@ -31,7 +31,6 @@ export const deckRepository: DeckRepository = {
userId: string;
name: string;
description?: string | null;
- newCardsPerDay?: number;
}): Promise<Deck> {
const [deck] = await db
.insert(decks)
@@ -39,7 +38,6 @@ export const deckRepository: DeckRepository = {
userId: data.userId,
name: data.name,
description: data.description ?? null,
- newCardsPerDay: data.newCardsPerDay ?? 20,
})
.returning();
if (!deck) {
@@ -54,7 +52,6 @@ export const deckRepository: DeckRepository = {
data: {
name?: string;
description?: string | null;
- newCardsPerDay?: number;
},
): Promise<Deck | undefined> {
const result = await db
diff --git a/src/server/repositories/review-log.ts b/src/server/repositories/review-log.ts
index 97488d2..c8950d6 100644
--- a/src/server/repositories/review-log.ts
+++ b/src/server/repositories/review-log.ts
@@ -1,7 +1,5 @@
-import { and, eq, gte, sql } from "drizzle-orm";
-import { getStartOfStudyDayBoundary } from "../../shared/date.js";
import { db } from "../db/index.js";
-import { CardState, cards, reviewLogs } from "../db/schema.js";
+import { reviewLogs } from "../db/schema.js";
import type { ReviewLog, ReviewLogRepository } from "./types.js";
export const reviewLogRepository: ReviewLogRepository = {
@@ -31,21 +29,4 @@ export const reviewLogRepository: ReviewLogRepository = {
}
return reviewLog;
},
-
- async countTodayNewCardReviews(deckId: string, now: Date): Promise<number> {
- const startOfDay = getStartOfStudyDayBoundary(now);
-
- 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/sync.test.ts b/src/server/repositories/sync.test.ts
index ce59cb5..8425839 100644
--- a/src/server/repositories/sync.test.ts
+++ b/src/server/repositories/sync.test.ts
@@ -16,7 +16,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/repositories/sync.ts b/src/server/repositories/sync.ts
index ca4c208..e197d37 100644
--- a/src/server/repositories/sync.ts
+++ b/src/server/repositories/sync.ts
@@ -53,7 +53,6 @@ export interface SyncDeckData {
id: string;
name: string;
description: string | null;
- newCardsPerDay: number;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
@@ -224,7 +223,6 @@ export const syncRepository: SyncRepository = {
userId,
name: deckData.name,
description: deckData.description,
- newCardsPerDay: deckData.newCardsPerDay,
createdAt: new Date(deckData.createdAt),
updatedAt: clientUpdatedAt,
deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) : null,
@@ -248,7 +246,6 @@ export const syncRepository: SyncRepository = {
.set({
name: deckData.name,
description: deckData.description,
- newCardsPerDay: deckData.newCardsPerDay,
updatedAt: clientUpdatedAt,
deletedAt: deckData.deletedAt
? new Date(deckData.deletedAt)
diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts
index 4042daf..71cb811 100644
--- a/src/server/repositories/types.ts
+++ b/src/server/repositories/types.ts
@@ -50,7 +50,6 @@ export interface Deck {
userId: string;
name: string;
description: string | null;
- newCardsPerDay: number;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
@@ -64,7 +63,6 @@ export interface DeckRepository {
userId: string;
name: string;
description?: string | null;
- newCardsPerDay?: number;
}): Promise<Deck>;
update(
id: string,
@@ -72,7 +70,6 @@ export interface DeckRepository {
data: {
name?: string;
description?: string | null;
- newCardsPerDay?: number;
},
): Promise<Deck | undefined>;
softDelete(id: string, userId: string): Promise<boolean>;
@@ -146,30 +143,13 @@ export interface CardRepository {
): Promise<Card | undefined>;
softDelete(id: string, deckId: string): Promise<boolean>;
softDeleteByNoteId(noteId: string): Promise<boolean>;
- findDueCards(deckId: string, now: Date, limit: number): Promise<Card[]>;
+ findDueCards(deckId: string, now: Date): Promise<Card[]>;
countDueCards(deckId: string, now: Date): Promise<number>;
findDueCardsWithNoteData(
deckId: string,
now: Date,
- limit: number,
): Promise<CardWithNoteData[]>;
- findDueCardsForStudy(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<CardForStudy[]>;
- findDueNewCardsForStudy(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<CardForStudy[]>;
- findDueReviewCardsForStudy(
- deckId: string,
- now: Date,
- limit: number,
- ): Promise<CardForStudy[]>;
- countDueNewCards(deckId: string, now: Date): Promise<number>;
- countDueReviewCards(deckId: string, now: Date): Promise<number>;
+ findDueCardsForStudy(deckId: string, now: Date): Promise<CardForStudy[]>;
updateFSRSFields(
id: string,
deckId: string,
@@ -210,7 +190,6 @@ export interface ReviewLogRepository {
elapsedDays: number;
durationMs?: number | null;
}): Promise<ReviewLog>;
- countTodayNewCardReviews(deckId: string, now: Date): Promise<number>;
}
export interface NoteType {
diff --git a/src/server/routes/cards.test.ts b/src/server/routes/cards.test.ts
index 4595e28..a063c95 100644
--- a/src/server/routes/cards.test.ts
+++ b/src/server/routes/cards.test.ts
@@ -26,12 +26,8 @@ function createMockCardRepo(): CardRepository {
softDeleteByNoteId: vi.fn(),
findDueCards: vi.fn(),
countDueCards: vi.fn(),
- countDueNewCards: vi.fn(),
- countDueReviewCards: vi.fn(),
findDueCardsWithNoteData: vi.fn(),
findDueCardsForStudy: vi.fn(),
- findDueNewCardsForStudy: vi.fn(),
- findDueReviewCardsForStudy: vi.fn(),
updateFSRSFields: vi.fn(),
};
}
@@ -66,7 +62,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/decks.test.ts b/src/server/routes/decks.test.ts
index d0854c1..f686024 100644
--- a/src/server/routes/decks.test.ts
+++ b/src/server/routes/decks.test.ts
@@ -6,7 +6,6 @@ import type {
CardRepository,
Deck,
DeckRepository,
- ReviewLogRepository,
} from "../repositories/index.js";
import { createDecksRouter } from "./decks.js";
@@ -32,23 +31,12 @@ function createMockCardRepo(): CardRepository {
softDeleteByNoteId: vi.fn(),
findDueCards: vi.fn(),
countDueCards: vi.fn().mockResolvedValue(0),
- countDueNewCards: vi.fn().mockResolvedValue(0),
- countDueReviewCards: vi.fn().mockResolvedValue(0),
findDueCardsWithNoteData: vi.fn(),
findDueCardsForStudy: vi.fn(),
- findDueNewCardsForStudy: vi.fn(),
- findDueReviewCardsForStudy: vi.fn(),
updateFSRSFields: vi.fn(),
};
}
-function createMockReviewLogRepo(): ReviewLogRepository {
- return {
- create: vi.fn(),
- countTodayNewCardReviews: vi.fn().mockResolvedValue(0),
- };
-}
-
const JWT_SECRET = process.env.JWT_SECRET || "test-secret";
async function createTestToken(userId: string): Promise<string> {
@@ -69,7 +57,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
@@ -92,18 +79,15 @@ describe("GET /api/decks", () => {
let app: Hono;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockDeckRepo = createMockDeckRepo();
mockCardRepo = createMockCardRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
const decksRouter = createDecksRouter({
deckRepo: mockDeckRepo,
cardRepo: mockCardRepo,
- reviewLogRepo: mockReviewLogRepo,
});
app = new Hono();
app.onError(errorHandler);
@@ -155,18 +139,15 @@ describe("POST /api/decks", () => {
let app: Hono;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockDeckRepo = createMockDeckRepo();
mockCardRepo = createMockCardRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
const decksRouter = createDecksRouter({
deckRepo: mockDeckRepo,
cardRepo: mockCardRepo,
- reviewLogRepo: mockReviewLogRepo,
});
app = new Hono();
app.onError(errorHandler);
@@ -194,7 +175,6 @@ describe("POST /api/decks", () => {
userId: "user-uuid-123",
name: "New Deck",
description: undefined,
- newCardsPerDay: 20,
});
});
@@ -202,7 +182,6 @@ describe("POST /api/decks", () => {
const newDeck = createMockDeck({
name: "Full Deck",
description: "Full description",
- newCardsPerDay: 30,
});
vi.mocked(mockDeckRepo.create).mockResolvedValue(newDeck);
@@ -215,7 +194,6 @@ describe("POST /api/decks", () => {
body: JSON.stringify({
name: "Full Deck",
description: "Full description",
- newCardsPerDay: 30,
}),
});
@@ -226,7 +204,6 @@ describe("POST /api/decks", () => {
userId: "user-uuid-123",
name: "Full Deck",
description: "Full description",
- newCardsPerDay: 30,
});
});
@@ -271,18 +248,15 @@ describe("GET /api/decks/:id", () => {
let app: Hono;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockDeckRepo = createMockDeckRepo();
mockCardRepo = createMockCardRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
const decksRouter = createDecksRouter({
deckRepo: mockDeckRepo,
cardRepo: mockCardRepo,
- reviewLogRepo: mockReviewLogRepo,
});
app = new Hono();
app.onError(errorHandler);
@@ -344,18 +318,15 @@ describe("PUT /api/decks/:id", () => {
let app: Hono;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockDeckRepo = createMockDeckRepo();
mockCardRepo = createMockCardRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
const decksRouter = createDecksRouter({
deckRepo: mockDeckRepo,
cardRepo: mockCardRepo,
- reviewLogRepo: mockReviewLogRepo,
});
app = new Hono();
app.onError(errorHandler);
@@ -405,27 +376,6 @@ describe("PUT /api/decks/:id", () => {
expect(body.deck?.description).toBe("New description");
});
- it("updates newCardsPerDay", async () => {
- const updatedDeck = createMockDeck({ newCardsPerDay: 50 });
- vi.mocked(mockDeckRepo.update).mockResolvedValue(updatedDeck);
-
- const res = await app.request(
- "/api/decks/00000000-0000-0000-0000-000000000000",
- {
- method: "PUT",
- headers: {
- Authorization: `Bearer ${authToken}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ newCardsPerDay: 50 }),
- },
- );
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as DeckResponse;
- expect(body.deck?.newCardsPerDay).toBe(50);
- });
-
it("returns 404 for non-existent deck", async () => {
vi.mocked(mockDeckRepo.update).mockResolvedValue(undefined);
@@ -477,18 +427,15 @@ describe("DELETE /api/decks/:id", () => {
let app: Hono;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockDeckRepo = createMockDeckRepo();
mockCardRepo = createMockCardRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
const decksRouter = createDecksRouter({
deckRepo: mockDeckRepo,
cardRepo: mockCardRepo,
- reviewLogRepo: mockReviewLogRepo,
});
app = new Hono();
app.onError(errorHandler);
diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts
index d73aa0c..ed7077e 100644
--- a/src/server/routes/decks.ts
+++ b/src/server/routes/decks.ts
@@ -7,25 +7,20 @@ import {
cardRepository,
type DeckRepository,
deckRepository,
- type ReviewLogRepository,
- reviewLogRepository,
} from "../repositories/index.js";
import { createDeckSchema, updateDeckSchema } from "../schemas/index.js";
export interface DeckDependencies {
deckRepo: DeckRepository;
cardRepo: CardRepository;
- reviewLogRepo: ReviewLogRepository;
}
const deckIdParamSchema = z.object({
id: z.uuid(),
});
-const REVIEW_CARDS_LIMIT = 80;
-
export function createDecksRouter(deps: DeckDependencies) {
- const { deckRepo, cardRepo, reviewLogRepo } = deps;
+ const { deckRepo, cardRepo } = deps;
return new Hono()
.use("*", authMiddleware)
@@ -35,26 +30,7 @@ export function createDecksRouter(deps: DeckDependencies) {
const now = new Date();
const decksWithDueCount = await Promise.all(
decks.map(async (deck) => {
- const [dueNewCards, dueReviewCards, reviewedNewCards] =
- await Promise.all([
- cardRepo.countDueNewCards(deck.id, now),
- cardRepo.countDueReviewCards(deck.id, now),
- reviewLogRepo.countTodayNewCardReviews(deck.id, now),
- ]);
-
- // Apply the same limits as the study screen
- const newCardBudget = Math.max(
- 0,
- deck.newCardsPerDay - reviewedNewCards,
- );
- const newCardsToStudy = Math.min(dueNewCards, newCardBudget);
- const reviewCardsToStudy = Math.min(
- dueReviewCards,
- REVIEW_CARDS_LIMIT,
- );
-
- const dueCardCount = newCardsToStudy + reviewCardsToStudy;
-
+ const dueCardCount = await cardRepo.countDueCards(deck.id, now);
return { ...deck, dueCardCount };
}),
);
@@ -68,7 +44,6 @@ export function createDecksRouter(deps: DeckDependencies) {
userId: user.id,
name: data.name,
description: data.description,
- newCardsPerDay: data.newCardsPerDay,
});
return c.json({ deck }, 201);
@@ -83,18 +58,7 @@ export function createDecksRouter(deps: DeckDependencies) {
}
const now = new Date();
- const [dueNewCards, dueReviewCards, reviewedNewCards] = await Promise.all(
- [
- cardRepo.countDueNewCards(deck.id, now),
- cardRepo.countDueReviewCards(deck.id, now),
- reviewLogRepo.countTodayNewCardReviews(deck.id, now),
- ],
- );
-
- const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards);
- const newCardsToStudy = Math.min(dueNewCards, newCardBudget);
- const reviewCardsToStudy = Math.min(dueReviewCards, REVIEW_CARDS_LIMIT);
- const dueCardCount = newCardsToStudy + reviewCardsToStudy;
+ const dueCardCount = await cardRepo.countDueCards(deck.id, now);
return c.json({ deck: { ...deck, dueCardCount } }, 200);
})
@@ -131,5 +95,4 @@ export function createDecksRouter(deps: DeckDependencies) {
export const decks = createDecksRouter({
deckRepo: deckRepository,
cardRepo: cardRepository,
- reviewLogRepo: reviewLogRepository,
});
diff --git a/src/server/routes/notes.test.ts b/src/server/routes/notes.test.ts
index e354fa6..116a57f 100644
--- a/src/server/routes/notes.test.ts
+++ b/src/server/routes/notes.test.ts
@@ -57,7 +57,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts
index 514d966..119b25d 100644
--- a/src/server/routes/study.test.ts
+++ b/src/server/routes/study.test.ts
@@ -26,12 +26,8 @@ function createMockCardRepo(): CardRepository {
softDeleteByNoteId: vi.fn(),
findDueCards: vi.fn(),
countDueCards: vi.fn(),
- countDueNewCards: vi.fn(),
- countDueReviewCards: vi.fn(),
findDueCardsWithNoteData: vi.fn(),
findDueCardsForStudy: vi.fn(),
- findDueNewCardsForStudy: vi.fn(),
- findDueReviewCardsForStudy: vi.fn(),
updateFSRSFields: vi.fn(),
};
}
@@ -49,7 +45,6 @@ function createMockDeckRepo(): DeckRepository {
function createMockReviewLogRepo(): ReviewLogRepository {
return {
create: vi.fn(),
- countTodayNewCardReviews: vi.fn().mockResolvedValue(0),
};
}
@@ -73,7 +68,6 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
@@ -152,14 +146,13 @@ describe("GET /api/decks/:deckId/study", () => {
let app: Hono;
let mockCardRepo: ReturnType<typeof createMockCardRepo>;
let mockDeckRepo: ReturnType<typeof createMockDeckRepo>;
- let mockReviewLogRepo: ReturnType<typeof createMockReviewLogRepo>;
let authToken: string;
beforeEach(async () => {
vi.clearAllMocks();
mockCardRepo = createMockCardRepo();
mockDeckRepo = createMockDeckRepo();
- mockReviewLogRepo = createMockReviewLogRepo();
+ const mockReviewLogRepo = createMockReviewLogRepo();
const studyRouter = createStudyRouter({
cardRepo: mockCardRepo,
deckRepo: mockDeckRepo,
@@ -175,8 +168,7 @@ describe("GET /api/decks/:deckId/study", () => {
vi.mocked(mockDeckRepo.findById).mockResolvedValue(
createMockDeck({ id: DECK_ID }),
);
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]);
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]);
+ vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue([]);
const res = await app.request(`/api/decks/${DECK_ID}/study`, {
method: "GET",
@@ -190,20 +182,14 @@ describe("GET /api/decks/:deckId/study", () => {
DECK_ID,
"user-uuid-123",
);
- expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith(
- DECK_ID,
- expect.any(Date),
- 20,
- );
- expect(mockCardRepo.findDueReviewCardsForStudy).toHaveBeenCalledWith(
+ expect(mockCardRepo.findDueCardsForStudy).toHaveBeenCalledWith(
DECK_ID,
expect.any(Date),
- 80,
);
});
it("returns due cards", async () => {
- const newCards = [
+ const mockCards = [
createMockCardForStudy({
id: "card-1",
front: "Q1",
@@ -211,8 +197,6 @@ describe("GET /api/decks/:deckId/study", () => {
state: CardState.New,
fieldValuesMap: {},
}),
- ];
- const reviewCards = [
createMockCardForStudy({
id: "card-2",
front: "Q2",
@@ -224,10 +208,7 @@ describe("GET /api/decks/:deckId/study", () => {
vi.mocked(mockDeckRepo.findById).mockResolvedValue(
createMockDeck({ id: DECK_ID }),
);
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue(newCards);
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue(
- reviewCards,
- );
+ vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue(mockCards);
const res = await app.request(`/api/decks/${DECK_ID}/study`, {
method: "GET",
@@ -259,10 +240,7 @@ describe("GET /api/decks/:deckId/study", () => {
vi.mocked(mockDeckRepo.findById).mockResolvedValue(
createMockDeck({ id: DECK_ID }),
);
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue(
- mockCards,
- );
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]);
+ vi.mocked(mockCardRepo.findDueCardsForStudy).mockResolvedValue(mockCards);
const res = await app.request(`/api/decks/${DECK_ID}/study`, {
method: "GET",
@@ -276,81 +254,6 @@ describe("GET /api/decks/:deckId/study", () => {
expect(body.cards?.[0]?.fieldValuesMap?.Front).toBe("Question");
});
- it("limits new cards based on newCardsPerDay", async () => {
- vi.mocked(mockDeckRepo.findById).mockResolvedValue(
- createMockDeck({ id: DECK_ID, newCardsPerDay: 5 }),
- );
- vi.mocked(mockReviewLogRepo.countTodayNewCardReviews).mockResolvedValue(3);
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]);
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]);
-
- await app.request(`/api/decks/${DECK_ID}/study`, {
- method: "GET",
- headers: { Authorization: `Bearer ${authToken}` },
- });
-
- expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith(
- DECK_ID,
- expect.any(Date),
- 2,
- );
- });
-
- it("returns 0 new cards when daily limit is reached", async () => {
- vi.mocked(mockDeckRepo.findById).mockResolvedValue(
- createMockDeck({ id: DECK_ID, newCardsPerDay: 5 }),
- );
- vi.mocked(mockReviewLogRepo.countTodayNewCardReviews).mockResolvedValue(5);
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue([]);
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue([]);
-
- await app.request(`/api/decks/${DECK_ID}/study`, {
- method: "GET",
- headers: { Authorization: `Bearer ${authToken}` },
- });
-
- expect(mockCardRepo.findDueNewCardsForStudy).toHaveBeenCalledWith(
- DECK_ID,
- expect.any(Date),
- 0,
- );
- });
-
- it("places new cards before review cards in response", async () => {
- const newCards = [
- createMockCardForStudy({
- id: "new-1",
- state: CardState.New,
- fieldValuesMap: {},
- }),
- ];
- const reviewCards = [
- createMockCardForStudy({
- id: "review-1",
- state: CardState.Review,
- fieldValuesMap: {},
- }),
- ];
- vi.mocked(mockDeckRepo.findById).mockResolvedValue(
- createMockDeck({ id: DECK_ID }),
- );
- vi.mocked(mockCardRepo.findDueNewCardsForStudy).mockResolvedValue(newCards);
- vi.mocked(mockCardRepo.findDueReviewCardsForStudy).mockResolvedValue(
- reviewCards,
- );
-
- const res = await app.request(`/api/decks/${DECK_ID}/study`, {
- method: "GET",
- headers: { Authorization: `Bearer ${authToken}` },
- });
-
- expect(res.status).toBe(200);
- const body = (await res.json()) as StudyResponse;
- expect(body.cards).toHaveLength(2);
- expect(body.cards?.[0]?.id).toBe("new-1");
- expect(body.cards?.[1]?.id).toBe("review-1");
- });
-
it("returns 404 for non-existent deck", async () => {
vi.mocked(mockDeckRepo.findById).mockResolvedValue(undefined);
diff --git a/src/server/routes/study.ts b/src/server/routes/study.ts
index d05f3ca..0f42f93 100644
--- a/src/server/routes/study.ts
+++ b/src/server/routes/study.ts
@@ -52,20 +52,9 @@ export function createStudyRouter(deps: StudyDependencies) {
const now = new Date();
- // Calculate new card budget based on today's already-reviewed new cards
- const reviewedNewCards = await reviewLogRepo.countTodayNewCardReviews(
- deckId,
- now,
- );
- const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards);
+ const cards = await cardRepo.findDueCardsForStudy(deckId, now);
- // Fetch new cards (limited) and review cards separately
- const [newCards, reviewCards] = await Promise.all([
- cardRepo.findDueNewCardsForStudy(deckId, now, newCardBudget),
- cardRepo.findDueReviewCardsForStudy(deckId, now, 80),
- ]);
-
- return c.json({ cards: [...newCards, ...reviewCards] }, 200);
+ return c.json({ cards }, 200);
})
.post(
"/:cardId",
diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts
index 8ea2ce3..4c0d8d8 100644
--- a/src/server/routes/sync.test.ts
+++ b/src/server/routes/sync.test.ts
@@ -143,7 +143,6 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Test Deck",
description: "A test deck",
- newCardsPerDay: 20,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: null,
@@ -302,7 +301,6 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440003",
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: null,
@@ -349,7 +347,6 @@ describe("POST /api/sync/push", () => {
id: "not-a-uuid",
name: "",
description: null,
- newCardsPerDay: -1,
createdAt: "invalid-date",
updatedAt: "2024-01-01T00:00:00.000Z",
deletedAt: null,
@@ -432,7 +429,6 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440004",
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
deletedAt: null,
@@ -634,7 +630,6 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440007",
name: "Deleted Deck",
description: null,
- newCardsPerDay: 20,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: "2024-01-02T00:00:00.000Z",
@@ -830,7 +825,6 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: null,
@@ -906,7 +900,6 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: "A test description",
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: null,
@@ -1041,7 +1034,6 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: null,
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-01T00:00:00.000Z"),
deletedAt: null,
@@ -1215,7 +1207,6 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Deleted Deck",
description: null,
- newCardsPerDay: 20,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: new Date("2024-01-02T00:00:00.000Z"),
diff --git a/src/server/routes/sync.ts b/src/server/routes/sync.ts
index c571f8a..a9ea3b3 100644
--- a/src/server/routes/sync.ts
+++ b/src/server/routes/sync.ts
@@ -18,7 +18,6 @@ const syncDeckSchema = z.object({
id: z.uuid(),
name: z.string().min(1).max(255),
description: z.string().nullable(),
- newCardsPerDay: z.number().int().min(0).max(1000),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
deletedAt: z.string().datetime().nullable(),
diff --git a/src/server/schemas/index.ts b/src/server/schemas/index.ts
index fc1bd77..aa9ceea 100644
--- a/src/server/schemas/index.ts
+++ b/src/server/schemas/index.ts
@@ -48,7 +48,6 @@ export const deckSchema = z.object({
userId: z.uuid(),
name: z.string().min(1).max(255),
description: z.string().max(1000).nullable(),
- newCardsPerDay: z.number().int().min(0).default(20),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
deletedAt: z.coerce.date().nullable(),
@@ -59,14 +58,12 @@ export const deckSchema = z.object({
export const createDeckSchema = z.object({
name: z.string().min(1).max(255),
description: z.string().max(1000).nullable().optional(),
- newCardsPerDay: z.number().int().min(0).default(20),
});
// Deck update input schema
export const updateDeckSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(1000).nullable().optional(),
- newCardsPerDay: z.number().int().min(0).optional(),
});
// Card schema
diff --git a/src/server/types/index.ts b/src/server/types/index.ts
index bfba06f..08f8379 100644
--- a/src/server/types/index.ts
+++ b/src/server/types/index.ts
@@ -33,7 +33,6 @@ export interface Deck {
userId: string;
name: string;
description: string | null;
- newCardsPerDay: number;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;