diff --git a/apps/manacore/apps/web/src/hooks.server.ts b/apps/manacore/apps/web/src/hooks.server.ts index ba1b0b200..21b618758 100644 --- a/apps/manacore/apps/web/src/hooks.server.ts +++ b/apps/manacore/apps/web/src/hooks.server.ts @@ -8,14 +8,29 @@ import type { Handle } from '@sveltejs/kit'; * but Docker containers need runtime configuration. */ +// Auth URL const PUBLIC_MANA_CORE_AUTH_URL_CLIENT = process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || ''; +// Backend URLs for dashboard widgets +const PUBLIC_TODO_API_URL_CLIENT = + process.env.PUBLIC_TODO_API_URL_CLIENT || process.env.PUBLIC_TODO_API_URL || ''; +const PUBLIC_CALENDAR_API_URL_CLIENT = + process.env.PUBLIC_CALENDAR_API_URL_CLIENT || process.env.PUBLIC_CALENDAR_API_URL || ''; +const PUBLIC_CLOCK_API_URL_CLIENT = + process.env.PUBLIC_CLOCK_API_URL_CLIENT || process.env.PUBLIC_CLOCK_API_URL || ''; +const PUBLIC_CONTACTS_API_URL_CLIENT = + process.env.PUBLIC_CONTACTS_API_URL_CLIENT || process.env.PUBLIC_CONTACTS_API_URL || ''; + export const handle: Handle = async ({ event, resolve }) => { return resolve(event, { transformPageChunk: ({ html }) => { const envScript = ``; return html.replace('', `${envScript}`); }, diff --git a/apps/manacore/apps/web/src/lib/api/services/calendar.ts b/apps/manacore/apps/web/src/lib/api/services/calendar.ts index cefced7d4..444b6ef2d 100644 --- a/apps/manacore/apps/web/src/lib/api/services/calendar.ts +++ b/apps/manacore/apps/web/src/lib/api/services/calendar.ts @@ -4,12 +4,32 @@ * Fetches events from the Calendar backend for dashboard widgets. */ +import { browser } from '$app/environment'; import { createApiClient, type ApiResult } from '../base-client'; -// Backend URL - falls back to localhost for development -const CALENDAR_API_URL = import.meta.env.PUBLIC_CALENDAR_API_URL || 'http://localhost:3014/api/v1'; +// Get Calendar API URL dynamically at runtime +function getCalendarApiUrl(): string { + if (browser && typeof window !== 'undefined') { + // Client-side: use injected window variable (set by hooks.server.ts) + const injectedUrl = (window as unknown as { __PUBLIC_CALENDAR_API_URL__?: string }) + .__PUBLIC_CALENDAR_API_URL__; + if (injectedUrl) { + return `${injectedUrl}/api/v1`; + } + } + // Fallback for local development + return 'http://localhost:3016/api/v1'; +} -const client = createApiClient(CALENDAR_API_URL); +// Lazy-initialized client to ensure we get the correct URL at runtime +let _client: ReturnType | null = null; + +function getClient() { + if (!_client) { + _client = createApiClient(getCalendarApiUrl()); + } + return _client; +} /** * Calendar entity from Calendar backend @@ -59,7 +79,7 @@ export const calendarService = { const startDate = new Date().toISOString().split('T')[0]; const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; - const result = await client.get<{ events: CalendarEvent[] }>( + const result = await getClient().get<{ events: CalendarEvent[] }>( `/events?startDate=${startDate}&endDate=${endDate}` ); @@ -75,7 +95,7 @@ export const calendarService = { */ async getTodayEvents(): Promise> { const today = new Date().toISOString().split('T')[0]; - const result = await client.get<{ events: CalendarEvent[] }>( + const result = await getClient().get<{ events: CalendarEvent[] }>( `/events?startDate=${today}&endDate=${today}` ); @@ -90,7 +110,7 @@ export const calendarService = { * Get all calendars */ async getCalendars(): Promise> { - const result = await client.get<{ calendars: Calendar[] }>('/calendars'); + const result = await getClient().get<{ calendars: Calendar[] }>('/calendars'); if (result.error || !result.data) { return { data: null, error: result.error }; @@ -109,7 +129,7 @@ export const calendarService = { const startDate = new Date().toISOString().split('T')[0]; const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0]; - const result = await client.get<{ events: CalendarEvent[] }>( + const result = await getClient().get<{ events: CalendarEvent[] }>( `/events?calendarIds=${calendarId}&startDate=${startDate}&endDate=${endDate}` ); diff --git a/apps/manacore/apps/web/src/lib/api/services/contacts.ts b/apps/manacore/apps/web/src/lib/api/services/contacts.ts index 2491e8427..b2aa226f3 100644 --- a/apps/manacore/apps/web/src/lib/api/services/contacts.ts +++ b/apps/manacore/apps/web/src/lib/api/services/contacts.ts @@ -4,12 +4,32 @@ * Fetches contacts from the Contacts backend for dashboard widgets. */ +import { browser } from '$app/environment'; import { createApiClient, type ApiResult } from '../base-client'; -// Backend URL - falls back to localhost for development -const CONTACTS_API_URL = import.meta.env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015/api/v1'; +// Get Contacts API URL dynamically at runtime +function getContactsApiUrl(): string { + if (browser && typeof window !== 'undefined') { + // Client-side: use injected window variable (set by hooks.server.ts) + const injectedUrl = (window as unknown as { __PUBLIC_CONTACTS_API_URL__?: string }) + .__PUBLIC_CONTACTS_API_URL__; + if (injectedUrl) { + return `${injectedUrl}/api/v1`; + } + } + // Fallback for local development + return 'http://localhost:3015/api/v1'; +} -const client = createApiClient(CONTACTS_API_URL); +// Lazy-initialized client to ensure we get the correct URL at runtime +let _client: ReturnType | null = null; + +function getClient() { + if (!_client) { + _client = createApiClient(getContactsApiUrl()); + } + return _client; +} /** * Contact entity from Contacts backend @@ -55,7 +75,7 @@ export const contactsService = { * Get favorite contacts */ async getFavoriteContacts(limit: number = 5): Promise> { - const result = await client.get(`/contacts?isFavorite=true&limit=${limit}`); + const result = await getClient().get(`/contacts?isFavorite=true&limit=${limit}`); return result; }, @@ -63,7 +83,7 @@ export const contactsService = { * Get recent contacts (by updatedAt) */ async getRecentContacts(limit: number = 5): Promise> { - const result = await client.get(`/contacts?limit=${limit}`); + const result = await getClient().get(`/contacts?limit=${limit}`); if (result.error || !result.data) { return result; @@ -82,7 +102,7 @@ export const contactsService = { * Get contacts with upcoming birthdays */ async getUpcomingBirthdays(days: number = 30): Promise> { - const result = await client.get('/contacts'); + const result = await getClient().get('/contacts'); if (result.error || !result.data) { return result; @@ -113,7 +133,7 @@ export const contactsService = { * Get contact count */ async getContactCount(): Promise> { - const result = await client.get('/contacts'); + const result = await getClient().get('/contacts'); if (result.error || !result.data) { return { data: null, error: result.error }; diff --git a/apps/manacore/apps/web/src/lib/api/services/todo.ts b/apps/manacore/apps/web/src/lib/api/services/todo.ts index 8ab3f6bc6..16389b036 100644 --- a/apps/manacore/apps/web/src/lib/api/services/todo.ts +++ b/apps/manacore/apps/web/src/lib/api/services/todo.ts @@ -4,12 +4,32 @@ * Fetches tasks from the Todo backend for dashboard widgets. */ +import { browser } from '$app/environment'; import { createApiClient, type ApiResult } from '../base-client'; -// Backend URL - falls back to localhost for development -const TODO_API_URL = import.meta.env.PUBLIC_TODO_API_URL || 'http://localhost:3017/api/v1'; +// Get Todo API URL dynamically at runtime +function getTodoApiUrl(): string { + if (browser && typeof window !== 'undefined') { + // Client-side: use injected window variable (set by hooks.server.ts) + const injectedUrl = (window as unknown as { __PUBLIC_TODO_API_URL__?: string }) + .__PUBLIC_TODO_API_URL__; + if (injectedUrl) { + return `${injectedUrl}/api/v1`; + } + } + // Fallback for local development + return 'http://localhost:3018/api/v1'; +} -const client = createApiClient(TODO_API_URL); +// Lazy-initialized client to ensure we get the correct URL at runtime +let _client: ReturnType | null = null; + +function getClient() { + if (!_client) { + _client = createApiClient(getTodoApiUrl()); + } + return _client; +} /** * Task entity from Todo backend @@ -49,7 +69,7 @@ export const todoService = { * Get today's tasks */ async getTodayTasks(): Promise> { - const result = await client.get<{ tasks: Task[] }>('/tasks/today'); + const result = await getClient().get<{ tasks: Task[] }>('/tasks/today'); if (result.error || !result.data) { return { data: null, error: result.error }; @@ -62,7 +82,7 @@ export const todoService = { * Get upcoming tasks for the next N days */ async getUpcomingTasks(days: number = 7): Promise> { - const result = await client.get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`); + const result = await getClient().get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`); if (result.error || !result.data) { return { data: null, error: result.error }; @@ -75,7 +95,7 @@ export const todoService = { * Get inbox tasks (unassigned to project) */ async getInboxTasks(): Promise> { - const result = await client.get<{ tasks: Task[] }>('/tasks/inbox'); + const result = await getClient().get<{ tasks: Task[] }>('/tasks/inbox'); if (result.error || !result.data) { return { data: null, error: result.error }; @@ -88,7 +108,7 @@ export const todoService = { * Get all projects */ async getProjects(): Promise> { - const result = await client.get<{ projects: Project[] }>('/projects'); + const result = await getClient().get<{ projects: Project[] }>('/projects'); if (result.error || !result.data) { return { data: null, error: result.error }; diff --git a/apps/todo/apps/web/src/lib/api/client.ts b/apps/todo/apps/web/src/lib/api/client.ts index 606a54945..eb4e6ed00 100644 --- a/apps/todo/apps/web/src/lib/api/client.ts +++ b/apps/todo/apps/web/src/lib/api/client.ts @@ -12,12 +12,28 @@ interface ApiError { statusCode: number; } +/** + * Get the backend URL, preferring runtime-injected value in browser + * This allows Docker to inject PUBLIC_BACKEND_URL_CLIENT at runtime + * instead of using the build-time PUBLIC_BACKEND_URL + */ +function getBackendUrl(): string { + if (browser && typeof window !== 'undefined') { + const runtimeUrl = (window as Window & { __PUBLIC_BACKEND_URL__?: string }) + .__PUBLIC_BACKEND_URL__; + if (runtimeUrl) { + return runtimeUrl; + } + } + return PUBLIC_BACKEND_URL || 'http://localhost:3018'; +} + class ApiClient { - private baseUrl: string; private accessToken: string | null = null; - constructor() { - this.baseUrl = PUBLIC_BACKEND_URL || 'http://localhost:3018'; + // Use getter to evaluate URL at request time (browser may hydrate after construction) + private get baseUrl(): string { + return getBackendUrl(); } setAccessToken(token: string | null) { diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 7e0d1940a..b33ba737c 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -173,8 +173,16 @@ services: environment: NODE_ENV: staging PORT: 5173 + # Auth URLs PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001 PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001 + # Backend URLs for dashboard widgets + PUBLIC_TODO_API_URL: http://todo-backend:3018 + PUBLIC_TODO_API_URL_CLIENT: http://46.224.108.214:3018 + PUBLIC_CALENDAR_API_URL: http://calendar-backend:3016 + PUBLIC_CALENDAR_API_URL_CLIENT: http://46.224.108.214:3016 + PUBLIC_CLOCK_API_URL: http://clock-backend:3017 + PUBLIC_CLOCK_API_URL_CLIENT: http://46.224.108.214:3017 ports: - "5173:5173" healthcheck: