From 38b8fc0e9927c4146b4c8b309b2bcc644abd63d0 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Wed, 25 Feb 2026 23:02:35 +0900 Subject: feat(decks): add default note type setting per deck Allow each deck to specify a default note type that is auto-selected when creating new notes. Includes DB schema migration, server API updates, sync layer support, and UI for editing the default in the deck settings modal. Co-Authored-By: Claude Opus 4.6 --- src/server/db/schema.ts | 3 +++ src/server/repositories/deck.test.ts | 1 + src/server/repositories/deck.ts | 3 +++ src/server/repositories/sync.test.ts | 1 + src/server/repositories/sync.ts | 3 +++ src/server/repositories/types.ts | 3 +++ src/server/routes/cards.test.ts | 1 + src/server/routes/decks.test.ts | 1 + src/server/routes/decks.ts | 1 + src/server/routes/notes.test.ts | 1 + src/server/routes/study.test.ts | 1 + src/server/routes/sync.test.ts | 8 ++++++++ src/server/routes/sync.ts | 1 + src/server/schemas/index.ts | 3 +++ 14 files changed, 31 insertions(+) (limited to 'src/server') diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 55be210..24c4a78 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -101,6 +101,9 @@ export const decks = pgTable("decks", { .references(() => users.id), name: varchar("name", { length: 255 }).notNull(), description: text("description"), + defaultNoteTypeId: uuid("default_note_type_id").references( + () => noteTypes.id, + ), createdAt: timestamp("created_at", { withTimezone: true }) .notNull() .defaultNow(), diff --git a/src/server/repositories/deck.test.ts b/src/server/repositories/deck.test.ts index ab6e2fc..93fdce7 100644 --- a/src/server/repositories/deck.test.ts +++ b/src/server/repositories/deck.test.ts @@ -7,6 +7,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/repositories/deck.ts b/src/server/repositories/deck.ts index 97af5f7..66dd9c5 100644 --- a/src/server/repositories/deck.ts +++ b/src/server/repositories/deck.ts @@ -31,6 +31,7 @@ export const deckRepository: DeckRepository = { userId: string; name: string; description?: string | null; + defaultNoteTypeId?: string | null; }): Promise { const [deck] = await db .insert(decks) @@ -38,6 +39,7 @@ export const deckRepository: DeckRepository = { userId: data.userId, name: data.name, description: data.description ?? null, + defaultNoteTypeId: data.defaultNoteTypeId ?? null, }) .returning(); if (!deck) { @@ -52,6 +54,7 @@ export const deckRepository: DeckRepository = { data: { name?: string; description?: string | null; + defaultNoteTypeId?: string | null; }, ): Promise { const result = await db diff --git a/src/server/repositories/sync.test.ts b/src/server/repositories/sync.test.ts index 8425839..6f02440 100644 --- a/src/server/repositories/sync.test.ts +++ b/src/server/repositories/sync.test.ts @@ -16,6 +16,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: null, + defaultNoteTypeId: null, 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 e197d37..d321de4 100644 --- a/src/server/repositories/sync.ts +++ b/src/server/repositories/sync.ts @@ -53,6 +53,7 @@ export interface SyncDeckData { id: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: string; updatedAt: string; deletedAt: string | null; @@ -223,6 +224,7 @@ export const syncRepository: SyncRepository = { userId, name: deckData.name, description: deckData.description, + defaultNoteTypeId: deckData.defaultNoteTypeId, createdAt: new Date(deckData.createdAt), updatedAt: clientUpdatedAt, deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) : null, @@ -246,6 +248,7 @@ export const syncRepository: SyncRepository = { .set({ name: deckData.name, description: deckData.description, + defaultNoteTypeId: deckData.defaultNoteTypeId, updatedAt: clientUpdatedAt, deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts index 7e0819a..eb9141a 100644 --- a/src/server/repositories/types.ts +++ b/src/server/repositories/types.ts @@ -50,6 +50,7 @@ export interface Deck { userId: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: Date; updatedAt: Date; deletedAt: Date | null; @@ -63,6 +64,7 @@ export interface DeckRepository { userId: string; name: string; description?: string | null; + defaultNoteTypeId?: string | null; }): Promise; update( id: string, @@ -70,6 +72,7 @@ export interface DeckRepository { data: { name?: string; description?: string | null; + defaultNoteTypeId?: string | null; }, ): Promise; softDelete(id: string, userId: string): Promise; diff --git a/src/server/routes/cards.test.ts b/src/server/routes/cards.test.ts index 57df729..2032b0a 100644 --- a/src/server/routes/cards.test.ts +++ b/src/server/routes/cards.test.ts @@ -65,6 +65,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", + defaultNoteTypeId: null, 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 009c8a5..c92cd99 100644 --- a/src/server/routes/decks.test.ts +++ b/src/server/routes/decks.test.ts @@ -60,6 +60,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts index 069b933..06b4eb7 100644 --- a/src/server/routes/decks.ts +++ b/src/server/routes/decks.ts @@ -56,6 +56,7 @@ export function createDecksRouter(deps: DeckDependencies) { userId: user.id, name: data.name, description: data.description, + defaultNoteTypeId: data.defaultNoteTypeId, }); return c.json({ deck }, 201); diff --git a/src/server/routes/notes.test.ts b/src/server/routes/notes.test.ts index 116a57f..d7cba49 100644 --- a/src/server/routes/notes.test.ts +++ b/src/server/routes/notes.test.ts @@ -57,6 +57,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", + defaultNoteTypeId: null, 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 0f2fbe7..65702ff 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -71,6 +71,7 @@ function createMockDeck(overrides: Partial = {}): Deck { userId: "user-uuid-123", name: "Test Deck", description: "Test description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts index 4c0d8d8..7458475 100644 --- a/src/server/routes/sync.test.ts +++ b/src/server/routes/sync.test.ts @@ -143,6 +143,7 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440000", name: "Test Deck", description: "A test deck", + defaultNoteTypeId: null, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: null, @@ -301,6 +302,7 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440003", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: null, @@ -429,6 +431,7 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440004", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-01T00:00:00.000Z", deletedAt: null, @@ -630,6 +633,7 @@ describe("POST /api/sync/push", () => { id: "550e8400-e29b-41d4-a716-446655440007", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, createdAt: "2024-01-01T00:00:00.000Z", updatedAt: "2024-01-02T00:00:00.000Z", deletedAt: "2024-01-02T00:00:00.000Z", @@ -825,6 +829,7 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-02T00:00:00.000Z"), deletedAt: null, @@ -900,6 +905,7 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: "A test description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-02T00:00:00.000Z"), deletedAt: null, @@ -1034,6 +1040,7 @@ describe("GET /api/sync/pull", () => { userId, name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-01T00:00:00.000Z"), deletedAt: null, @@ -1207,6 +1214,7 @@ describe("GET /api/sync/pull", () => { userId, name: "Deleted Deck", description: null, + defaultNoteTypeId: null, 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 a9ea3b3..0c74c22 100644 --- a/src/server/routes/sync.ts +++ b/src/server/routes/sync.ts @@ -18,6 +18,7 @@ const syncDeckSchema = z.object({ id: z.uuid(), name: z.string().min(1).max(255), description: z.string().nullable(), + defaultNoteTypeId: z.uuid().nullable(), 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 aa9ceea..42328f2 100644 --- a/src/server/schemas/index.ts +++ b/src/server/schemas/index.ts @@ -48,6 +48,7 @@ export const deckSchema = z.object({ userId: z.uuid(), name: z.string().min(1).max(255), description: z.string().max(1000).nullable(), + defaultNoteTypeId: z.uuid().nullable(), createdAt: z.coerce.date(), updatedAt: z.coerce.date(), deletedAt: z.coerce.date().nullable(), @@ -58,12 +59,14 @@ export const deckSchema = z.object({ export const createDeckSchema = z.object({ name: z.string().min(1).max(255), description: z.string().max(1000).nullable().optional(), + defaultNoteTypeId: z.uuid().nullable().optional(), }); // Deck update input schema export const updateDeckSchema = z.object({ name: z.string().min(1).max(255).optional(), description: z.string().max(1000).nullable().optional(), + defaultNoteTypeId: z.uuid().nullable().optional(), }); // Card schema -- cgit v1.3-1-g0d28