refactor: rename Wizamart to Orion across entire codebase

Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart
with Orion/orion/ORION across 184 files. This includes database
identifiers, email addresses, domain references, R2 bucket names,
DNS prefixes, encryption salt, Celery app name, config defaults,
Docker configs, CI configs, documentation, seed data, and templates.

Renames homepage-wizamart.html template to homepage-orion.html.
Fixes duplicate file_pattern key in api.yaml architecture rule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 16:46:56 +01:00
parent 34ee7bb7ad
commit e9253fbd84
184 changed files with 1227 additions and 1228 deletions

View File

@@ -145,7 +145,7 @@ api_endpoint_rules:
- Dependencies (app/api/deps.py) - authentication/authorization validation - Dependencies (app/api/deps.py) - authentication/authorization validation
- Services (app/services/) - business logic validation - Services (app/services/) - business logic validation
The global exception handler catches all WizamartException subclasses and The global exception handler catches all OrionException subclasses and
converts them to appropriate HTTP responses. converts them to appropriate HTTP responses.
WRONG (endpoint raises exception): WRONG (endpoint raises exception):
@@ -213,7 +213,6 @@ api_endpoint_rules:
file_pattern: file_pattern:
- "app/api/v1/vendor/**/*.py" - "app/api/v1/vendor/**/*.py"
- "app/modules/*/routes/api/store*.py" - "app/modules/*/routes/api/store*.py"
file_pattern:
- "app/api/v1/storefront/**/*.py" - "app/api/v1/storefront/**/*.py"
- "app/modules/*/routes/api/storefront*.py" - "app/modules/*/routes/api/storefront*.py"
discouraged_patterns: discouraged_patterns:

View File

@@ -44,17 +44,17 @@ exception_rules:
- "exc_info=True" - "exc_info=True"
- id: "EXC-004" - id: "EXC-004"
name: "Domain exceptions must inherit from WizamartException" name: "Domain exceptions must inherit from OrionException"
severity: "error" severity: "error"
description: | description: |
All custom domain exceptions must inherit from WizamartException (or its All custom domain exceptions must inherit from OrionException (or its
subclasses like ResourceNotFoundException, ValidationException, etc.). subclasses like ResourceNotFoundException, ValidationException, etc.).
This ensures the global exception handler catches and converts them properly. This ensures the global exception handler catches and converts them properly.
pattern: pattern:
file_pattern: file_pattern:
- "app/exceptions/**/*.py" - "app/exceptions/**/*.py"
- "app/modules/*/exceptions.py" - "app/modules/*/exceptions.py"
required_base_class: "WizamartException" required_base_class: "OrionException"
example_good: | example_good: |
class VendorNotFoundException(ResourceNotFoundException): class VendorNotFoundException(ResourceNotFoundException):
def __init__(self, vendor_code: str): def __init__(self, vendor_code: str):
@@ -65,7 +65,7 @@ exception_rules:
severity: "error" severity: "error"
description: | description: |
The global exception handler must be set up in app initialization to The global exception handler must be set up in app initialization to
catch WizamartException and convert to HTTP responses. catch OrionException and convert to HTTP responses.
pattern: pattern:
file_pattern: "app/main.py" file_pattern: "app/main.py"
required_patterns: required_patterns:

View File

@@ -157,7 +157,7 @@ javascript_rules:
- Page URLs (not API calls) like window.location.href = `/vendor/${vendorCode}/...` - Page URLs (not API calls) like window.location.href = `/vendor/${vendorCode}/...`
Why this matters: Why this matters:
- Including vendorCode causes 404 errors ("/vendor/wizamart/orders" not found) - Including vendorCode causes 404 errors ("/vendor/orion/orders" not found)
- The JWT token already identifies the vendor - The JWT token already identifies the vendor
- Consistent with the API design pattern - Consistent with the API design pattern
pattern: pattern:

View File

@@ -154,16 +154,16 @@ module_rules:
severity: "warning" severity: "warning"
description: | description: |
Self-contained modules should have an exceptions.py file defining Self-contained modules should have an exceptions.py file defining
module-specific exceptions that inherit from WizamartException. module-specific exceptions that inherit from OrionException.
Structure: Structure:
app/modules/{module}/exceptions.py app/modules/{module}/exceptions.py
Example: Example:
# app/modules/analytics/exceptions.py # app/modules/analytics/exceptions.py
from app.exceptions import WizamartException from app.exceptions import OrionException
class AnalyticsException(WizamartException): class AnalyticsException(OrionException):
"""Base exception for analytics module.""" """Base exception for analytics module."""
pass pass

View File

@@ -6,7 +6,7 @@ DEBUG=False
# ============================================================================= # =============================================================================
# PROJECT INFORMATION # PROJECT INFORMATION
# ============================================================================= # =============================================================================
PROJECT_NAME=Wizamart - Multi-Store Marketplace Platform PROJECT_NAME=Orion - Multi-Store Marketplace Platform
DESCRIPTION=Multi-tenants multi-themes ecommerce application DESCRIPTION=Multi-tenants multi-themes ecommerce application
VERSION=2.2.0 VERSION=2.2.0
@@ -14,17 +14,17 @@ VERSION=2.2.0
# DATABASE CONFIGURATION (PostgreSQL required) # DATABASE CONFIGURATION (PostgreSQL required)
# ============================================================================= # =============================================================================
# Default works with: docker-compose up -d db # Default works with: docker-compose up -d db
DATABASE_URL=postgresql://wizamart_user:secure_password@localhost:5432/wizamart_db DATABASE_URL=postgresql://orion_user:secure_password@localhost:5432/orion_db
# For production, use your PostgreSQL connection string: # For production, use your PostgreSQL connection string:
# DATABASE_URL=postgresql://username:password@production-host:5432/wizamart_db # DATABASE_URL=postgresql://username:password@production-host:5432/orion_db
# ============================================================================= # =============================================================================
# ADMIN INITIALIZATION # ADMIN INITIALIZATION
# ============================================================================= # =============================================================================
# These are used by init_production.py to create the platform admin # These are used by init_production.py to create the platform admin
# ⚠️ CHANGE THESE IN PRODUCTION! # ⚠️ CHANGE THESE IN PRODUCTION!
ADMIN_EMAIL=admin@wizamart.com ADMIN_EMAIL=admin@orion.lu
ADMIN_USERNAME=admin ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-me-in-production ADMIN_PASSWORD=change-me-in-production
ADMIN_FIRST_NAME=Platform ADMIN_FIRST_NAME=Platform
@@ -49,9 +49,9 @@ API_PORT=8000
# Development # Development
DOCUMENTATION_URL=http://localhost:8001 DOCUMENTATION_URL=http://localhost:8001
# Staging # Staging
# DOCUMENTATION_URL=https://staging-docs.wizamart.com # DOCUMENTATION_URL=https://staging-docs.orion.lu
# Production # Production
# DOCUMENTATION_URL=https://docs.wizamart.com # DOCUMENTATION_URL=https://docs.orion.lu
# ============================================================================= # =============================================================================
# RATE LIMITING # RATE LIMITING
@@ -70,7 +70,7 @@ LOG_FILE=logs/app.log
# PLATFORM DOMAIN CONFIGURATION # PLATFORM DOMAIN CONFIGURATION
# ============================================================================= # =============================================================================
# Your main platform domain # Your main platform domain
PLATFORM_DOMAIN=wizamart.com PLATFORM_DOMAIN=orion.lu
# Custom domain features # Custom domain features
# Enable/disable custom domains # Enable/disable custom domains
@@ -85,7 +85,7 @@ SSL_PROVIDER=letsencrypt
AUTO_PROVISION_SSL=False AUTO_PROVISION_SSL=False
# DNS verification # DNS verification
DNS_VERIFICATION_PREFIX=_wizamart-verify DNS_VERIFICATION_PREFIX=_orion-verify
DNS_VERIFICATION_TTL=3600 DNS_VERIFICATION_TTL=3600
# ============================================================================= # =============================================================================
@@ -103,8 +103,8 @@ STRIPE_TRIAL_DAYS=30
# ============================================================================= # =============================================================================
# Provider: smtp, sendgrid, mailgun, ses # Provider: smtp, sendgrid, mailgun, ses
EMAIL_PROVIDER=smtp EMAIL_PROVIDER=smtp
EMAIL_FROM_ADDRESS=noreply@wizamart.com EMAIL_FROM_ADDRESS=noreply@orion.lu
EMAIL_FROM_NAME=Wizamart EMAIL_FROM_NAME=Orion
EMAIL_REPLY_TO= EMAIL_REPLY_TO=
# SMTP Settings (used when EMAIL_PROVIDER=smtp) # SMTP Settings (used when EMAIL_PROVIDER=smtp)
@@ -185,7 +185,7 @@ STORAGE_BACKEND=local
R2_ACCOUNT_ID= R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID= R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY= R2_SECRET_ACCESS_KEY=
R2_BUCKET_NAME=wizamart-media R2_BUCKET_NAME=orion-media
# Public URL for R2 bucket (optional - for custom domain) # Public URL for R2 bucket (optional - for custom domain)
# If not set, uses Cloudflare's default R2 public URL # If not set, uses Cloudflare's default R2 public URL

View File

@@ -45,11 +45,11 @@ jobs:
postgres: postgres:
image: postgres:15 image: postgres:15
env: env:
POSTGRES_DB: wizamart_test POSTGRES_DB: orion_test
POSTGRES_USER: test_user POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password POSTGRES_PASSWORD: test_password
options: >- options: >-
--health-cmd "pg_isready -U test_user -d wizamart_test" --health-cmd "pg_isready -U test_user -d orion_test"
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s
--health-retries 5 --health-retries 5
@@ -57,8 +57,8 @@ jobs:
env: env:
# act_runner executes jobs in Docker containers on the same network as services, # act_runner executes jobs in Docker containers on the same network as services,
# so use the service name (postgres) as hostname with the internal port (5432) # so use the service name (postgres) as hostname with the internal port (5432)
TEST_DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_test" TEST_DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/orion_test"
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_test" DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/orion_test"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

2
.gitignore vendored
View File

@@ -183,5 +183,5 @@ tailadmin-free-tailwind-dashboard-template/
static/shared/css/tailwind.css static/shared/css/tailwind.css
# Export files # Export files
wizamart_letzshop_export_*.csv orion_letzshop_export_*.csv
exports/ exports/

View File

@@ -43,13 +43,13 @@ pytest:
alias: postgres alias: postgres
variables: variables:
# PostgreSQL service configuration # PostgreSQL service configuration
POSTGRES_DB: wizamart_test POSTGRES_DB: orion_test
POSTGRES_USER: test_user POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password POSTGRES_PASSWORD: test_password
# Application database URL for tests # Application database URL for tests
TEST_DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_test" TEST_DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/orion_test"
# Skip database validation during import (tests use TEST_DATABASE_URL) # Skip database validation during import (tests use TEST_DATABASE_URL)
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_test" DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/orion_test"
before_script: before_script:
- pip install uv - pip install uv
- uv sync --frozen - uv sync --frozen

View File

