lilith-platform.live/codebase/@features/my/shared/quinn-api-rewrite.ts

166 lines
No EOL
4.8 KiB
TypeScript

/**
* Maps quinn.my BFF paths to quinn.api paths for direct browser → :3030 cutover.
* SSO cookie auth is validated by quinn.api (ssoRequired on /my/*, /admin/*, /engine/tour-optimizer).
*
* Returns null when the path must stay on my-backend (:3024) — local_db modules only.
*/
/** 1:1 /api/<segment> → /my/<segment> */
export const QUINN_API_MY_PREFIXES = [
'bookings',
'claude-accounts',
'credentials',
'financials',
'flight-monitor',
'flights',
'hotel-observations',
'hotel-stays',
'inspiration',
'journal',
'notifications',
'outreach',
'pending-income',
'planner',
'price-watches',
'projects',
'prospector',
'prospects',
'reminders',
'tour-hotels',
'tour-legs',
'tour-stops',
] as const;
/** BFF first segment → quinn.api /my/<target> */
export const QUINN_API_SEGMENT_MAP: Record<string, string> = {
events: 'demand-events',
'hotel-rooms': 'hotel-rooms',
people: 'contacts',
};
const MY_PREFIX_SET = new Set<string>(QUINN_API_MY_PREFIXES);
const CLIENT_ADMIN_SUBPATHS = new Set([
'reputation-score',
'reputation-events',
'screening',
'qualification',
]);
function rewriteClientsPath(pathname: string, method: string): string | null {
if (!pathname.startsWith('/api/clients')) return null;
const rest = pathname.slice('/api/clients'.length);
if (!rest || rest === '/') return '/my/clients';
const parts = rest.split('/').filter(Boolean);
const id = parts[0];
const sub = parts[1];
const subId = parts[2];
if (id && /^\d+$/.test(id) && sub && CLIENT_ADMIN_SUBPATHS.has(sub)) {
if (sub === 'reputation-score') return `/admin/clients/${id}/reputation-score`;
if (sub === 'qualification') return `/admin/clients/${id}/qualification`;
if (sub === 'reputation-events') {
if (subId && /^\d+$/.test(subId)) return `/admin/reputation-events/${subId}`;
return `/admin/reputation-events?clientId=${id}`;
}
if (sub === 'screening') {
if (method === 'POST') return '/admin/screening/check';
return `/admin/screening?clientId=${id}`;
}
}
return `/my/clients${rest}`;
}
function rewriteCalendarPath(pathname: string): string | null {
if (pathname.startsWith('/api/calendars')) {
return `/my/calendars${pathname.slice('/api/calendars'.length)}`;
}
if (pathname === '/api/calendar/sync') return '/my/calendar/sync';
if (pathname.startsWith('/api/calendar/client-bookings/')) {
const id = pathname.split('/')[4];
if (id) return `/my/client-bookings/${id}`;
}
if (pathname === '/api/calendar/client-bookings') return '/my/calendar/client-bookings';
if (pathname.startsWith('/api/calendar/events')) {
const rest = pathname.slice('/api/calendar/events'.length);
return `/my/calendar-events${rest}`;
}
return null;
}
/**
* Rewrite BFF path (+ optional method for screening POST) to quinn.api path.
*/
export function rewriteBffPathToQuinnApi(pathname: string, method = 'GET'): string | null {
if (pathname === '/api/waitlist') return '/admin/waitlist-subscriptions';
if (pathname === '/api/tour-optimizer') return '/engine/tour-optimizer';
if (pathname === '/public/bookings') return '/public/bookings';
const clients = rewriteClientsPath(pathname, method.toUpperCase());
if (clients) return clients;
const calendar = rewriteCalendarPath(pathname);
if (calendar) return calendar;
if (pathname.startsWith('/api/city-visits')) {
return `/my/tour-schedule-visits${pathname.slice('/api/city-visits'.length)}`;
}
if (pathname.startsWith('/api/messages')) {
return `/my/messages${pathname.slice('/api/messages'.length)}`;
}
if (!pathname.startsWith('/api/')) return null;
const segment = pathname.split('/')[2] ?? '';
if (!segment) return null;
const mapped = QUINN_API_SEGMENT_MAP[segment];
if (mapped) {
const rest = pathname.slice(`/api/${segment}`.length);
return `/my/${mapped}${rest}`;
}
if (MY_PREFIX_SET.has(segment)) {
const rest = pathname.slice(`/api/${segment}`.length);
return `/my/${segment}${rest}`;
}
return null;
}
/** Append query params required by quinn.api when the BFF used to inject them. */
export function augmentQuinnApiSearch(pathname: string, search: string): string {
const raw = search.startsWith('?') ? search.slice(1) : search;
const params = new URLSearchParams(raw);
if (pathname.startsWith('/api/calendar/events') && !params.has('provider')) {
params.set('provider', 'icloud');
}
const qs = params.toString();
return qs ? `?${qs}` : '';
}
/** Longest-first prefixes for vite / nginx location ordering. */
export function quinnApiProxyPrefixes(): string[] {
const all = [
...QUINN_API_MY_PREFIXES,
...Object.keys(QUINN_API_SEGMENT_MAP),
'waitlist',
'clients',
'calendars',
'calendar',
'city-visits',
'messages',
'tour-optimizer',
];
return [...new Set(all)].sort((a, b) => b.length - a.length);
}