diff --git a/apps/citycorners/apps/backend/src/location/location-lookup.service.ts b/apps/citycorners/apps/backend/src/location/location-lookup.service.ts
index 37a22e267..74f459c3a 100644
--- a/apps/citycorners/apps/backend/src/location/location-lookup.service.ts
+++ b/apps/citycorners/apps/backend/src/location/location-lookup.service.ts
@@ -6,6 +6,7 @@ export interface LookupResult {
description: string;
address?: string;
category?: string;
+ imageUrl?: string;
sources: { url: string; title: string }[];
}
@@ -82,11 +83,15 @@ export class LocationLookupService {
// Build a description from the best snippet or extracted text
const description = this.buildDescription(snippets, extractedTexts);
+ // Try to find an image URL from search results
+ const imageUrl = this.extractImageUrl(results);
+
return {
name: query,
description,
address,
category,
+ imageUrl,
sources: results.slice(0, 5).map((r: any) => ({
url: r.url,
title: r.title,
@@ -140,6 +145,28 @@ export class LocationLookupService {
return 'sight';
}
+ private extractImageUrl(results: any[]): string | undefined {
+ for (const result of results) {
+ // SearXNG results may include img_src or thumbnail
+ if (result.img_src && this.isValidImageUrl(result.img_src)) {
+ return result.img_src;
+ }
+ if (result.thumbnail && this.isValidImageUrl(result.thumbnail)) {
+ return result.thumbnail;
+ }
+ }
+ return undefined;
+ }
+
+ private isValidImageUrl(url: string): boolean {
+ try {
+ const parsed = new URL(url);
+ return parsed.protocol === 'https:' && /\.(jpg|jpeg|png|webp)/i.test(parsed.pathname);
+ } catch {
+ return false;
+ }
+ }
+
private buildDescription(snippets: string[], extractedTexts: string[]): string {
// Prefer extracted text (more detailed)
if (extractedTexts.length > 0) {
diff --git a/apps/citycorners/apps/web/src/lib/i18n/locales/de.json b/apps/citycorners/apps/web/src/lib/i18n/locales/de.json
index 7e9f5b0f3..f78917c1e 100644
--- a/apps/citycorners/apps/web/src/lib/i18n/locales/de.json
+++ b/apps/citycorners/apps/web/src/lib/i18n/locales/de.json
@@ -96,7 +96,13 @@
"submit": "Ort einreichen",
"submitting": "Wird eingereicht...",
"loginRequired": "Melde dich an, um Orte hinzuzufügen.",
- "error": "Fehler beim Einreichen. Bitte versuche es erneut."
+ "error": "Fehler beim Einreichen. Bitte versuche es erneut.",
+ "imageUrl": "Bild-URL (optional)",
+ "imageUrlPlaceholder": "https://example.com/bild.jpg",
+ "imagePreview": "Bildvorschau",
+ "imageLoadError": "Bild konnte nicht geladen werden.",
+ "geocoding": "Koordinaten werden ermittelt...",
+ "coordinatesFound": "Koordinaten gefunden"
},
"offline": {
"title": "Keine Verbindung",
diff --git a/apps/citycorners/apps/web/src/lib/i18n/locales/en.json b/apps/citycorners/apps/web/src/lib/i18n/locales/en.json
index 352b0a2a7..2ee6fead0 100644
--- a/apps/citycorners/apps/web/src/lib/i18n/locales/en.json
+++ b/apps/citycorners/apps/web/src/lib/i18n/locales/en.json
@@ -96,7 +96,13 @@
"submit": "Submit place",
"submitting": "Submitting...",
"loginRequired": "Sign in to add places.",
- "error": "Failed to submit. Please try again."
+ "error": "Failed to submit. Please try again.",
+ "imageUrl": "Image URL (optional)",
+ "imageUrlPlaceholder": "https://example.com/image.jpg",
+ "imagePreview": "Image preview",
+ "imageLoadError": "Image could not be loaded.",
+ "geocoding": "Finding coordinates...",
+ "coordinatesFound": "Coordinates found"
},
"offline": {
"title": "No connection",
diff --git a/apps/citycorners/apps/web/src/routes/(app)/+page.svelte b/apps/citycorners/apps/web/src/routes/(app)/+page.svelte
index b86655c89..6695b0c71 100644
--- a/apps/citycorners/apps/web/src/routes/(app)/+page.svelte
+++ b/apps/citycorners/apps/web/src/routes/(app)/+page.svelte
@@ -53,9 +53,20 @@
- {$_('home.title')}
- {$_('home.subtitle')}
+
+
+
{$_('home.title')}
+
{$_('home.subtitle')}
+
+
+
+
diff --git a/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte b/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte
index 381e700e1..feb9d811f 100644
--- a/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte
+++ b/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte
@@ -15,8 +15,13 @@
let category = $state
('sight');
let description = $state('');
let address = $state('');
+ let imageUrl = $state('');
+ let latitude = $state(undefined);
+ let longitude = $state(undefined);
let submitting = $state(false);
let error = $state('');
+ let geocoding = $state(false);
+ let imageError = $state(false);
const categories = [
{ value: 'sight', labelKey: 'category.sight' },
@@ -46,13 +51,25 @@
address = data.result.address || '';
category = data.result.category || 'sight';
sources = data.result.sources || [];
+ if (data.result.imageUrl) {
+ imageUrl = data.result.imageUrl;
+ imageError = false;
+ }
+ if (data.result.latitude && data.result.longitude) {
+ latitude = data.result.latitude;
+ longitude = data.result.longitude;
+ }
} else {
name = searchQuery.trim();
}
lookupDone = true;
+
+ // Auto-geocode if we got an address but no coordinates
+ if (address && !latitude) {
+ geocodeAddress();
+ }
} catch {
- // Fallback: just use the search query as name
name = searchQuery.trim();
lookupDone = true;
} finally {
@@ -71,9 +88,46 @@
name = '';
description = '';
address = '';
+ imageUrl = '';
category = 'sight';
+ latitude = undefined;
+ longitude = undefined;
sources = [];
error = '';
+ imageError = false;
+ }
+
+ async function geocodeAddress() {
+ const addr = address.trim();
+ if (!addr) return;
+
+ geocoding = true;
+ try {
+ const q = addr.includes('Konstanz') ? addr : `${addr}, Konstanz`;
+ const res = await fetch(
+ `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(q)}&limit=1`,
+ { headers: { 'User-Agent': 'CityCorners/1.0' } }
+ );
+ const results = await res.json();
+ if (results.length > 0) {
+ latitude = parseFloat(results[0].lat);
+ longitude = parseFloat(results[0].lon);
+ }
+ } catch {
+ // Geocoding is best-effort
+ } finally {
+ geocoding = false;
+ }
+ }
+
+ let geocodeTimeout: ReturnType | undefined;
+ function handleAddressInput() {
+ clearTimeout(geocodeTimeout);
+ geocodeTimeout = setTimeout(() => {
+ if (address.trim().length > 5) {
+ geocodeAddress();
+ }
+ }, 1000);
}
async function handleSubmit() {
@@ -89,18 +143,25 @@
return;
}
+ const body: Record = {
+ name: name.trim(),
+ category,
+ description: description.trim(),
+ };
+ if (address.trim()) body.address = address.trim();
+ if (imageUrl.trim() && !imageError) body.imageUrl = imageUrl.trim();
+ if (latitude !== undefined && longitude !== undefined) {
+ body.latitude = latitude;
+ body.longitude = longitude;
+ }
+
const res = await fetch(api('/locations'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
- body: JSON.stringify({
- name: name.trim(),
- category,
- description: description.trim(),
- address: address.trim() || undefined,
- }),
+ body: JSON.stringify(body),
});
if (res.ok) {
@@ -261,9 +322,42 @@
id="address"
type="text"
bind:value={address}
+ oninput={handleAddressInput}
placeholder={$_('add.addressPlaceholder')}
class="w-full rounded-lg border border-border bg-background px-4 py-2.5 text-foreground placeholder:text-foreground-secondary/50 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
/>
+ {#if geocoding}
+ {$_('add.geocoding')}
+ {:else if latitude !== undefined && longitude !== undefined}
+ {$_('add.coordinatesFound')}
+ {/if}
+
+
+
+
+
+
(imageError = false)}
+ placeholder={$_('add.imageUrlPlaceholder')}
+ class="w-full rounded-lg border border-border bg-background px-4 py-2.5 text-foreground placeholder:text-foreground-secondary/50 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary"
+ />
+ {#if imageUrl.trim() && !imageError}
+
+

(imageError = true)}
+ />
+
+ {:else if imageError}
+
{$_('add.imageLoadError')}
+ {/if}
diff --git a/apps/todo/apps/web/src/routes/(app)/+page.svelte b/apps/todo/apps/web/src/routes/(app)/+page.svelte
index 8d2cb69c1..ca6274bb4 100644
--- a/apps/todo/apps/web/src/routes/(app)/+page.svelte
+++ b/apps/todo/apps/web/src/routes/(app)/+page.svelte
@@ -4,6 +4,7 @@
import { de } from 'date-fns/locale';
import { Sparkle, ArrowDown } from '@manacore/shared-icons';
import { tasksStore } from '$lib/stores/tasks.svelte';
+ import { authStore } from '$lib/stores/auth.svelte';
import { viewStore } from '$lib/stores/view.svelte';
import { applyTaskFilters } from '$lib/utils/task-filters';
import TaskList from '$lib/components/TaskList.svelte';
@@ -29,6 +30,11 @@
onMount(async () => {
viewStore.setToday();
+ // Wait for auth to be initialized (layout onMount runs after page onMount in Svelte)
+ while (!authStore.initialized) {
+ await new Promise((r) => setTimeout(r, 50));
+ }
+
try {
// Fetch tasks (works in both guest and authenticated mode)
await tasksStore.fetchAllTasks();
diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml
index 6ff54ea46..757a76493 100644
--- a/docker-compose.macmini.yml
+++ b/docker-compose.macmini.yml
@@ -6,7 +6,6 @@
# 4000-4099: Matrix Stack
# 5000-5099: Web Frontends
# 5100-5199: Games
-# 6000-6099: Automation & Workflows
# 8000-8099: Monitoring Dashboards
# 9000-9199: Infrastructure & Exporters
#
@@ -32,7 +31,7 @@ services:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
- interval: 10s
+ interval: 30s
timeout: 5s
retries: 5
@@ -47,7 +46,7 @@ services:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
- interval: 10s
+ interval: 30s
timeout: 5s
retries: 5
@@ -67,7 +66,7 @@ services:
- "9001:9001"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
- interval: 30s
+ interval: 120s
timeout: 20s
retries: 3
@@ -163,7 +162,7 @@ services:
- "3001:3001"
healthcheck:
test: ["CMD", "node", "-e", "const http = require('http'); http.get('http://127.0.0.1:3001/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -203,7 +202,7 @@ services:
- "3010:3010"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3010/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -221,7 +220,7 @@ services:
# Internal only - no external port mapping
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/healthz"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 15s
@@ -254,7 +253,7 @@ services:
- "3020:3020"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3020/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -294,7 +293,7 @@ services:
- "3015:3015"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3015/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -320,7 +319,7 @@ services:
- "3050:3050"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3050/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -356,7 +355,7 @@ services:
- "3030:3030"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3030/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -383,7 +382,7 @@ services:
- "3031:3031"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3031/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -414,7 +413,7 @@ services:
- "3032:3032"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3032/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -444,7 +443,7 @@ services:
- "3033:3033"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3033/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -480,7 +479,7 @@ services:
- "3034:3034"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3034/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -515,7 +514,7 @@ services:
- "3035:3035"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3035/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -542,7 +541,7 @@ services:
- "3036:3036"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3036/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -572,7 +571,7 @@ services:
- "3037:3037"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3037/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -601,7 +600,7 @@ services:
- "3038:3038"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3038/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -634,7 +633,7 @@ services:
- "3039:3039"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3039/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -664,7 +663,7 @@ services:
- "3007:3007"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3007/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -697,7 +696,7 @@ services:
- "3010:3010"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3010/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -735,7 +734,7 @@ services:
- "3022:3022"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3022/api/v1/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -765,7 +764,7 @@ services:
- "3041:3041"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3041/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -800,7 +799,7 @@ services:
- "9002:9002" # Metrics
healthcheck:
test: ["CMD", "curl", "-fSs", "http://localhost:8008/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 60s
@@ -818,7 +817,7 @@ services:
- "4080:80"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -840,7 +839,7 @@ services:
- "4090:5180"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -879,7 +878,7 @@ services:
- "4010:4010"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4010/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -908,7 +907,7 @@ services:
- "4011:4011"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -949,7 +948,7 @@ services:
- "4012:4012"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4012/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -982,7 +981,7 @@ services:
- "4013:4013"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4013/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1018,7 +1017,7 @@ services:
- "4014:4014"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4014/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1052,7 +1051,7 @@ services:
- "4015:4015"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4015/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1086,7 +1085,7 @@ services:
- "4016:4016"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4016/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1116,7 +1115,7 @@ services:
- "4017:4017"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4017/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1157,7 +1156,7 @@ services:
- "4018:4018"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4018/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1191,7 +1190,7 @@ services:
- "4019:4019"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4019/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1224,7 +1223,7 @@ services:
- "4021:4021"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4021/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1261,7 +1260,7 @@ services:
- "4020:4020"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4020/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1301,7 +1300,7 @@ services:
- "4022:4022"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4022/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1337,7 +1336,7 @@ services:
- "5000:5000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5000/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1360,7 +1359,7 @@ services:
- "5010:5010"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5010/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1386,7 +1385,7 @@ services:
- "5011:5011"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5011/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1412,7 +1411,7 @@ services:
- "5018:5018"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5018/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1440,7 +1439,7 @@ services:
- "5012:5012"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5012/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1463,7 +1462,7 @@ services:
- "5013:5013"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5013/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1493,7 +1492,7 @@ services:
- "5014:5014"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5014/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1522,7 +1521,7 @@ services:
- "5015:5015"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5015/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1545,7 +1544,7 @@ services:
- "5016:5016"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5016/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1568,7 +1567,7 @@ services:
- "5017:5017"
healthcheck:
test: ["CMD", "node", "-e", "const http = require('http'); http.get('http://127.0.0.1:5017/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1597,7 +1596,7 @@ services:
- "5020:5020"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5020/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1629,7 +1628,7 @@ services:
- "5019:5019"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5019/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1658,7 +1657,7 @@ services:
- "5180:5180"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1687,7 +1686,7 @@ services:
- "5022:5022"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5022/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1726,7 +1725,7 @@ services:
- "3040:3040"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3040/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1755,7 +1754,7 @@ services:
- "5021:5021"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5021/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1790,7 +1789,7 @@ services:
- "3025:3025"
healthcheck:
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:3025/health').raise_for_status()"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 30s
@@ -1817,53 +1816,13 @@ services:
- "5090:5090"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5090/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "com.centurylinklabs.watchtower.enable=true"
- # ============================================
- # Tier 6: Automation & Workflows (Ports 6000-6099)
- # ============================================
-
- n8n:
- image: n8nio/n8n:latest
- container_name: mana-auto-n8n
- restart: always
- depends_on:
- postgres:
- condition: service_healthy
- environment:
- DB_TYPE: postgresdb
- DB_POSTGRESDB_HOST: postgres
- DB_POSTGRESDB_PORT: 5432
- DB_POSTGRESDB_DATABASE: n8n
- DB_POSTGRESDB_USER: postgres
- DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:-mana123}
- N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:-change-me-n8n-encryption-key}
- N8N_USER_MANAGEMENT_JWT_SECRET: ${N8N_JWT_SECRET:-change-me-n8n-jwt-secret}
- N8N_HOST: n8n.mana.how
- N8N_PROTOCOL: https
- WEBHOOK_URL: https://n8n.mana.how/
- N8N_BASIC_AUTH_ACTIVE: "false"
- N8N_PORT: 6000
- GENERIC_TIMEZONE: Europe/Berlin
- TZ: Europe/Berlin
- N8N_DIAGNOSTICS_ENABLED: "false"
- N8N_VERSION_NOTIFICATIONS_ENABLED: "false"
- volumes:
- - n8n_data:/home/node/.n8n
- ports:
- - "6000:6000"
- healthcheck:
- test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:6000/healthz"]
- interval: 30s
- timeout: 10s
- retries: 3
- start_period: 40s
-
# ============================================
# Tier 7: Monitoring Dashboards (Ports 8000-8099)
# ============================================
@@ -1893,7 +1852,7 @@ services:
- "8000:8000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -1913,7 +1872,7 @@ services:
- "8010:3000"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
start_period: 40s
@@ -1942,7 +1901,7 @@ services:
- "9090:9090"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -1954,7 +1913,7 @@ services:
- "9091:9091"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -1973,7 +1932,7 @@ services:
- "9110:8080"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2022,7 +1981,7 @@ services:
- "9100:9100"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2053,7 +2012,7 @@ services:
- "8880:8880"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2075,7 +2034,7 @@ services:
- "9093:9093"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2095,7 +2054,7 @@ services:
- "9095:8080"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"]
- interval: 30s
+ interval: 120s
timeout: 5s
retries: 3
start_period: 5s
@@ -2148,7 +2107,7 @@ services:
condition: service_healthy
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2189,7 +2148,7 @@ services:
- "5100:5100"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"]
- interval: 30s
+ interval: 120s
timeout: 10s
retries: 3
@@ -2204,7 +2163,5 @@ volumes:
name: mana-grafana-data
analytics_data:
name: mana-analytics-data
- n8n_data:
- name: mana-n8n-data
matrix_bots_data:
name: mana-matrix-bots-data
diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts
index 4767fbb28..2e7fe3bf8 100644
--- a/services/mana-core-auth/src/auth/services/better-auth.service.ts
+++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts
@@ -1139,15 +1139,16 @@ export class BetterAuthService {
// Decode to check the algorithm
const decoded = jwt.decode(token, { complete: true });
- // Use our JWKS endpoint (NestJS prefix: /api/v1)
- const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001';
- const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
+ // Use our JWKS endpoint via localhost (self-referencing avoids external URL issues in Docker)
+ const port = this.configService.get('PORT') || 3001;
+ const jwksUrl = new URL(`http://localhost:${port}/api/v1/auth/jwks`);
// Create JWKS fetcher
const JWKS = createRemoteJWKSet(jwksUrl);
// IMPORTANT: Match Better Auth signing config exactly (better-auth.config.ts)
// Signing uses: issuer = BASE_URL, audience = JWT_AUDIENCE || 'manacore'
+ const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001';
const issuer = baseUrl; // Better Auth uses BASE_URL as issuer for OIDC compatibility
const audience = this.configService.get('jwt.audience') || 'manacore';
diff --git a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
index de1970822..04618b9c5 100644
--- a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
+++ b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
@@ -35,10 +35,10 @@ export class JwtAuthGuard implements CanActivate {
}
try {
- // Lazy initialize JWKS
+ // Lazy initialize JWKS via localhost (self-referencing avoids external URL issues in Docker)
if (!this.jwks) {
- const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001';
- const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
+ const port = this.configService.get('PORT') || 3001;
+ const jwksUrl = new URL(`http://localhost:${port}/api/v1/auth/jwks`);
this.jwks = createRemoteJWKSet(jwksUrl);
}