From a31ccc6c62abdf055aa73f6b7efa587968282de5 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 27 Mar 2026 21:27:04 +0100 Subject: [PATCH] feat(infra): add api.mana.how route + Prometheus scrape targets for Go services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cloudflare Tunnel: api.mana.how → localhost:3060 (Go API Gateway) - Prometheus: scrape targets for mana-api-gateway:3060 and mana-matrix-bot:4000 Co-Authored-By: Claude Opus 4.6 (1M context) --- CLAUDE.md | 2 + apps/citycorners/apps/web/package.json | 1 + .../apps/web/src/lib/data/guest-seed.ts | 49 ++++ .../apps/web/src/lib/data/local-store.ts | 63 ++++++ .../apps/web/src/routes/(app)/+layout.svelte | 210 ++++++++++-------- apps/skilltree/apps/web/package.json | 1 + .../apps/web/src/lib/data/guest-seed.ts | 51 +++++ .../apps/web/src/lib/data/local-store.ts | 71 ++++++ .../apps/web/src/routes/+layout.svelte | 7 + cloudflared-config.yml | 16 ++ docker/prometheus/prometheus.yml | 58 +++++ 11 files changed, 434 insertions(+), 95 deletions(-) create mode 100644 apps/citycorners/apps/web/src/lib/data/guest-seed.ts create mode 100644 apps/citycorners/apps/web/src/lib/data/local-store.ts create mode 100644 apps/skilltree/apps/web/src/lib/data/guest-seed.ts create mode 100644 apps/skilltree/apps/web/src/lib/data/local-store.ts diff --git a/CLAUDE.md b/CLAUDE.md index 35d10b9cc..a82618f59 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -543,6 +543,8 @@ Logged in: App → IndexedDB → UI → SyncEngine → mana-sync (Go) → Postg | Mukke | songs, playlists, playlistSongs, projects, markers | Done | | Context | spaces, documents | Done | | Photos | albums, albumItems, favorites, tags, photoTags | Done | +| SkilltTree | skills, activities, achievements | Done | +| CityCorners | locations, favorites | Done | ### Dev Commands (Local-First Stack) diff --git a/apps/citycorners/apps/web/package.json b/apps/citycorners/apps/web/package.json index 6a3f993e4..2ee445fed 100644 --- a/apps/citycorners/apps/web/package.json +++ b/apps/citycorners/apps/web/package.json @@ -33,6 +33,7 @@ "dependencies": { "@manacore/shared-auth": "workspace:*", "@manacore/shared-auth-ui": "workspace:*", + "@manacore/local-store": "workspace:*", "@manacore/shared-branding": "workspace:*", "@manacore/shared-error-tracking": "workspace:*", "@manacore/shared-feedback-service": "workspace:*", diff --git a/apps/citycorners/apps/web/src/lib/data/guest-seed.ts b/apps/citycorners/apps/web/src/lib/data/guest-seed.ts new file mode 100644 index 000000000..d062473d9 --- /dev/null +++ b/apps/citycorners/apps/web/src/lib/data/guest-seed.ts @@ -0,0 +1,49 @@ +/** + * Guest seed data for the CityCorners app. + * + * Provides iconic Konstanz locations for the onboarding experience. + */ + +import type { LocalLocation } from './local-store'; + +export const guestLocations: LocalLocation[] = [ + { + id: 'loc-muenster', + name: 'Konstanzer Münster', + category: 'sight', + description: + 'Das Münster Unserer Lieben Frau ist die ehemalige Bischofskirche des Bistums Konstanz und Wahrzeichen der Stadt.', + address: 'Münsterplatz 1, 78462 Konstanz', + latitude: 47.6603, + longitude: 9.1752, + }, + { + id: 'loc-imperia', + name: 'Imperia', + category: 'sight', + description: + 'Die 9 Meter hohe Statue im Hafen von Konstanz dreht sich einmal in 4 Minuten um ihre Achse.', + address: 'Hafen, 78462 Konstanz', + latitude: 47.6596, + longitude: 9.1789, + }, + { + id: 'loc-insel', + name: 'Mainau – Blumeninsel', + category: 'park', + description: + 'Die Blumeninsel Mainau im Bodensee ist bekannt für ihre Gärten, das Schmetterlingshaus und das Barockschloss.', + address: 'Mainau 1, 78465 Konstanz', + latitude: 47.7051, + longitude: 9.1919, + }, + { + id: 'loc-strandbad', + name: 'Strandbad Horn', + category: 'beach', + description: 'Beliebtes Freibad am Bodensee mit Sandstrand und Blick auf die Alpen.', + address: 'Eichhornstraße 100, 78464 Konstanz', + latitude: 47.6753, + longitude: 9.2001, + }, +]; diff --git a/apps/citycorners/apps/web/src/lib/data/local-store.ts b/apps/citycorners/apps/web/src/lib/data/local-store.ts new file mode 100644 index 000000000..264159914 --- /dev/null +++ b/apps/citycorners/apps/web/src/lib/data/local-store.ts @@ -0,0 +1,63 @@ +/** + * CityCorners — Local-First Data Layer + * + * Locations and favorites stored locally for offline browsing. + * Location lookup and web search remain server-side. + */ + +import { createLocalStore, type BaseRecord } from '@manacore/local-store'; +import { guestLocations } from './guest-seed'; + +// ─── Types ────────────────────────────────────────────────── + +export interface LocalLocation extends BaseRecord { + name: string; + category: + | 'sight' + | 'restaurant' + | 'shop' + | 'museum' + | 'cafe' + | 'bar' + | 'park' + | 'beach' + | 'hotel' + | 'event_venue' + | 'viewpoint'; + description?: string | null; + address?: string | null; + latitude?: number | null; + longitude?: number | null; + imageUrl?: string | null; + timeline?: Array<{ year: number; event: string }> | null; +} + +export interface LocalFavorite extends BaseRecord { + locationId: string; +} + +// ─── Store ────────────────────────────────────────────────── + +const SYNC_SERVER_URL = import.meta.env.PUBLIC_SYNC_SERVER_URL || 'http://localhost:3050'; + +export const citycornersStore = createLocalStore({ + appId: 'citycorners', + collections: [ + { + name: 'locations', + indexes: ['category', 'name'], + guestSeed: guestLocations, + }, + { + name: 'favorites', + indexes: ['locationId'], + }, + ], + sync: { + serverUrl: SYNC_SERVER_URL, + }, +}); + +// Typed collection accessors +export const locationCollection = citycornersStore.collection('locations'); +export const favoriteCollection = citycornersStore.collection('favorites'); diff --git a/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte b/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte index 24fae8f85..a3c0484b1 100644 --- a/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/citycorners/apps/web/src/routes/(app)/+layout.svelte @@ -9,6 +9,9 @@ import { theme } from '$lib/stores/theme.svelte'; import { authStore } from '$lib/stores/auth.svelte'; import { favoritesStore } from '$lib/stores/favorites.svelte'; + import { AuthGate, GuestWelcomeModal } from '@manacore/shared-auth-ui'; + import { shouldShowGuestWelcome } from '@manacore/shared-auth-ui'; + import { citycornersStore } from '$lib/data/local-store'; import { THEME_DEFINITIONS, DEFAULT_THEME_VARIANTS } from '@manacore/shared-theme'; import type { ThemeVariant } from '@manacore/shared-theme'; import { getPillAppItems } from '@manacore/shared-branding'; @@ -172,109 +175,126 @@ showNav = !showNav; } - onMount(async () => { - const savedNav = localStorage.getItem('citycorners-nav-visible'); - if (savedNav !== null) showNav = savedNav !== 'false'; + let showGuestWelcome = $state(false); + async function handleAuthReady() { + await citycornersStore.initialize(); if (authStore.isAuthenticated) { + citycornersStore.startSync(() => authStore.getValidToken()); await tagStore.fetchTags(); } - }); + if (!authStore.isAuthenticated && shouldShowGuestWelcome('citycorners')) { + showGuestWelcome = true; + } + const savedNav = localStorage.getItem('citycorners-nav-visible'); + if (savedNav !== null) showNav = savedNav !== 'false'; + } -
- {#if showNav} - - {/if} - - - {#if isTagStripVisible} - ({ - id: t.id, - name: t.name, - color: t.color || '#3b82f6', - }))} - selectedIds={[]} - onToggle={() => {}} - onClear={() => {}} - managementHref="/tags" - loading={tagStore.loading} - /> - {/if} - - - - - -
-
- {@render children()} -
-
-
+ + {#if isTagStripVisible} + ({ + id: t.id, + name: t.name, + color: t.color || '#3b82f6', + }))} + selectedIds={[]} + onToggle={() => {}} + onClear={() => {}} + managementHref="/tags" + loading={tagStore.loading} + /> + {/if} + + + + + + +
+
+ {@render children()} +
+
+ + + (showGuestWelcome = false)} + onLogin={() => goto('/login')} + onRegister={() => goto('/register')} + locale={($locale || 'de') === 'de' ? 'de' : 'en'} + /> +