diff --git a/docs/api/authentication-flow-diagrams.md b/docs/api/authentication-flow-diagrams.md index 2a6f393b..c6a91bd9 100644 --- a/docs/api/authentication-flow-diagrams.md +++ b/docs/api/authentication-flow-diagrams.md @@ -7,12 +7,12 @@ │ Browser │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ -│ │ Admin Area │ │ Store Area │ │ Shop Area │ │ -│ │ /admin/* │ │ /store/* │ │ /shop/* │ │ +│ │ Admin Area │ │ Store Area │ │ Storefront Area │ │ +│ │ /admin/* │ │ /store/* │ │ /storefront/* │ │ │ │ │ │ │ │ │ │ │ │ 🍪 admin_token │ │ 🍪 store_token │ │ 🍪 customer_ │ │ │ │ Path: /admin │ │ Path: /store │ │ token │ │ -│ │ │ │ │ │ Path: /shop │ │ +│ │ │ │ │ │ Path: /storefront │ │ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │ │ │ │ │ │ ├──────────────────────┼─────────────────────┤ │ @@ -22,8 +22,8 @@ │ │ │ ▼ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ -│ Admin Backend │ │ Store Backend │ │ Shop Backend │ -│ /admin/* │ │ /store/* │ │ /shop/* │ +│ Admin Backend │ │ Store Backend │ │ Storefront Backend │ +│ /admin/* │ │ /store/* │ │ /storefront/* │ │ │ │ │ │ │ │ ✅ admin_token │ │ ✅ store_token │ │ ✅ customer_ │ │ ❌ store_token │ │ ❌ admin_token │ │ token │ @@ -128,14 +128,14 @@ └────────────────────────────────────┘ ``` -## Login Flow - Customer (Shop) +## Login Flow - Customer (Storefront) ``` ┌──────────┐ │ Browser │ └──────────┘ │ - │ POST /api/v1/shop/auth/login + │ POST /api/v1/storefront/auth/login │ { email, password } ▼ ┌─────────────────────────┐ @@ -146,20 +146,20 @@ │ 3. Generate JWT │ └─────────────────────────┘ │ - │ Set-Cookie: customer_token=; Path=/shop; HttpOnly; SameSite=Lax + │ Set-Cookie: customer_token=; Path=/storefront; HttpOnly; SameSite=Lax │ Response: { access_token, user } ▼ ┌──────────┐ │ Browser │──────────────────────────────────────┐ │ │ │ -│ 🍪 customer_token (Path=/shop) │ +│ 🍪 customer_token (Path=/storefront) │ │ 💾 localStorage.access_token │ └──────────┘ │ │ │ - ├── Navigate to /shop/account/dashboard ─────┤ + ├── Navigate to /storefront/account/dashboard ─────┤ │ (Cookie sent automatically) │ │ │ - └── API call to /api/v1/shop/orders ─────────┤ + └── API call to /api/v1/storefront/orders ─────────┤ (Authorization: Bearer ) │ │ ┌────────────────────────────────────────┐ @@ -189,7 +189,7 @@ ┌───────────────────────────┼───────────────────────────┐ │ │ │ │ │ Starts with Starts with Starts with Starts with Starts with - /admin/* /store/* /shop/* /api/* (public) + /admin/* /store/* /storefront/* /api/* (public) │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐┌────────────────┐ @@ -307,17 +307,17 @@ Customer trying to access admin route: ``` ``` -Customer cookie sent to shop route (allowed): +Customer cookie sent to storefront route (allowed): ┌──────────────────────────────────────────┐ -│ Cookie: customer_token= (Path=/shop)│ -│ Request: GET /shop/account/orders │ +│ Cookie: customer_token= (Path=/storefront)│ +│ Request: GET /storefront/account/orders │ └──────────────────────────────────────────┘ │ ▼ Browser checks cookie path │ ▼ - Path /shop matches /shop + Path /storefront matches /storefront │ ▼ ✅ Cookie SENT automatically @@ -342,7 +342,7 @@ LOGIN ├── Server sets cookie: │ • Name: admin_token, store_token, or customer_token │ • Value: JWT - │ • Path: /admin, /store, or /shop (context-specific) + │ • Path: /admin, /store, or /storefront (context-specific) │ • HttpOnly: true │ • Secure: true (production) │ • SameSite: Lax @@ -386,5 +386,5 @@ LOGOUT 2. **Role Checking** = Strict role validation at each boundary (admin, store, customer) 3. **Dual Auth Support** = Cookies for HTML pages, headers for API endpoints 4. **Security First** = HttpOnly, Secure, SameSite protection on all cookies -5. **Clear Boundaries** = Each context (admin/store/shop) is completely isolated +5. **Clear Boundaries** = Each context (admin/store/storefront) is completely isolated 6. **Three User Types** = Admins manage platform, stores manage stores, customers shop diff --git a/docs/api/authentication-quick-reference.md b/docs/api/authentication-quick-reference.md index 85d6f980..dc8ea610 100644 --- a/docs/api/authentication-quick-reference.md +++ b/docs/api/authentication-quick-reference.md @@ -27,7 +27,7 @@ def store_page(user: User = Depends(get_current_store_from_cookie_or_header)): pass # Customer page - NOTE: Returns Customer, not User! -@router.get("/shop/account/dashboard") +@router.get("/storefront/account/dashboard") def customer_page(customer: Customer = Depends(get_current_customer_from_cookie_or_header)): pass # customer.id, customer.email, customer.store_id ``` @@ -53,7 +53,7 @@ def store_api(user: User = Depends(get_current_store_api)): pass # user.token_store_id for store context # Customer API - NOTE: Returns Customer, not User! -@router.post("/api/v1/shop/orders") +@router.post("/api/v1/storefront/orders") def customer_api(request: Request, customer: Customer = Depends(get_current_customer_api)): pass # customer.id, request.state.store validated to match ``` @@ -66,7 +66,7 @@ def customer_api(request: Request, customer: Customer = Depends(get_current_cust |---------|--------|------|------|--------| | **Admin** | `admin_token` | `/admin` | `admin` | `/admin/*` | | **Store** | `store_token` | `/store` | `store` | `/store/*` | -| **Customer** | `customer_token` | `/shop` | `customer` | `/shop/account/*` | +| **Customer** | `customer_token` | `/storefront` | `customer` | `/storefront/account/*` | --- @@ -208,8 +208,8 @@ console.log(parseJwt(localStorage.getItem('token'))); 3. ✅ **Admins** cannot access store/customer portals 4. ✅ **Stores** cannot access admin/customer portals 5. ✅ **Customers** cannot access admin/store portals -6. ✅ **Public shop** (`/shop/products`) needs no auth -7. ✅ **Customer accounts** (`/shop/account/*`) need auth +6. ✅ **Public storefront** (`/storefront/products`) needs no auth +7. ✅ **Customer accounts** (`/storefront/account/*`) need auth --- diff --git a/docs/api/authentication.md b/docs/api/authentication.md index e8cc8db1..ba34558b 100644 --- a/docs/api/authentication.md +++ b/docs/api/authentication.md @@ -25,8 +25,8 @@ 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 -- **Customer Shop** - Public storefront and customer accounts +- **Store Portal** - Multi-tenant storefront management +- **Customer Storefront** - Public storefront and customer accounts Each context uses **dual authentication** supporting both cookie-based (for HTML pages) and header-based (for API calls) authentication with complete isolation between contexts. @@ -92,12 +92,12 @@ Each authentication context uses a separate cookie with path restrictions: |----------|------------------|-------------|--------------| | Admin | `admin_token` | `/admin` | Admin routes only | | Store | `store_token` | `/store` | Store routes only | -| Customer | `customer_token` | `/shop` | Shop routes only | +| Customer | `customer_token` | `/storefront` | Storefront routes only | **Browser Behavior:** - When requesting `/admin/*`, browser sends `admin_token` cookie only - When requesting `/store/*`, browser sends `store_token` cookie only -- When requesting `/shop/*`, browser sends `customer_token` cookie only +- When requesting `/storefront/*`, browser sends `customer_token` cookie only This prevents cookie leakage between contexts. @@ -157,7 +157,7 @@ Set-Cookie: admin_token=; Path=/admin; HttpOnly; Secure; SameSite=Lax **Role:** `store` **Cookie:** `store_token` (path=/store) -**Purpose:** Store shop management, product catalog, orders, team management. +**Purpose:** Store management, product catalog, orders, team management. **Access Control:** - ❌ Admin users blocked (admins use admin portal for store management) @@ -205,24 +205,24 @@ Set-Cookie: store_token=; Path=/store; HttpOnly; Secure; SameSite=Lax ### 3. Customer Context -**Routes:** `/shop/account/*` (authenticated), `/shop/*` (public) +**Routes:** `/storefront/account/*` (authenticated), `/storefront/*` (public) **Role:** `customer` -**Cookie:** `customer_token` (path=/shop) +**Cookie:** `customer_token` (path=/storefront) **Purpose:** Product browsing (public), customer accounts, orders, profile management. **Important - URL Pattern Context:** -The `/shop/*` routes work differently depending on deployment mode: -- **Subdomain Mode** (Production): `https://store.platform.com/shop/products` -- **Custom Domain** (Production): `https://customdomain.com/shop/products` -- **Path-Based** (Development): `http://localhost:8000/stores/{store_code}/shop/products` +The `/storefront/*` routes work differently depending on deployment mode: +- **Subdomain Mode** (Production): `https://store.platform.com/storefront/products` +- **Custom Domain** (Production): `https://customdomain.com/storefront/products` +- **Path-Based** (Development): `http://localhost:8000/storefront/{store_code}/products` -In path-based development mode, the full URL includes the store code (e.g., `/stores/acme/shop/products`), but the routes are still defined as `/shop/*` internally. See [URL Routing Guide](../architecture/url-routing/overview.md) for details. +In path-based development mode, the full URL includes the store code (e.g., `/storefront/acme/products`), but the routes are still defined as `/storefront/*` internally. See [URL Routing Guide](../architecture/url-routing/overview.md) for details. **Access Control:** -- **Public Routes** (`/shop/products`, `/shop/cart`, etc.): +- **Public Routes** (`/storefront/products`, `/storefront/cart`, etc.): - ✅ Anyone can access (no authentication) -- **Account Routes** (`/shop/account/*`): +- **Account Routes** (`/storefront/account/*`): - ❌ Admin users blocked - ❌ Store users blocked - ✅ Customer users only @@ -256,7 +256,7 @@ curl -X POST http://localhost:8000/api/v1/platform/stores/1/customers/login \ Additionally, sets cookie: ``` -Set-Cookie: customer_token=; Path=/shop; HttpOnly; Secure; SameSite=Lax +Set-Cookie: customer_token=; Path=/storefront; HttpOnly; Secure; SameSite=Lax ``` --- @@ -323,13 +323,13 @@ async def store_dashboard( }) # Customer account page -@router.get("/shop/account/dashboard", response_class=HTMLResponse) +@router.get("/storefront/account/dashboard", response_class=HTMLResponse) async def customer_dashboard( request: Request, current_user: User = Depends(get_current_customer_from_cookie_or_header), db: Session = Depends(get_db) ): - return templates.TemplateResponse("shop/account/dashboard.html", { + return templates.TemplateResponse("storefront/account/dashboard.html", { "request": request, "user": current_user }) @@ -375,7 +375,7 @@ def create_product( return {"message": "Product created"} # Customer API -@router.post("/api/v1/shop/orders") +@router.post("/api/v1/storefront/orders") def create_order( order_data: OrderCreate, current_user: User = Depends(get_current_customer_api), @@ -389,10 +389,10 @@ def create_order( Simply don't use any authentication dependency: ```python -@router.get("/shop/products") +@router.get("/storefront/products") async def public_products(request: Request): # No authentication required - return templates.TemplateResponse("shop/products.html", { + return templates.TemplateResponse("storefront/products.html", { "request": request }) ``` @@ -498,7 +498,7 @@ def get_orders( **Security Features:** - **Token type validation:** Only accepts tokens with `type: "customer"` - admin and store tokens are rejected - **Store validation:** Validates that `token.store_id` matches `request.state.store.id` (URL-based store) -- Prevents cross-store token reuse (customer from Store A cannot use token on Store B's shop) +- Prevents cross-store token reuse (customer from Store A cannot use token on Store B's storefront) **Usage:** ```python @@ -637,23 +637,23 @@ async def store_login_page( ```python from models.database.customer import Customer -# Shop login page redirect -@router.get("/shop/account/login") -async def customer_login_page( +# Storefront login page redirect +@router.get("/storefront/account/login") +async def storefront_login_page( request: Request, customer: Customer | None = Depends(get_current_customer_optional) ): if customer: # Customer already logged in, redirect to account page - return RedirectResponse(url="/shop/account", status_code=302) + return RedirectResponse(url="/storefront/account", status_code=302) # Not logged in, show login form - return templates.TemplateResponse("shop/login.html", {"request": request}) + return templates.TemplateResponse("storefront/login.html", {"request": request}) ``` **Use Cases:** - Login pages (redirect if already authenticated) -- Public shop pages with conditional customer content (e.g., "My Orders" link) +- Public storefront pages with conditional customer content (e.g., "My Orders" link) - Root redirects based on authentication status --- @@ -744,7 +744,7 @@ All login endpoints return: Additionally, the response sets an HTTP-only cookie: - Admin: `admin_token` (path=/admin) - Store: `store_token` (path=/store) -- Customer: `customer_token` (path=/shop) +- Customer: `customer_token` (path=/storefront) --- @@ -752,7 +752,7 @@ Additionally, the response sets an HTTP-only cookie: ### Role-Based Access Control Matrix -| User Role | Admin Portal | Store Portal | Shop Catalog | Customer Account | +| User Role | Admin Portal | Store Portal | Storefront Catalog | Customer Account | |-----------|--------------|---------------|--------------|------------------| | Admin | ✅ Full | ❌ Blocked | ✅ View | ❌ Blocked | | Store | ❌ Blocked | ✅ Full | ✅ View | ❌ Blocked | @@ -863,7 +863,7 @@ Custom exceptions (such as those raised for missing claims) are preserved with t 4. **Test navigation:** - Navigate to `/admin/dashboard` - Should work ✅ - Navigate to `/store/TESTSTORE/dashboard` - Should fail (cookie not sent) ❌ - - Navigate to `/shop/account/dashboard` - Should fail (cookie not sent) ❌ + - Navigate to `/storefront/account/dashboard` - Should fail (cookie not sent) ❌ 5. **Logout:** ``` @@ -886,23 +886,23 @@ Custom exceptions (such as those raised for missing claims) are preserved with t 4. **Test navigation:** - Navigate to `/store/{STORE_CODE}/dashboard` - Should work ✅ - Navigate to `/admin/dashboard` - Should fail ❌ - - Navigate to `/shop/account/dashboard` - Should fail ❌ + - Navigate to `/storefront/account/dashboard` - Should fail ❌ #### Test Customer Authentication 1. **Navigate to customer login:** ``` - http://localhost:8000/shop/account/login + http://localhost:8000/storefront/account/login ``` 2. **Login with customer credentials** 3. **Verify cookie in DevTools:** - Look for `customer_token` cookie - - Verify `Path` is `/shop` + - Verify `Path` is `/storefront` 4. **Test navigation:** - - Navigate to `/shop/account/dashboard` - Should work ✅ + - Navigate to `/storefront/account/dashboard` - Should work ✅ - Navigate to `/admin/dashboard` - Should fail ❌ - Navigate to `/store/{CODE}/dashboard` - Should fail ❌ @@ -955,7 +955,7 @@ curl -X POST http://localhost:8000/api/v1/platform/stores/1/customers/login \ -d '{"username":"customer","password":"customer123"}' # Test authenticated endpoint with token -curl http://localhost:8000/api/v1/shop/orders \ +curl http://localhost:8000/api/v1/storefront/orders \ -H "Authorization: Bearer " # Test cross-context blocking @@ -1101,8 +1101,8 @@ Admins should not log into store portal as this violates security boundaries. **Symptom:** Customer trying to access management interfaces **Explanation:** Customers only have access to: -- Public shop routes: `/shop/products`, etc. -- Their account: `/shop/account/*` +- Public storefront routes: `/storefront/products`, etc. +- Their account: `/storefront/account/*` Admin and store portals are not accessible to customers. @@ -1169,7 +1169,7 @@ Look for: 2. **Don't mix authentication contexts:** - Admin users should use admin portal - Store users should use store portal - - Customers should use shop + - Customers should use storefront 3. **Always check user.is_active:** ```python diff --git a/docs/api/error-handling.md b/docs/api/error-handling.md index 3ad33b1c..f4c8c1cd 100644 --- a/docs/api/error-handling.md +++ b/docs/api/error-handling.md @@ -167,7 +167,7 @@ Returns JSON error responses suitable for API clients. ### Admin/Store Dashboard (`/admin/*`, `/store/*`) Returns JSON errors or redirects to error pages based on accept headers. -### Shop Requests (`/shop/*`) +### Storefront Requests (`/storefront/*`) Returns themed error pages matching the store's shop design. ## Logging diff --git a/docs/api/rbac-visual-guide.md b/docs/api/rbac-visual-guide.md index 52ce7c86..b312bd0a 100644 --- a/docs/api/rbac-visual-guide.md +++ b/docs/api/rbac-visual-guide.md @@ -335,5 +335,5 @@ Customer → Store Portal Customer → Own Account Cookie Isolation: admin_token (path=/admin) ← Only sent to /admin/* store_token (path=/store) ← Only sent to /store/* -customer_token (path=/shop) ← Only sent to /shop/* +customer_token (path=/storefront) ← Only sent to /storefront/* ``` diff --git a/docs/api/rbac.md b/docs/api/rbac.md index 6c6b8032..58e05589 100644 --- a/docs/api/rbac.md +++ b/docs/api/rbac.md @@ -86,7 +86,7 @@ The application operates in three isolated contexts: |---------|--------|----------------|------------| | **Admin** | `/admin/*` | `admin_token` cookie | `super_admin`, `platform_admin` | | **Store** | `/store/*` | `store_token` cookie | `merchant_owner`, `store_member` | -| **Shop** | `/shop/account/*` | `customer_token` cookie | Customers | +| **Storefront** | `/storefront/account/*` | `customer_token` cookie | Customers | **Important:** These contexts are security boundaries. Admin users cannot access store routes, store users cannot access admin routes, and customers are entirely separate. @@ -802,7 +802,7 @@ role = Role( ┌─────────────┐ │ Client │ │ │ -│ 🍪 customer_token (path=/shop) │ +│ 🍪 customer_token (path=/storefront) │ │ 💾 localStorage.token │ └─────────────┘ ``` @@ -838,7 +838,7 @@ response.set_cookie( response.set_cookie( key="customer_token", value=jwt_token, - path="/shop", # Only sent to /shop/* routes + path="/storefront", # Only sent to /storefront/* routes httponly=True, secure=True, samesite="lax" diff --git a/docs/api/storefront-api-reference.md b/docs/api/storefront-api-reference.md index 3922298d..e377c8a2 100644 --- a/docs/api/storefront-api-reference.md +++ b/docs/api/storefront-api-reference.md @@ -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/orion/shop/products +Referer: http://localhost:8000/storefront/orion/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/orion/shop/products +Referer: http://localhost:8000/storefront/orion/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/orion/shop/cart +Referer: http://localhost:8000/storefront/orion/cart ``` **Response (200 OK):** diff --git a/docs/architecture/api-consolidation-proposal.md b/docs/architecture/api-consolidation-proposal.md index 418cd386..51e27fbf 100644 --- a/docs/architecture/api-consolidation-proposal.md +++ b/docs/architecture/api-consolidation-proposal.md @@ -6,9 +6,9 @@ ## Executive Summary -The platform currently has **two parallel API structures** for shop/customer-facing endpoints: +The platform currently has **two parallel API structures** for storefront/customer-facing endpoints: 1. **Original:** `/api/v1/platform/stores/{store_id}/*` -2. **New:** `/api/v1/shop/*` +2. **New:** `/api/v1/storefront/*` This divergence creates confusion, maintenance overhead, and potential bugs. This document analyzes the situation and proposes a consolidation strategy. @@ -47,26 +47,26 @@ POST /api/v1/platform/stores/auth/register → Customer registration --- -### 2. New Architecture (`/api/v1/shop/`) +### 2. New Architecture (`/api/v1/storefront/`) -**Location:** `app/api/v1/shop/` +**Location:** `app/api/v1/storefront/` **Endpoints:** ``` -GET /api/v1/shop/content-pages/navigation → CMS navigation pages -GET /api/v1/shop/content-pages/{slug} → CMS page content +GET /api/v1/storefront/content-pages/navigation → CMS navigation pages +GET /api/v1/storefront/content-pages/{slug} → CMS page content ``` **Characteristics:** - ✅ **Store-agnostic URLs:** Clean paths without store_id - ✅ **Middleware-driven:** Relies on `StoreContextMiddleware` to inject store -- ✅ **Simpler URLs:** `/api/v1/shop/products` vs `/api/v1/platform/stores/123/products` +- ✅ **Simpler URLs:** `/api/v1/storefront/products` vs `/api/v1/platform/stores/123/products` - ❌ **Incomplete:** Only CMS endpoints implemented - ❌ **Divergent:** Not consistent with existing public API **Current Usage:** - CMS content pages only -- Called from shop templates (e.g., `shop/products.html`, `shop/home.html`) +- Called from storefront templates (e.g., `storefront/products.html`, `storefront/home.html`) --- @@ -77,7 +77,7 @@ GET /api/v1/shop/content-pages/{slug} → CMS page content ```javascript // ❌ INCONSISTENT - Two different patterns for same context // CMS pages use new pattern -fetch('/api/v1/shop/content-pages/about') +fetch('/api/v1/storefront/content-pages/about') // Products use old pattern fetch('/api/v1/platform/stores/123/products') @@ -86,7 +86,7 @@ fetch('/api/v1/platform/stores/123/products') ### Confusion Developers must remember: -- "Is this endpoint under `/shop` or `/public/stores`?" +- "Is this endpoint under `/storefront` or `/public/stores`?" - "Do I need to pass store_id or is it from middleware?" - "Which authentication endpoints do I use?" @@ -102,7 +102,7 @@ Developers must remember: **Current Issue:** CMS pages not loading at `/stores/orion/about` **Root Cause:** -- CMS API exists at `/api/v1/shop/content-pages/{slug}` +- CMS API exists at `/api/v1/storefront/content-pages/{slug}` - No corresponding HTML route handler in `store_pages.py` - JavaScript might be calling wrong endpoint @@ -110,13 +110,13 @@ Developers must remember: ## Options Analysis -### Option 1: Move Everything to `/api/v1/shop/*` (Middleware-Driven) +### Option 1: Move Everything to `/api/v1/storefront/*` (Middleware-Driven) -**Approach:** Consolidate all customer-facing endpoints under `/api/v1/shop/*` +**Approach:** Consolidate all customer-facing endpoints under `/api/v1/storefront/*` **Proposed Structure:** ``` -/api/v1/shop/ +/api/v1/storefront/ ├── auth/ │ ├── POST /login → Customer login │ ├── POST /register → Customer registration @@ -143,7 +143,7 @@ Developers must remember: **Implementation:** - Store extracted by `StoreContextMiddleware` from request - All endpoints use `request.state.store` instead of path parameter -- URLs are cleaner: `/api/v1/shop/products` instead of `/api/v1/platform/stores/123/products` +- URLs are cleaner: `/api/v1/storefront/products` instead of `/api/v1/platform/stores/123/products` **Pros:** - ✅ Clean, consistent API structure @@ -162,17 +162,17 @@ Developers must remember: --- -### Option 2: Keep `/api/v1/platform/stores/*` and Deprecate `/api/v1/shop/*` +### Option 2: Keep `/api/v1/platform/stores/*` and Deprecate `/api/v1/storefront/*` **Approach:** Move CMS endpoints to `/api/v1/platform/stores/{store_id}/content-pages/*` **Proposed Changes:** ``` # Move CMS endpoints -FROM: /api/v1/shop/content-pages/navigation +FROM: /api/v1/storefront/content-pages/navigation TO: /api/v1/platform/stores/{store_id}/content-pages/navigation -FROM: /api/v1/shop/content-pages/{slug} +FROM: /api/v1/storefront/content-pages/{slug} TO: /api/v1/platform/stores/{store_id}/content-pages/{slug} ``` @@ -228,7 +228,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)): ## Recommendation -### **OPTION 1: Consolidate to `/api/v1/shop/*` (Middleware-Driven)** +### **OPTION 1: Consolidate to `/api/v1/storefront/*` (Middleware-Driven)** **Rationale:** @@ -237,7 +237,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)): 2. **User Experience**: Cleaner URLs are easier for frontend developers: ```javascript // ✅ GOOD - fetch('/api/v1/shop/products') + fetch('/api/v1/storefront/products') // ❌ BAD fetch('/api/v1/platform/stores/123/products') @@ -245,7 +245,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)): 3. **Multi-Tenant Best Practice**: Store context should be implicit (from domain/path), not explicit in every API call. -4. **Consistency**: All shop endpoints follow same pattern - no mixing `/shop` and `/public/stores`. +4. **Consistency**: All storefront endpoints follow same pattern - no mixing `/storefront` and `/public/stores`. 5. **Future-Proof**: Easier to add new shop features without worrying about store_id paths. @@ -258,7 +258,7 @@ async def get_products_legacy(store_id: int, db: Session = Depends(get_db)): **Day 1-2: Move Products** ```bash # Copy and adapt -app/api/v1/platform/stores/products.py → app/api/v1/shop/products.py +app/api/v1/platform/stores/products.py → app/api/v1/storefront/products.py # Changes: - Remove store_id path parameter @@ -268,24 +268,24 @@ app/api/v1/platform/stores/products.py → app/api/v1/shop/products.py **Day 3: Move Cart** ```bash -app/api/v1/platform/stores/cart.py → app/api/v1/shop/cart.py +app/api/v1/platform/stores/cart.py → app/api/v1/storefront/cart.py ``` **Day 4: Move Orders** ```bash -app/api/v1/platform/stores/orders.py → app/api/v1/shop/orders.py +app/api/v1/platform/stores/orders.py → app/api/v1/storefront/orders.py ``` **Day 5: Move Auth** ```bash -app/api/v1/platform/stores/auth.py → app/api/v1/shop/auth.py +app/api/v1/platform/stores/auth.py → app/api/v1/storefront/auth.py ``` ### Phase 2: Update Frontend (Week 1) **Templates:** - Update all `fetch()` calls in shop templates -- Change from `/api/v1/platform/stores/${storeId}/...` to `/api/v1/shop/...` +- Change from `/api/v1/platform/stores/${storeId}/...` to `/api/v1/storefront/...` **JavaScript:** - Update any shop-related API client code @@ -335,10 +335,10 @@ const storeId = 123; fetch(`/api/v1/platform/stores/${storeId}/products`) ``` -### After (Proposed - `/api/v1/shop`) +### After (Proposed - `/api/v1/storefront`) ```python -# app/api/v1/shop/products.py +# app/api/v1/storefront/products.py @router.get("/products") def get_product_catalog( request: Request, @@ -350,7 +350,7 @@ def get_product_catalog( ```javascript // Frontend -fetch('/api/v1/shop/products') // Store context automatic +fetch('/api/v1/storefront/products') // Store context automatic ``` --- @@ -382,7 +382,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t ### Quick Fix: Just Move CMS to Public API ```python -# Move: app/api/v1/shop/content_pages.py +# Move: app/api/v1/storefront/content_pages.py # To: app/api/v1/platform/stores/content_pages.py # Update routes: @@ -401,7 +401,7 @@ If full migration is not approved immediately, we can do a **minimal fix** for t **Question for Team:** Should we: -1. ✅ **Consolidate to `/api/v1/shop/*`** (Recommended) +1. ✅ **Consolidate to `/api/v1/storefront/*`** (Recommended) 2. ❌ **Keep `/api/v1/platform/stores/*`** and move CMS there 3. ❌ **Hybrid approach** with both patterns 4. ❌ **Quick fix only** - move CMS, address later @@ -422,15 +422,15 @@ Should we: - 🚧 `search.py` - Stub - 🚧 `shop.py` - Stub -### `/api/v1/shop/*` +### `/api/v1/storefront/*` - ✅ `content_pages.py` - CMS pages ### To Be Created (if Option 1 chosen) -- 📝 `shop/products.py` -- 📝 `shop/cart.py` -- 📝 `shop/orders.py` -- 📝 `shop/auth.py` -- 📝 `shop/stores.py` (marketplace listing) +- 📝 `storefront/products.py` +- 📝 `storefront/cart.py` +- 📝 `storefront/orders.py` +- 📝 `storefront/auth.py` +- 📝 `storefront/stores.py` (marketplace listing) --- diff --git a/docs/architecture/api-migration-status.md b/docs/architecture/api-migration-status.md index 3a1a5407..f74e6fa4 100644 --- a/docs/architecture/api-migration-status.md +++ b/docs/architecture/api-migration-status.md @@ -1,55 +1,55 @@ -# API Migration Status - `/api/v1/shop/*` Consolidation +# API Migration Status - `/api/v1/storefront/*` Consolidation **Date:** 2025-11-22 **Status:** 🎉 MIGRATION COMPLETE - All Phases Done -**Decision:** Option 1 - Full Consolidation to `/api/v1/shop/*` +**Decision:** Option 1 - Full Consolidation to `/api/v1/storefront/*` --- ## Progress Overview -### ✅ Phase 1: New Shop API Endpoints (COMPLETE) +### ✅ Phase 1: New Storefront API Endpoints (COMPLETE) -All new shop endpoints have been created using middleware-based store context: +All new storefront endpoints have been created using middleware-based store context: ### ✅ Middleware Update: Referer-Based Store Extraction (COMPLETE) -Updated `StoreContextMiddleware` to support shop API routes: -- Added `is_shop_api_request()` method to detect `/api/v1/shop/*` routes +Updated `StoreContextMiddleware` to support storefront API routes: +- Added `is_storefront_api_request()` method to detect `/api/v1/storefront/*` routes - Added `extract_store_from_referer()` method to extract store from Referer/Origin headers -- Modified `dispatch()` to handle shop API routes specially (no longer skips them) -- Shop API now receives store context from the page that made the API call +- Modified `dispatch()` to handle storefront API routes specially (no longer skips them) +- Storefront API now receives store context from the page that made the API call **How it works:** -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` +1. Browser JavaScript on `/storefront/orion/products` calls `/api/v1/storefront/products` +2. Browser automatically sends `Referer: http://localhost:8000/storefront/orion/products` 3. Middleware extracts `orion` from Referer path 4. Queries database to get Store object 5. Sets `request.state.store` for the API endpoint ### ✅ Phase 1a: Endpoint Testing (COMPLETE) -Tested shop API endpoints with Referer header: +Tested storefront API endpoints with Referer header: | Endpoint | Method | Test Result | Notes | |----------|--------|-------------|-------| -| `/api/v1/shop/products` | GET | ✅ Working | Returns paginated product list | -| `/api/v1/shop/products?search=Sample` | GET | ✅ Working | Search functionality works | -| `/api/v1/shop/products?is_featured=true` | GET | ✅ Working | Featured filter works | -| `/api/v1/shop/products/{id}` | GET | ✅ Working | Product details returned | -| `/api/v1/shop/cart/{session_id}` | GET | ✅ Working | Empty cart returns correctly | -| `/api/v1/shop/content-pages/navigation` | GET | ✅ Working | Navigation links returned | +| `/api/v1/storefront/products` | GET | ✅ Working | Returns paginated product list | +| `/api/v1/storefront/products?search=Sample` | GET | ✅ Working | Search functionality works | +| `/api/v1/storefront/products?is_featured=true` | GET | ✅ Working | Featured filter works | +| `/api/v1/storefront/products/{id}` | GET | ✅ Working | Product details returned | +| `/api/v1/storefront/cart/{session_id}` | GET | ✅ Working | Empty cart returns correctly | +| `/api/v1/storefront/content-pages/navigation` | GET | ✅ Working | Navigation links returned | **All tested endpoints successfully receive store context from Referer header.** | Endpoint File | Status | Routes | Description | |--------------|--------|--------|-------------| -| `shop/products.py` | ✅ Complete | 3 routes | Product catalog, details, search | -| `shop/cart.py` | ✅ Complete | 5 routes | Cart CRUD operations | -| `shop/orders.py` | ✅ Complete | 3 routes | Order placement & history | -| `shop/auth.py` | ✅ Complete | 5 routes | Login, register, password reset | -| `shop/content_pages.py` | ✅ Existing | 2 routes | CMS pages (already present) | -| `shop/__init__.py` | ✅ Updated | - | Router aggregation | +| `storefront/products.py` | ✅ Complete | 3 routes | Product catalog, details, search | +| `storefront/cart.py` | ✅ Complete | 5 routes | Cart CRUD operations | +| `storefront/orders.py` | ✅ Complete | 3 routes | Order placement & history | +| `storefront/auth.py` | ✅ Complete | 5 routes | Login, register, password reset | +| `storefront/content_pages.py` | ✅ Existing | 2 routes | CMS pages (already present) | +| `storefront/__init__.py` | ✅ Updated | - | Router aggregation | **Total:** 18 new API endpoints created @@ -57,46 +57,46 @@ Tested shop API endpoints with Referer header: ## New API Structure -### Shop Endpoints (`/api/v1/shop/*`) +### Storefront Endpoints (`/api/v1/storefront/*`) All endpoints use `request.state.store` (injected by `StoreContextMiddleware`). #### Products (Public - No Auth) ``` -GET /api/v1/shop/products → Product catalog (paginated) -GET /api/v1/shop/products/{id} → Product details -GET /api/v1/shop/products/search?q=... → Search products +GET /api/v1/storefront/products → Product catalog (paginated) +GET /api/v1/storefront/products/{id} → Product details +GET /api/v1/storefront/products/search?q=... → Search products ``` #### Cart (Public - Session Based) ``` -GET /api/v1/shop/cart/{session_id} → Get cart -POST /api/v1/shop/cart/{session_id}/items → Add to cart -PUT /api/v1/shop/cart/{session_id}/items/{id} → Update item -DELETE /api/v1/shop/cart/{session_id}/items/{id} → Remove item -DELETE /api/v1/shop/cart/{session_id} → Clear cart +GET /api/v1/storefront/cart/{session_id} → Get cart +POST /api/v1/storefront/cart/{session_id}/items → Add to cart +PUT /api/v1/storefront/cart/{session_id}/items/{id} → Update item +DELETE /api/v1/storefront/cart/{session_id}/items/{id} → Remove item +DELETE /api/v1/storefront/cart/{session_id} → Clear cart ``` #### Orders (Authenticated - Customer) ``` -POST /api/v1/shop/orders → Place order (auth required) -GET /api/v1/shop/orders → Order history (auth required) -GET /api/v1/shop/orders/{id} → Order details (auth required) +POST /api/v1/storefront/orders → Place order (auth required) +GET /api/v1/storefront/orders → Order history (auth required) +GET /api/v1/storefront/orders/{id} → Order details (auth required) ``` #### Authentication (Public) ``` -POST /api/v1/shop/auth/register → Register customer -POST /api/v1/shop/auth/login → Customer login -POST /api/v1/shop/auth/logout → Customer logout -POST /api/v1/shop/auth/forgot-password → Request reset -POST /api/v1/shop/auth/reset-password → Reset password +POST /api/v1/storefront/auth/register → Register customer +POST /api/v1/storefront/auth/login → Customer login +POST /api/v1/storefront/auth/logout → Customer logout +POST /api/v1/storefront/auth/forgot-password → Request reset +POST /api/v1/storefront/auth/reset-password → Reset password ``` #### CMS Content (Public) ``` -GET /api/v1/shop/content-pages/navigation → Navigation links -GET /api/v1/shop/content-pages/{slug} → Page content +GET /api/v1/storefront/content-pages/navigation → Navigation links +GET /api/v1/storefront/content-pages/{slug} → Page content ``` --- @@ -130,7 +130,7 @@ def endpoint_handler(request: Request, ...): - **Public endpoints** (products, cart, CMS): No authentication - **Authenticated endpoints** (orders): Use `get_current_customer_api` dependency -- **Cookie strategy**: Customer tokens stored at `path=/shop` only +- **Cookie strategy**: Customer tokens stored at `path=/storefront` only ### Error Handling @@ -145,7 +145,7 @@ def endpoint_handler(request: Request, ...): ### Files Created ✨ ``` -app/api/v1/shop/ +app/api/v1/storefront/ ├── products.py (NEW - 182 lines) ├── cart.py (NEW - 242 lines) ├── orders.py (NEW - 193 lines) @@ -156,9 +156,9 @@ app/api/v1/shop/ ### Files Modified 🔧 ``` -app/exceptions/error_renderer.py → Added base_url calculation for shop context +app/exceptions/error_renderer.py → Added base_url calculation for storefront context app/routes/store_pages.py → Added CMS route handler -app/templates/shop/errors/*.html → Fixed links to use base_url +app/templates/storefront/errors/*.html → Fixed links to use base_url docs/architecture/ ├── api-consolidation-proposal.md → Analysis & recommendation └── api-migration-status.md → This file @@ -170,27 +170,27 @@ docs/architecture/ ### ✅ Phase 2: Frontend Migration (COMPLETE) -Updated all shop templates to use new API endpoints: +Updated all storefront templates to use new API endpoints: | Template | Old Endpoint | New Endpoint | Status | |----------|-------------|--------------|---------| -| `shop/account/login.html` | `/api/v1/platform/stores/${id}/customers/login` | `/api/v1/shop/auth/login` | ✅ Complete | -| `shop/account/register.html` | `/api/v1/platform/stores/${id}/customers/register` | `/api/v1/shop/auth/register` | ✅ Complete | -| `shop/product.html` | `/api/v1/platform/stores/${id}/products/${pid}` | `/api/v1/shop/products/${pid}` | ✅ Complete | -| `shop/product.html` | `/api/v1/platform/stores/${id}/products?limit=4` | `/api/v1/shop/products?limit=4` | ✅ Complete | -| `shop/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete | -| `shop/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items` | `/api/v1/shop/cart/${sid}/items` | ✅ Complete | -| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/shop/cart/${sid}` | ✅ Complete | -| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete | -| `shop/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/shop/cart/${sid}/items/${pid}` | ✅ Complete | -| `shop/products.html` | Already using `/api/v1/shop/products` | (No change needed) | ✅ Already Updated | -| `shop/home.html` | Already using `/api/v1/shop/products?featured=true` | (No change needed) | ✅ Already Updated | +| `storefront/account/login.html` | `/api/v1/platform/stores/${id}/customers/login` | `/api/v1/storefront/auth/login` | ✅ Complete | +| `storefront/account/register.html` | `/api/v1/platform/stores/${id}/customers/register` | `/api/v1/storefront/auth/register` | ✅ Complete | +| `storefront/product.html` | `/api/v1/platform/stores/${id}/products/${pid}` | `/api/v1/storefront/products/${pid}` | ✅ Complete | +| `storefront/product.html` | `/api/v1/platform/stores/${id}/products?limit=4` | `/api/v1/storefront/products?limit=4` | ✅ Complete | +| `storefront/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/storefront/cart/${sid}` | ✅ Complete | +| `storefront/product.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items` | `/api/v1/storefront/cart/${sid}/items` | ✅ Complete | +| `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}` | `/api/v1/storefront/cart/${sid}` | ✅ Complete | +| `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (PUT) | `/api/v1/storefront/cart/${sid}/items/${pid}` | ✅ Complete | +| `storefront/cart.html` | `/api/v1/platform/stores/${id}/cart/${sid}/items/${pid}` (DELETE) | `/api/v1/storefront/cart/${sid}/items/${pid}` | ✅ Complete | +| `storefront/products.html` | Already using `/api/v1/storefront/products` | (No change needed) | ✅ Already Updated | +| `storefront/home.html` | Already using `/api/v1/storefront/products?featured=true` | (No change needed) | ✅ Already Updated | **Total Changes:** 9 API endpoint migrations across 3 template files **Verification:** ```bash -grep -r "api/v1/public/stores" app/templates/shop --include="*.html" +grep -r "api/v1/public/stores" app/templates/storefront --include="*.html" # Returns: (no results - all migrated) ``` @@ -199,16 +199,16 @@ grep -r "api/v1/public/stores" app/templates/shop --include="*.html" Cleaned up old `/api/v1/platform/stores/*` endpoints: **Files Removed:** -- ❌ `auth.py` - Migrated to `/api/v1/shop/auth.py` -- ❌ `products.py` - Migrated to `/api/v1/shop/products.py` -- ❌ `cart.py` - Migrated to `/api/v1/shop/cart.py` -- ❌ `orders.py` - Migrated to `/api/v1/shop/orders.py` +- ❌ `auth.py` - Migrated to `/api/v1/storefront/auth.py` +- ❌ `products.py` - Migrated to `/api/v1/storefront/products.py` +- ❌ `cart.py` - Migrated to `/api/v1/storefront/cart.py` +- ❌ `orders.py` - Migrated to `/api/v1/storefront/orders.py` - ❌ `payments.py` - Empty placeholder (removed) - ❌ `search.py` - Empty placeholder (removed) -- ❌ `shop.py` - Empty placeholder (removed) +- ❌ `storefront.py` - Empty placeholder (removed) **Files Kept:** -- ✅ `stores.py` - Store lookup endpoints (truly public, not shop-specific) +- ✅ `stores.py` - Store lookup endpoints (truly public, not storefront-specific) - `GET /api/v1/platform/stores/by-code/{store_code}` - `GET /api/v1/platform/stores/by-subdomain/{subdomain}` - `GET /api/v1/platform/stores/{store_id}/info` @@ -216,7 +216,7 @@ Cleaned up old `/api/v1/platform/stores/*` endpoints: **Updated:** - ✅ `/app/api/v1/platform/__init__.py` - Now only includes store lookup endpoints -**Result:** Old shop endpoints completely removed, only store lookup remains in `/api/v1/platform/stores/*` +**Result:** Old storefront endpoints completely removed, only store lookup remains in `/api/v1/platform/stores/*` ### ⚠️ Phase 4: Deprecation Warnings (SKIPPED - Not Needed) @@ -291,12 +291,12 @@ Old endpoint cleanup completed immediately (no gradual migration needed): ### After (New Pattern) ``` # Clean - store from context -/api/v1/shop/products -/api/v1/shop/products/456 -/api/v1/shop/cart/abc-session-id -/api/v1/shop/cart/abc-session-id/items -/api/v1/shop/orders -/api/v1/shop/auth/login +/api/v1/storefront/products +/api/v1/storefront/products/456 +/api/v1/storefront/cart/abc-session-id +/api/v1/storefront/cart/abc-session-id/items +/api/v1/storefront/orders +/api/v1/storefront/auth/login ``` **URL Reduction:** ~40% shorter URLs on average @@ -308,7 +308,7 @@ Old endpoint cleanup completed immediately (no gradual migration needed): ### For Frontend Developers - ✅ Cleaner, more intuitive URLs - ✅ No need to track store_id in state -- ✅ Consistent API pattern across all shop endpoints +- ✅ Consistent API pattern across all storefront endpoints - ✅ Automatic store context from middleware ### For Backend Developers @@ -331,7 +331,7 @@ If issues arise, rollback is simple since old endpoints still exist: 1. **Revert frontend changes:** ```bash - git checkout app/templates/shop/*.html + git checkout app/templates/storefront/*.html ``` 2. **Old endpoints still work:** @@ -357,7 +357,7 @@ Add logging to track which pattern is being used: logger.info( "API call", extra={ - "endpoint_pattern": "new" if "/shop/" in request.url.path else "old", + "endpoint_pattern": "new" if "/storefront/" in request.url.path else "old", "path": request.url.path, "store_id": store.id if store else None, } @@ -377,7 +377,7 @@ logger.info( ### ✅ Decided -1. **Use `/api/v1/shop/*` pattern?** → YES (Option 1) +1. **Use `/api/v1/storefront/*` pattern?** → YES (Option 1) 2. **Store from middleware?** → YES 3. **Keep old endpoints during migration?** → YES 4. **Deprecation period?** → 2-4 weeks @@ -412,7 +412,7 @@ logger.info( Migration is considered successful when: - [x] All new endpoints created and tested -- [x] Middleware updated to support shop API routes +- [x] Middleware updated to support storefront API routes - [x] Store context extraction from Referer working - [x] All frontend templates updated (9 API calls across 3 files) - [x] Old endpoint usage drops to zero (verified with grep) @@ -432,7 +432,7 @@ Migration is considered successful when: - [Middleware Documentation](./middleware.md) - How middleware works **Issues?** Review: -- Server logs: Check for `[SHOP_API]` log entries +- Server logs: Check for `[STOREFRONT_API]` log entries - Browser console: Check for failed API calls - Network tab: Verify correct endpoints are called diff --git a/docs/architecture/auth-rbac.md b/docs/architecture/auth-rbac.md index 029aefe7..99fd462f 100644 --- a/docs/architecture/auth-rbac.md +++ b/docs/architecture/auth-rbac.md @@ -7,7 +7,7 @@ Complete guide to the authentication and authorization system powering the multi The platform uses a JWT-based authentication system combined with role-based access control (RBAC) to secure all interfaces: - **Admin** interface - **Store** dashboard -- **Shop** storefront +- **Storefront** - **REST API** endpoints ## Authentication System @@ -60,17 +60,17 @@ User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member" ### Customer Role (Separate Model) -**Access**: Public shop and own account space +**Access**: Public storefront and own account space **Capabilities**: -- Browse store shops +- Browse store storefronts - Place orders - Manage their own account and order history - View order status - Update profile information -- Can register directly from shop frontend +- Can register directly from storefront frontend -**Account Creation**: Self-registration via shop frontend (email verification required) +**Account Creation**: Self-registration via storefront frontend (email verification required) **Authentication**: Standard JWT authentication (separate `Customer` model, not `User`) @@ -89,7 +89,7 @@ User.role: "super_admin" | "platform_admin" | "merchant_owner" | "store_member" - Manage products and inventory - Process orders - View analytics and reports -- Configure shop settings +- Configure storefront settings - Manage team members (invite, remove, update roles) - Access store-specific APIs @@ -155,8 +155,8 @@ The platform has three distinct areas with different access requirements: | Area | URL Pattern | Access | Purpose | |------|-------------|--------|---------| | **Admin** | `/admin/*` or `admin.platform.com` | Super admins and platform admins (`is_admin`) | Platform administration and store management | -| **Store** | `/store/*` | Merchant owners and store members (`is_store_user`) | Store dashboard and shop management | -| **Shop** | `/shop/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront | +| **Store** | `/store/*` | Merchant owners and store members (`is_store_user`) | Store dashboard and storefront management | +| **Storefront** | `/storefront/*`, custom domains, subdomains | Customers and public | Public-facing eCommerce storefront | | **API** | `/api/*` | All authenticated users (role-based) | REST API for all operations | ## Account Registration Flow @@ -174,7 +174,7 @@ The platform has three distinct areas with different access requirements: - Activation: Upon clicking email verification link ### Customer Accounts -- ✅ Can register directly on store shop +- ✅ Can register directly on store storefront - Activation: Upon clicking registration email link - Used for: Shopping and order management @@ -222,8 +222,8 @@ Allows access to customer users and admins. **Usage**: ```python -@app.get("/shop/orders") -async def customer_orders( +@app.get("/storefront/orders") +async def storefront_orders( current_user: User = Depends(auth_manager.require_customer) ): return {"orders": [...]} @@ -390,13 +390,13 @@ graph TD D[Merchant Owner
role=merchant_owner] --> E[Store Dashboard] D --> F[Team Management] - D --> G[Shop Settings] + D --> G[Storefront Settings] D --> H[All Store Permissions - 75] I[Store Member
role=store_member] --> E I --> J[Role-Based Permissions] - K[Customer
separate model] --> L[Shop Access] + K[Customer
separate model] --> L[Storefront Access] K --> M[Own Orders] K --> N[Own Profile] ``` diff --git a/docs/architecture/frontend-detection.md b/docs/architecture/frontend-detection.md index 62b6c1c8..4360589d 100644 --- a/docs/architecture/frontend-detection.md +++ b/docs/architecture/frontend-detection.md @@ -68,7 +68,7 @@ The `FrontendDetector` uses the following priority order: 2. Path-based detection: - /admin/* or /api/v1/admin/* → ADMIN - /store/* or /api/v1/store/* → STORE - - /storefront/*, /shop/*, /stores/* → STOREFRONT + - /storefront/*, /stores/* → STOREFRONT - /api/v1/platform/* → PLATFORM 3. Store subdomain (orion.omsflow.lu) → STOREFRONT 4. Store context set by middleware → STOREFRONT @@ -88,8 +88,8 @@ STORE_PATH_PREFIXES = ("/store/", "/api/v1/store") STOREFRONT_PATH_PREFIXES = ( "/storefront", "/api/v1/storefront", - "/shop", # Legacy support - "/api/v1/shop", # Legacy support + "/shop", # Legacy support (redirects to /storefront) + "/api/v1/shop", # Legacy support (redirects to /api/v1/storefront) "/stores/", # Path-based store access ) diff --git a/docs/architecture/language-i18n.md b/docs/architecture/language-i18n.md index b7f1b893..d9e6554a 100644 --- a/docs/architecture/language-i18n.md +++ b/docs/architecture/language-i18n.md @@ -186,7 +186,7 @@ function languageSelector(currentLang, enabledLanguages) { ### Rule FE-003: Language Selector Must Be In Shared JavaScript **Language selector function MUST be defined in:** -- `static/shop/js/shop-layout.js` for storefront +- `static/storefront/js/storefront-layout.js` for storefront - `static/store/js/init-alpine.js` for store dashboard ```javascript @@ -444,7 +444,7 @@ static/locales/ "title": "Tableau de bord" } }, - "shop": { + "storefront": { "cart": { "empty": "Votre panier est vide" } @@ -466,7 +466,7 @@ static/locales/ ### Language Selector Implementation Checklist -- [ ] Function defined in appropriate JS file (`shop-layout.js` or `init-alpine.js`) +- [ ] Function defined in appropriate JS file (`storefront-layout.js` or `init-alpine.js`) - [ ] Function exported to `window.languageSelector` - [ ] Uses `tojson|safe` filter for language array - [ ] Provides default values for both parameters @@ -492,14 +492,14 @@ static/locales/ | File | Type | Notes | |------|------|-------| -| `static/shop/js/shop-layout.js` | JS | `languageSelector()` for storefront | +| `static/storefront/js/storefront-layout.js` | JS | `languageSelector()` for storefront | | `app/modules/core/static/store/js/init-alpine.js` | JS | `languageSelector()` for store dashboard | | `app/modules/core/static/admin/js/init-alpine.js` | JS | `languageSelector()` for admin | | `app/modules/core/static/merchant/js/init-alpine.js` | JS | `languageSelector()` for merchant | | `app/templates/store/partials/header.html` | Template | Store dashboard language selector | | `app/templates/admin/partials/header.html` | Template | Admin language selector | | `app/templates/merchant/partials/header.html` | Template | Merchant language selector | -| `app/templates/shop/base.html` | Template | Storefront language selector | +| `app/templates/storefront/base.html` | Template | Storefront language selector | | `app/modules/core/routes/api/platform.py` | API | Language endpoints (`/api/v1/platform/language/*`) | | `middleware/language.py` | Middleware | Language detection per frontend type | | `static/locales/*.json` | JSON | Translation files | diff --git a/docs/architecture/middleware.md b/docs/architecture/middleware.md index 9473df9c..f366cba7 100644 --- a/docs/architecture/middleware.md +++ b/docs/architecture/middleware.md @@ -8,7 +8,7 @@ The application uses a custom middleware stack that processes **every request** - REST API calls (`/api/*`) - Admin interface pages (`/admin/*`) - Store dashboard pages (`/store/*`) -- Shop pages (`/shop/*` or custom domains) +- Storefront pages (`/storefront/*` or custom domains) This middleware layer is **system-wide** and enables the multi-tenant architecture to function seamlessly. @@ -89,7 +89,7 @@ INFO Response: 200 for GET /admin/dashboard (0.143s) ### 2. Store Context Middleware -**Purpose**: Detect which store's shop the request is for (multi-tenant core) +**Purpose**: Detect which store's storefront the request is for (multi-tenant core) **What it does**: - Detects store from: @@ -103,7 +103,7 @@ INFO Response: 200 for GET /admin/dashboard (0.143s) **Example**: ``` -Request: https://orion.platform.com/shop/products +Request: https://orion.platform.com/storefront/products ↓ Middleware detects: store_code = "orion" ↓ @@ -111,14 +111,14 @@ Queries database: SELECT * FROM stores WHERE code = 'orion' ↓ Injects: request.state.store = request.state.store_id = 1 - request.state.clean_path = "/shop/products" + request.state.clean_path = "/storefront/products" ``` **Why it's critical**: Without this, the system wouldn't know which store's data to show **See**: [Multi-Tenant System](multi-tenant.md) for routing modes -**Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where shop routes are registered twice with different prefixes (`/shop` and `/stores/{store_code}/shop`). This approach is simpler and uses FastAPI's native routing capabilities. +**Note on Path-Based Routing:** Previous implementations used a `PathRewriteMiddleware` to rewrite paths at runtime. This has been replaced with **double router mounting** in `main.py`, where storefront routes are registered twice with different prefixes (`/storefront` and `/stores/{store_code}/storefront`). This approach is simpler and uses FastAPI's native routing capabilities. ### 3. Frontend Type Detection Middleware @@ -139,7 +139,7 @@ Injects: request.state.store = 2. Path-based detection: - /admin/* or /api/v1/admin/* → ADMIN - /store/* or /api/v1/store/* → STORE - - /storefront/*, /shop/*, /stores/* → STOREFRONT + - /storefront/*, /stores/* → STOREFRONT - /api/v1/platform/* → PLATFORM 3. Store subdomain (orion.omsflow.lu) → STOREFRONT 4. Store context set by middleware → STOREFRONT @@ -171,7 +171,7 @@ Injects: request.state.store = } ``` -**Why it's needed**: Each store shop can have custom branding +**Why it's needed**: Each store storefront can have custom branding ## Naming Conventions @@ -311,7 +311,7 @@ graph TD **Breaking this order will break the application!** -**Note:** Path-based routing (e.g., `/stores/{code}/shop/*`) is handled by double router mounting in `main.py`, not by middleware. Platform path-based routing (e.g., `/platforms/oms/`) IS handled by PlatformContextMiddleware which rewrites the path. +**Note:** Path-based routing (e.g., `/stores/{code}/storefront/*`) is handled by double router mounting in `main.py`, not by middleware. Platform path-based routing (e.g., `/platforms/oms/`) IS handled by PlatformContextMiddleware which rewrites the path. ## Request State Variables @@ -332,7 +332,7 @@ Middleware components inject these variables into `request.state`: ```python from fastapi import Request -@app.get("/shop/products") +@app.get("/storefront/products") async def get_products(request: Request): # Access store store = request.state.store @@ -374,26 +374,26 @@ async def get_products(request: Request): ## Request Flow Example -### Example: Shop Product Page Request +### Example: Storefront Product Page Request -**URL**: `https://orion.myplatform.com/shop/products` +**URL**: `https://orion.myplatform.com/storefront/products` **Middleware Processing**: ``` 1. LoggingMiddleware ↓ Starts timer - ↓ Logs: "Request: GET /shop/products from 192.168.1.100" + ↓ Logs: "Request: GET /storefront/products from 192.168.1.100" 2. StoreContextMiddleware ↓ Detects subdomain: "orion" ↓ Queries DB: store = get_store_by_code("orion") ↓ Sets: request.state.store = ↓ Sets: request.state.store_id = 1 - ↓ Sets: request.state.clean_path = "/shop/products" + ↓ Sets: request.state.clean_path = "/storefront/products" 3. FrontendTypeMiddleware - ↓ Uses FrontendDetector with path: "/shop/products" + ↓ Uses FrontendDetector with path: "/storefront/products" ↓ Has store context: Yes ↓ Detects storefront frontend ↓ Sets: request.state.frontend_type = FrontendType.STOREFRONT @@ -403,7 +403,7 @@ async def get_products(request: Request): ↓ Sets: request.state.theme = {...theme config...} 5. FastAPI Router - ↓ Matches route: @app.get("/shop/products") + ↓ Matches route: @app.get("/storefront/products") ↓ Calls handler function 6. Route Handler @@ -415,7 +415,7 @@ async def get_products(request: Request): ↓ Returns HTML with store theme 9. LoggingMiddleware (response phase) - ↓ Logs: "Response: 200 for GET /shop/products (0.143s)" + ↓ Logs: "Response: 200 for GET /storefront/products (0.143s)" ↓ Adds header: X-Process-Time: 0.143 ``` @@ -510,9 +510,9 @@ def test_store_detection_subdomain(): Test the full middleware stack: ```python -def test_shop_request_flow(client): +def test_storefront_request_flow(client): response = client.get( - "/shop/products", + "/storefront/products", headers={"Host": "orion.platform.com"} ) diff --git a/docs/architecture/multi-tenant.md b/docs/architecture/multi-tenant.md index a8429009..5318ee29 100644 --- a/docs/architecture/multi-tenant.md +++ b/docs/architecture/multi-tenant.md @@ -4,14 +4,14 @@ Complete guide to the multi-tenant architecture supporting custom domains, subdo ## Overview -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. +The Orion platform supports **three deployment modes** for multi-tenancy, allowing each store to have their own isolated storefront while sharing the same application instance and database. -**Key Concept**: One application, multiple isolated store shops, each accessible via different URLs. +**Key Concept**: One application, multiple isolated store storefronts, each accessible via different URLs. **Important Distinction:** - **Store Dashboard** (all modes): `/store/{code}/*` (singular) - Management interface for stores -- **Shop Storefront** (path-based only): `/stores/{code}/shop/*` (plural) - Customer-facing shop -- This naming distinction helps separate administrative routes from public-facing shop routes +- **Storefront** (path-based only): `/storefront/{code}/*` - Customer-facing storefront +- This naming distinction helps separate administrative routes from public-facing storefront routes ## The Three Routing Modes @@ -21,16 +21,16 @@ The Orion platform supports **three deployment modes** for multi-tenancy, allowi **Example**: ``` -customdomain1.com → Store 1 Shop -anothershop.com → Store 2 Shop -beststore.net → Store 3 Shop +customdomain1.com → Store 1 Storefront +anothershop.com → Store 2 Storefront +beststore.net → Store 3 Storefront ``` **How it works**: 1. Store registers a custom domain 2. Domain's DNS is configured to point to the platform 3. Platform detects store by matching domain in database -4. Store's shop is displayed with their theme/branding +4. Store's storefront is displayed with their theme/branding **Use Case**: Professional stores who want their own branded domain @@ -50,9 +50,9 @@ store_id | domain **Example**: ``` -store1.platform.com → Store 1 Shop -store2.platform.com → Store 2 Shop -store3.platform.com → Store 3 Shop +store1.platform.com → Store 1 Storefront +store2.platform.com → Store 2 Storefront +store3.platform.com → Store 3 Storefront admin.platform.com → Admin Interface ``` @@ -69,9 +69,9 @@ admin.platform.com → Admin Interface # Stores table id | code | name ---|---------|---------- -1 | store1 | Store One Shop -2 | store2 | Store Two Shop -3 | store3 | Store Three Shop +1 | store1 | Store One Storefront +2 | store2 | Store Two Storefront +3 | store3 | Store Three Storefront ``` ### 3. Path-Based Mode @@ -80,9 +80,9 @@ id | code | name **Example**: ``` -platform.com/stores/store1/shop → Store 1 Shop -platform.com/stores/store2/shop → Store 2 Shop -platform.com/stores/store3/shop → Store 3 Shop +platform.com/storefront/store1 → Store 1 Storefront +platform.com/storefront/store2 → Store 2 Storefront +platform.com/storefront/store3 → Store 3 Storefront ``` **How it works**: @@ -94,7 +94,7 @@ platform.com/stores/store3/shop → Store 3 Shop **Use Case**: Development and testing environments only **Path Patterns**: -- `/stores/{code}/shop/*` - Storefront pages (correct pattern) +- `/storefront/{code}/*` - Storefront pages (correct pattern) - `/store/{code}/*` - Store dashboard pages (different context) ## Routing Mode Comparison @@ -107,7 +107,7 @@ platform.com/stores/store3/shop → Store 3 Shop | **SEO Benefits** | Best (own domain) | Good | Limited | | **Cost** | High (domain + SSL) | Low (wildcard SSL) | Lowest | | **Isolation** | Best (separate domain) | Good | Good | -| **URL Appearance** | `shop.com` | `shop.platform.com` | `platform.com/store/shop` | +| **URL Appearance** | `shop.com` | `shop.platform.com` | `platform.com/storefront/store` | ## Implementation Details @@ -133,7 +133,7 @@ def detect_store(request): # 3. Try path-based path = request.url.path - if path.startswith("/store/") or path.startswith("/stores/"): + if path.startswith("/store/") or path.startswith("/storefront/"): store_code = extract_code_from_path(path) store = find_by_code(store_code) if store: @@ -146,11 +146,11 @@ def detect_store(request): For path-based routing, clean paths are extracted: -**Path-Based Shop Routes** (Development): +**Path-Based Storefront Routes** (Development): ``` -Original: /stores/ORION/shop/products +Original: /storefront/ORION/products Extracted: store_code = "ORION" -Clean: /shop/products +Clean: /storefront/products ``` **Store Dashboard Routes** (All environments): @@ -160,11 +160,11 @@ Extracted: store_code = "ORION" Clean: /dashboard ``` -**Note**: The shop storefront uses `/stores/` (plural) while the store dashboard uses `/store/` (singular). This distinction helps separate customer-facing shop routes from store management routes. +**Note**: The storefront uses `/storefront/` while the store dashboard uses `/store/` (singular). This distinction helps separate customer-facing storefront routes from store management routes. **Why Clean Path?** - FastAPI routes don't include store prefix -- Routes defined as: `@app.get("/shop/products")` +- Routes defined as: `@app.get("/storefront/products")` - Path must be rewritten to match routes ## Database Schema @@ -197,7 +197,7 @@ CREATE TABLE store_domains ( ```sql -- Stores INSERT INTO stores (code, name) VALUES - ('orion', 'Orion Shop'), + ('orion', 'Orion Storefront'), ('techstore', 'Tech Store'), ('fashionhub', 'Fashion Hub'); @@ -220,9 +220,9 @@ INSERT INTO store_domains (store_id, domain) VALUES **URLs**: ``` myplatform.com/admin -myplatform.com/stores/shop1/shop -myplatform.com/stores/shop2/shop -myplatform.com/stores/shop3/shop +myplatform.com/storefront/shop1 +myplatform.com/storefront/shop2 +myplatform.com/storefront/shop3 ``` **Infrastructure**: @@ -272,8 +272,8 @@ shop3.myplatform.com → Store 3 shop4.myplatform.com → Store 4 # Path-based (free tier) -myplatform.com/stores/shop5/shop → Store 5 -myplatform.com/stores/shop6/shop → Store 6 +myplatform.com/storefront/shop5 → Store 5 +myplatform.com/storefront/shop6 → Store 6 ``` **Infrastructure**: @@ -391,7 +391,7 @@ static/ **Request**: ```http -GET /shop/products HTTP/1.1 +GET /storefront/products HTTP/1.1 Host: customdomain.com ``` @@ -404,8 +404,8 @@ Host: customdomain.com - Sets: request.state.store = 2. ContextDetectionMiddleware - - Analyzes: path = "/shop/products" - - Sets: context_type = SHOP + - Analyzes: path = "/storefront/products" + - Sets: context_type = STOREFRONT 3. ThemeContextMiddleware - Queries: store_themes WHERE store_id = 1 @@ -420,7 +420,7 @@ Host: customdomain.com **Request**: ```http -GET /shop/products HTTP/1.1 +GET /storefront/products HTTP/1.1 Host: orion.myplatform.com ``` @@ -439,22 +439,22 @@ Host: orion.myplatform.com **Request**: ```http -GET /stores/ORION/shop/products HTTP/1.1 +GET /storefront/ORION/products HTTP/1.1 Host: myplatform.com ``` **Processing**: ``` 1. StoreContextMiddleware - - Checks: path starts with "/store/" + - Checks: path starts with "/storefront/" - Extracts: code = "ORION" - Queries: stores WHERE code = "ORION" - Sets: request.state.store = - - Sets: request.state.clean_path = "/shop/products" + - Sets: request.state.clean_path = "/storefront/products" 2. FastAPI Router - - Routes registered with prefix: /stores/{store_code}/shop - - Matches: /stores/ORION/shop/products + - Routes registered with prefix: /storefront/{store_code} + - Matches: /storefront/ORION/products - store_code path parameter = "ORION" 3-4. Same as previous examples (Context, Theme middleware) @@ -487,17 +487,17 @@ def test_store_detection_subdomain(): ### Integration Tests ```python -def test_shop_page_multi_tenant(client): +def test_storefront_page_multi_tenant(client): # Test subdomain routing response = client.get( - "/shop/products", + "/storefront/products", headers={"Host": "orion.platform.com"} ) assert "Orion" in response.text # Test different store response = client.get( - "/shop/products", + "/storefront/products", headers={"Host": "techstore.platform.com"} ) assert "Tech Store" in response.text diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 0f93765d..627288f4 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -49,8 +49,8 @@ admin.platform.com → Admin Interface #### Path-Based Mode (Development Only) ``` -platform.com/stores/store1/shop → Store 1 Shop -platform.com/stores/store2/shop → Store 2 Shop +platform.com/storefront/store1 → Store 1 Shop +platform.com/storefront/store2 → Store 2 Shop platform.com/admin → Admin Interface ``` @@ -124,7 +124,7 @@ graph TB F -->|API /api/*| G[API Router] F -->|Admin /admin/*| H[Admin Page Router] F -->|Store /store/*| I[Store Page Router] - F -->|Shop /shop/*| J[Shop Page Router] + F -->|Shop /storefront/*| J[Shop Page Router] G --> K[JSON Response] H --> L[Admin HTML] @@ -163,7 +163,7 @@ graph TB **Access**: Store users (owners and team members) -### Shop Interface (`/shop/*` or custom domains) +### Shop Interface (`/storefront/*` or custom domains) **Purpose**: Customer-facing storefront diff --git a/docs/architecture/request-flow.md b/docs/architecture/request-flow.md index a3b2c564..bfe867f5 100644 --- a/docs/architecture/request-flow.md +++ b/docs/architecture/request-flow.md @@ -42,7 +42,7 @@ graph TB ```http # Shop page request (subdomain mode) -GET https://orion.platform.com/shop/products +GET https://orion.platform.com/storefront/products Host: orion.platform.com # API request @@ -69,7 +69,7 @@ Host: platform.com start_time = time.time() # Log entry -logger.info(f"Request: GET /shop/products from 192.168.1.100") +logger.info(f"Request: GET /storefront/products from 192.168.1.100") ``` **Output**: Nothing added to `request.state` yet @@ -87,7 +87,7 @@ logger.info(f"Request: GET /shop/products from 192.168.1.100") ```python # Input host = "orion.platform.com" -path = "/shop/products" +path = "/storefront/products" # Detection logic if host != settings.platform_domain: @@ -102,14 +102,14 @@ if host != settings.platform_domain: # Set request state request.state.store = store request.state.store_id = store.id - request.state.clean_path = "/shop/products" # Already clean + request.state.clean_path = "/storefront/products" # Already clean ``` **Request State After**: ```python request.state.store = request.state.store_id = 1 -request.state.clean_path = "/shop/products" +request.state.clean_path = "/storefront/products" ``` ### 4. Router Matching (FastAPI Native) @@ -117,18 +117,18 @@ request.state.clean_path = "/shop/products" **What happens**: - FastAPI matches the request path against registered routes - For path-based development mode, routes are registered with two prefixes: - - `/shop/*` for subdomain/custom domain - - `/stores/{store_code}/shop/*` for path-based development + - `/storefront/*` for subdomain/custom domain + - `/stores/{store_code}/storefront/*` for path-based development **Example** (Path-Based Mode): ```python # In main.py - Double router mounting -app.include_router(shop_pages.router, prefix="/shop") -app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") +app.include_router(storefront_pages.router, prefix="/storefront") +app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefront") -# Request: /stores/ORION/shop/products -# Matches: Second router (/stores/{store_code}/shop) +# Request: /stores/ORION/storefront/products +# Matches: Second router (/stores/{store_code}/storefront) # Route: @router.get("/products") # store_code available as path parameter = "ORION" ``` @@ -148,7 +148,7 @@ app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") ```python host = request.headers.get("host", "") -path = request.state.clean_path # "/shop/products" +path = request.state.clean_path # "/storefront/products" has_store = hasattr(request.state, 'store') and request.state.store # FrontendDetector handles all detection logic centrally @@ -210,11 +210,11 @@ request.state.theme = { **Route Matching**: ```python -# Request path (after rewrite): "/shop/products" +# Request path (after rewrite): "/storefront/products" # Matches this route -@app.get("/shop/products") -async def get_shop_products(request: Request): +@app.get("/storefront/products") +async def get_storefront_products(request: Request): # Handler code pass ``` @@ -254,7 +254,7 @@ async def shop_products_page( ### 9. Template Rendering (Jinja2) -**Template** (`templates/shop/products.html`): +**Template** (`templates/storefront/products.html`): ```jinja2 @@ -322,7 +322,7 @@ async def shop_products_page( duration = time.time() - start_time # 0.143 seconds logger.info( - f"Response: 200 for GET /shop/products (0.143s)" + f"Response: 200 for GET /storefront/products (0.143s)" ) # Add header @@ -397,7 +397,7 @@ sequenceDiagram Template-->>Client: Admin HTML page ``` -### Shop Page Flow (Full Multi-Tenant) +### Storefront Page Flow (Full Multi-Tenant) ```mermaid sequenceDiagram @@ -412,7 +412,7 @@ sequenceDiagram participant DB participant Template - Client->>Logging: GET /shop/products
Host: orion.platform.com + Client->>Logging: GET /storefront/products
Host: orion.platform.com Logging->>Store: Pass request Store->>DB: Query store by subdomain DB-->>Store: Store object @@ -420,7 +420,7 @@ sequenceDiagram Store->>Path: Pass request Note over Path: Path already clean Path->>Context: Pass request - Context->>Context: Detect Shop context + Context->>Context: Detect Storefront context Note over Context: frontend_type = STOREFRONT Context->>Theme: Pass request Theme->>DB: Query theme @@ -431,7 +431,7 @@ sequenceDiagram Handler->>DB: Query products for store DB-->>Handler: Product list Handler->>Template: Render with theme - Template-->>Client: Themed shop HTML + Template-->>Client: Themed storefront HTML ``` ## Request State Timeline @@ -445,14 +445,14 @@ After StoreContextMiddleware: { store: , store_id: 1, - clean_path: "/shop/products" + clean_path: "/storefront/products" } After FrontendTypeMiddleware: { store: , store_id: 1, - clean_path: "/shop/products", + clean_path: "/storefront/products", frontend_type: FrontendType.STOREFRONT } @@ -460,7 +460,7 @@ After ThemeContextMiddleware: { store: , store_id: 1, - clean_path: "/shop/products", + clean_path: "/storefront/products", frontend_type: FrontendType.STOREFRONT, theme: { primary_color: "#3B82F6", diff --git a/docs/architecture/theme-system/overview.md b/docs/architecture/theme-system/overview.md index b2aceae2..491bb7f1 100644 --- a/docs/architecture/theme-system/overview.md +++ b/docs/architecture/theme-system/overview.md @@ -56,7 +56,7 @@ CREATE TABLE store_themes ( store_id INTEGER UNIQUE NOT NULL REFERENCES stores(id) ON DELETE CASCADE, theme_name VARCHAR(100) DEFAULT 'default', is_active BOOLEAN DEFAULT TRUE, - + -- Colors (JSON) colors JSONB DEFAULT '{ "primary": "#6366f1", @@ -66,30 +66,30 @@ CREATE TABLE store_themes ( "text": "#1f2937", "border": "#e5e7eb" }'::jsonb, - + -- Typography font_family_heading VARCHAR(100) DEFAULT 'Inter, sans-serif', font_family_body VARCHAR(100) DEFAULT 'Inter, sans-serif', - + -- Branding logo_url VARCHAR(500), logo_dark_url VARCHAR(500), favicon_url VARCHAR(500), banner_url VARCHAR(500), - + -- Layout layout_style VARCHAR(50) DEFAULT 'grid', header_style VARCHAR(50) DEFAULT 'fixed', product_card_style VARCHAR(50) DEFAULT 'modern', - + -- Customization custom_css TEXT, social_links JSONB DEFAULT '{}'::jsonb, - + -- Meta meta_title_template VARCHAR(200), meta_description TEXT, - + -- Timestamps created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() @@ -122,7 +122,7 @@ from sqlalchemy.orm import relationship class Store(Base): # ... existing fields ... - + # Add theme relationship theme = relationship( "StoreTheme", @@ -130,7 +130,7 @@ class Store(Base): uselist=False, # One-to-one relationship cascade="all, delete-orphan" ) - + @property def active_theme(self): """Get store's active theme or return None""" @@ -161,7 +161,7 @@ app.middleware("http")(theme_context_middleware) ### Step 5: Create Shop Base Template -File: `app/templates/shop/base.html` +File: `app/templates/storefront/base.html` See complete template in `/home/claude/shop_base_template.html` @@ -184,7 +184,7 @@ See complete template in `/home/claude/shop_base_template.html` ### Step 6: Create Shop Layout JavaScript -File: `static/shop/js/shop-layout.js` +File: `static/storefront/js/shop-layout.js` See complete code in `/home/claude/shop_layout.js` @@ -208,13 +208,13 @@ from middleware.theme_context import get_current_theme async def shop_home(request: Request, db: Session = Depends(get_db)): store = request.state.store theme = get_current_theme(request) # or request.state.theme - + # Get products for store products = db.query(Product).filter( Product.store_id == store.id, Product.is_active == True ).all() - + return templates.TemplateResponse("shop/home.html", { "request": request, "store": store, @@ -308,7 +308,7 @@ theme = { border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } - + .product-card:hover { transform: translateY(-4px); box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); @@ -340,7 +340,7 @@ THEME_PRESETS = { "header": "fixed" } }, - + "classic": { "colors": { "primary": "#1e40af", @@ -356,7 +356,7 @@ THEME_PRESETS = { "header": "static" } }, - + "minimal": { "colors": { "primary": "#000000", @@ -372,7 +372,7 @@ THEME_PRESETS = { "header": "transparent" } }, - + "vibrant": { "colors": { "primary": "#f59e0b", @@ -394,16 +394,16 @@ def apply_preset(theme: StoreTheme, preset_name: str): """Apply a preset to a store theme""" if preset_name not in THEME_PRESETS: raise ValueError(f"Unknown preset: {preset_name}") - + preset = THEME_PRESETS[preset_name] - + theme.theme_name = preset_name theme.colors = preset["colors"] theme.font_family_heading = preset["fonts"]["heading"] theme.font_family_body = preset["fonts"]["body"] theme.layout_style = preset["layout"]["style"] theme.header_style = preset["layout"]["header"] - + return theme ``` @@ -420,11 +420,11 @@ def get_store_theme(store_id: int, db: Session = Depends(get_db)): theme = db.query(StoreTheme).filter( StoreTheme.store_id == store_id ).first() - + if not theme: # Return default theme return get_default_theme() - + return theme.to_dict() @@ -435,38 +435,38 @@ def update_store_theme( db: Session = Depends(get_db) ): """Update or create theme for store""" - + theme = db.query(StoreTheme).filter( StoreTheme.store_id == store_id ).first() - + if not theme: theme = StoreTheme(store_id=store_id) db.add(theme) - + # Update fields if "colors" in theme_data: theme.colors = theme_data["colors"] - + if "fonts" in theme_data: theme.font_family_heading = theme_data["fonts"].get("heading") theme.font_family_body = theme_data["fonts"].get("body") - + if "branding" in theme_data: theme.logo_url = theme_data["branding"].get("logo") theme.logo_dark_url = theme_data["branding"].get("logo_dark") theme.favicon_url = theme_data["branding"].get("favicon") - + if "layout" in theme_data: theme.layout_style = theme_data["layout"].get("style") theme.header_style = theme_data["layout"].get("header") - + if "custom_css" in theme_data: theme.custom_css = theme_data["custom_css"] - + db.commit() db.refresh(theme) - + return theme.to_dict() @@ -478,22 +478,22 @@ def apply_theme_preset( ): """Apply a preset theme to store""" from app.core.theme_presets import apply_preset, THEME_PRESETS - + if preset_name not in THEME_PRESETS: raise HTTPException(400, f"Unknown preset: {preset_name}") - + theme = db.query(StoreTheme).filter( StoreTheme.store_id == store_id ).first() - + if not theme: theme = StoreTheme(store_id=store_id) db.add(theme) - + apply_preset(theme, preset_name) db.commit() db.refresh(theme) - + return { "message": f"Applied {preset_name} preset", "theme": theme.to_dict() @@ -636,7 +636,7 @@ Create a marketplace of premium themes: ```python class PremiumTheme(Base): __tablename__ = "premium_themes" - + id = Column(Integer, primary_key=True) name = Column(String(100)) description = Column(Text) @@ -650,7 +650,7 @@ Respect user's system preferences: ```javascript // Detect system dark mode preference -if (window.matchMedia && +if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { this.dark = true; } @@ -662,7 +662,7 @@ Track which themes perform best: ```python class ThemeAnalytics(Base): __tablename__ = "theme_analytics" - + theme_id = Column(Integer, ForeignKey("store_themes.id")) conversion_rate = Column(Numeric(5, 2)) avg_session_duration = Column(Integer) diff --git a/docs/architecture/url-routing/overview.md b/docs/architecture/url-routing/overview.md index 8f887f3b..b5c1cd04 100644 --- a/docs/architecture/url-routing/overview.md +++ b/docs/architecture/url-routing/overview.md @@ -42,6 +42,61 @@ http://localhost:8000/platforms/loyalty/storefront/TECHPRO/cart --- +## Development URL Quick Reference + +All development URLs use `http://localhost:8000` as the base. + +### Login Pages + +| Panel | URL | Description | +|-------|-----|-------------| +| Admin | `/admin/login` | Platform-wide admin panel | +| Merchant | `/merchants/login` | Merchant management panel | +| Store Dashboard | `/platforms/{platform_code}/store/{store_code}/login` | Store staff login | +| Storefront | `/platforms/{platform_code}/storefront/{store_code}/account/login` | Customer login | + +### Key Entry Points + +| Panel | URL | Description | +|-------|-----|-------------| +| Admin Dashboard | `/admin/` | Admin panel home | +| Merchant Dashboard | `/merchants/dashboard` | Merchant panel home | +| Store Dashboard | `/platforms/{platform_code}/store/{store_code}/dashboard` | Store management | +| Storefront Homepage | `/platforms/{platform_code}/storefront/{store_code}/` | Customer-facing store | +| Platform Homepage | `/platforms/{platform_code}/` | Platform marketing site | + +### Full Example (OMS Platform, Store Code "ACME") + +``` +Admin login: http://localhost:8000/admin/login +Merchant login: http://localhost:8000/merchants/login +Store login: http://localhost:8000/platforms/oms/store/ACME/login +Store dashboard: http://localhost:8000/platforms/oms/store/ACME/dashboard +Storefront login: http://localhost:8000/platforms/oms/storefront/ACME/account/login +Storefront homepage: http://localhost:8000/platforms/oms/storefront/ACME/ +Storefront products: http://localhost:8000/platforms/oms/storefront/ACME/products +Storefront cart: http://localhost:8000/platforms/oms/storefront/ACME/cart +Storefront checkout: http://localhost:8000/platforms/oms/storefront/ACME/checkout +Storefront account: http://localhost:8000/platforms/oms/storefront/ACME/account/dashboard +``` + +### API Endpoints + +``` +Admin API: http://localhost:8000/api/v1/admin/... +Store API: http://localhost:8000/api/v1/store/... +Storefront API: http://localhost:8000/api/v1/storefront/... +``` + +### Notes + +- **Admin and Merchant** panels are global — no platform prefix needed. +- **Store and Storefront** panels require the `/platforms/{platform_code}/` prefix in development. This prefix is stripped by `PlatformContextMiddleware` before routing. +- In **production**, storefronts are accessed via subdomain (`acme.omsflow.lu/`) or custom domain. The root path `/` is the storefront. +- The storefront router is **double-mounted** at `/storefront/` and `/storefront/{store_code}/` to support both production and development modes transparently. + +--- + ## Multi-Platform URL Routing Orion supports multiple platforms (OMS, Loyalty, Site Builder), each with its own marketing site and store ecosystem. diff --git a/docs/backend/middleware-reference.md b/docs/backend/middleware-reference.md index 9bf20c58..2e3af19a 100644 --- a/docs/backend/middleware-reference.md +++ b/docs/backend/middleware-reference.md @@ -92,7 +92,7 @@ Centralized class for detecting which frontend a request targets based on URL pa 2. Path-based detection: - `/admin/*`, `/api/v1/admin/*` → ADMIN - `/store/*`, `/api/v1/store/*` → STORE - - `/storefront/*`, `/shop/*`, `/stores/*` → STOREFRONT + - `/storefront/*`, `/stores/*` → STOREFRONT - `/api/v1/platform/*` → PLATFORM 3. Store subdomain → STOREFRONT 4. Store context set → STOREFRONT @@ -210,17 +210,17 @@ Middleware for request/response logging and performance monitoring. **Modern Approach: Double Router Mounting** -Instead of using middleware to rewrite paths, the application registers shop routes **twice** with different prefixes: +Instead of using middleware to rewrite paths, the application registers storefront routes **twice** with different prefixes: ```python # In main.py -app.include_router(shop_pages.router, prefix="/shop") -app.include_router(shop_pages.router, prefix="/stores/{store_code}/shop") +app.include_router(storefront_pages.router, prefix="/storefront") +app.include_router(storefront_pages.router, prefix="/stores/{store_code}/storefront") ``` **How It Works:** -- **Subdomain/Custom Domain Mode**: Routes match `/shop/*` prefix -- **Path-Based Development Mode**: Routes match `/stores/{store_code}/shop/*` prefix +- **Subdomain/Custom Domain Mode**: Routes match `/storefront/*` prefix +- **Path-Based Development Mode**: Routes match `/stores/{store_code}/storefront/*` prefix - FastAPI handles routing naturally without path manipulation - Store code is available as a path parameter when needed diff --git a/docs/backend/overview.md b/docs/backend/overview.md index 49ade15c..df1c24a1 100644 --- a/docs/backend/overview.md +++ b/docs/backend/overview.md @@ -14,13 +14,13 @@ app/ │ ├── v1/ # Version 1 API │ │ ├── admin/ # Admin API endpoints │ │ ├── store/ # Store API endpoints -│ │ └── shop/ # Shop API endpoints +│ │ └── storefront/ # Storefront API endpoints │ └── main.py # API router configuration │ ├── routes/ # Page routes (HTML) │ ├── admin_pages.py # Admin page routes │ ├── store_pages.py # Store page routes -│ └── shop_pages.py # Shop page routes +│ └── storefront_pages.py # Storefront page routes │ ├── services/ # Business logic layer │ ├── admin_service.py @@ -84,7 +84,7 @@ class ProductService: product_service = ProductService() -# Step 3: API Route (app/api/v1/shop/products.py) +# Step 3: API Route (app/api/v1/storefront/products.py) from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from app.core.database import get_db diff --git a/docs/backend/store-in-token-architecture.md b/docs/backend/store-in-token-architecture.md index 70a466d8..3f4077e1 100644 --- a/docs/backend/store-in-token-architecture.md +++ b/docs/backend/store-in-token-architecture.md @@ -365,12 +365,12 @@ The following permission dependencies now use token-based store context: - `require_all_store_permissions()` - Gets store from token, sets `request.state.store` - `get_user_permissions` - Gets store from token, sets `request.state.store` -### Shop Endpoints -Shop endpoints (public, no authentication) still use `require_store_context()`: -- `app/api/v1/shop/products.py` - Uses URL/subdomain/domain detection -- `app/api/v1/shop/cart.py` - Uses URL/subdomain/domain detection +### Storefront Endpoints +Storefront endpoints (public, no authentication) still use `require_store_context()`: +- `app/api/v1/storefront/products.py` - Uses URL/subdomain/domain detection +- `app/api/v1/storefront/cart.py` - Uses URL/subdomain/domain detection -This is correct behavior - shop endpoints need to detect store from the request URL, not from JWT token. +This is correct behavior - storefront endpoints need to detect store from the request URL, not from JWT token. ## Benefits of Store-in-Token diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 793f6232..0f36095c 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -215,7 +215,7 @@ COPY . . # Build Tailwind CSS RUN tailwindcss -i ./static/admin/css/tailwind.css -o ./static/admin/css/tailwind.output.css --minify \ && tailwindcss -i ./static/store/css/tailwind.css -o ./static/store/css/tailwind.output.css --minify \ - && tailwindcss -i ./static/shop/css/tailwind.css -o ./static/shop/css/tailwind.output.css --minify \ + && tailwindcss -i ./static/storefront/css/tailwind.css -o ./static/storefront/css/tailwind.output.css --minify \ && tailwindcss -i ./static/public/css/tailwind.css -o ./static/public/css/tailwind.output.css --minify # Create non-root user diff --git a/docs/deployment/stripe-integration.md b/docs/deployment/stripe-integration.md index 404e5731..d06817ee 100644 --- a/docs/deployment/stripe-integration.md +++ b/docs/deployment/stripe-integration.md @@ -27,22 +27,22 @@ class StorePaymentConfig(Base, TimestampMixin): id = Column(Integer, primary_key=True, index=True) store_id = Column(Integer, ForeignKey("stores.id"), nullable=False, unique=True) - + # Stripe Connect configuration stripe_account_id = Column(String(255)) # Stripe Connect account ID stripe_account_status = Column(String(50)) # pending, active, restricted, inactive stripe_onboarding_url = Column(Text) # Onboarding link for store stripe_dashboard_url = Column(Text) # Store's Stripe dashboard - + # Payment settings accepts_payments = Column(Boolean, default=False) currency = Column(String(3), default="EUR") platform_fee_percentage = Column(Numeric(5, 2), default=2.5) # Platform commission - + # Payout settings payout_schedule = Column(String(20), default="weekly") # daily, weekly, monthly minimum_payout = Column(Numeric(10, 2), default=20.00) - + # Relationships store = relationship("Store", back_populates="payment_config") @@ -58,31 +58,31 @@ class Payment(Base, TimestampMixin): store_id = Column(Integer, ForeignKey("stores.id"), nullable=False) order_id = Column(Integer, ForeignKey("orders.id"), nullable=False) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) - + # Stripe payment details stripe_payment_intent_id = Column(String(255), unique=True, index=True) stripe_charge_id = Column(String(255), index=True) stripe_transfer_id = Column(String(255)) # Transfer to store account - + # Payment amounts (in cents to avoid floating point issues) amount_total = Column(Integer, nullable=False) # Total customer payment amount_store = Column(Integer, nullable=False) # Amount to store amount_platform_fee = Column(Integer, nullable=False) # Platform commission currency = Column(String(3), default="EUR") - + # Payment status status = Column(String(50), nullable=False) # pending, succeeded, failed, refunded payment_method = Column(String(50)) # card, bank_transfer, etc. - + # Metadata stripe_metadata = Column(Text) # JSON string of Stripe metadata failure_reason = Column(Text) refund_reason = Column(Text) - + # Timestamps paid_at = Column(DateTime) refunded_at = Column(DateTime) - + # Relationships store = relationship("Store") order = relationship("Order", back_populates="payment") @@ -109,21 +109,21 @@ class PaymentMethod(Base, TimestampMixin): id = Column(Integer, primary_key=True, index=True) store_id = Column(Integer, ForeignKey("stores.id"), nullable=False) customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False) - + # Stripe payment method details stripe_payment_method_id = Column(String(255), nullable=False, index=True) payment_method_type = Column(String(50), nullable=False) # card, sepa_debit, etc. - + # Card details (if applicable) card_brand = Column(String(50)) # visa, mastercard, etc. card_last4 = Column(String(4)) card_exp_month = Column(Integer) card_exp_year = Column(Integer) - + # Settings is_default = Column(Boolean, default=False) is_active = Column(Boolean, default=True) - + # Relationships store = relationship("Store") customer = relationship("Customer") @@ -138,15 +138,15 @@ class PaymentMethod(Base, TimestampMixin): # Update models/database/order.py class Order(Base, TimestampMixin): # ... existing fields ... - + # Payment integration payment_status = Column(String(50), default="pending") # pending, paid, failed, refunded payment_intent_id = Column(String(255)) # Stripe PaymentIntent ID total_amount_cents = Column(Integer, nullable=False) # Amount in cents - + # Relationships payment = relationship("Payment", back_populates="order", uselist=False) - + @property def total_amount_euros(self): """Convert cents to euros for display.""" @@ -185,25 +185,25 @@ class PaymentService: self.db = db def create_payment_intent( - self, - store_id: int, - order_id: int, + self, + store_id: int, + order_id: int, amount_euros: Decimal, customer_email: str, metadata: Optional[Dict] = None ) -> Dict: """Create Stripe PaymentIntent for store order.""" - + # Get store payment configuration payment_config = self.get_store_payment_config(store_id) if not payment_config.accepts_payments: raise PaymentNotConfiguredException(f"Store {store_id} not configured for payments") - + # Calculate amounts amount_cents = int(amount_euros * 100) platform_fee_cents = int(amount_cents * (payment_config.platform_fee_percentage / 100)) store_amount_cents = amount_cents - platform_fee_cents - + try: # Create PaymentIntent with Stripe Connect payment_intent = stripe.PaymentIntent.create( @@ -222,7 +222,7 @@ class PaymentService: receipt_email=customer_email, description=f"Order payment for store {store_id}" ) - + # Create payment record payment = Payment( store_id=store_id, @@ -236,17 +236,17 @@ class PaymentService: status='pending', stripe_metadata=json.dumps(payment_intent.metadata) ) - + self.db.add(payment) - + # Update order order = self.db.query(Order).filter(Order.id == order_id).first() if order: order.payment_intent_id = payment_intent.id order.payment_status = 'pending' - + self.db.commit() - + return { 'payment_intent_id': payment_intent.id, 'client_secret': payment_intent.client_secret, @@ -255,58 +255,58 @@ class PaymentService: 'platform_fee': platform_fee_cents / 100, 'currency': payment_config.currency } - + except stripe.error.StripeError as e: logger.error(f"Stripe error creating PaymentIntent: {e}") raise PaymentProcessingException(f"Payment processing failed: {str(e)}") def confirm_payment(self, payment_intent_id: str) -> Payment: """Confirm payment and update records.""" - + try: # Retrieve PaymentIntent from Stripe payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id) - + # Find payment record payment = self.db.query(Payment).filter( Payment.stripe_payment_intent_id == payment_intent_id ).first() - + if not payment: raise PaymentNotFoundException(f"Payment not found for intent {payment_intent_id}") - + # Update payment status based on Stripe status if payment_intent.status == 'succeeded': payment.status = 'succeeded' payment.stripe_charge_id = payment_intent.charges.data[0].id if payment_intent.charges.data else None payment.paid_at = datetime.utcnow() - + # Update order status order = self.db.query(Order).filter(Order.id == payment.order_id).first() if order: order.payment_status = 'paid' order.status = 'processing' # Move order to processing - + elif payment_intent.status == 'payment_failed': payment.status = 'failed' payment.failure_reason = payment_intent.last_payment_error.message if payment_intent.last_payment_error else "Unknown error" - + # Update order status order = self.db.query(Order).filter(Order.id == payment.order_id).first() if order: order.payment_status = 'failed' - + self.db.commit() - + return payment - + except stripe.error.StripeError as e: logger.error(f"Stripe error confirming payment: {e}") raise PaymentProcessingException(f"Payment confirmation failed: {str(e)}") def create_store_stripe_account(self, store_id: int, store_data: Dict) -> str: """Create Stripe Connect account for store.""" - + try: # Create Stripe Connect Express account account = stripe.Account.create( @@ -333,27 +333,27 @@ class PaymentService: 'platform': 'multi_tenant_ecommerce' } ) - + # Update or create payment configuration payment_config = self.get_or_create_store_payment_config(store_id) payment_config.stripe_account_id = account.id payment_config.stripe_account_status = account.charges_enabled and account.payouts_enabled and 'active' or 'pending' - + self.db.commit() - + return account.id - + except stripe.error.StripeError as e: logger.error(f"Stripe error creating account: {e}") raise PaymentConfigurationException(f"Failed to create payment account: {str(e)}") def create_onboarding_link(self, store_id: int) -> str: """Create Stripe onboarding link for store.""" - + payment_config = self.get_store_payment_config(store_id) if not payment_config.stripe_account_id: raise PaymentNotConfiguredException("Store does not have Stripe account") - + try: account_link = stripe.AccountLink.create( account=payment_config.stripe_account_id, @@ -361,13 +361,13 @@ class PaymentService: return_url=f"{settings.frontend_url}/store/admin/payments/success", type='account_onboarding', ) - + # Update onboarding URL payment_config.stripe_onboarding_url = account_link.url self.db.commit() - + return account_link.url - + except stripe.error.StripeError as e: logger.error(f"Stripe error creating onboarding link: {e}") raise PaymentConfigurationException(f"Failed to create onboarding link: {str(e)}") @@ -377,28 +377,28 @@ class PaymentService: config = self.db.query(StorePaymentConfig).filter( StorePaymentConfig.store_id == store_id ).first() - + if not config: raise PaymentNotConfiguredException(f"No payment configuration for store {store_id}") - + return config def webhook_handler(self, event_type: str, event_data: Dict) -> None: """Handle Stripe webhook events.""" - + if event_type == 'payment_intent.succeeded': payment_intent_id = event_data['object']['id'] self.confirm_payment(payment_intent_id) - + elif event_type == 'payment_intent.payment_failed': payment_intent_id = event_data['object']['id'] self.confirm_payment(payment_intent_id) - + elif event_type == 'account.updated': # Update store account status account_id = event_data['object']['id'] self.update_store_account_status(account_id, event_data['object']) - + # Add more webhook handlers as needed ``` @@ -426,7 +426,7 @@ async def get_payment_config( ): """Get store payment configuration.""" payment_service = PaymentService(db) - + try: config = payment_service.get_store_payment_config(store.id) return { @@ -454,17 +454,17 @@ async def setup_payments( ): """Set up Stripe payments for store.""" payment_service = PaymentService(db) - + store_data = { "business_name": store.name, "business_email": store.business_email, "business_phone": store.business_phone, **setup_data } - + account_id = payment_service.create_store_stripe_account(store.id, store_data) onboarding_url = payment_service.create_onboarding_link(store.id) - + return { "stripe_account_id": account_id, "onboarding_url": onboarding_url, @@ -481,7 +481,7 @@ async def create_payment_intent( ): """Create payment intent for customer order.""" payment_service = PaymentService(db) - + payment_intent = payment_service.create_payment_intent( store_id=store_id, order_id=payment_data['order_id'], @@ -489,7 +489,7 @@ async def create_payment_intent( customer_email=payment_data['customer_email'], metadata=payment_data.get('metadata', {}) ) - + return payment_intent @@ -500,10 +500,10 @@ async def stripe_webhook( ): """Handle Stripe webhook events.""" import stripe - + payload = await request.body() sig_header = request.headers.get('stripe-signature') - + try: event = stripe.Webhook.construct_event( payload, sig_header, settings.stripe_webhook_secret @@ -512,10 +512,10 @@ async def stripe_webhook( raise HTTPException(status_code=400, detail="Invalid payload") except stripe.error.SignatureVerificationError: raise HTTPException(status_code=400, detail="Invalid signature") - + payment_service = PaymentService(db) payment_service.webhook_handler(event['type'], event['data']) - + return {"status": "success"} ``` @@ -524,7 +524,7 @@ async def stripe_webhook( ### Checkout Process ```javascript -// frontend/js/shop/checkout.js +// frontend/js/storefront/checkout.js class CheckoutManager { constructor(storeId) { this.storeId = storeId; @@ -562,7 +562,7 @@ class CheckoutManager { const { error } = await this.stripe.confirmPayment({ elements: this.elements, confirmParams: { - return_url: `${window.location.origin}/shop/order-confirmation`, + return_url: `${window.location.origin}/storefront/order-confirmation`, receipt_email: orderData.customerEmail } }); @@ -614,4 +614,4 @@ Webhook updates account status to 'active' Store can now accept payments ``` -This integration provides secure, compliant payment processing while maintaining store isolation and enabling proper revenue distribution between stores and the platform. \ No newline at end of file +This integration provides secure, compliant payment processing while maintaining store isolation and enabling proper revenue distribution between stores and the platform. diff --git a/docs/development/architecture-fixes-2026-01.md b/docs/development/architecture-fixes-2026-01.md index b98937c4..10c2c396 100644 --- a/docs/development/architecture-fixes-2026-01.md +++ b/docs/development/architecture-fixes-2026-01.md @@ -157,7 +157,7 @@ def get_categories(...): ### 1.3 Shop Auth API (Password Reset) -**File:** `app/api/v1/shop/auth.py` +**File:** `app/api/v1/storefront/auth.py` **Problem:** Business logic in `forgot_password()` and `reset_password()` endpoints. @@ -391,7 +391,7 @@ async init() { ### 5.2 Shop Base Template -**File:** `app/templates/shop/base.html` +**File:** `app/templates/storefront/base.html` **Problem:** `request.state.language` used without default (LANG-009 violation). @@ -442,12 +442,12 @@ if in_language_names_block and stripped in ("}", "]"): | Rule ID | Description | Fixed Files | |---------|-------------|-------------| | API-001 | Endpoint must use Pydantic models | admin/email_templates.py | -| API-003 | Endpoint must NOT contain business logic | store/settings.py, shop/auth.py | +| API-003 | Endpoint must NOT contain business logic | store/settings.py, storefront/auth.py | | JS-001 | Must use apiClient for API calls | email-templates.js (admin & store) | | JS-002 | Must use Utils.showToast() for notifications | email-templates.js (admin & store) | | JS-003 | Must call parent init for Alpine components | store/email-templates.js | | TPL-015 | Must use correct block names | admin/email-templates.html | -| LANG-009 | Must provide language default | shop/base.html | +| LANG-009 | Must provide language default | storefront/base.html | --- diff --git a/docs/development/auth-dependencies-guide.md b/docs/development/auth-dependencies-guide.md index c1af6b1b..bb616441 100644 --- a/docs/development/auth-dependencies-guide.md +++ b/docs/development/auth-dependencies-guide.md @@ -26,8 +26,8 @@ FastAPI routes use different authentication dependencies based on whether they s | Dependency | Use Case | Accepts | Description | |------------|----------|---------|-------------| -| `get_current_customer_from_cookie_or_header` | HTML pages | Cookie OR Header | For shop account pages like `/shop/account/dashboard` | -| `get_current_customer_api` | API endpoints | Header ONLY | For shop API routes like `/api/v1/shop/cart` | +| `get_current_customer_from_cookie_or_header` | HTML pages | Cookie OR Header | For storefront account pages like `/storefront/account/dashboard` | +| `get_current_customer_api` | API endpoints | Header ONLY | For storefront API routes like `/api/v1/storefront/cart` | | `get_current_customer_optional` | Login pages | Cookie OR Header | Returns `None` instead of raising exception | ## When to Use Which? diff --git a/docs/development/customer-auth-summary.md b/docs/development/customer-auth-summary.md index 85abe413..b188ff54 100644 --- a/docs/development/customer-auth-summary.md +++ b/docs/development/customer-auth-summary.md @@ -16,16 +16,16 @@ ## Key Files ### Created -- `app/templates/shop/account/login.html` -- `app/templates/shop/account/register.html` -- `app/templates/shop/account/forgot-password.html` -- `app/templates/shop/account/dashboard.html` +- `app/templates/storefront/account/login.html` +- `app/templates/storefront/account/register.html` +- `app/templates/storefront/account/forgot-password.html` +- `app/templates/storefront/account/dashboard.html` ### Modified -- `app/api/v1/shop/auth.py` - Dynamic cookie paths +- `app/api/v1/storefront/auth.py` - Dynamic cookie paths - `app/api/deps.py` - Customer authentication dependency - `app/services/customer_service.py` - Direct JWT token creation -- `app/routes/shop_pages.py` - Customer type hints +- `app/routes/storefront_pages.py` - Customer type hints - `middleware/store_context.py` - Harmonized detection methods ## Critical Architecture Decision @@ -41,10 +41,10 @@ JWT tokens have `type: "customer"` to distinguish them. ```python # Domain/Subdomain access -cookie_path = "/shop" +cookie_path = "/storefront" -# Path-based access (/stores/orion/shop) -cookie_path = f"/stores/{store_code}/shop" +# Path-based access (/storefront/orion) +cookie_path = f"/storefront/{store_code}" ``` ## Authentication Flow @@ -58,7 +58,7 @@ cookie_path = f"/stores/{store_code}/shop" ## Logout Flow 1. User clicks "Logout" button → Custom Tailwind modal appears -2. User confirms → API call to `/api/v1/shop/auth/logout` +2. User confirms → API call to `/api/v1/storefront/auth/logout` 3. Cookie deleted, localStorage cleared 4. Success toast shown, redirect to login page @@ -68,9 +68,9 @@ cookie_path = f"/stores/{store_code}/shop" ``` # Path-based access -http://localhost:8000/stores/orion/shop/account/login -http://localhost:8000/stores/orion/shop/account/register -http://localhost:8000/stores/orion/shop/account/dashboard +http://localhost:8000/storefront/orion/account/login +http://localhost:8000/storefront/orion/account/register +http://localhost:8000/storefront/orion/account/dashboard ``` ## Next Steps (TODO) diff --git a/docs/development/customer-authentication-implementation.md b/docs/development/customer-authentication-implementation.md index 9d197ba9..a6a98540 100644 --- a/docs/development/customer-authentication-implementation.md +++ b/docs/development/customer-authentication-implementation.md @@ -5,11 +5,11 @@ ## Overview -This document describes the implementation of customer authentication for the shop frontend, including login, registration, and account management pages. This work creates a complete separation between customer authentication and admin/store authentication systems. +This document describes the implementation of customer authentication for the storefront frontend, including login, registration, and account management pages. This work creates a complete separation between customer authentication and admin/store authentication systems. ## Problem Statement -The shop frontend needed proper authentication pages (login, registration, forgot password) and a working customer authentication system. The initial implementation had several issues: +The storefront frontend needed proper authentication pages (login, registration, forgot password) and a working customer authentication system. The initial implementation had several issues: 1. No styled authentication pages for customers 2. Customer authentication was incorrectly trying to use the User model (admins/stores) @@ -27,7 +27,7 @@ The shop frontend needed proper authentication pages (login, registration, forgo - Have `username` field - Managed via `app/services/auth_service.py` -- **Customers** (`models/database/customer.py`): Shop customers +- **Customers** (`models/database/customer.py`): Storefront customers - Store-scoped (each store has independent customers) - No `role` or `username` fields - Have `customer_number`, `total_orders`, store relationship @@ -56,9 +56,9 @@ Cookies must be set with paths that match how the store is accessed: | Access Method | Example URL | Cookie Path | |--------------|-------------|-------------| -| Domain | `orion.lu/shop/account/login` | `/shop` | -| Subdomain | `orion.localhost/shop/account/login` | `/shop` | -| Path-based | `localhost/stores/orion/shop/account/login` | `/stores/orion/shop` | +| Domain | `orion.lu/storefront/account/login` | `/storefront` | +| Subdomain | `orion.localhost/storefront/account/login` | `/storefront` | +| Path-based | `localhost/stores/orion/storefront/account/login` | `/storefront/orion` | This ensures cookies are only sent to the correct store's routes. @@ -66,32 +66,32 @@ This ensures cookies are only sent to the correct store's routes. ### Files Created -1. **`app/templates/shop/account/login.html`** +1. **`app/templates/storefront/account/login.html`** - Customer login page - - Extends `shop/base.html` (follows design pattern) + - Extends `storefront/base.html` (follows design pattern) - Uses Tailwind CSS and Alpine.js - Theme-aware styling with CSS variables - Two-column layout (branding + form) - Form validation and error handling -2. **`app/templates/shop/account/register.html`** +2. **`app/templates/storefront/account/register.html`** - Customer registration page - Fields: first_name, last_name, email, phone (optional), password - Client-side validation - Marketing consent checkbox - Theme integration -3. **`app/templates/shop/account/forgot-password.html`** +3. **`app/templates/storefront/account/forgot-password.html`** - Password reset request page - Two-state UI (form → success) - Email validation -4. **`app/templates/shop/account/dashboard.html`** +4. **`app/templates/storefront/account/dashboard.html`** - Customer account dashboard - Displays account summary, order statistics - Quick links to orders, profile, addresses - Logout functionality - - Follows shop design pattern (extends base.html) + - Follows storefront design pattern (extends base.html) ### Important Schema Change @@ -117,7 +117,7 @@ class UserLogin(BaseModel): **Impact**: This change affects all login endpoints: - Admin login: `/api/v1/admin/auth/login` - Store login: `/api/v1/store/auth/login` -- Customer login: `/api/v1/shop/auth/login` +- Customer login: `/api/v1/storefront/auth/login` **Updated Files**: - `app/services/auth_service.py` - Changed `user_credentials.username` to `user_credentials.email_or_username` @@ -127,7 +127,7 @@ class UserLogin(BaseModel): ### Files Modified -#### 1. `app/api/v1/shop/auth.py` +#### 1. `app/api/v1/storefront/auth.py` **Changes**: - Added `CustomerLoginResponse` model (uses `CustomerResponse` instead of `UserResponse`) @@ -143,11 +143,11 @@ class UserLogin(BaseModel): store_context = getattr(request.state, 'store_context', None) access_method = store_context.get('detection_method', 'unknown') if store_context else 'unknown' -cookie_path = "/shop" # Default for domain/subdomain access +cookie_path = "/storefront" # Default for domain/subdomain access if access_method == "path": - # For path-based access like /stores/orion/shop + # For path-based access like /storefront/orion full_prefix = store_context.get('full_prefix', '/store/') if store_context else '/store/' - cookie_path = f"{full_prefix}{store.subdomain}/shop" + cookie_path = f"/storefront/{store.subdomain}" response.set_cookie( key="customer_token", @@ -232,7 +232,7 @@ def get_current_customer_from_cookie_or_header( return customer # Returns Customer, not User ``` -#### 4. `app/routes/shop_pages.py` +#### 4. `app/routes/storefront_pages.py` **Changes**: - Changed import from `User` to `Customer` @@ -300,35 +300,35 @@ The implementation properly supports all three store access methods: #### Domain-based Access ``` -URL: https://orion.lu/shop/account/login -Cookie Path: /shop -Cookie Sent To: https://orion.lu/shop/* +URL: https://orion.lu/storefront/account/login +Cookie Path: /storefront +Cookie Sent To: https://orion.lu/storefront/* ``` #### Subdomain-based Access ``` -URL: https://orion.myplatform.com/shop/account/login -Cookie Path: /shop -Cookie Sent To: https://orion.myplatform.com/shop/* +URL: https://orion.myplatform.com/storefront/account/login +Cookie Path: /storefront +Cookie Sent To: https://orion.myplatform.com/storefront/* ``` #### Path-based Access ``` -URL: https://myplatform.com/stores/orion/shop/account/login -Cookie Path: /stores/orion/shop -Cookie Sent To: https://myplatform.com/stores/orion/shop/* +URL: https://myplatform.com/storefront/orion/account/login +Cookie Path: /storefront/orion +Cookie Sent To: https://myplatform.com/storefront/orion/* ``` ## Authentication Flow ### Login Flow -1. **User loads login page** → `GET /stores/orion/shop/account/login` +1. **User loads login page** → `GET /storefront/orion/account/login` - Middleware detects store from path - Sets `detection_method = "path"` in store_context - Renders login template -2. **User submits credentials** → `POST /api/v1/shop/auth/login` +2. **User submits credentials** → `POST /api/v1/storefront/auth/login` - Middleware detects store from Referer header - Sets `detection_method = "path"` (harmonized!) - Validates credentials via `customer_service.login_customer()` @@ -337,7 +337,7 @@ Cookie Sent To: https://myplatform.com/stores/orion/shop/* - Sets `customer_token` cookie with correct path - Returns token + customer data -3. **Browser redirects to dashboard** → `GET /stores/orion/shop/account/dashboard` +3. **Browser redirects to dashboard** → `GET /storefront/orion/account/dashboard` - Browser sends `customer_token` cookie (path matches!) - Dependency `get_current_customer_from_cookie_or_header` extracts token - Decodes JWT, validates `type == "customer"` @@ -352,7 +352,7 @@ Cookie Sent To: https://myplatform.com/stores/orion/shop/* - Smooth animations with transitions - Dark mode support -2. **User confirms logout** → `POST /api/v1/shop/auth/logout` +2. **User confirms logout** → `POST /api/v1/storefront/auth/logout` - Calculates cookie path (same logic as login) - Deletes cookie with matching path - Returns success message @@ -374,7 +374,7 @@ response.set_cookie( secure=True, # HTTPS only (production/staging) samesite="lax", # CSRF protection max_age=1800, # 30 minutes (matches JWT expiry) - path=cookie_path, # Restricted to store's shop routes + path=cookie_path, # Restricted to store's storefront routes ) ``` @@ -395,10 +395,10 @@ response.set_cookie( ### Frontend Templates -All authentication pages follow the shop template pattern: +All authentication pages follow the storefront template pattern: ```jinja2 -{% extends "shop/base.html" %} +{% extends "storefront/base.html" %} {% block title %}Page Title{% endblock %} @@ -540,9 +540,9 @@ function accountDashboard() { 3. **Session Management**: No refresh tokens, single JWT with 30-minute expiry 4. **Account Pages Are Placeholders**: - - `/account/orders` - needs order history implementation - - `/account/profile` - needs profile editing implementation - - `/account/addresses` - needs address management implementation + - `/storefront/account/orders` - needs order history implementation + - `/storefront/account/profile` - needs profile editing implementation + - `/storefront/account/addresses` - needs address management implementation ## Future Enhancements @@ -580,11 +580,11 @@ function accountDashboard() { - **Customer Model**: `models/database/customer.py` - **Customer Service**: `app/services/customer_service.py` -- **Auth Endpoints**: `app/api/v1/shop/auth.py` +- **Auth Endpoints**: `app/api/v1/storefront/auth.py` - **Auth Dependencies**: `app/api/deps.py` -- **Shop Routes**: `app/routes/shop_pages.py` +- **Storefront Routes**: `app/routes/storefront_pages.py` - **Store Context**: `middleware/store_context.py` -- **Templates**: `app/templates/shop/account/` +- **Templates**: `app/templates/storefront/account/` ## Deployment Notes @@ -604,10 +604,10 @@ No migrations required. Uses existing `customer` table. Ensure these files exist: - `static/shared/js/log-config.js` - `static/shared/js/icons.js` -- `static/shop/js/shop-layout.js` +- `static/storefront/js/storefront-layout.js` - `static/shared/js/utils.js` - `static/shared/js/api-client.js` -- `static/shop/css/shop.css` +- `static/storefront/css/storefront.css` ## Troubleshooting diff --git a/docs/development/database-seeder/database-seeder-documentation.md b/docs/development/database-seeder/database-seeder-documentation.md index 65bc600e..a5694538 100644 --- a/docs/development/database-seeder/database-seeder-documentation.md +++ b/docs/development/database-seeder/database-seeder-documentation.md @@ -217,10 +217,10 @@ python scripts/seed_database.py [--reset] [--minimal] - Username: `admin` - Password: `admin123` -### Store Shops -- ORION: `http://localhost:8000/shop/ORION` -- FASHIONHUB: `http://localhost:8000/shop/FASHIONHUB` -- BOOKSTORE: `http://localhost:8000/shop/BOOKSTORE` +### Store Storefronts +- ORION: `http://localhost:8000/storefront/ORION` +- FASHIONHUB: `http://localhost:8000/storefront/FASHIONHUB` +- BOOKSTORE: `http://localhost:8000/storefront/BOOKSTORE` ### Theme Editors - ORION Theme: `http://localhost:8000/admin/stores/ORION/theme` @@ -383,7 +383,7 @@ This creates 7 platform default pages that all stores inherit: - **Platform Defaults**: Created with `store_id=NULL`, available to all stores - **Store Overrides**: Stores can create custom versions with the same slug - **Automatic Fallback**: System checks store override first, falls back to platform default -- **Navigation**: Pages marked with `show_in_footer` appear in shop footer automatically +- **Navigation**: Pages marked with `show_in_footer` appear in storefront footer automatically - **Idempotent**: Script skips pages that already exist **Access URLs:** diff --git a/docs/development/error-rendering/error-rendering-developer-documentation.md b/docs/development/error-rendering/error-rendering-developer-documentation.md index b566d703..c47aa717 100644 --- a/docs/development/error-rendering/error-rendering-developer-documentation.md +++ b/docs/development/error-rendering/error-rendering-developer-documentation.md @@ -2,7 +2,7 @@ **Version:** 1.0.0 **Last Updated:** 2025 -**Status:** Phase 1 Complete (Admin), Phase 2-3 Pending (Store, Shop) +**Status:** Phase 1 Complete (Admin), Phase 2-3 Pending (Store, Storefront) --- @@ -27,16 +27,16 @@ ### Purpose -The error handling system provides context-aware error responses throughout the application. It automatically determines whether to return JSON (for API calls) or HTML error pages (for browser requests), and renders appropriate error pages based on the request context (Admin, Store Dashboard, or Shop). +The error handling system provides context-aware error responses throughout the application. It automatically determines whether to return JSON (for API calls) or HTML error pages (for browser requests), and renders appropriate error pages based on the request context (Admin, Store Dashboard, or Storefront). ### Key Features -- **Context-Aware**: Different error pages for Admin, Store, and Shop areas +- **Context-Aware**: Different error pages for Admin, Store, and Storefront areas - **Automatic Detection**: Distinguishes between API and HTML page requests - **Consistent JSON API**: API endpoints always return standardized JSON errors - **Fallback Mechanism**: Gracefully handles missing templates - **Debug Mode**: Shows technical details to admin users only -- **Theme Integration**: Shop error pages support store theming (Phase 3) +- **Theme Integration**: Storefront error pages support store theming (Phase 3) - **Security**: 401 errors automatically redirect to appropriate login pages ### Design Principles @@ -58,7 +58,7 @@ HTTP Request ↓ Store Context Middleware (detects store from domain/subdomain/path) ↓ -Context Detection Middleware (detects API/Admin/Store/Shop) +Context Detection Middleware (detects API/Admin/Store/Storefront) ↓ Route Handler (processes request, may throw exception) ↓ @@ -84,7 +84,7 @@ app/exceptions/ app/templates/ ├── admin/errors/ # Admin error pages (Phase 1 - COMPLETE) ├── store/errors/ # Store error pages (Phase 2 - PENDING) -├── shop/errors/ # Shop error pages (Phase 3 - PENDING) +├── storefront/errors/ # Storefront error pages (Phase 3 - PENDING) └── shared/ # Shared fallback error pages (COMPLETE) ``` @@ -169,8 +169,8 @@ ErrorPageRenderer.render_error_page( "show_debug": bool, # Whether to show debug info "context_type": str, # Request context type "path": str, # Request path - "store": dict, # Store info (shop context only) - "theme": dict, # Theme data (shop context only) + "store": dict, # Store info (storefront context only) + "theme": dict, # Theme data (storefront context only) } ``` @@ -223,7 +223,7 @@ if path.startswith("/admin") or host.startswith("admin."): if path.startswith("/store/"): return RequestContext.STORE_DASHBOARD -# Priority 4: Shop Context +# Priority 4: Storefront Context if hasattr(request.state, 'store') and request.state.store: return RequestContext.SHOP @@ -255,7 +255,7 @@ admin.platform.com/dashboard → ADMIN context store1.platform.com/store/dashboard → STORE_DASHBOARD context ``` -**Shop Access:** +**Storefront Access:** ``` store1.platform.com/ → SHOP context customdomain.com/ → SHOP context (if store verified) @@ -320,7 +320,7 @@ API endpoints MUST always return JSON, even if the client sends `Accept: text/ht |---------|-------------| | ADMIN | `/admin/login` | | STORE_DASHBOARD | `/store/login` | -| SHOP | `/shop/login` | +| SHOP | `/storefront/login` | | FALLBACK | `/admin/login` | --- @@ -350,7 +350,7 @@ app/templates/ │ └── errors/ │ └── (same structure as admin) │ -├── shop/ +├── storefront/ │ └── errors/ │ └── (same structure as admin) │ @@ -446,7 +446,7 @@ Each context has its own `base.html` template: - Primary: "Go to Dashboard" → `/store/dashboard` - Secondary: "Go Back" → `javascript:history.back()` -#### Shop Error Pages +#### Storefront Error Pages **Purpose:** Error pages for customers on store storefronts @@ -454,16 +454,16 @@ Each context has its own `base.html` template: - **Uses store theme** (colors, logo, fonts) - Customer-friendly language - No technical jargon -- Links to shop homepage +- Links to storefront homepage - Customer support contact **Action Buttons:** -- Primary: "Continue Shopping" → Shop homepage +- Primary: "Continue Shopping" → Storefront homepage - Secondary: "Contact Support" → Store support page **Theme Integration:** ```html - +