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:
@@ -145,7 +145,7 @@ api_endpoint_rules:
|
||||
- Dependencies (app/api/deps.py) - authentication/authorization 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.
|
||||
|
||||
WRONG (endpoint raises exception):
|
||||
@@ -213,7 +213,6 @@ api_endpoint_rules:
|
||||
file_pattern:
|
||||
- "app/api/v1/vendor/**/*.py"
|
||||
- "app/modules/*/routes/api/store*.py"
|
||||
file_pattern:
|
||||
- "app/api/v1/storefront/**/*.py"
|
||||
- "app/modules/*/routes/api/storefront*.py"
|
||||
discouraged_patterns:
|
||||
|
||||
@@ -44,17 +44,17 @@ exception_rules:
|
||||
- "exc_info=True"
|
||||
|
||||
- id: "EXC-004"
|
||||
name: "Domain exceptions must inherit from WizamartException"
|
||||
name: "Domain exceptions must inherit from OrionException"
|
||||
severity: "error"
|
||||
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.).
|
||||
This ensures the global exception handler catches and converts them properly.
|
||||
pattern:
|
||||
file_pattern:
|
||||
- "app/exceptions/**/*.py"
|
||||
- "app/modules/*/exceptions.py"
|
||||
required_base_class: "WizamartException"
|
||||
required_base_class: "OrionException"
|
||||
example_good: |
|
||||
class VendorNotFoundException(ResourceNotFoundException):
|
||||
def __init__(self, vendor_code: str):
|
||||
@@ -65,7 +65,7 @@ exception_rules:
|
||||
severity: "error"
|
||||
description: |
|
||||
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:
|
||||
file_pattern: "app/main.py"
|
||||
required_patterns:
|
||||
|
||||
@@ -157,7 +157,7 @@ javascript_rules:
|
||||
- Page URLs (not API calls) like window.location.href = `/vendor/${vendorCode}/...`
|
||||
|
||||
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
|
||||
- Consistent with the API design pattern
|
||||
pattern:
|
||||
|
||||
@@ -154,16 +154,16 @@ module_rules:
|
||||
severity: "warning"
|
||||
description: |
|
||||
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:
|
||||
app/modules/{module}/exceptions.py
|
||||
|
||||
Example:
|
||||
# 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."""
|
||||
pass
|
||||
|
||||
|
||||
22
.env.example
22
.env.example
@@ -6,7 +6,7 @@ DEBUG=False
|
||||
# =============================================================================
|
||||
# PROJECT INFORMATION
|
||||
# =============================================================================
|
||||
PROJECT_NAME=Wizamart - Multi-Store Marketplace Platform
|
||||
PROJECT_NAME=Orion - Multi-Store Marketplace Platform
|
||||
DESCRIPTION=Multi-tenants multi-themes ecommerce application
|
||||
VERSION=2.2.0
|
||||
|
||||
@@ -14,17 +14,17 @@ VERSION=2.2.0
|
||||
# DATABASE CONFIGURATION (PostgreSQL required)
|
||||
# =============================================================================
|
||||
# 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:
|
||||
# DATABASE_URL=postgresql://username:password@production-host:5432/wizamart_db
|
||||
# DATABASE_URL=postgresql://username:password@production-host:5432/orion_db
|
||||
|
||||
# =============================================================================
|
||||
# ADMIN INITIALIZATION
|
||||
# =============================================================================
|
||||
# These are used by init_production.py to create the platform admin
|
||||
# ⚠️ CHANGE THESE IN PRODUCTION!
|
||||
ADMIN_EMAIL=admin@wizamart.com
|
||||
ADMIN_EMAIL=admin@orion.lu
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=change-me-in-production
|
||||
ADMIN_FIRST_NAME=Platform
|
||||
@@ -49,9 +49,9 @@ API_PORT=8000
|
||||
# Development
|
||||
DOCUMENTATION_URL=http://localhost:8001
|
||||
# Staging
|
||||
# DOCUMENTATION_URL=https://staging-docs.wizamart.com
|
||||
# DOCUMENTATION_URL=https://staging-docs.orion.lu
|
||||
# Production
|
||||
# DOCUMENTATION_URL=https://docs.wizamart.com
|
||||
# DOCUMENTATION_URL=https://docs.orion.lu
|
||||
|
||||
# =============================================================================
|
||||
# RATE LIMITING
|
||||
@@ -70,7 +70,7 @@ LOG_FILE=logs/app.log
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
# Your main platform domain
|
||||
PLATFORM_DOMAIN=wizamart.com
|
||||
PLATFORM_DOMAIN=orion.lu
|
||||
|
||||
# Custom domain features
|
||||
# Enable/disable custom domains
|
||||
@@ -85,7 +85,7 @@ SSL_PROVIDER=letsencrypt
|
||||
AUTO_PROVISION_SSL=False
|
||||
|
||||
# DNS verification
|
||||
DNS_VERIFICATION_PREFIX=_wizamart-verify
|
||||
DNS_VERIFICATION_PREFIX=_orion-verify
|
||||
DNS_VERIFICATION_TTL=3600
|
||||
|
||||
# =============================================================================
|
||||
@@ -103,8 +103,8 @@ STRIPE_TRIAL_DAYS=30
|
||||
# =============================================================================
|
||||
# Provider: smtp, sendgrid, mailgun, ses
|
||||
EMAIL_PROVIDER=smtp
|
||||
EMAIL_FROM_ADDRESS=noreply@wizamart.com
|
||||
EMAIL_FROM_NAME=Wizamart
|
||||
EMAIL_FROM_ADDRESS=noreply@orion.lu
|
||||
EMAIL_FROM_NAME=Orion
|
||||
EMAIL_REPLY_TO=
|
||||
|
||||
# SMTP Settings (used when EMAIL_PROVIDER=smtp)
|
||||
@@ -185,7 +185,7 @@ STORAGE_BACKEND=local
|
||||
R2_ACCOUNT_ID=
|
||||
R2_ACCESS_KEY_ID=
|
||||
R2_SECRET_ACCESS_KEY=
|
||||
R2_BUCKET_NAME=wizamart-media
|
||||
R2_BUCKET_NAME=orion-media
|
||||
|
||||
# Public URL for R2 bucket (optional - for custom domain)
|
||||
# If not set, uses Cloudflare's default R2 public URL
|
||||
|
||||
@@ -45,11 +45,11 @@ jobs:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
env:
|
||||
POSTGRES_DB: wizamart_test
|
||||
POSTGRES_DB: orion_test
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
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-timeout 5s
|
||||
--health-retries 5
|
||||
@@ -57,8 +57,8 @@ jobs:
|
||||
env:
|
||||
# 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)
|
||||
TEST_DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_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/orion_test"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -183,5 +183,5 @@ tailadmin-free-tailwind-dashboard-template/
|
||||
static/shared/css/tailwind.css
|
||||
|
||||
# Export files
|
||||
wizamart_letzshop_export_*.csv
|
||||
orion_letzshop_export_*.csv
|
||||
exports/
|
||||
|
||||
@@ -43,13 +43,13 @@ pytest:
|
||||
alias: postgres
|
||||
variables:
|
||||
# PostgreSQL service configuration
|
||||
POSTGRES_DB: wizamart_test
|
||||
POSTGRES_DB: orion_test
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
# 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)
|
||||
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/wizamart_test"
|
||||
DATABASE_URL: "postgresql://test_user:test_password@postgres:5432/orion_test"
|
||||
before_script:
|
||||
- pip install uv
|
||||
- uv sync --frozen
|
||||
|
||||
@@ -116,7 +116,7 @@ return {
|
||||
|
||||
### Duplicate /shop/ Prefix
|
||||
|
||||
**Problem:** Routes like `/stores/wizamart/shop/shop/products/4`
|
||||
**Problem:** Routes like `/stores/orion/shop/shop/products/4`
|
||||
|
||||
**Root Cause:**
|
||||
```python
|
||||
@@ -136,7 +136,7 @@ All routes in `shop_pages.py` fixed.
|
||||
|
||||
### 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:
|
||||
- `shop/base.html` - Header, footer, navigation
|
||||
@@ -290,15 +290,15 @@ Comprehensive guide covering:
|
||||
### Test URLs
|
||||
```
|
||||
Landing Pages:
|
||||
- http://localhost:8000/stores/wizamart/
|
||||
- http://localhost:8000/stores/orion/
|
||||
- http://localhost:8000/stores/fashionhub/
|
||||
- http://localhost:8000/stores/bookstore/
|
||||
|
||||
Shop Pages:
|
||||
- http://localhost:8000/stores/wizamart/shop/
|
||||
- http://localhost:8000/stores/wizamart/shop/products
|
||||
- http://localhost:8000/stores/wizamart/shop/products/1
|
||||
- http://localhost:8000/stores/wizamart/shop/cart
|
||||
- http://localhost:8000/stores/orion/shop/
|
||||
- http://localhost:8000/stores/orion/shop/products
|
||||
- http://localhost:8000/stores/orion/shop/products/1
|
||||
- http://localhost:8000/stores/orion/shop/cart
|
||||
```
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
10
Makefile
10
Makefile
@@ -1,4 +1,4 @@
|
||||
# Wizamart Multi-Tenant E-Commerce Platform Makefile
|
||||
# Orion Multi-Tenant E-Commerce Platform Makefile
|
||||
# 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
|
||||
@@ -132,7 +132,7 @@ seed-tiers:
|
||||
|
||||
# First-time installation - Complete setup with configuration validation
|
||||
platform-install:
|
||||
@echo "🚀 WIZAMART PLATFORM INSTALLATION"
|
||||
@echo "🚀 ORION PLATFORM INSTALLATION"
|
||||
@echo "=================================="
|
||||
$(PYTHON) scripts/seed/install.py
|
||||
|
||||
@@ -235,7 +235,7 @@ test-db-status:
|
||||
# =============================================================================
|
||||
|
||||
# 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
|
||||
MARKER_EXPR :=
|
||||
@@ -530,7 +530,7 @@ endif
|
||||
# =============================================================================
|
||||
|
||||
help:
|
||||
@echo "Wizamart Platform Development Commands"
|
||||
@echo "Orion Platform Development Commands"
|
||||
@echo ""
|
||||
@echo "=== SETUP ==="
|
||||
@echo " install - Install production dependencies"
|
||||
@@ -681,4 +681,4 @@ help-db:
|
||||
@echo " - Email provider settings (SMTP/SendGrid/Mailgun/SES)"
|
||||
@echo " - ADMIN_PASSWORD (strong password)"
|
||||
@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!"
|
||||
|
||||
12
README.md
12
README.md
@@ -34,7 +34,7 @@ This FastAPI application provides a complete ecommerce backend solution designed
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
wizamart/
|
||||
orion/
|
||||
├── main.py # FastAPI application entry point
|
||||
├── app/
|
||||
│ ├── core/
|
||||
@@ -179,8 +179,8 @@ make qa
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <wizamart-repo>
|
||||
cd wizamart-repo
|
||||
git clone <orion-repo>
|
||||
cd orion-repo
|
||||
|
||||
# Create virtual environment
|
||||
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
|
||||
- `GET /api/v1/marketplace/import-status/{job_id}` - Check import status
|
||||
- `GET /api/v1/marketplace/import-jobs` - List import jobs
|
||||
-
|
||||
-
|
||||
### Inventory Endpoints
|
||||
- `POST /api/v1/inventory` - Set inventory quantity
|
||||
- `POST /api/v1/inventory/add` - Add to inventory
|
||||
@@ -700,7 +700,7 @@ make help
|
||||
|
||||
This will display all available commands organized by category:
|
||||
- **Setup**: Installation and environment setup
|
||||
- **Development**: Development servers and workflows
|
||||
- **Development**: Development servers and workflows
|
||||
- **Documentation**: Documentation building and deployment
|
||||
- **Testing**: Various test execution options
|
||||
- **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
|
||||
- **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.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
If you discover a security vulnerability in this project, please report it responsibly:
|
||||
|
||||
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:
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ Landing pages have been created for three stores with different templates.
|
||||
|
||||
## 📍 Test URLs
|
||||
|
||||
### 1. WizaMart - Modern Template
|
||||
### 1. Orion - Modern Template
|
||||
**Landing Page:**
|
||||
- http://localhost:8000/stores/wizamart/
|
||||
- http://localhost:8000/stores/orion/
|
||||
|
||||
**Shop Page:**
|
||||
- http://localhost:8000/stores/wizamart/shop/
|
||||
- http://localhost:8000/stores/orion/shop/
|
||||
|
||||
**What to expect:**
|
||||
- Full-screen hero section with animations
|
||||
@@ -93,8 +93,8 @@ db.close()
|
||||
"
|
||||
```
|
||||
|
||||
Then visit: http://localhost:8000/stores/wizamart/
|
||||
- Should automatically redirect to: http://localhost:8000/stores/wizamart/shop/
|
||||
Then visit: http://localhost:8000/stores/orion/
|
||||
- Should automatically redirect to: http://localhost:8000/stores/orion/shop/
|
||||
|
||||
---
|
||||
|
||||
@@ -111,17 +111,17 @@ Or programmatically:
|
||||
```python
|
||||
from scripts.create_landing_page import create_landing_page
|
||||
|
||||
# Change WizaMart to default template
|
||||
create_landing_page('wizamart', template='default')
|
||||
# Change Orion to default template
|
||||
create_landing_page('orion', template='default')
|
||||
|
||||
# Change to minimal
|
||||
create_landing_page('wizamart', template='minimal')
|
||||
create_landing_page('orion', template='minimal')
|
||||
|
||||
# Change to full
|
||||
create_landing_page('wizamart', template='full')
|
||||
create_landing_page('orion', template='full')
|
||||
|
||||
# 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 |
|
||||
|--------|-----------|----------|------------------|
|
||||
| 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/ |
|
||||
| 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:
|
||||
```
|
||||
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
|
||||
10|3|landing|The Book Store - Your Literary Haven|full|1
|
||||
```
|
||||
@@ -180,7 +180,7 @@ Expected output:
|
||||
|
||||
## ✅ Success Checklist
|
||||
|
||||
- [ ] WizaMart landing page loads (modern template)
|
||||
- [ ] Orion landing page loads (modern template)
|
||||
- [ ] Fashion Hub landing page loads (minimal template)
|
||||
- [ ] Book Store landing page loads (full template)
|
||||
- [ ] "Shop Now" buttons work correctly
|
||||
|
||||
@@ -19,7 +19,7 @@ def upgrade() -> None:
|
||||
"platforms",
|
||||
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("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("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/*)"),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
It includes:
|
||||
@@ -66,7 +66,7 @@ def get_all_task_modules() -> list[str]:
|
||||
|
||||
# Create Celery application
|
||||
celery_app = Celery(
|
||||
"wizamart",
|
||||
"orion",
|
||||
broker=REDIS_URL,
|
||||
backend=REDIS_URL,
|
||||
include=get_all_task_modules(),
|
||||
|
||||
@@ -27,7 +27,7 @@ class Settings(BaseSettings):
|
||||
# =============================================================================
|
||||
# PROJECT INFORMATION
|
||||
# =============================================================================
|
||||
project_name: str = "Wizamart - Multi-Store Marketplace Platform"
|
||||
project_name: str = "Orion - Multi-Store Marketplace Platform"
|
||||
version: str = "2.2.0"
|
||||
|
||||
# Clean description without HTML
|
||||
@@ -47,12 +47,12 @@ class Settings(BaseSettings):
|
||||
# =============================================================================
|
||||
# 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_email: str = "admin@wizamart.com"
|
||||
admin_email: str = "admin@orion.lu"
|
||||
admin_username: str = "admin"
|
||||
admin_password: str = "admin123" # CHANGE IN PRODUCTION!
|
||||
admin_first_name: str = "Platform"
|
||||
@@ -96,7 +96,7 @@ class Settings(BaseSettings):
|
||||
# =============================================================================
|
||||
# PLATFORM DOMAIN CONFIGURATION
|
||||
# =============================================================================
|
||||
platform_domain: str = "wizamart.com"
|
||||
platform_domain: str = "orion.lu"
|
||||
|
||||
# Custom domain features
|
||||
allow_custom_domains: bool = True
|
||||
@@ -107,7 +107,7 @@ class Settings(BaseSettings):
|
||||
auto_provision_ssl: bool = False
|
||||
|
||||
# DNS verification
|
||||
dns_verification_prefix: str = "_wizamart-verify"
|
||||
dns_verification_prefix: str = "_orion-verify"
|
||||
dns_verification_ttl: int = 3600
|
||||
|
||||
# =============================================================================
|
||||
@@ -130,8 +130,8 @@ class Settings(BaseSettings):
|
||||
# =============================================================================
|
||||
# Provider: smtp, sendgrid, mailgun, ses
|
||||
email_provider: str = "smtp"
|
||||
email_from_address: str = "noreply@wizamart.com"
|
||||
email_from_name: str = "Wizamart"
|
||||
email_from_address: str = "noreply@orion.lu"
|
||||
email_from_name: str = "Orion"
|
||||
email_reply_to: str = "" # Optional reply-to address
|
||||
|
||||
# SMTP Settings (used when email_provider=smtp)
|
||||
@@ -201,7 +201,7 @@ class Settings(BaseSettings):
|
||||
r2_account_id: str | None = None
|
||||
r2_access_key_id: 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)
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -9,7 +9,7 @@ Detection priority:
|
||||
1. Admin subdomain (admin.oms.lu)
|
||||
2. Path-based admin/store (/admin/*, /store/*, /api/v1/admin/*)
|
||||
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/*)
|
||||
6. Default to PLATFORM (marketing pages)
|
||||
|
||||
@@ -62,7 +62,7 @@ class FrontendDetector:
|
||||
Detect frontend type from request.
|
||||
|
||||
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")
|
||||
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")
|
||||
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 and subdomain not in cls.RESERVED_SUBDOMAINS:
|
||||
logger.debug(
|
||||
@@ -138,7 +138,7 @@ class FrontendDetector:
|
||||
@classmethod
|
||||
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.
|
||||
Handles special case of admin.localhost for development.
|
||||
|
||||
@@ -32,13 +32,13 @@ async def lifespan(app: FastAPI):
|
||||
|
||||
# === STARTUP ===
|
||||
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")
|
||||
|
||||
yield
|
||||
|
||||
# === SHUTDOWN ===
|
||||
app_logger.info("Shutting down Wizamart platform")
|
||||
app_logger.info("Shutting down Orion platform")
|
||||
# Add cleanup tasks here if needed
|
||||
|
||||
|
||||
|
||||
@@ -33,16 +33,16 @@ from .base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ExternalServiceException,
|
||||
OrionException,
|
||||
RateLimitException,
|
||||
ResourceNotFoundException,
|
||||
ServiceUnavailableException,
|
||||
ValidationException,
|
||||
WizamartException,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base exception class
|
||||
"WizamartException",
|
||||
"OrionException",
|
||||
# Validation and business logic
|
||||
"ValidationException",
|
||||
"BusinessLogicException",
|
||||
|
||||
@@ -11,7 +11,7 @@ This module provides classes and functions for:
|
||||
from typing import Any
|
||||
|
||||
|
||||
class WizamartException(Exception):
|
||||
class OrionException(Exception):
|
||||
"""Base exception class for all custom exceptions."""
|
||||
|
||||
def __init__(
|
||||
@@ -39,7 +39,7 @@ class WizamartException(Exception):
|
||||
return result
|
||||
|
||||
|
||||
class ValidationException(WizamartException):
|
||||
class ValidationException(OrionException):
|
||||
"""Raised when request validation fails."""
|
||||
|
||||
def __init__(
|
||||
@@ -60,7 +60,7 @@ class ValidationException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class AuthenticationException(WizamartException):
|
||||
class AuthenticationException(OrionException):
|
||||
"""Raised when authentication fails."""
|
||||
|
||||
def __init__(
|
||||
@@ -77,7 +77,7 @@ class AuthenticationException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class AuthorizationException(WizamartException):
|
||||
class AuthorizationException(OrionException):
|
||||
"""Raised when user lacks permission for an operation."""
|
||||
|
||||
def __init__(
|
||||
@@ -94,7 +94,7 @@ class AuthorizationException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class ResourceNotFoundException(WizamartException):
|
||||
class ResourceNotFoundException(OrionException):
|
||||
"""Raised when a requested resource is not found."""
|
||||
|
||||
def __init__(
|
||||
@@ -120,7 +120,7 @@ class ResourceNotFoundException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class ConflictException(WizamartException):
|
||||
class ConflictException(OrionException):
|
||||
"""Raised when a resource conflict occurs."""
|
||||
|
||||
def __init__(
|
||||
@@ -137,7 +137,7 @@ class ConflictException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class BusinessLogicException(WizamartException):
|
||||
class BusinessLogicException(OrionException):
|
||||
"""Raised when business logic rules are violated."""
|
||||
|
||||
def __init__(
|
||||
@@ -154,7 +154,7 @@ class BusinessLogicException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class ExternalServiceException(WizamartException):
|
||||
class ExternalServiceException(OrionException):
|
||||
"""Raised when an external service fails."""
|
||||
|
||||
def __init__(
|
||||
@@ -175,7 +175,7 @@ class ExternalServiceException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class RateLimitException(WizamartException):
|
||||
class RateLimitException(OrionException):
|
||||
"""Raised when rate limit is exceeded."""
|
||||
|
||||
def __init__(
|
||||
@@ -196,7 +196,7 @@ class RateLimitException(WizamartException):
|
||||
)
|
||||
|
||||
|
||||
class ServiceUnavailableException(WizamartException):
|
||||
class ServiceUnavailableException(OrionException):
|
||||
"""Raised when service is unavailable."""
|
||||
|
||||
def __init__(self, message: str = "Service temporarily unavailable"):
|
||||
|
||||
@@ -19,7 +19,7 @@ from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from app.modules.enums import FrontendType
|
||||
from middleware.frontend_type import get_frontend_type
|
||||
|
||||
from .base import WizamartException
|
||||
from .base import OrionException
|
||||
from .error_renderer import ErrorPageRenderer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -28,8 +28,8 @@ logger = logging.getLogger(__name__)
|
||||
def setup_exception_handlers(app):
|
||||
"""Setup exception handlers for the FastAPI app."""
|
||||
|
||||
@app.exception_handler(WizamartException)
|
||||
async def custom_exception_handler(request: Request, exc: WizamartException):
|
||||
@app.exception_handler(OrionException)
|
||||
async def custom_exception_handler(request: Request, exc: OrionException):
|
||||
"""Handle custom exceptions with context-aware rendering."""
|
||||
|
||||
# Special handling for auth errors on HTML page requests (redirect to login)
|
||||
|
||||
@@ -37,7 +37,7 @@ class MerchantSubscription(Base, TimestampMixin):
|
||||
|
||||
Example:
|
||||
Merchant "Boucherie Luxembourg" subscribes to:
|
||||
- Wizamart OMS (Professional tier)
|
||||
- Orion OMS (Professional tier)
|
||||
- Loyalty+ (Essential tier)
|
||||
|
||||
Their stores inherit features from the merchant's subscription.
|
||||
|
||||
@@ -119,7 +119,7 @@ async def signup_success_page(
|
||||
Shown after successful account creation.
|
||||
"""
|
||||
context = get_platform_context(request, db)
|
||||
context["page_title"] = "Welcome to Wizamart!"
|
||||
context["page_title"] = "Welcome to Orion!"
|
||||
context["store_code"] = store_code
|
||||
|
||||
return templates.TemplateResponse(
|
||||
|
||||
@@ -324,7 +324,7 @@ function storeInvoices() {
|
||||
|
||||
try {
|
||||
// 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) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{# Standalone Pricing Page #}
|
||||
{% extends "platform/base.html" %}
|
||||
|
||||
{% block title %}{{ _("cms.platform.pricing.title") }} - Wizamart{% endblock %}
|
||||
{% block title %}{{ _("cms.platform.pricing.title") }} - Orion{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="{ annual: false }" class="py-16 lg:py-24">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{# Multi-step Signup Wizard #}
|
||||
{% extends "platform/base.html" %}
|
||||
|
||||
{% block title %}Start Your Free Trial - Wizamart{% endblock %}
|
||||
{% block title %}Start Your Free Trial - Orion{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
{# Stripe.js for payment #}
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
"creating_account": "Erstelle Ihr Konto..."
|
||||
},
|
||||
"success": {
|
||||
"title": "Willkommen bei Wizamart!",
|
||||
"title": "Willkommen bei Orion!",
|
||||
"subtitle": "Ihr Konto wurde erstellt und Ihre {trial_days}-tägige kostenlose Testphase hat begonnen.",
|
||||
"what_next": "Was kommt als Nächstes?",
|
||||
"step_connect": "Letzshop verbinden:",
|
||||
@@ -149,7 +149,7 @@
|
||||
},
|
||||
"cta": {
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"quick_links": "Schnelllinks",
|
||||
"platform": "Plattform",
|
||||
"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",
|
||||
"terms": "Nutzungsbedingungen",
|
||||
"about": "Über uns",
|
||||
@@ -188,7 +188,7 @@
|
||||
"how_step1": "Letzshop verbinden",
|
||||
"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_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_desc": "Ein Klick, um konforme PDF-Rechnungen mit korrekter MwSt für jedes EU-Land zu erstellen.",
|
||||
"how_step4": "Ihr Geschäft ausbauen",
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
"creating_account": "Creating your account..."
|
||||
},
|
||||
"success": {
|
||||
"title": "Welcome to Wizamart!",
|
||||
"title": "Welcome to Orion!",
|
||||
"subtitle": "Your account has been created and your {trial_days}-day free trial has started.",
|
||||
"what_next": "What's Next?",
|
||||
"step_connect": "Connect Letzshop:",
|
||||
@@ -149,7 +149,7 @@
|
||||
},
|
||||
"cta": {
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"quick_links": "Quick Links",
|
||||
"platform": "Platform",
|
||||
"contact": "Contact",
|
||||
"copyright": "© {year} Wizamart. Built for Luxembourg e-commerce.",
|
||||
"copyright": "© {year} Orion. Built for Luxembourg e-commerce.",
|
||||
"privacy": "Privacy Policy",
|
||||
"terms": "Terms of Service",
|
||||
"about": "About Us",
|
||||
@@ -188,7 +188,7 @@
|
||||
"how_step1": "Connect Letzshop",
|
||||
"how_step1_desc": "Enter your Letzshop API credentials. Done in 2 minutes, no technical skills needed.",
|
||||
"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_desc": "One click to create compliant PDF invoices with correct VAT for any EU country.",
|
||||
"how_step4": "Grow Your Business",
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
"creating_account": "Création de votre compte..."
|
||||
},
|
||||
"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é.",
|
||||
"what_next": "Et maintenant ?",
|
||||
"step_connect": "Connecter Letzshop :",
|
||||
@@ -149,7 +149,7 @@
|
||||
},
|
||||
"cta": {
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"quick_links": "Liens rapides",
|
||||
"platform": "Plateforme",
|
||||
"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é",
|
||||
"terms": "Conditions d'utilisation",
|
||||
"about": "À propos",
|
||||
@@ -188,7 +188,7 @@
|
||||
"how_step1": "Connecter Letzshop",
|
||||
"how_step1_desc": "Entrez vos identifiants API Letzshop. Fait en 2 minutes, aucune compétence technique requise.",
|
||||
"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_desc": "Un clic pour créer des factures PDF conformes avec la TVA correcte pour tout pays UE.",
|
||||
"how_step4": "Développez votre entreprise",
|
||||
|
||||
@@ -133,7 +133,7 @@
|
||||
"creating_account": "Erstellt Äre Kont..."
|
||||
},
|
||||
"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.",
|
||||
"what_next": "Wat kënnt duerno?",
|
||||
"step_connect": "Letzshop verbannen:",
|
||||
@@ -149,7 +149,7 @@
|
||||
},
|
||||
"cta": {
|
||||
"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"
|
||||
},
|
||||
"footer": {
|
||||
@@ -157,7 +157,7 @@
|
||||
"quick_links": "Séier Linken",
|
||||
"platform": "Plattform",
|
||||
"contact": "Kontakt",
|
||||
"copyright": "© {year} Wizamart. Gemaach fir de lëtzebuergeschen E-Commerce.",
|
||||
"copyright": "© {year} Orion. Gemaach fir de lëtzebuergeschen E-Commerce.",
|
||||
"privacy": "Dateschutzrichtlinn",
|
||||
"terms": "Notzungsbedéngungen",
|
||||
"about": "Iwwer eis",
|
||||
@@ -188,7 +188,7 @@
|
||||
"how_step1": "Letzshop verbannen",
|
||||
"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_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_desc": "Ee Klick fir konform PDF Rechnunge mat korrekter TVA fir all EU Land ze erstellen.",
|
||||
"how_step4": "Äert Geschäft ausbauen",
|
||||
|
||||
@@ -165,8 +165,8 @@ async def homepage(
|
||||
logger.info(f"[HOMEPAGE] Rendering CMS homepage with template: {template_path}")
|
||||
return templates.TemplateResponse(template_path, context)
|
||||
|
||||
# Fallback: Default wizamart homepage (no CMS content)
|
||||
logger.info("[HOMEPAGE] No CMS homepage found, using default wizamart template")
|
||||
# Fallback: Default orion homepage (no CMS content)
|
||||
logger.info("[HOMEPAGE] No CMS homepage found, using default orion template")
|
||||
context = get_platform_context(request, db)
|
||||
context["tiers"] = _get_tiers_data(db)
|
||||
|
||||
@@ -204,7 +204,7 @@ async def homepage(
|
||||
]
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"cms/platform/homepage-wizamart.html",
|
||||
"cms/platform/homepage-orion.html",
|
||||
context,
|
||||
)
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ async def store_content_page(
|
||||
Generic content page handler for store shop (CMS).
|
||||
|
||||
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:
|
||||
- Two-tier system: Store overrides take priority, fallback to platform defaults
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{# 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" %}
|
||||
|
||||
{% block title %}
|
||||
Wizamart - The Back-Office for Letzshop Sellers
|
||||
Orion - The Back-Office for Letzshop Sellers
|
||||
{% endblock %}
|
||||
|
||||
{% 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-yellow-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>
|
||||
{# Mock dashboard content #}
|
||||
<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="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>
|
||||
<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 class="hidden lg:block absolute top-1/2 -right-4 w-8 h-0.5 bg-blue-300"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{# app/templates/platform/homepage-wizamart.html #}
|
||||
{# Wizamart Marketing Homepage - Letzshop OMS Platform #}
|
||||
{# app/templates/platform/homepage-orion.html #}
|
||||
{# Orion Marketing Homepage - Letzshop OMS Platform #}
|
||||
{% extends "platform/base.html" %}
|
||||
{% 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 content %}
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
{# CTA Button #}
|
||||
{% 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">
|
||||
{{ _("cms.platform.pricing.contact_sales") }}
|
||||
</a>
|
||||
@@ -7,10 +7,10 @@ Exceptions for core platform functionality including:
|
||||
- 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."""
|
||||
|
||||
|
||||
|
||||
@@ -663,11 +663,11 @@ def send_test_email(
|
||||
email_log = email_service.send_raw(
|
||||
to_email=request.to_email,
|
||||
to_name=None,
|
||||
subject="Wizamart Platform - Test Email",
|
||||
subject="Orion Platform - Test Email",
|
||||
body_html=f"""
|
||||
<html>
|
||||
<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>If you received this email, your email settings are working correctly!</p>
|
||||
<hr style="border: none; border-top: 1px solid #e5e7eb; margin: 20px 0;">
|
||||
@@ -678,7 +678,7 @@ def send_test_email(
|
||||
</body>
|
||||
</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,
|
||||
)
|
||||
|
||||
|
||||
@@ -616,7 +616,7 @@ class OnboardingService:
|
||||
"success": True,
|
||||
"step_completed": True,
|
||||
"onboarding_completed": True,
|
||||
"message": "Onboarding complete! Welcome to Wizamart.",
|
||||
"message": "Onboarding complete! Welcome to Orion.",
|
||||
"redirect_url": f"/store/{store_code}/dashboard",
|
||||
}
|
||||
|
||||
|
||||
@@ -437,7 +437,7 @@ function storeLetzshop() {
|
||||
});
|
||||
|
||||
// Get the token for authentication
|
||||
const token = localStorage.getItem('wizamart_token');
|
||||
const token = localStorage.getItem('orion_token');
|
||||
if (!token) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const onboardingLog = window.LogConfig?.createLogger('ONBOARDING') || console;
|
||||
// Onboarding translations
|
||||
const onboardingTranslations = {
|
||||
en: {
|
||||
title: 'Welcome to Wizamart',
|
||||
title: 'Welcome to Orion',
|
||||
subtitle: 'Complete these steps to set up your store',
|
||||
steps: {
|
||||
merchant_profile: 'Merchant Profile',
|
||||
@@ -71,7 +71,7 @@ const onboardingTranslations = {
|
||||
},
|
||||
step4: {
|
||||
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: 'days',
|
||||
start_import: 'Start Import',
|
||||
@@ -94,7 +94,7 @@ const onboardingTranslations = {
|
||||
},
|
||||
},
|
||||
fr: {
|
||||
title: 'Bienvenue sur Wizamart',
|
||||
title: 'Bienvenue sur Orion',
|
||||
subtitle: 'Complétez ces étapes pour configurer votre boutique',
|
||||
steps: {
|
||||
merchant_profile: 'Profil Entreprise',
|
||||
@@ -149,7 +149,7 @@ const onboardingTranslations = {
|
||||
},
|
||||
step4: {
|
||||
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: 'jours',
|
||||
start_import: 'Démarrer l\'Import',
|
||||
@@ -172,7 +172,7 @@ const onboardingTranslations = {
|
||||
},
|
||||
},
|
||||
de: {
|
||||
title: 'Willkommen bei Wizamart',
|
||||
title: 'Willkommen bei Orion',
|
||||
subtitle: 'Führen Sie diese Schritte aus, um Ihren Shop einzurichten',
|
||||
steps: {
|
||||
merchant_profile: 'Firmenprofil',
|
||||
@@ -227,7 +227,7 @@ const onboardingTranslations = {
|
||||
},
|
||||
step4: {
|
||||
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: 'Tage',
|
||||
start_import: 'Import Starten',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
{# Letzshop Store Finder Page #}
|
||||
{% extends "platform/base.html" %}
|
||||
|
||||
{% block title %}{{ _("cms.platform.find_shop.title") }} - Wizamart{% endblock %}
|
||||
{% block title %}{{ _("cms.platform.find_shop.title") }} - Orion{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="storeFinderData()" class="py-16 lg:py-24">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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="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') }}" />
|
||||
@@ -22,7 +22,7 @@
|
||||
<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>
|
||||
</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>
|
||||
<!-- Logout Button -->
|
||||
<button @click="handleLogout()"
|
||||
|
||||
@@ -11,7 +11,7 @@ Architecture:
|
||||
- Stores MUST configure email settings to send transactional emails
|
||||
- Platform emails (billing, subscription) still use platform settings
|
||||
- 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
|
||||
|
||||
@@ -291,7 +291,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"login_url": "https://example.com/login",
|
||||
"trial_days": "14",
|
||||
"tier_name": "Business",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"order_confirmation": {
|
||||
"customer_name": "Jane Doe",
|
||||
@@ -300,13 +300,13 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"order_items_count": "3",
|
||||
"order_date": "2024-01-15",
|
||||
"shipping_address": "123 Main St, Luxembourg City, L-1234",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"password_reset": {
|
||||
"customer_name": "John Doe",
|
||||
"reset_link": "https://example.com/reset?token=abc123",
|
||||
"expiry_hours": "1",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"team_invite": {
|
||||
"invitee_name": "Jane",
|
||||
@@ -315,7 +315,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"role": "Admin",
|
||||
"accept_url": "https://example.com/accept",
|
||||
"expires_in_days": "7",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"subscription_welcome": {
|
||||
"store_name": "Acme Corp",
|
||||
@@ -324,7 +324,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"amount": "€49.99",
|
||||
"next_billing_date": "2024-02-15",
|
||||
"dashboard_url": "https://example.com/dashboard",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"payment_failed": {
|
||||
"store_name": "Acme Corp",
|
||||
@@ -332,15 +332,15 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"amount": "€49.99",
|
||||
"retry_date": "2024-01-18",
|
||||
"update_payment_url": "https://example.com/billing",
|
||||
"support_email": "support@wizamart.com",
|
||||
"platform_name": "Wizamart",
|
||||
"support_email": "support@orion.lu",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"subscription_cancelled": {
|
||||
"store_name": "Acme Corp",
|
||||
"tier_name": "Business",
|
||||
"end_date": "2024-02-15",
|
||||
"reactivate_url": "https://example.com/billing",
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
},
|
||||
"trial_ending": {
|
||||
"store_name": "Acme Corp",
|
||||
@@ -349,7 +349,7 @@ def _get_sample_variables(template_code: str) -> dict[str, Any]:
|
||||
"trial_end_date": "2024-01-18",
|
||||
"upgrade_url": "https://example.com/upgrade",
|
||||
"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"})
|
||||
|
||||
@@ -184,9 +184,9 @@ def preview_template(
|
||||
variables = {
|
||||
**_get_sample_variables(code),
|
||||
**preview_data.variables,
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
"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(
|
||||
@@ -216,9 +216,9 @@ def send_test_email(
|
||||
variables = {
|
||||
**_get_sample_variables(code),
|
||||
**test_data.variables,
|
||||
"platform_name": "Wizamart",
|
||||
"platform_name": "Orion",
|
||||
"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:
|
||||
|
||||
@@ -51,24 +51,24 @@ from app.modules.messaging.models import (
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Platform branding constants
|
||||
PLATFORM_NAME = "Wizamart"
|
||||
PLATFORM_SUPPORT_EMAIL = "support@wizamart.com"
|
||||
PLATFORM_NAME = "Orion"
|
||||
PLATFORM_SUPPORT_EMAIL = "support@orion.lu"
|
||||
PLATFORM_DEFAULT_LANGUAGE = "en"
|
||||
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"}
|
||||
|
||||
# Powered by Wizamart footer (added for Essential/Professional tiers)
|
||||
# Powered by Orion footer (added for Essential/Professional tiers)
|
||||
POWERED_BY_FOOTER_HTML = """
|
||||
<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;">
|
||||
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>
|
||||
</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
|
||||
@@ -1045,7 +1045,7 @@ class EmailService:
|
||||
|
||||
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.
|
||||
Business and Enterprise tiers get white-label (no footer).
|
||||
@@ -1066,7 +1066,7 @@ class EmailService:
|
||||
store_id: int | 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:
|
||||
Tuple of (modified_html, modified_text)
|
||||
@@ -1168,7 +1168,7 @@ class EmailService:
|
||||
store_logo_url=store.get_logo_url(),
|
||||
is_whitelabel=True,
|
||||
)
|
||||
# Standard: Wizamart branding with store details
|
||||
# Standard: Orion branding with store details
|
||||
return BrandingContext(
|
||||
platform_name=PLATFORM_NAME,
|
||||
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):
|
||||
- Uses store's SMTP/provider settings if configured
|
||||
- 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):
|
||||
- Uses platform's email settings from config
|
||||
- No "Powered by Wizamart" footer
|
||||
- No "Powered by Orion" footer
|
||||
|
||||
Args:
|
||||
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)
|
||||
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:
|
||||
body_html, body_text = self._inject_powered_by_footer(
|
||||
body_html, body_text, store_id
|
||||
|
||||
@@ -284,12 +284,12 @@ class StoreEmailSettingsService:
|
||||
def _send_smtp_test(self, settings: StoreEmailSettings, to_email: str) -> None:
|
||||
"""Send test email via SMTP."""
|
||||
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["To"] = to_email
|
||||
|
||||
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"
|
||||
f"Provider: SMTP\n"
|
||||
f"Host: {settings.smtp_host}\n"
|
||||
@@ -298,7 +298,7 @@ class StoreEmailSettingsService:
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; padding: 20px;">
|
||||
<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;">
|
||||
Your email settings are configured correctly!
|
||||
</p>
|
||||
@@ -341,12 +341,12 @@ class StoreEmailSettingsService:
|
||||
message = Mail(
|
||||
from_email=(settings.from_email, settings.from_name),
|
||||
to_emails=to_email,
|
||||
subject="Wizamart Email Configuration Test",
|
||||
subject="Orion Email Configuration Test",
|
||||
html_content="""
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; padding: 20px;">
|
||||
<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;">
|
||||
Your SendGrid settings are configured correctly!
|
||||
</p>
|
||||
@@ -374,12 +374,12 @@ class StoreEmailSettingsService:
|
||||
data={
|
||||
"from": f"{settings.from_name} <{settings.from_email}>",
|
||||
"to": to_email,
|
||||
"subject": "Wizamart Email Configuration Test",
|
||||
"subject": "Orion Email Configuration Test",
|
||||
"html": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; padding: 20px;">
|
||||
<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;">
|
||||
Your Mailgun settings are configured correctly!
|
||||
</p>
|
||||
@@ -417,14 +417,14 @@ class StoreEmailSettingsService:
|
||||
Source=f"{settings.from_name} <{settings.from_email}>",
|
||||
Destination={"ToAddresses": [to_email]},
|
||||
Message={
|
||||
"Subject": {"Data": "Wizamart Email Configuration Test"},
|
||||
"Subject": {"Data": "Orion Email Configuration Test"},
|
||||
"Body": {
|
||||
"Html": {
|
||||
"Data": """
|
||||
<html>
|
||||
<body style="font-family: Arial, sans-serif; padding: 20px;">
|
||||
<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;">
|
||||
Your Amazon SES settings are configured correctly!
|
||||
</p>
|
||||
|
||||
@@ -236,7 +236,7 @@ function emailTemplatesPage() {
|
||||
expires_in_days: '7'
|
||||
}
|
||||
};
|
||||
return samples[templateCode] || { platform_name: 'Wizamart' };
|
||||
return samples[templateCode] || { platform_name: 'Orion' };
|
||||
},
|
||||
|
||||
// Test Email
|
||||
|
||||
@@ -35,8 +35,8 @@ class TestEmailProviders:
|
||||
subject="Test Subject",
|
||||
body_html="<h1>Hello</h1>",
|
||||
body_text="Hello",
|
||||
from_email="noreply@wizamart.com",
|
||||
from_name="Wizamart",
|
||||
from_email="noreply@orion.lu",
|
||||
from_name="Orion",
|
||||
)
|
||||
|
||||
assert success is True
|
||||
@@ -53,9 +53,9 @@ class TestEmailProviders:
|
||||
subject="Test Subject",
|
||||
body_html="<h1>Hello</h1>",
|
||||
body_text=None,
|
||||
from_email="noreply@wizamart.com",
|
||||
from_name="Wizamart",
|
||||
reply_to="support@wizamart.com",
|
||||
from_email="noreply@orion.lu",
|
||||
from_name="Orion",
|
||||
reply_to="support@orion.lu",
|
||||
)
|
||||
|
||||
assert success is True
|
||||
|
||||
@@ -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-sm font-medium text-gray-700 dark:text-gray-300">Platform Settings</span>
|
||||
</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-sm font-medium text-gray-700 dark:text-gray-300">Capacity Planning Docs</span>
|
||||
</a>
|
||||
|
||||
@@ -12,9 +12,9 @@ from typing import Any
|
||||
|
||||
from app.exceptions.base import (
|
||||
BusinessLogicException,
|
||||
OrionException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
WizamartException,
|
||||
)
|
||||
|
||||
__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."""
|
||||
|
||||
def __init__(self, store_id: int):
|
||||
@@ -205,7 +205,7 @@ class InvoiceValidationException(BusinessLogicException):
|
||||
)
|
||||
|
||||
|
||||
class InvoicePDFGenerationException(WizamartException):
|
||||
class InvoicePDFGenerationException(OrionException):
|
||||
"""Raised when PDF generation fails."""
|
||||
|
||||
def __init__(self, invoice_id: int, reason: str):
|
||||
|
||||
@@ -13,9 +13,9 @@ from app.exceptions.base import (
|
||||
BusinessLogicException,
|
||||
ConflictException,
|
||||
ExternalServiceException,
|
||||
OrionException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
WizamartException,
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
@@ -116,7 +116,7 @@ class UserAlreadyExistsException(ConflictException):
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class PlatformNotFoundException(WizamartException):
|
||||
class PlatformNotFoundException(OrionException):
|
||||
"""Raised when a platform is not found."""
|
||||
|
||||
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."""
|
||||
|
||||
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."""
|
||||
|
||||
def __init__(self, code: str, reason: str):
|
||||
|
||||
@@ -34,7 +34,7 @@ class MerchantDomain(Base, TimestampMixin):
|
||||
|
||||
Examples:
|
||||
- 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
|
||||
"""
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class Platform(Base, TimestampMixin):
|
||||
Represents a business offering/product line.
|
||||
|
||||
Examples:
|
||||
- Wizamart OMS (Order Management System)
|
||||
- Orion OMS (Order Management System)
|
||||
- Loyalty+ (Loyalty Program Platform)
|
||||
- Site Builder (Website Builder for Local Businesses)
|
||||
|
||||
@@ -62,7 +62,7 @@ class Platform(Base, TimestampMixin):
|
||||
name = Column(
|
||||
String(100),
|
||||
nullable=False,
|
||||
comment="Display name (e.g., 'Wizamart OMS')",
|
||||
comment="Display name (e.g., 'Orion OMS')",
|
||||
)
|
||||
|
||||
description = Column(
|
||||
|
||||
@@ -40,8 +40,8 @@ class StorePlatform(Base, TimestampMixin):
|
||||
- Store platform-specific settings
|
||||
|
||||
Example:
|
||||
- Store "WizaMart" is on OMS platform (Professional tier)
|
||||
- Store "WizaMart" is also on Loyalty platform (Basic tier)
|
||||
- Store "Orion" is on OMS platform (Professional tier)
|
||||
- Store "Orion" is also on Loyalty platform (Basic tier)
|
||||
"""
|
||||
|
||||
__tablename__ = "store_platforms"
|
||||
|
||||
@@ -249,7 +249,7 @@ def verify_merchant_domain_ownership(
|
||||
Verify merchant domain ownership via DNS TXT record (Admin only).
|
||||
|
||||
**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
|
||||
3. If found, marks domain as verified
|
||||
|
||||
|
||||
@@ -247,14 +247,14 @@ def verify_domain_ownership(
|
||||
Verify domain ownership via DNS TXT record (Admin only).
|
||||
|
||||
**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
|
||||
3. If found, marks domain as verified
|
||||
|
||||
**Requirements:**
|
||||
- Store must have added TXT record to their DNS
|
||||
- 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:**
|
||||
- Domain can be activated
|
||||
|
||||
@@ -42,7 +42,7 @@ def get_store_info(
|
||||
**Returns only active stores** to prevent access to disabled accounts.
|
||||
|
||||
Args:
|
||||
store_code: The store's unique code (e.g., 'WIZAMART')
|
||||
store_code: The store's unique code (e.g., 'ORION')
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -293,7 +293,7 @@ class MerchantDomainService:
|
||||
Verify merchant domain ownership via DNS TXT record.
|
||||
|
||||
The merchant must add a TXT record:
|
||||
Name: _wizamart-verify.{domain}
|
||||
Name: _orion-verify.{domain}
|
||||
Value: {verification_token}
|
||||
"""
|
||||
try:
|
||||
@@ -304,7 +304,7 @@ class MerchantDomainService:
|
||||
|
||||
try:
|
||||
txt_records = dns.resolver.resolve(
|
||||
f"_wizamart-verify.{domain.domain}", "TXT"
|
||||
f"_orion-verify.{domain.domain}", "TXT"
|
||||
)
|
||||
|
||||
for txt in txt_records:
|
||||
@@ -331,7 +331,7 @@ class MerchantDomainService:
|
||||
except dns.resolver.NXDOMAIN:
|
||||
raise DomainVerificationFailedException(
|
||||
domain.domain,
|
||||
f"DNS record _wizamart-verify.{domain.domain} not found",
|
||||
f"DNS record _orion-verify.{domain.domain} not found",
|
||||
)
|
||||
except dns.resolver.NoAnswer:
|
||||
raise DomainVerificationFailedException(
|
||||
@@ -368,7 +368,7 @@ class MerchantDomainService:
|
||||
},
|
||||
"txt_record": {
|
||||
"type": "TXT",
|
||||
"name": "_wizamart-verify",
|
||||
"name": "_orion-verify",
|
||||
"value": domain.verification_token,
|
||||
"ttl": 3600,
|
||||
},
|
||||
|
||||
@@ -284,7 +284,7 @@ class StoreDomainService:
|
||||
Verify domain ownership via DNS TXT record.
|
||||
|
||||
The store must add a TXT record:
|
||||
Name: _wizamart-verify.{domain}
|
||||
Name: _orion-verify.{domain}
|
||||
Value: {verification_token}
|
||||
|
||||
Args:
|
||||
@@ -309,7 +309,7 @@ class StoreDomainService:
|
||||
# Query DNS TXT records
|
||||
try:
|
||||
txt_records = dns.resolver.resolve(
|
||||
f"_wizamart-verify.{domain.domain}", "TXT"
|
||||
f"_orion-verify.{domain.domain}", "TXT"
|
||||
)
|
||||
|
||||
# Check if verification token is present
|
||||
@@ -333,7 +333,7 @@ class StoreDomainService:
|
||||
except dns.resolver.NXDOMAIN:
|
||||
raise DomainVerificationFailedException(
|
||||
domain.domain,
|
||||
f"DNS record _wizamart-verify.{domain.domain} not found",
|
||||
f"DNS record _orion-verify.{domain.domain} not found",
|
||||
)
|
||||
except dns.resolver.NoAnswer:
|
||||
raise DomainVerificationFailedException(
|
||||
@@ -382,7 +382,7 @@ class StoreDomainService:
|
||||
},
|
||||
"txt_record": {
|
||||
"type": "TXT",
|
||||
"name": "_wizamart-verify",
|
||||
"name": "_orion-verify",
|
||||
"value": domain.verification_token,
|
||||
"ttl": 3600,
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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 -->
|
||||
<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" />
|
||||
|
||||
@@ -514,7 +514,7 @@ class TestMerchantDomainServiceInstructions:
|
||||
assert "instructions" in instructions
|
||||
assert "txt_record" in instructions
|
||||
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
|
||||
|
||||
def test_get_verification_instructions_not_found(self, db):
|
||||
|
||||
@@ -411,7 +411,7 @@ class TestStoreDomainServiceInstructions:
|
||||
assert "instructions" in instructions
|
||||
assert "txt_record" in instructions
|
||||
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
|
||||
|
||||
def test_get_verification_instructions_not_found(self, db):
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<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 -->
|
||||
<link href="/static/shared/fonts/inter.css" rel="stylesheet" />
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
{# 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 #}
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
Wizamart
|
||||
Orion
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -190,7 +190,7 @@
|
||||
<span class="text-white font-bold text-xl">W</span>
|
||||
</div>
|
||||
<span class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
Wizamart
|
||||
Orion
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-sm">
|
||||
@@ -225,7 +225,7 @@
|
||||
</a>
|
||||
</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") }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Salt for key derivation - fixed to ensure consistent encryption/decryption
|
||||
# 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):
|
||||
|
||||
@@ -36,7 +36,7 @@ from main import app
|
||||
# Use environment variable or default to local Docker test database
|
||||
TEST_DATABASE_URL = os.getenv(
|
||||
"TEST_DATABASE_URL",
|
||||
"postgresql://test_user:test_password@localhost:5433/wizamart_test"
|
||||
"postgresql://test_user:test_password@localhost:5433/orion_test"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ services:
|
||||
image: postgres:15
|
||||
restart: "no"
|
||||
environment:
|
||||
POSTGRES_DB: wizamart_test
|
||||
POSTGRES_DB: orion_test
|
||||
POSTGRES_USER: test_user
|
||||
POSTGRES_PASSWORD: test_password
|
||||
ports:
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
tmpfs:
|
||||
- /var/lib/postgresql/data # Use RAM for faster tests
|
||||
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
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
@@ -4,8 +4,8 @@ services:
|
||||
image: postgres:15
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: wizamart_db
|
||||
POSTGRES_USER: wizamart_user
|
||||
POSTGRES_DB: orion_db
|
||||
POSTGRES_USER: orion_user
|
||||
POSTGRES_PASSWORD: secure_password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
@@ -13,7 +13,7 @@ services:
|
||||
ports:
|
||||
- "5432:5432"
|
||||
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
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
ports:
|
||||
- "8001:8000" # Use 8001 to avoid conflict with local dev server
|
||||
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}
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
USE_CELERY: "true"
|
||||
@@ -63,7 +63,7 @@ services:
|
||||
- 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
|
||||
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
|
||||
depends_on:
|
||||
db:
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## 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
|
||||
- **Store Portal** - Multi-tenant shop management
|
||||
|
||||
@@ -73,7 +73,7 @@ from app.exceptions import (
|
||||
|
||||
## 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
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@@ -15,7 +15,7 @@ For hands-on API exploration and testing, use our interactive documentation:
|
||||
|
||||
### Base URL
|
||||
```
|
||||
Production: https://wizamart.com/api/v1
|
||||
Production: https://orion.lu/api/v1
|
||||
Development: http://localhost:8000/api/v1
|
||||
```
|
||||
|
||||
@@ -182,4 +182,4 @@ X-RateLimit-Reset: 1640995200
|
||||
- **[Authentication Guide](authentication.md)** - Learn about API authentication
|
||||
- **[Error Handling](error-handling.md)** - Understanding API errors
|
||||
- **[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
|
||||
|
||||
@@ -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`:
|
||||
|
||||
1. **Browser makes API call** from storefront page (e.g., `/stores/wizamart/storefront/products`)
|
||||
2. **Browser automatically sends Referer header**: `http://localhost:8000/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/orion/storefront/products`
|
||||
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`
|
||||
6. **No store_id needed in URL!**
|
||||
|
||||
### Supported Store Detection Methods
|
||||
|
||||
- **Path-based**: `/stores/wizamart/storefront/products` → extracts `wizamart`
|
||||
- **Subdomain**: `wizamart.platform.com` → extracts `wizamart`
|
||||
- **Path-based**: `/stores/orion/storefront/products` → extracts `orion`
|
||||
- **Subdomain**: `orion.platform.com` → extracts `orion`
|
||||
- **Custom domain**: `customshop.com` → looks up store by domain
|
||||
|
||||
---
|
||||
@@ -92,7 +92,7 @@ Get paginated list of products for current store.
|
||||
|
||||
```http
|
||||
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):**
|
||||
@@ -142,7 +142,7 @@ Get detailed information for a specific product.
|
||||
|
||||
```http
|
||||
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):**
|
||||
@@ -200,7 +200,7 @@ Retrieve cart contents for a session.
|
||||
|
||||
```http
|
||||
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):**
|
||||
|
||||
@@ -99,7 +99,7 @@ Developers must remember:
|
||||
|
||||
### Broken Features
|
||||
|
||||
**Current Issue:** CMS pages not loading at `/stores/wizamart/about`
|
||||
**Current Issue:** CMS pages not loading at `/stores/orion/about`
|
||||
|
||||
**Root Cause:**
|
||||
- CMS API exists at `/api/v1/shop/content-pages/{slug}`
|
||||
|
||||
@@ -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
|
||||
|
||||
**How it works:**
|
||||
1. Browser JavaScript on `/stores/wizamart/shop/products` calls `/api/v1/shop/products`
|
||||
2. Browser automatically sends `Referer: http://localhost:8000/stores/wizamart/shop/products`
|
||||
3. Middleware extracts `wizamart` from Referer path
|
||||
1. Browser JavaScript on `/stores/orion/shop/products` calls `/api/v1/shop/products`
|
||||
2. Browser automatically sends `Referer: http://localhost:8000/stores/orion/shop/products`
|
||||
3. Middleware extracts `orion` from Referer path
|
||||
4. Queries database to get Store object
|
||||
5. Sets `request.state.store` for the API endpoint
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ def get_products(
|
||||
"sub": "user_id",
|
||||
"username": "john.doe",
|
||||
"store_id": 123, ← Store context
|
||||
"store_code": "WIZAMART", ← Store code
|
||||
"store_code": "ORION", ← Store code
|
||||
"store_role": "Owner" ← Store role
|
||||
}
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ This document defines the harmonized architecture for all background tasks in th
|
||||
|
||||
## 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 |
|
||||
|-----------|---------|------|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
|
||||
@@ -175,16 +175,16 @@ Customer → DNS → Server → Nginx → FastAPI
|
||||
|
||||
FastAPI Middleware:
|
||||
host = "store1.platform.com"
|
||||
|
||||
|
||||
Step 1: Custom domain? NO (ends with .platform.com)
|
||||
Step 2: Subdomain? YES
|
||||
Extract "store1"
|
||||
Query: SELECT * FROM stores
|
||||
Query: SELECT * FROM stores
|
||||
WHERE subdomain = 'store1'
|
||||
Result: Store 1
|
||||
|
||||
|
||||
request.state.store = Store 1
|
||||
|
||||
|
||||
Route → Render Store 1's shop
|
||||
```
|
||||
|
||||
@@ -195,18 +195,18 @@ Customer → localhost:8000/store/store1/
|
||||
FastAPI Middleware:
|
||||
host = "localhost:8000"
|
||||
path = "/store/store1/"
|
||||
|
||||
|
||||
Step 1: Custom domain? NO (localhost)
|
||||
Step 2: Subdomain? NO (localhost has no subdomain)
|
||||
Step 3: Path-based? YES
|
||||
Extract "store1" from path
|
||||
Query: SELECT * FROM stores
|
||||
Query: SELECT * FROM stores
|
||||
WHERE subdomain = 'store1'
|
||||
Result: Store 1
|
||||
|
||||
|
||||
request.state.store = Store 1
|
||||
request.state.clean_path = "/" (strip /store/store1)
|
||||
|
||||
|
||||
Route → Render Store 1's shop
|
||||
```
|
||||
|
||||
@@ -402,7 +402,7 @@ Value: 123.45.67.89
|
||||
TTL: 3600
|
||||
|
||||
Type: TXT
|
||||
Name: _wizamart-verify
|
||||
Name: _orion-verify
|
||||
Value: abc123xyz (verification token from your platform)
|
||||
TTL: 3600
|
||||
```
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
│ Exception Handler │
|
||||
│ app/exceptions/handler.py │
|
||||
│ │
|
||||
│ @app.exception_handler(WizamartException) │
|
||||
│ @app.exception_handler(OrionException) │
|
||||
│ async def custom_exception_handler(...): │
|
||||
│ return JSONResponse( │
|
||||
│ status_code=exc.status_code, │
|
||||
@@ -331,7 +331,7 @@ Step 2: Get Instructions
|
||||
┌────────────────────────────────────┐
|
||||
│ System returns instructions: │
|
||||
│ "Add TXT record: │
|
||||
│ _wizamart-verify.myshop.com │
|
||||
│ _orion-verify.myshop.com │
|
||||
│ Value: abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
@@ -343,7 +343,7 @@ Step 3: Store Adds DNS Record
|
||||
▼
|
||||
┌────────────────────────────────────┐
|
||||
│ DNS Provider (GoDaddy/etc) │
|
||||
│ _wizamart-verify.myshop.com TXT │
|
||||
│ _orion-verify.myshop.com TXT │
|
||||
│ "abc123..." │
|
||||
└────────────────────────────────────┘
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ The application serves multiple frontends from a single codebase:
|
||||
|----------|-------------|--------------|
|
||||
| **ADMIN** | Platform administration | `/admin/*`, `/api/v1/admin/*`, `admin.oms.lu/*` |
|
||||
| **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` |
|
||||
|
||||
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
|
||||
- /storefront/*, /shop/*, /stores/* → STOREFRONT
|
||||
- /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
|
||||
5. Default → PLATFORM
|
||||
```
|
||||
@@ -133,7 +133,7 @@ from app.modules.enums import FrontendType
|
||||
|
||||
# Full detection
|
||||
frontend_type = FrontendDetector.detect(
|
||||
host="wizamart.oms.lu",
|
||||
host="orion.oms.lu",
|
||||
path="/products",
|
||||
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 API | localhost | /api/v1/store/products | STORE |
|
||||
| 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 |
|
||||
|
||||
### Production Mode (domains)
|
||||
@@ -168,7 +168,7 @@ if FrontendDetector.is_storefront(host, path, has_store_context=True):
|
||||
| Request | Host | Path | Frontend |
|
||||
|---------|------|------|----------|
|
||||
| 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 |
|
||||
| Platform root | oms.lu | /pricing | PLATFORM |
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
|
||||
@@ -49,7 +49,7 @@ language = "lux" # ❌ Use "lb"
|
||||
Language is resolved in this order (highest to lowest priority):
|
||||
|
||||
1. **URL parameter** (`?lang=fr`)
|
||||
2. **Cookie** (`wizamart_language`)
|
||||
2. **Cookie** (`orion_language`)
|
||||
3. **User preference** (database: `preferred_language`)
|
||||
4. **Store default** (database: `storefront_language` or `dashboard_language`)
|
||||
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")
|
||||
|
||||
response.set_cookie(
|
||||
key="wizamart_language",
|
||||
key="orion_language",
|
||||
value=request.language,
|
||||
max_age=365 * 24 * 60 * 60, # 1 year
|
||||
httponly=True,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 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:**
|
||||
|
||||
@@ -26,7 +26,7 @@ graph TB
|
||||
AZ[Amazon<br/>API]
|
||||
EB[eBay<br/>API]
|
||||
CW[CodesWholesale<br/>Digital Supplier API]
|
||||
WS[Store Storefront<br/>Wizamart Shop]
|
||||
WS[Store Storefront<br/>Orion Shop]
|
||||
end
|
||||
|
||||
subgraph "Integration Layer"
|
||||
@@ -42,7 +42,7 @@ graph TB
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Wizamart Core"
|
||||
subgraph "Orion Core"
|
||||
MP[Marketplace Products]
|
||||
P[Store Products]
|
||||
O[Unified Orders]
|
||||
@@ -263,7 +263,7 @@ graph TB
|
||||
```python
|
||||
class OrderChannel(str, Enum):
|
||||
"""Order source channel."""
|
||||
STOREFRONT = "storefront" # Store's own Wizamart shop
|
||||
STOREFRONT = "storefront" # Store's own Orion shop
|
||||
LETZSHOP = "letzshop"
|
||||
AMAZON = "amazon"
|
||||
EBAY = "ebay"
|
||||
@@ -1325,7 +1325,7 @@ def map_codeswholesale_product(cw_product: dict) -> dict:
|
||||
|
||||
### Order Status Mapping
|
||||
|
||||
| Wizamart Status | Letzshop | Amazon | eBay |
|
||||
| Orion Status | Letzshop | Amazon | eBay |
|
||||
|-----------------|----------|--------|------|
|
||||
| PENDING | PENDING | Pending | - |
|
||||
| CONFIRMED | PAID | Unshipped | Paid |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 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)
|
||||
|
||||
@@ -103,11 +103,11 @@ INFO Response: 200 for GET /admin/dashboard (0.143s)
|
||||
|
||||
**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>
|
||||
request.state.store_id = 1
|
||||
@@ -141,7 +141,7 @@ Injects: request.state.store = <Store object>
|
||||
- /store/* or /api/v1/store/* → STORE
|
||||
- /storefront/*, /shop/*, /stores/* → STOREFRONT
|
||||
- /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
|
||||
5. Default → PLATFORM
|
||||
```
|
||||
@@ -165,8 +165,8 @@ Injects: request.state.store = <Store object>
|
||||
{
|
||||
"primary_color": "#3B82F6",
|
||||
"secondary_color": "#10B981",
|
||||
"logo_url": "/static/stores/wizamart/logo.png",
|
||||
"favicon_url": "/static/stores/wizamart/favicon.ico",
|
||||
"logo_url": "/static/stores/orion/logo.png",
|
||||
"favicon_url": "/static/stores/orion/favicon.ico",
|
||||
"custom_css": "/* store-specific styles */"
|
||||
}
|
||||
```
|
||||
@@ -376,7 +376,7 @@ async def get_products(request: Request):
|
||||
|
||||
### Example: Shop Product Page Request
|
||||
|
||||
**URL**: `https://wizamart.myplatform.com/shop/products`
|
||||
**URL**: `https://orion.myplatform.com/shop/products`
|
||||
|
||||
**Middleware Processing**:
|
||||
|
||||
@@ -386,9 +386,9 @@ async def get_products(request: Request):
|
||||
↓ Logs: "Request: GET /shop/products from 192.168.1.100"
|
||||
|
||||
2. StoreContextMiddleware
|
||||
↓ Detects subdomain: "wizamart"
|
||||
↓ Queries DB: store = get_store_by_code("wizamart")
|
||||
↓ Sets: request.state.store = <Store: Wizamart>
|
||||
↓ Detects subdomain: "orion"
|
||||
↓ Queries DB: store = get_store_by_code("orion")
|
||||
↓ Sets: request.state.store = <Store: Orion>
|
||||
↓ Sets: request.state.store_id = 1
|
||||
↓ Sets: request.state.clean_path = "/shop/products"
|
||||
|
||||
@@ -496,13 +496,13 @@ from middleware.store_context import StoreContextManager
|
||||
|
||||
def test_store_detection_subdomain():
|
||||
# Mock request
|
||||
request = create_mock_request(host="wizamart.platform.com")
|
||||
request = create_mock_request(host="orion.platform.com")
|
||||
|
||||
# Test detection
|
||||
manager = StoreContextManager()
|
||||
store = manager.detect_store_from_subdomain(request)
|
||||
|
||||
assert store.code == "wizamart"
|
||||
assert store.code == "orion"
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
@@ -513,11 +513,11 @@ Test the full middleware stack:
|
||||
def test_shop_request_flow(client):
|
||||
response = client.get(
|
||||
"/shop/products",
|
||||
headers={"Host": "wizamart.platform.com"}
|
||||
headers={"Host": "orion.platform.com"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Wizamart" in response.text
|
||||
assert "Orion" in response.text
|
||||
```
|
||||
|
||||
**See**: [Testing Guide](../testing/testing-guide.md)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@@ -1029,11 +1029,11 @@ __all__ = ["process_import", "export_products"]
|
||||
|
||||
### Exceptions
|
||||
|
||||
Module-specific exceptions inherit from `WizamartException`.
|
||||
Module-specific exceptions inherit from `OrionException`.
|
||||
|
||||
| Location | Base Class | Usage |
|
||||
|----------|------------|-------|
|
||||
| `exceptions.py` | `WizamartException` | Domain errors |
|
||||
| `exceptions.py` | `OrionException` | Domain errors |
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
@@ -1044,9 +1044,9 @@ app/modules/{module}/
|
||||
**Example:**
|
||||
```python
|
||||
# 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."""
|
||||
pass
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## 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
|
||||
|
||||
@@ -71,7 +71,7 @@ CREATE TABLE platforms (
|
||||
code VARCHAR(50) UNIQUE NOT NULL, -- 'oms', 'loyalty', 'sitebuilder'
|
||||
name VARCHAR(100) NOT NULL, -- 'Order Management System'
|
||||
description TEXT,
|
||||
domain VARCHAR(255), -- 'oms.wizamart.lu'
|
||||
domain VARCHAR(255), -- 'oms.orion.lu'
|
||||
path_prefix VARCHAR(50), -- '/oms'
|
||||
logo 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
|
||||
|
||||
**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
|
||||
|
||||
### Request Processing
|
||||
@@ -284,6 +284,6 @@ Request: GET /about
|
||||
|
||||
| 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/` |
|
||||
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
|
||||
|
||||
@@ -4,7 +4,7 @@ Complete guide to the multi-tenant architecture supporting custom domains, subdo
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -148,15 +148,15 @@ For path-based routing, clean paths are extracted:
|
||||
|
||||
**Path-Based Shop Routes** (Development):
|
||||
```
|
||||
Original: /stores/WIZAMART/shop/products
|
||||
Extracted: store_code = "WIZAMART"
|
||||
Original: /stores/ORION/shop/products
|
||||
Extracted: store_code = "ORION"
|
||||
Clean: /shop/products
|
||||
```
|
||||
|
||||
**Store Dashboard Routes** (All environments):
|
||||
```
|
||||
Original: /store/WIZAMART/dashboard
|
||||
Extracted: store_code = "WIZAMART"
|
||||
Original: /store/ORION/dashboard
|
||||
Extracted: store_code = "ORION"
|
||||
Clean: /dashboard
|
||||
```
|
||||
|
||||
@@ -197,13 +197,13 @@ CREATE TABLE store_domains (
|
||||
```sql
|
||||
-- Stores
|
||||
INSERT INTO stores (code, name) VALUES
|
||||
('wizamart', 'Wizamart Shop'),
|
||||
('orion', 'Orion Shop'),
|
||||
('techstore', 'Tech Store'),
|
||||
('fashionhub', 'Fashion Hub');
|
||||
|
||||
-- Custom Domains
|
||||
INSERT INTO store_domains (store_id, domain) VALUES
|
||||
(1, 'wizamart.com'),
|
||||
(1, 'orion.lu'),
|
||||
(2, 'mytechstore.net');
|
||||
```
|
||||
|
||||
@@ -421,16 +421,16 @@ Host: customdomain.com
|
||||
**Request**:
|
||||
```http
|
||||
GET /shop/products HTTP/1.1
|
||||
Host: wizamart.myplatform.com
|
||||
Host: orion.myplatform.com
|
||||
```
|
||||
|
||||
**Processing**:
|
||||
```
|
||||
1. StoreContextMiddleware
|
||||
- Checks: host != "myplatform.com"
|
||||
- Extracts: subdomain = "wizamart"
|
||||
- Queries: stores WHERE code = "wizamart"
|
||||
- Sets: request.state.store = <Store "wizamart">
|
||||
- Extracts: subdomain = "orion"
|
||||
- Queries: stores WHERE code = "orion"
|
||||
- Sets: request.state.store = <Store "orion">
|
||||
|
||||
2-4. Same as Example 1
|
||||
```
|
||||
@@ -439,7 +439,7 @@ Host: wizamart.myplatform.com
|
||||
|
||||
**Request**:
|
||||
```http
|
||||
GET /stores/WIZAMART/shop/products HTTP/1.1
|
||||
GET /stores/ORION/shop/products HTTP/1.1
|
||||
Host: myplatform.com
|
||||
```
|
||||
|
||||
@@ -447,15 +447,15 @@ Host: myplatform.com
|
||||
```
|
||||
1. StoreContextMiddleware
|
||||
- Checks: path starts with "/store/"
|
||||
- Extracts: code = "WIZAMART"
|
||||
- Queries: stores WHERE code = "WIZAMART"
|
||||
- Extracts: code = "ORION"
|
||||
- Queries: stores WHERE code = "ORION"
|
||||
- Sets: request.state.store = <Store>
|
||||
- Sets: request.state.clean_path = "/shop/products"
|
||||
|
||||
2. FastAPI Router
|
||||
- Routes registered with prefix: /stores/{store_code}/shop
|
||||
- Matches: /stores/WIZAMART/shop/products
|
||||
- store_code path parameter = "WIZAMART"
|
||||
- Matches: /stores/ORION/shop/products
|
||||
- store_code path parameter = "ORION"
|
||||
|
||||
3-4. Same as previous examples (Context, Theme middleware)
|
||||
```
|
||||
@@ -491,9 +491,9 @@ def test_shop_page_multi_tenant(client):
|
||||
# Test subdomain routing
|
||||
response = client.get(
|
||||
"/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
|
||||
response = client.get(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
- **Store** - Store dashboard for managing shops
|
||||
- **Shop** - Customer-facing storefronts
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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
|
||||
|
||||
@@ -42,8 +42,8 @@ graph TB
|
||||
|
||||
```http
|
||||
# Shop page request (subdomain mode)
|
||||
GET https://wizamart.platform.com/shop/products
|
||||
Host: wizamart.platform.com
|
||||
GET https://orion.platform.com/shop/products
|
||||
Host: orion.platform.com
|
||||
|
||||
# API request
|
||||
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
|
||||
# Input
|
||||
host = "wizamart.platform.com"
|
||||
host = "orion.platform.com"
|
||||
path = "/shop/products"
|
||||
|
||||
# Detection logic
|
||||
if host != settings.platform_domain:
|
||||
# Subdomain detected
|
||||
store_code = host.split('.')[0] # "wizamart"
|
||||
store_code = host.split('.')[0] # "orion"
|
||||
|
||||
# Query database
|
||||
store = db.query(Store).filter(
|
||||
@@ -107,7 +107,7 @@ if host != settings.platform_domain:
|
||||
|
||||
**Request State After**:
|
||||
```python
|
||||
request.state.store = <Store: Wizamart>
|
||||
request.state.store = <Store: Orion>
|
||||
request.state.store_id = 1
|
||||
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="/stores/{store_code}/shop")
|
||||
|
||||
# Request: /stores/WIZAMART/shop/products
|
||||
# Request: /stores/ORION/shop/products
|
||||
# Matches: Second router (/stores/{store_code}/shop)
|
||||
# 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.
|
||||
@@ -194,7 +194,7 @@ if hasattr(request.state, 'store_id'):
|
||||
request.state.theme = {
|
||||
"primary_color": "#3B82F6",
|
||||
"secondary_color": "#10B981",
|
||||
"logo_url": "/static/stores/wizamart/logo.png",
|
||||
"logo_url": "/static/stores/orion/logo.png",
|
||||
"custom_css": "..."
|
||||
}
|
||||
```
|
||||
@@ -289,7 +289,7 @@ async def shop_products_page(
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Wizamart - Products</title>
|
||||
<title>Orion - Products</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #3B82F6;
|
||||
@@ -298,7 +298,7 @@ async def shop_products_page(
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Wizamart Shop</h1>
|
||||
<h1>Orion Shop</h1>
|
||||
<div class="products">
|
||||
<div class="product-card">
|
||||
<h2>Product 1</h2>
|
||||
@@ -412,7 +412,7 @@ sequenceDiagram
|
||||
Logging->>Store: Pass request
|
||||
Store->>DB: Query store by subdomain
|
||||
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
|
||||
Note over Path: Path already clean
|
||||
Path->>Context: Pass request
|
||||
@@ -443,14 +443,14 @@ Initial State: {}
|
||||
store_id: 1,
|
||||
clean_path: "/shop/products"
|
||||
}
|
||||
|
||||
|
||||
After FrontendTypeMiddleware:
|
||||
{
|
||||
store: <Store: Orion>,
|
||||
store_id: 1,
|
||||
clean_path: "/shop/products",
|
||||
frontend_type: FrontendType.STOREFRONT
|
||||
}
|
||||
}
|
||||
|
||||
After ThemeContextMiddleware:
|
||||
{
|
||||
@@ -458,14 +458,14 @@ After FrontendTypeMiddleware:
|
||||
store_id: 1,
|
||||
clean_path: "/shop/products",
|
||||
frontend_type: FrontendType.STOREFRONT,
|
||||
theme: {
|
||||
theme: {
|
||||
primary_color: "#3B82F6",
|
||||
secondary_color: "#10B981",
|
||||
logo_url: "/static/stores/orion/logo.png",
|
||||
custom_css: "..."
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Wizamart Multi-Tenant URL Routing Guide
|
||||
# Orion Multi-Tenant URL Routing Guide
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -13,8 +13,8 @@ There are three ways depending on the deployment mode:
|
||||
https://STORE_SUBDOMAIN.platform.com/storefront/products
|
||||
|
||||
Example:
|
||||
https://acme.wizamart.com/storefront/products
|
||||
https://techpro.wizamart.com/storefront/categories/electronics
|
||||
https://acme.orion.lu/storefront/products
|
||||
https://techpro.orion.lu/storefront/categories/electronics
|
||||
```
|
||||
|
||||
### 2. **CUSTOM DOMAIN MODE** (Production - Premium)
|
||||
@@ -39,7 +39,7 @@ http://localhost:8000/platforms/loyalty/stores/techpro/storefront/checkout
|
||||
|
||||
## 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
|
||||
|
||||
@@ -61,8 +61,8 @@ Wizamart supports multiple platforms (OMS, Loyalty, Site Builder), each with its
|
||||
|
||||
| URL | What it serves |
|
||||
|-----|----------------|
|
||||
| `wizamart.lu/` | Main marketing site homepage |
|
||||
| `wizamart.lu/about` | Main marketing site about page |
|
||||
| `orion.lu/` | Main marketing site homepage |
|
||||
| `orion.lu/about` | Main marketing site about page |
|
||||
| `oms.lu/` | OMS platform homepage |
|
||||
| `oms.lu/pricing` | OMS platform pricing page |
|
||||
| `oms.lu/admin/` | Admin panel for OMS platform |
|
||||
@@ -138,7 +138,7 @@ Request arrives
|
||||
|
||||
| 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` |
|
||||
| Loyalty | `loyalty` | `localhost:8000/platforms/loyalty/` | `loyalty.lu` |
|
||||
| Site Builder | `site-builder` | `localhost:8000/platforms/site-builder/` | `sitebuilder.lu` |
|
||||
@@ -155,12 +155,12 @@ Request arrives
|
||||
|
||||
**Example:**
|
||||
- Store subdomain: `acme`
|
||||
- Platform domain: `wizamart.com`
|
||||
- Customer Storefront URL: `https://acme.wizamart.com/storefront/products`
|
||||
- Product Detail: `https://acme.wizamart.com/storefront/products/123`
|
||||
- Platform domain: `orion.lu`
|
||||
- Customer Storefront URL: `https://acme.orion.lu/storefront/products`
|
||||
- Product Detail: `https://acme.orion.lu/storefront/products/123`
|
||||
|
||||
**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"`
|
||||
3. Queries: `SELECT * FROM stores WHERE subdomain = 'acme'`
|
||||
4. Finds Store with ID=1 (ACME Store)
|
||||
@@ -171,7 +171,7 @@ Request arrives
|
||||
9. Renders template with ACME's colors, logo, and products
|
||||
|
||||
**Advantages:**
|
||||
- Single SSL certificate for all stores (*.wizamart.com)
|
||||
- Single SSL certificate for all stores (*.orion.lu)
|
||||
- Easy to manage DNS (just add subdomains)
|
||||
- Customers don't need to bring their own domain
|
||||
|
||||
@@ -199,7 +199,7 @@ id | store_id | domain | is_active | is_verified
|
||||
|
||||
**How It Works:**
|
||||
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"`
|
||||
4. Queries: `SELECT * FROM store_domains WHERE domain = 'store.acme-corp.com'`
|
||||
5. Finds `StoreDomain` with `store_id = 1`
|
||||
@@ -249,17 +249,17 @@ id | store_id | domain | is_active | is_verified
|
||||
|
||||
### Subdomain/Custom Domain (PRODUCTION)
|
||||
```
|
||||
https://acme.wizamart.com/storefront/ → Homepage
|
||||
https://acme.wizamart.com/storefront/products → Product Catalog
|
||||
https://acme.wizamart.com/storefront/products/123 → Product Detail
|
||||
https://acme.wizamart.com/storefront/categories/electronics → Category Page
|
||||
https://acme.wizamart.com/storefront/cart → Shopping Cart
|
||||
https://acme.wizamart.com/storefront/checkout → Checkout
|
||||
https://acme.wizamart.com/storefront/search?q=laptop → Search Results
|
||||
https://acme.wizamart.com/storefront/account/login → Customer Login
|
||||
https://acme.wizamart.com/storefront/account/dashboard → Account Dashboard (Auth Required)
|
||||
https://acme.wizamart.com/storefront/account/orders → Order History (Auth Required)
|
||||
https://acme.wizamart.com/storefront/account/profile → Profile (Auth Required)
|
||||
https://acme.orion.lu/storefront/ → Homepage
|
||||
https://acme.orion.lu/storefront/products → Product Catalog
|
||||
https://acme.orion.lu/storefront/products/123 → Product Detail
|
||||
https://acme.orion.lu/storefront/categories/electronics → Category Page
|
||||
https://acme.orion.lu/storefront/cart → Shopping Cart
|
||||
https://acme.orion.lu/storefront/checkout → Checkout
|
||||
https://acme.orion.lu/storefront/search?q=laptop → Search Results
|
||||
https://acme.orion.lu/storefront/account/login → Customer Login
|
||||
https://acme.orion.lu/storefront/account/dashboard → Account Dashboard (Auth Required)
|
||||
https://acme.orion.lu/storefront/account/orders → Order History (Auth Required)
|
||||
https://acme.orion.lu/storefront/account/profile → Profile (Auth Required)
|
||||
```
|
||||
|
||||
### Path-Based (DEVELOPMENT)
|
||||
@@ -304,7 +304,7 @@ POST /api/v1/storefront/stores/1/products/{id}/reviews → Add product review
|
||||
|
||||
### Example: No Cross-Store Leakage
|
||||
```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
|
||||
|
||||
# Backend checks:
|
||||
@@ -317,14 +317,14 @@ if store.id != requested_store_id: # if 1 != 2
|
||||
|
||||
## 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 │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
method: GET
|
||||
host: acme.wizamart.com
|
||||
host: acme.orion.lu
|
||||
path: /storefront/products
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
@@ -332,7 +332,7 @@ if store.id != requested_store_id: # if 1 != 2
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
A) store_context_middleware
|
||||
├─ Detects host: "acme.wizamart.com"
|
||||
├─ Detects host: "acme.orion.lu"
|
||||
├─ Extracts subdomain: "acme"
|
||||
├─ Queries: SELECT * FROM stores WHERE subdomain = 'acme'
|
||||
└─ 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:
|
||||
|
||||
```python
|
||||
# Theme loaded for https://acme.wizamart.com
|
||||
# Theme loaded for https://acme.orion.lu
|
||||
request.state.theme = {
|
||||
"theme_name": "modern",
|
||||
"colors": {
|
||||
@@ -438,7 +438,7 @@ In Jinja2 template:
|
||||
- Each store looks completely separate and branded
|
||||
|
||||
### 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`
|
||||
- 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:**
|
||||
|
||||
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
|
||||
- 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
|
||||
Wizamart Version: Current Development
|
||||
Orion Version: Current Development
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**Last Updated:** 2026-01-28
|
||||
**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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -42,15 +42,15 @@ Transform the single-platform OMS into a multi-platform system supporting indepe
|
||||
|
||||
```bash
|
||||
# 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
|
||||
alembic upgrade head
|
||||
|
||||
# 3. Verify migration
|
||||
psql -d wizamart -c "SELECT * FROM platforms;"
|
||||
psql -d wizamart -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 * FROM platforms;"
|
||||
psql -d orion -c "SELECT COUNT(*) FROM store_platforms;"
|
||||
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
|
||||
@@ -78,7 +78,7 @@ Changes completed:
|
||||
File: `app/routes/platform/homepage.py`
|
||||
|
||||
Current state (BROKEN):
|
||||
- Uses hardcoded `homepage-wizamart.html` template
|
||||
- Uses hardcoded `homepage-orion.html` template
|
||||
- Admin CMS changes are saved but ignored by route
|
||||
|
||||
Fix required:
|
||||
@@ -101,7 +101,7 @@ if page:
|
||||
)
|
||||
else:
|
||||
# Fallback to hardcoded (temporary)
|
||||
return templates.TemplateResponse("platform/homepage-wizamart.html", {...})
|
||||
return templates.TemplateResponse("platform/homepage-orion.html", {...})
|
||||
```
|
||||
|
||||
### 2.5 Update Content Page Routes
|
||||
@@ -255,7 +255,7 @@ Inserts Loyalty platform with:
|
||||
|
||||
| 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/` |
|
||||
| Loyalty | `loyalty` | `localhost:9999/platforms/loyalty/` | `loyalty.lu/` |
|
||||
|
||||
@@ -324,7 +324,7 @@ Included in `docs/architecture/multi-platform-cms.md`:
|
||||
### 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
|
||||
# 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
|
||||
|
||||
# 2. Test main marketing site
|
||||
|
||||
@@ -23,7 +23,7 @@ The platform is evolving from a single OMS product to a **multi-platform busines
|
||||
| Issue | Description |
|
||||
|-------|-------------|
|
||||
| **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 |
|
||||
| **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 |
|
||||
@@ -48,7 +48,7 @@ ContentPage (store_id = NULL)
|
||||
│ PLATFORM LEVEL │
|
||||
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
|
||||
│ │ Platform A │ │ Platform B │ │ Platform C │ │
|
||||
│ │ (Wizamart OMS) │ │ (Loyalty+) │ │ (Site Builder) │ │
|
||||
│ │ (Orion OMS) │ │ (Loyalty+) │ │ (Site Builder) │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ • Homepage │ │ • Homepage │ │ • Homepage │ │
|
||||
│ │ • About │ │ • About │ │ • About │ │
|
||||
@@ -76,7 +76,7 @@ ContentPage (store_id = NULL)
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ STORE LEVEL (isolated) │
|
||||
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ Store 1 (WizaMart) │ │ Store 2 (TechStore) │ │
|
||||
│ │ Store 1 (Orion) │ │ Store 2 (TechStore) │ │
|
||||
│ │ Platform A, Tier: Pro │ │ Platform A, Tier: Basic │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ Override Pages: │ │ Override Pages: │ │
|
||||
@@ -114,14 +114,14 @@ When a customer visits `store1.example.com/about`:
|
||||
class Platform(Base):
|
||||
"""
|
||||
Represents a business offering/product line.
|
||||
Examples: Wizamart OMS, Loyalty+, Site Builder
|
||||
Examples: Orion OMS, Loyalty+, Site Builder
|
||||
"""
|
||||
__tablename__ = "platforms"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
code = Column(String(50), unique=True, nullable=False) # "oms", "loyalty", "sites"
|
||||
name = Column(String(100), nullable=False) # "Wizamart OMS"
|
||||
domain = Column(String(255), nullable=True) # "wizamart.lu"
|
||||
name = Column(String(100), nullable=False) # "Orion OMS"
|
||||
domain = Column(String(255), nullable=True) # "orion.lu"
|
||||
|
||||
# Branding
|
||||
logo = Column(String(500), nullable=True)
|
||||
@@ -219,8 +219,8 @@ class Store(Base):
|
||||
|-----------|:-----------:|:---------:|:----------------:|---------|
|
||||
| Platform Marketing Page | ✓ | NULL | TRUE | Platform A's homepage, pricing |
|
||||
| Store Default Page | ✓ | NULL | FALSE | Generic "About Our Store" template |
|
||||
| Store Override Page | ✓ | ✓ | FALSE | WizaMart's custom About page |
|
||||
| Store Custom Page | ✓ | ✓ | FALSE | WizaMart's "Store Locations" page |
|
||||
| Store Override Page | ✓ | ✓ | FALSE | Orion's custom About page |
|
||||
| Store Custom Page | ✓ | ✓ | FALSE | Orion's "Store Locations" page |
|
||||
|
||||
---
|
||||
|
||||
@@ -237,7 +237,7 @@ class Store(Base):
|
||||
3. Admin fills in:
|
||||
- Code: "loyalty"
|
||||
- Name: "Loyalty+"
|
||||
- Domain: "loyalty.wizamart.lu"
|
||||
- Domain: "loyalty.orion.lu"
|
||||
- Logo, theme colors
|
||||
4. Admin saves platform
|
||||
5. System creates platform record
|
||||
@@ -250,7 +250,7 @@ class Store(Base):
|
||||
- Contact (is_platform_page=True)
|
||||
8. Each page can use different templates (modern, minimal, etc.)
|
||||
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
|
||||
@@ -308,7 +308,7 @@ class Store(Base):
|
||||
|
||||
### Journey 4: Store Overrides a Default Page
|
||||
|
||||
**Actor:** Store (WizaMart)
|
||||
**Actor:** Store (Orion)
|
||||
**Goal:** Customize the About page with store-specific content
|
||||
|
||||
```
|
||||
@@ -318,21 +318,21 @@ class Store(Base):
|
||||
4. Clicks "Override" button
|
||||
5. System creates a copy with store_id set
|
||||
6. Store edits content:
|
||||
- Title: "About WizaMart"
|
||||
- Content: "WizaMart was founded in 2020 in Luxembourg..."
|
||||
- Title: "About Orion"
|
||||
- Content: "Orion was founded in 2020 in Luxembourg..."
|
||||
- Adds store images
|
||||
7. Store saves and publishes
|
||||
8. Page list now shows:
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ About Us │ Custom Override │ Published │ Edit │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
9. Customer visits wizamart.example.com/about
|
||||
10. Sees WizaMart's custom About page
|
||||
9. Customer visits orion.example.com/about
|
||||
10. Sees Orion's custom About 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
|
||||
|
||||
```
|
||||
@@ -346,12 +346,12 @@ class Store(Base):
|
||||
- Show in footer: Yes
|
||||
5. Store saves and publishes
|
||||
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
|
||||
|
||||
**Actor:** Store (WizaMart)
|
||||
**Actor:** Store (Orion)
|
||||
**Goal:** Remove customization and use platform default again
|
||||
|
||||
```
|
||||
@@ -372,19 +372,19 @@ class Store(Base):
|
||||
**Goal:** Read store policies before purchasing
|
||||
|
||||
```
|
||||
1. Customer visits wizamart.example.com
|
||||
1. Customer visits orion.example.com
|
||||
2. Browses products, adds to cart
|
||||
3. Wants to check return policy
|
||||
4. Clicks "Returns" in footer
|
||||
5. System resolves page:
|
||||
- Check: WizaMart override? NO
|
||||
- Check: Orion override? NO
|
||||
- Check: Platform A default? YES
|
||||
- Serve: Platform A default "Return Policy" page
|
||||
6. Customer reads return policy
|
||||
7. Customer clicks "About Us" in footer
|
||||
8. System resolves page:
|
||||
- Check: WizaMart override? YES
|
||||
- Serve: WizaMart's custom "About WizaMart" page
|
||||
- Check: Orion override? YES
|
||||
- Serve: Orion's custom "About Orion" page
|
||||
9. Customer sees store-specific About page
|
||||
```
|
||||
|
||||
@@ -539,7 +539,7 @@ GET /api/v1/platform/{code}/pages/navigation # Get platform nav
|
||||
```sql
|
||||
-- 1. Create default platform
|
||||
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
|
||||
UPDATE content_pages
|
||||
@@ -567,7 +567,7 @@ SET platform_id = (SELECT id FROM platforms WHERE code = 'oms');
|
||||
## Open Questions
|
||||
|
||||
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 C: Subdomain detection
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ from models.database.base import TimestampMixin
|
||||
class StoreDomain(Base, TimestampMixin):
|
||||
"""
|
||||
Custom domain mapping for stores.
|
||||
|
||||
|
||||
Allows stores to use their own domains (e.g., myshop.com)
|
||||
instead of subdomains (store1.platform.com).
|
||||
"""
|
||||
@@ -103,11 +103,11 @@ class StoreDomain(Base, TimestampMixin):
|
||||
|
||||
# Primary Key
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
|
||||
# Foreign Keys
|
||||
store_id = Column(
|
||||
Integer,
|
||||
ForeignKey("stores.id", ondelete="CASCADE"),
|
||||
Integer,
|
||||
ForeignKey("stores.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
@@ -116,12 +116,12 @@ class StoreDomain(Base, TimestampMixin):
|
||||
domain = Column(String(255), nullable=False, unique=True, index=True)
|
||||
is_primary = Column(Boolean, default=False, nullable=False)
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
|
||||
# Verification
|
||||
is_verified = Column(Boolean, default=False, nullable=False)
|
||||
verification_token = Column(String(100), unique=True, nullable=True)
|
||||
verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
|
||||
# SSL Status
|
||||
ssl_status = Column(String(50), default="pending")
|
||||
ssl_verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
@@ -171,9 +171,9 @@ class StoreDomain(Base, TimestampMixin):
|
||||
|
||||
class Store(Base, TimestampMixin):
|
||||
__tablename__ = "stores"
|
||||
|
||||
|
||||
# ... existing fields ...
|
||||
|
||||
|
||||
# Add relationship
|
||||
domains = relationship(
|
||||
"StoreDomain",
|
||||
@@ -181,7 +181,7 @@ class Store(Base, TimestampMixin):
|
||||
cascade="all, delete-orphan",
|
||||
order_by="StoreDomain.is_primary.desc()"
|
||||
)
|
||||
|
||||
|
||||
# Helper method
|
||||
@property
|
||||
def primary_domain(self):
|
||||
@@ -270,10 +270,10 @@ class StoreDomainCreate(BaseModel):
|
||||
"""Validate and normalize domain."""
|
||||
# Remove protocol if present
|
||||
domain = v.replace("https://", "").replace("http://", "")
|
||||
|
||||
|
||||
# Remove trailing slash
|
||||
domain = domain.rstrip("/")
|
||||
|
||||
|
||||
# Convert to lowercase
|
||||
domain = domain.lower().strip()
|
||||
|
||||
@@ -331,7 +331,7 @@ class StoreDomainResponse(BaseModel):
|
||||
|
||||
class StoreDomainListResponse(BaseModel):
|
||||
"""Schema for paginated store domain list."""
|
||||
|
||||
|
||||
domains: List[StoreDomainResponse]
|
||||
total: int
|
||||
|
||||
@@ -535,7 +535,7 @@ class DNSVerificationException(ExternalServiceException):
|
||||
| 409 | `ConflictException` | Resource conflicts |
|
||||
| 422 | `ValidationException` | Input validation errors |
|
||||
| 429 | `RateLimitException` | Rate limiting |
|
||||
| 500 | `WizamartException` | Generic errors |
|
||||
| 500 | `OrionException` | Generic errors |
|
||||
| 502 | `ExternalServiceException` | Third-party failures |
|
||||
|
||||
### Step 2: Update Exception Exports
|
||||
@@ -561,7 +561,7 @@ from .store_domain import (
|
||||
|
||||
__all__ = [
|
||||
# ... existing exports ...
|
||||
|
||||
|
||||
# Store Domain
|
||||
"StoreDomainNotFoundException",
|
||||
"StoreDomainAlreadyExistsException",
|
||||
@@ -1133,7 +1133,7 @@ def delete_store_domain(
|
||||
Delete a custom domain (Admin only).
|
||||
|
||||
**Warning:** This is permanent and cannot be undone.
|
||||
|
||||
|
||||
**Raises:**
|
||||
- 404: Domain not found
|
||||
"""
|
||||
@@ -1230,7 +1230,7 @@ async def admin_store_domains_page(
|
||||
Manage custom domains for {{ store_code }}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
<button
|
||||
onclick="openAddDomainModal()"
|
||||
class="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700">
|
||||
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="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>
|
||||
|
||||
|
||||
<form id="addDomainForm">
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium mb-2">Domain</label>
|
||||
<input
|
||||
type="text"
|
||||
<input
|
||||
type="text"
|
||||
name="domain"
|
||||
placeholder="myshop.com"
|
||||
class="w-full px-3 py-2 border rounded-lg"
|
||||
required>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="flex items-center">
|
||||
<input type="checkbox" name="is_primary" class="mr-2">
|
||||
<span class="text-sm">Set as primary domain</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
<button
|
||||
type="submit"
|
||||
class="flex-1 bg-purple-600 text-white py-2 rounded-lg hover:bg-purple-700">
|
||||
Add Domain
|
||||
</button>
|
||||
<button
|
||||
<button
|
||||
type="button"
|
||||
onclick="closeAddDomainModal()"
|
||||
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')}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const store = await response.json();
|
||||
storeId = store.id;
|
||||
@@ -1317,14 +1317,14 @@ async function loadStoreId() {
|
||||
|
||||
async function loadDomains() {
|
||||
if (!storeId) return;
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
renderDomains(data.domains);
|
||||
@@ -1336,12 +1336,12 @@ async function loadDomains() {
|
||||
|
||||
function renderDomains(domains) {
|
||||
const container = document.getElementById('domainsList');
|
||||
|
||||
|
||||
if (domains.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500">No domains added yet.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
container.innerHTML = domains.map(domain => `
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -1358,13 +1358,13 @@ function renderDomains(domains) {
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
${!domain.is_verified ? `
|
||||
<button
|
||||
<button
|
||||
onclick="verifyDomain(${domain.id})"
|
||||
class="px-3 py-1 bg-green-600 text-white text-sm rounded hover:bg-green-700">
|
||||
Verify
|
||||
</button>
|
||||
` : ''}
|
||||
<button
|
||||
<button
|
||||
onclick="deleteDomain(${domain.id})"
|
||||
class="px-3 py-1 bg-red-600 text-white text-sm rounded hover:bg-red-700">
|
||||
Delete
|
||||
@@ -1386,13 +1386,13 @@ function closeAddDomainModal() {
|
||||
|
||||
document.getElementById('addDomainForm').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
||||
const formData = new FormData(e.target);
|
||||
const data = {
|
||||
domain: formData.get('domain'),
|
||||
is_primary: formData.get('is_primary') === 'on'
|
||||
};
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/stores/${storeId}/domains`, {
|
||||
method: 'POST',
|
||||
@@ -1402,7 +1402,7 @@ document.getElementById('addDomainForm').addEventListener('submit', async (e) =>
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
closeAddDomainModal();
|
||||
await loadDomains();
|
||||
@@ -1425,7 +1425,7 @@ async function verifyDomain(domainId) {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
await loadDomains();
|
||||
alert('Domain verified successfully!');
|
||||
@@ -1441,7 +1441,7 @@ async function verifyDomain(domainId) {
|
||||
|
||||
async function deleteDomain(domainId) {
|
||||
if (!confirm('Are you sure you want to delete this domain?')) return;
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/admin/stores/domains/${domainId}`, {
|
||||
method: 'DELETE',
|
||||
@@ -1449,7 +1449,7 @@ async function deleteDomain(domainId) {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (response.ok) {
|
||||
await loadDomains();
|
||||
alert('Domain deleted successfully!');
|
||||
@@ -1497,9 +1497,9 @@ def test_add_domain_success(db_session, test_store, service):
|
||||
domain="test.com",
|
||||
is_primary=True
|
||||
)
|
||||
|
||||
|
||||
domain = service.add_domain(db_session, test_store.id, domain_data)
|
||||
|
||||
|
||||
assert domain.domain == "test.com"
|
||||
assert domain.is_primary is True
|
||||
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):
|
||||
"""Test adding duplicate domain raises exception."""
|
||||
domain_data = StoreDomainCreate(domain=existing_domain.domain)
|
||||
|
||||
|
||||
with pytest.raises(StoreDomainAlreadyExistsException):
|
||||
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):
|
||||
domain_data = StoreDomainCreate(domain=f"test{i}.com")
|
||||
service.add_domain(db_session, test_store.id, domain_data)
|
||||
|
||||
|
||||
# Try adding one more
|
||||
domain_data = StoreDomainCreate(domain="overflow.com")
|
||||
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},
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
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
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 422
|
||||
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",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
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}",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 200
|
||||
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",
|
||||
headers=admin_headers
|
||||
)
|
||||
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json()["error_code"] == "STORE_DOMAIN_NOT_FOUND"
|
||||
```
|
||||
@@ -1794,6 +1794,6 @@ For questions or issues:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-01-15
|
||||
**Version:** 1.0
|
||||
**Last Updated:** 2025-01-15
|
||||
**Version:** 1.0
|
||||
**Maintainer:** Development Team
|
||||
|
||||
@@ -4,7 +4,7 @@ Guide for developing backend features, services, and API endpoints.
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ def get_product(
|
||||
│ "sub": "user_id", │
|
||||
│ "username": "john.doe", │
|
||||
│ "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 │
|
||||
│ } │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
@@ -436,14 +436,14 @@ def test_store_in_token():
|
||||
token_data = auth_manager.create_access_token(
|
||||
user=user,
|
||||
store_id=123,
|
||||
store_code="WIZAMART",
|
||||
store_code="ORION",
|
||||
store_role="Owner",
|
||||
)
|
||||
|
||||
# Verify token contains store data
|
||||
payload = jwt.decode(token_data["access_token"], secret_key)
|
||||
assert payload["store_id"] == 123
|
||||
assert payload["store_code"] == "WIZAMART"
|
||||
assert payload["store_code"] == "ORION"
|
||||
assert payload["store_role"] == "Owner"
|
||||
|
||||
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 │
|
||||
│ │
|
||||
│ ✅ Catches all WizamartException subclasses │
|
||||
│ ✅ Catches all OrionException subclasses │
|
||||
│ ✅ Converts to appropriate HTTP responses │
|
||||
│ ✅ Provides consistent error formatting │
|
||||
└────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
@@ -607,7 +607,7 @@ HTTP 403 Forbidden
|
||||
"message": "You don't have permission to perform this action",
|
||||
"details": {
|
||||
"required_permission": "products.delete",
|
||||
"store_code": "wizamart"
|
||||
"store_code": "orion"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -622,7 +622,7 @@ HTTP 403 Forbidden
|
||||
"message": "This operation requires store owner privileges",
|
||||
"details": {
|
||||
"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
Reference in New Issue
Block a user