diff --git a/ka-note/.env.example b/ka-note/.env.example index 7c52c8f..1592e2f 100644 --- a/ka-note/.env.example +++ b/ka-note/.env.example @@ -12,6 +12,8 @@ AZURE_TENANT_ID= # App Registration → Certificates & secrets → New client secret AZURE_GRAPH_CLIENT_ID= AZURE_GRAPH_CLIENT_SECRET= +# Fallback email when auth provides no email (e.g. API key login) +CALENDAR_USER_EMAIL= # ── CLIENT (Vite — copy relevant lines to client/.env) ─────────────────────── # VITE_AZURE_CLIENT_ID= diff --git a/ka-note/VERSION b/ka-note/VERSION index 5961097..27e2dba 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.1.81 \ No newline at end of file +1.1.84 \ No newline at end of file diff --git a/ka-note/client/src/lib/components/JournalView.svelte b/ka-note/client/src/lib/components/JournalView.svelte index caae225..e311923 100644 --- a/ka-note/client/src/lib/components/JournalView.svelte +++ b/ka-note/client/src/lib/components/JournalView.svelte @@ -16,7 +16,7 @@ import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte'; import EventCard from './EventCard.svelte'; import { eventsForDate } from '$lib/stores/agenda'; - import { createEvent, updateEventNotes } from '$lib/db/repositories'; + import { createEvent, updateEventNotes, upsertContext } from '$lib/db/repositories'; import { fetchCalendarEvents, type CalendarEvent } from '$lib/utils/calendarApi'; import { extractMentionName, quoteMention } from '$lib/actions/mentionCore'; import type { PersonMeta } from '@ka-note/shared'; @@ -222,6 +222,11 @@ let calendarError = $state(null); let calendarEvents = $state([]); + // Unknown-persons dialog (shown after calendar import) + interface UnknownAttendee { name: string; email: string; selected: boolean; } + let unknownDialogOpen = $state(false); + let unknownAttendees = $state([]); + async function openCalendarPicker() { if (!showNewEventForm) openNewEventForm(); calendarLoading = true; @@ -241,23 +246,48 @@ newEventTitle = ev.subject; newEventTime = ev.start; pendingBodyPreview = ev.bodyPreview; + calendarPickerOpen = false; const persons = await db.contexts .filter(c => !c.deletedAt && c.type === 'person') .toArray(); - const mentions = ev.attendees.map(att => { + + const mentions: string[] = []; + const unknowns: UnknownAttendee[] = []; + + for (const att of ev.attendees) { const match = persons.find( p => (p.meta as PersonMeta | null)?.email?.toLowerCase() === att.email.toLowerCase() ); if (match) { - return quoteMention('@', extractMentionName(match)); + mentions.push(quoteMention('@', extractMentionName(match))); + } else { + const cleaned = att.name.replace(/\s*\(.*?\)\s*$/, '').trim(); + mentions.push(quoteMention('@', cleaned)); + if (att.email) unknowns.push({ name: cleaned, email: att.email, selected: true }); } - // Strip company suffix e.g. "Lars Leifer (KRAH)" → "Lars Leifer" - const cleaned = att.name.replace(/\s*\(.*?\)\s*$/, '').trim(); - return quoteMention('@', cleaned); - }); + } + newEventParticipants = mentions.join(' '); - calendarPickerOpen = false; + + if (unknowns.length > 0) { + unknownAttendees = unknowns; + unknownDialogOpen = true; + } + } + + async function confirmUnknownPersons() { + for (const u of unknownAttendees.filter(u => u.selected)) { + const slug = u.name.toLowerCase().replace(/\s+/g, '-'); + await upsertContext({ + id: `u-${slug}`, + name: `Person ${u.name}`, + type: 'person', + sortOrder: 99, + meta: { fullName: u.name, email: u.email, phone: '', duSince: '' } satisfies PersonMeta, + }); + } + unknownDialogOpen = false; } const dateEvents = $derived(eventsForDate(selectedDate)); @@ -468,6 +498,32 @@ {/if} {/if} + + {#if unknownDialogOpen} +
+

Unbekannte Teilnehmer anlegen?

