diff --git a/src/lib/db/apiKeyContextSources.ts b/src/lib/db/apiKeyContextSources.ts new file mode 100644 index 0000000000..19c1f12ee9 --- /dev/null +++ b/src/lib/db/apiKeyContextSources.ts @@ -0,0 +1,107 @@ +import { getDbInstance } from "./core"; + +export interface ApiKeyContextSource { + apiKeyId: string; + sourceType: string; + token: string | null; + baseUrl: string | null; + vaultPath: string | null; + enabled: boolean; +} + +interface ContextSourceRow { + api_key_id: string; + source_type: string; + token: string | null; + base_url: string | null; + vault_path: string | null; + enabled: number; +} + +function rowToSource(row: ContextSourceRow): ApiKeyContextSource { + return { + apiKeyId: row.api_key_id, + sourceType: row.source_type, + token: row.token, + baseUrl: row.base_url, + vaultPath: row.vault_path, + enabled: row.enabled === 1, + }; +} + +export function getApiKeyContextSource( + apiKeyId: string | null | undefined, + sourceType: string +): (ApiKeyContextSource & { enabled: true }) | null { + if (!apiKeyId) return null; + const db = getDbInstance(); + const row = db + .prepare( + "SELECT * FROM api_key_context_sources WHERE api_key_id = ? AND source_type = ? AND enabled = 1" + ) + .get(apiKeyId, sourceType) as ContextSourceRow | undefined; + if (!row) return null; + return rowToSource(row) as ApiKeyContextSource & { enabled: true }; +} + +export function setApiKeyContextSource( + apiKeyId: string, + sourceType: string, + config: { token?: string; baseUrl?: string; vaultPath?: string; enabled?: boolean } +): void { + const db = getDbInstance(); + const existing = db + .prepare("SELECT * FROM api_key_context_sources WHERE api_key_id = ? AND source_type = ?") + .get(apiKeyId, sourceType) as ContextSourceRow | undefined; + + const now = new Date().toISOString(); + if (existing) { + db.prepare( + `UPDATE api_key_context_sources SET + token = COALESCE(?, token), + base_url = COALESCE(?, base_url), + vault_path = COALESCE(?, vault_path), + enabled = COALESCE(?, enabled), + updated_at = ? + WHERE api_key_id = ? AND source_type = ?` + ).run( + config.token ?? null, + config.baseUrl ?? null, + config.vaultPath ?? null, + config.enabled !== undefined ? (config.enabled ? 1 : 0) : null, + now, + apiKeyId, + sourceType + ); + } else { + db.prepare( + `INSERT INTO api_key_context_sources + (api_key_id, source_type, token, base_url, vault_path, enabled, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + ).run( + apiKeyId, + sourceType, + config.token ?? null, + config.baseUrl ?? null, + config.vaultPath ?? null, + config.enabled !== undefined ? (config.enabled ? 1 : 0) : 1, + now, + now + ); + } +} + +export function deleteApiKeyContextSource(apiKeyId: string, sourceType: string): void { + const db = getDbInstance(); + db.prepare( + "DELETE FROM api_key_context_sources WHERE api_key_id = ? AND source_type = ?" + ).run(apiKeyId, sourceType); +} + +export function listApiKeyContextSources(apiKeyId: string): ApiKeyContextSource[] { + const db = getDbInstance(); + const rows = db + .prepare("SELECT * FROM api_key_context_sources WHERE api_key_id = ?") + .all(apiKeyId) as ContextSourceRow[]; + return rows.map(rowToSource); +} diff --git a/src/lib/db/migrations/092_api_key_context_sources.sql b/src/lib/db/migrations/092_api_key_context_sources.sql new file mode 100644 index 0000000000..c5df007ab8 --- /dev/null +++ b/src/lib/db/migrations/092_api_key_context_sources.sql @@ -0,0 +1,13 @@ +-- Per-API-key context source configuration (Obsidian, Notion, etc.) +CREATE TABLE IF NOT EXISTS api_key_context_sources ( + api_key_id TEXT NOT NULL, + source_type TEXT NOT NULL, + token TEXT, + base_url TEXT, + vault_path TEXT, + enabled INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL DEFAULT (datetime('now')), + updated_at TEXT NOT NULL DEFAULT (datetime('now')), + PRIMARY KEY (api_key_id, source_type), + FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE +); diff --git a/src/lib/db/obsidian.ts b/src/lib/db/obsidian.ts index 2cffd52e46..da1291b230 100644 --- a/src/lib/db/obsidian.ts +++ b/src/lib/db/obsidian.ts @@ -1,4 +1,5 @@ import { getDbInstance } from "./core"; +import { getApiKeyContextSource } from "./apiKeyContextSources"; const OBSIDIAN_NAMESPACE = "obsidian"; const OBSIDIAN_TOKEN_KEY = "api_key"; @@ -251,8 +252,7 @@ export function getObsidianConfigForApiKey(apiKeyId: string | null | undefined): } { if (apiKeyId) { try { - // Per-key context source lookup not yet implemented — fall through to global - const perKey = null as null | { enabled: boolean; token: string; baseUrl?: string; vaultPath?: string }; + const perKey = getApiKeyContextSource(apiKeyId, "obsidian"); if (perKey && perKey.enabled && perKey.token) { return { token: perKey.token, diff --git a/src/lib/localDb.ts b/src/lib/localDb.ts index 014dec751f..a34ad6fd3b 100755 --- a/src/lib/localDb.ts +++ b/src/lib/localDb.ts @@ -603,3 +603,11 @@ export { } from "./db/plugins"; export type { PluginRow, PluginCreateInput } from "./db/plugins"; + +export { + getApiKeyContextSource, + setApiKeyContextSource, + deleteApiKeyContextSource, + listApiKeyContextSources, +} from "./db/apiKeyContextSources"; +export type { ApiKeyContextSource } from "./db/apiKeyContextSources";