fix: resolve frontend API routing issues for Traefik deployment
Test and Lint / backend-test (push) Successful in 1m16s
continuous-integration/drone/push Build is failing
Test and Lint / frontend-test (push) Successful in 2m18s
Version and Release / version-bump (push) Successful in 43s
Version and Release / trigger-drone (push) Successful in 4s

Major fixes for production deployment with Traefik:

1. API Path Fixes:
   - Remove double /api prefix from all frontend service calls
   - Fix auth.service.ts to use correct paths (/auth/admin/login)
   - Update all services to use single /api prefix from base URL
   - Fix template literal paths in photo services

2. Docker Configuration:
   - Add build args for VITE_API_URL in docker-compose.prod.yml
   - Create Dockerfile.prod with proper API URL configuration
   - Ensure frontend is built with correct API base path

3. Documentation:
   - Add comprehensive TRAEFIK_DEPLOYMENT.md guide
   - Document proper Traefik labels and routing configuration
   - Include troubleshooting steps for common issues
   - Explain network configuration and SSL handling

This resolves:
- 502 Bad Gateway errors
- Double /api/api paths in requests
- Frontend unable to communicate with backend
- Login functionality not working

The frontend now correctly calls the backend API through Traefik's
routing, with all requests going to /api/* being forwarded to the
backend service on port 3000.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-13 23:17:45 +02:00
parent 689861f671
commit ac1cd96ecd
25 changed files with 422 additions and 75 deletions
+5 -5
View File
@@ -66,13 +66,13 @@ export interface AnalyticsData {
export const adminService = {
// Dashboard statistics
async getDashboardStats(): Promise<DashboardStats> {
const response = await api.get<DashboardStats>('/api/admin/dashboard/stats');
const response = await api.get<DashboardStats>('/admin/dashboard/stats');
return response.data;
},
// Recent activity
async getRecentActivity(limit: number = 10): Promise<Activity[]> {
const response = await api.get<Activity[]>('/api/admin/dashboard/activity', {
const response = await api.get<Activity[]>('/admin/dashboard/activity', {
params: { limit }
});
return response.data;
@@ -80,7 +80,7 @@ export const adminService = {
// Analytics data
async getAnalytics(days: number = 7): Promise<AnalyticsData> {
const response = await api.get<AnalyticsData>('/api/admin/dashboard/analytics', {
const response = await api.get<AnalyticsData>('/admin/dashboard/analytics', {
params: { days }
});
return response.data;
@@ -88,7 +88,7 @@ export const adminService = {
// System health check
async getSystemHealth(): Promise<SystemHealth> {
const response = await api.get<SystemHealth>('/api/admin/dashboard/health');
const response = await api.get<SystemHealth>('/admin/dashboard/health');
return response.data;
},
@@ -124,6 +124,6 @@ export const adminService = {
// Change password
async changePassword(data: { currentPassword: string; newPassword: string }): Promise<void> {
await api.post('/api/admin/auth/change-password', data);
await api.post('/admin/auth/change-password', data);
}
};
+5 -5
View File
@@ -46,7 +46,7 @@ export interface ArchivesResponse {
export const archiveService = {
// Get all archives with pagination
async getArchives(page: number = 1, limit: number = 20): Promise<ArchivesResponse> {
const response = await api.get<ArchivesResponse>('/api/admin/archives', {
const response = await api.get<ArchivesResponse>('/admin/archives', {
params: { page, limit }
});
return response.data;
@@ -54,18 +54,18 @@ export const archiveService = {
// Get single archive details
async getArchiveDetails(id: number): Promise<ArchiveDetails> {
const response = await api.get<ArchiveDetails>(`/api/admin/archives/${id}`);
const response = await api.get<ArchiveDetails>(`/admin/archives/${id}`);
return response.data;
},
// Restore archive
async restoreArchive(id: number): Promise<void> {
await api.post(`/api/admin/archives/${id}/restore`);
await api.post(`/admin/archives/${id}/restore`);
},
// Download archive
async downloadArchive(id: number, filename: string): Promise<void> {
const response = await api.get(`/api/admin/archives/${id}/download`, {
const response = await api.get(`/admin/archives/${id}/download`, {
responseType: 'blob'
});
@@ -82,7 +82,7 @@ export const archiveService = {
// Delete archive permanently
async deleteArchive(id: number): Promise<void> {
await api.delete(`/api/admin/archives/${id}`);
await api.delete(`/admin/archives/${id}`);
},
// Format bytes to human readable
+2 -2
View File
@@ -5,7 +5,7 @@ export const authService = {
// Admin authentication
async adminLogin(credentials: { email: string; password: string; recaptchaToken?: string | null }): Promise<LoginResponse> {
// Backend expects 'username' field, but we accept email
const response = await api.post<LoginResponse>('/api/auth/admin/login', {
const response = await api.post<LoginResponse>('/auth/admin/login', {
username: credentials.email,
password: credentials.password,
recaptchaToken: credentials.recaptchaToken
@@ -22,7 +22,7 @@ export const authService = {
// Gallery authentication
async verifyGalleryPassword(slug: string, password: string, recaptchaToken?: string | null): Promise<GalleryAuthResponse> {
const response = await api.post<GalleryAuthResponse>('/api/auth/gallery/verify', {
const response = await api.post<GalleryAuthResponse>('/auth/gallery/verify', {
slug,
password,
recaptchaToken
+5 -5
View File
@@ -19,30 +19,30 @@ export interface CreateCategoryData {
export const categoriesService = {
// Get all global categories
async getGlobalCategories(): Promise<PhotoCategory[]> {
const response = await api.get<PhotoCategory[]>('/api/admin/categories/global');
const response = await api.get<PhotoCategory[]>('/admin/categories/global');
return response.data;
},
// Get categories for a specific event (global + event-specific)
async getEventCategories(eventId: number): Promise<PhotoCategory[]> {
const response = await api.get<PhotoCategory[]>(`/api/admin/categories/event/${eventId}`);
const response = await api.get<PhotoCategory[]>(`/admin/categories/event/${eventId}`);
return response.data;
},
// Create a new category
async createCategory(data: CreateCategoryData): Promise<PhotoCategory> {
const response = await api.post<PhotoCategory>('/api/admin/categories', data);
const response = await api.post<PhotoCategory>('/admin/categories', data);
return response.data;
},
// Update a category
async updateCategory(id: number, name: string): Promise<PhotoCategory> {
const response = await api.put<PhotoCategory>(`/api/admin/categories/${id}`, { name });
const response = await api.put<PhotoCategory>(`/admin/categories/${id}`, { name });
return response.data;
},
// Delete a category
async deleteCategory(id: number): Promise<void> {
await api.delete(`/api/admin/categories/${id}`);
await api.delete(`/admin/categories/${id}`);
}
};
+4 -4
View File
@@ -13,25 +13,25 @@ export interface CMSPage {
export const cmsService = {
// Get all CMS pages
async getPages(): Promise<CMSPage[]> {
const response = await api.get<CMSPage[]>('/api/admin/cms/pages');
const response = await api.get<CMSPage[]>('/admin/cms/pages');
return response.data;
},
// Get a single CMS page
async getPage(slug: string): Promise<CMSPage> {
const response = await api.get<CMSPage>(`/api/admin/cms/pages/${slug}`);
const response = await api.get<CMSPage>(`/admin/cms/pages/${slug}`);
return response.data;
},
// Update a CMS page
async updatePage(slug: string, data: Partial<CMSPage>): Promise<CMSPage> {
const response = await api.put<CMSPage>(`/api/admin/cms/pages/${slug}`, data);
const response = await api.put<CMSPage>(`/admin/cms/pages/${slug}`, data);
return response.data;
},
// Get public CMS page (no auth required)
async getPublicPage(slug: string, lang: string = 'en'): Promise<{ title: string; content: string }> {
const response = await api.get<{ title: string; content: string }>(`/api/public/pages/${slug}`, {
const response = await api.get<{ title: string; content: string }>(`/public/pages/${slug}`, {
params: { lang }
});
return response.data;
+7 -7
View File
@@ -35,41 +35,41 @@ export interface EmailPreview {
export const emailService = {
// Get email configuration
async getConfig(): Promise<EmailConfig> {
const response = await api.get<EmailConfig>('/api/admin/email/config');
const response = await api.get<EmailConfig>('/admin/email/config');
return response.data;
},
// Update email configuration
async updateConfig(config: EmailConfig): Promise<void> {
await api.post('/api/admin/email/config', config);
await api.post('/admin/email/config', config);
},
// Test email configuration
async testEmail(testEmail: string): Promise<void> {
await api.post('/api/admin/email/test', { test_email: testEmail });
await api.post('/admin/email/test', { test_email: testEmail });
},
// Get all email templates
async getTemplates(): Promise<EmailTemplate[]> {
const response = await api.get<EmailTemplate[]>('/api/admin/email/templates');
const response = await api.get<EmailTemplate[]>('/admin/email/templates');
return response.data;
},
// Get single template
async getTemplate(key: string): Promise<EmailTemplate> {
const response = await api.get<EmailTemplate>(`/api/admin/email/templates/${key}`);
const response = await api.get<EmailTemplate>(`/admin/email/templates/${key}`);
return response.data;
},
// Update email template
async updateTemplate(key: string, template: Partial<EmailTemplate>): Promise<void> {
await api.put(`/api/admin/email/templates/${key}`, template);
await api.put(`/admin/email/templates/${key}`, template);
},
// Preview email template
async previewTemplate(key: string, previewData: Record<string, string>, language: 'en' | 'de' = 'en'): Promise<EmailPreview> {
const response = await api.post<EmailPreview>(
`/api/admin/email/templates/${key}/preview`,
`/admin/email/templates/${key}/preview`,
{ preview_data: previewData, language }
);
return response.data;
+10 -10
View File
@@ -53,36 +53,36 @@ export const eventsService = {
params.append('status', status);
}
const response = await api.get<EventsListResponse>(`/api/admin/events?${params}`);
const response = await api.get<EventsListResponse>(`/admin/events?${params}`);
return response.data;
},
// Get single event details (admin)
async getEvent(id: number): Promise<Event> {
const response = await api.get<Event>(`/api/admin/events/${id}`);
const response = await api.get<Event>(`/admin/events/${id}`);
return response.data;
},
// Create new event (admin)
async createEvent(data: CreateEventData): Promise<Event> {
const response = await api.post<Event>('/api/admin/events', data);
const response = await api.post<Event>('/admin/events', data);
return response.data;
},
// Update event (admin)
async updateEvent(id: number, data: UpdateEventData): Promise<Event> {
const response = await api.put<Event>(`/api/admin/events/${id}`, data);
const response = await api.put<Event>(`/admin/events/${id}`, data);
return response.data;
},
// Delete/deactivate event (admin)
async deleteEvent(id: number): Promise<void> {
await api.delete(`/api/admin/events/${id}`);
await api.delete(`/admin/events/${id}`);
},
// Force archive event (admin)
async archiveEvent(id: number): Promise<void> {
await api.post(`/api/admin/events/${id}/archive`);
await api.post(`/admin/events/${id}/archive`);
},
// Bulk archive events (admin)
@@ -93,7 +93,7 @@ export const eventsService = {
failed: Array<{ id: number; name: string; error: string }>;
};
}> {
const response = await api.post('/api/admin/events/bulk-archive', {
const response = await api.post('/admin/events/bulk-archive', {
eventIds,
});
return response.data;
@@ -101,7 +101,7 @@ export const eventsService = {
// Extend event expiration (admin)
async extendExpiration(id: number, days: number): Promise<Event> {
const response = await api.post<Event>(`/api/events/${id}/extend`, {
const response = await api.post<Event>(`/events/${id}/extend`, {
days,
});
return response.data;
@@ -109,13 +109,13 @@ export const eventsService = {
// Get event categories
async getEventCategories(eventId: number): Promise<Array<{ id: number; name: string; slug: string }>> {
const response = await api.get(`/api/admin/categories/event/${eventId}`);
const response = await api.get(`/admin/categories/event/${eventId}`);
return response.data || [];
},
// Reset event password
async resetPassword(eventId: number, sendEmail: boolean = true): Promise<{ message: string; newPassword: string; emailSent: boolean }> {
const response = await api.post(`/api/admin/events/${eventId}/reset-password`, { sendEmail });
const response = await api.post(`/admin/events/${eventId}/reset-password`, { sendEmail });
return response.data;
},
};
+6 -6
View File
@@ -4,26 +4,26 @@ import type { GalleryInfo, GalleryData, GalleryStats } from '../types';
export const galleryService = {
// Verify share token
async verifyToken(slug: string, token: string): Promise<{ valid: boolean }> {
const response = await api.get<{ valid: boolean }>(`/api/gallery/${slug}/verify-token/${token}`);
const response = await api.get<{ valid: boolean }>(`/gallery/${slug}/verify-token/${token}`);
return response.data;
},
// Get basic gallery info (no auth required)
async getGalleryInfo(slug: string, token?: string): Promise<GalleryInfo> {
const params = token ? { token } : {};
const response = await api.get<GalleryInfo>(`/api/gallery/${slug}/info`, { params });
const response = await api.get<GalleryInfo>(`/gallery/${slug}/info`, { params });
return response.data;
},
// Get gallery photos (requires auth)
async getGalleryPhotos(slug: string): Promise<GalleryData> {
const response = await api.get<GalleryData>(`/api/gallery/${slug}/photos`);
const response = await api.get<GalleryData>(`/gallery/${slug}/photos`);
return response.data;
},
// Download single photo
async downloadPhoto(slug: string, photoId: number, filename: string): Promise<void> {
const response = await api.get(`/api/gallery/${slug}/download/${photoId}`, {
const response = await api.get(`/gallery/${slug}/download/${photoId}`, {
responseType: 'blob',
});
@@ -40,7 +40,7 @@ export const galleryService = {
// Download all photos as ZIP
async downloadAllPhotos(slug: string): Promise<void> {
const response = await api.get(`/api/gallery/${slug}/download-all`, {
const response = await api.get(`/gallery/${slug}/download-all`, {
responseType: 'blob',
});
@@ -57,7 +57,7 @@ export const galleryService = {
// Get gallery statistics
async getGalleryStats(slug: string): Promise<GalleryStats> {
const response = await api.get<GalleryStats>(`/api/gallery/${slug}/stats`);
const response = await api.get<GalleryStats>(`/gallery/${slug}/stats`);
return response.data;
},
};
@@ -22,7 +22,7 @@ export interface NotificationsResponse {
export const notificationsService = {
// Get notifications
async getNotifications(includeRead: boolean = false, limit: number = 20): Promise<NotificationsResponse> {
const response = await api.get('/api/admin/notifications', {
const response = await api.get('/admin/notifications', {
params: { includeRead, limit }
});
return response.data;
@@ -30,17 +30,17 @@ export const notificationsService = {
// Mark single notification as read
async markAsRead(notificationId: number): Promise<void> {
await api.put(`/api/admin/notifications/${notificationId}/read`);
await api.put(`/admin/notifications/${notificationId}/read`);
},
// Mark all notifications as read
async markAllAsRead(): Promise<void> {
await api.put('/api/admin/notifications/read-all');
await api.put('/admin/notifications/read-all');
},
// Clear old notifications
async clearOldNotifications(): Promise<{ deletedCount: number }> {
const response = await api.delete('/api/admin/notifications/clear-old');
const response = await api.delete('/admin/notifications/clear-old');
return response.data;
},
+6 -6
View File
@@ -39,7 +39,7 @@ class PhotosService {
}
const queryString = params.toString();
const url = `/api/admin/events/${eventId}/photos${queryString ? `?${queryString}` : ''}`;
const url = `/admin/events/${eventId}/photos${queryString ? `?${queryString}` : ''}`;
const response = await api.get(url);
@@ -48,26 +48,26 @@ class PhotosService {
}
async deletePhoto(eventId: number, photoId: number): Promise<void> {
await api.delete(`/api/admin/events/${eventId}/photos/${photoId}`);
await api.delete(`/admin/events/${eventId}/photos/${photoId}`);
}
async deletePhotos(eventId: number, photoIds: number[]): Promise<void> {
await api.post(`/api/admin/events/${eventId}/photos/bulk-delete`, { photoIds });
await api.post(`/admin/events/${eventId}/photos/bulk-delete`, { photoIds });
}
async updatePhotoCategory(eventId: number, photoId: number, categoryId: number | null): Promise<void> {
await api.patch(`/api/admin/events/${eventId}/photos/${photoId}`, { category_id: categoryId });
await api.patch(`/admin/events/${eventId}/photos/${photoId}`, { category_id: categoryId });
}
async updatePhotosCategory(eventId: number, photoIds: number[], categoryId: number | null): Promise<void> {
await api.post(`/api/admin/events/${eventId}/photos/bulk-update`, {
await api.post(`/admin/events/${eventId}/photos/bulk-update`, {
photoIds,
updates: { category_id: categoryId }
});
}
async downloadPhoto(eventId: number, photoId: number, filename: string): Promise<void> {
const response = await api.get(`/api/admin/events/${eventId}/photos/${photoId}/download`, {
const response = await api.get(`/admin/events/${eventId}/photos/${photoId}/download`, {
responseType: 'blob'
});
+7 -7
View File
@@ -79,19 +79,19 @@ export interface SystemStatus {
export const settingsService = {
// Get all settings
async getAllSettings(): Promise<Record<string, any>> {
const response = await api.get<Record<string, any>>('/api/admin/settings');
const response = await api.get<Record<string, any>>('/admin/settings');
return response.data;
},
// Get settings by type
async getSettingsByType(type: 'branding' | 'theme' | 'general'): Promise<Record<string, any>> {
const response = await api.get<Record<string, any>>(`/api/admin/settings/${type}`);
const response = await api.get<Record<string, any>>(`/admin/settings/${type}`);
return response.data;
},
// Update branding settings
async updateBranding(settings: BrandingSettings): Promise<void> {
await api.put('/api/admin/settings/branding', settings);
await api.put('/admin/settings/branding', settings);
},
// Upload logo
@@ -150,23 +150,23 @@ export const settingsService = {
// Update theme settings
async updateTheme(settings: ThemeSettings): Promise<void> {
await api.put('/api/admin/settings/theme', settings);
await api.put('/admin/settings/theme', settings);
},
// Update multiple settings at once
async updateSettings(settings: Record<string, any>): Promise<void> {
await api.put('/api/admin/settings/general', settings);
await api.put('/admin/settings/general', settings);
},
// Get storage information
async getStorageInfo(): Promise<StorageInfo> {
const response = await api.get<StorageInfo>('/api/admin/settings/storage/info');
const response = await api.get<StorageInfo>('/admin/settings/storage/info');
return response.data;
},
// Get system status
async getSystemStatus(): Promise<SystemStatus> {
const response = await api.get<SystemStatus>('/api/admin/system/status');
const response = await api.get<SystemStatus>('/admin/system/status');
return response.data;
},