@@ -116,7 +116,7 @@ return {
### Duplicate /shop/ Prefix ### Duplicate /shop/ Prefix
**Problem:** Routes like `/stores/wizamart/shop/shop/products/4` **Problem:** Routes like `/stores/orion/shop/shop/products/4`
**Root Cause:** **Root Cause:**
```python ```python
@@ -136,7 +136,7 @@ All routes in `shop_pages.py` fixed.
### Missing /shop/ in Template Links ### Missing /shop/ in Template Links
**Problem:** Links went to `/stores/wizamart/products` instead of `/shop/products` **Problem:** Links went to `/stores/orion/products` instead of `/shop/products`
**Fix:** Updated all templates: **Fix:** Updated all templates:
- `shop/base.html` - Header, footer, navigation - `shop/base.html` - Header, footer, navigation
@@ -290,15 +290,15 @@ Comprehensive guide covering:
### Test URLs ### Test URLs
``` ```
Landing Pages: Landing Pages:
- http://localhost:8000/stores/wizamart/ - http://localhost:8000/stores/orion/
- http://localhost:8000/stores/fashionhub/ - http://localhost:8000/stores/fashionhub/
- http://localhost:8000/stores/bookstore/ - http://localhost:8000/stores/bookstore/
Shop Pages: Shop Pages:
- http://localhost:8000/stores/wizamart/shop/ - http://localhost:8000/stores/orion/shop/
- http://localhost:8000/stores/wizamart/shop/products - http://localhost:8000/stores/orion/shop/products
- http://localhost:8000/stores/wizamart/shop/products/1 - http://localhost:8000/stores/orion/shop/products/1
- http://localhost:8000/stores/wizamart/shop/cart - http://localhost:8000/stores/orion/shop/cart
``` ```
## Breaking Changes ## Breaking Changes

View File

@@ -1,4 +1,4 @@
# Wizamart Multi-Tenant E-Commerce Platform Makefile # Orion Multi-Tenant E-Commerce Platform Makefile
# Cross-platform compatible (Windows & Linux) # Cross-platform compatible (Windows & Linux)
.PHONY: install install-dev install-docs install-all dev test test-coverage lint format check docker-build docker-up docker-down clean help tailwind-install tailwind-dev tailwind-build tailwind-watch arch-check arch-check-file arch-check-object test-db-up test-db-down test-db-reset test-db-status celery-worker celery-beat celery-dev flower celery-status celery-purge urls .PHONY: install install-dev install-docs install-all dev test test-coverage lint format check docker-build docker-up docker-down clean help tailwind-install tailwind-dev tailwind-build tailwind-watch arch-check arch-check-file arch-check-object test-db-up test-db-down test-db-reset test-db-status celery-worker celery-beat celery-dev flower celery-status celery-purge urls
@@ -132,7 +132,7 @@ seed-tiers:
# First-time installation - Complete setup with configuration validation # First-time installation - Complete setup with configuration validation
platform-install: platform-install:
@echo "🚀 WIZAMART PLATFORM INSTALLATION" @echo "🚀 ORION PLATFORM INSTALLATION"
@echo "==================================" @echo "=================================="
$(PYTHON) scripts/seed/install.py $(PYTHON) scripts/seed/install.py
@@ -235,7 +235,7 @@ test-db-status:
# ============================================================================= # =============================================================================
# Test database URL # Test database URL
TEST_DB_URL := postgresql://test_user:test_password@localhost:5433/wizamart_test TEST_DB_URL := postgresql://test_user:test_password@localhost:5433/orion_test
# Build pytest marker expression from module= and frontend= params # Build pytest marker expression from module= and frontend= params
MARKER_EXPR := MARKER_EXPR :=
@@ -530,7 +530,7 @@ endif
# ============================================================================= # =============================================================================
help: help:
@echo "Wizamart Platform Development Commands" @echo "Orion Platform Development Commands"
@echo "" @echo ""
@echo "=== SETUP ===" @echo "=== SETUP ==="
@echo " install - Install production dependencies" @echo " install - Install production dependencies"
@@ -681,4 +681,4 @@ help-db:
@echo " - Email provider settings (SMTP/SendGrid/Mailgun/SES)" @echo " - Email provider settings (SMTP/SendGrid/Mailgun/SES)"
@echo " - ADMIN_PASSWORD (strong password)" @echo " - ADMIN_PASSWORD (strong password)"
@echo " 2. make platform-install # Validates + initializes" @echo " 2. make platform-install # Validates + initializes"
@echo " 3. DO NOT run seed-demo in production!" @echo " 3. DO NOT run seed-demo in production!"

View File

@@ -34,7 +34,7 @@ This FastAPI application provides a complete ecommerce backend solution designed
### Project Structure ### Project Structure
``` ```
wizamart/ orion/
├── main.py # FastAPI application entry point ├── main.py # FastAPI application entry point
├── app/ ├── app/
│ ├── core/ │ ├── core/
@@ -179,8 +179,8 @@ make qa
```bash ```bash
# Clone the repository # Clone the repository
git clone <wizamart-repo> git clone <orion-repo>
cd wizamart-repo cd orion-repo
# Create virtual environment # Create virtual environment
python -m venv venv python -m venv venv
@@ -447,7 +447,7 @@ PROD002,"Super Gadget","A fantastic gadget",19.99,EUR,GadgetInc,9876543210987,Am
- `POST /api/v1/marketplace/import-product` - Start CSV import - `POST /api/v1/marketplace/import-product` - Start CSV import
- `GET /api/v1/marketplace/import-status/{job_id}` - Check import status - `GET /api/v1/marketplace/import-status/{job_id}` - Check import status
- `GET /api/v1/marketplace/import-jobs` - List import jobs - `GET /api/v1/marketplace/import-jobs` - List import jobs
- -
### Inventory Endpoints ### Inventory Endpoints
- `POST /api/v1/inventory` - Set inventory quantity - `POST /api/v1/inventory` - Set inventory quantity
- `POST /api/v1/inventory/add` - Add to inventory - `POST /api/v1/inventory/add` - Add to inventory
@@ -700,7 +700,7 @@ make help
This will display all available commands organized by category: This will display all available commands organized by category:
- **Setup**: Installation and environment setup - **Setup**: Installation and environment setup
- **Development**: Development servers and workflows - **Development**: Development servers and workflows
- **Documentation**: Documentation building and deployment - **Documentation**: Documentation building and deployment
- **Testing**: Various test execution options - **Testing**: Various test execution options
- **Code Quality**: Formatting, linting, and quality checks - **Code Quality**: Formatting, linting, and quality checks
@@ -734,4 +734,4 @@ This will display all available commands organized by category:
- **Health Check**: http://localhost:8000/health - **Health Check**: http://localhost:8000/health
- **Version Info**: http://localhost:8000/ - **Version Info**: http://localhost:8000/
For issues and feature requests, please create an issue in the repository. For issues and feature requests, please create an issue in the repository.

View File

@@ -11,7 +11,7 @@
If you discover a security vulnerability in this project, please report it responsibly: If you discover a security vulnerability in this project, please report it responsibly:
1. **Do not** open a public issue 1. **Do not** open a public issue
2. Email the security team at: security@wizamart.com 2. Email the security team at: security@orion.lu
3. Include: 3. Include:
- Description of the vulnerability - Description of the vulnerability
- Steps to reproduce - Steps to reproduce

View File

@@ -1,6 +1,6 @@
# Terminology Guide # Terminology Guide
This document defines the standard terminology used throughout the Wizamart codebase. This document defines the standard terminology used throughout the Orion codebase.
## Core Multi-Tenant Entities ## Core Multi-Tenant Entities

View File

@@ -6,12 +6,12 @@ Landing pages have been created for three stores with different templates.
## 📍 Test URLs ## 📍 Test URLs
### 1. WizaMart - Modern Template ### 1. Orion - Modern Template
**Landing Page:** **Landing Page:**
- http://localhost:8000/stores/wizamart/ - http://localhost:8000/stores/orion/
**Shop Page:** **Shop Page:**
- http://localhost:8000/stores/wizamart/shop/ - http://localhost:8000/stores/orion/shop/
**What to expect:** **What to expect:**
- Full-screen hero section with animations - Full-screen hero section with animations
@@ -93,8 +93,8 @@ db.close()
" "
``` ```
Then visit: http://localhost:8000/stores/wizamart/ Then visit: http://localhost:8000/stores/orion/
- Should automatically redirect to: http://localhost:8000/stores/wizamart/shop/ - Should automatically redirect to: http://localhost:8000/stores/orion/shop/
--- ---
@@ -111,17 +111,17 @@ Or programmatically:
```python ```python
from scripts.create_landing_page import create_landing_page from scripts.create_landing_page import create_landing_page
# Change WizaMart to default template # Change Orion to default template
create_landing_page('wizamart', template='default') create_landing_page('orion', template='default')
# Change to minimal # Change to minimal
create_landing_page('wizamart', template='minimal') create_landing_page('orion', template='minimal')
# Change to full # Change to full
create_landing_page('wizamart', template='full') create_landing_page('orion', template='full')
# Change back to modern # Change back to modern
create_landing_page('wizamart', template='modern') create_landing_page('orion', template='modern')
``` ```
--- ---
@@ -130,7 +130,7 @@ create_landing_page('wizamart', template='modern')
| Store | Subdomain | Template | Landing Page URL | | Store | Subdomain | Template | Landing Page URL |
|--------|-----------|----------|------------------| |--------|-----------|----------|------------------|
| WizaMart | wizamart | **modern** | http://localhost:8000/stores/wizamart/ | | Orion | orion | **modern** | http://localhost:8000/stores/orion/ |
| Fashion Hub | fashionhub | **minimal** | http://localhost:8000/stores/fashionhub/ | | Fashion Hub | fashionhub | **minimal** | http://localhost:8000/stores/fashionhub/ |
| The Book Store | bookstore | **full** | http://localhost:8000/stores/bookstore/ | | The Book Store | bookstore | **full** | http://localhost:8000/stores/bookstore/ |
@@ -146,7 +146,7 @@ sqlite3 letzshop.db "SELECT id, store_id, slug, title, template, is_published FR
Expected output: Expected output:
``` ```
8|1|landing|Welcome to WizaMart|modern|1 8|1|landing|Welcome to Orion|modern|1
9|2|landing|Fashion Hub - Style & Elegance|minimal|1 9|2|landing|Fashion Hub - Style & Elegance|minimal|1
10|3|landing|The Book Store - Your Literary Haven|full|1 10|3|landing|The Book Store - Your Literary Haven|full|1
``` ```
@@ -180,7 +180,7 @@ Expected output:
## ✅ Success Checklist ## ✅ Success Checklist
- [ ] WizaMart landing page loads (modern template) - [ ] Orion landing page loads (modern template)
- [ ] Fashion Hub landing page loads (minimal template) - [ ] Fashion Hub landing page loads (minimal template)
- [ ] Book Store landing page loads (full template) - [ ] Book Store landing page loads (full template)
- [ ] "Shop Now" buttons work correctly - [ ] "Shop Now" buttons work correctly

View File

@@ -19,7 +19,7 @@ def upgrade() -> None:
"platforms", "platforms",
sa.Column("id", sa.Integer(), primary_key=True, index=True), sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("code", sa.String(50), unique=True, nullable=False, index=True, comment="Unique platform identifier (e.g., 'oms', 'loyalty', 'sites')"), sa.Column("code", sa.String(50), unique=True, nullable=False, index=True, comment="Unique platform identifier (e.g., 'oms', 'loyalty', 'sites')"),
sa.Column("name", sa.String(100), nullable=False, comment="Display name (e.g., 'Wizamart OMS')"), sa.Column("name", sa.String(100), nullable=False, comment="Display name (e.g., 'Orion OMS')"),
sa.Column("description", sa.Text(), nullable=True, comment="Platform description for admin/marketing purposes"), sa.Column("description", sa.Text(), nullable=True, comment="Platform description for admin/marketing purposes"),
sa.Column("domain", sa.String(255), unique=True, nullable=True, index=True, comment="Production domain (e.g., 'oms.lu', 'loyalty.lu')"), sa.Column("domain", sa.String(255), unique=True, nullable=True, index=True, comment="Production domain (e.g., 'oms.lu', 'loyalty.lu')"),
sa.Column("path_prefix", sa.String(50), unique=True, nullable=True, index=True, comment="Development path prefix (e.g., 'oms' for localhost:9999/oms/*)"), sa.Column("path_prefix", sa.String(50), unique=True, nullable=True, index=True, comment="Development path prefix (e.g., 'oms' for localhost:9999/oms/*)"),

View File

@@ -1,6 +1,6 @@
# app/core/celery_config.py # app/core/celery_config.py
""" """
Celery configuration for Wizamart background task processing. Celery configuration for Orion background task processing.
This module configures Celery with Redis as the broker and result backend. This module configures Celery with Redis as the broker and result backend.
It includes: It includes:
@@ -66,7 +66,7 @@ def get_all_task_modules() -> list[str]:
# Create Celery application # Create Celery application
celery_app = Celery( celery_app = Celery(
"wizamart", "orion",
broker=REDIS_URL, broker=REDIS_URL,
backend=REDIS_URL, backend=REDIS_URL,
include=get_all_task_modules(), include=get_all_task_modules(),

View File

@@ -27,7 +27,7 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
# PROJECT INFORMATION # PROJECT INFORMATION
# ============================================================================= # =============================================================================
project_name: str = "Wizamart - Multi-Store Marketplace Platform" project_name: str = "Orion - Multi-Store Marketplace Platform"
version: str = "2.2.0" version: str = "2.2.0"
# Clean description without HTML # Clean description without HTML
@@ -47,12 +47,12 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
# DATABASE (PostgreSQL only) # DATABASE (PostgreSQL only)
# ============================================================================= # =============================================================================
database_url: str = "postgresql://wizamart_user:secure_password@localhost:5432/wizamart_db" database_url: str = "postgresql://orion_user:secure_password@localhost:5432/orion_db"
# ============================================================================= # =============================================================================
# ADMIN INITIALIZATION (for init_production.py) # ADMIN INITIALIZATION (for init_production.py)
# ============================================================================= # =============================================================================
admin_email: str = "admin@wizamart.com" admin_email: str = "admin@orion.lu"
admin_username: str = "admin" admin_username: str = "admin"
admin_password: str = "admin123" # CHANGE IN PRODUCTION! admin_password: str = "admin123" # CHANGE IN PRODUCTION!
admin_first_name: str = "Platform" admin_first_name: str = "Platform"
@@ -96,7 +96,7 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
# PLATFORM DOMAIN CONFIGURATION # PLATFORM DOMAIN CONFIGURATION
# ============================================================================= # =============================================================================
platform_domain: str = "wizamart.com" platform_domain: str = "orion.lu"
# Custom domain features # Custom domain features
allow_custom_domains: bool = True allow_custom_domains: bool = True
@@ -107,7 +107,7 @@ class Settings(BaseSettings):
auto_provision_ssl: bool = False auto_provision_ssl: bool = False
# DNS verification # DNS verification
dns_verification_prefix: str = "_wizamart-verify" dns_verification_prefix: str = "_orion-verify"
dns_verification_ttl: int = 3600 dns_verification_ttl: int = 3600
# ============================================================================= # =============================================================================
@@ -130,8 +130,8 @@ class Settings(BaseSettings):
# ============================================================================= # =============================================================================
# Provider: smtp, sendgrid, mailgun, ses # Provider: smtp, sendgrid, mailgun, ses
email_provider: str = "smtp" email_provider: str = "smtp"
email_from_address: str = "noreply@wizamart.com" email_from_address: str = "noreply@orion.lu"
email_from_name: str = "Wizamart" email_from_name: str = "Orion"
email_reply_to: str = "" # Optional reply-to address email_reply_to: str = "" # Optional reply-to address
# SMTP Settings (used when email_provider=smtp) # SMTP Settings (used when email_provider=smtp)
@@ -201,7 +201,7 @@ class Settings(BaseSettings):
r2_account_id: str | None = None r2_account_id: str | None = None
r2_access_key_id: str | None = None r2_access_key_id: str | None = None
r2_secret_access_key: str | None = None r2_secret_access_key: str | None = None
r2_bucket_name: str = "wizamart-media" r2_bucket_name: str = "orion-media"
r2_public_url: str | None = None # Custom domain for public access (e.g., https://media.yoursite.com) r2_public_url: str | None = None # Custom domain for public access (e.g., https://media.yoursite.com)
# ============================================================================= # =============================================================================

View File

@@ -9,7 +9,7 @@ Detection priority:
1. Admin subdomain (admin.oms.lu) 1. Admin subdomain (admin.oms.lu)
2. Path-based admin/store (/admin/*, /store/*, /api/v1/admin/*) 2. Path-based admin/store (/admin/*, /store/*, /api/v1/admin/*)
3. Custom domain lookup (mybakery.lu -> STOREFRONT) 3. Custom domain lookup (mybakery.lu -> STOREFRONT)
4. Store subdomain (wizamart.oms.lu -> STOREFRONT) 4. Store subdomain (orion.oms.lu -> STOREFRONT)
5. Storefront paths (/storefront/*, /api/v1/storefront/*) 5. Storefront paths (/storefront/*, /api/v1/storefront/*)
6. Default to PLATFORM (marketing pages) 6. Default to PLATFORM (marketing pages)
@@ -62,7 +62,7 @@ class FrontendDetector:
Detect frontend type from request. Detect frontend type from request.
Args: Args:
host: Request host header (e.g., "oms.lu", "wizamart.oms.lu", "localhost:8000") host: Request host header (e.g., "oms.lu", "orion.oms.lu", "localhost:8000")
path: Request path (e.g., "/admin/stores", "/storefront/products") path: Request path (e.g., "/admin/stores", "/storefront/products")
has_store_context: True if request.state.store is set (from middleware) has_store_context: True if request.state.store is set (from middleware)
@@ -110,7 +110,7 @@ class FrontendDetector:
logger.debug("[FRONTEND_DETECTOR] Detected PLATFORM from path") logger.debug("[FRONTEND_DETECTOR] Detected PLATFORM from path")
return FrontendType.PLATFORM return FrontendType.PLATFORM
# 3. Store subdomain detection (wizamart.oms.lu) # 3. Store subdomain detection (orion.oms.lu)
# If subdomain exists and is not reserved -> it's a store storefront # If subdomain exists and is not reserved -> it's a store storefront
if subdomain and subdomain not in cls.RESERVED_SUBDOMAINS: if subdomain and subdomain not in cls.RESERVED_SUBDOMAINS:
logger.debug( logger.debug(
@@ -138,7 +138,7 @@ class FrontendDetector:
@classmethod @classmethod
def _get_subdomain(cls, host: str) -> str | None: def _get_subdomain(cls, host: str) -> str | None:
""" """
Extract subdomain from host (e.g., 'wizamart' from 'wizamart.oms.lu'). Extract subdomain from host (e.g., 'orion' from 'orion.oms.lu').
Returns None for localhost, IP addresses, or root domains. Returns None for localhost, IP addresses, or root domains.
Handles special case of admin.localhost for development. Handles special case of admin.localhost for development.

View File

@@ -32,13 +32,13 @@ async def lifespan(app: FastAPI):
# === STARTUP === # === STARTUP ===
app_logger = setup_logging() app_logger = setup_logging()
app_logger.info("Starting Wizamart multi-tenant platform") app_logger.info("Starting Orion multi-tenant platform")
logger.info("[OK] Application startup completed") logger.info("[OK] Application startup completed")
yield yield
# === SHUTDOWN === # === SHUTDOWN ===
app_logger.info("Shutting down Wizamart platform") app_logger.info("Shutting down Orion platform")
# Add cleanup tasks here if needed # Add cleanup tasks here if needed

View File

@@ -33,16 +33,16 @@ from .base import (
BusinessLogicException, BusinessLogicException,
ConflictException, ConflictException,
ExternalServiceException, ExternalServiceException,
OrionException,
RateLimitException, RateLimitException,
ResourceNotFoundException, ResourceNotFoundException,
ServiceUnavailableException, ServiceUnavailableException,
ValidationException, ValidationException,
WizamartException,
) )
__all__ = [ __all__ = [
# Base exception class # Base exception class
"WizamartException", "OrionException",
# Validation and business logic # Validation and business logic
"ValidationException", "ValidationException",
"BusinessLogicException", "BusinessLogicException",

View File

@@ -11,7 +11,7 @@ This module provides classes and functions for:
from typing import Any from typing import Any
class WizamartException(Exception): class OrionException(Exception):
"""Base exception class for all custom exceptions.""" """Base exception class for all custom exceptions."""
def __init__( def __init__(
@@ -39,7 +39,7 @@ class WizamartException(Exception):
return result return result
class ValidationException(WizamartException): class ValidationException(OrionException):
"""Raised when request validation fails.""" """Raised when request validation fails."""
def __init__( def __init__(
@@ -60,7 +60,7 @@ class ValidationException(WizamartException):
) )
class AuthenticationException(WizamartException): class AuthenticationException(OrionException):
"""Raised when authentication fails.""" """Raised when authentication fails."""
def __init__( def __init__(
@@ -77,7 +77,7 @@ class AuthenticationException(WizamartException):
) )
class AuthorizationException(WizamartException): class AuthorizationException(OrionException):
"""Raised when user lacks permission for an operation.""" """Raised when user lacks permission for an operation."""
def __init__( def __init__(
@@ -94,7 +94,7 @@ class AuthorizationException(WizamartException):
) )
class ResourceNotFoundException(WizamartException): class ResourceNotFoundException(OrionException):
"""Raised when a requested resource is not found.""" """Raised when a requested resource is not found."""
def __init__( def __init__(
@@ -120,7 +120,7 @@ class ResourceNotFoundException(WizamartException):
) )
class ConflictException(WizamartException): class ConflictException(OrionException):
"""Raised when a resource conflict occurs.""" """Raised when a resource conflict occurs."""
def __init__( def __init__(
@@ -137,7 +137,7 @@ class ConflictException(WizamartException):
) )
class BusinessLogicException(WizamartException): class BusinessLogicException(OrionException):
"""Raised when business logic rules are violated.""" """Raised when business logic rules are violated."""
def __init__( def __init__(
@@ -154,7 +154,7 @@ class BusinessLogicException(WizamartException):
) )
class ExternalServiceException(WizamartException): class ExternalServiceException(OrionException):
"""Raised when an external service fails.""" """Raised when an external service fails."""
def __init__( def __init__(
@@ -175,7 +175,7 @@ class ExternalServiceException(WizamartException):
) )
class RateLimitException(WizamartException): class RateLimitException(OrionException):
"""Raised when rate limit is exceeded.""" """Raised when rate limit is exceeded."""
def __init__( def __init__(
@@ -196,7 +196,7 @@ class RateLimitException(WizamartException):
) )
class ServiceUnavailableException(WizamartException): class ServiceUnavailableException(OrionException):
"""Raised when service is unavailable.""" """Raised when service is unavailable."""
def __init__(self, message: str = "Service temporarily unavailable"): def __init__(self, message: str = "Service temporarily unavailable"):

View File

@@ -19,7 +19,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
from app.modules.enums import FrontendType from app.modules.enums import FrontendType
from middleware.frontend_type import get_frontend_type from middleware.frontend_type import get_frontend_type
from .base import WizamartException from .base import OrionException
from .error_renderer import ErrorPageRenderer from .error_renderer import ErrorPageRenderer
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,8 +28,8 @@ logger = logging.getLogger(__name__)
def setup_exception_handlers(app): def setup_exception_handlers(app):
"""Setup exception handlers for the FastAPI app.""" """Setup exception handlers for the FastAPI app."""
@app.exception_handler(WizamartException) @app.exception_handler(OrionException)
async def custom_exception_handler(request: Request, exc: WizamartException): async def custom_exception_handler(request: Request, exc: OrionException):
"""Handle custom exceptions with context-aware rendering.""" """Handle custom exceptions with context-aware rendering."""
# Special handling for auth errors on HTML page requests (redirect to login) # Special handling for auth errors on HTML page requests (redirect to login)

View File

@@ -37,7 +37,7 @@ class MerchantSubscription(Base, TimestampMixin):
Example: Example:
Merchant "Boucherie Luxembourg" subscribes to: Merchant "Boucherie Luxembourg" subscribes to:
- Wizamart OMS (Professional tier) - Orion OMS (Professional tier)
- Loyalty+ (Essential tier) - Loyalty+ (Essential tier)
Their stores inherit features from the merchant's subscription. Their stores inherit features from the merchant's subscription.

View File

@@ -119,7 +119,7 @@ async def signup_success_page(
Shown after successful account creation. Shown after successful account creation.
""" """
context = get_platform_context(request, db) context = get_platform_context(request, db)
context["page_title"] = "Welcome to Wizamart!" context["page_title"] = "Welcome to Orion!"
context["store_code"] = store_code context["store_code"] = store_code
return templates.TemplateResponse( return templates.TemplateResponse(

View File

@@ -324,7 +324,7 @@ function storeInvoices() {
try { try {
// Get the token for authentication // Get the token for authentication
const token = localStorage.getItem('wizamart_token') || localStorage.getItem('store_token'); const token = localStorage.getItem('orion_token') || localStorage.getItem('store_token');
if (!token) { if (!token) {
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }

View File

@@ -2,7 +2,7 @@
{# Standalone Pricing Page #} {# Standalone Pricing Page #}
{% extends "platform/base.html" %} {% extends "platform/base.html" %}
{% block title %}{{ _("cms.platform.pricing.title") }} - Wizamart{% endblock %} {% block title %}{{ _("cms.platform.pricing.title") }} - Orion{% endblock %}
{% block content %} {% block content %}
<div x-data="{ annual: false }" class="py-16 lg:py-24"> <div x-data="{ annual: false }" class="py-16 lg:py-24">

View File

@@ -2,7 +2,7 @@
{# Multi-step Signup Wizard #} {# Multi-step Signup Wizard #}
{% extends "platform/base.html" %} {% extends "platform/base.html" %}
{% block title %}Start Your Free Trial - Wizamart{% endblock %} {% block title %}Start Your Free Trial - Orion{% endblock %}
{% block extra_head %} {% block extra_head %}
{# Stripe.js for payment #} {# Stripe.js for payment #}

View File

@@ -133,7 +133,7 @@
"creating_account": "Erstelle Ihr Konto..." "creating_account": "Erstelle Ihr Konto..."
}, },
"success": { "success": {
"title": "Willkommen bei Wizamart!", "title": "Willkommen bei Orion!",
"subtitle": "Ihr Konto wurde erstellt und Ihre {trial_days}-tägige kostenlose Testphase hat begonnen.", "subtitle": "Ihr Konto wurde erstellt und Ihre {trial_days}-tägige kostenlose Testphase hat begonnen.",
"what_next": "Was kommt als Nächstes?", "what_next": "Was kommt als Nächstes?",
"step_connect": "Letzshop verbinden:", "step_connect": "Letzshop verbinden:",
@@ -149,7 +149,7 @@
}, },
"cta": { "cta": {
"title": "Bereit, Ihre Bestellungen zu optimieren?", "title": "Bereit, Ihre Bestellungen zu optimieren?",
"subtitle": "Schließen Sie sich Letzshop-Händlern an, die Wizamart für ihre Bestellverwaltung vertrauen. Starten Sie heute Ihre {trial_days}-tägige kostenlose Testversion.", "subtitle": "Schließen Sie sich Letzshop-Händlern an, die Orion für ihre Bestellverwaltung vertrauen. Starten Sie heute Ihre {trial_days}-tägige kostenlose Testversion.",
"button": "Kostenlos testen" "button": "Kostenlos testen"
}, },
"footer": { "footer": {
@@ -157,7 +157,7 @@
"quick_links": "Schnelllinks", "quick_links": "Schnelllinks",
"platform": "Plattform", "platform": "Plattform",
"contact": "Kontakt", "contact": "Kontakt",
"copyright": "© {year} Wizamart. Entwickelt für den luxemburgischen E-Commerce.", "copyright": "© {year} Orion. Entwickelt für den luxemburgischen E-Commerce.",
"privacy": "Datenschutzerklärung", "privacy": "Datenschutzerklärung",
"terms": "Nutzungsbedingungen", "terms": "Nutzungsbedingungen",
"about": "Über uns", "about": "Über uns",
@@ -188,7 +188,7 @@
"how_step1": "Letzshop verbinden", "how_step1": "Letzshop verbinden",
"how_step1_desc": "Geben Sie Ihre Letzshop-API-Zugangsdaten ein. In 2 Minuten erledigt, keine technischen Kenntnisse erforderlich.", "how_step1_desc": "Geben Sie Ihre Letzshop-API-Zugangsdaten ein. In 2 Minuten erledigt, keine technischen Kenntnisse erforderlich.",
"how_step2": "Bestellungen kommen rein", "how_step2": "Bestellungen kommen rein",
"how_step2_desc": "Bestellungen werden automatisch synchronisiert. Bestätigen und Tracking direkt von Wizamart hinzufügen.", "how_step2_desc": "Bestellungen werden automatisch synchronisiert. Bestätigen und Tracking direkt von Orion hinzufügen.",
"how_step3": "Rechnungen erstellen", "how_step3": "Rechnungen erstellen",
"how_step3_desc": "Ein Klick, um konforme PDF-Rechnungen mit korrekter MwSt für jedes EU-Land zu erstellen.", "how_step3_desc": "Ein Klick, um konforme PDF-Rechnungen mit korrekter MwSt für jedes EU-Land zu erstellen.",
"how_step4": "Ihr Geschäft ausbauen", "how_step4": "Ihr Geschäft ausbauen",

View File

@@ -133,7 +133,7 @@
"creating_account": "Creating your account..." "creating_account": "Creating your account..."
}, },
"success": { "success": {
"title": "Welcome to Wizamart!", "title": "Welcome to Orion!",
"subtitle": "Your account has been created and your {trial_days}-day free trial has started.", "subtitle": "Your account has been created and your {trial_days}-day free trial has started.",
"what_next": "What's Next?", "what_next": "What's Next?",
"step_connect": "Connect Letzshop:", "step_connect": "Connect Letzshop:",
@@ -149,7 +149,7 @@
}, },
"cta": { "cta": {
"title": "Ready to Streamline Your Orders?", "title": "Ready to Streamline Your Orders?",
"subtitle": "Join Letzshop stores who trust Wizamart for their order management. Start your {trial_days}-day free trial today.", "subtitle": "Join Letzshop stores who trust Orion for their order management. Start your {trial_days}-day free trial today.",
"button": "Start Free Trial" "button": "Start Free Trial"
}, },
"footer": { "footer": {
@@ -157,7 +157,7 @@
"quick_links": "Quick Links", "quick_links": "Quick Links",
"platform": "Platform", "platform": "Platform",
"contact": "Contact", "contact": "Contact",
"copyright": "© {year} Wizamart. Built for Luxembourg e-commerce.", "copyright": "© {year} Orion. Built for Luxembourg e-commerce.",
"privacy": "Privacy Policy", "privacy": "Privacy Policy",
"terms": "Terms of Service", "terms": "Terms of Service",
"about": "About Us", "about": "About Us",
@@ -188,7 +188,7 @@
"how_step1": "Connect Letzshop", "how_step1": "Connect Letzshop",
"how_step1_desc": "Enter your Letzshop API credentials. Done in 2 minutes, no technical skills needed.", "how_step1_desc": "Enter your Letzshop API credentials. Done in 2 minutes, no technical skills needed.",
"how_step2": "Orders Flow In", "how_step2": "Orders Flow In",
"how_step2_desc": "Orders sync automatically. Confirm and add tracking directly from Wizamart.", "how_step2_desc": "Orders sync automatically. Confirm and add tracking directly from Orion.",
"how_step3": "Generate Invoices", "how_step3": "Generate Invoices",
"how_step3_desc": "One click to create compliant PDF invoices with correct VAT for any EU country.", "how_step3_desc": "One click to create compliant PDF invoices with correct VAT for any EU country.",
"how_step4": "Grow Your Business", "how_step4": "Grow Your Business",

View File

@@ -133,7 +133,7 @@
"creating_account": "Création de votre compte..." "creating_account": "Création de votre compte..."
}, },
"success": { "success": {
"title": "Bienvenue sur Wizamart !", "title": "Bienvenue sur Orion !",
"subtitle": "Votre compte a été créé et votre essai gratuit de {trial_days} jours a commencé.", "subtitle": "Votre compte a été créé et votre essai gratuit de {trial_days} jours a commencé.",
"what_next": "Et maintenant ?", "what_next": "Et maintenant ?",
"step_connect": "Connecter Letzshop :", "step_connect": "Connecter Letzshop :",
@@ -149,7 +149,7 @@
}, },
"cta": { "cta": {
"title": "Prêt à optimiser vos commandes ?", "title": "Prêt à optimiser vos commandes ?",
"subtitle": "Rejoignez les vendeurs Letzshop qui font confiance à Wizamart pour leur gestion de commandes. Commencez votre essai gratuit de {trial_days} jours aujourd'hui.", "subtitle": "Rejoignez les vendeurs Letzshop qui font confiance à Orion pour leur gestion de commandes. Commencez votre essai gratuit de {trial_days} jours aujourd'hui.",
"button": "Essai gratuit" "button": "Essai gratuit"
}, },
"footer": { "footer": {
@@ -157,7 +157,7 @@
"quick_links": "Liens rapides", "quick_links": "Liens rapides",
"platform": "Plateforme", "platform": "Plateforme",
"contact": "Contact", "contact": "Contact",
"copyright": "© {year} Wizamart. Conçu pour le e-commerce luxembourgeois.", "copyright": "© {year} Orion. Conçu pour le e-commerce luxembourgeois.",
"privacy": "Politique de confidentialité", "privacy": "Politique de confidentialité",
"terms": "Conditions d'utilisation", "terms": "Conditions d'utilisation",
"about": "À propos", "about": "À propos",
@@ -188,7 +188,7 @@
"how_step1": "Connecter Letzshop", "how_step1": "Connecter Letzshop",
"how_step1_desc": "Entrez vos identifiants API Letzshop. Fait en 2 minutes, aucune compétence technique requise.", "how_step1_desc": "Entrez vos identifiants API Letzshop. Fait en 2 minutes, aucune compétence technique requise.",
"how_step2": "Les commandes arrivent", "how_step2": "Les commandes arrivent",
"how_step2_desc": "Les commandes se synchronisent automatiquement. Confirmez et ajoutez le suivi directement depuis Wizamart.", "how_step2_desc": "Les commandes se synchronisent automatiquement. Confirmez et ajoutez le suivi directement depuis Orion.",
"how_step3": "Générer des factures", "how_step3": "Générer des factures",
"how_step3_desc": "Un clic pour créer des factures PDF conformes avec la TVA correcte pour tout pays UE.", "how_step3_desc": "Un clic pour créer des factures PDF conformes avec la TVA correcte pour tout pays UE.",
"how_step4": "Développez votre entreprise", "how_step4": "Développez votre entreprise",

View File

@@ -133,7 +133,7 @@
"creating_account": "Erstellt Äre Kont..." "creating_account": "Erstellt Äre Kont..."
}, },
"success": { "success": {
"title": "Wëllkomm bei Wizamart!", "title": "Wëllkomm bei Orion!",
"subtitle": "Äre Kont gouf erstallt an Är {trial_days}-Deeg gratis Testversioun huet ugefaang.", "subtitle": "Äre Kont gouf erstallt an Är {trial_days}-Deeg gratis Testversioun huet ugefaang.",
"what_next": "Wat kënnt duerno?", "what_next": "Wat kënnt duerno?",
"step_connect": "Letzshop verbannen:", "step_connect": "Letzshop verbannen:",
@@ -149,7 +149,7 @@
}, },
"cta": { "cta": {
"title": "Prett fir Är Bestellungen ze optiméieren?", "title": "Prett fir Är Bestellungen ze optiméieren?",
"subtitle": "Schléisst Iech Letzshop Händler un déi Wizamart fir hir Bestellungsverwaltung vertrauen. Fänkt haut Är {trial_days}-Deeg gratis Testversioun un.", "subtitle": "Schléisst Iech Letzshop Händler un déi Orion fir hir Bestellungsverwaltung vertrauen. Fänkt haut Är {trial_days}-Deeg gratis Testversioun un.",
"button": "Gratis Testen" "button": "Gratis Testen"
}, },
"footer": { "footer": {
@@ -157,7 +157,7 @@
"quick_links": "Séier Linken", "quick_links": "Séier Linken",
"platform": "Plattform", "platform": "Plattform",
"contact": "Kontakt", "contact": "Kontakt",
"copyright": "© {year} Wizamart. Gemaach fir de lëtzebuergeschen E-Commerce.", "copyright": "© {year} Orion. Gemaach fir de lëtzebuergeschen E-Commerce.",
"privacy": "Dateschutzrichtlinn", "privacy": "Dateschutzrichtlinn",
"terms": "Notzungsbedéngungen", "terms": "Notzungsbedéngungen",
"about": "Iwwer eis", "about": "Iwwer eis",
@@ -188,7 +188,7 @@
"how_step1": "Letzshop verbannen", "how_step1": "Letzshop verbannen",
"how_step1_desc": "Gitt Är Letzshop API Zougangsdaten an. An 2 Minutte fäerdeg, keng technesch Kenntnisser néideg.", "how_step1_desc": "Gitt Är Letzshop API Zougangsdaten an. An 2 Minutte fäerdeg, keng technesch Kenntnisser néideg.",
"how_step2": "Bestellunge kommen eran", "how_step2": "Bestellunge kommen eran",
"how_step2_desc": "Bestellunge ginn automatesch synchroniséiert. Confirméiert an Tracking direkt vu Wizamart derbäisetzen.", "how_step2_desc": "Bestellunge ginn automatesch synchroniséiert. Confirméiert an Tracking direkt vu Orion derbäisetzen.",
"how_step3": "Rechnunge generéieren", "how_step3": "Rechnunge generéieren",
"how_step3_desc": "Ee Klick fir konform PDF Rechnunge mat korrekter TVA fir all EU Land ze erstellen.", "how_step3_desc": "Ee Klick fir konform PDF Rechnunge mat korrekter TVA fir all EU Land ze erstellen.",
"how_step4": "Äert Geschäft ausbauen", "how_step4": "Äert Geschäft ausbauen",

View File

@@ -165,8 +165,8 @@ async def homepage(
logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}") logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}")
return templates.TemplateResponse(template_path, context) return templates.TemplateResponse(template_path, context)
# Fallback: Default wizamart homepage (no CMS content) # Fallback: Default orion homepage (no CMS content)
logger.info("[HOMEPAGE] No CMS homepage found, using default wizamart template") logger.info("[HOMEPAGE] No CMS homepage found, using default orion template")
context = get_platform_context(request, db) context = get_platform_context(request, db)
context["tiers"] = _get_tiers_data(db) context["tiers"] = _get_tiers_data(db)
@@ -204,7 +204,7 @@ async def homepage(
] ]
return templates.TemplateResponse( return templates.TemplateResponse(
"cms/platform/homepage-wizamart.html", "cms/platform/homepage-orion.html",
context, context,
) )

View File

@@ -160,7 +160,7 @@ async def store_content_page(
Generic content page handler for store shop (CMS). Generic content page handler for store shop (CMS).
Handles dynamic content pages like: Handles dynamic content pages like:
- /stores/wizamart/about, /stores/wizamart/faq, /stores/wizamart/contact, etc. - /stores/orion/about, /stores/orion/faq, /stores/orion/contact, etc.
Features: Features:
- Two-tier system: Store overrides take priority, fallback to platform defaults - Two-tier system: Store overrides take priority, fallback to platform defaults

View File

@@ -1,9 +1,9 @@
{# app/templates/platform/homepage-modern.html #} {# app/templates/platform/homepage-modern.html #}
{# Wizamart OMS - Luxembourg-focused homepage inspired by Veeqo #} {# Orion OMS - Luxembourg-focused homepage inspired by Veeqo #}
{% extends "platform/base.html" %} {% extends "platform/base.html" %}
{% block title %} {% block title %}
Wizamart - The Back-Office for Letzshop Sellers Orion - The Back-Office for Letzshop Sellers
{% endblock %} {% endblock %}
{% block extra_head %} {% block extra_head %}
@@ -85,7 +85,7 @@
<div class="w-3 h-3 rounded-full bg-red-500"></div> <div class="w-3 h-3 rounded-full bg-red-500"></div>
<div class="w-3 h-3 rounded-full bg-yellow-500"></div> <div class="w-3 h-3 rounded-full bg-yellow-500"></div>
<div class="w-3 h-3 rounded-full bg-green-500"></div> <div class="w-3 h-3 rounded-full bg-green-500"></div>
<span class="ml-4 text-gray-400 text-sm">Wizamart Dashboard</span> <span class="ml-4 text-gray-400 text-sm">Orion Dashboard</span>
</div> </div>
{# Mock dashboard content #} {# Mock dashboard content #}
<div class="p-6 space-y-4"> <div class="p-6 space-y-4">
@@ -219,7 +219,7 @@
<div class="bg-white dark:bg-gray-900 rounded-2xl p-8 shadow-lg h-full"> <div class="bg-white dark:bg-gray-900 rounded-2xl p-8 shadow-lg h-full">
<div class="w-12 h-12 rounded-full bg-blue-500 text-white flex items-center justify-center font-bold text-xl mb-6">2</div> <div class="w-12 h-12 rounded-full bg-blue-500 text-white flex items-center justify-center font-bold text-xl mb-6">2</div>
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">Orders Flow In</h3> <h3 class="text-xl font-bold text-gray-900 dark:text-white mb-3">Orders Flow In</h3>
<p class="text-gray-600 dark:text-gray-400">Orders sync automatically. Confirm and add tracking directly from Wizamart.</p> <p class="text-gray-600 dark:text-gray-400">Orders sync automatically. Confirm and add tracking directly from Orion.</p>
</div> </div>
<div class="hidden lg:block absolute top-1/2 -right-4 w-8 h-0.5 bg-blue-300"></div> <div class="hidden lg:block absolute top-1/2 -right-4 w-8 h-0.5 bg-blue-300"></div>
</div> </div>

View File

@@ -1,9 +1,9 @@
{# app/templates/platform/homepage-wizamart.html #} {# app/templates/platform/homepage-orion.html #}
{# Wizamart Marketing Homepage - Letzshop OMS Platform #} {# Orion Marketing Homepage - Letzshop OMS Platform #}
{% extends "platform/base.html" %} {% extends "platform/base.html" %}
{% from 'shared/macros/inputs.html' import toggle_switch %} {% from 'shared/macros/inputs.html' import toggle_switch %}
{% block title %}Wizamart - Order Management for Letzshop Sellers{% endblock %} {% block title %}Orion - Order Management for Letzshop Sellers{% endblock %}
{% block meta_description %}Lightweight OMS for Letzshop stores. Manage orders, inventory, and invoicing. Start your 30-day free trial today.{% endblock %} {% block meta_description %}Lightweight OMS for Letzshop stores. Manage orders, inventory, and invoicing. Start your 30-day free trial today.{% endblock %}
{% block content %} {% block content %}
@@ -212,7 +212,7 @@
{# CTA Button #} {# CTA Button #}
{% if tier.is_enterprise %} {% if tier.is_enterprise %}
<a href="mailto:sales@wizamart.com?subject=Enterprise%20Plan%20Inquiry" <a href="mailto:sales@orion.lu?subject=Enterprise%20Plan%20Inquiry"
class="block w-full py-3 px-4 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white font-semibold rounded-xl text-center hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"> class="block w-full py-3 px-4 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-white font-semibold rounded-xl text-center hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors">
{{ _("cms.platform.pricing.contact_sales") }} {{ _("cms.platform.pricing.contact_sales") }}
</a> </a>

View File

@@ -7,10 +7,10 @@ Exceptions for core platform functionality including:
- Settings management - Settings management
""" """
from app.exceptions import WizamartException from app.exceptions import OrionException
class CoreException(WizamartException): # noqa: MOD-025 class CoreException(OrionException): # noqa: MOD-025
"""Base exception for core module.""" """Base exception for core module."""

View File

@@ -663,11 +663,11 @@ def send_test_email(
email_log = email_service.send_raw( email_log = email_service.send_raw(
to_email=request.to_email, to_email=request.to_email,
to_name=None, to_name=None,
subject="Wizamart Platform - Test Email", subject="Orion Platform - Test Email",
body_html=f""" body_html=f"""
<html> <html>
<body style="font-family: Arial, sans-serif; padding: 20px;"> <body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #6b46c1;">Test Email from Wizamart</h2> <h2 style="color: #6b46c1;">Test Email from Orion</h2>
<p>This is a test email to verify your platform email configuration.</p> <p>This is a test email to verify your platform email configuration.</p>
<p>If you received this email, your email settings are working correctly!</p> <p>If you received this email, your email settings are working correctly!</p>
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;"> <hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;">
@@ -678,7 +678,7 @@ def send_test_email(
</body> </body>
</html> </html>
""", """,
body_text=f"Test email from Wizamart platform.\n\nProvider: {app_settings.email_provider}\nFrom: {app_settings.email_from_address}", body_text=f"Test email from Orion platform.\n\nProvider: {app_settings.email_provider}\nFrom: {app_settings.email_from_address}",
is_platform_email=True, is_platform_email=True,
) )

View File

@@ -616,7 +616,7 @@ class OnboardingService:
"success": True, "success": True,
"step_completed": True, "step_completed": True,
"onboarding_completed": True, "onboarding_completed": True,
"message": "Onboarding complete! Welcome to Wizamart.", "message": "Onboarding complete! Welcome to Orion.",
"redirect_url": f"/store/{store_code}/dashboard", "redirect_url": f"/store/{store_code}/dashboard",
} }

View File

@@ -437,7 +437,7 @@ function storeLetzshop() {
}); });
// Get the token for authentication // Get the token for authentication
const token = localStorage.getItem('wizamart_token'); const token = localStorage.getItem('orion_token');
if (!token) { if (!token) {
throw new Error('Not authenticated'); throw new Error('Not authenticated');
} }

View File

@@ -16,7 +16,7 @@ const onboardingLog = window.LogConfig?.createLogger('ONBOARDING') || console;
// Onboarding translations // Onboarding translations
const onboardingTranslations = { const onboardingTranslations = {
en: { en: {
title: 'Welcome to Wizamart', title: 'Welcome to Orion',
subtitle: 'Complete these steps to set up your store', subtitle: 'Complete these steps to set up your store',
steps: { steps: {
merchant_profile: 'Merchant Profile', merchant_profile: 'Merchant Profile',
@@ -71,7 +71,7 @@ const onboardingTranslations = {
}, },
step4: { step4: {
title: 'Historical Order Import', title: 'Historical Order Import',
description: 'Import your existing orders from Letzshop to start managing them in Wizamart.', description: 'Import your existing orders from Letzshop to start managing them in Orion.',
days_back: 'Import orders from last', days_back: 'Import orders from last',
days: 'days', days: 'days',
start_import: 'Start Import', start_import: 'Start Import',
@@ -94,7 +94,7 @@ const onboardingTranslations = {
}, },
}, },
fr: { fr: {
title: 'Bienvenue sur Wizamart', title: 'Bienvenue sur Orion',
subtitle: 'Complétez ces étapes pour configurer votre boutique', subtitle: 'Complétez ces étapes pour configurer votre boutique',
steps: { steps: {
merchant_profile: 'Profil Entreprise', merchant_profile: 'Profil Entreprise',
@@ -149,7 +149,7 @@ const onboardingTranslations = {
}, },
step4: { step4: {
title: 'Import Historique des Commandes', title: 'Import Historique des Commandes',
description: 'Importez vos commandes existantes de Letzshop pour commencer à les gérer dans Wizamart.', description: 'Importez vos commandes existantes de Letzshop pour commencer à les gérer dans Orion.',
days_back: 'Importer les commandes des derniers', days_back: 'Importer les commandes des derniers',
days: 'jours', days: 'jours',
start_import: 'Démarrer l\'Import', start_import: 'Démarrer l\'Import',
@@ -172,7 +172,7 @@ const onboardingTranslations = {
}, },
}, },
de: { de: {
title: 'Willkommen bei Wizamart', title: 'Willkommen bei Orion',
subtitle: 'Führen Sie diese Schritte aus, um Ihren Shop einzurichten', subtitle: 'Führen Sie diese Schritte aus, um Ihren Shop einzurichten',
steps: { steps: {
merchant_profile: 'Firmenprofil', merchant_profile: 'Firmenprofil',
@@ -227,7 +227,7 @@ const onboardingTranslations = {
}, },
step4: { step4: {
title: 'Historischer Bestellimport', title: 'Historischer Bestellimport',
description: 'Importieren Sie Ihre bestehenden Bestellungen von Letzshop, um sie in Wizamart zu verwalten.', description: 'Importieren Sie Ihre bestehenden Bestellungen von Letzshop, um sie in Orion zu verwalten.',
days_back: 'Bestellungen der letzten importieren', days_back: 'Bestellungen der letzten importieren',
days: 'Tage', days: 'Tage',
start_import: 'Import Starten', start_import: 'Import Starten',

View File

@@ -2,7 +2,7 @@
{# Letzshop Store Finder Page #} {# Letzshop Store Finder Page #}
{% extends "platform/base.html" %} {% extends "platform/base.html" %}
{% block title %}{{ _("cms.platform.find_shop.title") }} - Wizamart{% endblock %} {% block title %}{{ _("cms.platform.find_shop.title") }} - Orion{% endblock %}
{% block content %} {% block content %}
<div x-data="storeFinderData()" class="py-16 lg:py-24"> <div x-data="storeFinderData()" class="py-16 lg:py-24">

View File

@@ -5,7 +5,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Welcome to Wizamart - Setup Your Account</title> <title>Welcome to Orion - Setup Your Account</title>
<link href="/static/shared/fonts/inter.css" rel="stylesheet" /> <link href="/static/shared/fonts/inter.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', path='store/css/tailwind.output.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', path='store/css/tailwind.output.css') }}" />
@@ -22,7 +22,7 @@
<div class="w-10 h-10 rounded-lg bg-purple-600 flex items-center justify-center"> <div class="w-10 h-10 rounded-lg bg-purple-600 flex items-center justify-center">
<span class="text-white font-bold text-xl">W</span> <span class="text-white font-bold text-xl">W</span>
</div> </div>
<span class="text-xl font-semibold text-gray-800 dark:text-white">Wizamart</span> <span class="text-xl font-semibold text-gray-800 dark:text-white">Orion</span>
</div> </div>
<!-- Logout Button --> <!-- Logout Button -->
<button @click="handleLogout()" <button @click="handleLogout()"

View File

@@ -11,7 +11,7 @@ Architecture:
- Stores MUST configure email settings to send transactional emails - Stores MUST configure email settings to send transactional emails
- Platform emails (billing, subscription) still use platform settings - Platform emails (billing, subscription) still use platform settings
- Advanced providers (SendGrid, Mailgun, SES) are tier-gated (Business+) - Advanced providers (SendGrid, Mailgun, SES) are tier-gated (Business+)
- "Powered by Wizamart" footer is added for Essential/Professional tiers - "Powered by Orion" footer is added for Essential/Professional tiers
""" """
import enum import enum

View File

@@ -291,7 +291,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"login_url": "https://example.com/login", "login_url": "https://example.com/login",
"trial_days": "14", "trial_days": "14",
"tier_name": "Business", "tier_name": "Business",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"order_confirmation": { "order_confirmation": {
"customer_name": "Jane Doe", "customer_name": "Jane Doe",
@@ -300,13 +300,13 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"order_items_count": "3", "order_items_count": "3",
"order_date": "2024-01-15", "order_date": "2024-01-15",
"shipping_address": "123 Main St, Luxembourg City, L-1234", "shipping_address": "123 Main St, Luxembourg City, L-1234",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"password_reset": { "password_reset": {
"customer_name": "John Doe", "customer_name": "John Doe",
"reset_link": "https://example.com/reset?token=abc123", "reset_link": "https://example.com/reset?token=abc123",
"expiry_hours": "1", "expiry_hours": "1",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"team_invite": { "team_invite": {
"invitee_name": "Jane", "invitee_name": "Jane",
@@ -315,7 +315,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"role": "Admin", "role": "Admin",
"accept_url": "https://example.com/accept", "accept_url": "https://example.com/accept",
"expires_in_days": "7", "expires_in_days": "7",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"subscription_welcome": { "subscription_welcome": {
"store_name": "Acme Corp", "store_name": "Acme Corp",
@@ -324,7 +324,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"amount": "€49.99", "amount": "€49.99",
"next_billing_date": "2024-02-15", "next_billing_date": "2024-02-15",
"dashboard_url": "https://example.com/dashboard", "dashboard_url": "https://example.com/dashboard",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"payment_failed": { "payment_failed": {
"store_name": "Acme Corp", "store_name": "Acme Corp",
@@ -332,15 +332,15 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"amount": "€49.99", "amount": "€49.99",
"retry_date": "2024-01-18", "retry_date": "2024-01-18",
"update_payment_url": "https://example.com/billing", "update_payment_url": "https://example.com/billing",
"support_email": "support@wizamart.com", "support_email": "support@orion.lu",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"subscription_cancelled": { "subscription_cancelled": {
"store_name": "Acme Corp", "store_name": "Acme Corp",
"tier_name": "Business", "tier_name": "Business",
"end_date": "2024-02-15", "end_date": "2024-02-15",
"reactivate_url": "https://example.com/billing", "reactivate_url": "https://example.com/billing",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
"trial_ending": { "trial_ending": {
"store_name": "Acme Corp", "store_name": "Acme Corp",
@@ -349,7 +349,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
"trial_end_date": "2024-01-18", "trial_end_date": "2024-01-18",
"upgrade_url": "https://example.com/upgrade", "upgrade_url": "https://example.com/upgrade",
"features_list": "Unlimited products, API access, Priority support", "features_list": "Unlimited products, API access, Priority support",
"platform_name": "Wizamart", "platform_name": "Orion",
}, },
} }
return samples.get(template_code, {"platform_name": "Wizamart"}) return samples.get(template_code, {"platform_name": "Orion"})

View File

@@ -184,9 +184,9 @@ def preview_template(
variables = { variables = {
**_get_sample_variables(code), **_get_sample_variables(code),
**preview_data.variables, **preview_data.variables,
"platform_name": "Wizamart", "platform_name": "Orion",
"store_name": store.name if store else "Your Store", "store_name": store.name if store else "Your Store",
"support_email": store.contact_email if store else "support@wizamart.com", "support_email": store.contact_email if store else "support@orion.lu",
} }
return service.preview_store_template( return service.preview_store_template(
@@ -216,9 +216,9 @@ def send_test_email(
variables = { variables = {
**_get_sample_variables(code), **_get_sample_variables(code),
**test_data.variables, **test_data.variables,
"platform_name": "Wizamart", "platform_name": "Orion",
"store_name": store.name if store else "Your Store", "store_name": store.name if store else "Your Store",
"support_email": store.contact_email if store else "support@wizamart.com", "support_email": store.contact_email if store else "support@orion.lu",
} }
try: try:

View File

@@ -51,24 +51,24 @@ from app.modules.messaging.models import (
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Platform branding constants # Platform branding constants
PLATFORM_NAME = "Wizamart" PLATFORM_NAME = "Orion"
PLATFORM_SUPPORT_EMAIL = "support@wizamart.com" PLATFORM_SUPPORT_EMAIL = "support@orion.lu"
PLATFORM_DEFAULT_LANGUAGE = "en" PLATFORM_DEFAULT_LANGUAGE = "en"
SUPPORTED_LANGUAGES = ["en", "fr", "de", "lb"] SUPPORTED_LANGUAGES = ["en", "fr", "de", "lb"]
# Tiers that get white-label (no "Powered by Wizamart" footer) # Tiers that get white-label (no "Powered by Orion" footer)
WHITELABEL_TIERS = {"business", "enterprise"} WHITELABEL_TIERS = {"business", "enterprise"}
# Powered by Wizamart footer (added for Essential/Professional tiers) # Powered by Orion footer (added for Essential/Professional tiers)
POWERED_BY_FOOTER_HTML = """ POWERED_BY_FOOTER_HTML = """
<div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center;"> <div style="margin-top: 30px; padding-top: 20px; border-top: 1px solid #e5e7eb; text-align: center;">
<p style="color: #9ca3af; font-size: 12px; margin: 0;"> <p style="color: #9ca3af; font-size: 12px; margin: 0;">
Powered by <a href="https://wizamart.com" style="color: #6b46c1; text-decoration: none;">Wizamart</a> Powered by <a href="https://orion.lu" style="color: #6b46c1; text-decoration: none;">Orion</a>
</p> </p>
</div> </div>
""" """
POWERED_BY_FOOTER_TEXT = "\n\n---\nPowered by Wizamart - https://wizamart.com" POWERED_BY_FOOTER_TEXT = "\n\n---\nPowered by Orion - https://orion.lu"
@dataclass @dataclass
@@ -1045,7 +1045,7 @@ class EmailService:
def _should_add_powered_by_footer(self, store_id: int | None) -> bool: def _should_add_powered_by_footer(self, store_id: int | None) -> bool:
""" """
Check if "Powered by Wizamart" footer should be added. Check if "Powered by Orion" footer should be added.
Footer is added for Essential and Professional tiers. Footer is added for Essential and Professional tiers.
Business and Enterprise tiers get white-label (no footer). Business and Enterprise tiers get white-label (no footer).
@@ -1066,7 +1066,7 @@ class EmailService:
store_id: int | None, store_id: int | None,
) -> tuple[str, str | None]: ) -> tuple[str, str | None]:
""" """
Inject "Powered by Wizamart" footer if needed based on tier. Inject "Powered by Orion" footer if needed based on tier.
Returns: Returns:
Tuple of (modified_html, modified_text) Tuple of (modified_html, modified_text)
@@ -1168,7 +1168,7 @@ class EmailService:
store_logo_url=store.get_logo_url(), store_logo_url=store.get_logo_url(),
is_whitelabel=True, is_whitelabel=True,
) )
# Standard: Wizamart branding with store details # Standard: Orion branding with store details
return BrandingContext( return BrandingContext(
platform_name=PLATFORM_NAME, platform_name=PLATFORM_NAME,
platform_logo_url=None, # Use default platform logo platform_logo_url=None, # Use default platform logo
@@ -1401,11 +1401,11 @@ class EmailService:
For store emails (when store_id is provided and is_platform_email=False): For store emails (when store_id is provided and is_platform_email=False):
- Uses store's SMTP/provider settings if configured - Uses store's SMTP/provider settings if configured
- Uses store's from_email, from_name, reply_to - Uses store's from_email, from_name, reply_to
- Adds "Powered by Wizamart" footer for Essential/Professional tiers - Adds "Powered by Orion" footer for Essential/Professional tiers
For platform emails (is_platform_email=True or no store_id): For platform emails (is_platform_email=True or no store_id):
- Uses platform's email settings from config - Uses platform's email settings from config
- No "Powered by Wizamart" footer - No "Powered by Orion" footer
Args: Args:
is_platform_email: If True, always use platform settings (for billing, etc.) is_platform_email: If True, always use platform settings (for billing, etc.)
@@ -1437,7 +1437,7 @@ class EmailService:
from_name = from_name or self._platform_config.get("from_name", settings.email_from_name) from_name = from_name or self._platform_config.get("from_name", settings.email_from_name)
reply_to = reply_to or self._platform_config.get("reply_to") or settings.email_reply_to or None reply_to = reply_to or self._platform_config.get("reply_to") or settings.email_reply_to or None
# Inject "Powered by Wizamart" footer for non-whitelabel tiers # Inject "Powered by Orion" footer for non-whitelabel tiers
if store_id and not is_platform_email: if store_id and not is_platform_email:
body_html, body_text = self._inject_powered_by_footer( body_html, body_text = self._inject_powered_by_footer(
body_html, body_text, store_id body_html, body_text, store_id

View File

@@ -284,12 +284,12 @@ class StoreEmailSettingsService:
def _send_smtp_test(self, settings: StoreEmailSettings, to_email: str) -> None: def _send_smtp_test(self, settings: StoreEmailSettings, to_email: str) -> None:
"""Send test email via SMTP.""" """Send test email via SMTP."""
msg = MIMEMultipart("alternative") msg = MIMEMultipart("alternative")
msg["Subject"] = "Wizamart Email Configuration Test" msg["Subject"] = "Orion Email Configuration Test"
msg["From"] = f"{settings.from_name} <{settings.from_email}>" msg["From"] = f"{settings.from_name} <{settings.from_email}>"
msg["To"] = to_email msg["To"] = to_email
text_content = ( text_content = (
"This is a test email from Wizamart.\n\n" "This is a test email from Orion.\n\n"
"Your email settings are configured correctly!\n\n" "Your email settings are configured correctly!\n\n"
f"Provider: SMTP\n" f"Provider: SMTP\n"
f"Host: {settings.smtp_host}\n" f"Host: {settings.smtp_host}\n"
@@ -298,7 +298,7 @@ class StoreEmailSettingsService:
<html> <html>
<body style="font-family: Arial, sans-serif; padding: 20px;"> <body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #6b46c1;">Email Configuration Test</h2> <h2 style="color: #6b46c1;">Email Configuration Test</h2>
<p>This is a test email from <strong>Wizamart</strong>.</p> <p>This is a test email from <strong>Orion</strong>.</p>
<p style="color: #22c55e; font-weight: bold;"> <p style="color: #22c55e; font-weight: bold;">
Your email settings are configured correctly! Your email settings are configured correctly!
</p> </p>
@@ -341,12 +341,12 @@ class StoreEmailSettingsService:
message = Mail( message = Mail(
from_email=(settings.from_email, settings.from_name), from_email=(settings.from_email, settings.from_name),
to_emails=to_email, to_emails=to_email,
subject="Wizamart Email Configuration Test", subject="Orion Email Configuration Test",
html_content=""" html_content="""
<html> <html>
<body style="font-family: Arial, sans-serif; padding: 20px;"> <body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #6b46c1;">Email Configuration Test</h2> <h2 style="color: #6b46c1;">Email Configuration Test</h2>
<p>This is a test email from <strong>Wizamart</strong>.</p> <p>This is a test email from <strong>Orion</strong>.</p>
<p style="color: #22c55e; font-weight: bold;"> <p style="color: #22c55e; font-weight: bold;">
Your SendGrid settings are configured correctly! Your SendGrid settings are configured correctly!
</p> </p>
@@ -374,12 +374,12 @@ class StoreEmailSettingsService:
data={ data={
"from": f"{settings.from_name} <{settings.from_email}>", "from": f"{settings.from_name} <{settings.from_email}>",
"to": to_email, "to": to_email,
"subject": "Wizamart Email Configuration Test", "subject": "Orion Email Configuration Test",
"html": """ "html": """
<html> <html>
<body style="font-family: Arial, sans-serif; padding: 20px;"> <body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #6b46c1;">Email Configuration Test</h2> <h2 style="color: #6b46c1;">Email Configuration Test</h2>
<p>This is a test email from <strong>Wizamart</strong>.</p> <p>This is a test email from <strong>Orion</strong>.</p>
<p style="color: #22c55e; font-weight: bold;"> <p style="color: #22c55e; font-weight: bold;">
Your Mailgun settings are configured correctly! Your Mailgun settings are configured correctly!
</p> </p>
@@ -417,14 +417,14 @@ class StoreEmailSettingsService:
Source=f"{settings.from_name} <{settings.from_email}>", Source=f"{settings.from_name} <{settings.from_email}>",
Destination={"ToAddresses": [to_email]}, Destination={"ToAddresses": [to_email]},
Message={ Message={
"Subject": {"Data": "Wizamart Email Configuration Test"}, "Subject": {"Data": "Orion Email Configuration Test"},
"Body": { "Body": {
"Html": { "Html": {
"Data": """ "Data": """
<html> <html>
<body style="font-family: Arial, sans-serif; padding: 20px;"> <body style="font-family: Arial, sans-serif; padding: 20px;">
<h2 style="color: #6b46c1;">Email Configuration Test</h2> <h2 style="color: #6b46c1;">Email Configuration Test</h2>
<p>This is a test email from <strong>Wizamart</strong>.</p> <p>This is a test email from <strong>Orion</strong>.</p>
<p style="color: #22c55e; font-weight: bold;"> <p style="color: #22c55e; font-weight: bold;">
Your Amazon SES settings are configured correctly! Your Amazon SES settings are configured correctly!
</p> </p>

View File

@@ -236,7 +236,7 @@ function emailTemplatesPage() {
expires_in_days: '7' expires_in_days: '7'
} }
}; };
return samples[templateCode] || { platform_name: 'Wizamart' }; return samples[templateCode] || { platform_name: 'Orion' };
}, },
// Test Email // Test Email

View File

@@ -35,8 +35,8 @@ class TestEmailProviders:
subject="Test Subject", subject="Test Subject",
body_html="<h1>Hello</h1>", body_html="<h1>Hello</h1>",
body_text="Hello", body_text="Hello",
from_email="noreply@wizamart.com", from_email="noreply@orion.lu",
from_name="Wizamart", from_name="Orion",
) )
assert success is True assert success is True
@@ -53,9 +53,9 @@ class TestEmailProviders:
subject="Test Subject", subject="Test Subject",
body_html="<h1>Hello</h1>", body_html="<h1>Hello</h1>",
body_text=None, body_text=None,
from_email="noreply@wizamart.com", from_email="noreply@orion.lu",
from_name="Wizamart", from_name="Orion",
reply_to="support@wizamart.com", reply_to="support@orion.lu",
) )
assert success is True assert success is True

View File

@@ -253,7 +253,7 @@
<span class="text-gray-600 dark:text-gray-400" x-html="$icon('cog', 'w-5 h-5')"></span> <span class="text-gray-600 dark:text-gray-400" x-html="$icon('cog', 'w-5 h-5')"></span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Platform Settings</span> <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Platform Settings</span>
</a> </a>
<a href="https://docs.wizamart.com/architecture/capacity-planning/" target="_blank" class="flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> <a href="https://docs.orion.lu/architecture/capacity-planning/" target="_blank" class="flex items-center gap-3 p-3 rounded-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
<span class="text-blue-600 dark:text-blue-400" x-html="$icon('book-open', 'w-5 h-5')"></span> <span class="text-blue-600 dark:text-blue-400" x-html="$icon('book-open', 'w-5 h-5')"></span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Capacity Planning Docs</span> <span class="text-sm font-medium text-gray-700 dark:text-gray-300">Capacity Planning Docs</span>
</a> </a>

View File

@@ -12,9 +12,9 @@ from typing import Any
from app.exceptions.base import ( from app.exceptions.base import (
BusinessLogicException, BusinessLogicException,
OrionException,
ResourceNotFoundException, ResourceNotFoundException,
ValidationException, ValidationException,
WizamartException,
) )
__all__ = [ __all__ = [
@@ -182,7 +182,7 @@ class InvoiceSettingsNotFoundException(ResourceNotFoundException):
) )
class InvoiceSettingsAlreadyExistException(WizamartException): # noqa: MOD-025 class InvoiceSettingsAlreadyExistException(OrionException): # noqa: MOD-025
"""Raised when trying to create invoice settings that already exist.""" """Raised when trying to create invoice settings that already exist."""
def __init__(self, store_id: int): def __init__(self, store_id: int):
@@ -205,7 +205,7 @@ class InvoiceValidationException(BusinessLogicException):
) )
class InvoicePDFGenerationException(WizamartException): class InvoicePDFGenerationException(OrionException):
"""Raised when PDF generation fails.""" """Raised when PDF generation fails."""
def __init__(self, invoice_id: int, reason: str): def __init__(self, invoice_id: int, reason: str):

View File

@@ -13,9 +13,9 @@ from app.exceptions.base import (
BusinessLogicException, BusinessLogicException,
ConflictException, ConflictException,
ExternalServiceException, ExternalServiceException,
OrionException,
ResourceNotFoundException, ResourceNotFoundException,
ValidationException, ValidationException,
WizamartException,
) )
# ============================================================================= # =============================================================================
@@ -116,7 +116,7 @@ class UserAlreadyExistsException(ConflictException):
# ============================================================================= # =============================================================================
class PlatformNotFoundException(WizamartException): class PlatformNotFoundException(OrionException):
"""Raised when a platform is not found.""" """Raised when a platform is not found."""
def __init__(self, code: str): def __init__(self, code: str):
@@ -128,7 +128,7 @@ class PlatformNotFoundException(WizamartException):
) )
class PlatformInactiveException(WizamartException): # noqa: MOD-025 class PlatformInactiveException(OrionException): # noqa: MOD-025
"""Raised when trying to access an inactive platform.""" """Raised when trying to access an inactive platform."""
def __init__(self, code: str): def __init__(self, code: str):
@@ -140,7 +140,7 @@ class PlatformInactiveException(WizamartException): # noqa: MOD-025
) )
class PlatformUpdateException(WizamartException): # noqa: MOD-025 class PlatformUpdateException(OrionException): # noqa: MOD-025
"""Raised when platform update fails.""" """Raised when platform update fails."""
def __init__(self, code: str, reason: str): def __init__(self, code: str, reason: str):

View File

@@ -34,7 +34,7 @@ class MerchantDomain(Base, TimestampMixin):
Examples: Examples:
- myloyaltyprogram.lu -> Merchant "WizaCorp" (all stores inherit) - myloyaltyprogram.lu -> Merchant "WizaCorp" (all stores inherit)
- Store WIZAMART overrides with StoreDomain -> mysuperloyaltyprogram.lu - Store ORION overrides with StoreDomain -> mysuperloyaltyprogram.lu
- Store WIZAGADGETS -> inherits myloyaltyprogram.lu - Store WIZAGADGETS -> inherits myloyaltyprogram.lu
""" """

View File

@@ -32,7 +32,7 @@ class Platform(Base, TimestampMixin):
Represents a business offering/product line. Represents a business offering/product line.
Examples: Examples:
- Wizamart OMS (Order Management System) - Orion OMS (Order Management System)
- Loyalty+ (Loyalty Program Platform) - Loyalty+ (Loyalty Program Platform)
- Site Builder (Website Builder for Local Businesses) - Site Builder (Website Builder for Local Businesses)
@@ -62,7 +62,7 @@ class Platform(Base, TimestampMixin):
name = Column( name = Column(
String(100), String(100),
nullable=False, nullable=False,
comment="Display name (e.g., 'Wizamart OMS')", comment="Display name (e.g., 'Orion OMS')",
) )
description = Column( description = Column(

View File

@@ -40,8 +40,8 @@ class StorePlatform(Base, TimestampMixin):
- Store platform-specific settings - Store platform-specific settings
Example: Example:
- Store "WizaMart" is on OMS platform (Professional tier) - Store "Orion" is on OMS platform (Professional tier)
- Store "WizaMart" is also on Loyalty platform (Basic tier) - Store "Orion" is also on Loyalty platform (Basic tier)
""" """
__tablename__ = "store_platforms" __tablename__ = "store_platforms"

View File

@@ -249,7 +249,7 @@ def verify_merchant_domain_ownership(
Verify merchant domain ownership via DNS TXT record (Admin only). Verify merchant domain ownership via DNS TXT record (Admin only).
**Verification Process:** **Verification Process:**
1. Queries DNS for TXT record: `_wizamart-verify.{domain}` 1. Queries DNS for TXT record: `_orion-verify.{domain}`
2. Checks if verification token matches 2. Checks if verification token matches
3. If found, marks domain as verified 3. If found, marks domain as verified

View File

@@ -247,14 +247,14 @@ def verify_domain_ownership(
Verify domain ownership via DNS TXT record (Admin only). Verify domain ownership via DNS TXT record (Admin only).
**Verification Process:** **Verification Process:**
1. Queries DNS for TXT record: `_wizamart-verify.{domain}` 1. Queries DNS for TXT record: `_orion-verify.{domain}`
2. Checks if verification token matches 2. Checks if verification token matches
3. If found, marks domain as verified 3. If found, marks domain as verified
**Requirements:** **Requirements:**
- Store must have added TXT record to their DNS - Store must have added TXT record to their DNS
- DNS propagation may take 5-15 minutes - DNS propagation may take 5-15 minutes
- Record format: `_wizamart-verify.domain.com` TXT `{token}` - Record format: `_orion-verify.domain.com` TXT `{token}`
**After verification:** **After verification:**
- Domain can be activated - Domain can be activated

View File

@@ -42,7 +42,7 @@ def get_store_info(
**Returns only active stores** to prevent access to disabled accounts. **Returns only active stores** to prevent access to disabled accounts.
Args: Args:
store_code: The store's unique code (e.g., 'WIZAMART') store_code: The store's unique code (e.g., 'ORION')
db: Database session db: Database session
Returns: Returns:

View File

@@ -293,7 +293,7 @@ class MerchantDomainService:
Verify merchant domain ownership via DNS TXT record. Verify merchant domain ownership via DNS TXT record.
The merchant must add a TXT record: The merchant must add a TXT record:
Name: _wizamart-verify.{domain} Name: _orion-verify.{domain}
Value: {verification_token} Value: {verification_token}
""" """
try: try:
@@ -304,7 +304,7 @@ class MerchantDomainService:
try: try:
txt_records = dns.resolver.resolve( txt_records = dns.resolver.resolve(
f"_wizamart-verify.{domain.domain}", "TXT" f"_orion-verify.{domain.domain}", "TXT"
) )
for txt in txt_records: for txt in txt_records:
@@ -331,7 +331,7 @@ class MerchantDomainService:
except dns.resolver.NXDOMAIN: except dns.resolver.NXDOMAIN:
raise DomainVerificationFailedException( raise DomainVerificationFailedException(
domain.domain, domain.domain,
f"DNS record _wizamart-verify.{domain.domain} not found", f"DNS record _orion-verify.{domain.domain} not found",
) )
except dns.resolver.NoAnswer: except dns.resolver.NoAnswer:
raise DomainVerificationFailedException( raise DomainVerificationFailedException(
@@ -368,7 +368,7 @@ class MerchantDomainService:
}, },
"txt_record": { "txt_record": {
"type": "TXT", "type": "TXT",
"name": "_wizamart-verify", "name": "_orion-verify",
"value": domain.verification_token, "value": domain.verification_token,
"ttl": 3600, "ttl": 3600,
}, },

View File

@@ -284,7 +284,7 @@ class StoreDomainService:
Verify domain ownership via DNS TXT record. Verify domain ownership via DNS TXT record.
The store must add a TXT record: The store must add a TXT record:
Name: _wizamart-verify.{domain} Name: _orion-verify.{domain}
Value: {verification_token} Value: {verification_token}
Args: Args:
@@ -309,7 +309,7 @@ class StoreDomainService:
# Query DNS TXT records # Query DNS TXT records
try: try:
txt_records = dns.resolver.resolve( txt_records = dns.resolver.resolve(
f"_wizamart-verify.{domain.domain}", "TXT" f"_orion-verify.{domain.domain}", "TXT"
) )
# Check if verification token is present # Check if verification token is present
@@ -333,7 +333,7 @@ class StoreDomainService:
except dns.resolver.NXDOMAIN: except dns.resolver.NXDOMAIN:
raise DomainVerificationFailedException( raise DomainVerificationFailedException(
domain.domain, domain.domain,
f"DNS record _wizamart-verify.{domain.domain} not found", f"DNS record _orion-verify.{domain.domain} not found",
) )
except dns.resolver.NoAnswer: except dns.resolver.NoAnswer:
raise DomainVerificationFailedException( raise DomainVerificationFailedException(
@@ -382,7 +382,7 @@ class StoreDomainService:
}, },
"txt_record": { "txt_record": {
"type": "TXT", "type": "TXT",
"name": "_wizamart-verify", "name": "_orion-verify",
"value": domain.verification_token, "value": domain.verification_token,
"ttl": 3600, "ttl": 3600,
}, },

View File

@@ -5,7 +5,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Merchant Login - Wizamart</title> <title>Merchant Login - Orion</title>
<!-- Fonts: Local fallback + Google Fonts --> <!-- Fonts: Local fallback + Google Fonts -->
<link href="/static/shared/fonts/inter.css" rel="stylesheet" /> <link href="/static/shared/fonts/inter.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />

View File

@@ -514,7 +514,7 @@ class TestMerchantDomainServiceInstructions:
assert "instructions" in instructions assert "instructions" in instructions
assert "txt_record" in instructions assert "txt_record" in instructions
assert instructions["txt_record"]["type"] == "TXT" assert instructions["txt_record"]["type"] == "TXT"
assert instructions["txt_record"]["name"] == "_wizamart-verify" assert instructions["txt_record"]["name"] == "_orion-verify"
assert "common_registrars" in instructions assert "common_registrars" in instructions
def test_get_verification_instructions_not_found(self, db): def test_get_verification_instructions_not_found(self, db):

View File

@@ -411,7 +411,7 @@ class TestStoreDomainServiceInstructions:
assert "instructions" in instructions assert "instructions" in instructions
assert "txt_record" in instructions assert "txt_record" in instructions
assert instructions["txt_record"]["type"] == "TXT" assert instructions["txt_record"]["type"] == "TXT"
assert instructions["txt_record"]["name"] == "_wizamart-verify" assert instructions["txt_record"]["name"] == "_orion-verify"
assert "common_registrars" in instructions assert "common_registrars" in instructions
def test_get_verification_instructions_not_found(self, db): def test_get_verification_instructions_not_found(self, db):

View File

@@ -4,7 +4,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{% block title %}Merchant Portal{% endblock %} - Wizamart</title> <title>{% block title %}Merchant Portal{% endblock %} - Orion</title>
<!-- Fonts: Local fallback + Google Fonts --> <!-- Fonts: Local fallback + Google Fonts -->
<link href="/static/shared/fonts/inter.css" rel="stylesheet" /> <link href="/static/shared/fonts/inter.css" rel="stylesheet" />

View File

@@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
{# Dynamic page title #} {# Dynamic page title #}
<title>{% block title %}Wizamart - Order Management for Letzshop Sellers{% endblock %}</title> <title>{% block title %}Orion - Order Management for Letzshop Sellers{% endblock %}</title>
{# SEO Meta Tags #} {# SEO Meta Tags #}
<meta name="description" content="{% block meta_description %}Lightweight OMS for Letzshop stores in Luxembourg. Order management, inventory, and invoicing made simple.{% endblock %}"> <meta name="description" content="{% block meta_description %}Lightweight OMS for Letzshop stores in Luxembourg. Order management, inventory, and invoicing made simple.{% endblock %}">
@@ -64,7 +64,7 @@
<span class="text-white font-bold text-xl">W</span> <span class="text-white font-bold text-xl">W</span>
</div> </div>
<span class="text-xl font-bold text-gray-900 dark:text-white"> <span class="text-xl font-bold text-gray-900 dark:text-white">
Wizamart Orion
</span> </span>
</a> </a>
</div> </div>
@@ -190,7 +190,7 @@
<span class="text-white font-bold text-xl">W</span> <span class="text-white font-bold text-xl">W</span>
</div> </div>
<span class="text-xl font-bold text-gray-900 dark:text-white"> <span class="text-xl font-bold text-gray-900 dark:text-white">
Wizamart Orion
</span> </span>
</div> </div>
<p class="text-gray-600 dark:text-gray-400 text-sm"> <p class="text-gray-600 dark:text-gray-400 text-sm">
@@ -225,7 +225,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="/store/wizamart/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors"> <a href="/store/orion/login" class="text-gray-600 dark:text-gray-400 hover:text-primary transition-colors">
{{ _("cms.platform.nav.store_login") }} {{ _("cms.platform.nav.store_login") }}
</a> </a>
</li> </li>

View File

@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
# Salt for key derivation - fixed to ensure consistent encryption/decryption # Salt for key derivation - fixed to ensure consistent encryption/decryption
# In production, this should be stored securely and not changed # In production, this should be stored securely and not changed
_ENCRYPTION_SALT = b"wizamart_encryption_salt_v1" _ENCRYPTION_SALT = b"orion_encryption_salt_v1"
class EncryptionError(Exception): class EncryptionError(Exception):

View File

@@ -36,7 +36,7 @@ from main import app
# Use environment variable or default to local Docker test database # Use environment variable or default to local Docker test database
TEST_DATABASE_URL = os.getenv( TEST_DATABASE_URL = os.getenv(
"TEST_DATABASE_URL", "TEST_DATABASE_URL",
"postgresql://test_user:test_password@localhost:5433/wizamart_test" "postgresql://test_user:test_password@localhost:5433/orion_test"
) )

View File

@@ -5,7 +5,7 @@ services:
image: postgres:15 image: postgres:15
restart: "no" restart: "no"
environment: environment:
POSTGRES_DB: wizamart_test POSTGRES_DB: orion_test
POSTGRES_USER: test_user POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_password POSTGRES_PASSWORD: test_password
ports: ports:
@@ -13,7 +13,7 @@ services:
tmpfs: tmpfs:
- /var/lib/postgresql/data # Use RAM for faster tests - /var/lib/postgresql/data # Use RAM for faster tests
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U test_user -d wizamart_test"] test: ["CMD-SHELL", "pg_isready -U test_user -d orion_test"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View File

@@ -4,8 +4,8 @@ services:
image: postgres:15 image: postgres:15
restart: always restart: always
environment: environment:
POSTGRES_DB: wizamart_db POSTGRES_DB: orion_db
POSTGRES_USER: wizamart_user POSTGRES_USER: orion_user
POSTGRES_PASSWORD: secure_password POSTGRES_PASSWORD: secure_password
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
@@ -13,7 +13,7 @@ services:
ports: ports:
- "5432:5432" - "5432:5432"
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U wizamart_user -d wizamart_db"] test: ["CMD-SHELL", "pg_isready -U orion_user -d orion_db"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@@ -37,7 +37,7 @@ services:
ports: ports:
- "8001:8000" # Use 8001 to avoid conflict with local dev server - "8001:8000" # Use 8001 to avoid conflict with local dev server
environment: environment:
DATABASE_URL: postgresql://wizamart_user:secure_password@db:5432/wizamart_db DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-super-secret-key} JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-super-secret-key}
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
USE_CELERY: "true" USE_CELERY: "true"
@@ -63,7 +63,7 @@ services:
- full # Only start with: docker compose --profile full up -d - full # Only start with: docker compose --profile full up -d
command: celery -A app.core.celery_config worker --loglevel=info -Q default,long_running,scheduled command: celery -A app.core.celery_config worker --loglevel=info -Q default,long_running,scheduled
environment: environment:
DATABASE_URL: postgresql://wizamart_user:secure_password@db:5432/wizamart_db DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
depends_on: depends_on:
db: db:

View File

@@ -22,7 +22,7 @@
## System Overview ## System Overview
The Wizamart platform uses a **context-based authentication system** with three isolated security domains: The Orion platform uses a **context-based authentication system** with three isolated security domains:
- **Admin Portal** - Platform administration and management - **Admin Portal** - Platform administration and management
- **Store Portal** - Multi-tenant shop management - **Store Portal** - Multi-tenant shop management

View File

@@ -73,7 +73,7 @@ from app.exceptions import (
## Error Response Format ## Error Response Format
All custom exceptions (inheriting from `WizamartException`) return a structured JSON format: All custom exceptions (inheriting from `OrionException`) return a structured JSON format:
```json ```json
{ {

View File

@@ -1,6 +1,6 @@
# API Overview # API Overview
The Wizamart API provides comprehensive endpoints for managing products, shops, users, and marketplace imports. This section provides high-level guidance and concepts for working with our API. The Orion API provides comprehensive endpoints for managing products, shops, users, and marketplace imports. This section provides high-level guidance and concepts for working with our API.
## Interactive Documentation ## Interactive Documentation
@@ -15,7 +15,7 @@ For hands-on API exploration and testing, use our interactive documentation:
### Base URL ### Base URL
``` ```
Production: https://wizamart.com/api/v1 Production: https://orion.lu/api/v1
Development: http://localhost:8000/api/v1 Development: http://localhost:8000/api/v1
``` ```
@@ -182,4 +182,4 @@ X-RateLimit-Reset: 1640995200
- **[Authentication Guide](authentication.md)** - Learn about API authentication - **[Authentication Guide](authentication.md)** - Learn about API authentication
- **[Error Handling](error-handling.md)** - Understanding API errors - **[Error Handling](error-handling.md)** - Understanding API errors
- **[Rate Limiting](rate-limiting.md)** - Rate limiting details - **[Rate Limiting](rate-limiting.md)** - Rate limiting details
- **[Interactive Docs](http://localhost:8000/docs)** - Try the API live - **[Interactive Docs](http://localhost:8000/docs)** - Try the API live

View File

@@ -24,17 +24,17 @@ The Storefront API provides customer-facing endpoints for browsing products, man
All Storefront API endpoints automatically receive store context from the `StoreContextMiddleware`: All Storefront API endpoints automatically receive store context from the `StoreContextMiddleware`:
1. **Browser makes API call** from storefront page (e.g., `/stores/wizamart/storefront/products`) 1. **Browser makes API call** from storefront page (e.g., `/stores/orion/storefront/products`)
2. **Browser automatically sends Referer header**: `http://localhost:8000/stores/wizamart/storefront/products` 2. **Browser automatically sends Referer header**: `http://localhost:8000/stores/orion/storefront/products`
3. **Middleware extracts store** from Referer path/subdomain/domain 3. **Middleware extracts store** from Referer path/subdomain/domain
4. **Middleware sets** `request.state.store = <Store: wizamart>` 4. **Middleware sets** `request.state.store = <Store: orion>`
5. **API endpoint accesses store**: `store = request.state.store` 5. **API endpoint accesses store**: `store = request.state.store`
6. **No store_id needed in URL!** 6. **No store_id needed in URL!**
### Supported Store Detection Methods ### Supported Store Detection Methods
- **Path-based**: `/stores/wizamart/storefront/products` → extracts `wizamart` - **Path-based**: `/stores/orion/storefront/products` → extracts `orion`
- **Subdomain**: `wizamart.platform.com` → extracts `wizamart` - **Subdomain**: `orion.platform.com` → extracts `orion`
- **Custom domain**: `customshop.com` → looks up store by domain - **Custom domain**: `customshop.com` → looks up store by domain
--- ---
@@ -92,7 +92,7 @@ Get paginated list of products for current store.
```http ```http
GET /api/v1/storefront/products?skip=0&limit=20&is_featured=true GET /api/v1/storefront/products?skip=0&limit=20&is_featured=true
Referer: http://localhost:8000/stores/wizamart/shop/products Referer: http://localhost:8000/stores/orion/shop/products
``` ```
**Response (200 OK):** **Response (200 OK):**
@@ -142,7 +142,7 @@ Get detailed information for a specific product.
```http ```http
GET /api/v1/storefront/products/1 GET /api/v1/storefront/products/1
Referer: http://localhost:8000/stores/wizamart/shop/products Referer: http://localhost:8000/stores/orion/shop/products
``` ```
**Response (200 OK):** **Response (200 OK):**
@@ -200,7 +200,7 @@ Retrieve cart contents for a session.
```http ```http
GET /api/v1/storefront/cart/session-abc-123 GET /api/v1/storefront/cart/session-abc-123
Referer: http://localhost:8000/stores/wizamart/shop/cart Referer: http://localhost:8000/stores/orion/shop/cart
``` ```
**Response (200 OK):** **Response (200 OK):**

View File

@@ -99,7 +99,7 @@ Developers must remember:
### Broken Features ### Broken Features
**Current Issue:** CMS pages not loading at `/stores/wizamart/about` **Current Issue:** CMS pages not loading at `/stores/orion/about`
**Root Cause:** **Root Cause:**
- CMS API exists at `/api/v1/shop/content-pages/{slug}` - CMS API exists at `/api/v1/shop/content-pages/{slug}`

View File

@@ -21,9 +21,9 @@ Updated `StoreContextMiddleware` to support shop API routes:
- Shop API now receives store context from the page that made the API call - Shop API now receives store context from the page that made the API call
**How it works:** **How it works:**
1. Browser JavaScript on `/stores/wizamart/shop/products` calls `/api/v1/shop/products` 1. Browser JavaScript on `/stores/orion/shop/products` calls `/api/v1/shop/products`
2. Browser automatically sends `Referer: http://localhost:8000/stores/wizamart/shop/products` 2. Browser automatically sends `Referer: http://localhost:8000/stores/orion/shop/products`
3. Middleware extracts `wizamart` from Referer path 3. Middleware extracts `orion` from Referer path
4. Queries database to get Store object 4. Queries database to get Store object
5. Sets `request.state.store` for the API endpoint 5. Sets `request.state.store` for the API endpoint

View File

@@ -286,7 +286,7 @@ def get_products(
"sub": "user_id", "sub": "user_id",
"username": "john.doe", "username": "john.doe",
"store_id": 123, Store context "store_id": 123, Store context
"store_code": "WIZAMART", Store code "store_code": "ORION", Store code
"store_role": "Owner" Store role "store_role": "Owner" Store role
} }
``` ```

View File

@@ -6,7 +6,7 @@ This document defines the harmonized architecture for all background tasks in th
## Task Queue Infrastructure ## Task Queue Infrastructure
Wizamart uses **Celery with Redis** for production-grade background task processing: Orion uses **Celery with Redis** for production-grade background task processing:
| Component | Purpose | Port | | Component | Purpose | Port |
|-----------|---------|------| |-----------|---------|------|

View File

@@ -1,6 +1,6 @@
# Capacity Planning & Infrastructure Sizing # Capacity Planning & Infrastructure Sizing
This document provides comprehensive capacity planning guidelines for the Wizamart platform, including resource requirements, scaling thresholds, and monitoring recommendations. This document provides comprehensive capacity planning guidelines for the Orion platform, including resource requirements, scaling thresholds, and monitoring recommendations.
> **Related:** [Pricing Strategy](../marketing/pricing.md) for tier definitions and limits > **Related:** [Pricing Strategy](../marketing/pricing.md) for tier definitions and limits

View File

@@ -175,16 +175,16 @@ Customer → DNS → Server → Nginx → FastAPI
FastAPI Middleware: FastAPI Middleware:
host = "store1.platform.com" host = "store1.platform.com"
Step 1: Custom domain? NO (ends with .platform.com) Step 1: Custom domain? NO (ends with .platform.com)
Step 2: Subdomain? YES Step 2: Subdomain? YES
Extract "store1" Extract "store1"
Query: SELECT * FROM stores Query: SELECT * FROM stores
WHERE subdomain = 'store1' WHERE subdomain = 'store1'
Result: Store 1 Result: Store 1
request.state.store = Store 1 request.state.store = Store 1
Route → Render Store 1's shop Route → Render Store 1's shop
``` ```
@@ -195,18 +195,18 @@ Customer → localhost:8000/store/store1/
FastAPI Middleware: FastAPI Middleware:
host = "localhost:8000" host = "localhost:8000"
path = "/store/store1/" path = "/store/store1/"
Step 1: Custom domain? NO (localhost) Step 1: Custom domain? NO (localhost)
Step 2: Subdomain? NO (localhost has no subdomain) Step 2: Subdomain? NO (localhost has no subdomain)
Step 3: Path-based? YES Step 3: Path-based? YES
Extract "store1" from path Extract "store1" from path
Query: SELECT * FROM stores Query: SELECT * FROM stores
WHERE subdomain = 'store1' WHERE subdomain = 'store1'
Result: Store 1 Result: Store 1
request.state.store = Store 1 request.state.store = Store 1
request.state.clean_path = "/" (strip /store/store1) request.state.clean_path = "/" (strip /store/store1)
Route → Render Store 1's shop Route → Render Store 1's shop
``` ```
@@ -402,7 +402,7 @@ Value: 123.45.67.89
TTL: 3600 TTL: 3600
Type: TXT Type: TXT
Name: _wizamart-verify Name: _orion-verify
Value: abc123xyz (verification token from your platform) Value: abc123xyz (verification token from your platform)
TTL: 3600 TTL: 3600
``` ```

View File

@@ -237,7 +237,7 @@
│ Exception Handler │ │ Exception Handler │
│ app/exceptions/handler.py │ │ app/exceptions/handler.py │
│ │ │ │
│ @app.exception_handler(WizamartException) │ │ @app.exception_handler(OrionException) │
│ async def custom_exception_handler(...): │ │ async def custom_exception_handler(...): │
│ return JSONResponse( │ │ return JSONResponse( │
│ status_code=exc.status_code, │ │ status_code=exc.status_code, │
@@ -331,7 +331,7 @@ Step 2: Get Instructions
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ System returns instructions: │ │ System returns instructions: │
│ "Add TXT record: │ │ "Add TXT record: │
│ _wizamart-verify.myshop.com │ │ _orion-verify.myshop.com │
│ Value: abc123..." │ │ Value: abc123..." │
└────────────────────────────────────┘ └────────────────────────────────────┘
@@ -343,7 +343,7 @@ Step 3: Store Adds DNS Record
┌────────────────────────────────────┐ ┌────────────────────────────────────┐
│ DNS Provider (GoDaddy/etc) │ │ DNS Provider (GoDaddy/etc) │
│ _wizamart-verify.myshop.com TXT │ │ _orion-verify.myshop.com TXT │
│ "abc123..." │ │ "abc123..." │
└────────────────────────────────────┘ └────────────────────────────────────┘

View File

@@ -10,7 +10,7 @@ The application serves multiple frontends from a single codebase:
|----------|-------------|--------------| |----------|-------------|--------------|
| **ADMIN** | Platform administration | `/admin/*`, `/api/v1/admin/*`, `admin.oms.lu/*` | | **ADMIN** | Platform administration | `/admin/*`, `/api/v1/admin/*`, `admin.oms.lu/*` |
| **STORE** | Store dashboard | `/store/*`, `/api/v1/store/*` | | **STORE** | Store dashboard | `/store/*`, `/api/v1/store/*` |
| **STOREFRONT** | Customer-facing shop | `/storefront/*`, `/stores/*`, `wizamart.oms.lu/*` | | **STOREFRONT** | Customer-facing shop | `/storefront/*`, `/stores/*`, `orion.oms.lu/*` |
| **PLATFORM** | Marketing pages | `/`, `/pricing`, `/about` | | **PLATFORM** | Marketing pages | `/`, `/pricing`, `/about` |
The `FrontendDetector` class provides centralized, consistent detection of which frontend a request targets. The `FrontendDetector` class provides centralized, consistent detection of which frontend a request targets.
@@ -70,7 +70,7 @@ The `FrontendDetector` uses the following priority order:
- /store/* or /api/v1/store/* → STORE - /store/* or /api/v1/store/* → STORE
- /storefront/*, /shop/*, /stores/* → STOREFRONT - /storefront/*, /shop/*, /stores/* → STOREFRONT
- /api/v1/platform/* → PLATFORM - /api/v1/platform/* → PLATFORM
3. Store subdomain (wizamart.oms.lu) → STOREFRONT 3. Store subdomain (orion.oms.lu) → STOREFRONT
4. Store context set by middleware → STOREFRONT 4. Store context set by middleware → STOREFRONT
5. Default → PLATFORM 5. Default → PLATFORM
``` ```
@@ -133,7 +133,7 @@ from app.modules.enums import FrontendType
# Full detection # Full detection
frontend_type = FrontendDetector.detect( frontend_type = FrontendDetector.detect(
host="wizamart.oms.lu", host="orion.oms.lu",
path="/products", path="/products",
has_store_context=True has_store_context=True
) )
@@ -160,7 +160,7 @@ if FrontendDetector.is_storefront(host, path, has_store_context=True):
| Store dashboard | localhost | /store/settings | STORE | | Store dashboard | localhost | /store/settings | STORE |
| Store API | localhost | /api/v1/store/products | STORE | | Store API | localhost | /api/v1/store/products | STORE |
| Storefront | localhost | /storefront/products | STOREFRONT | | Storefront | localhost | /storefront/products | STOREFRONT |
| Storefront (path-based) | localhost | /stores/wizamart/products | STOREFRONT | | Storefront (path-based) | localhost | /stores/orion/products | STOREFRONT |
| Marketing | localhost | /pricing | PLATFORM | | Marketing | localhost | /pricing | PLATFORM |
### Production Mode (domains) ### Production Mode (domains)
@@ -168,7 +168,7 @@ if FrontendDetector.is_storefront(host, path, has_store_context=True):
| Request | Host | Path | Frontend | | Request | Host | Path | Frontend |
|---------|------|------|----------| |---------|------|------|----------|
| Admin subdomain | admin.oms.lu | /dashboard | ADMIN | | Admin subdomain | admin.oms.lu | /dashboard | ADMIN |
| Store subdomain | wizamart.oms.lu | /products | STOREFRONT | | Store subdomain | orion.oms.lu | /products | STOREFRONT |
| Custom domain | mybakery.lu | /products | STOREFRONT | | Custom domain | mybakery.lu | /products | STOREFRONT |
| Platform root | oms.lu | /pricing | PLATFORM | | Platform root | oms.lu | /pricing | PLATFORM |

View File

@@ -1,6 +1,6 @@
# Language & Internationalization (i18n) Architecture # Language & Internationalization (i18n) Architecture
This document defines **strict rules** for implementing language support across the Wizamart platform. This document defines **strict rules** for implementing language support across the Orion platform.
> **IMPORTANT:** These rules are mandatory. Violations will cause runtime errors, inconsistent UX, or security issues. > **IMPORTANT:** These rules are mandatory. Violations will cause runtime errors, inconsistent UX, or security issues.
@@ -49,7 +49,7 @@ language = "lux" # ❌ Use "lb"
Language is resolved in this order (highest to lowest priority): Language is resolved in this order (highest to lowest priority):
1. **URL parameter** (`?lang=fr`) 1. **URL parameter** (`?lang=fr`)
2. **Cookie** (`wizamart_language`) 2. **Cookie** (`orion_language`)
3. **User preference** (database: `preferred_language`) 3. **User preference** (database: `preferred_language`)
4. **Store default** (database: `storefront_language` or `dashboard_language`) 4. **Store default** (database: `storefront_language` or `dashboard_language`)
5. **Accept-Language header** (browser) 5. **Accept-Language header** (browser)
@@ -270,7 +270,7 @@ async def set_language(request: LanguageSetRequest, response: Response):
raise HTTPException(status_code=400, detail="Unsupported language") raise HTTPException(status_code=400, detail="Unsupported language")
response.set_cookie( response.set_cookie(
key="wizamart_language", key="orion_language",
value=request.language, value=request.language,
max_age=365 * 24 * 60 * 60, # 1 year max_age=365 * 24 * 60 * 60, # 1 year
httponly=True, httponly=True,

View File

@@ -2,7 +2,7 @@
## Executive Summary ## Executive Summary
This document defines the complete architecture for integrating Wizamart with multiple external marketplaces (Letzshop, Amazon, eBay) and digital product suppliers (CodesWholesale). The integration is **bidirectional**, supporting both inbound flows (products, orders) and outbound flows (inventory sync, fulfillment status). This document defines the complete architecture for integrating Orion with multiple external marketplaces (Letzshop, Amazon, eBay) and digital product suppliers (CodesWholesale). The integration is **bidirectional**, supporting both inbound flows (products, orders) and outbound flows (inventory sync, fulfillment status).
**Key Capabilities:** **Key Capabilities:**
@@ -26,7 +26,7 @@ graph TB
AZ[Amazon<br/>API] AZ[Amazon<br/>API]
EB[eBay<br/>API] EB[eBay<br/>API]
CW[CodesWholesale<br/>Digital Supplier API] CW[CodesWholesale<br/>Digital Supplier API]
WS[Store Storefront<br/>Wizamart Shop] WS[Store Storefront<br/>Orion Shop]
end end
subgraph "Integration Layer" subgraph "Integration Layer"
@@ -42,7 +42,7 @@ graph TB
end end
end end
subgraph "Wizamart Core" subgraph "Orion Core"
MP[Marketplace Products] MP[Marketplace Products]
P[Store Products] P[Store Products]
O[Unified Orders] O[Unified Orders]
@@ -263,7 +263,7 @@ graph TB
```python ```python
class OrderChannel(str, Enum): class OrderChannel(str, Enum):
"""Order source channel.""" """Order source channel."""
STOREFRONT = "storefront" # Store's own Wizamart shop STOREFRONT = "storefront" # Store's own Orion shop
LETZSHOP = "letzshop" LETZSHOP = "letzshop"
AMAZON = "amazon" AMAZON = "amazon"
EBAY = "ebay" EBAY = "ebay"
@@ -1325,7 +1325,7 @@ def map_codeswholesale_product(cw_product: dict) -> dict:
### Order Status Mapping ### Order Status Mapping
| Wizamart Status | Letzshop | Amazon | eBay | | Orion Status | Letzshop | Amazon | eBay |
|-----------------|----------|--------|------| |-----------------|----------|--------|------|
| PENDING | PENDING | Pending | - | | PENDING | PENDING | Pending | - |
| CONFIRMED | PAID | Unshipped | Paid | | CONFIRMED | PAID | Unshipped | Paid |

View File

@@ -1,6 +1,6 @@
# Menu Management Architecture # Menu Management Architecture
The Wizamart platform provides a **module-driven menu system** where each module defines its own menu items. The `MenuDiscoveryService` aggregates menus from all enabled modules, applying visibility configuration and permission filtering. The Orion platform provides a **module-driven menu system** where each module defines its own menu items. The `MenuDiscoveryService` aggregates menus from all enabled modules, applying visibility configuration and permission filtering.
## Overview ## Overview

View File

@@ -2,7 +2,7 @@
## Overview ## Overview
The Wizamart platform implements a hierarchical multi-tenant architecture where **Merchants** are the primary business entities and **Stores** are storefronts/brands that operate under merchants. The Orion platform implements a hierarchical multi-tenant architecture where **Merchants** are the primary business entities and **Stores** are storefronts/brands that operate under merchants.
``` ```
Merchant (Business Entity) Merchant (Business Entity)

View File

@@ -103,11 +103,11 @@ INFO Response: 200 for GET /admin/dashboard (0.143s)
**Example**: **Example**:
``` ```
Request: https://wizamart.platform.com/shop/products Request: https://orion.platform.com/shop/products
Middleware detects: store_code = "wizamart" Middleware detects: store_code = "orion"
Queries database: SELECT * FROM stores WHERE code = 'wizamart' Queries database: SELECT * FROM stores WHERE code = 'orion'
Injects: request.state.store = <Store object> Injects: request.state.store = <Store object>
request.state.store_id = 1 request.state.store_id = 1
@@ -141,7 +141,7 @@ Injects: request.state.store = <Store object>
- /store/* or /api/v1/store/* STORE - /store/* or /api/v1/store/* STORE
- /storefront/*, /shop/*, /stores/* STOREFRONT - /storefront/*, /shop/*, /stores/* STOREFRONT
- /api/v1/platform/* PLATFORM - /api/v1/platform/* PLATFORM
3. Store subdomain (wizamart.oms.lu) STOREFRONT 3. Store subdomain (orion.oms.lu) STOREFRONT
4. Store context set by middleware STOREFRONT 4. Store context set by middleware STOREFRONT
5. Default PLATFORM 5. Default PLATFORM
``` ```
@@ -165,8 +165,8 @@ Injects: request.state.store = <Store object>
{ {
"primary_color": "#3B82F6", "primary_color": "#3B82F6",
"secondary_color": "#10B981", "secondary_color": "#10B981",
"logo_url": "/static/stores/wizamart/logo.png", "logo_url": "/static/stores/orion/logo.png",
"favicon_url": "/static/stores/wizamart/favicon.ico", "favicon_url": "/static/stores/orion/favicon.ico",
"custom_css": "/* store-specific styles */" "custom_css": "/* store-specific styles */"
} }
``` ```
@@ -376,7 +376,7 @@ async def get_products(request: Request):
### Example: Shop Product Page Request ### Example: Shop Product Page Request
**URL**: `https://wizamart.myplatform.com/shop/products` **URL**: `https://orion.myplatform.com/shop/products`
**Middleware Processing**: **Middleware Processing**:
@@ -386,9 +386,9 @@ async def get_products(request: Request):
↓ Logs: "Request: GET /shop/products from 192.168.1.100" ↓ Logs: "Request: GET /shop/products from 192.168.1.100"
2. StoreContextMiddleware 2. StoreContextMiddleware
↓ Detects subdomain: "wizamart" ↓ Detects subdomain: "orion"
↓ Queries DB: store = get_store_by_code("wizamart") ↓ Queries DB: store = get_store_by_code("orion")
↓ Sets: request.state.store = <Store: Wizamart> ↓ Sets: request.state.store = <Store: Orion>
↓ Sets: request.state.store_id = 1 ↓ Sets: request.state.store_id = 1
↓ Sets: request.state.clean_path = "/shop/products" ↓ Sets: request.state.clean_path = "/shop/products"
@@ -496,13 +496,13 @@ from middleware.store_context import StoreContextManager
def test_store_detection_subdomain(): def test_store_detection_subdomain():
# Mock request # Mock request
request = create_mock_request(host="wizamart.platform.com") request = create_mock_request(host="orion.platform.com")
# Test detection # Test detection
manager = StoreContextManager() manager = StoreContextManager()
store = manager.detect_store_from_subdomain(request) store = manager.detect_store_from_subdomain(request)
assert store.code == "wizamart" assert store.code == "orion"
``` ```
### Integration Testing ### Integration Testing
@@ -513,11 +513,11 @@ Test the full middleware stack:
def test_shop_request_flow(client): def test_shop_request_flow(client):
response = client.get( response = client.get(
"/shop/products", "/shop/products",
headers={"Host": "wizamart.platform.com"} headers={"Host": "orion.platform.com"}
) )
assert response.status_code == 200 assert response.status_code == 200
assert "Wizamart" in response.text assert "Orion" in response.text
``` ```
**See**: [Testing Guide](../testing/testing-guide.md) **See**: [Testing Guide](../testing/testing-guide.md)

View File

@@ -1,6 +1,6 @@
# Module System Architecture # Module System Architecture
The Wizamart platform uses a **plug-and-play modular architecture** where modules are fully self-contained and automatically discovered. Simply create a module directory with the required structure, and the framework handles registration, routing, and resource loading automatically. The Orion platform uses a **plug-and-play modular architecture** where modules are fully self-contained and automatically discovered. Simply create a module directory with the required structure, and the framework handles registration, routing, and resource loading automatically.
## Key Features ## Key Features
@@ -1029,11 +1029,11 @@ __all__ = ["process_import", "export_products"]
### Exceptions ### Exceptions
Module-specific exceptions inherit from `WizamartException`. Module-specific exceptions inherit from `OrionException`.
| Location | Base Class | Usage | | Location | Base Class | Usage |
|----------|------------|-------| |----------|------------|-------|
| `exceptions.py` | `WizamartException` | Domain errors | | `exceptions.py` | `OrionException` | Domain errors |
**Structure:** **Structure:**
``` ```
@@ -1044,9 +1044,9 @@ app/modules/{module}/
**Example:** **Example:**
```python ```python
# app/modules/orders/exceptions.py # app/modules/orders/exceptions.py
from app.exceptions import WizamartException from app.exceptions import OrionException
class OrderException(WizamartException): class OrderException(OrionException):
"""Base exception for orders module.""" """Base exception for orders module."""
pass pass

View File

@@ -2,7 +2,7 @@
## Overview ## Overview
The Multi-Platform CMS enables Wizamart to serve multiple business offerings (OMS, Loyalty, Site Builder) from a single codebase, each with its own marketing site and store ecosystem. The Multi-Platform CMS enables Orion to serve multiple business offerings (OMS, Loyalty, Site Builder) from a single codebase, each with its own marketing site and store ecosystem.
## Three-Tier Content Hierarchy ## Three-Tier Content Hierarchy
@@ -71,7 +71,7 @@ CREATE TABLE platforms (
code VARCHAR(50) UNIQUE NOT NULL, -- 'oms', 'loyalty', 'sitebuilder' code VARCHAR(50) UNIQUE NOT NULL, -- 'oms', 'loyalty', 'sitebuilder'
name VARCHAR(100) NOT NULL, -- 'Order Management System' name VARCHAR(100) NOT NULL, -- 'Order Management System'
description TEXT, description TEXT,
domain VARCHAR(255), -- 'oms.wizamart.lu' domain VARCHAR(255), -- 'oms.orion.lu'
path_prefix VARCHAR(50), -- '/oms' path_prefix VARCHAR(50), -- '/oms'
logo VARCHAR(255), logo VARCHAR(255),
logo_dark VARCHAR(255), logo_dark VARCHAR(255),
@@ -122,7 +122,7 @@ The system uses different URL patterns for development vs production:
- Platform sites: `localhost:9999/platforms/{code}/` → specific platform - Platform sites: `localhost:9999/platforms/{code}/` → specific platform
**Production (custom domains):** **Production (custom domains):**
- Main marketing site: `wizamart.lu/``main` platform - Main marketing site: `orion.lu/``main` platform
- Platform sites: `oms.lu/`, `loyalty.lu/` → specific platform - Platform sites: `oms.lu/`, `loyalty.lu/` → specific platform
### Request Processing ### Request Processing
@@ -284,6 +284,6 @@ Request: GET /about
| Platform | Code | Dev URL | Prod URL | | Platform | Code | Dev URL | Prod URL |
|----------|------|---------|----------| |----------|------|---------|----------|
| Main Marketing | `main` | `localhost:9999/` | `wizamart.lu/` | | Main Marketing | `main` | `localhost:9999/` | `orion.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` | | OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` | | Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |

View File

@@ -4,7 +4,7 @@ Complete guide to the multi-tenant architecture supporting custom domains, subdo
## Overview ## Overview
The Wizamart platform supports **three deployment modes** for multi-tenancy, allowing each store to have their own isolated shop while sharing the same application instance and database. The Orion platform supports **three deployment modes** for multi-tenancy, allowing each store to have their own isolated shop while sharing the same application instance and database.
**Key Concept**: One application, multiple isolated store shops, each accessible via different URLs. **Key Concept**: One application, multiple isolated store shops, each accessible via different URLs.
@@ -148,15 +148,15 @@ For path-based routing, clean paths are extracted:
**Path-Based Shop Routes** (Development): **Path-Based Shop Routes** (Development):
``` ```
Original: /stores/WIZAMART/shop/products Original: /stores/ORION/shop/products
Extracted: store_code = "WIZAMART" Extracted: store_code = "ORION"
Clean: /shop/products Clean: /shop/products
``` ```
**Store Dashboard Routes** (All environments): **Store Dashboard Routes** (All environments):
``` ```
Original: /store/WIZAMART/dashboard Original: /store/ORION/dashboard
Extracted: store_code = "WIZAMART" Extracted: store_code = "ORION"
Clean: /dashboard Clean: /dashboard
``` ```
@@ -197,13 +197,13 @@ CREATE TABLE store_domains (
```sql ```sql
-- Stores -- Stores
INSERT INTO stores (code, name) VALUES INSERT INTO stores (code, name) VALUES
('wizamart', 'Wizamart Shop'), ('orion', 'Orion Shop'),
('techstore', 'Tech Store'), ('techstore', 'Tech Store'),
('fashionhub', 'Fashion Hub'); ('fashionhub', 'Fashion Hub');
-- Custom Domains -- Custom Domains
INSERT INTO store_domains (store_id, domain) VALUES INSERT INTO store_domains (store_id, domain) VALUES
(1, 'wizamart.com'), (1, 'orion.lu'),
(2, 'mytechstore.net'); (2, 'mytechstore.net');
``` ```
@@ -421,16 +421,16 @@ Host: customdomain.com
**Request**: **Request**:
```http ```http
GET /shop/products HTTP/1.1 GET /shop/products HTTP/1.1
Host: wizamart.myplatform.com Host: orion.myplatform.com
``` ```
**Processing**: **Processing**:
``` ```
1. StoreContextMiddleware 1. StoreContextMiddleware
- Checks: host != "myplatform.com" - Checks: host != "myplatform.com"
- Extracts: subdomain = "wizamart" - Extracts: subdomain = "orion"
- Queries: stores WHERE code = "wizamart" - Queries: stores WHERE code = "orion"
- Sets: request.state.store = <Store "wizamart"> - Sets: request.state.store = <Store "orion">
2-4. Same as Example 1 2-4. Same as Example 1
``` ```
@@ -439,7 +439,7 @@ Host: wizamart.myplatform.com
**Request**: **Request**:
```http ```http
GET /stores/WIZAMART/shop/products HTTP/1.1 GET /stores/ORION/shop/products HTTP/1.1
Host: myplatform.com Host: myplatform.com
``` ```
@@ -447,15 +447,15 @@ Host: myplatform.com
``` ```
1. StoreContextMiddleware 1. StoreContextMiddleware
- Checks: path starts with "/store/" - Checks: path starts with "/store/"
- Extracts: code = "WIZAMART" - Extracts: code = "ORION"
- Queries: stores WHERE code = "WIZAMART" - Queries: stores WHERE code = "ORION"
- Sets: request.state.store = <Store> - Sets: request.state.store = <Store>
- Sets: request.state.clean_path = "/shop/products" - Sets: request.state.clean_path = "/shop/products"
2. FastAPI Router 2. FastAPI Router
- Routes registered with prefix: /stores/{store_code}/shop - Routes registered with prefix: /stores/{store_code}/shop
- Matches: /stores/WIZAMART/shop/products - Matches: /stores/ORION/shop/products
- store_code path parameter = "WIZAMART" - store_code path parameter = "ORION"
3-4. Same as previous examples (Context, Theme middleware) 3-4. Same as previous examples (Context, Theme middleware)
``` ```
@@ -491,9 +491,9 @@ def test_shop_page_multi_tenant(client):
# Test subdomain routing # Test subdomain routing
response = client.get( response = client.get(
"/shop/products", "/shop/products",
headers={"Host": "wizamart.platform.com"} headers={"Host": "orion.platform.com"}
) )
assert "Wizamart" in response.text assert "Orion" in response.text
# Test different store # Test different store
response = client.get( response = client.get(

View File

@@ -1,6 +1,6 @@
# Observability Framework # Observability Framework
The Wizamart platform includes a comprehensive observability framework for monitoring application health, collecting metrics, and tracking errors. This is part of the Framework Layer - infrastructure that modules depend on. The Orion platform includes a comprehensive observability framework for monitoring application health, collecting metrics, and tracking errors. This is part of the Framework Layer - infrastructure that modules depend on.
## Overview ## Overview

View File

@@ -1,10 +1,10 @@
# System Architecture # System Architecture
High-level overview of the Wizamart multi-tenant e-commerce platform architecture. High-level overview of the Orion multi-tenant e-commerce platform architecture.
## Overview ## Overview
Wizamart is a **multi-tenant e-commerce platform** that supports three distinct interfaces: Orion is a **multi-tenant e-commerce platform** that supports three distinct interfaces:
- **Admin** - Platform administration and store management - **Admin** - Platform administration and store management
- **Store** - Store dashboard for managing shops - **Store** - Store dashboard for managing shops
- **Shop** - Customer-facing storefronts - **Shop** - Customer-facing storefronts

View File

@@ -1,6 +1,6 @@
# Request Flow # Request Flow
Complete journey of a request through the Wizamart platform, from client to response. Complete journey of a request through the Orion platform, from client to response.
## Overview ## Overview
@@ -42,8 +42,8 @@ graph TB
```http ```http
# Shop page request (subdomain mode) # Shop page request (subdomain mode)
GET https://wizamart.platform.com/shop/products GET https://orion.platform.com/shop/products
Host: wizamart.platform.com Host: orion.platform.com
# API request # API request
GET https://platform.com/api/v1/products?store_id=1 GET https://platform.com/api/v1/products?store_id=1
@@ -86,13 +86,13 @@ logger.info(f"Request: GET /shop/products from 192.168.1.100")
```python ```python
# Input # Input
host = "wizamart.platform.com" host = "orion.platform.com"
path = "/shop/products" path = "/shop/products"
# Detection logic # Detection logic
if host != settings.platform_domain: if host != settings.platform_domain:
# Subdomain detected # Subdomain detected
store_code = host.split('.')[0] # "wizamart" store_code = host.split('.')[0] # "orion"
# Query database # Query database
store = db.query(Store).filter( store = db.query(Store).filter(
@@ -107,7 +107,7 @@ if host != settings.platform_domain:
**Request State After**: **Request State After**:
```python ```python
request.state.store = <Store: Wizamart> request.state.store = <Store: Orion>
request.state.store_id = 1 request.state.store_id = 1
request.state.clean_path = "/shop/products" request.state.clean_path = "/shop/products"
``` ```
@@ -127,10 +127,10 @@ request.state.clean_path = "/shop/products"
app.include_router(shop_pages.router, prefix="/shop") app.include_router(shop_pages.router, prefix="/shop")
app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop")
# Request: /stores/WIZAMART/shop/products # Request: /stores/ORION/shop/products
# Matches: Second router (/stores/{store_code}/shop) # Matches: Second router (/stores/{store_code}/shop)
# Route: @router.get("/products") # Route: @router.get("/products")
# store_code available as path parameter = "WIZAMART" # store_code available as path parameter = "ORION"
``` ```
**Note:** Previous implementations used `PathRewriteMiddleware` to rewrite paths. This has been replaced with FastAPI's native routing via double router mounting. **Note:** Previous implementations used `PathRewriteMiddleware` to rewrite paths. This has been replaced with FastAPI's native routing via double router mounting.
@@ -194,7 +194,7 @@ if hasattr(request.state, 'store_id'):
request.state.theme = { request.state.theme = {
"primary_color": "#3B82F6", "primary_color": "#3B82F6",
"secondary_color": "#10B981", "secondary_color": "#10B981",
"logo_url": "/static/stores/wizamart/logo.png", "logo_url": "/static/stores/orion/logo.png",
"custom_css": "..." "custom_css": "..."
} }
``` ```
@@ -289,7 +289,7 @@ async def shop_products_page(
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Wizamart - Products</title> <title>Orion - Products</title>
<style> <style>
:root { :root {
--primary-color: #3B82F6; --primary-color: #3B82F6;
@@ -298,7 +298,7 @@ async def shop_products_page(
</style> </style>
</head> </head>
<body> <body>
<h1>Wizamart Shop</h1> <h1>Orion Shop</h1>
<div class="products"> <div class="products">
<div class="product-card"> <div class="product-card">
<h2>Product 1</h2> <h2>Product 1</h2>
@@ -412,7 +412,7 @@ sequenceDiagram
Logging->>Store: Pass request Logging->>Store: Pass request
Store->>DB: Query store by subdomain Store->>DB: Query store by subdomain
DB-->>Store: Store object DB-->>Store: Store object
Note over Store: Set store, store_id, clean_path Note over Store: Set store, store_id, clean_path
Store->>Path: Pass request Store->>Path: Pass request
Note over Path: Path already clean Note over Path: Path already clean
Path->>Context: Pass request Path->>Context: Pass request
@@ -443,14 +443,14 @@ Initial State: {}
store_id: 1, store_id: 1,
clean_path: "/shop/products" clean_path: "/shop/products"
} }
After FrontendTypeMiddleware: After FrontendTypeMiddleware:
{ {
store: <Store: Orion>, store: <Store: Orion>,
store_id: 1, store_id: 1,
clean_path: "/shop/products", clean_path: "/shop/products",
frontend_type: FrontendType.STOREFRONT frontend_type: FrontendType.STOREFRONT
} }
After ThemeContextMiddleware: After ThemeContextMiddleware:
{ {
@@ -458,14 +458,14 @@ After FrontendTypeMiddleware:
store_id: 1, store_id: 1,
clean_path: "/shop/products", clean_path: "/shop/products",
frontend_type: FrontendType.STOREFRONT, frontend_type: FrontendType.STOREFRONT,
theme: { theme: {
primary_color: "#3B82F6", primary_color: "#3B82F6",
secondary_color: "#10B981", secondary_color: "#10B981",
logo_url: "/static/stores/orion/logo.png", logo_url: "/static/stores/orion/logo.png",
custom_css: "..." custom_css: "..."
} }
} }
``` ```
## Performance Metrics ## Performance Metrics

View File

@@ -1,8 +1,8 @@
# Wizamart Multi-Tenant URL Routing Guide # Orion Multi-Tenant URL Routing Guide
## Quick Answer ## Quick Answer
**How do customers access a store's storefront in Wizamart?** **How do customers access a store's storefront in Orion?**
There are three ways depending on the deployment mode: There are three ways depending on the deployment mode:
@@ -13,8 +13,8 @@ There are three ways depending on the deployment mode:
https://STORE_SUBDOMAIN.platform.com/storefront/products https://STORE_SUBDOMAIN.platform.com/storefront/products
Example: Example:
https://acme.wizamart.com/storefront/products https://acme.orion.lu/storefront/products
https://techpro.wizamart.com/storefront/categories/electronics https://techpro.orion.lu/storefront/categories/electronics
``` ```
### 2. **CUSTOM DOMAIN MODE** (Production - Premium) ### 2. **CUSTOM DOMAIN MODE** (Production - Premium)
@@ -39,7 +39,7 @@ http://localhost:8000/platforms/loyalty/stores/techpro/storefront/checkout
## Multi-Platform URL Routing ## Multi-Platform URL Routing
Wizamart supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem. Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem.
### Platform URL Structure ### Platform URL Structure
@@ -61,8 +61,8 @@ Wizamart supports multiple platforms (OMS, Loyalty, Site Builder), each with its
| URL | What it serves | | URL | What it serves |
|-----|----------------| |-----|----------------|
| `wizamart.lu/` | Main marketing site homepage | | `orion.lu/` | Main marketing site homepage |
| `wizamart.lu/about` | Main marketing site about page | | `orion.lu/about` | Main marketing site about page |
| `oms.lu/` | OMS platform homepage | | `oms.lu/` | OMS platform homepage |
| `oms.lu/pricing` | OMS platform pricing page | | `oms.lu/pricing` | OMS platform pricing page |
| `oms.lu/admin/` | Admin panel for OMS platform | | `oms.lu/admin/` | Admin panel for OMS platform |
@@ -138,7 +138,7 @@ Request arrives
| Platform | Code | Dev URL | Prod Domain | | Platform | Code | Dev URL | Prod Domain |
|----------|------|---------|-------------| |----------|------|---------|-------------|
| Main Marketing | `main` | `localhost:8000/` | `wizamart.lu` | | Main Marketing | `main` | `localhost:8000/` | `orion.lu` |
| OMS | `oms` | `localhost:8000/platforms/oms/` | `oms.lu` | | OMS | `oms` | `localhost:8000/platforms/oms/` | `oms.lu` |
| Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `loyalty.lu` | | Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `loyalty.lu` |
| Site Builder | `site-builder` | `localhost:8000/platforms/site-builder/` | `sitebuilder.lu` | | Site Builder | `site-builder` | `localhost:8000/platforms/site-builder/` | `sitebuilder.lu` |
@@ -155,12 +155,12 @@ Request arrives
**Example:** **Example:**
- Store subdomain: `acme` - Store subdomain: `acme`
- Platform domain: `wizamart.com` - Platform domain: `orion.lu`
- Customer Storefront URL: `https://acme.wizamart.com/storefront/products` - Customer Storefront URL: `https://acme.orion.lu/storefront/products`
- Product Detail: `https://acme.wizamart.com/storefront/products/123` - Product Detail: `https://acme.orion.lu/storefront/products/123`
**How It Works:** **How It Works:**
1. Customer visits `https://acme.wizamart.com/storefront/products` 1. Customer visits `https://acme.orion.lu/storefront/products`
2. `store_context_middleware` detects subdomain `"acme"` 2. `store_context_middleware` detects subdomain `"acme"`
3. Queries: `SELECT * FROM stores WHERE subdomain = 'acme'` 3. Queries: `SELECT * FROM stores WHERE subdomain = 'acme'`
4. Finds Store with ID=1 (ACME Store) 4. Finds Store with ID=1 (ACME Store)
@@ -171,7 +171,7 @@ Request arrives
9. Renders template with ACME's colors, logo, and products 9. Renders template with ACME's colors, logo, and products
**Advantages:** **Advantages:**
- Single SSL certificate for all stores (*.wizamart.com) - Single SSL certificate for all stores (*.orion.lu)
- Easy to manage DNS (just add subdomains) - Easy to manage DNS (just add subdomains)
- Customers don't need to bring their own domain - Customers don't need to bring their own domain
@@ -199,7 +199,7 @@ id | store_id | domain | is_active | is_verified
**How It Works:** **How It Works:**
1. Customer visits `https://store.acme-corp.com/storefront/products` 1. Customer visits `https://store.acme-corp.com/storefront/products`
2. `store_context_middleware` detects custom domain (not *.wizamart.com, not localhost) 2. `store_context_middleware` detects custom domain (not *.orion.lu, not localhost)
3. Normalizes domain to `"store.acme-corp.com"` 3. Normalizes domain to `"store.acme-corp.com"`
4. Queries: `SELECT * FROM store_domains WHERE domain = 'store.acme-corp.com'` 4. Queries: `SELECT * FROM store_domains WHERE domain = 'store.acme-corp.com'`
5. Finds `StoreDomain` with `store_id = 1` 5. Finds `StoreDomain` with `store_id = 1`
@@ -249,17 +249,17 @@ id | store_id | domain | is_active | is_verified
### Subdomain/Custom Domain (PRODUCTION) ### Subdomain/Custom Domain (PRODUCTION)
``` ```
https://acme.wizamart.com/storefront/ → Homepage https://acme.orion.lu/storefront/ → Homepage
https://acme.wizamart.com/storefront/products → Product Catalog https://acme.orion.lu/storefront/products → Product Catalog
https://acme.wizamart.com/storefront/products/123 → Product Detail https://acme.orion.lu/storefront/products/123 → Product Detail
https://acme.wizamart.com/storefront/categories/electronics → Category Page https://acme.orion.lu/storefront/categories/electronics → Category Page
https://acme.wizamart.com/storefront/cart → Shopping Cart https://acme.orion.lu/storefront/cart → Shopping Cart
https://acme.wizamart.com/storefront/checkout → Checkout https://acme.orion.lu/storefront/checkout → Checkout
https://acme.wizamart.com/storefront/search?q=laptop → Search Results https://acme.orion.lu/storefront/search?q=laptop → Search Results
https://acme.wizamart.com/storefront/account/login → Customer Login https://acme.orion.lu/storefront/account/login → Customer Login
https://acme.wizamart.com/storefront/account/dashboard → Account Dashboard (Auth Required) https://acme.orion.lu/storefront/account/dashboard → Account Dashboard (Auth Required)
https://acme.wizamart.com/storefront/account/orders → Order History (Auth Required) https://acme.orion.lu/storefront/account/orders → Order History (Auth Required)
https://acme.wizamart.com/storefront/account/profile → Profile (Auth Required) https://acme.orion.lu/storefront/account/profile → Profile (Auth Required)
``` ```
### Path-Based (DEVELOPMENT) ### Path-Based (DEVELOPMENT)
@@ -304,7 +304,7 @@ POST /api/v1/storefront/stores/1/products/{id}/reviews → Add product review
### Example: No Cross-Store Leakage ### Example: No Cross-Store Leakage
```python ```python
# Customer on acme.wizamart.com tries to access TechPro's products # Customer on acme.orion.lu tries to access TechPro's products
# They make API call to /api/v1/storefront/stores/2/products # They make API call to /api/v1/storefront/stores/2/products
# Backend checks: # Backend checks:
@@ -317,14 +317,14 @@ if store.id != requested_store_id: # if 1 != 2
## Request Lifecycle: Complete Flow ## Request Lifecycle: Complete Flow
### Scenario: Customer visits `https://acme.wizamart.com/storefront/products` ### Scenario: Customer visits `https://acme.orion.lu/storefront/products`
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ 1. REQUEST ARRIVES │ │ 1. REQUEST ARRIVES │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
method: GET method: GET
host: acme.wizamart.com host: acme.orion.lu
path: /storefront/products path: /storefront/products
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
@@ -332,7 +332,7 @@ if store.id != requested_store_id: # if 1 != 2
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
A) store_context_middleware A) store_context_middleware
├─ Detects host: "acme.wizamart.com" ├─ Detects host: "acme.orion.lu"
├─ Extracts subdomain: "acme" ├─ Extracts subdomain: "acme"
├─ Queries: SELECT * FROM stores WHERE subdomain = 'acme' ├─ Queries: SELECT * FROM stores WHERE subdomain = 'acme'
└─ Sets: request.state.store = Store(ACME Store) └─ Sets: request.state.store = Store(ACME Store)
@@ -391,7 +391,7 @@ if store.id != requested_store_id: # if 1 != 2
Each store's storefront is fully branded with their custom theme: Each store's storefront is fully branded with their custom theme:
```python ```python
# Theme loaded for https://acme.wizamart.com # Theme loaded for https://acme.orion.lu
request.state.theme = { request.state.theme = {
"theme_name": "modern", "theme_name": "modern",
"colors": { "colors": {
@@ -438,7 +438,7 @@ In Jinja2 template:
- Each store looks completely separate and branded - Each store looks completely separate and branded
### 2. Store Perspective ### 2. Store Perspective
- Stores can use a subdomain (free/standard): `acme.wizamart.com` - Stores can use a subdomain (free/standard): `acme.orion.lu`
- Or their own custom domain (premium): `store.acme-corp.com` - Or their own custom domain (premium): `store.acme-corp.com`
- Both routes go to the exact same backend code - Both routes go to the exact same backend code
@@ -471,7 +471,7 @@ app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefr
**How This Works:** **How This Works:**
1. **For Subdomain/Custom Domain Mode:** 1. **For Subdomain/Custom Domain Mode:**
- URL: `https://acme.wizamart.com/storefront/products` - URL: `https://acme.orion.lu/storefront/products`
- Matches: First router with `/storefront` prefix - Matches: First router with `/storefront` prefix
- Route: `@router.get("/products")` → Full path: `/storefront/products` - Route: `@router.get("/products")` → Full path: `/storefront/products`
@@ -528,4 +528,4 @@ Set-Cookie: customer_token=eyJ...; Path=/storefront; HttpOnly; SameSite=Lax
--- ---
Generated: January 30, 2026 Generated: January 30, 2026
Wizamart Version: Current Development Orion Version: Current Development

View File

@@ -5,7 +5,7 @@
**Last Updated:** 2026-01-28 **Last Updated:** 2026-01-28
**Target:** v1.0.0 **Target:** v1.0.0
This is the unified migration plan for transforming Wizamart into a fully modular architecture. This is the unified migration plan for transforming Orion into a fully modular architecture.
--- ---

View File

@@ -42,15 +42,15 @@ Transform the single-platform OMS into a multi-platform system supporting indepe
```bash ```bash
# 1. Backup database first # 1. Backup database first
pg_dump wizamart > wizamart_backup_$(date +%Y%m%d).sql pg_dump orion > orion_backup_$(date +%Y%m%d).sql
# 2. Run migration # 2. Run migration
alembic upgrade head alembic upgrade head
# 3. Verify migration # 3. Verify migration
psql -d wizamart -c "SELECT * FROM platforms;" psql -d orion -c "SELECT * FROM platforms;"
psql -d wizamart -c "SELECT COUNT(*) FROM store_platforms;" psql -d orion -c "SELECT COUNT(*) FROM store_platforms;"
psql -d wizamart -c "SELECT platform_id, is_platform_page, COUNT(*) FROM content_pages GROUP BY 1, 2;" psql -d orion -c "SELECT platform_id, is_platform_page, COUNT(*) FROM content_pages GROUP BY 1, 2;"
``` ```
### 2.2 Register PlatformContextMiddleware in main.py ### 2.2 Register PlatformContextMiddleware in main.py
@@ -78,7 +78,7 @@ Changes completed:
File: `app/routes/platform/homepage.py` File: `app/routes/platform/homepage.py`
Current state (BROKEN): Current state (BROKEN):
- Uses hardcoded `homepage-wizamart.html` template - Uses hardcoded `homepage-orion.html` template
- Admin CMS changes are saved but ignored by route - Admin CMS changes are saved but ignored by route
Fix required: Fix required:
@@ -101,7 +101,7 @@ if page:
) )
else: else:
# Fallback to hardcoded (temporary) # Fallback to hardcoded (temporary)
return templates.TemplateResponse("platform/homepage-wizamart.html", {...}) return templates.TemplateResponse("platform/homepage-orion.html", {...})
``` ```
### 2.5 Update Content Page Routes ### 2.5 Update Content Page Routes
@@ -255,7 +255,7 @@ Inserts Loyalty platform with:
| Platform | Code | Dev URL | Prod URL | | Platform | Code | Dev URL | Prod URL |
|----------|------|---------|----------| |----------|------|---------|----------|
| Main Marketing | `main` | `localhost:9999/` | `wizamart.lu/` | | Main Marketing | `main` | `localhost:9999/` | `orion.lu/` |
| OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` | | OMS | `oms` | `localhost:9999/platforms/oms/` | `oms.lu/` |
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` | | Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
@@ -324,7 +324,7 @@ Included in `docs/architecture/multi-platform-cms.md`:
### Three-Tier Content Resolution ### Three-Tier Content Resolution
``` ```
Customer visits: oms.lu/stores/wizamart/about Customer visits: oms.lu/stores/orion/about
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────┐
@@ -366,7 +366,7 @@ All phases are complete. Use these commands to verify:
```bash ```bash
# 1. Check platforms in database # 1. Check platforms in database
psql -d wizamart -c "SELECT code, name, domain FROM platforms;" psql -d orion -c "SELECT code, name, domain FROM platforms;"
# Expected: main, oms, loyalty # Expected: main, oms, loyalty
# 2. Test main marketing site # 2. Test main marketing site

View File

@@ -23,7 +23,7 @@ The platform is evolving from a single OMS product to a **multi-platform busines
| Issue | Description | | Issue | Description |
|-------|-------------| |-------|-------------|
| **Conflated page types** | Platform pages and store defaults share `store_id = NULL`, making them indistinguishable | | **Conflated page types** | Platform pages and store defaults share `store_id = NULL`, making them indistinguishable |
| **Hardcoded homepage** | Platform homepage uses `homepage-wizamart.html` directly, ignoring CMS | | **Hardcoded homepage** | Platform homepage uses `homepage-orion.html` directly, ignoring CMS |
| **Non-functional admin UI** | `/admin/platform-homepage` saves to CMS but route doesn't use it | | **Non-functional admin UI** | `/admin/platform-homepage` saves to CMS but route doesn't use it |
| **Single platform assumption** | Architecture assumes one platform, can't scale to multiple offerings | | **Single platform assumption** | Architecture assumes one platform, can't scale to multiple offerings |
| **No platform isolation** | No way to have separate About/FAQ/Pricing pages per platform | | **No platform isolation** | No way to have separate About/FAQ/Pricing pages per platform |
@@ -48,7 +48,7 @@ ContentPage (store_id = NULL)
│ PLATFORM LEVEL │ │ PLATFORM LEVEL │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Platform A │ │ Platform B │ │ Platform C │ │ │ │ Platform A │ │ Platform B │ │ Platform C │ │
│ │ (Wizamart OMS) │ │ (Loyalty+) │ │ (Site Builder) │ │ │ │ (Orion OMS) │ │ (Loyalty+) │ │ (Site Builder) │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ • Homepage │ │ • Homepage │ │ • Homepage │ │ │ │ • Homepage │ │ • Homepage │ │ • Homepage │ │
│ │ • About │ │ • About │ │ • About │ │ │ │ • About │ │ • About │ │ • About │ │
@@ -76,7 +76,7 @@ ContentPage (store_id = NULL)
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ STORE LEVEL (isolated) │ │ STORE LEVEL (isolated) │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Store 1 (WizaMart) │ │ Store 2 (TechStore) │ │ │ │ Store 1 (Orion) │ │ Store 2 (TechStore) │ │
│ │ Platform A, Tier: Pro │ │ Platform A, Tier: Basic │ │ │ │ Platform A, Tier: Pro │ │ Platform A, Tier: Basic │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ Override Pages: │ │ Override Pages: │ │ │ │ Override Pages: │ │ Override Pages: │ │
@@ -114,14 +114,14 @@ When a customer visits `store1.example.com/about`:
class Platform(Base): class Platform(Base):
""" """
Represents a business offering/product line. Represents a business offering/product line.
Examples: Wizamart OMS, Loyalty+, Site Builder Examples: Orion OMS, Loyalty+, Site Builder
""" """
__tablename__ = "platforms" __tablename__ = "platforms"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
code = Column(String(50), unique=True, nullable=False) # "oms", "loyalty", "sites" code = Column(String(50), unique=True, nullable=False) # "oms", "loyalty", "sites"
name = Column(String(100), nullable=False) # "Wizamart OMS" name = Column(String(100), nullable=False) # "Orion OMS"
domain = Column(String(255), nullable=True) # "wizamart.lu" domain = Column(String(255), nullable=True) # "orion.lu"
# Branding # Branding
logo = Column(String(500), nullable=True) logo = Column(String(500), nullable=True)
@@ -219,8 +219,8 @@ class Store(Base):
|-----------|:-----------:|:---------:|:----------------:|---------| |-----------|:-----------:|:---------:|:----------------:|---------|
| Platform Marketing Page | ✓ | NULL | TRUE | Platform A's homepage, pricing | | Platform Marketing Page | ✓ | NULL | TRUE | Platform A's homepage, pricing |
| Store Default Page | ✓ | NULL | FALSE | Generic "About Our Store" template | | Store Default Page | ✓ | NULL | FALSE | Generic "About Our Store" template |
| Store Override Page | ✓ | ✓ | FALSE | WizaMart's custom About page | | Store Override Page | ✓ | ✓ | FALSE | Orion's custom About page |
| Store Custom Page | ✓ | ✓ | FALSE | WizaMart's "Store Locations" page | | Store Custom Page | ✓ | ✓ | FALSE | Orion's "Store Locations" page |
--- ---
@@ -237,7 +237,7 @@ class Store(Base):
3. Admin fills in: 3. Admin fills in:
- Code: "loyalty" - Code: "loyalty"
- Name: "Loyalty+" - Name: "Loyalty+"
- Domain: "loyalty.wizamart.lu" - Domain: "loyalty.orion.lu"
- Logo, theme colors - Logo, theme colors
4. Admin saves platform 4. Admin saves platform
5. System creates platform record 5. System creates platform record
@@ -250,7 +250,7 @@ class Store(Base):
- Contact (is_platform_page=True) - Contact (is_platform_page=True)
8. Each page can use different templates (modern, minimal, etc.) 8. Each page can use different templates (modern, minimal, etc.)
9. Admin publishes pages 9. Admin publishes pages
10. Platform marketing site is now live at loyalty.wizamart.lu 10. Platform marketing site is now live at loyalty.orion.lu
``` ```
### Journey 2: Platform Admin Creates Store Defaults ### Journey 2: Platform Admin Creates Store Defaults
@@ -308,7 +308,7 @@ class Store(Base):
### Journey 4: Store Overrides a Default Page ### Journey 4: Store Overrides a Default Page
**Actor:** Store (WizaMart) **Actor:** Store (Orion)
**Goal:** Customize the About page with store-specific content **Goal:** Customize the About page with store-specific content
``` ```
@@ -318,21 +318,21 @@ class Store(Base):
4. Clicks "Override" button 4. Clicks "Override" button
5. System creates a copy with store_id set 5. System creates a copy with store_id set
6. Store edits content: 6. Store edits content:
- Title: "About WizaMart" - Title: "About Orion"
- Content: "WizaMart was founded in 2020 in Luxembourg..." - Content: "Orion was founded in 2020 in Luxembourg..."
- Adds store images - Adds store images
7. Store saves and publishes 7. Store saves and publishes
8. Page list now shows: 8. Page list now shows:
┌─────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────┐
│ About Us │ Custom Override │ Published │ Edit │ │ About Us │ Custom Override │ Published │ Edit │
└─────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────┘
9. Customer visits wizamart.example.com/about 9. Customer visits orion.example.com/about
10. Sees WizaMart's custom About page 10. Sees Orion's custom About page
``` ```
### Journey 5: Store Creates a Custom Page ### Journey 5: Store Creates a Custom Page
**Actor:** Store (WizaMart) **Actor:** Store (Orion)
**Goal:** Add a new page that doesn't exist in defaults **Goal:** Add a new page that doesn't exist in defaults
``` ```
@@ -346,12 +346,12 @@ class Store(Base):
- Show in footer: Yes - Show in footer: Yes
5. Store saves and publishes 5. Store saves and publishes
6. Page appears in storefront footer navigation 6. Page appears in storefront footer navigation
7. Accessible at wizamart.example.com/store-locations 7. Accessible at orion.example.com/store-locations
``` ```
### Journey 6: Store Reverts Override to Default ### Journey 6: Store Reverts Override to Default
**Actor:** Store (WizaMart) **Actor:** Store (Orion)
**Goal:** Remove customization and use platform default again **Goal:** Remove customization and use platform default again
``` ```
@@ -372,19 +372,19 @@ class Store(Base):
**Goal:** Read store policies before purchasing **Goal:** Read store policies before purchasing
``` ```
1. Customer visits wizamart.example.com 1. Customer visits orion.example.com
2. Browses products, adds to cart 2. Browses products, adds to cart
3. Wants to check return policy 3. Wants to check return policy
4. Clicks "Returns" in footer 4. Clicks "Returns" in footer
5. System resolves page: 5. System resolves page:
- Check: WizaMart override? NO - Check: Orion override? NO
- Check: Platform A default? YES - Check: Platform A default? YES
- Serve: Platform A default "Return Policy" page - Serve: Platform A default "Return Policy" page
6. Customer reads return policy 6. Customer reads return policy
7. Customer clicks "About Us" in footer 7. Customer clicks "About Us" in footer
8. System resolves page: 8. System resolves page:
- Check: WizaMart override? YES - Check: Orion override? YES
- Serve: WizaMart's custom "About WizaMart" page - Serve: Orion's custom "About Orion" page
9. Customer sees store-specific About page 9. Customer sees store-specific About page
``` ```
@@ -539,7 +539,7 @@ GET /api/v1/platform/{code}/pages/navigation # Get platform nav
```sql ```sql
-- 1. Create default platform -- 1. Create default platform
INSERT INTO platforms (code, name, domain, is_active) INSERT INTO platforms (code, name, domain, is_active)
VALUES ('oms', 'Wizamart OMS', 'localhost:8000', true); VALUES ('oms', 'Orion OMS', 'localhost:8000', true);
-- 2. Update all existing content_pages -- 2. Update all existing content_pages
UPDATE content_pages UPDATE content_pages
@@ -567,7 +567,7 @@ SET platform_id = (SELECT id FROM platforms WHERE code = 'oms');
## Open Questions ## Open Questions
1. **Domain routing**: How to route requests to correct platform? 1. **Domain routing**: How to route requests to correct platform?
- Option A: Separate domains (oms.wizamart.lu, loyalty.wizamart.lu) - Option A: Separate domains (oms.orion.lu, loyalty.orion.lu)
- Option B: Path-based (/oms/*, /loyalty/*) - Option B: Path-based (/oms/*, /loyalty/*)
- Option C: Subdomain detection - Option C: Subdomain detection

View File

@@ -95,7 +95,7 @@ from models.database.base import TimestampMixin
class StoreDomain(Base, TimestampMixin): class StoreDomain(Base, TimestampMixin):
""" """
Custom domain mapping for stores. Custom domain mapping for stores.
Allows stores to use their own domains (e.g., myshop.com) Allows stores to use their own domains (e.g., myshop.com)
instead of subdomains (store1.platform.com). instead of subdomains (store1.platform.com).
""" """
@@ -103,11 +103,11 @@ class StoreDomain(Base, TimestampMixin):
# Primary Key # Primary Key
id = Column(Integer, primary_key=True, index=True) id = Column(Integer, primary_key=True, index=True)
# Foreign Keys # Foreign Keys
store_id = Column( store_id = Column(
Integer, Integer,
ForeignKey("stores.id", ondelete="CASCADE"), ForeignKey("stores.id", ondelete="CASCADE"),
nullable=False, nullable=False,
index=True index=True
) )
@@ -116,12 +116,12 @@ class StoreDomain(Base, TimestampMixin):
domain = Column(String(255), nullable=False, unique=True, index=True) domain = Column(String(255), nullable=False, unique=True, index=True)
is_primary = Column(Boolean, default=False, nullable=False) is_primary = Column(Boolean, default=False, nullable=False)
is_active = Column(Boolean, default=True, nullable=False) is_active = Column(Boolean, default=True, nullable=False)
# Verification # Verification
is_verified = Column(Boolean, default=False, nullable=False) is_verified = Column(Boolean, default=False, nullable=False)
verification_token = Column(String(100), unique=True, nullable=True) verification_token = Column(String(100), unique=True, nullable=True)
verified_at = Column(DateTime(timezone=True), nullable=True) verified_at = Column(DateTime(timezone=True), nullable=True)
# SSL Status # SSL Status
ssl_status = Column(String(50), default="pending") ssl_status = Column(String(50), default="pending")
ssl_verified_at = Column(DateTime(timezone=True), nullable=True) ssl_verified_at = Column(DateTime(timezone=True), nullable=True)
@@ -171,9 +171,9 @@ class StoreDomain(Base, TimestampMixin):
class Store(Base, TimestampMixin): class Store(Base, TimestampMixin):
__tablename__ = "stores" __tablename__ = "stores"
# ... existing fields ... # ... existing fields ...
# Add relationship # Add relationship
domains = relationship( domains = relationship(
"StoreDomain", "StoreDomain",
@@ -181,7 +181,7 @@ class Store(Base, TimestampMixin):
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="StoreDomain.is_primary.desc()" order_by="StoreDomain.is_primary.desc()"
) )
# Helper method # Helper method
@property @property
def primary_domain(self): def primary_domain(self):
@@ -270,10 +270,10 @@ class StoreDomainCreate(BaseModel):
"""Validate and normalize domain.""" """Validate and normalize domain."""
# Remove protocol if present # Remove protocol if present
domain = v.replace("https://", "").replace("http://", "") domain = v.replace("https://", "").replace("http://", "")
# Remove trailing slash # Remove trailing slash
domain = domain.rstrip("/") domain = domain.rstrip("/")
# Convert to lowercase # Convert to lowercase
domain = domain.lower().strip() domain = domain.lower().strip()
@@ -331,7 +331,7 @@ class StoreDomainResponse(BaseModel):
class StoreDomainListResponse(BaseModel): class StoreDomainListResponse(BaseModel):
"""Schema for paginated store domain list.""" """Schema for paginated store domain list."""
domains: List[StoreDomainResponse] domains: List[StoreDomainResponse]
total: int total: int
@@ -535,7 +535,7 @@ class DNSVerificationException(ExternalServiceException):
| 409 | `ConflictException` | Resource conflicts | | 409 | `ConflictException` | Resource conflicts |
| 422 | `ValidationException` | Input validation errors | | 422 | `ValidationException` | Input validation errors |
| 429 | `RateLimitException` | Rate limiting | | 429 | `RateLimitException` | Rate limiting |
| 500 | `WizamartException` | Generic errors | | 500 | `OrionException` | Generic errors |
| 502 | `ExternalServiceException` | Third-party failures | | 502 | `ExternalServiceException` | Third-party failures |
### Step 2: Update Exception Exports ### Step 2: Update Exception Exports
@@ -561,7 +561,7 @@ from .store_domain import (
__all__ = [ __all__ = [
# ... existing exports ... # ... existing exports ...
# Store Domain # Store Domain
"StoreDomainNotFoundException", "StoreDomainNotFoundException",
"StoreDomainAlreadyExistsException", "StoreDomainAlreadyExistsException",
@@ -1133,7 +1133,7 @@ def delete_store_domain(
Delete a custom domain (Admin only). Delete a custom domain (Admin only).
**Warning:** This is permanent and cannot be undone. **Warning:** This is permanent and cannot be undone.
**Raises:** **Raises:**
- 404: Domain not found - 404: Domain not found
""" """
@@ -1230,7 +1230,7 @@ async def admin_store_domains_page(
Manage custom domains for {{ store_code }} Manage custom domains for {{ store_code }}
</p> </p>
</div> </div>
<button <button
onclick="openAddDomainModal()" onclick="openAddDomainModal()"
class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"> class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700">
Add Domain Add Domain
@@ -1248,32 +1248,32 @@ async def admin_store_domains_page(
<div class="flex items-center justify-center min-h-screen px-4"> <div class="flex items-center justify-center min-h-screen px-4">
<div class="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg max-w-md w-full p-6">
<h2 class="text-2xl font-semibold mb-4">Add Custom Domain</h2> <h2 class="text-2xl font-semibold mb-4">Add Custom Domain</h2>
<form id="addDomainForm"> <form id="addDomainForm">
<div class="mb-4"> <div class="mb-4">
<label class="block text-sm font-medium mb-2">Domain</label> <label class="block text-sm font-medium mb-2">Domain</label>
<input <input
type="text" type="text"
name="domain" name="domain"
placeholder="myshop.com" placeholder="myshop.com"
class="w-full px-3 py-2 border rounded-lg" class="w-full px-3 py-2 border rounded-lg"
required> required>
</div> </div>
<div class="mb-4"> <div class="mb-4">
<label class="flex items-center"> <label class="flex items-center">
<input type="checkbox" name="is_primary" class="mr-2"> <input type="checkbox" name="is_primary" class="mr-2">
<span class="text-sm">Set as primary domain</span> <span class="text-sm">Set as primary domain</span>
</label> </label>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
type="submit" type="submit"
class="flex-1 bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700"> class="flex-1 bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700">
Add Domain Add Domain
</button> </button>
<button <button
type="button" type="button"
onclick="closeAddDomainModal()" onclick="closeAddDomainModal()"
class="flex-1 bg-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-400"> class="flex-1 bg-gray-300 text-gray-700 py-2 rounded-lg hover:bg-gray-400">
@@ -1305,7 +1305,7 @@ async function loadStoreId() {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${localStorage.getItem('token')}`
} }
}); });
if (response.ok) { if (response.ok) {
const store = await response.json(); const store = await response.json();
storeId = store.id; storeId = store.id;
@@ -1317,14 +1317,14 @@ async function loadStoreId() {
async function loadDomains() { async function loadDomains() {
if (!storeId) return; if (!storeId) return;
try { try {
const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, { const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, {
headers: { headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${localStorage.getItem('token')}`
} }
}); });
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
renderDomains(data.domains); renderDomains(data.domains);
@@ -1336,12 +1336,12 @@ async function loadDomains() {
function renderDomains(domains) { function renderDomains(domains) {
const container = document.getElementById('domainsList'); const container = document.getElementById('domainsList');
if (domains.length === 0) { if (domains.length === 0) {
container.innerHTML = '<p class="text-gray-500">No domains added yet.</p>'; container.innerHTML = '<p class="text-gray-500">No domains added yet.</p>';
return; return;
} }
container.innerHTML = domains.map(domain => ` container.innerHTML = domains.map(domain => `
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6"> <div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
@@ -1358,13 +1358,13 @@ function renderDomains(domains) {
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
${!domain.is_verified ? ` ${!domain.is_verified ? `
<button <button
onclick="verifyDomain(${domain.id})" onclick="verifyDomain(${domain.id})"
class="px-3 py-1 bg-green-600 text-white text-sm rounded hover:bg-green-700"> class="px-3 py-1 bg-green-600 text-white text-sm rounded hover:bg-green-700">
Verify Verify
</button> </button>
` : ''} ` : ''}
<button <button
onclick="deleteDomain(${domain.id})" onclick="deleteDomain(${domain.id})"
class="px-3 py-1 bg-red-600 text-white text-sm rounded hover:bg-red-700"> class="px-3 py-1 bg-red-600 text-white text-sm rounded hover:bg-red-700">
Delete Delete
@@ -1386,13 +1386,13 @@ function closeAddDomainModal() {
document.getElementById('addDomainForm').addEventListener('submit', async (e) => { document.getElementById('addDomainForm').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.target); const formData = new FormData(e.target);
const data = { const data = {
domain: formData.get('domain'), domain: formData.get('domain'),
is_primary: formData.get('is_primary') === 'on' is_primary: formData.get('is_primary') === 'on'
}; };
try { try {
const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, { const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, {
method: 'POST', method: 'POST',
@@ -1402,7 +1402,7 @@ document.getElementById('addDomainForm').addEventListener('submit', async (e) =>
}, },
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
if (response.ok) { if (response.ok) {
closeAddDomainModal(); closeAddDomainModal();
await loadDomains(); await loadDomains();
@@ -1425,7 +1425,7 @@ async function verifyDomain(domainId) {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${localStorage.getItem('token')}`
} }
}); });
if (response.ok) { if (response.ok) {
await loadDomains(); await loadDomains();
alert('Domain verified successfully!'); alert('Domain verified successfully!');
@@ -1441,7 +1441,7 @@ async function verifyDomain(domainId) {
async function deleteDomain(domainId) { async function deleteDomain(domainId) {
if (!confirm('Are you sure you want to delete this domain?')) return; if (!confirm('Are you sure you want to delete this domain?')) return;
try { try {
const response = await fetch(`/api/v1/admin/stores/domains/${domainId}`, { const response = await fetch(`/api/v1/admin/stores/domains/${domainId}`, {
method: 'DELETE', method: 'DELETE',
@@ -1449,7 +1449,7 @@ async function deleteDomain(domainId) {
'Authorization': `Bearer ${localStorage.getItem('token')}` 'Authorization': `Bearer ${localStorage.getItem('token')}`
} }
}); });
if (response.ok) { if (response.ok) {
await loadDomains(); await loadDomains();
alert('Domain deleted successfully!'); alert('Domain deleted successfully!');
@@ -1497,9 +1497,9 @@ def test_add_domain_success(db_session, test_store, service):
domain="test.com", domain="test.com",
is_primary=True is_primary=True
) )
domain = service.add_domain(db_session, test_store.id, domain_data) domain = service.add_domain(db_session, test_store.id, domain_data)
assert domain.domain == "test.com" assert domain.domain == "test.com"
assert domain.is_primary is True assert domain.is_primary is True
assert domain.is_verified is False assert domain.is_verified is False
@@ -1509,7 +1509,7 @@ def test_add_domain_success(db_session, test_store, service):
def test_add_domain_already_exists(db_session, test_store, existing_domain, service): def test_add_domain_already_exists(db_session, test_store, existing_domain, service):
"""Test adding duplicate domain raises exception.""" """Test adding duplicate domain raises exception."""
domain_data = StoreDomainCreate(domain=existing_domain.domain) domain_data = StoreDomainCreate(domain=existing_domain.domain)
with pytest.raises(StoreDomainAlreadyExistsException): with pytest.raises(StoreDomainAlreadyExistsException):
service.add_domain(db_session, test_store.id, domain_data) service.add_domain(db_session, test_store.id, domain_data)
@@ -1520,7 +1520,7 @@ def test_add_domain_max_limit_reached(db_session, test_store, service):
for i in range(service.max_domains_per_store): for i in range(service.max_domains_per_store):
domain_data = StoreDomainCreate(domain=f"test{i}.com") domain_data = StoreDomainCreate(domain=f"test{i}.com")
service.add_domain(db_session, test_store.id, domain_data) service.add_domain(db_session, test_store.id, domain_data)
# Try adding one more # Try adding one more
domain_data = StoreDomainCreate(domain="overflow.com") domain_data = StoreDomainCreate(domain="overflow.com")
with pytest.raises(MaxDomainsReachedException): with pytest.raises(MaxDomainsReachedException):
@@ -1549,7 +1549,7 @@ def test_add_domain_endpoint(client, admin_headers, test_store):
json={"domain": "newshop.com", "is_primary": False}, json={"domain": "newshop.com", "is_primary": False},
headers=admin_headers headers=admin_headers
) )
assert response.status_code == 201 assert response.status_code == 201
data = response.json() data = response.json()
assert data["domain"] == "newshop.com" assert data["domain"] == "newshop.com"
@@ -1563,7 +1563,7 @@ def test_add_domain_invalid_format(client, admin_headers, test_store):
json={"domain": "admin.example.com"}, # Reserved subdomain json={"domain": "admin.example.com"}, # Reserved subdomain
headers=admin_headers headers=admin_headers
) )
assert response.status_code == 422 assert response.status_code == 422
assert "reserved" in response.json()["message"].lower() assert "reserved" in response.json()["message"].lower()
@@ -1574,7 +1574,7 @@ def test_list_domains_endpoint(client, admin_headers, test_store, test_domain):
f"/api/v1/admin/stores/{test_store.id}/domains", f"/api/v1/admin/stores/{test_store.id}/domains",
headers=admin_headers headers=admin_headers
) )
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["total"] >= 1 assert data["total"] >= 1
@@ -1587,7 +1587,7 @@ def test_delete_domain_endpoint(client, admin_headers, test_domain):
f"/api/v1/admin/stores/domains/{test_domain.id}", f"/api/v1/admin/stores/domains/{test_domain.id}",
headers=admin_headers headers=admin_headers
) )
assert response.status_code == 200 assert response.status_code == 200
assert "deleted successfully" in response.json()["message"] assert "deleted successfully" in response.json()["message"]
@@ -1598,7 +1598,7 @@ def test_verify_domain_not_found(client, admin_headers):
"/api/v1/admin/stores/domains/99999/verify", "/api/v1/admin/stores/domains/99999/verify",
headers=admin_headers headers=admin_headers
) )
assert response.status_code == 404 assert response.status_code == 404
assert response.json()["error_code"] == "STORE_DOMAIN_NOT_FOUND" assert response.json()["error_code"] == "STORE_DOMAIN_NOT_FOUND"
``` ```
@@ -1794,6 +1794,6 @@ For questions or issues:
--- ---
**Last Updated:** 2025-01-15 **Last Updated:** 2025-01-15
**Version:** 1.0 **Version:** 1.0
**Maintainer:** Development Team **Maintainer:** Development Team

View File

@@ -4,7 +4,7 @@ Guide for developing backend features, services, and API endpoints.
## Overview ## Overview
The Wizamart backend is built with FastAPI and follows a service-oriented architecture pattern. This guide covers backend development practices, patterns, and technical references. The Orion backend is built with FastAPI and follows a service-oriented architecture pattern. This guide covers backend development practices, patterns, and technical references.
## Backend Structure ## Backend Structure

View File

@@ -55,7 +55,7 @@ def get_product(
│ "sub": "user_id", │ │ "sub": "user_id", │
│ "username": "john.doe", │ │ "username": "john.doe", │
│ "store_id": 123, ← Store context in token │ │ "store_id": 123, ← Store context in token │
│ "store_code": "WIZAMART", ← Store code in token │ │ "store_code": "ORION", ← Store code in token │
│ "store_role": "Owner" ← Store role in token │ │ "store_role": "Owner" ← Store role in token │
│ } │ │ } │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
@@ -436,14 +436,14 @@ def test_store_in_token():
token_data = auth_manager.create_access_token( token_data = auth_manager.create_access_token(
user=user, user=user,
store_id=123, store_id=123,
store_code="WIZAMART", store_code="ORION",
store_role="Owner", store_role="Owner",
) )
# Verify token contains store data # Verify token contains store data
payload = jwt.decode(token_data["access_token"], secret_key) payload = jwt.decode(token_data["access_token"], secret_key)
assert payload["store_id"] == 123 assert payload["store_id"] == 123
assert payload["store_code"] == "WIZAMART" assert payload["store_code"] == "ORION"
assert payload["store_role"] == "Owner" assert payload["store_role"] == "Owner"
def test_api_endpoint_uses_token_store(): def test_api_endpoint_uses_token_store():
@@ -517,7 +517,7 @@ The architecture enforces a strict layered pattern for where exceptions should b
┌────────────────────────────────────────────────────────────────────────────┐ ┌────────────────────────────────────────────────────────────────────────────┐
│ GLOBAL EXCEPTION HANDLER - app/exceptions/handler.py │ │ GLOBAL EXCEPTION HANDLER - app/exceptions/handler.py │
│ │ │ │
│ ✅ Catches all WizamartException subclasses │ │ ✅ Catches all OrionException subclasses │
│ ✅ Converts to appropriate HTTP responses │ │ ✅ Converts to appropriate HTTP responses │
│ ✅ Provides consistent error formatting │ │ ✅ Provides consistent error formatting │
└────────────────────────────────────────────────────────────────────────────┘ └────────────────────────────────────────────────────────────────────────────┘

View File

@@ -607,7 +607,7 @@ HTTP 403 Forbidden
"message": "You don't have permission to perform this action", "message": "You don't have permission to perform this action",
"details": { "details": {
"required_permission": "products.delete", "required_permission": "products.delete",
"store_code": "wizamart" "store_code": "orion"
} }
} }
``` ```
@@ -622,7 +622,7 @@ HTTP 403 Forbidden
"message": "This operation requires store owner privileges", "message": "This operation requires store owner privileges",
"details": { "details": {
"operation": "team management", "operation": "team management",
"store_code": "wizamart" "store_code": "orion"
} }
} }
``` ```

Some files were not shown because too many files have changed in this diff Show More