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: