The footer and meta description tag used platform.description (raw column)
instead of platform.get_translated_description(), so DE and LB translations
were never displayed. FR/EN appeared to work because the base description
field was synced from the default language on admin save.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The store dashboard endpoint uses require_platform which needs the
PlatformContextMiddleware to resolve a platform. TestClient uses
'testserver' as Host, which the middleware maps to the 'main' platform.
Added _ensure_main_platform fixture to create it in the test DB.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The reset_all_data() function used `ContentPage.store_id is not None` which
is a Python identity check (always True), causing it to delete ALL content
pages including platform defaults. Same bug in print_summary() caused the
count to always show 0 platform pages. Fixed both to use proper SQLAlchemy
.is_(None) / .is_not(None) syntax.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ASCII diagram showing all services, Docker networks, port bindings,
and traffic flow from Cloudflare through Caddy to each container.
Clearly marks which ports are internet-exposed, localhost-only,
or Docker-internal-only.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add description_translations JSON column to Platform model + migration
- Add language tabs to platform admin edit form for multilingual descriptions
- Update API schemas to include description_translations in request/response
- Translate pricing section UI labels via _t() macro (monthly/annual/CTA/etc.)
- Add Luxembourgish (lb) support to all platforms (OMS, Main, Loyalty, Hosting)
- Seed description_translations, contact emails, and social links for all platforms
- Add LuxWeb Agency demo merchant with hosting stores, team, and content pages
- Fix language code typo: lu → lb in platform-edit.js availableLanguages
- Fix store content pages to use correct primary platform instead of hardcoded OMS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Gitea docker-compose in Step 7 to bind port 3000 to 127.0.0.1
- Add REDIS_PASSWORD to Step 10 critical production values
- Replace misleading UFW rules with Docker port binding instructions
- Add warning about Docker bypassing UFW in Step 14
- Add initial setup note for temporary Gitea port exposure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Redis password via REDIS_PASSWORD env var (--requirepass flag)
- Update all REDIS_URL and REDIS_ADDR references to include password
- Restrict /metrics endpoint to localhost and Docker internal networks (403 for external requests)
- Document Gitea port 3000 localhost binding fix (must be applied manually on server)
- Add REDIS_PASSWORD to .env.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Docker bypasses UFW iptables, so bare port mappings like "5432:5432"
exposed the database to the public internet. Removed port mappings for
PostgreSQL and Redis (they only need Docker-internal networking), and
bound the API port to 127.0.0.1 since only Caddy needs to reach it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add name_translations JSON column to SubscriptionTier for multi-language
tier names. Pre-resolve tier names and build dynamic feature lists from
module providers in route handlers. Fix Jinja2 macro scoping by importing
pricing partial with context. Backfill content_translations for all 43
content pages across 4 platforms (en/fr/de/lb).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add sections covering CMS locale file structure, translated template
inventory, TranslatableText pattern for sections, and the new
title_translations/content_translations model API with migration cms_002.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Phase 5: Translate homepage-modern.html (~90 new locale keys, all
hardcoded strings replaced with _() calls for dashboard mock,
features, pricing tiers, testimonial sections)
- Phase 6: Translate homepage-minimal.html (17 new locale keys for
fallback content, features, and CTA sections)
- Phase 7: Add multi-language page.title/content support with
title_translations and content_translations JSON columns, Alembic
migration cms_002, translated title/content resolution in templates,
and seed script updates with tt() helper
- Phase 8: Complete lb.json audit — fill 6 missing keys (messages,
confirmations), also backfill same keys in fr.json and de.json
All 4 locale files now have 340 keys with full parity.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the complete nuclear reset sequence (tested end-to-end):
stop → build → infra up → schema reset → migrations → seeds → start.
Update seeded data counts to match current output (30 CMS pages,
12 tiers, 3 admins, 28 email templates). Switch from exec to run --rm
for seed commands so they work before services are started.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
celery-beat was missing env_file and DATABASE_URL, so it had no access
to app config (Stripe keys, etc.). Also downgrade "Stripe API key not
configured" from warning to debug to stop log spam when Stripe is not
yet set up.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Celery worker was OOM-killed (41 restarts) at 512MB with 4 concurrent
workers. Reduce concurrency to 2, increase worker limit to 768MB, and
reclaim memory from over-provisioned services (db 512→256, beat 256→128,
flower 256→192). Total allocation stays within 4GB server budget.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redis had no maxmemory set, causing the Prometheus alert expression
(used/max) to evaluate to +Inf. Set maxmemory to 100mb with allkeys-lru
eviction policy, and guard the alert expression against division by zero.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove all hardcoded OMS-specific content from platform base template:
nav links, contact info, brand name, and footer columns. Everything is
now dynamic via platform model and CMS page queries. Wire up legal_pages
context (privacy/terms) from database instead of hardcoded fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_get_tiers_data() was querying all active tiers across all platforms.
Now accepts platform_id parameter to scope tiers to the current platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ProductCard/ProductsSection schema and _products.html section macro
- Rewrite seed script with 3-platform homepage sections (wizard, OMS, loyalty),
platform marketing pages, and store defaults with {{store_name}} placeholders
- Add resolve_placeholders() to ContentPageService for store default pages
- Fix SQLAlchemy filter bugs: replace Python `is None` with `.is_(None)` across
all ContentPageService query methods (was silently breaking all platform page lookups)
- Remove hardcoded orion fallback and delete homepage-orion.html
- Add placeholder hint box with click-to-copy in admin content page editor
- Export ProductCard/ProductsSection from cms schemas __init__
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add OnboardingProviderProtocol so modules declare their own post-signup
onboarding steps. The core OnboardingAggregator discovers enabled
providers and exposes a dashboard API (GET /dashboard/onboarding).
A session-scoped banner on the store dashboard shows a checklist that
guides merchants through setup without blocking signup.
Signup is simplified from 4 steps to 3 (Plan → Account → Payment):
store creation is merged into account creation, store language is
captured from the user's browsing language, and platform-specific
template branching is removed.
Includes 47 unit and integration tests covering all new providers,
the aggregator, the API endpoint, and the signup service changes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move core signup service from marketplace to billing module, add
automatic Stripe product/price sync for tiers, create loyalty-specific
signup wizard, and enforce that platform is always explicitly known
(no silent defaulting to primary/hardcoded ID).
Key changes:
- New billing SignupService with separated account/store creation steps
- Stripe auto-sync on tier create/update (new prices, archive old)
- Loyalty signup template (Plan → Account → Store → Payment)
- platform_code is now required throughout the signup flow
- Pricing/signup pages return 404 if platform not detected
- OMS-specific logic (Letzshop claiming) stays in marketplace module
- Bootstrap script: scripts/seed/sync_stripe_products.py
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move db.commit() from services to API endpoints and Celery tasks.
Services now use db.flush() only; endpoints own the transaction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrates scanning pipeline from marketing-.lu-domains app into Orion module.
Supports digital (domain scan) and offline (manual capture) lead channels
with enrichment, scoring, campaign management, and interaction tracking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use SUDO_USER to resolve correct home directory when run with sudo.
Use --project-directory instead of -f for docker compose lookups.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- deploy.sh: add DB health wait before migrations, prune old Docker images
- restore.sh: add redis-exporter to stop list, replace sleep with DB health wait
- verify-server.sh: add redis-exporter to expected containers, add Sentry + Redis exporter checks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename loadMenuConfig() → loadUserMenuConfig() in adminMyMenuConfig()
to avoid shadowing the sidebar's loadMenuConfig() inherited from data()
via the spread operator. The name collision caused the sidebar to never
populate its menuData, showing only the fallback "Dashboard" link.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dynamic script creation (document.createElement) ignores the defer
attribute per HTML spec — scripts are async regardless. Alpine.js CDN
loaded fast and auto-initialized before page scripts had executed,
causing ReferenceError for x-data functions (adminStores, dark,
isSideMenuOpen, etc.) and blank pages.
Fix: Replace dynamic script creation with static <script defer> tags
and move extra_scripts block BEFORE Alpine.js in all 4 base templates
(admin, store, merchant, storefront). Alpine.js is now always the last
deferred script, ensuring all page functions are defined before it
auto-initializes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All route files (admin.py, store.py) now export `router` instead of
`admin_router`/`store_router`. Consumer code (definition.py, __init__.py)
imports as `router as admin_router` where distinction is needed.
ModuleDefinition fields remain admin_router/store_router.
64 files changed across all modules. Architecture rules, docs, and
migration plan updated. Added noqa:API001 support to validator for
pre-existing raw dict endpoints now visible with standardized router name.
All 1114 tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Enforce MOD-025/MOD-026 rules: zero top-level cross-module model imports
remain in any service file. All 66 files migrated using deferred import
patterns (method-body, _get_model() helpers, instance-cached self._Model)
and new cross-module service methods in tenancy. Documentation updated
with Pattern 6 (deferred imports), migration plan marked complete, and
violations status reflects 84→0 service-layer violations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 84 import sites now use the canonical path
app.modules.tenancy.schemas.auth directly — no need
for backwards-compatibility re-exports.
Update audit validator to check module schemas locations
instead of only the legacy models/schema/ path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all auth schemas (UserContext, UserLogin, LoginResponse, etc.) from
legacy models/schema/auth.py to app/modules/tenancy/schemas/auth.py per
MOD-019. Update 84 import sites across 14 modules. Legacy file now
re-exports for backwards compatibility.
Add missing tenancy service methods for cross-module consumers:
- merchant_service.get_merchant_by_owner_id()
- merchant_service.get_merchant_count_for_owner()
- admin_service.get_user_by_id() (public, was private-only)
- platform_service.get_active_store_count()
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add admin store roles page with merchant→store cascading for superadmin
and store-only selection for platform admin
- Add permission catalog API with translated labels/descriptions (en/fr/de/lb)
- Add permission translations to all 15 module locale files (60 files total)
- Add info icon tooltips for permission descriptions in role editor
- Add store roles menu item and admin menu item in module definition
- Fix store-selector.js URL construction bug when apiEndpoint has query params
- Add admin store roles API (CRUD + platform scoping)
- Add integration tests for admin store roles and permission catalog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
createModuleLogger is not available in store context. Use the same
window.LogConfig.createLogger pattern as team.js.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The inline storeRoles() was missing ...data() spread, causing Alpine
errors for dark mode, sidebar, storeCode etc. Follow the same pattern
as team.js: external JS file with ...data() and parent init() call.
Uses apiClient and Utils.showToast per architecture rules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 4-layer access control stack (subscription → module → menu → permissions):
- P1: Wire requires_permission into menu sidebar filtering
- P2: Expose window.USER_PERMISSIONS for Alpine.js client-side gating
- P3: Add page-level permission guards on store routes
- P4: Role CRUD API endpoints and role editor UI
- P5: Audit trail for all role/permission changes
Includes unit tests (menu permission filtering, role CRUD service) and
integration tests (role API endpoints). All 404 core+tenancy tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add create_oms_admin (admin@omsflow.lu) alongside existing loyalty admin,
both using a shared create_platform_admin helper. Rename "Dashboard" and
"Staff login" labels to "Store panel" and "Store login" across seed output.
Add customer login URLs to production-style access section.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>