fix(calendar-web): inject cross-app API URLs for client-side

The calendar frontend integrates with todo and contacts backends
for tasks and birthdays. The env vars were only available during
SSR, not in client-side JavaScript.

- Add PUBLIC_TODO_BACKEND_URL and PUBLIC_CONTACTS_API_URL injection
  in hooks.server.ts
- Update todos.ts to use injected window variable
- Update birthdays.ts to use injected window variable

Fixes 404 errors on calendar.mana.how for /tasks/* and /contacts/birthdays

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2026-02-02 03:26:46 +01:00
parent a7c1908f25
commit 9a22c89857
3 changed files with 72 additions and 16 deletions

View file

@ -13,6 +13,10 @@ const PUBLIC_BACKEND_URL_CLIENT =
process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
const PUBLIC_STT_URL = process.env.PUBLIC_STT_URL || 'https://stt-api.mana.how';
// Cross-app integration URLs (for todo and contacts APIs)
const PUBLIC_TODO_BACKEND_URL = process.env.PUBLIC_TODO_BACKEND_URL || 'http://localhost:3018';
const PUBLIC_CONTACTS_API_URL = process.env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015';
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
transformPageChunk: ({ html }) => {
@ -22,6 +26,8 @@ export const handle: Handle = async ({ event, resolve }) => {
window.__PUBLIC_MANA_CORE_AUTH_URL__ = "${PUBLIC_MANA_CORE_AUTH_URL_CLIENT}";
window.__PUBLIC_BACKEND_URL__ = "${PUBLIC_BACKEND_URL_CLIENT}";
window.__PUBLIC_STT_URL__ = "${PUBLIC_STT_URL}";
window.__PUBLIC_TODO_BACKEND_URL__ = "${PUBLIC_TODO_BACKEND_URL}";
window.__PUBLIC_CONTACTS_API_URL__ = "${PUBLIC_CONTACTS_API_URL}";
</script>`;
return html.replace('<head>', `<head>${envScript}`);
},

View file

@ -3,19 +3,44 @@
* Allows Calendar app to fetch contact birthdays for display
*/
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
import { createApiClient } from '@manacore/shared-api-client';
import { authStore } from '$lib/stores/auth.svelte';
const CONTACTS_API_BASE = env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015';
// Get contacts API base URL from injected window variable (browser) or env (SSR)
function getContactsApiBase(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_CONTACTS_API_URL__?: string })
.__PUBLIC_CONTACTS_API_URL__;
if (injectedUrl) return injectedUrl;
}
return env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015';
}
const contactsClient = createApiClient({
baseUrl: CONTACTS_API_BASE,
apiPrefix: '/api/v1',
getAuthToken: () => authStore.getValidToken(),
timeout: 30000,
debug: import.meta.env.DEV,
});
let _contactsClient: ReturnType<typeof createApiClient> | null = null;
function getContactsClient() {
if (!_contactsClient) {
_contactsClient = createApiClient({
baseUrl: getContactsApiBase(),
apiPrefix: '/api/v1',
getAuthToken: () => authStore.getValidToken(),
timeout: 30000,
debug: import.meta.env.DEV,
});
}
return _contactsClient;
}
// For backwards compatibility
const contactsClient = {
get: <T>(endpoint: string) => getContactsClient().get<T>(endpoint),
post: <T>(endpoint: string, body?: unknown) => getContactsClient().post<T>(endpoint, body),
put: <T>(endpoint: string, body?: unknown) => getContactsClient().put<T>(endpoint, body),
patch: <T>(endpoint: string, body?: unknown) => getContactsClient().patch<T>(endpoint, body),
delete: <T>(endpoint: string) => getContactsClient().delete<T>(endpoint),
};
// ============================================
// Types for Birthday Integration

View file

@ -3,19 +3,44 @@
* Allows Calendar app to fetch/manage todos from the Todo service
*/
import { browser } from '$app/environment';
import { env } from '$env/dynamic/public';
import { createApiClient, buildQueryString, type ApiResult } from '@manacore/shared-api-client';
import { authStore } from '$lib/stores/auth.svelte';
const TODO_API_BASE = env.PUBLIC_TODO_BACKEND_URL || 'http://localhost:3018';
// Get todo API base URL from injected window variable (browser) or env (SSR)
function getTodoApiBase(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_TODO_BACKEND_URL__?: string })
.__PUBLIC_TODO_BACKEND_URL__;
if (injectedUrl) return injectedUrl;
}
return env.PUBLIC_TODO_BACKEND_URL || 'http://localhost:3018';
}
const todoClient = createApiClient({
baseUrl: TODO_API_BASE,
apiPrefix: '/api/v1',
getAuthToken: () => authStore.getValidToken(),
timeout: 30000,
debug: import.meta.env.DEV,
});
let _todoClient: ReturnType<typeof createApiClient> | null = null;
function getTodoClient() {
if (!_todoClient) {
_todoClient = createApiClient({
baseUrl: getTodoApiBase(),
apiPrefix: '/api/v1',
getAuthToken: () => authStore.getValidToken(),
timeout: 30000,
debug: import.meta.env.DEV,
});
}
return _todoClient;
}
// For backwards compatibility
const todoClient = {
get: <T>(endpoint: string) => getTodoClient().get<T>(endpoint),
post: <T>(endpoint: string, body?: unknown) => getTodoClient().post<T>(endpoint, body),
put: <T>(endpoint: string, body?: unknown) => getTodoClient().put<T>(endpoint, body),
patch: <T>(endpoint: string, body?: unknown) => getTodoClient().patch<T>(endpoint, body),
delete: <T>(endpoint: string) => getTodoClient().delete<T>(endpoint),
};
// ============================================
// Types (mirrored from @todo/shared for cross-app use)