diff options
44 files changed, 1296 insertions, 21 deletions
diff --git a/drizzle/0005_certain_stick.sql b/drizzle/0005_certain_stick.sql new file mode 100644 index 0000000..379ee7c --- /dev/null +++ b/drizzle/0005_certain_stick.sql @@ -0,0 +1,3 @@ +ALTER TABLE "decks" ADD COLUMN "default_note_type_id" uuid;--> statement-breakpoint +ALTER TABLE "decks" ADD CONSTRAINT "decks_default_note_type_id_note_types_id_fk" FOREIGN KEY ("default_note_type_id") REFERENCES "public"."note_types"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "decks" DROP COLUMN "new_cards_per_day";
\ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..489a3c3 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,992 @@ +{ + "id": "dd73ed21-7aea-4a0a-a0bf-04bb8076e635", + "prevId": "4978370b-924e-40b1-9fd4-770994e1a90a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.cards": { + "name": "cards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deck_id": { + "name": "deck_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "note_id": { + "name": "note_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_reversed": { + "name": "is_reversed", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "front": { + "name": "front", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "back": { + "name": "back", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "due": { + "name": "due", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "stability": { + "name": "stability", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "difficulty": { + "name": "difficulty", + "type": "real", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "elapsed_days": { + "name": "elapsed_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "scheduled_days": { + "name": "scheduled_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reps": { + "name": "reps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lapses": { + "name": "lapses", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_review": { + "name": "last_review", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "cards_deck_id_decks_id_fk": { + "name": "cards_deck_id_decks_id_fk", + "tableFrom": "cards", + "tableTo": "decks", + "columnsFrom": [ + "deck_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cards_note_id_notes_id_fk": { + "name": "cards_note_id_notes_id_fk", + "tableFrom": "cards", + "tableTo": "notes", + "columnsFrom": [ + "note_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.decks": { + "name": "decks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_note_type_id": { + "name": "default_note_type_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "decks_user_id_users_id_fk": { + "name": "decks_user_id_users_id_fk", + "tableFrom": "decks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "decks_default_note_type_id_note_types_id_fk": { + "name": "decks_default_note_type_id_note_types_id_fk", + "tableFrom": "decks", + "tableTo": "note_types", + "columnsFrom": [ + "default_note_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_field_types": { + "name": "note_field_types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "note_type_id": { + "name": "note_type_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "note_field_types_note_type_id_note_types_id_fk": { + "name": "note_field_types_note_type_id_note_types_id_fk", + "tableFrom": "note_field_types", + "tableTo": "note_types", + "columnsFrom": [ + "note_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_field_values": { + "name": "note_field_values", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "note_id": { + "name": "note_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "note_field_type_id": { + "name": "note_field_type_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "note_field_values_note_id_notes_id_fk": { + "name": "note_field_values_note_id_notes_id_fk", + "tableFrom": "note_field_values", + "tableTo": "notes", + "columnsFrom": [ + "note_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "note_field_values_note_field_type_id_note_field_types_id_fk": { + "name": "note_field_values_note_field_type_id_note_field_types_id_fk", + "tableFrom": "note_field_values", + "tableTo": "note_field_types", + "columnsFrom": [ + "note_field_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.note_types": { + "name": "note_types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "front_template": { + "name": "front_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "back_template": { + "name": "back_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_reversible": { + "name": "is_reversible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "note_types_user_id_users_id_fk": { + "name": "note_types_user_id_users_id_fk", + "tableFrom": "note_types", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notes": { + "name": "notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deck_id": { + "name": "deck_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "note_type_id": { + "name": "note_type_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "notes_deck_id_decks_id_fk": { + "name": "notes_deck_id_decks_id_fk", + "tableFrom": "notes", + "tableTo": "decks", + "columnsFrom": [ + "deck_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notes_note_type_id_note_types_id_fk": { + "name": "notes_note_type_id_note_types_id_fk", + "tableFrom": "notes", + "tableTo": "note_types", + "columnsFrom": [ + "note_type_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.refresh_tokens": { + "name": "refresh_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "refresh_tokens_user_id_users_id_fk": { + "name": "refresh_tokens_user_id_users_id_fk", + "tableFrom": "refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.review_logs": { + "name": "review_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "card_id": { + "name": "card_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "rating": { + "name": "rating", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "scheduled_days": { + "name": "scheduled_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "elapsed_days": { + "name": "elapsed_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reviewed_at": { + "name": "reviewed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "review_logs_card_id_cards_id_fk": { + "name": "review_logs_card_id_cards_id_fk", + "tableFrom": "review_logs", + "tableTo": "cards", + "columnsFrom": [ + "card_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "review_logs_user_id_users_id_fk": { + "name": "review_logs_user_id_users_id_fk", + "tableFrom": "review_logs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "username": { + "name": "username", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.crdt_documents": { + "name": "crdt_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "binary": { + "name": "binary", + "type": "varchar(1048576)", + "primaryKey": false, + "notNull": true + }, + "sync_version": { + "name": "sync_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "crdt_documents_user_entity_idx": { + "name": "crdt_documents_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "crdt_documents_entity_type_idx": { + "name": "crdt_documents_entity_type_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "crdt_documents_sync_version_idx": { + "name": "crdt_documents_sync_version_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sync_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "crdt_documents_user_id_users_id_fk": { + "name": "crdt_documents_user_id_users_id_fk", + "tableFrom": "crdt_documents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +}
\ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index fe8279e..9d2626d 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1767164715079, "tag": "0004_clean_leopardon", "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1772025984247, + "tag": "0005_certain_stick", + "breakpoints": true } ] }
\ No newline at end of file diff --git a/src/client/atoms/decks.ts b/src/client/atoms/decks.ts index 8c8397f..0d20586 100644 --- a/src/client/atoms/decks.ts +++ b/src/client/atoms/decks.ts @@ -6,6 +6,7 @@ export interface Deck { id: string; name: string; description: string | null; + defaultNoteTypeId: string | null; dueCardCount: number; newCardCount: number; totalCardCount: number; diff --git a/src/client/components/CreateNoteModal.tsx b/src/client/components/CreateNoteModal.tsx index 912aea8..cc39bf6 100644 --- a/src/client/components/CreateNoteModal.tsx +++ b/src/client/components/CreateNoteModal.tsx @@ -27,6 +27,7 @@ interface NoteTypeSummary { interface CreateNoteModalProps { isOpen: boolean; deckId: string; + defaultNoteTypeId?: string | null; onClose: () => void; onNoteCreated: () => void; } @@ -34,6 +35,7 @@ interface CreateNoteModalProps { export function CreateNoteModal({ isOpen, deckId, + defaultNoteTypeId, onClose, onNoteCreated, }: CreateNoteModalProps) { @@ -88,10 +90,13 @@ export function CreateNoteModal({ setNoteTypes(data.noteTypes); setHasLoadedNoteTypes(true); - // Auto-select first note type if available - const firstNoteType = data.noteTypes[0]; - if (firstNoteType) { - await fetchNoteTypeDetails(firstNoteType.id); + // Auto-select default note type if specified, otherwise first + const targetNoteType = + (defaultNoteTypeId && + data.noteTypes.find((nt) => nt.id === defaultNoteTypeId)) || + data.noteTypes[0]; + if (targetNoteType) { + await fetchNoteTypeDetails(targetNoteType.id); } } catch (err) { if (err instanceof ApiClientError) { @@ -102,7 +107,7 @@ export function CreateNoteModal({ } finally { setIsLoadingNoteTypes(false); } - }, [fetchNoteTypeDetails]); + }, [fetchNoteTypeDetails, defaultNoteTypeId]); useEffect(() => { if (isOpen && !hasLoadedNoteTypes) { diff --git a/src/client/components/EditDeckModal.test.tsx b/src/client/components/EditDeckModal.test.tsx index b22cb1d..248c74f 100644 --- a/src/client/components/EditDeckModal.test.tsx +++ b/src/client/components/EditDeckModal.test.tsx @@ -8,6 +8,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; const mockPut = vi.fn(); const mockHandleResponse = vi.fn(); +const mockGetNoteTypes = vi.fn(); + vi.mock("../api/client", () => ({ apiClient: { rpc: { @@ -17,6 +19,9 @@ vi.mock("../api/client", () => ({ $put: (args: unknown) => mockPut(args), }, }, + "note-types": { + $get: () => mockGetNoteTypes(), + }, }, }, handleResponse: (res: unknown) => mockHandleResponse(res), @@ -42,6 +47,7 @@ describe("EditDeckModal", () => { id: "deck-123", name: "Test Deck", description: "Test description", + defaultNoteTypeId: null as string | null, }; const defaultProps = { @@ -51,15 +57,25 @@ describe("EditDeckModal", () => { onDeckUpdated: vi.fn(), }; + const noteTypesResponse = { ok: true, _type: "noteTypes" }; + const putResponse = { ok: true, _type: "put" }; + beforeEach(() => { vi.clearAllMocks(); - mockPut.mockResolvedValue({ ok: true }); - mockHandleResponse.mockResolvedValue({ - deck: { - id: "deck-123", - name: "Test Deck", - description: "Test description", - }, + mockPut.mockResolvedValue(putResponse); + mockGetNoteTypes.mockResolvedValue(noteTypesResponse); + mockHandleResponse.mockImplementation((res: unknown) => { + if (res === noteTypesResponse) { + return Promise.resolve({ noteTypes: [] }); + } + return Promise.resolve({ + deck: { + id: "deck-123", + name: "Test Deck", + description: "Test description", + defaultNoteTypeId: null, + }, + }); }); }); @@ -187,6 +203,7 @@ describe("EditDeckModal", () => { json: { name: "Updated Deck", description: "Test description", + defaultNoteTypeId: null, }, }); }); @@ -220,6 +237,7 @@ describe("EditDeckModal", () => { json: { name: "Test Deck", description: "New description", + defaultNoteTypeId: null, }, }); }); @@ -243,6 +261,7 @@ describe("EditDeckModal", () => { json: { name: "Test Deck", description: null, + defaultNoteTypeId: null, }, }); }); @@ -266,6 +285,7 @@ describe("EditDeckModal", () => { json: { name: "Deck", description: "Description", + defaultNoteTypeId: null, }, }); }); @@ -299,12 +319,16 @@ describe("EditDeckModal", () => { it("displays API error message", async () => { const user = userEvent.setup(); + render(<EditDeckModal {...defaultProps} />); + + // Wait for note types to load, then override handleResponse for the PUT + await waitFor(() => { + expect(mockGetNoteTypes).toHaveBeenCalled(); + }); mockHandleResponse.mockRejectedValue( new ApiClientError("Deck name already exists", 400), ); - render(<EditDeckModal {...defaultProps} />); - await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { @@ -333,12 +357,16 @@ describe("EditDeckModal", () => { it("displays error when handleResponse throws", async () => { const user = userEvent.setup(); + render(<EditDeckModal {...defaultProps} />); + + // Wait for note types to load, then override handleResponse for the PUT + await waitFor(() => { + expect(mockGetNoteTypes).toHaveBeenCalled(); + }); mockHandleResponse.mockRejectedValue( new ApiClientError("Not authenticated", 401), ); - render(<EditDeckModal {...defaultProps} />); - await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { @@ -374,12 +402,16 @@ describe("EditDeckModal", () => { const user = userEvent.setup(); const onClose = vi.fn(); - mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400)); - const { rerender } = render( <EditDeckModal {...defaultProps} onClose={onClose} />, ); + // Wait for note types to load, then override handleResponse for the PUT + await waitFor(() => { + expect(mockGetNoteTypes).toHaveBeenCalled(); + }); + mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400)); + // Trigger error await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { diff --git a/src/client/components/EditDeckModal.tsx b/src/client/components/EditDeckModal.tsx index 8e95295..9a79de8 100644 --- a/src/client/components/EditDeckModal.tsx +++ b/src/client/components/EditDeckModal.tsx @@ -1,10 +1,16 @@ -import { type FormEvent, useEffect, useState } from "react"; +import { type FormEvent, useCallback, useEffect, useState } from "react"; import { ApiClientError, apiClient } from "../api"; interface Deck { id: string; name: string; description: string | null; + defaultNoteTypeId: string | null; +} + +interface NoteTypeSummary { + id: string; + name: string; } interface EditDeckModalProps { @@ -22,18 +28,45 @@ export function EditDeckModal({ }: EditDeckModalProps) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); + const [defaultNoteTypeId, setDefaultNoteTypeId] = useState<string | null>( + null, + ); + const [noteTypes, setNoteTypes] = useState<NoteTypeSummary[]>([]); + const [isLoadingNoteTypes, setIsLoadingNoteTypes] = useState(false); const [error, setError] = useState<string | null>(null); const [isSubmitting, setIsSubmitting] = useState(false); + const fetchNoteTypes = useCallback(async () => { + setIsLoadingNoteTypes(true); + try { + const res = await apiClient.rpc.api["note-types"].$get(); + const data = await apiClient.handleResponse<{ + noteTypes: NoteTypeSummary[]; + }>(res); + setNoteTypes(data.noteTypes); + } catch { + // Non-critical: note type list is optional + } finally { + setIsLoadingNoteTypes(false); + } + }, []); + // Sync form state when deck changes useEffect(() => { if (deck) { setName(deck.name); setDescription(deck.description ?? ""); + setDefaultNoteTypeId(deck.defaultNoteTypeId); setError(null); } }, [deck]); + useEffect(() => { + if (isOpen) { + fetchNoteTypes(); + } + }, [isOpen, fetchNoteTypes]); + const handleClose = () => { setError(null); onClose(); @@ -52,6 +85,7 @@ export function EditDeckModal({ json: { name: name.trim(), description: description.trim() || null, + defaultNoteTypeId: defaultNoteTypeId || null, }, }); await apiClient.handleResponse(res); @@ -147,6 +181,30 @@ export function EditDeckModal({ /> </div> + <div> + <label + htmlFor="edit-deck-default-note-type" + className="block text-sm font-medium text-slate mb-1.5" + > + Default Note Type{" "} + <span className="text-muted font-normal">(optional)</span> + </label> + <select + id="edit-deck-default-note-type" + value={defaultNoteTypeId ?? ""} + onChange={(e) => setDefaultNoteTypeId(e.target.value || null)} + disabled={isSubmitting || isLoadingNoteTypes} + className="w-full px-4 py-2.5 bg-ivory border border-border rounded-lg text-slate transition-all duration-200 hover:border-muted focus:border-primary focus:ring-2 focus:ring-primary/10 disabled:opacity-50 disabled:cursor-not-allowed" + > + <option value="">None</option> + {noteTypes.map((nt) => ( + <option key={nt.id} value={nt.id}> + {nt.name} + </option> + ))} + </select> + </div> + <div className="flex gap-3 justify-end pt-2"> <button type="button" diff --git a/src/client/db/index.test.ts b/src/client/db/index.test.ts index 0dd3758..32011b5 100644 --- a/src/client/db/index.test.ts +++ b/src/client/db/index.test.ts @@ -64,6 +64,7 @@ describe("KiokuDatabase", () => { userId: "user-1", name: "Test Deck", description: "A test deck", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), deletedAt: null, diff --git a/src/client/db/index.ts b/src/client/db/index.ts index 50d8bbd..a3fbd49 100644 --- a/src/client/db/index.ts +++ b/src/client/db/index.ts @@ -77,6 +77,7 @@ export interface LocalDeck { userId: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: Date; updatedAt: Date; deletedAt: Date | null; @@ -251,6 +252,17 @@ export class KiokuDatabase extends Dexie { notes: "id, deckId, noteTypeId, updatedAt", noteFieldValues: "id, noteId, noteFieldTypeId, updatedAt", }); + + // Version 4: Add defaultNoteTypeId to decks (no index change needed) + this.version(4).stores({ + decks: "id, userId, updatedAt", + cards: "id, deckId, noteId, updatedAt, due, state", + reviewLogs: "id, cardId, userId, reviewedAt", + noteTypes: "id, userId, updatedAt", + noteFieldTypes: "id, noteTypeId, updatedAt", + notes: "id, deckId, noteTypeId, updatedAt", + noteFieldValues: "id, noteId, noteFieldTypeId, updatedAt", + }); } } diff --git a/src/client/db/repositories.test.ts b/src/client/db/repositories.test.ts index b461990..6a77760 100644 --- a/src/client/db/repositories.test.ts +++ b/src/client/db/repositories.test.ts @@ -33,6 +33,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Test Deck", description: "A test deck", + defaultNoteTypeId: null, }); expect(deck.id).toBeDefined(); @@ -51,6 +52,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const found = await db.decks.get(created.id); @@ -64,6 +66,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const found = await localDeckRepository.findById(created.id); @@ -82,16 +85,19 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Deck 1", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.create({ userId: "user-1", name: "Deck 2", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.create({ userId: "user-2", name: "Other User Deck", description: null, + defaultNoteTypeId: null, }); const decks = await localDeckRepository.findByUserId("user-1"); @@ -104,6 +110,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.delete(deck.id); @@ -118,6 +125,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Original Name", description: null, + defaultNoteTypeId: null, }); const updated = await localDeckRepository.update(deck.id, { @@ -147,6 +155,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const result = await localDeckRepository.delete(deck.id); @@ -169,11 +178,13 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Unsynced", description: null, + defaultNoteTypeId: null, }); const deck2 = await localDeckRepository.create({ userId: "user-1", name: "Synced", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck2.id, 1); @@ -189,6 +200,7 @@ describe("localDeckRepository", () => { userId: "user-1", name: "Test", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 5); @@ -212,6 +224,7 @@ describe("localCardRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); deckId = deck.id; }); @@ -411,6 +424,7 @@ describe("localReviewLogRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); deckId = deck.id; @@ -935,6 +949,7 @@ describe("localNoteRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); deckId = deck.id; @@ -1094,6 +1109,7 @@ describe("localNoteFieldValueRepository", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const noteType = await localNoteTypeRepository.create({ diff --git a/src/client/pages/DeckCardsPage.test.tsx b/src/client/pages/DeckCardsPage.test.tsx index 58107d4..6e8f444 100644 --- a/src/client/pages/DeckCardsPage.test.tsx +++ b/src/client/pages/DeckCardsPage.test.tsx @@ -73,6 +73,7 @@ const mockDeck = { id: "deck-1", name: "Japanese Vocabulary", description: "Common Japanese words", + defaultNoteTypeId: null, dueCardCount: 0, newCardCount: 0, totalCardCount: 0, diff --git a/src/client/pages/DeckCardsPage.tsx b/src/client/pages/DeckCardsPage.tsx index 8c839da..b791eac 100644 --- a/src/client/pages/DeckCardsPage.tsx +++ b/src/client/pages/DeckCardsPage.tsx @@ -22,7 +22,12 @@ import { } from "react"; import { useDebouncedCallback } from "use-debounce"; import { Link, useParams } from "wouter"; -import { type Card, cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms"; +import { + type Card, + cardsByDeckAtomFamily, + type Deck, + deckByIdAtomFamily, +} from "../atoms"; import { CreateNoteModal } from "../components/CreateNoteModal"; import { DeleteCardModal } from "../components/DeleteCardModal"; import { DeleteNoteModal } from "../components/DeleteNoteModal"; @@ -638,6 +643,9 @@ export function DeckCardsPage() { <CreateNoteModal isOpen={isCreateModalOpen} deckId={deckId} + defaultNoteTypeId={ + queryClient.getQueryData<Deck>(["decks", deckId])?.defaultNoteTypeId + } onClose={() => setIsCreateModalOpen(false)} onNoteCreated={handleCardMutation} /> diff --git a/src/client/pages/DeckDetailPage.test.tsx b/src/client/pages/DeckDetailPage.test.tsx index bb9d80c..c473275 100644 --- a/src/client/pages/DeckDetailPage.test.tsx +++ b/src/client/pages/DeckDetailPage.test.tsx @@ -60,6 +60,7 @@ const mockDeck = { id: "deck-1", name: "Japanese Vocabulary", description: "Common Japanese words", + defaultNoteTypeId: null, dueCardCount: 0, newCardCount: 0, totalCardCount: 0, diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx index 0a02051..99e919b 100644 --- a/src/client/pages/DeckDetailPage.tsx +++ b/src/client/pages/DeckDetailPage.tsx @@ -8,7 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useAtomValue } from "jotai"; import { Suspense, useState } from "react"; import { Link, useParams } from "wouter"; -import { cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms"; +import { cardsByDeckAtomFamily, type Deck, deckByIdAtomFamily } from "../atoms"; import { CreateNoteModal } from "../components/CreateNoteModal"; import { ErrorBoundary } from "../components/ErrorBoundary"; import { LoadingSpinner } from "../components/LoadingSpinner"; @@ -204,6 +204,9 @@ export function DeckDetailPage() { <CreateNoteModal isOpen={isCreateModalOpen} deckId={deckId} + defaultNoteTypeId={ + queryClient.getQueryData<Deck>(["decks", deckId])?.defaultNoteTypeId + } onClose={() => setIsCreateModalOpen(false)} onNoteCreated={() => { queryClient.invalidateQueries({ diff --git a/src/client/pages/HomePage.test.tsx b/src/client/pages/HomePage.test.tsx index de7a768..adcc651 100644 --- a/src/client/pages/HomePage.test.tsx +++ b/src/client/pages/HomePage.test.tsx @@ -92,6 +92,7 @@ const mockDecks = [ id: "deck-1", name: "Japanese Vocabulary", description: "Common Japanese words", + defaultNoteTypeId: null, dueCardCount: 5, newCardCount: 0, totalCardCount: 100, @@ -103,6 +104,7 @@ const mockDecks = [ id: "deck-2", name: "Spanish Verbs", description: null, + defaultNoteTypeId: null, dueCardCount: 0, newCardCount: 0, totalCardCount: 0, @@ -258,6 +260,7 @@ describe("HomePage", () => { id: "deck-1", name: "No Description Deck", description: null, + defaultNoteTypeId: null, dueCardCount: 0, newCardCount: 0, totalCardCount: 0, @@ -340,6 +343,7 @@ describe("HomePage", () => { id: "deck-new", name: "New Deck", description: "A new deck", + defaultNoteTypeId: null, dueCardCount: 0, newCardCount: 0, totalCardCount: 0, diff --git a/src/client/sync/conflict.test.ts b/src/client/sync/conflict.test.ts index d11e150..ba6586f 100644 --- a/src/client/sync/conflict.test.ts +++ b/src/client/sync/conflict.test.ts @@ -155,6 +155,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Name", description: "Local description", + defaultNoteTypeId: null, }); const serverDeck = { @@ -165,6 +166,7 @@ describe("ConflictResolver", () => { createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-03"), deletedAt: null, + defaultNoteTypeId: null, syncVersion: 5, }; @@ -186,6 +188,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const localCard = await localCardRepository.create({ @@ -236,11 +239,13 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Deck 1", description: null, + defaultNoteTypeId: null, }); const deck2 = await localDeckRepository.create({ userId: "user-1", name: "Local Deck 2", description: null, + defaultNoteTypeId: null, }); const pushResult: SyncPushResult = { @@ -267,6 +272,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Deck 1", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -277,6 +283,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Deck 2", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -306,6 +313,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ @@ -388,6 +396,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -415,6 +424,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Only Deck", description: null, + defaultNoteTypeId: null, }); const pushResult: SyncPushResult = { @@ -451,6 +461,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Name", description: null, + defaultNoteTypeId: null, }); const pushResult: SyncPushResult = { @@ -471,6 +482,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Name", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -501,6 +513,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Deck Name", description: "Local description", + defaultNoteTypeId: null, }); // Store local CRDT document @@ -518,6 +531,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Deck Name", description: "Server description", + defaultNoteTypeId: null, createdAt: localDeck.createdAt, updatedAt: new Date(Date.now() + 1000), deletedAt: null, @@ -575,6 +589,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Name", description: null, + defaultNoteTypeId: null, }); const serverDeck = { @@ -582,6 +597,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Name", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -632,6 +648,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Name", description: null, + defaultNoteTypeId: null, }); const serverDeck = { @@ -639,6 +656,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Name", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -680,6 +698,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Local Name", description: null, + defaultNoteTypeId: null, }); const serverDeck = { @@ -687,6 +706,7 @@ describe("ConflictResolver", () => { userId: "user-1", name: "Server Name", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, diff --git a/src/client/sync/conflict.ts b/src/client/sync/conflict.ts index 64287a1..fcb5bf4 100644 --- a/src/client/sync/conflict.ts +++ b/src/client/sync/conflict.ts @@ -67,6 +67,7 @@ function serverDeckToLocal(deck: ServerDeck): LocalDeck { userId: deck.userId, name: deck.name, description: deck.description, + defaultNoteTypeId: deck.defaultNoteTypeId, createdAt: new Date(deck.createdAt), updatedAt: new Date(deck.updatedAt), deletedAt: deck.deletedAt ? new Date(deck.deletedAt) : null, diff --git a/src/client/sync/crdt/concurrent-edits.test.ts b/src/client/sync/crdt/concurrent-edits.test.ts index d55b233..e9d7b58 100644 --- a/src/client/sync/crdt/concurrent-edits.test.ts +++ b/src/client/sync/crdt/concurrent-edits.test.ts @@ -34,6 +34,7 @@ function createTestDeck(overrides: Partial<LocalDeck> = {}): LocalDeck { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, diff --git a/src/client/sync/crdt/document-manager.test.ts b/src/client/sync/crdt/document-manager.test.ts index b578c77..2da92a1 100644 --- a/src/client/sync/crdt/document-manager.test.ts +++ b/src/client/sync/crdt/document-manager.test.ts @@ -51,6 +51,7 @@ describe("createDocument", () => { userId: "user-1", name: "My Deck", description: null, + defaultNoteTypeId: null, createdAt: Date.now(), deletedAt: null, }, @@ -153,6 +154,7 @@ describe("saveDocument and loadDocument", () => { userId: "user-1", name: "Test Deck", description: "A test deck", + defaultNoteTypeId: null, createdAt: 1234567890, deletedAt: null, }, @@ -222,6 +224,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => { userId: "user-1", name: "My Deck", description: "A deck for testing", + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -246,6 +249,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => { userId: "user-1", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: deletedAt, deletedAt: deletedAt, @@ -271,6 +275,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => { userId: "user-2", name: "Converted Deck", description: "Converted from CRDT", + defaultNoteTypeId: null, createdAt: now - 10000, deletedAt: null, }, @@ -456,6 +461,7 @@ describe("createDocumentFromEntity", () => { userId: "user-1", name: "Test", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -536,6 +542,7 @@ describe("getLastModified", () => { userId: "user-1", name: "Test", description: null, + defaultNoteTypeId: null, createdAt: timestamp, deletedAt: null, }, @@ -558,6 +565,7 @@ describe("isDeleted", () => { userId: "user-1", name: "Test", description: null, + defaultNoteTypeId: null, createdAt: Date.now(), deletedAt: null, }, @@ -578,6 +586,7 @@ describe("isDeleted", () => { userId: "user-1", name: "Test", description: null, + defaultNoteTypeId: null, createdAt: Date.now(), deletedAt: Date.now(), }, diff --git a/src/client/sync/crdt/document-manager.ts b/src/client/sync/crdt/document-manager.ts index b753d88..ab121ec 100644 --- a/src/client/sync/crdt/document-manager.ts +++ b/src/client/sync/crdt/document-manager.ts @@ -187,6 +187,7 @@ function getEmptyDocumentData( userId: "", name: "", description: null, + defaultNoteTypeId: null, createdAt: 0, deletedAt: null, }, @@ -300,6 +301,7 @@ export function deckToCrdtDocument(deck: LocalDeck): CrdtDeckDocument { userId: deck.userId, name: deck.name, description: deck.description, + defaultNoteTypeId: deck.defaultNoteTypeId, createdAt: deck.createdAt.getTime(), deletedAt: deck.deletedAt?.getTime() ?? null, }, @@ -317,6 +319,7 @@ export function crdtDocumentToDeck( userId: doc.data.userId, name: doc.data.name, description: doc.data.description, + defaultNoteTypeId: doc.data.defaultNoteTypeId ?? null, createdAt: new Date(doc.data.createdAt), updatedAt: new Date(doc.meta.lastModified), deletedAt: doc.data.deletedAt ? new Date(doc.data.deletedAt) : null, diff --git a/src/client/sync/crdt/migration.test.ts b/src/client/sync/crdt/migration.test.ts index ba90be2..531e8fe 100644 --- a/src/client/sync/crdt/migration.test.ts +++ b/src/client/sync/crdt/migration.test.ts @@ -109,6 +109,7 @@ describe("migration", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, diff --git a/src/client/sync/crdt/repositories.test.ts b/src/client/sync/crdt/repositories.test.ts index f7b75b3..c400b22 100644 --- a/src/client/sync/crdt/repositories.test.ts +++ b/src/client/sync/crdt/repositories.test.ts @@ -35,6 +35,7 @@ describe("crdtDeckRepository", () => { userId: "user-1", name: "Test Deck", description: "A test deck", + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -378,6 +379,7 @@ describe("entitiesToCrdtDocuments", () => { userId: "user-1", name: "Deck 1", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -389,6 +391,7 @@ describe("entitiesToCrdtDocuments", () => { userId: "user-1", name: "Deck 2", description: "Second deck", + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -415,6 +418,7 @@ describe("mergeAndConvert", () => { userId: "user-1", name: "Remote Deck", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -436,6 +440,7 @@ describe("mergeAndConvert", () => { userId: "user-1", name: "Original", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, @@ -476,6 +481,7 @@ describe("mergeAndConvert", () => { userId: "user-1", name: "Same", description: null, + defaultNoteTypeId: null, createdAt: now, updatedAt: now, deletedAt: null, diff --git a/src/client/sync/crdt/types.test.ts b/src/client/sync/crdt/types.test.ts index 07ae0f2..7a501b2 100644 --- a/src/client/sync/crdt/types.test.ts +++ b/src/client/sync/crdt/types.test.ts @@ -149,6 +149,7 @@ describe("CRDT Document type structures", () => { userId: "user-1", name: "My Deck", description: "A test deck", + defaultNoteTypeId: null, createdAt: now, deletedAt: null, }, @@ -303,6 +304,7 @@ describe("CRDT Document type structures", () => { userId: "user-1", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, createdAt: now - 86400000, deletedAt: now, }, diff --git a/src/client/sync/crdt/types.ts b/src/client/sync/crdt/types.ts index e2f5d4c..434f3b0 100644 --- a/src/client/sync/crdt/types.ts +++ b/src/client/sync/crdt/types.ts @@ -36,6 +36,7 @@ export interface CrdtDeckDocument { userId: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: number; // Unix timestamp in ms deletedAt: number | null; }; diff --git a/src/client/sync/manager.test.ts b/src/client/sync/manager.test.ts index a9be10d..97e99d2 100644 --- a/src/client/sync/manager.test.ts +++ b/src/client/sync/manager.test.ts @@ -83,6 +83,7 @@ describe("SyncManager", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); } @@ -411,6 +412,7 @@ describe("SyncManager", () => { userId: "user-1", name: "Server Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, diff --git a/src/client/sync/pull.test.ts b/src/client/sync/pull.test.ts index 0f1b689..56213bb 100644 --- a/src/client/sync/pull.test.ts +++ b/src/client/sync/pull.test.ts @@ -48,6 +48,7 @@ describe("pullResultToLocalData", () => { userId: "user-1", name: "Test Deck", description: "A description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -68,6 +69,7 @@ describe("pullResultToLocalData", () => { userId: "user-1", name: "Test Deck", description: "A description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -83,6 +85,7 @@ describe("pullResultToLocalData", () => { userId: "user-1", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-03T12:00:00Z"), deletedAt: new Date("2024-01-03T12:00:00Z"), @@ -535,6 +538,7 @@ describe("PullService", () => { userId: "user-1", name: "Server Deck", description: "From server", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T10:00:00Z"), deletedAt: null, @@ -567,6 +571,7 @@ describe("PullService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -668,6 +673,7 @@ describe("PullService", () => { userId: "user-1", name: "Old Name", description: null, + defaultNoteTypeId: null, }); const pullFromServer = vi.fn().mockResolvedValue({ @@ -677,6 +683,7 @@ describe("PullService", () => { userId: "user-1", name: "Updated Name", description: "Updated description", + defaultNoteTypeId: null, createdAt: existingDeck.createdAt, updatedAt: new Date(), deletedAt: null, @@ -709,6 +716,7 @@ describe("PullService", () => { userId: "user-1", name: "Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -865,6 +873,7 @@ describe("PullService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -919,6 +928,7 @@ describe("PullService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -988,6 +998,7 @@ describe("PullService", () => { userId: "user-1", name: "Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -1167,6 +1178,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Test Deck", description: "A test description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1199,6 +1211,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Local Deck", description: "Local description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-01T12:00:00Z"), deletedAt: null, @@ -1221,6 +1234,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Remote Deck", description: "Remote description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), // Later timestamp deletedAt: null, @@ -1251,6 +1265,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Deck 1", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1262,6 +1277,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Deck 2", description: "Second deck", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1300,6 +1316,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1354,6 +1371,7 @@ describe("applyCrdtChanges", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1451,6 +1469,7 @@ describe("PullService with CRDT changes", () => { userId: "user-1", name: "CRDT Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1501,6 +1520,7 @@ describe("PullService with CRDT changes", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -1577,6 +1597,7 @@ describe("PullService with CRDT changes", () => { userId: "user-1", name: "Regular Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, diff --git a/src/client/sync/pull.ts b/src/client/sync/pull.ts index ce4d992..5fa561b 100644 --- a/src/client/sync/pull.ts +++ b/src/client/sync/pull.ts @@ -30,6 +30,7 @@ export interface ServerDeck { userId: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: Date; updatedAt: Date; deletedAt: Date | null; @@ -167,6 +168,7 @@ function serverDeckToLocal(deck: ServerDeck): LocalDeck { userId: deck.userId, name: deck.name, description: deck.description, + defaultNoteTypeId: deck.defaultNoteTypeId, createdAt: new Date(deck.createdAt), updatedAt: new Date(deck.updatedAt), deletedAt: deck.deletedAt ? new Date(deck.deletedAt) : null, diff --git a/src/client/sync/push.test.ts b/src/client/sync/push.test.ts index 8605ede..0efc796 100644 --- a/src/client/sync/push.test.ts +++ b/src/client/sync/push.test.ts @@ -81,6 +81,7 @@ describe("pendingChangesToPushData", () => { userId: "user-1", name: "Test Deck", description: "A description", + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -101,6 +102,7 @@ describe("pendingChangesToPushData", () => { id: "deck-1", name: "Test Deck", description: "A description", + defaultNoteTypeId: null, createdAt: "2024-01-01T10:00:00.000Z", updatedAt: "2024-01-02T15:30:00.000Z", deletedAt: null, @@ -114,6 +116,7 @@ describe("pendingChangesToPushData", () => { userId: "user-1", name: "Deleted Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-03T12:00:00Z"), deletedAt: new Date("2024-01-03T12:00:00Z"), @@ -527,6 +530,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const pushToServer = vi.fn().mockResolvedValue({ @@ -565,6 +569,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -611,6 +616,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -668,6 +674,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const pushToServer = vi.fn().mockResolvedValue({ @@ -695,6 +702,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const pushToServer = vi.fn().mockResolvedValue({ @@ -720,6 +728,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const pushToServer = vi @@ -739,6 +748,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ @@ -884,6 +894,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -931,6 +942,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -994,6 +1006,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const noteType = await localNoteTypeRepository.create({ @@ -1080,6 +1093,7 @@ describe("PushService", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const pushService = new PushService({ @@ -1102,6 +1116,7 @@ describe("generateCrdtChanges", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1339,6 +1354,7 @@ describe("generateCrdtChanges", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, @@ -1480,6 +1496,7 @@ describe("pendingChangesToPushData with crdtChanges", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date("2024-01-01T10:00:00Z"), updatedAt: new Date("2024-01-02T15:30:00Z"), deletedAt: null, diff --git a/src/client/sync/push.ts b/src/client/sync/push.ts index e5c5fd4..79df825 100644 --- a/src/client/sync/push.ts +++ b/src/client/sync/push.ts @@ -39,6 +39,7 @@ export interface SyncDeckData { id: string; name: string; description: string | null; + defaultNoteTypeId: string | null; createdAt: string; updatedAt: string; deletedAt: string | null; @@ -153,6 +154,7 @@ function deckToSyncData(deck: LocalDeck): SyncDeckData { id: deck.id, name: deck.name, description: deck.description, + defaultNoteTypeId: deck.defaultNoteTypeId, createdAt: deck.createdAt.toISOString(), updatedAt: deck.updatedAt.toISOString(), deletedAt: deck.deletedAt?.toISOString() ?? null, diff --git a/src/client/sync/queue.test.ts b/src/client/sync/queue.test.ts index 436046b..dd4e116 100644 --- a/src/client/sync/queue.test.ts +++ b/src/client/sync/queue.test.ts @@ -68,6 +68,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const changes = await syncQueue.getPendingChanges(); @@ -80,6 +81,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localCardRepository.create({ deckId: deck.id, @@ -99,6 +101,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ deckId: deck.id, @@ -127,6 +130,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -141,6 +145,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localCardRepository.create({ deckId: deck.id, @@ -174,6 +179,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const hasPending = await syncQueue.hasPendingChanges(); @@ -298,6 +304,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await syncQueue.markSynced({ @@ -320,6 +327,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ deckId: deck.id, @@ -349,6 +357,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ deckId: deck.id, @@ -408,6 +417,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Server Deck", description: null, + defaultNoteTypeId: null, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, @@ -435,6 +445,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); await localDeckRepository.markSynced(deck.id, 1); @@ -481,6 +492,7 @@ describe("SyncQueue", () => { userId: "user-1", name: "Test Deck", description: null, + defaultNoteTypeId: null, }); const card = await localCardRepository.create({ deckId: deck.id, 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> = {}): 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<Deck> { 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<Deck | undefined> { 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> = {}): 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<Deck>; update( id: string, @@ -70,6 +72,7 @@ export interface DeckRepository { data: { name?: string; description?: string | null; + defaultNoteTypeId?: string | null; }, ): Promise<Deck | undefined>; softDelete(id: string, userId: string): Promise<boolean>; 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> = {}): 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> = {}): 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> = {}): 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> = {}): 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 |
