feat: add logging, marketplace, and admin enhancements
Database & Migrations: - Add application_logs table migration for hybrid cloud logging - Add companies table migration and restructure vendor relationships Logging System: - Implement hybrid logging system (database + file) - Add log_service for centralized log management - Create admin logs page with filtering and viewing capabilities - Add init_log_settings.py script for log configuration - Enhance core logging with database integration Marketplace Integration: - Add marketplace admin page with product management - Create marketplace vendor page with product listings - Implement marketplace.js for both admin and vendor interfaces - Add marketplace integration documentation Admin Enhancements: - Add imports management page and functionality - Create settings page for admin configuration - Add vendor themes management page - Enhance vendor detail and edit pages - Improve code quality dashboard and violation details - Add logs viewing and management - Update icons guide and shared icon system Architecture & Documentation: - Document frontend structure and component architecture - Document models structure and relationships - Add vendor-in-token architecture documentation - Add vendor RBAC (role-based access control) documentation - Document marketplace integration patterns - Update architecture patterns documentation Infrastructure: - Add platform static files structure (css, img, js) - Move architecture_scan.py to proper models location - Update model imports and registrations - Enhance exception handling - Update dependency injection patterns UI/UX: - Improve vendor edit interface - Update admin user interface - Enhance page templates documentation - Add vendor marketplace interface
This commit is contained in:
@@ -221,6 +221,101 @@ async def create_vendor(
|
||||
return result
|
||||
```
|
||||
|
||||
### Rule API-005: Vendor Context from Token (Not URL)
|
||||
|
||||
**Vendor API endpoints MUST extract vendor context from JWT token, NOT from URL.**
|
||||
|
||||
> **Rationale:** Embedding vendor context in JWT tokens enables clean RESTful API endpoints, eliminates URL-based vendor detection issues, and improves security by cryptographically signing vendor access.
|
||||
|
||||
**❌ BAD: URL-based vendor detection**
|
||||
|
||||
```python
|
||||
from middleware.vendor_context import require_vendor_context
|
||||
|
||||
@router.get("/products")
|
||||
def get_products(
|
||||
vendor: Vendor = Depends(require_vendor_context()), # ❌ Requires vendor in URL
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
# This fails on /api/v1/vendor/products (no vendor in URL)
|
||||
products = product_service.get_vendor_products(db, vendor.id)
|
||||
return products
|
||||
```
|
||||
|
||||
**Issues with URL-based approach:**
|
||||
- ❌ Only works with routes like `/vendor/{vendor_code}/dashboard`
|
||||
- ❌ Fails on API routes like `/api/v1/vendor/products` (no vendor in URL)
|
||||
- ❌ Inconsistent between page routes and API routes
|
||||
- ❌ Violates RESTful API design
|
||||
- ❌ Requires database lookup on every request
|
||||
|
||||
**✅ GOOD: Token-based vendor context**
|
||||
|
||||
```python
|
||||
@router.get("/products")
|
||||
def get_products(
|
||||
current_user: User = Depends(get_current_vendor_api), # ✅ Vendor in token
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
# Extract vendor from JWT token
|
||||
if not hasattr(current_user, "token_vendor_id"):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Token missing vendor information. Please login again.",
|
||||
)
|
||||
|
||||
vendor_id = current_user.token_vendor_id
|
||||
|
||||
# Use vendor_id from token
|
||||
products = product_service.get_vendor_products(db, vendor_id)
|
||||
return products
|
||||
```
|
||||
|
||||
**Benefits of token-based approach:**
|
||||
- ✅ Works on all routes (page and API)
|
||||
- ✅ Clean RESTful API endpoints
|
||||
- ✅ Vendor context cryptographically signed in JWT
|
||||
- ✅ No database lookup needed for vendor detection
|
||||
- ✅ Consistent authentication mechanism
|
||||
- ✅ Security: Cannot be tampered with by client
|
||||
|
||||
**Token structure:**
|
||||
```json
|
||||
{
|
||||
"sub": "user_id",
|
||||
"username": "john.doe",
|
||||
"vendor_id": 123, ← Vendor context
|
||||
"vendor_code": "WIZAMART", ← Vendor code
|
||||
"vendor_role": "Owner" ← Vendor role
|
||||
}
|
||||
```
|
||||
|
||||
**Available token attributes:**
|
||||
- `current_user.token_vendor_id` - Vendor ID (use for database queries)
|
||||
- `current_user.token_vendor_code` - Vendor code (use for logging)
|
||||
- `current_user.token_vendor_role` - Vendor role (Owner, Manager, etc.)
|
||||
|
||||
**Migration checklist:**
|
||||
1. Remove `vendor: Vendor = Depends(require_vendor_context())`
|
||||
2. Remove unused imports: `from middleware.vendor_context import require_vendor_context`
|
||||
3. Extract vendor from token: `vendor_id = current_user.token_vendor_id`
|
||||
4. Add token validation check (see example above)
|
||||
5. Update logging to use `current_user.token_vendor_code`
|
||||
|
||||
**See also:** `docs/backend/vendor-in-token-architecture.md` for complete migration guide
|
||||
|
||||
**Files requiring migration:**
|
||||
- `app/api/v1/vendor/customers.py`
|
||||
- `app/api/v1/vendor/notifications.py`
|
||||
- `app/api/v1/vendor/media.py`
|
||||
- `app/api/v1/vendor/marketplace.py`
|
||||
- `app/api/v1/vendor/inventory.py`
|
||||
- `app/api/v1/vendor/settings.py`
|
||||
- `app/api/v1/vendor/analytics.py`
|
||||
- `app/api/v1/vendor/payments.py`
|
||||
- `app/api/v1/vendor/profile.py`
|
||||
|
||||
---
|
||||
|
||||
## Service Layer Patterns
|
||||
|
||||
545
docs/architecture/frontend-structure.md
Normal file
545
docs/architecture/frontend-structure.md
Normal file
@@ -0,0 +1,545 @@
|
||||
# Frontend Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
This application has **4 distinct frontends**, each with its own templates and static assets:
|
||||
|
||||
1. **Platform** - Public platform pages (homepage, about, contact)
|
||||
2. **Admin** - Administrative control panel
|
||||
3. **Vendor** - Vendor management portal
|
||||
4. **Shop** - Customer-facing e-commerce store
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── templates/
|
||||
│ ├── platform/ # Platform public pages
|
||||
│ ├── admin/ # Admin portal pages
|
||||
│ ├── vendor/ # Vendor portal pages
|
||||
│ ├── shop/ # Shop customer pages
|
||||
│ └── shared/ # Shared components (emails, errors)
|
||||
│
|
||||
└── static/
|
||||
├── platform/ # Platform static assets
|
||||
│ ├── js/
|
||||
│ ├── css/
|
||||
│ └── img/
|
||||
├── admin/ # Admin static assets
|
||||
│ ├── js/
|
||||
│ ├── css/
|
||||
│ └── img/
|
||||
├── vendor/ # Vendor static assets
|
||||
│ ├── js/
|
||||
│ ├── css/
|
||||
│ └── img/
|
||||
├── shop/ # Shop static assets
|
||||
│ ├── js/
|
||||
│ ├── css/
|
||||
│ └── img/
|
||||
└── shared/ # Shared assets (icons, utilities)
|
||||
├── js/
|
||||
├── css/
|
||||
└── img/
|
||||
```
|
||||
|
||||
## Frontend Details
|
||||
|
||||
### 1. Platform Frontend
|
||||
|
||||
**Purpose:** Public-facing platform pages (marketing, info pages)
|
||||
|
||||
**Location:**
|
||||
- Templates: `app/templates/platform/`
|
||||
- Static: `static/platform/`
|
||||
|
||||
**Pages:**
|
||||
- Homepage (multiple layouts: default, minimal, modern)
|
||||
- Content pages (about, privacy, terms)
|
||||
- Landing pages
|
||||
|
||||
**Features:**
|
||||
- SEO-optimized
|
||||
- Multi-layout homepage support
|
||||
- Content management system integration
|
||||
- Responsive design
|
||||
|
||||
**Routes:** `/`, `/about`, `/contact`, etc.
|
||||
|
||||
**Authentication:** Not required (public access)
|
||||
|
||||
---
|
||||
|
||||
### 2. Admin Frontend
|
||||
|
||||
**Purpose:** Platform administration and management
|
||||
|
||||
**Location:**
|
||||
- Templates: `app/templates/admin/`
|
||||
- Static: `static/admin/`
|
||||
|
||||
**Pages:**
|
||||
- Dashboard
|
||||
- Vendor management
|
||||
- User management
|
||||
- Content management
|
||||
- Theme customization
|
||||
- System settings
|
||||
- Logs and monitoring
|
||||
- Code quality dashboard
|
||||
|
||||
**Technology Stack:**
|
||||
- Alpine.js for reactive components
|
||||
- Tailwind CSS for styling
|
||||
- Heroicons for icons
|
||||
- Centralized logging system
|
||||
- API-driven architecture
|
||||
|
||||
**Routes:** `/admin/*`
|
||||
|
||||
**Authentication:** Admin role required
|
||||
|
||||
---
|
||||
|
||||
### 3. Vendor Frontend
|
||||
|
||||
**Purpose:** Vendor portal for product and order management
|
||||
|
||||
**Location:**
|
||||
- Templates: `app/templates/vendor/`
|
||||
- Static: `static/vendor/`
|
||||
|
||||
**Pages:**
|
||||
- Vendor dashboard
|
||||
- Product management
|
||||
- Inventory management
|
||||
- Order management
|
||||
- Analytics
|
||||
- Profile settings
|
||||
|
||||
**Technology Stack:**
|
||||
- Alpine.js for reactive components
|
||||
- Tailwind CSS for styling
|
||||
- Heroicons for icons
|
||||
- API-driven architecture
|
||||
- Vendor context middleware
|
||||
|
||||
**Routes:** `/vendor/{vendor_code}/*`
|
||||
|
||||
**Authentication:** Vendor role required
|
||||
|
||||
---
|
||||
|
||||
### 4. Shop Frontend
|
||||
|
||||
**Purpose:** Customer-facing e-commerce store
|
||||
|
||||
**Location:**
|
||||
- Templates: `app/templates/shop/`
|
||||
- Static: `static/shop/`
|
||||
|
||||
**Pages:**
|
||||
- Product catalog
|
||||
- Product details
|
||||
- Shopping cart
|
||||
- Checkout
|
||||
- Order tracking
|
||||
- Customer account
|
||||
|
||||
**Technology Stack:**
|
||||
- Alpine.js for interactive features
|
||||
- Tailwind CSS for styling
|
||||
- E-commerce specific components
|
||||
- Payment integration
|
||||
- Shopping cart management
|
||||
|
||||
**Routes:** `/shop/*`
|
||||
|
||||
**Authentication:** Optional (required for checkout)
|
||||
|
||||
---
|
||||
|
||||
## Using Static Assets
|
||||
|
||||
Each frontend has its own static directory for frontend-specific assets. Use the appropriate directory based on which frontend the asset belongs to.
|
||||
|
||||
### Platform Static Assets (`static/platform/`)
|
||||
|
||||
**JavaScript Files:**
|
||||
```html
|
||||
<!-- In platform templates -->
|
||||
<script src="{{ url_for('static', path='platform/js/homepage.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='platform/js/animations.js') }}"></script>
|
||||
```
|
||||
|
||||
**CSS Files:**
|
||||
```html
|
||||
<link href="{{ url_for('static', path='platform/css/styles.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', path='platform/css/landing.css') }}" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Images:**
|
||||
```html
|
||||
<img src="{{ url_for('static', path='platform/img/hero-banner.jpg') }}" alt="Hero">
|
||||
<img src="{{ url_for('static', path='platform/img/features/feature-1.svg') }}" alt="Feature">
|
||||
```
|
||||
|
||||
**Current Usage:** Platform currently uses only shared assets (fonts, Tailwind CSS). Platform-specific directories are ready for future platform-specific assets.
|
||||
|
||||
---
|
||||
|
||||
### Admin Static Assets (`static/admin/`)
|
||||
|
||||
**JavaScript Files:**
|
||||
```html
|
||||
<!-- In admin templates -->
|
||||
<script src="{{ url_for('static', path='admin/js/dashboard.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='admin/js/vendors.js') }}"></script>
|
||||
```
|
||||
|
||||
**CSS Files:**
|
||||
```html
|
||||
<link href="{{ url_for('static', path='admin/css/custom.css') }}" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Images:**
|
||||
```html
|
||||
<img src="{{ url_for('static', path='admin/img/placeholder.png') }}" alt="Placeholder">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Vendor Static Assets (`static/vendor/`)
|
||||
|
||||
**JavaScript Files:**
|
||||
```html
|
||||
<!-- In vendor templates -->
|
||||
<script src="{{ url_for('static', path='vendor/js/dashboard.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='vendor/js/products.js') }}"></script>
|
||||
```
|
||||
|
||||
**CSS Files:**
|
||||
```html
|
||||
<link href="{{ url_for('static', path='vendor/css/custom.css') }}" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Images:**
|
||||
```html
|
||||
<img src="{{ url_for('static', path='vendor/img/no-products.svg') }}" alt="No Products">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Shop Static Assets (`static/shop/`)
|
||||
|
||||
**JavaScript Files:**
|
||||
```html
|
||||
<!-- In shop templates -->
|
||||
<script src="{{ url_for('static', path='shop/js/cart.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='shop/js/checkout.js') }}"></script>
|
||||
```
|
||||
|
||||
**CSS Files:**
|
||||
```html
|
||||
<link href="{{ url_for('static', path='shop/css/product-gallery.css') }}" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Images:**
|
||||
```html
|
||||
<img src="{{ url_for('static', path='shop/img/placeholder-product.jpg') }}" alt="Product">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### When to Use Shared vs. Frontend-Specific
|
||||
|
||||
**Use `static/shared/` when:**
|
||||
- Asset is used by 2 or more frontends
|
||||
- Common utilities (icons, API client, utilities)
|
||||
- Brand assets (logos, favicons)
|
||||
- Core libraries (Alpine.js, Tailwind CSS fallbacks)
|
||||
|
||||
**Use `static/{frontend}/` when:**
|
||||
- Asset is only used by one specific frontend
|
||||
- Frontend-specific styling
|
||||
- Frontend-specific JavaScript components
|
||||
- Frontend-specific images/graphics
|
||||
|
||||
**Example Decision Tree:**
|
||||
```
|
||||
Icon system (used by all 4 frontends) → static/shared/js/icons.js
|
||||
Admin dashboard chart → static/admin/js/charts.js
|
||||
Vendor product form → static/vendor/js/product-form.js
|
||||
Platform hero image → static/platform/img/hero.jpg
|
||||
Shop product carousel → static/shop/js/carousel.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shared Resources
|
||||
|
||||
### Templates (`app/templates/shared/`)
|
||||
|
||||
**Shared components used across multiple frontends:**
|
||||
- Email templates
|
||||
- Error pages (404, 500)
|
||||
- Common partials
|
||||
|
||||
### Static Assets (`static/shared/`)
|
||||
|
||||
**Shared JavaScript:**
|
||||
- `js/icons.js` - Heroicons system (used by all frontends)
|
||||
- `js/utils.js` - Common utilities
|
||||
- `js/api-client.js` - API communication
|
||||
- `js/log-config.js` - Centralized logging
|
||||
|
||||
**Shared CSS:**
|
||||
- Common utility classes
|
||||
- Shared theme variables
|
||||
|
||||
**Shared Images:**
|
||||
- Logos
|
||||
- Brand assets
|
||||
- Icons
|
||||
|
||||
---
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
### 1. Separation of Concerns
|
||||
|
||||
Each frontend is completely isolated:
|
||||
- Own templates directory
|
||||
- Own static assets directory
|
||||
- Own JavaScript components
|
||||
- Own CSS styles
|
||||
|
||||
**Benefits:**
|
||||
- Clear boundaries
|
||||
- Independent development
|
||||
- No cross-contamination
|
||||
- Easy to maintain
|
||||
|
||||
### 2. Shared Core
|
||||
|
||||
Common functionality is shared via `static/shared/`:
|
||||
- Icon system
|
||||
- API client
|
||||
- Utilities
|
||||
- Logging
|
||||
|
||||
**Benefits:**
|
||||
- DRY principle
|
||||
- Consistent behavior
|
||||
- Single source of truth
|
||||
- Easy updates
|
||||
|
||||
### 3. Template Inheritance
|
||||
|
||||
Each frontend has a base template:
|
||||
- `platform/base.html`
|
||||
- `admin/base.html`
|
||||
- `vendor/base.html`
|
||||
- `shop/base.html`
|
||||
|
||||
**Benefits:**
|
||||
- Consistent layout within frontend
|
||||
- Easy to customize per frontend
|
||||
- Different design systems possible
|
||||
|
||||
### 4. API-Driven
|
||||
|
||||
All frontends communicate with backend via APIs:
|
||||
- `/api/v1/admin/*` - Admin APIs
|
||||
- `/api/v1/vendor/*` - Vendor APIs
|
||||
- `/api/v1/shop/*` - Shop APIs
|
||||
- `/api/v1/platform/*` - Platform APIs
|
||||
|
||||
**Benefits:**
|
||||
- Clear backend contracts
|
||||
- Testable independently
|
||||
- Can be replaced with SPA if needed
|
||||
- Mobile app ready
|
||||
|
||||
---
|
||||
|
||||
## Frontend Technology Matrix
|
||||
|
||||
| Frontend | Framework | CSS | Icons | Auth Required | Base URL |
|
||||
|----------|-----------|-----------|------------|---------------|-------------------|
|
||||
| Platform | Alpine.js | Tailwind | Heroicons | No | `/` |
|
||||
| Admin | Alpine.js | Tailwind | Heroicons | Yes (Admin) | `/admin` |
|
||||
| Vendor | Alpine.js | Tailwind | Heroicons | Yes (Vendor) | `/vendor/{code}` |
|
||||
| Shop | Alpine.js | Tailwind | Heroicons | Optional | `/shop` |
|
||||
|
||||
---
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Adding a New Page
|
||||
|
||||
1. **Determine which frontend** the page belongs to
|
||||
2. **Create template** in appropriate `app/templates/{frontend}/` directory
|
||||
3. **Create JavaScript** (if needed) in `static/{frontend}/js/`
|
||||
4. **Create CSS** (if needed) in `static/{frontend}/css/`
|
||||
5. **Add route** in appropriate route handler
|
||||
6. **Update navigation** in frontend's base template
|
||||
|
||||
### Using Shared Resources
|
||||
|
||||
**Icons:**
|
||||
```html
|
||||
<span x-html="$icon('icon-name', 'w-5 h-5')"></span>
|
||||
```
|
||||
|
||||
**API Client:**
|
||||
```javascript
|
||||
const data = await apiClient.get('/api/v1/admin/users');
|
||||
```
|
||||
|
||||
**Utilities:**
|
||||
```javascript
|
||||
Utils.showToast('Success!', 'success');
|
||||
Utils.formatDate(dateString);
|
||||
```
|
||||
|
||||
**Logging:**
|
||||
```javascript
|
||||
const log = window.LogConfig.loggers.myPage;
|
||||
log.info('Page loaded');
|
||||
```
|
||||
|
||||
### Frontend-Specific Resources
|
||||
|
||||
**Platform-specific JavaScript:**
|
||||
```html
|
||||
<script src="{{ url_for('static', path='platform/js/homepage.js') }}"></script>
|
||||
```
|
||||
|
||||
**Admin-specific CSS:**
|
||||
```html
|
||||
<link href="{{ url_for('static', path='admin/css/dashboard.css') }}" rel="stylesheet">
|
||||
```
|
||||
|
||||
**Vendor-specific images:**
|
||||
```html
|
||||
<img src="{{ url_for('static', path='vendor/img/logo.png') }}">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Moving Assets Between Frontends
|
||||
|
||||
If an asset is used by multiple frontends:
|
||||
1. **Move to `static/shared/`**
|
||||
2. **Update all references**
|
||||
3. **Test all affected frontends**
|
||||
|
||||
If an asset is only used by one frontend:
|
||||
1. **Move to `static/{frontend}/`**
|
||||
2. **Update references in that frontend only**
|
||||
|
||||
### Deprecation Path
|
||||
|
||||
When removing a frontend:
|
||||
1. Remove `app/templates/{frontend}/`
|
||||
2. Remove `static/{frontend}/`
|
||||
3. Remove routes
|
||||
4. Update documentation
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Additional Frontends
|
||||
|
||||
- **Partner Portal** - For business partners/affiliates
|
||||
- **API Documentation** - Interactive API docs (Swagger UI)
|
||||
- **Mobile App** - Native mobile using existing APIs
|
||||
|
||||
### Frontend Modernization
|
||||
|
||||
Each frontend can be independently modernized:
|
||||
- Replace Alpine.js with React/Vue/Svelte
|
||||
- Add TypeScript
|
||||
- Implement SSR/SSG
|
||||
- Convert to PWA
|
||||
|
||||
The API-driven architecture allows this flexibility.
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Per-Frontend Testing
|
||||
|
||||
Each frontend should have:
|
||||
- **Unit tests** for JavaScript components
|
||||
- **Integration tests** for API interactions
|
||||
- **E2E tests** for critical user flows
|
||||
- **Accessibility tests**
|
||||
- **Responsive design tests**
|
||||
|
||||
### Shared Resource Testing
|
||||
|
||||
Shared resources need:
|
||||
- **Unit tests** for utilities
|
||||
- **Integration tests** with all frontends
|
||||
- **Visual regression tests** for icons
|
||||
|
||||
---
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Per-Frontend Optimization
|
||||
|
||||
Each frontend can optimize independently:
|
||||
- Code splitting
|
||||
- Lazy loading
|
||||
- Asset minification
|
||||
- CDN deployment
|
||||
- Browser caching
|
||||
|
||||
### Shared Resource Optimization
|
||||
|
||||
Shared resources are cached globally:
|
||||
- Long cache headers
|
||||
- Versioning via query params
|
||||
- CDN distribution
|
||||
- Compression
|
||||
|
||||
---
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Frontend-Specific Security
|
||||
|
||||
Each frontend has different security needs:
|
||||
- **Platform:** XSS protection, CSP
|
||||
- **Admin:** CSRF tokens, admin-only routes
|
||||
- **Vendor:** Vendor isolation, rate limiting
|
||||
- **Shop:** PCI compliance, secure checkout
|
||||
|
||||
### Shared Security
|
||||
|
||||
All frontends use:
|
||||
- JWT authentication
|
||||
- HTTPS only
|
||||
- Secure headers
|
||||
- Input sanitization
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
The 4-frontend architecture provides:
|
||||
- ✅ Clear separation of concerns
|
||||
- ✅ Independent development and deployment
|
||||
- ✅ Shared core functionality
|
||||
- ✅ Flexibility for future changes
|
||||
- ✅ Optimized for each user type
|
||||
- ✅ Maintainable and scalable
|
||||
|
||||
Each frontend serves a specific purpose and audience, with shared infrastructure for common needs.
|
||||
469
docs/architecture/models-structure.md
Normal file
469
docs/architecture/models-structure.md
Normal file
@@ -0,0 +1,469 @@
|
||||
# Models Structure
|
||||
|
||||
## Overview
|
||||
|
||||
This project follows a **standardized models structure** at the root level, separating database models from Pydantic schemas.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
models/
|
||||
├── database/ # SQLAlchemy database models (ORM)
|
||||
│ ├── __init__.py
|
||||
│ ├── user.py
|
||||
│ ├── vendor.py
|
||||
│ ├── product.py
|
||||
│ ├── order.py
|
||||
│ ├── admin.py
|
||||
│ ├── architecture_scan.py
|
||||
│ └── ...
|
||||
│
|
||||
└── schema/ # Pydantic schemas (API validation)
|
||||
├── __init__.py
|
||||
├── auth.py
|
||||
├── admin.py
|
||||
├── product.py
|
||||
├── order.py
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Important Rules
|
||||
|
||||
### ✅ DO: Use Root-Level Models
|
||||
|
||||
**ALL models must be in the root `models/` directory:**
|
||||
- Database models → `models/database/`
|
||||
- Pydantic schemas → `models/schema/`
|
||||
|
||||
### ❌ DON'T: Create `app/models/`
|
||||
|
||||
**NEVER create or use `app/models/` directory.**
|
||||
|
||||
The application structure is:
|
||||
```
|
||||
app/ # Application code (routes, services, core)
|
||||
models/ # Models (database & schemas)
|
||||
```
|
||||
|
||||
NOT:
|
||||
```
|
||||
app/
|
||||
models/ # ❌ WRONG - Don't create this!
|
||||
models/ # ✓ Correct location
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Database Models (`models/database/`)
|
||||
|
||||
### Purpose
|
||||
SQLAlchemy ORM models that represent database tables.
|
||||
|
||||
### Naming Convention
|
||||
- Singular class names: `User`, `Product`, `Order`
|
||||
- File names match class: `user.py`, `product.py`, `order.py`
|
||||
|
||||
### Example Structure
|
||||
|
||||
**File:** `models/database/product.py`
|
||||
```python
|
||||
"""Product database model"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, Float, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Product(Base):
|
||||
"""Product database model"""
|
||||
|
||||
__tablename__ = "products"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
price = Column(Float, nullable=False)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"))
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="products")
|
||||
```
|
||||
|
||||
### Exporting Models
|
||||
|
||||
All database models must be exported in `models/database/__init__.py`:
|
||||
|
||||
```python
|
||||
# models/database/__init__.py
|
||||
from .user import User
|
||||
from .vendor import Vendor
|
||||
from .product import Product
|
||||
from .order import Order, OrderItem
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
"Vendor",
|
||||
"Product",
|
||||
"Order",
|
||||
"OrderItem",
|
||||
]
|
||||
```
|
||||
|
||||
### Importing Database Models
|
||||
|
||||
```python
|
||||
# ✅ CORRECT - Import from models.database
|
||||
from models.database import User, Product
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
# ❌ WRONG - Don't import from app.models
|
||||
from app.models.user import User # This path doesn't exist!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pydantic Schemas (`models/schema/`)
|
||||
|
||||
### Purpose
|
||||
Pydantic models for API request/response validation and serialization.
|
||||
|
||||
### Naming Convention
|
||||
- Use descriptive suffixes: `Create`, `Update`, `Response`, `InDB`
|
||||
- Group related schemas in same file
|
||||
- File names match domain: `auth.py`, `product.py`, `order.py`
|
||||
|
||||
### Example Structure
|
||||
|
||||
**File:** `models/schema/product.py`
|
||||
```python
|
||||
"""Product Pydantic schemas"""
|
||||
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ProductBase(BaseModel):
|
||||
"""Base product schema"""
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
price: float = Field(..., gt=0)
|
||||
|
||||
|
||||
class ProductCreate(ProductBase):
|
||||
"""Schema for creating a product"""
|
||||
vendor_id: int
|
||||
|
||||
|
||||
class ProductUpdate(BaseModel):
|
||||
"""Schema for updating a product"""
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = None
|
||||
price: Optional[float] = Field(None, gt=0)
|
||||
|
||||
|
||||
class ProductResponse(ProductBase):
|
||||
"""Schema for product API response"""
|
||||
id: int
|
||||
vendor_id: int
|
||||
|
||||
class Config:
|
||||
from_attributes = True # Pydantic v2
|
||||
# orm_mode = True # Pydantic v1
|
||||
```
|
||||
|
||||
### Exporting Schemas
|
||||
|
||||
Export schemas in `models/schema/__init__.py`:
|
||||
|
||||
```python
|
||||
# models/schema/__init__.py
|
||||
from .auth import LoginRequest, TokenResponse
|
||||
from .product import ProductCreate, ProductUpdate, ProductResponse
|
||||
|
||||
__all__ = [
|
||||
"LoginRequest",
|
||||
"TokenResponse",
|
||||
"ProductCreate",
|
||||
"ProductUpdate",
|
||||
"ProductResponse",
|
||||
]
|
||||
```
|
||||
|
||||
### Importing Schemas
|
||||
|
||||
```python
|
||||
# ✅ CORRECT
|
||||
from models.schema import ProductCreate, ProductResponse
|
||||
from models.schema.auth import LoginRequest
|
||||
|
||||
# ❌ WRONG
|
||||
from app.models.schema.product import ProductCreate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Database Model with Schema
|
||||
|
||||
**Database Model:** `models/database/vendor.py`
|
||||
```python
|
||||
from sqlalchemy import Column, Integer, String, Boolean
|
||||
from .base import Base
|
||||
|
||||
class Vendor(Base):
|
||||
__tablename__ = "vendors"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(255), nullable=False)
|
||||
code = Column(String(50), unique=True, nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
```
|
||||
|
||||
**Pydantic Schema:** `models/schema/vendor.py`
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
class VendorBase(BaseModel):
|
||||
name: str
|
||||
code: str
|
||||
|
||||
class VendorCreate(VendorBase):
|
||||
pass
|
||||
|
||||
class VendorResponse(VendorBase):
|
||||
id: int
|
||||
is_active: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
```
|
||||
|
||||
**Usage in API:**
|
||||
```python
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from models.database import Vendor
|
||||
from models.schema import VendorCreate, VendorResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/vendors", response_model=VendorResponse)
|
||||
def create_vendor(vendor_data: VendorCreate, db: Session):
|
||||
# VendorCreate validates input
|
||||
db_vendor = Vendor(**vendor_data.dict())
|
||||
db.add(db_vendor)
|
||||
db.commit()
|
||||
db.refresh(db_vendor)
|
||||
# VendorResponse serializes output
|
||||
return db_vendor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pattern 2: Complex Schemas
|
||||
|
||||
For complex domains, organize schemas by purpose:
|
||||
|
||||
```python
|
||||
# models/schema/order.py
|
||||
class OrderBase(BaseModel):
|
||||
"""Base order fields"""
|
||||
pass
|
||||
|
||||
class OrderCreate(OrderBase):
|
||||
"""Create order from customer"""
|
||||
items: List[OrderItemCreate]
|
||||
|
||||
class OrderUpdate(BaseModel):
|
||||
"""Admin order update"""
|
||||
status: Optional[OrderStatus]
|
||||
|
||||
class OrderResponse(OrderBase):
|
||||
"""Order API response"""
|
||||
id: int
|
||||
items: List[OrderItemResponse]
|
||||
|
||||
class OrderAdminResponse(OrderResponse):
|
||||
"""Extended response for admin"""
|
||||
internal_notes: Optional[str]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If you accidentally created models in the wrong location:
|
||||
|
||||
### Moving Database Models
|
||||
|
||||
```bash
|
||||
# If you created app/models/my_model.py (WRONG)
|
||||
# Move to correct location:
|
||||
mv app/models/my_model.py models/database/my_model.py
|
||||
|
||||
# Update imports in all files
|
||||
# FROM: from app.models.my_model import MyModel
|
||||
# TO: from models.database.my_model import MyModel
|
||||
|
||||
# Add to models/database/__init__.py
|
||||
# Remove app/models/ directory
|
||||
rm -rf app/models/
|
||||
```
|
||||
|
||||
### Moving Pydantic Schemas
|
||||
|
||||
```bash
|
||||
# If you created app/schemas/my_schema.py (WRONG)
|
||||
# Move to correct location:
|
||||
mv app/schemas/my_schema.py models/schema/my_schema.py
|
||||
|
||||
# Update imports
|
||||
# FROM: from app.schemas.my_schema import MySchema
|
||||
# TO: from models.schema.my_schema import MySchema
|
||||
|
||||
# Add to models/schema/__init__.py
|
||||
# Remove app/schemas/ directory
|
||||
rm -rf app/schemas/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Why This Structure?
|
||||
|
||||
### ✅ Benefits
|
||||
|
||||
1. **Clear Separation**
|
||||
- Database layer separate from application layer
|
||||
- Easy to understand where models live
|
||||
|
||||
2. **Import Consistency**
|
||||
- `from models.database import ...`
|
||||
- `from models.schema import ...`
|
||||
- No confusion about import paths
|
||||
|
||||
3. **Testing**
|
||||
- Easy to mock database models
|
||||
- Easy to test schema validation
|
||||
|
||||
4. **Scalability**
|
||||
- Models can be used by multiple apps
|
||||
- Clean separation of concerns
|
||||
|
||||
5. **Tool Compatibility**
|
||||
- Alembic migrations find models easily
|
||||
- IDE autocomplete works better
|
||||
- Linters understand structure
|
||||
|
||||
### ❌ Problems with `app/models/`
|
||||
|
||||
1. **Confusion**: Is it database or schema?
|
||||
2. **Import Issues**: Circular dependencies
|
||||
3. **Migration Problems**: Alembic can't find models
|
||||
4. **Inconsistency**: Different parts of codebase use different paths
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
Use this checklist when adding new models:
|
||||
|
||||
### Database Model Checklist
|
||||
- [ ] File in `models/database/{name}.py`
|
||||
- [ ] Inherits from `Base`
|
||||
- [ ] Has `__tablename__` defined
|
||||
- [ ] Exported in `models/database/__init__.py`
|
||||
- [ ] Imported using `from models.database import ...`
|
||||
- [ ] NO file in `app/models/`
|
||||
|
||||
### Pydantic Schema Checklist
|
||||
- [ ] File in `models/schema/{name}.py`
|
||||
- [ ] Inherits from `BaseModel`
|
||||
- [ ] Has descriptive suffix (`Create`, `Update`, `Response`)
|
||||
- [ ] Exported in `models/schema/__init__.py`
|
||||
- [ ] Imported using `from models.schema import ...`
|
||||
- [ ] NO file in `app/schemas/`
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── app/
|
||||
│ ├── api/ # API routes
|
||||
│ ├── core/ # Core functionality (config, database, auth)
|
||||
│ ├── services/ # Business logic
|
||||
│ ├── templates/ # Jinja2 templates
|
||||
│ └── routes/ # Page routes
|
||||
│
|
||||
├── models/ # ✓ Models live here!
|
||||
│ ├── database/ # ✓ SQLAlchemy models
|
||||
│ └── schema/ # ✓ Pydantic schemas
|
||||
│
|
||||
├── static/ # Frontend assets
|
||||
├── docs/ # Documentation
|
||||
├── tests/ # Tests
|
||||
└── scripts/ # Utility scripts
|
||||
```
|
||||
|
||||
**NOT:**
|
||||
```
|
||||
app/
|
||||
models/ # ❌ Don't create this
|
||||
schemas/ # ❌ Don't create this
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples from the Codebase
|
||||
|
||||
### ✅ Correct Examples
|
||||
|
||||
**Database Model:**
|
||||
```python
|
||||
# models/database/architecture_scan.py
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
class ArchitectureScan(Base):
|
||||
__tablename__ = "architecture_scans"
|
||||
id = Column(Integer, primary_key=True)
|
||||
```
|
||||
|
||||
**Import in Service:**
|
||||
```python
|
||||
# app/services/code_quality_service.py
|
||||
from models.database.architecture_scan import ArchitectureScan
|
||||
```
|
||||
|
||||
**Pydantic Schema:**
|
||||
```python
|
||||
# models/schema/admin.py
|
||||
from pydantic import BaseModel
|
||||
|
||||
class AdminDashboardStats(BaseModel):
|
||||
total_vendors: int
|
||||
total_users: int
|
||||
```
|
||||
|
||||
**Import in API:**
|
||||
```python
|
||||
# app/api/v1/admin/dashboard.py
|
||||
from models.schema.admin import AdminDashboardStats
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
**Golden Rule:** All models in `models/`, never in `app/models/` or `app/schemas/`.
|
||||
|
||||
**Quick Reference:**
|
||||
- Database models → `models/database/`
|
||||
- Pydantic schemas → `models/schema/`
|
||||
- Import pattern → `from models.{type} import ...`
|
||||
- No models in `app/` directory
|
||||
|
||||
This standard ensures consistency, clarity, and maintainability across the entire project.
|
||||
Reference in New Issue
Block a user