+
+ {#each unknownAttendees as u} + + {/each} +
+
+ + +
+
+ {/if} + {#each $dateEvents ?? [] as event (event.id)}
diff --git a/ka-note/server/drizzle/0014_api_key_email.sql b/ka-note/server/drizzle/0014_api_key_email.sql new file mode 100644 index 0000000..ce6e942 --- /dev/null +++ b/ka-note/server/drizzle/0014_api_key_email.sql @@ -0,0 +1 @@ +ALTER TABLE `api_keys` ADD `email` text NOT NULL DEFAULT ''; diff --git a/ka-note/server/drizzle/0014_classy_power_pack.sql b/ka-note/server/drizzle/0014_classy_power_pack.sql new file mode 100644 index 0000000..60d6646 --- /dev/null +++ b/ka-note/server/drizzle/0014_classy_power_pack.sql @@ -0,0 +1 @@ +ALTER TABLE `api_keys` ADD `email` text DEFAULT '' NOT NULL; \ No newline at end of file diff --git a/ka-note/server/drizzle/meta/0014_snapshot.json b/ka-note/server/drizzle/meta/0014_snapshot.json new file mode 100644 index 0000000..208a79f --- /dev/null +++ b/ka-note/server/drizzle/meta/0014_snapshot.json @@ -0,0 +1,1153 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "690dfff1-a52a-4c26-968c-c540deaf5871", + "prevId": "d8154271-c91d-4ad4-b288-7c09662cd1dc", + "tables": { + "ai_locks": { + "name": "ai_locks", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "locked_at": { + "name": "locked_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "api_keys_key_hash_unique": { + "name": "api_keys_key_hash_unique", + "columns": [ + "key_hash" + ], + "isUnique": true + }, + "api_keys_user_id_idx": { + "name": "api_keys_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "api_keys_key_hash_idx": { + "name": "api_keys_key_hash_idx", + "columns": [ + "key_hash" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "contexts": { + "name": "contexts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "meta": { + "name": "meta", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_favorite": { + "name": "is_favorite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "contexts_updated_at_idx": { + "name": "contexts_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "contexts_user_id_idx": { + "name": "contexts_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "contexts_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "contexts_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "history_entries": { + "name": "history_entries", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "linked_context_id": { + "name": "linked_context_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done_at": { + "name": "done_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wiedervorlage_date": { + "name": "wiedervorlage_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "wiedervorlage_resolved_at": { + "name": "wiedervorlage_resolved_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "history_entries_updated_at_idx": { + "name": "history_entries_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "history_entries_topic_id_idx": { + "name": "history_entries_topic_id_idx", + "columns": [ + "topic_id" + ], + "isUnique": false + }, + "history_entries_user_id_idx": { + "name": "history_entries_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "history_entries_topic_id_user_id_topics_id_user_id_fk": { + "name": "history_entries_topic_id_user_id_topics_id_user_id_fk", + "tableFrom": "history_entries", + "tableTo": "topics", + "columnsFrom": [ + "topic_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "history_entries_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "history_entries_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_blobs": { + "name": "image_blobs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "image_blobs_user_id_idx": { + "name": "image_blobs_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "image_blobs_content_hash_idx": { + "name": "image_blobs_content_hash_idx", + "columns": [ + "content_hash" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "image_blobs_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "image_blobs_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notebooks": { + "name": "notebooks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_favorite": { + "name": "is_favorite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "notebooks_updated_at_idx": { + "name": "notebooks_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "notebooks_user_id_idx": { + "name": "notebooks_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "notebooks_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "notebooks_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_notebooks": { + "name": "page_notebooks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notebook_id": { + "name": "notebook_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "page_notebooks_updated_at_idx": { + "name": "page_notebooks_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "page_notebooks_page_id_idx": { + "name": "page_notebooks_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "page_notebooks_notebook_id_idx": { + "name": "page_notebooks_notebook_id_idx", + "columns": [ + "notebook_id" + ], + "isUnique": false + }, + "page_notebooks_user_id_idx": { + "name": "page_notebooks_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "page_notebooks_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "page_notebooks_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "pages": { + "name": "pages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_favorite": { + "name": "is_favorite", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "pages_updated_at_idx": { + "name": "pages_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "pages_user_id_idx": { + "name": "pages_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "pages_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "pages_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "ratings": { + "name": "ratings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "topic_id": { + "name": "topic_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "history_entry_id": { + "name": "history_entry_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "person_name": { + "name": "person_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "ratings_updated_at_idx": { + "name": "ratings_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "ratings_topic_id_idx": { + "name": "ratings_topic_id_idx", + "columns": [ + "topic_id" + ], + "isUnique": false + }, + "ratings_user_id_idx": { + "name": "ratings_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ratings_topic_id_user_id_topics_id_user_id_fk": { + "name": "ratings_topic_id_user_id_topics_id_user_id_fk", + "tableFrom": "ratings", + "tableTo": "topics", + "columnsFrom": [ + "topic_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "ratings_history_entry_id_user_id_history_entries_id_user_id_fk": { + "name": "ratings_history_entry_id_user_id_history_entries_id_user_id_fk", + "tableFrom": "ratings", + "tableTo": "history_entries", + "columnsFrom": [ + "history_entry_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ratings_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "ratings_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "topics": { + "name": "topics", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "snooze_until": { + "name": "snooze_until", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "is_new": { + "name": "is_new", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": { + "topics_updated_at_idx": { + "name": "topics_updated_at_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + }, + "topics_context_id_idx": { + "name": "topics_context_id_idx", + "columns": [ + "context_id" + ], + "isUnique": false + }, + "topics_user_id_idx": { + "name": "topics_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "topics_context_id_user_id_contexts_id_user_id_fk": { + "name": "topics_context_id_user_id_contexts_id_user_id_fk", + "tableFrom": "topics", + "tableTo": "contexts", + "columnsFrom": [ + "context_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "topics_id_user_id_pk": { + "columns": [ + "id", + "user_id" + ], + "name": "topics_id_user_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/ka-note/server/drizzle/meta/_journal.json b/ka-note/server/drizzle/meta/_journal.json index 67475c4..f4f51a0 100644 --- a/ka-note/server/drizzle/meta/_journal.json +++ b/ka-note/server/drizzle/meta/_journal.json @@ -99,6 +99,13 @@ "when": 1772100000000, "tag": "0013_fts_search", "breakpoints": true + }, + { + "idx": 14, + "version": "6", + "when": 1772212044510, + "tag": "0014_classy_power_pack", + "breakpoints": true } ] } \ No newline at end of file diff --git a/ka-note/server/ka-note.db-shm b/ka-note/server/ka-note.db-shm index 8fd4a43..146d21d 100644 Binary files a/ka-note/server/ka-note.db-shm and b/ka-note/server/ka-note.db-shm differ diff --git a/ka-note/server/ka-note.db-wal b/ka-note/server/ka-note.db-wal index 6dddd2b..ebef38a 100644 Binary files a/ka-note/server/ka-note.db-wal and b/ka-note/server/ka-note.db-wal differ diff --git a/ka-note/server/src/db/schema.ts b/ka-note/server/src/db/schema.ts index 74b7c8b..5d13ba0 100644 --- a/ka-note/server/src/db/schema.ts +++ b/ka-note/server/src/db/schema.ts @@ -144,6 +144,7 @@ export const apiKeys = sqliteTable('api_keys', { id: text('id').primaryKey(), userId: text('user_id').notNull(), label: text('label').notNull(), + email: text('email').notNull().default(''), keyHash: text('key_hash').notNull().unique(), createdAt: integer('created_at', { mode: 'timestamp' }).notNull(), lastUsedAt: integer('last_used_at', { mode: 'timestamp' }), diff --git a/ka-note/server/src/middleware/auth.ts b/ka-note/server/src/middleware/auth.ts index 2eb9af5..eb91a11 100644 --- a/ka-note/server/src/middleware/auth.ts +++ b/ka-note/server/src/middleware/auth.ts @@ -51,7 +51,7 @@ export const authMiddleware = createMiddleware(async (c, next) => { if (!key) { return c.json({ error: 'Invalid or revoked API key' }, 401); } - c.set('auth', { userId: key.userId, name: key.label, email: '' }); + c.set('auth', { userId: key.userId, name: key.label, email: key.email ?? '' }); // fire-and-forget lastUsedAt update db.update(apiKeys).set({ lastUsedAt: new Date() }).where(eq(apiKeys.id, key.id)).catch(() => {}); await next(); diff --git a/ka-note/server/src/routes/api-keys.ts b/ka-note/server/src/routes/api-keys.ts index c1e4449..d80a151 100644 --- a/ka-note/server/src/routes/api-keys.ts +++ b/ka-note/server/src/routes/api-keys.ts @@ -23,7 +23,7 @@ router.get('/', handle('api-keys/list', async (c) => { })); router.post('/', handle('api-keys/create', async (c) => { - const { userId } = c.get('auth'); + const { userId, email } = c.get('auth'); const body = await c.req.json<{ label: string }>(); if (!body.label?.trim()) { return c.json({ error: 'label required' }, 400); @@ -37,6 +37,7 @@ router.post('/', handle('api-keys/create', async (c) => { await db.insert(apiKeys).values({ id, userId, + email, label: body.label.trim(), keyHash, createdAt: now, diff --git a/ka-note/server/src/routes/calendar.ts b/ka-note/server/src/routes/calendar.ts index a0d1f76..bed15bf 100644 --- a/ka-note/server/src/routes/calendar.ts +++ b/ka-note/server/src/routes/calendar.ts @@ -15,7 +15,7 @@ calendar.get('/events', async (c) => { const auth = c.get('auth'); if (!auth.email) { - return c.json({ error: 'graph_unavailable', detail: 'No email in auth context (API key or dev-bypass)' }, 502); + return c.json({ error: 'graph_unavailable', detail: 'No email in auth context' }, 502); } try {