fix(manacore-web,todo-web): use runtime URLs for backend API services

- manacore-web: Update todo, calendar, contacts service files to use
  runtime URLs from window instead of import.meta.env (baked at build time)
- manacore-web: Add backend URL env vars to docker-compose.staging.yml
- manacore-web: Fix fallback ports (todo: 3017→3018, calendar: 3014→3016)
- todo-web: Update API client to use runtime URL from window

This enables Docker containers to use runtime-injected URLs instead of
build-time environment variables.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wuesteon 2025-12-08 22:03:12 +01:00
parent 7caeea4abd
commit 4398fbc29b
6 changed files with 123 additions and 24 deletions

View file

@ -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 = `<script>
window.__PUBLIC_MANA_CORE_AUTH_URL__ = "${PUBLIC_MANA_CORE_AUTH_URL_CLIENT}";
window.__PUBLIC_TODO_API_URL__ = "${PUBLIC_TODO_API_URL_CLIENT}";
window.__PUBLIC_CALENDAR_API_URL__ = "${PUBLIC_CALENDAR_API_URL_CLIENT}";
window.__PUBLIC_CLOCK_API_URL__ = "${PUBLIC_CLOCK_API_URL_CLIENT}";
window.__PUBLIC_CONTACTS_API_URL__ = "${PUBLIC_CONTACTS_API_URL_CLIENT}";
</script>`;
return html.replace('<head>', `<head>${envScript}`);
},

View file

@ -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<typeof createApiClient> | 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<ApiResult<CalendarEvent[]>> {
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<ApiResult<Calendar[]>> {
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}`
);

View file

@ -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<typeof createApiClient> | 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<ApiResult<Contact[]>> {
const result = await client.get<Contact[]>(`/contacts?isFavorite=true&limit=${limit}`);
const result = await getClient().get<Contact[]>(`/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<ApiResult<Contact[]>> {
const result = await client.get<Contact[]>(`/contacts?limit=${limit}`);
const result = await getClient().get<Contact[]>(`/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<ApiResult<Contact[]>> {
const result = await client.get<Contact[]>('/contacts');
const result = await getClient().get<Contact[]>('/contacts');
if (result.error || !result.data) {
return result;
@ -113,7 +133,7 @@ export const contactsService = {
* Get contact count
*/
async getContactCount(): Promise<ApiResult<{ total: number; favorites: number }>> {
const result = await client.get<Contact[]>('/contacts');
const result = await getClient().get<Contact[]>('/contacts');
if (result.error || !result.data) {
return { data: null, error: result.error };

View file

@ -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<typeof createApiClient> | 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<ApiResult<Task[]>> {
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<ApiResult<Task[]>> {
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<ApiResult<Task[]>> {
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<ApiResult<Project[]>> {
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 };

View file

@ -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) {

View file

@ -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: