import { NextRequest, NextResponse } from 'next/server'; import { logger } from '@/lib/logger'; import { getStalwartCredentials } from '@/lib/stalwart/credentials'; interface DiscoveryAccountRequest { key: string; candidates: string[]; } interface DiscoveryResult { url: string | null; resolvedAccount: string | null; } function buildPublicUrl(serverUrl: string, path: string): string { return new URL(path, serverUrl).toString(); } async function probeCalendarHome(serverUrl: string, authHeader: string, accountName: string): Promise { const targetUrl = buildPublicUrl(serverUrl, `/dav/cal/${encodeURIComponent(accountName)}`); const response = await fetch(targetUrl, { method: 'PROPFIND', headers: { Authorization: authHeader, Depth: '0', 'Content-Type': 'application/xml; charset=utf-8', }, body: ` `, redirect: 'manual', }); if (response.status === 207) { return targetUrl; } if (response.status >= 300 && response.status < 400) { const location = response.headers.get('Location'); if (location) { return new URL(location, targetUrl).toString(); } } return null; } export async function POST(request: NextRequest) { try { const creds = await getStalwartCredentials(request); if (!creds) { return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }); } const body = await request.json().catch(() => ({})); const accounts = Array.isArray(body.accounts) ? body.accounts as DiscoveryAccountRequest[] : []; const wellKnownUrl = buildPublicUrl(creds.serverUrl, '/.well-known/caldav'); const discovered: Record = {}; for (const account of accounts) { if (!account?.key) continue; const candidates = Array.from(new Set( (account.candidates || []) .map((candidate) => candidate?.trim()) .filter((candidate): candidate is string => Boolean(candidate)) )); let url: string | null = null; let resolvedAccount: string | null = null; for (const candidate of candidates) { try { url = await probeCalendarHome(creds.serverUrl, creds.authHeader, candidate); if (url) { resolvedAccount = candidate; break; } } catch (error) { logger.warn('CalDAV discovery probe failed', { accountKey: account.key, candidate, error: error instanceof Error ? error.message : 'Unknown', }); } } discovered[account.key] = { url, resolvedAccount }; } return NextResponse.json({ wellKnownUrl, accounts: discovered, }); } catch (error) { logger.error('CalDAV discovery failed', { error: error instanceof Error ? error.message : 'Unknown' }); return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } }