docs: migrate module documentation to single source of truth
Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
160
app/modules/tenancy/docs/data-model.md
Normal file
160
app/modules/tenancy/docs/data-model.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Tenancy Data Model
|
||||
|
||||
## Entity Relationship Overview
|
||||
|
||||
```
|
||||
┌──────────────┐
|
||||
│ Platform │
|
||||
└──────┬───────┘
|
||||
│ 1:N
|
||||
▼
|
||||
┌──────────────┐ ┌─────────────────┐
|
||||
│ Merchant │────▶│ MerchantDomain │
|
||||
└──────┬───────┘ 1:N └─────────────────┘
|
||||
│ 1:N
|
||||
▼
|
||||
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
|
||||
│ Store │────▶│ StoreUser │────▶│ Role │
|
||||
└──────┬───────┘ 1:N └──────┬──────┘ └──────────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────┐
|
||||
│ │ User │
|
||||
│ └──────────────┘
|
||||
│
|
||||
├────▶ StoreDomain (1:N)
|
||||
└────▶ StorePlatform (N:M bridge to Platform)
|
||||
```
|
||||
|
||||
## Core Entities
|
||||
|
||||
### Platform
|
||||
|
||||
Top-level SaaS instance. All merchants and stores belong to a platform.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `name` | String | Platform name |
|
||||
| `code` | String | Unique platform code |
|
||||
| `is_active` | Boolean | Platform enabled/disabled |
|
||||
|
||||
### Merchant
|
||||
|
||||
Business entity that owns one or more stores.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `name` | String | Business name |
|
||||
| `owner_user_id` | Integer | FK to User — the merchant owner |
|
||||
| `contact_email` | String | Primary contact email |
|
||||
| `is_active` | Boolean | Merchant active status |
|
||||
|
||||
### Store
|
||||
|
||||
A storefront belonging to a merchant. Central entity for multi-tenant scoping.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `merchant_id` | Integer | FK to Merchant |
|
||||
| `name` | String | Store name |
|
||||
| `code` | String | Unique store code (used in URLs) |
|
||||
| `is_active` | Boolean | Store active status |
|
||||
|
||||
### User
|
||||
|
||||
Admin users at platform, merchant, or store level.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `email` | String | Unique email |
|
||||
| `username` | String | Username |
|
||||
| `role` | UserRole | `super_admin`, `platform_admin`, `merchant_owner`, `store_member` |
|
||||
| `hashed_password` | String | Bcrypt password hash |
|
||||
| `is_active` | Boolean | Account active status |
|
||||
|
||||
### StoreUser
|
||||
|
||||
Membership link between users and stores, with role assignment.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `store_id` | Integer | FK to Store |
|
||||
| `user_id` | Integer | FK to User |
|
||||
| `role_id` | Integer | FK to Role (NULL for owners) |
|
||||
| `is_active` | Boolean | Membership active |
|
||||
| `invitation_token` | String | Pending invitation token |
|
||||
| `invitation_accepted_at` | DateTime | When invitation was accepted |
|
||||
|
||||
### Role
|
||||
|
||||
Permission roles for store team members.
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `id` | Integer | Primary key |
|
||||
| `store_id` | Integer | FK to Store |
|
||||
| `name` | String | Role name (Manager, Staff, etc.) |
|
||||
| `permissions` | JSON | Array of permission strings |
|
||||
|
||||
## Supporting Entities
|
||||
|
||||
### AdminPlatform
|
||||
|
||||
Links admin users to platforms they can manage.
|
||||
|
||||
### StorePlatform
|
||||
|
||||
N:M bridge between stores and platforms.
|
||||
|
||||
### StoreDomain
|
||||
|
||||
Custom domain configuration for stores.
|
||||
|
||||
### MerchantDomain
|
||||
|
||||
Custom domain configuration for merchants.
|
||||
|
||||
### EmailVerificationToken
|
||||
|
||||
Tokens for verifying email addresses during registration.
|
||||
|
||||
### UserPasswordResetToken
|
||||
|
||||
Tokens for password reset flow.
|
||||
|
||||
### AdminAuditLog
|
||||
|
||||
Audit trail for admin actions.
|
||||
|
||||
### AdminSession
|
||||
|
||||
Active admin login sessions.
|
||||
|
||||
### AdminSetting
|
||||
|
||||
Platform/store-level admin settings (key-value).
|
||||
|
||||
### PlatformAlert
|
||||
|
||||
System alerts displayed to platform admins.
|
||||
|
||||
### PlatformModule
|
||||
|
||||
Tracks which modules are enabled per platform.
|
||||
|
||||
### ApplicationLog
|
||||
|
||||
Application log entries for the monitoring module.
|
||||
|
||||
## Key Relationships
|
||||
|
||||
- A **Platform** has many **Merchants** (via StorePlatform)
|
||||
- A **Merchant** has many **Stores** and one **owner User**
|
||||
- A **Store** has many **StoreUsers** (team members)
|
||||
- A **StoreUser** has one **Role** (except owners who have all permissions)
|
||||
- **User.role** determines the user type: `super_admin` > `platform_admin` > `merchant_owner` > `store_member`
|
||||
64
app/modules/tenancy/docs/index.md
Normal file
64
app/modules/tenancy/docs/index.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Tenancy Management
|
||||
|
||||
Platform, merchant, store, and admin user management. Required for multi-tenant operation.
|
||||
|
||||
## Overview
|
||||
|
||||
| Aspect | Detail |
|
||||
|--------|--------|
|
||||
| Code | `tenancy` |
|
||||
| Classification | Core |
|
||||
| Dependencies | None |
|
||||
| Status | Active |
|
||||
|
||||
## Features
|
||||
|
||||
- `platform_management` — Platform-level administration
|
||||
- `merchant_management` — Merchant account management
|
||||
- `store_management` — Store configuration and setup
|
||||
- `admin_user_management` — Admin user accounts and team management
|
||||
|
||||
## Permissions
|
||||
|
||||
| Permission | Description |
|
||||
|------------|-------------|
|
||||
| `team.view` | View team members |
|
||||
| `team.invite` | Invite team members (owner only) |
|
||||
| `team.edit` | Edit team members (owner only) |
|
||||
| `team.remove` | Remove team members (owner only) |
|
||||
|
||||
## Data Model
|
||||
|
||||
See [Data Model](data-model.md) for full entity relationships.
|
||||
|
||||
- **Platform** — Top-level SaaS instances
|
||||
- **Merchant** — Business entities that own stores
|
||||
- **Store** — Storefronts with multi-tenant scoping
|
||||
- **User** — Admin users (super_admin, platform_admin, merchant_owner, store_member)
|
||||
- **StoreUser** — Store membership with role assignment
|
||||
- **Role** — Permission roles for team members
|
||||
- **StoreDomain / MerchantDomain** — Custom domain configuration
|
||||
- **EmailVerificationToken** — Email verification tokens
|
||||
|
||||
## API Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| `*` | `/api/v1/admin/auth/*` | Authentication (login, register, password reset) |
|
||||
| `*` | `/api/v1/admin/merchants/*` | Merchant CRUD |
|
||||
| `*` | `/api/v1/admin/modules/*` | Module enable/disable |
|
||||
| `*` | `/api/v1/admin/merchant-domains/*` | Domain management |
|
||||
| `*` | `/api/v1/admin/store-roles/*` | Admin role management |
|
||||
| `*` | `/api/v1/store/team/*` | Team & role management |
|
||||
| `*` | `/api/v1/store/onboarding/*` | Store onboarding wizard |
|
||||
|
||||
## Configuration
|
||||
|
||||
No module-specific configuration.
|
||||
|
||||
## Additional Documentation
|
||||
|
||||
- [Data Model](data-model.md) — Entity relationships and database schema
|
||||
- [RBAC System](rbac.md) — Role-based access control, permissions, pre-defined roles
|
||||
- [Store Onboarding](onboarding.md) — 4-step onboarding wizard
|
||||
- [Migration Plan](migration.md) — Code migration plan from legacy structure
|
||||
243
app/modules/tenancy/docs/migration.md
Normal file
243
app/modules/tenancy/docs/migration.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Tenancy Module Migration Plan
|
||||
|
||||
This document outlines the complete migration plan for the tenancy module, which manages the multi-tenant organizational hierarchy: platforms, merchants, stores, and users.
|
||||
|
||||
## Tenancy Module Domain
|
||||
|
||||
The tenancy module owns **identity and organizational hierarchy**:
|
||||
|
||||
- **Platforms** - Top-level SaaS instances
|
||||
- **Merchants** - Business entities that own stores
|
||||
- **Stores** - Storefronts/merchant accounts
|
||||
- **Users** - Admin users, store team members
|
||||
- **Authentication** - Login, tokens, sessions
|
||||
- **Teams** - Store team management
|
||||
- **Domains** - Custom domain configuration
|
||||
|
||||
## Migration Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ CURRENT STATE │
|
||||
│ │
|
||||
│ app/api/v1/admin/ app/api/v1/store/ app/services/ │
|
||||
│ ├── admin_users.py ├── auth.py ├── store_service │
|
||||
│ ├── merchants.py ├── profile.py ├── merchant_service │
|
||||
│ ├── platforms.py ├── team.py ├── platform_service │
|
||||
│ ├── stores.py └── ... ├── auth_service │
|
||||
│ ├── users.py └── ... │
|
||||
│ └── auth.py │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ TARGET STATE │
|
||||
│ │
|
||||
│ app/modules/tenancy/ │
|
||||
│ ├── routes/api/ │
|
||||
│ │ ├── admin.py # All admin tenancy routes │
|
||||
│ │ └── store.py # All store tenancy routes │
|
||||
│ ├── services/ │
|
||||
│ │ ├── store_service.py │
|
||||
│ │ ├── merchant_service.py │
|
||||
│ │ ├── platform_service.py │
|
||||
│ │ ├── auth_service.py │
|
||||
│ │ └── team_service.py │
|
||||
│ ├── models/ │
|
||||
│ │ ├── store.py │
|
||||
│ │ ├── merchant.py │
|
||||
│ │ ├── platform.py │
|
||||
│ │ └── user.py │
|
||||
│ └── schemas/ │
|
||||
│ └── ... │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Migrate to Tenancy
|
||||
|
||||
### Routes (Admin API)
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/api/v1/admin/admin_users.py` | `tenancy/routes/api/admin_users.py` | Admin user CRUD |
|
||||
| `app/api/v1/admin/merchants.py` | `tenancy/routes/api/admin_merchants.py` | Merchant management |
|
||||
| `app/api/v1/admin/platforms.py` | `tenancy/routes/api/admin_platforms.py` | Platform management |
|
||||
| `app/api/v1/admin/stores.py` | `tenancy/routes/api/admin_stores.py` | Store management |
|
||||
| `app/api/v1/admin/store_domains.py` | `tenancy/routes/api/admin_store_domains.py` | Domain configuration |
|
||||
| `app/api/v1/admin/users.py` | `tenancy/routes/api/admin_users.py` | Platform users |
|
||||
| `app/api/v1/admin/auth.py` | `tenancy/routes/api/admin_auth.py` | Admin authentication |
|
||||
|
||||
### Routes (Store API)
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/api/v1/store/auth.py` | `tenancy/routes/api/store_auth.py` | Store authentication |
|
||||
| `app/api/v1/store/profile.py` | `tenancy/routes/api/store_profile.py` | Store profile |
|
||||
| `app/api/v1/store/team.py` | `tenancy/routes/api/store_team.py` | Team management |
|
||||
|
||||
### Services
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/services/store_service.py` | `tenancy/services/store_service.py` | Core store operations |
|
||||
| `app/services/merchant_service.py` | `tenancy/services/merchant_service.py` | Merchant management |
|
||||
| `app/services/platform_service.py` | `tenancy/services/platform_service.py` | Platform management |
|
||||
| `app/services/admin_service.py` | `tenancy/services/admin_service.py` | Admin user operations |
|
||||
| `app/services/admin_platform_service.py` | `tenancy/services/admin_platform_service.py` | Admin-platform relations |
|
||||
| `app/services/store_domain_service.py` | `tenancy/services/store_domain_service.py` | Domain management |
|
||||
| `app/services/store_team_service.py` | `tenancy/services/store_team_service.py` | Team management |
|
||||
| `app/services/team_service.py` | `tenancy/services/team_service.py` | Team operations |
|
||||
| `app/services/auth_service.py` | `tenancy/services/auth_service.py` | Authentication logic |
|
||||
| `app/services/platform_signup_service.py` | `tenancy/services/platform_signup_service.py` | Platform onboarding |
|
||||
|
||||
### Models
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `models/database/store.py` | `tenancy/models/store.py` | Store entity |
|
||||
| `models/database/merchant.py` | `tenancy/models/merchant.py` | Merchant entity |
|
||||
| `models/database/platform.py` | `tenancy/models/platform.py` | Platform entity |
|
||||
| `models/database/admin.py` | `tenancy/models/admin.py` | Admin user entity |
|
||||
| `models/database/admin_platform.py` | `tenancy/models/admin_platform.py` | Admin-Platform relation |
|
||||
| `models/database/store_domain.py` | `tenancy/models/store_domain.py` | Store domains |
|
||||
| `models/database/store_platform.py` | `tenancy/models/store_platform.py` | Store-Platform relation |
|
||||
| `models/database/user.py` | `tenancy/models/user.py` | User base model |
|
||||
|
||||
### Schemas
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `models/schema/store.py` | `tenancy/schemas/store.py` | Store schemas |
|
||||
| `models/schema/merchant.py` | `tenancy/schemas/merchant.py` | Merchant schemas |
|
||||
| `models/schema/platform.py` | `tenancy/schemas/platform.py` | Platform schemas |
|
||||
| `models/schema/admin.py` | `tenancy/schemas/admin.py` | Admin schemas |
|
||||
| `models/schema/auth.py` | `tenancy/schemas/auth.py` | Auth schemas |
|
||||
|
||||
## Files to Keep in ROOT (Framework Level)
|
||||
|
||||
These are **framework infrastructure**, not domain logic:
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `app/api/v1/admin/modules.py` | Module system management |
|
||||
| `app/api/v1/admin/module_config.py` | Module configuration |
|
||||
| `app/api/v1/admin/menu_config.py` | Menu system |
|
||||
| `app/api/v1/admin/settings.py` | System settings |
|
||||
| `models/database/base.py` | SQLAlchemy base |
|
||||
| `models/database/platform_module.py` | Module enablement |
|
||||
| `models/database/admin_menu_config.py` | Menu configuration |
|
||||
|
||||
## Target Module Structure
|
||||
|
||||
After migration, tenancy module will have this structure:
|
||||
|
||||
```
|
||||
app/modules/tenancy/
|
||||
├── __init__.py
|
||||
├── definition.py
|
||||
├── exceptions.py
|
||||
├── config.py # Environment configuration
|
||||
├── routes/
|
||||
│ ├── __init__.py
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── admin.py # Aggregates admin sub-routers
|
||||
│ │ ├── admin_users.py # Admin user management
|
||||
│ │ ├── admin_merchants.py # Merchant management
|
||||
│ │ ├── admin_platforms.py # Platform management
|
||||
│ │ ├── admin_stores.py # Store management
|
||||
│ │ ├── admin_store_domains.py
|
||||
│ │ ├── admin_auth.py # Admin authentication
|
||||
│ │ ├── store.py # Aggregates store sub-routers
|
||||
│ │ ├── store_auth.py # Store authentication
|
||||
│ │ ├── store_profile.py # Store profile
|
||||
│ │ ├── store_team.py # Team management
|
||||
│ │ └── store_info.py # Public store lookup (DONE)
|
||||
│ └── pages/
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py # Admin HTML pages
|
||||
│ └── store.py # Store HTML pages
|
||||
├── services/
|
||||
│ ├── __init__.py
|
||||
│ ├── store_service.py
|
||||
│ ├── merchant_service.py
|
||||
│ ├── platform_service.py
|
||||
│ ├── admin_service.py
|
||||
│ ├── auth_service.py
|
||||
│ ├── team_service.py
|
||||
│ └── store_domain_service.py
|
||||
├── models/
|
||||
│ ├── __init__.py
|
||||
│ ├── store.py
|
||||
│ ├── merchant.py
|
||||
│ ├── platform.py
|
||||
│ ├── admin.py
|
||||
│ ├── user.py
|
||||
│ ├── store_domain.py
|
||||
│ └── store_platform.py
|
||||
├── schemas/
|
||||
│ ├── __init__.py
|
||||
│ ├── store.py
|
||||
│ ├── merchant.py
|
||||
│ ├── platform.py
|
||||
│ ├── admin.py
|
||||
│ └── auth.py
|
||||
├── templates/
|
||||
│ └── tenancy/
|
||||
│ ├── admin/
|
||||
│ └── store/
|
||||
├── static/
|
||||
│ ├── admin/js/
|
||||
│ └── store/js/
|
||||
└── locales/
|
||||
├── en.json
|
||||
├── de.json
|
||||
├── fr.json
|
||||
└── lb.json
|
||||
```
|
||||
|
||||
## Module Ownership Summary
|
||||
|
||||
| Module | Owns | Key Principle |
|
||||
|--------|------|---------------|
|
||||
| **tenancy** | Stores, Merchants, Platforms, Users, Auth, Teams | Identity & organizational hierarchy |
|
||||
| **core** | Dashboard, Settings | Foundational non-domain features |
|
||||
| **messaging** | Messages, Notifications, Email | Communication |
|
||||
| **cms** | Media, Images, Themes, Content | Content management |
|
||||
| **monitoring** | Logs, Health, Tasks, Audit | Observability |
|
||||
| **dev_tools** | Code quality, Tests | Development tools |
|
||||
| **ROOT** | Module system, Menu config, Base models | Framework infrastructure |
|
||||
|
||||
## Migration Order
|
||||
|
||||
Recommended order for migrating tenancy:
|
||||
|
||||
1. **Phase 1: Services** (no route changes)
|
||||
- Move services to `tenancy/services/`
|
||||
- Update imports throughout codebase
|
||||
- Keep re-exports in `app/services/` temporarily
|
||||
|
||||
2. **Phase 2: Models** (careful - many dependencies)
|
||||
- Move models to `tenancy/models/`
|
||||
- Update all imports
|
||||
- May need re-exports in `models/database/`
|
||||
|
||||
3. **Phase 3: Schemas**
|
||||
- Move schemas to `tenancy/schemas/`
|
||||
- Update route imports
|
||||
|
||||
4. **Phase 4: Routes**
|
||||
- Move routes to `tenancy/routes/api/`
|
||||
- Update aggregation in admin/store `__init__.py`
|
||||
- Delete legacy route files
|
||||
|
||||
5. **Phase 5: Cleanup**
|
||||
- Remove re-exports
|
||||
- Delete empty legacy files
|
||||
- Update documentation
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Module System Architecture](../../architecture/module-system.md)
|
||||
- [Module Auto-Discovery Migration](../../development/migration/module-autodiscovery-migration.md)
|
||||
- [Multi-Tenant Architecture](../../architecture/multi-tenant.md)
|
||||
191
app/modules/tenancy/docs/onboarding.md
Normal file
191
app/modules/tenancy/docs/onboarding.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Store Onboarding System
|
||||
|
||||
The store onboarding system is a mandatory 4-step wizard that guides new stores through the initial setup process after signup. Dashboard access is blocked until onboarding is completed.
|
||||
|
||||
## Overview
|
||||
|
||||
The onboarding wizard consists of four sequential steps:
|
||||
|
||||
1. **Merchant Profile Setup** - Basic merchant and contact information
|
||||
2. **Letzshop API Configuration** - Connect to Letzshop marketplace
|
||||
3. **Product & Order Import Configuration** - Set up CSV feed URLs
|
||||
4. **Order Sync** - Import historical orders with progress tracking
|
||||
|
||||
## User Flow
|
||||
|
||||
```
|
||||
Signup Complete
|
||||
↓
|
||||
Redirect to /store/{code}/onboarding
|
||||
↓
|
||||
Step 1: Merchant Profile
|
||||
↓
|
||||
Step 2: Letzshop API (with connection test)
|
||||
↓
|
||||
Step 3: Product Import Config
|
||||
↓
|
||||
Step 4: Order Sync (with progress bar)
|
||||
↓
|
||||
Onboarding Complete
|
||||
↓
|
||||
Redirect to Dashboard
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Mandatory Completion
|
||||
- Dashboard and other protected routes redirect to onboarding if not completed
|
||||
- Admin can skip onboarding for support cases (via admin API)
|
||||
|
||||
### Step Validation
|
||||
- Steps must be completed in order (no skipping ahead)
|
||||
- Each step validates required fields before proceeding
|
||||
|
||||
### Progress Persistence
|
||||
- Onboarding progress is saved in the database
|
||||
- Users can resume from where they left off
|
||||
- Page reload doesn't lose progress
|
||||
|
||||
### Connection Testing
|
||||
- Step 2 includes real-time Letzshop API connection testing
|
||||
- Shows success/failure status before saving credentials
|
||||
|
||||
### Historical Import
|
||||
- Step 4 triggers a background job for order import
|
||||
- Real-time progress bar with polling (2-second intervals)
|
||||
- Shows order count as import progresses
|
||||
|
||||
## Database Model
|
||||
|
||||
### StoreOnboarding Table
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| id | Integer | Primary key |
|
||||
| store_id | Integer | Foreign key to stores (unique) |
|
||||
| status | String(20) | not_started, in_progress, completed, skipped |
|
||||
| current_step | String(30) | Current step identifier |
|
||||
| step_*_completed | Boolean | Completion flag per step |
|
||||
| step_*_completed_at | DateTime | Completion timestamp per step |
|
||||
| skipped_by_admin | Boolean | Admin override flag |
|
||||
| skipped_reason | Text | Reason for skip (admin) |
|
||||
|
||||
### Onboarding Steps Enum
|
||||
|
||||
```python
|
||||
class OnboardingStep(str, enum.Enum):
|
||||
MERCHANT_PROFILE = "merchant_profile"
|
||||
LETZSHOP_API = "letzshop_api"
|
||||
PRODUCT_IMPORT = "product_import"
|
||||
ORDER_SYNC = "order_sync"
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
All endpoints are under `/api/v1/store/onboarding/`:
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/status` | Get full onboarding status |
|
||||
| GET | `/step/merchant-profile` | Get merchant profile data |
|
||||
| POST | `/step/merchant-profile` | Save merchant profile |
|
||||
| POST | `/step/letzshop-api/test` | Test API connection |
|
||||
| POST | `/step/letzshop-api` | Save API credentials |
|
||||
| GET | `/step/product-import` | Get import config |
|
||||
| POST | `/step/product-import` | Save import config |
|
||||
| POST | `/step/order-sync/trigger` | Start historical import |
|
||||
| GET | `/step/order-sync/progress/{job_id}` | Get import progress |
|
||||
| POST | `/step/order-sync/complete` | Complete onboarding |
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Signup Flow
|
||||
|
||||
When a store is created during signup, an onboarding record is automatically created:
|
||||
|
||||
```python
|
||||
# In platform_signup_service.py
|
||||
onboarding_service = OnboardingService(db)
|
||||
onboarding_service.create_onboarding(store.id)
|
||||
```
|
||||
|
||||
### Route Protection
|
||||
|
||||
Protected routes check onboarding status and redirect if not completed:
|
||||
|
||||
```python
|
||||
# In store_pages.py
|
||||
onboarding_service = OnboardingService(db)
|
||||
if not onboarding_service.is_completed(current_user.token_store_id):
|
||||
return RedirectResponse(f"/store/{store_code}/onboarding")
|
||||
```
|
||||
|
||||
### Historical Import
|
||||
|
||||
Step 4 uses the existing `LetzshopHistoricalImportJob` infrastructure:
|
||||
|
||||
```python
|
||||
order_service = LetzshopOrderService(db)
|
||||
job = order_service.create_historical_import_job(store_id, user_id)
|
||||
```
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### Template
|
||||
|
||||
`app/templates/store/onboarding.html`:
|
||||
- Standalone page (doesn't use store base template)
|
||||
- Progress indicator with step circles
|
||||
- Animated transitions between steps
|
||||
- Real-time sync progress bar
|
||||
|
||||
### JavaScript
|
||||
|
||||
`static/store/js/onboarding.js`:
|
||||
- Alpine.js component
|
||||
- API calls for each step
|
||||
- Connection test functionality
|
||||
- Progress polling for order sync
|
||||
|
||||
## Admin Skip Capability
|
||||
|
||||
For support cases, admins can skip onboarding:
|
||||
|
||||
```python
|
||||
onboarding_service.skip_onboarding(
|
||||
store_id=store_id,
|
||||
admin_user_id=admin_user_id,
|
||||
reason="Manual setup required for migration"
|
||||
)
|
||||
```
|
||||
|
||||
This sets `skipped_by_admin=True` and allows dashboard access without completing all steps.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `models/database/onboarding.py` | Database model and enums |
|
||||
| `models/schema/onboarding.py` | Pydantic schemas |
|
||||
| `app/services/onboarding_service.py` | Business logic |
|
||||
| `app/api/v1/store/onboarding.py` | API endpoints |
|
||||
| `app/routes/store_pages.py` | Page routes and redirects |
|
||||
| `app/templates/store/onboarding.html` | Frontend template |
|
||||
| `static/store/js/onboarding.js` | Alpine.js component |
|
||||
| `alembic/versions/m1b2c3d4e5f6_add_store_onboarding_table.py` | Migration |
|
||||
|
||||
## Testing
|
||||
|
||||
Run the onboarding tests:
|
||||
|
||||
```bash
|
||||
pytest tests/integration/api/v1/store/test_onboarding.py -v
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
No additional configuration is required. The onboarding system uses existing configurations:
|
||||
|
||||
- Letzshop API: Uses `LetzshopCredentialsService`
|
||||
- Order Import: Uses `LetzshopOrderService`
|
||||
- Email: Uses `EmailService` for welcome email (sent after signup)
|
||||
686
app/modules/tenancy/docs/rbac.md
Normal file
686
app/modules/tenancy/docs/rbac.md
Normal file
@@ -0,0 +1,686 @@
|
||||
# Store RBAC System - Complete Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The store dashboard implements a **Role-Based Access Control (RBAC)** system that distinguishes between **Owners** and **Team Members**, with granular permissions for team members.
|
||||
|
||||
---
|
||||
|
||||
## User Types
|
||||
|
||||
### 1. Merchant Owner
|
||||
|
||||
**Who:** The user who created/owns the store account.
|
||||
|
||||
**Characteristics:**
|
||||
- Has **ALL permissions** automatically (no role needed)
|
||||
- Cannot be removed or have permissions restricted
|
||||
- Can invite team members
|
||||
- Can create and manage roles
|
||||
- Identified by `User.role = "merchant_owner"` and `Merchant.owner_user_id`
|
||||
- `User.is_merchant_owner` property returns `True`
|
||||
- `User.is_store_user` property returns `True`
|
||||
- Ownership checked via `User.is_owner_of(store_id)`
|
||||
|
||||
**Database:**
|
||||
```python
|
||||
# User record for merchant owner
|
||||
{
|
||||
"id": 5,
|
||||
"role": "merchant_owner", # Role on User model
|
||||
}
|
||||
|
||||
# StoreUser record for owner
|
||||
{
|
||||
"store_id": 1,
|
||||
"user_id": 5,
|
||||
"role_id": None, # No role needed (owner has all perms)
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
# Merchant record links ownership
|
||||
{
|
||||
"owner_user_id": 5 # Links to User.id
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** The `user_type` column was removed from StoreUser. Ownership is determined by `User.role == "merchant_owner"` and `Merchant.owner_user_id`, not by a field on StoreUser.
|
||||
|
||||
**Permissions:**
|
||||
- All 75 permissions (complete access)
|
||||
- See full list below
|
||||
|
||||
---
|
||||
|
||||
### 2. Store Members (Team Members)
|
||||
|
||||
**Who:** Users invited by the merchant owner to help manage the store.
|
||||
|
||||
**Characteristics:**
|
||||
- Have **limited permissions** based on assigned role
|
||||
- Must be invited via email
|
||||
- Invitation must be accepted before activation
|
||||
- Can be assigned one of the pre-defined roles or custom role
|
||||
- Identified by `User.role = "store_member"`
|
||||
- `User.is_store_user` property returns `True`
|
||||
- Permissions come from `StoreUser.role_id -> Role.permissions`
|
||||
|
||||
**Database:**
|
||||
```python
|
||||
# User record for store member
|
||||
{
|
||||
"id": 7,
|
||||
"role": "store_member", # Role on User model
|
||||
}
|
||||
|
||||
# StoreUser record for team member
|
||||
{
|
||||
"store_id": 1,
|
||||
"user_id": 7,
|
||||
"role_id": 3, # Role required for permission lookup
|
||||
"is_active": True,
|
||||
"invitation_token": None, # Accepted
|
||||
"invitation_accepted_at": "2024-11-15 10:30:00"
|
||||
}
|
||||
|
||||
# Role record
|
||||
{
|
||||
"id": 3,
|
||||
"store_id": 1,
|
||||
"name": "Manager",
|
||||
"permissions": [
|
||||
"dashboard.view",
|
||||
"products.view",
|
||||
"products.create",
|
||||
"products.edit",
|
||||
"orders.view",
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Permissions:**
|
||||
- Limited based on assigned role
|
||||
- Can have between 0 and 75 permissions
|
||||
- Common roles: Manager, Staff, Support, Viewer, Marketing
|
||||
|
||||
---
|
||||
|
||||
## Permission System
|
||||
|
||||
### All Available Permissions (75 total)
|
||||
|
||||
```python
|
||||
class StorePermissions(str, Enum):
|
||||
# Dashboard (1)
|
||||
DASHBOARD_VIEW = "dashboard.view"
|
||||
|
||||
# Products (6)
|
||||
PRODUCTS_VIEW = "products.view"
|
||||
PRODUCTS_CREATE = "products.create"
|
||||
PRODUCTS_EDIT = "products.edit"
|
||||
PRODUCTS_DELETE = "products.delete"
|
||||
PRODUCTS_IMPORT = "products.import"
|
||||
PRODUCTS_EXPORT = "products.export"
|
||||
|
||||
# Stock/Inventory (3)
|
||||
STOCK_VIEW = "stock.view"
|
||||
STOCK_EDIT = "stock.edit"
|
||||
STOCK_TRANSFER = "stock.transfer"
|
||||
|
||||
# Orders (4)
|
||||
ORDERS_VIEW = "orders.view"
|
||||
ORDERS_EDIT = "orders.edit"
|
||||
ORDERS_CANCEL = "orders.cancel"
|
||||
ORDERS_REFUND = "orders.refund"
|
||||
|
||||
# Customers (4)
|
||||
CUSTOMERS_VIEW = "customers.view"
|
||||
CUSTOMERS_EDIT = "customers.edit"
|
||||
CUSTOMERS_DELETE = "customers.delete"
|
||||
CUSTOMERS_EXPORT = "customers.export"
|
||||
|
||||
# Marketing (3)
|
||||
MARKETING_VIEW = "marketing.view"
|
||||
MARKETING_CREATE = "marketing.create"
|
||||
MARKETING_SEND = "marketing.send"
|
||||
|
||||
# Reports (3)
|
||||
REPORTS_VIEW = "reports.view"
|
||||
REPORTS_FINANCIAL = "reports.financial"
|
||||
REPORTS_EXPORT = "reports.export"
|
||||
|
||||
# Settings (4)
|
||||
SETTINGS_VIEW = "settings.view"
|
||||
SETTINGS_EDIT = "settings.edit"
|
||||
SETTINGS_THEME = "settings.theme"
|
||||
SETTINGS_DOMAINS = "settings.domains"
|
||||
|
||||
# Team Management (4)
|
||||
TEAM_VIEW = "team.view"
|
||||
TEAM_INVITE = "team.invite"
|
||||
TEAM_EDIT = "team.edit"
|
||||
TEAM_REMOVE = "team.remove"
|
||||
|
||||
# Marketplace Imports (3)
|
||||
IMPORTS_VIEW = "imports.view"
|
||||
IMPORTS_CREATE = "imports.create"
|
||||
IMPORTS_CANCEL = "imports.cancel"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pre-Defined Roles
|
||||
|
||||
### 1. Owner (All 75 permissions)
|
||||
**Use case:** Store owner (automatically assigned)
|
||||
- Full access to everything
|
||||
- Cannot be restricted
|
||||
- No role record needed (permissions checked differently)
|
||||
|
||||
---
|
||||
|
||||
### 2. Manager (43 permissions)
|
||||
**Use case:** Senior staff who manage most operations
|
||||
|
||||
**Has access to:**
|
||||
- Dashboard, Products (all), Stock (all)
|
||||
- Orders (all), Customers (view, edit, export)
|
||||
- Marketing (all), Reports (all including financial)
|
||||
- Settings (view, theme)
|
||||
- Imports (all)
|
||||
|
||||
**Does NOT have:**
|
||||
- `customers.delete` - Cannot delete customers
|
||||
- `settings.edit` - Cannot change core settings
|
||||
- `settings.domains` - Cannot manage domains
|
||||
- `team.*` - Cannot manage team members
|
||||
|
||||
---
|
||||
|
||||
### 3. Staff (10 permissions)
|
||||
**Use case:** Daily operations staff
|
||||
|
||||
**Has access to:**
|
||||
- Dashboard view
|
||||
- Products (view, create, edit)
|
||||
- Stock (view, edit)
|
||||
- Orders (view, edit)
|
||||
- Customers (view, edit)
|
||||
|
||||
---
|
||||
|
||||
### 4. Support (6 permissions)
|
||||
**Use case:** Customer support team
|
||||
|
||||
**Has access to:**
|
||||
- Dashboard view
|
||||
- Products (view only)
|
||||
- Orders (view, edit)
|
||||
- Customers (view, edit)
|
||||
|
||||
---
|
||||
|
||||
### 5. Viewer (6 permissions)
|
||||
**Use case:** Read-only access for reporting/audit
|
||||
|
||||
**Has access to:**
|
||||
- Dashboard (view), Products (view), Stock (view)
|
||||
- Orders (view), Customers (view), Reports (view)
|
||||
|
||||
---
|
||||
|
||||
### 6. Marketing (7 permissions)
|
||||
**Use case:** Marketing team focused on campaigns
|
||||
|
||||
**Has access to:**
|
||||
- Dashboard (view), Customers (view, export)
|
||||
- Marketing (all), Reports (view)
|
||||
|
||||
---
|
||||
|
||||
## Permission Checking Logic
|
||||
|
||||
### How Permissions Are Checked
|
||||
|
||||
```python
|
||||
# In User model (models/database/user.py)
|
||||
|
||||
def has_store_permission(self, store_id: int, permission: str) -> bool:
|
||||
"""Check if user has a specific permission in a store."""
|
||||
|
||||
# Step 1: Check if user is owner
|
||||
if self.is_owner_of(store_id):
|
||||
return True # Owners have ALL permissions
|
||||
|
||||
# Step 2: Check team member permissions
|
||||
for vm in self.store_memberships:
|
||||
if vm.store_id == store_id and vm.is_active:
|
||||
if vm.role and permission in vm.role.permissions:
|
||||
return True # Permission found in role
|
||||
|
||||
# No permission found
|
||||
return False
|
||||
```
|
||||
|
||||
### Permission Checking Flow
|
||||
|
||||
```
|
||||
Request → Middleware → Extract store from URL
|
||||
↓
|
||||
Check user authentication
|
||||
↓
|
||||
Check if user is owner
|
||||
├── YES → Allow (all permissions)
|
||||
└── NO ↓
|
||||
Check if user is team member
|
||||
├── NO → Deny
|
||||
└── YES ↓
|
||||
Check if membership is active
|
||||
├── NO → Deny
|
||||
└── YES ↓
|
||||
Check if role has required permission
|
||||
├── NO → Deny (403 Forbidden)
|
||||
└── YES → Allow
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using Permissions in Code
|
||||
|
||||
### 1. Require Specific Permission
|
||||
|
||||
**When to use:** Endpoint needs one specific permission
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, Depends
|
||||
from app.api.deps import require_store_permission
|
||||
from app.core.permissions import StorePermissions
|
||||
from models.database.user import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/products")
|
||||
def create_product(
|
||||
product_data: ProductCreate,
|
||||
user: User = Depends(
|
||||
require_store_permission(StorePermissions.PRODUCTS_CREATE.value)
|
||||
)
|
||||
):
|
||||
# Create product...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Require ANY Permission
|
||||
|
||||
**When to use:** Endpoint can be accessed with any of several permissions
|
||||
|
||||
```python
|
||||
@router.get("/dashboard")
|
||||
def view_dashboard(
|
||||
user: User = Depends(
|
||||
require_any_store_permission(
|
||||
StorePermissions.DASHBOARD_VIEW.value,
|
||||
StorePermissions.REPORTS_VIEW.value
|
||||
)
|
||||
)
|
||||
):
|
||||
# Show dashboard...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Require ALL Permissions
|
||||
|
||||
**When to use:** Endpoint needs multiple permissions
|
||||
|
||||
```python
|
||||
@router.post("/products/bulk-delete")
|
||||
def bulk_delete_products(
|
||||
user: User = Depends(
|
||||
require_all_store_permissions(
|
||||
StorePermissions.PRODUCTS_VIEW.value,
|
||||
StorePermissions.PRODUCTS_DELETE.value
|
||||
)
|
||||
)
|
||||
):
|
||||
# Delete products...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. Require Owner Only
|
||||
|
||||
**When to use:** Endpoint is owner-only (team management, critical settings)
|
||||
|
||||
```python
|
||||
from app.api.deps import require_store_owner
|
||||
|
||||
@router.post("/team/invite")
|
||||
def invite_team_member(
|
||||
email: str,
|
||||
role_id: int,
|
||||
user: User = Depends(require_store_owner)
|
||||
):
|
||||
# Invite team member...
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Get User Permissions
|
||||
|
||||
**When to use:** Need to check permissions in business logic
|
||||
|
||||
```python
|
||||
from app.api.deps import get_user_permissions
|
||||
|
||||
@router.get("/my-permissions")
|
||||
def list_my_permissions(
|
||||
permissions: list = Depends(get_user_permissions)
|
||||
):
|
||||
return {"permissions": permissions}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### StoreUser Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE store_users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
store_id INTEGER NOT NULL REFERENCES stores(id),
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
role_id INTEGER REFERENCES roles(id), -- NULL for merchant owners
|
||||
invited_by INTEGER REFERENCES users(id),
|
||||
invitation_token VARCHAR,
|
||||
invitation_sent_at TIMESTAMP,
|
||||
invitation_accepted_at TIMESTAMP,
|
||||
is_active BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
-- Note: user_type column removed. Ownership determined by
|
||||
-- User.role = 'merchant_owner' and Merchant.owner_user_id.
|
||||
```
|
||||
|
||||
### Role Table
|
||||
|
||||
```sql
|
||||
CREATE TABLE roles (
|
||||
id SERIAL PRIMARY KEY,
|
||||
store_id INTEGER NOT NULL REFERENCES stores(id),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
permissions JSON DEFAULT '[]', -- Array of permission strings
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Team Member Lifecycle
|
||||
|
||||
### 1. Invitation
|
||||
|
||||
```
|
||||
Merchant owner invites user:
|
||||
→ User created with role="store_member"
|
||||
→ StoreUser created:
|
||||
{
|
||||
"role_id": 3, -- Assigned role
|
||||
"is_active": False,
|
||||
"invitation_token": "abc123...",
|
||||
"invitation_sent_at": "2024-11-29 10:00:00",
|
||||
"invitation_accepted_at": null
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Acceptance
|
||||
|
||||
```
|
||||
User accepts invitation → StoreUser updated:
|
||||
{
|
||||
"is_active": True,
|
||||
"invitation_token": null,
|
||||
"invitation_accepted_at": "2024-11-29 10:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Deactivation
|
||||
|
||||
```
|
||||
Owner deactivates member → StoreUser updated:
|
||||
{
|
||||
"is_active": False
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Custom Role Management
|
||||
|
||||
### Overview
|
||||
|
||||
Store owners can create, edit, and delete custom roles with granular permission selection. Preset roles (manager, staff, support, viewer, marketing) cannot be deleted but can be edited.
|
||||
|
||||
### Store Role CRUD API
|
||||
|
||||
All endpoints require **store owner** authentication.
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/api/v1/store/team/roles` | List all roles (creates defaults if none exist) |
|
||||
| `POST` | `/api/v1/store/team/roles` | Create a custom role |
|
||||
| `PUT` | `/api/v1/store/team/roles/{id}` | Update a role's name/permissions |
|
||||
| `DELETE` | `/api/v1/store/team/roles/{id}` | Delete a custom role |
|
||||
|
||||
**Validation rules:**
|
||||
- Cannot create/rename a role to a preset name (manager, staff, etc.)
|
||||
- Cannot delete preset roles
|
||||
- Cannot delete a role with assigned team members
|
||||
- All permission IDs are validated against the module-discovered permission catalog
|
||||
|
||||
### Permission Catalog API
|
||||
|
||||
Returns all available permissions grouped by category, with human-readable labels and descriptions.
|
||||
|
||||
```
|
||||
GET /api/v1/store/team/permissions/catalog
|
||||
```
|
||||
|
||||
**Required Permission:** `team.view`
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"categories": [
|
||||
{
|
||||
"id": "team",
|
||||
"label": "tenancy.permissions.category.team",
|
||||
"permissions": [
|
||||
{
|
||||
"id": "team.view",
|
||||
"label": "tenancy.permissions.team_view",
|
||||
"description": "tenancy.permissions.team_view_desc",
|
||||
"is_owner_only": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Permissions are discovered from all module `definition.py` files via `PermissionDiscoveryService.get_permissions_by_category()`.
|
||||
|
||||
### Role Editor UI
|
||||
|
||||
The role editor page at `/store/{store_code}/team/roles`:
|
||||
- Lists all roles (preset + custom) with permission counts
|
||||
- Modal for creating/editing roles with a permission matrix
|
||||
- Permissions displayed with labels, IDs, and hover descriptions
|
||||
- Category-level "Select All / Deselect All" toggles
|
||||
- Owner-only permissions marked with an "Owner" badge
|
||||
|
||||
**Alpine.js component:** `storeRoles()` in `app/modules/tenancy/static/store/js/roles.js`
|
||||
|
||||
**Menu location:** Account section > Roles (requires `team.view` permission)
|
||||
|
||||
---
|
||||
|
||||
## Admin Store Roles Management
|
||||
|
||||
### Overview
|
||||
|
||||
Super admins and platform admins can manage roles for any store via the admin panel. Platform admins are scoped to stores within their assigned platforms.
|
||||
|
||||
### Admin Role CRUD API
|
||||
|
||||
All endpoints require **admin authentication** (`get_current_admin_api`).
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| `GET` | `/api/v1/admin/store-roles?store_id=X` | List roles for a store |
|
||||
| `GET` | `/api/v1/admin/store-roles/permissions/catalog` | Permission catalog |
|
||||
| `POST` | `/api/v1/admin/store-roles?store_id=X` | Create a role |
|
||||
| `PUT` | `/api/v1/admin/store-roles/{id}?store_id=X` | Update a role |
|
||||
| `DELETE` | `/api/v1/admin/store-roles/{id}?store_id=X` | Delete a role |
|
||||
|
||||
### Platform Admin Scoping
|
||||
|
||||
Platform admins can only access stores that belong to one of their assigned platforms:
|
||||
|
||||
```python
|
||||
# In StoreTeamService.validate_admin_store_access():
|
||||
# 1. Super admin (accessible_platform_ids is None) → access all stores
|
||||
# 2. Platform admin → store must exist in StorePlatform where
|
||||
# platform_id is in the admin's accessible_platform_ids
|
||||
```
|
||||
|
||||
The scoping is enforced at the service layer via `validate_admin_store_access()`, called by every admin endpoint before performing operations.
|
||||
|
||||
### Admin UI
|
||||
|
||||
Page at `/admin/store-roles`:
|
||||
- Tom Select store search/selector (shared `initStoreSelector()` component)
|
||||
- Platform admins see only stores in their assigned platforms
|
||||
- Same role CRUD and permission matrix as the store-side UI
|
||||
- Located in the "User Management" admin menu section
|
||||
|
||||
**Alpine.js component:** `adminStoreRoles()` in `app/modules/tenancy/static/admin/js/store-roles.js`
|
||||
|
||||
### Audit Trail
|
||||
|
||||
All role operations are logged via `AuditAggregatorService`:
|
||||
|
||||
| Action | Description |
|
||||
|--------|-------------|
|
||||
| `role.create` | Custom role created |
|
||||
| `role.update` | Role name or permissions modified |
|
||||
| `role.delete` | Custom role deleted |
|
||||
| `member.role_change` | Team member assigned a different role |
|
||||
| `member.invite` | Team member invited |
|
||||
| `member.remove` | Team member removed |
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
### Missing Permission
|
||||
|
||||
```http
|
||||
HTTP 403 Forbidden
|
||||
|
||||
{
|
||||
"error_code": "INSUFFICIENT_STORE_PERMISSIONS",
|
||||
"message": "You don't have permission to perform this action",
|
||||
"details": {
|
||||
"required_permission": "products.delete",
|
||||
"store_code": "orion"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Not Owner
|
||||
|
||||
```http
|
||||
HTTP 403 Forbidden
|
||||
|
||||
{
|
||||
"error_code": "STORE_OWNER_ONLY",
|
||||
"message": "This operation requires store owner privileges",
|
||||
"details": {
|
||||
"operation": "team management",
|
||||
"store_code": "orion"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Inactive Membership
|
||||
|
||||
```http
|
||||
HTTP 403 Forbidden
|
||||
|
||||
{
|
||||
"error_code": "INACTIVE_STORE_MEMBERSHIP",
|
||||
"message": "Your store membership is inactive"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Merchant Owner vs Store Member
|
||||
|
||||
| Feature | Merchant Owner (`role="merchant_owner"`) | Store Member (`role="store_member"`) |
|
||||
|---------|-------|-------------|
|
||||
| **Permissions** | All 75 (automatic) | Based on role (0-75) |
|
||||
| **Role Required** | No | Yes |
|
||||
| **Can Be Removed** | No | Yes |
|
||||
| **Team Management** | Yes | No |
|
||||
| **Critical Settings** | Yes | No (usually) |
|
||||
| **Invitation Required** | No (creates store) | Yes |
|
||||
| **Ownership Determined By** | `Merchant.owner_user_id` | N/A |
|
||||
|
||||
### Permission Hierarchy
|
||||
|
||||
```
|
||||
Merchant Owner (75 permissions)
|
||||
└─ Manager (43 permissions)
|
||||
└─ Staff (10 permissions)
|
||||
└─ Support (6 permissions)
|
||||
└─ Viewer (6 permissions, read-only)
|
||||
|
||||
Marketing (7 permissions, specialized)
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use Constants:** Always use `StorePermissions.PERMISSION_NAME.value`
|
||||
2. **Least Privilege:** Give team members minimum permissions needed
|
||||
3. **Owner Only:** Keep sensitive operations owner-only
|
||||
4. **Custom Roles:** Create custom roles for specific needs
|
||||
5. **Regular Audit:** Review team member permissions regularly
|
||||
|
||||
---
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/modules/tenancy/services/store_team_service.py` | Role CRUD, platform scoping, audit trail |
|
||||
| `app/modules/tenancy/services/permission_discovery_service.py` | Permission catalog, role presets |
|
||||
| `app/modules/tenancy/routes/api/store_team.py` | Store team & role API endpoints |
|
||||
| `app/modules/tenancy/routes/api/admin_store_roles.py` | Admin store role API endpoints |
|
||||
| `app/modules/tenancy/schemas/team.py` | Request/response schemas |
|
||||
| `app/modules/tenancy/static/store/js/roles.js` | Store role editor Alpine.js component |
|
||||
| `app/modules/tenancy/static/admin/js/store-roles.js` | Admin role editor Alpine.js component |
|
||||
| `app/modules/tenancy/templates/tenancy/store/roles.html` | Store role editor template |
|
||||
| `app/modules/tenancy/templates/tenancy/admin/store-roles.html` | Admin role editor template |
|
||||
Reference in New Issue
Block a user