From 0a8243c55b8593e99a6ec45c251c81de7e119a4f Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sun, 12 Apr 2026 12:02:15 +0100 Subject: [PATCH] feat: add CalDAV source config to admin dashboard (#112) Add source definition for aelis.caldav with server URL, username, password, look-ahead days, and timezone fields. Route per-user credentials through /api/sources/:id/credentials instead of the admin provider config endpoint, controlled by a perUserCredentials flag on the source definition. Co-authored-by: Ona --- .../src/components/source-config-panel.tsx | 14 ++++- apps/admin-dashboard/src/lib/api.ts | 56 +++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/apps/admin-dashboard/src/components/source-config-panel.tsx b/apps/admin-dashboard/src/components/source-config-panel.tsx index 123f0b3..ed5f63f 100644 --- a/apps/admin-dashboard/src/components/source-config-panel.tsx +++ b/apps/admin-dashboard/src/components/source-config-panel.tsx @@ -20,7 +20,13 @@ import { import { Separator } from "@/components/ui/separator" import { Switch } from "@/components/ui/switch" import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip" -import { fetchSourceConfig, pushLocation, replaceSource, updateProviderConfig } from "@/lib/api" +import { + fetchSourceConfig, + pushLocation, + replaceSource, + updateProviderConfig, + updateSourceCredentials, +} from "@/lib/api" interface SourceConfigPanelProps { source: SourceDefinition @@ -83,7 +89,11 @@ export function SourceConfigPanel({ source, onUpdate }: SourceConfigPanelProps) (v) => typeof v === "string" && v.length > 0, ) if (hasCredentials) { - promises.push(updateProviderConfig(source.id, { credentials: credentialFields })) + if (source.perUserCredentials) { + promises.push(updateSourceCredentials(source.id, credentialFields)) + } else { + promises.push(updateProviderConfig(source.id, { credentials: credentialFields })) + } } await Promise.all(promises) diff --git a/apps/admin-dashboard/src/lib/api.ts b/apps/admin-dashboard/src/lib/api.ts index 2c6873f..d47d19a 100644 --- a/apps/admin-dashboard/src/lib/api.ts +++ b/apps/admin-dashboard/src/lib/api.ts @@ -23,6 +23,8 @@ export interface SourceDefinition { name: string description: string alwaysEnabled?: boolean + /** When true, secret fields are stored as per-user credentials via /api/sources/:id/credentials. */ + perUserCredentials?: boolean fields: Record } @@ -78,6 +80,44 @@ const sourceDefinitions: SourceDefinition[] = [ }, }, }, + { + id: "aelis.caldav", + name: "CalDAV", + description: "Calendar events from any CalDAV server (Nextcloud, Radicale, Baikal, etc.).", + perUserCredentials: true, + fields: { + serverUrl: { + type: "string", + label: "Server URL", + required: true, + secret: false, + description: "CalDAV server URL (e.g. https://nextcloud.example.com/remote.php/dav)", + }, + username: { + type: "string", + label: "Username", + required: true, + secret: false, + }, + password: { + type: "string", + label: "Password", + required: true, + secret: true, + }, + lookAheadDays: { + type: "number", + label: "Look-ahead Days", + defaultValue: 0, + description: "Number of additional days beyond today to fetch events for", + }, + timeZone: { + type: "string", + label: "Timezone", + description: "IANA timezone for determining \"today\" (e.g. Europe/London). Defaults to UTC.", + }, + }, + }, { id: "aelis.tfl", name: "TfL", @@ -164,6 +204,22 @@ export async function updateProviderConfig( } } +export async function updateSourceCredentials( + sourceId: string, + credentials: Record, +): Promise { + const res = await fetch(`${serverBase()}/sources/${sourceId}/credentials`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify(credentials), + }) + if (!res.ok) { + const data = (await res.json()) as { error?: string } + throw new Error(data.error ?? `Failed to update credentials: ${res.status}`) + } +} + export interface LocationInput { lat: number lng: number