Add rules to enforce consistent frontend component usage: - FE-001 (warning): Use pagination macro instead of inline HTML - FE-002 (warning): Use $icon() helper instead of inline SVGs - FE-003 (info): Use table macros for consistent styling - FE-004 (info): Use form macros for consistent styling Update validate_architecture.py to check FE-001 and FE-002 anti-patterns in templates, with exceptions for macro definitions and the components showcase page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
929 lines
31 KiB
YAML
929 lines
31 KiB
YAML
# Architecture Rules Configuration
|
|
# This file defines the key architectural decisions and patterns that must be followed
|
|
# across the application. The validator script uses these rules to check compliance.
|
|
|
|
version: "2.0"
|
|
project: "letzshop-product-import"
|
|
description: "Comprehensive architectural rules for multi-tenant e-commerce platform"
|
|
|
|
# ============================================================================
|
|
# CORE ARCHITECTURAL PRINCIPLES
|
|
# ============================================================================
|
|
|
|
principles:
|
|
- name: "Separation of Concerns"
|
|
description: "API endpoints should only handle HTTP concerns. Business logic belongs in services."
|
|
|
|
- name: "Layered Architecture"
|
|
description: "Routes → Services → Models. Each layer has specific responsibilities."
|
|
|
|
- name: "Type Safety"
|
|
description: "Use Pydantic models for request/response validation. Use SQLAlchemy models for database."
|
|
|
|
- name: "Proper Exception Handling"
|
|
description: "Services throw domain exceptions. Global handler converts to HTTP responses. Endpoints do NOT catch or raise HTTPException."
|
|
|
|
- name: "Multi-Tenancy"
|
|
description: "All queries must be scoped to vendor_id. No cross-vendor data access."
|
|
|
|
- name: "Consistent Naming"
|
|
description: "API files: plural, Services: singular+service, Models: singular"
|
|
|
|
# ============================================================================
|
|
# API ENDPOINT RULES (app/api/v1/**/*.py)
|
|
# ============================================================================
|
|
|
|
api_endpoint_rules:
|
|
|
|
- id: "API-001"
|
|
name: "Endpoint must use Pydantic models for request/response"
|
|
severity: "error"
|
|
description: |
|
|
All API endpoints must use Pydantic models (BaseModel) for request bodies
|
|
and response models. Never use raw dicts or SQLAlchemy models directly.
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
anti_patterns:
|
|
- "return dict"
|
|
- "-> dict"
|
|
- "return db_object"
|
|
example_good: |
|
|
@router.post("/vendors", response_model=VendorResponse)
|
|
async def create_vendor(vendor: VendorCreate):
|
|
return vendor_service.create_vendor(db, vendor)
|
|
|
|
- id: "API-002"
|
|
name: "Endpoint must NOT contain business logic"
|
|
severity: "error"
|
|
description: |
|
|
API endpoints should only handle HTTP concerns (validation, auth, response formatting).
|
|
All business logic must be delegated to service layer.
|
|
|
|
Transaction control (db.commit) IS allowed at endpoint level - this is the recommended
|
|
pattern for request-scoped transactions. One request = one transaction.
|
|
|
|
What's NOT allowed:
|
|
- db.add() - creating entities is business logic
|
|
- db.query() - complex queries are business logic
|
|
- db.delete() - deleting entities is business logic
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
anti_patterns:
|
|
- "db.add("
|
|
- "db.delete("
|
|
- "db.query("
|
|
# NOTE: db.commit() is intentionally NOT listed - it's allowed for transaction control
|
|
|
|
- id: "API-003"
|
|
name: "Endpoint must NOT raise ANY exceptions directly"
|
|
severity: "error"
|
|
description: |
|
|
API endpoints should NOT raise exceptions directly. Endpoints are a thin
|
|
orchestration layer that:
|
|
1. Accepts request parameters (validated by Pydantic)
|
|
2. Calls dependencies for authentication/authorization (deps.py raises exceptions)
|
|
3. Calls services for business logic (services raise domain exceptions)
|
|
4. Returns response (formatted by Pydantic)
|
|
|
|
Exception raising belongs in:
|
|
- Dependencies (app/api/deps.py) - authentication/authorization validation
|
|
- Services (app/services/) - business logic validation
|
|
|
|
The global exception handler catches all WizamartException subclasses and
|
|
converts them to appropriate HTTP responses.
|
|
|
|
WRONG (endpoint raises exception):
|
|
@router.get("/orders")
|
|
def get_orders(current_user: User = Depends(get_current_vendor_api)):
|
|
if not hasattr(current_user, "token_vendor_id"): # ❌ Redundant check
|
|
raise InvalidTokenException("...") # ❌ Don't raise here
|
|
return order_service.get_orders(db, current_user.token_vendor_id)
|
|
|
|
RIGHT (dependency guarantees, endpoint trusts):
|
|
@router.get("/orders")
|
|
def get_orders(current_user: User = Depends(get_current_vendor_api)):
|
|
# Dependency guarantees token_vendor_id is present
|
|
return order_service.get_orders(db, current_user.token_vendor_id)
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
anti_patterns:
|
|
- "raise HTTPException"
|
|
- "raise InvalidTokenException"
|
|
- "raise InsufficientPermissionsException"
|
|
- "if not hasattr\\(current_user.*token_vendor"
|
|
exceptions:
|
|
- "app/exceptions/handler.py" # Handler is allowed to use HTTPException
|
|
|
|
- id: "API-004"
|
|
name: "Endpoint must have proper authentication/authorization"
|
|
severity: "warning"
|
|
description: |
|
|
Protected endpoints must use Depends() for authentication.
|
|
Use get_current_user, get_current_admin, etc.
|
|
|
|
Auto-excluded files:
|
|
- */auth.py - Authentication endpoints (login, logout, register) are intentionally public
|
|
|
|
Public endpoint markers (place on line before or after decorator):
|
|
- # public - Descriptive marker for intentionally unauthenticated endpoints
|
|
- # noqa: API-004 - Standard noqa style to suppress warning
|
|
|
|
Example:
|
|
# public - Stripe webhook receives external callbacks
|
|
@router.post("/webhook/stripe")
|
|
def stripe_webhook(request: Request):
|
|
...
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
required_if_not_public:
|
|
- "Depends(get_current_"
|
|
auto_exclude_files:
|
|
- "*/auth.py"
|
|
public_markers:
|
|
- "# public"
|
|
- "# noqa: api-004"
|
|
|
|
- id: "API-005"
|
|
name: "Multi-tenant endpoints must scope queries to vendor_id"
|
|
severity: "error"
|
|
description: |
|
|
All queries in vendor/shop contexts must filter by vendor_id.
|
|
Use request.state.vendor_id from middleware.
|
|
pattern:
|
|
file_pattern: "app/api/v1/vendor/**/*.py"
|
|
file_pattern: "app/api/v1/shop/**/*.py"
|
|
discouraged_patterns:
|
|
- "db.query(.*).all()" # Without vendor filter
|
|
|
|
# ============================================================================
|
|
# SERVICE LAYER RULES (app/services/**/*.py)
|
|
# ============================================================================
|
|
|
|
service_layer_rules:
|
|
|
|
- id: "SVC-001"
|
|
name: "Service must NOT raise HTTPException"
|
|
severity: "error"
|
|
description: |
|
|
Services are business logic layer - they should NOT know about HTTP.
|
|
Raise domain-specific exceptions instead (ValueError, custom exceptions).
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
anti_patterns:
|
|
- "raise HTTPException"
|
|
- "from fastapi import HTTPException"
|
|
|
|
- id: "SVC-002"
|
|
name: "Service must use proper exception handling"
|
|
severity: "error"
|
|
description: |
|
|
Services should raise meaningful domain exceptions, not generic Exception.
|
|
Create custom exception classes for business rule violations.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
discouraged_patterns:
|
|
- "raise Exception\\("
|
|
|
|
- id: "SVC-003"
|
|
name: "Service methods must accept db session as parameter"
|
|
severity: "error"
|
|
description: |
|
|
Service methods should receive database session as a parameter for testability
|
|
and transaction control. Never create session inside service.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
required_in_method_signature:
|
|
- "db: Session"
|
|
anti_patterns:
|
|
- "SessionLocal()"
|
|
- "get_db()"
|
|
|
|
- id: "SVC-004"
|
|
name: "Service must use Pydantic models for input validation"
|
|
severity: "warning"
|
|
description: |
|
|
Service methods should accept Pydantic models for complex inputs
|
|
to ensure type safety and validation.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
encouraged_patterns:
|
|
- "BaseModel"
|
|
|
|
- id: "SVC-005"
|
|
name: "Service must scope queries to vendor_id in multi-tenant contexts"
|
|
severity: "error"
|
|
description: |
|
|
All database queries must be scoped to vendor_id to prevent cross-tenant data access.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
check: "vendor_scoping"
|
|
|
|
- id: "SVC-006"
|
|
name: "Service must NOT call db.commit()"
|
|
severity: "warning"
|
|
description: |
|
|
Services should NOT commit transactions. Transaction control belongs at the
|
|
API endpoint level where one request = one transaction.
|
|
|
|
This allows:
|
|
- Composing multiple service calls in a single transaction
|
|
- Clean rollback on any failure
|
|
- Easier testing of services in isolation
|
|
|
|
The endpoint should call db.commit() after all service operations succeed.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
anti_patterns:
|
|
- "db.commit()"
|
|
exceptions:
|
|
- "log_service.py" # Audit logs may need immediate commits
|
|
|
|
# ============================================================================
|
|
# MODEL RULES (models/database/*.py, models/schema/*.py)
|
|
# ============================================================================
|
|
|
|
model_rules:
|
|
|
|
- id: "MDL-001"
|
|
name: "Database models must use SQLAlchemy Base"
|
|
severity: "error"
|
|
description: |
|
|
All database models must inherit from SQLAlchemy Base and use proper
|
|
column definitions with types and constraints.
|
|
pattern:
|
|
file_pattern: "models/database/**/*.py"
|
|
required_patterns:
|
|
- "class.*\\(Base\\):"
|
|
|
|
- id: "MDL-002"
|
|
name: "Use Pydantic models separately from SQLAlchemy models"
|
|
severity: "error"
|
|
description: |
|
|
Never mix SQLAlchemy and Pydantic in the same model.
|
|
SQLAlchemy = database schema, Pydantic = API validation/serialization.
|
|
pattern:
|
|
file_pattern: "models/**/*.py"
|
|
anti_patterns:
|
|
- "class.*\\(Base, BaseModel\\):"
|
|
|
|
- id: "MDL-003"
|
|
name: "Pydantic models must use from_attributes for ORM mode"
|
|
severity: "error"
|
|
description: |
|
|
Pydantic response models must enable from_attributes to work with SQLAlchemy models.
|
|
pattern:
|
|
file_pattern: "models/schema/**/*.py"
|
|
required_in_response_models:
|
|
- "from_attributes = True"
|
|
|
|
- id: "MDL-004"
|
|
name: "Database models use singular table names"
|
|
severity: "warning"
|
|
description: |
|
|
Database table names should be singular lowercase (e.g., 'vendor' not 'vendors').
|
|
pattern:
|
|
file_pattern: "models/database/**/*.py"
|
|
check: "table_naming"
|
|
|
|
# ============================================================================
|
|
# EXCEPTION HANDLING RULES
|
|
# ============================================================================
|
|
|
|
exception_rules:
|
|
|
|
- id: "EXC-001"
|
|
name: "Define custom exceptions in exceptions module"
|
|
severity: "warning"
|
|
description: |
|
|
Create domain-specific exceptions in app/exceptions/ for better
|
|
error handling and clarity.
|
|
pattern:
|
|
file_pattern: "app/exceptions/**/*.py"
|
|
encouraged_structure: |
|
|
class VendorError(Exception):
|
|
"""Base exception for vendor-related errors"""
|
|
pass
|
|
|
|
- id: "EXC-002"
|
|
name: "Never use bare except"
|
|
severity: "error"
|
|
description: |
|
|
Always specify exception types. Bare except catches everything including
|
|
KeyboardInterrupt and SystemExit.
|
|
pattern:
|
|
file_pattern: "**/*.py"
|
|
anti_patterns:
|
|
- "except:"
|
|
- "except\\s*:"
|
|
|
|
- id: "EXC-003"
|
|
name: "Log all exceptions with context"
|
|
severity: "warning"
|
|
description: |
|
|
When catching exceptions, log them with context and stack trace.
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
encouraged_patterns:
|
|
- "logger.error"
|
|
- "exc_info=True"
|
|
|
|
- id: "EXC-004"
|
|
name: "Domain exceptions must inherit from WizamartException"
|
|
severity: "error"
|
|
description: |
|
|
All custom domain exceptions must inherit from WizamartException (or its
|
|
subclasses like ResourceNotFoundException, ValidationException, etc.).
|
|
This ensures the global exception handler catches and converts them properly.
|
|
pattern:
|
|
file_pattern: "app/exceptions/**/*.py"
|
|
required_base_class: "WizamartException"
|
|
example_good: |
|
|
class VendorNotFoundException(ResourceNotFoundException):
|
|
def __init__(self, vendor_code: str):
|
|
super().__init__(resource_type="Vendor", identifier=vendor_code)
|
|
|
|
- id: "EXC-005"
|
|
name: "Exception handler must be registered"
|
|
severity: "error"
|
|
description: |
|
|
The global exception handler must be set up in app initialization to
|
|
catch WizamartException and convert to HTTP responses.
|
|
pattern:
|
|
file_pattern: "app/main.py"
|
|
required_patterns:
|
|
- "setup_exception_handlers"
|
|
|
|
# ============================================================================
|
|
# NAMING CONVENTION RULES
|
|
# ============================================================================
|
|
|
|
naming_rules:
|
|
|
|
- id: "NAM-001"
|
|
name: "API files use PLURAL names"
|
|
severity: "error"
|
|
description: |
|
|
API endpoint files should use plural names (vendors.py, products.py)
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
check: "plural_naming"
|
|
exceptions:
|
|
- "__init__.py"
|
|
- "auth.py"
|
|
- "health.py"
|
|
|
|
- id: "NAM-002"
|
|
name: "Service files use SINGULAR + 'service' suffix"
|
|
severity: "error"
|
|
description: |
|
|
Service files should use singular name + _service (vendor_service.py)
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
check: "service_naming"
|
|
|
|
- id: "NAM-003"
|
|
name: "Model files use SINGULAR names"
|
|
severity: "error"
|
|
description: |
|
|
Both database and schema model files use singular names (product.py)
|
|
pattern:
|
|
file_pattern: "models/**/*.py"
|
|
check: "singular_naming"
|
|
|
|
- id: "NAM-004"
|
|
name: "Use consistent terminology: vendor not shop"
|
|
severity: "warning"
|
|
description: |
|
|
Use 'vendor' consistently, not 'shop' (except for shop frontend)
|
|
pattern:
|
|
file_pattern: "app/**/*.py"
|
|
discouraged_terms:
|
|
- "shop_id" # Use vendor_id
|
|
- "shop_service" # Use vendor_service
|
|
|
|
- id: "NAM-005"
|
|
name: "Use consistent terminology: inventory not stock"
|
|
severity: "warning"
|
|
description: |
|
|
Use 'inventory' consistently, not 'stock'
|
|
pattern:
|
|
file_pattern: "app/**/*.py"
|
|
discouraged_terms:
|
|
- "stock_service" # Use inventory_service
|
|
|
|
# ============================================================================
|
|
# JAVASCRIPT ARCHITECTURE RULES (Frontend)
|
|
# ============================================================================
|
|
|
|
javascript_rules:
|
|
|
|
- id: "JS-001"
|
|
name: "Use centralized logger, not console"
|
|
severity: "error"
|
|
description: |
|
|
Use window.LogConfig.createLogger() for consistent logging.
|
|
Never use console.log, console.error, console.warn directly.
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "console\\.log"
|
|
- "console\\.error"
|
|
- "console\\.warn"
|
|
exceptions:
|
|
- "// eslint-disable"
|
|
- "console.log('✅" # Bootstrap messages allowed
|
|
auto_exclude_files:
|
|
- "init-*.js" # Init files run before logger is available
|
|
|
|
- id: "JS-002"
|
|
name: "Use lowercase apiClient for API calls"
|
|
severity: "error"
|
|
description: |
|
|
Use lowercase 'apiClient' consistently, not 'ApiClient' or 'API_CLIENT'
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "ApiClient\\."
|
|
- "API_CLIENT\\."
|
|
required_pattern: "apiClient\\."
|
|
|
|
- id: "JS-003"
|
|
name: "Alpine components must spread ...data()"
|
|
severity: "error"
|
|
description: |
|
|
All Alpine.js components must inherit base layout data using spread operator
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
required_in_alpine_components:
|
|
- "\\.\\.\\.data\\(\\)"
|
|
|
|
- id: "JS-004"
|
|
name: "Alpine components must set currentPage"
|
|
severity: "error"
|
|
description: |
|
|
All Alpine.js page components must set a currentPage identifier
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
required_in_alpine_components:
|
|
- "currentPage:"
|
|
|
|
- id: "JS-005"
|
|
name: "Initialization methods must include guard"
|
|
severity: "error"
|
|
description: |
|
|
Init methods should prevent duplicate initialization with guard
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
recommended_pattern: |
|
|
if (window._pageInitialized) return;
|
|
window._pageInitialized = true;
|
|
|
|
- id: "JS-006"
|
|
name: "All async operations must have try/catch with error logging"
|
|
severity: "error"
|
|
description: |
|
|
All API calls and async operations must have error handling
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
check: "async_error_handling"
|
|
|
|
- id: "JS-007"
|
|
name: "Set loading state before async operations"
|
|
severity: "warning"
|
|
description: |
|
|
Loading state should be set before and cleared after async operations
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
recommended_pattern: |
|
|
loading = true;
|
|
try {
|
|
// operation
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
|
|
# ============================================================================
|
|
# TEMPLATE RULES (Jinja2)
|
|
# ============================================================================
|
|
|
|
template_rules:
|
|
|
|
- id: "TPL-001"
|
|
name: "Admin templates must extend admin/base.html"
|
|
severity: "error"
|
|
description: |
|
|
All admin templates must extend the base template for consistency.
|
|
|
|
Auto-excluded files:
|
|
- login.html - Standalone login page (no sidebar/navigation)
|
|
- errors/*.html - Error pages extend errors/base.html instead
|
|
- test-*.html - Test/development templates
|
|
|
|
Standalone template markers (place in first 5 lines):
|
|
- {# standalone #} - Mark template as intentionally standalone
|
|
- {# noqa: TPL-001 #} - Standard noqa style to suppress error
|
|
- <!-- standalone --> - HTML comment style
|
|
pattern:
|
|
file_pattern: "app/templates/admin/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]admin/base\\.html['\"] %}"
|
|
auto_exclude_files:
|
|
- "login.html"
|
|
- "errors/"
|
|
- "test-"
|
|
standalone_markers:
|
|
- "{# standalone #}"
|
|
- "{# noqa: tpl-001 #}"
|
|
- "<!-- standalone -->"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-002"
|
|
name: "Vendor templates must extend vendor/base.html"
|
|
severity: "error"
|
|
description: "All vendor templates must extend the base template"
|
|
pattern:
|
|
file_pattern: "app/templates/vendor/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]vendor/base\\.html['\"] %}"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-003"
|
|
name: "Shop templates must extend shop/base.html"
|
|
severity: "error"
|
|
description: "All shop templates must extend the base template"
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]shop/base\\.html['\"] %}"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-004"
|
|
name: "Use x-text for dynamic text content (prevents XSS)"
|
|
severity: "warning"
|
|
description: |
|
|
Use x-text directive for dynamic content to prevent XSS vulnerabilities
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<p x-text="item.name"></p>'
|
|
|
|
- id: "TPL-005"
|
|
name: "Use x-html ONLY for safe content"
|
|
severity: "error"
|
|
description: |
|
|
Use x-html only for trusted content like icons, never for user-generated content
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
safe_usage:
|
|
- 'x-html="\\$icon\\('
|
|
|
|
- id: "TPL-006"
|
|
name: "Implement loading state for data loads"
|
|
severity: "warning"
|
|
description: |
|
|
All templates that load data should show loading state
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<div x-show="loading">Loading...</div>'
|
|
|
|
- id: "TPL-007"
|
|
name: "Implement empty state when no data"
|
|
severity: "warning"
|
|
description: |
|
|
Show empty state when lists have no items
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<template x-if="items.length === 0">No items</template>'
|
|
|
|
# ============================================================================
|
|
# FRONTEND COMPONENT RULES
|
|
# ============================================================================
|
|
|
|
frontend_component_rules:
|
|
|
|
- id: "FE-001"
|
|
name: "Use pagination macro instead of inline HTML"
|
|
severity: "warning"
|
|
description: |
|
|
Use the shared pagination macro instead of duplicating pagination HTML.
|
|
Import from shared/macros/pagination.html.
|
|
|
|
WRONG (inline pagination):
|
|
<div class="grid px-4 py-3 text-xs font-semibold...">
|
|
<span class="flex items-center col-span-3">
|
|
Showing ...
|
|
</span>
|
|
<nav aria-label="Table navigation">...
|
|
|
|
RIGHT (use macro):
|
|
{% from 'shared/macros/pagination.html' import pagination %}
|
|
{{ pagination() }}
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- 'aria-label="Table navigation"'
|
|
- "previousPage\\(\\).*nextPage\\(\\)"
|
|
exceptions:
|
|
- "shared/macros/pagination.html"
|
|
- "components.html" # Showcase page
|
|
|
|
- id: "FE-002"
|
|
name: "Use $icon() helper instead of inline SVGs"
|
|
severity: "warning"
|
|
description: |
|
|
Use the Alpine.js $icon() helper for consistent iconography.
|
|
Do not use inline <svg> elements.
|
|
|
|
WRONG (inline SVG):
|
|
<svg class="w-4 h-4" viewBox="0 0 20 20">...</svg>
|
|
|
|
RIGHT (icon helper):
|
|
<span x-html="$icon('arrow-left', 'w-4 h-4')"></span>
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "<svg.*viewBox.*>.*</svg>"
|
|
exceptions:
|
|
- "base.html" # Base templates may define SVG sprites
|
|
- "components.html" # Showcase page
|
|
- "shared/macros/" # Macros may contain SVGs for compatibility
|
|
|
|
- id: "FE-003"
|
|
name: "Use table macros for consistent table styling"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared table macros for consistent table styling.
|
|
Import from shared/macros/tables.html.
|
|
|
|
Recommended macros:
|
|
- table_wrapper() - Wraps table with overflow and shadow
|
|
- table_header(['Column1', 'Column2']) - Consistent header styling
|
|
- table_empty_state(colspan, 'icon', 'Title', 'Message') - Empty states
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/tables.html' import"
|
|
|
|
- id: "FE-004"
|
|
name: "Use form macros for consistent form styling"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared form macros for consistent input styling and validation.
|
|
Import from shared/macros/forms.html.
|
|
|
|
Recommended macros:
|
|
- form_input(label, name, x_model, ...) - Text inputs with validation
|
|
- form_select(label, x_model, options) - Select dropdowns
|
|
- form_textarea(label, x_model, rows) - Textareas
|
|
- form_checkbox(label, x_model) - Checkboxes
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/forms.html' import"
|
|
|
|
# ============================================================================
|
|
# FRONTEND STYLING RULES
|
|
# ============================================================================
|
|
|
|
styling_rules:
|
|
|
|
- id: "CSS-001"
|
|
name: "Use Tailwind utility classes"
|
|
severity: "warning"
|
|
description: |
|
|
Prefer Tailwind utility classes over custom CSS
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged: true
|
|
|
|
- id: "CSS-002"
|
|
name: "Support dark mode with dark: prefix"
|
|
severity: "warning"
|
|
description: |
|
|
All color classes should include dark mode variants
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: 'class="bg-white dark:bg-gray-800"'
|
|
|
|
- id: "CSS-003"
|
|
name: "Shop templates use vendor theme CSS variables"
|
|
severity: "error"
|
|
description: |
|
|
Shop templates must use CSS variables for vendor-specific theming
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
required_pattern: 'var\\(--color-primary\\)'
|
|
|
|
- id: "CSS-004"
|
|
name: "Mobile-first responsive design"
|
|
severity: "warning"
|
|
description: |
|
|
Use mobile-first responsive classes
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: 'class="grid-cols-1 md:grid-cols-2 lg:grid-cols-4"'
|
|
|
|
# ============================================================================
|
|
# MIDDLEWARE RULES
|
|
# ============================================================================
|
|
|
|
middleware_rules:
|
|
|
|
- id: "MDW-001"
|
|
name: "Middleware files use simple nouns without _middleware suffix"
|
|
severity: "warning"
|
|
description: |
|
|
Middleware files should be named with simple nouns (auth.py, not auth_middleware.py)
|
|
pattern:
|
|
file_pattern: "middleware/**/*.py"
|
|
check: "middleware_naming"
|
|
|
|
- id: "MDW-002"
|
|
name: "Vendor context must be injected for vendor/shop routes"
|
|
severity: "error"
|
|
description: |
|
|
Vendor context middleware must set request.state.vendor_id and request.state.vendor
|
|
pattern:
|
|
file_pattern: "middleware/vendor_context.py"
|
|
required: true
|
|
|
|
# ============================================================================
|
|
# MULTI-TENANCY RULES
|
|
# ============================================================================
|
|
|
|
multi_tenancy_rules:
|
|
|
|
- id: "MT-001"
|
|
name: "All queries must be scoped to vendor_id"
|
|
severity: "error"
|
|
description: |
|
|
In vendor/shop contexts, all database queries must filter by vendor_id
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
context: "vendor_shop"
|
|
required_pattern: ".filter\\(.*vendor_id.*\\)"
|
|
|
|
- id: "MT-002"
|
|
name: "No cross-vendor data access"
|
|
severity: "error"
|
|
description: |
|
|
Queries must never access data from other vendors
|
|
pattern:
|
|
file_pattern: "app/services/**/*.py"
|
|
enforcement: "database_query_level"
|
|
|
|
# ============================================================================
|
|
# AUTHENTICATION & AUTHORIZATION RULES
|
|
# ============================================================================
|
|
|
|
auth_rules:
|
|
|
|
- id: "AUTH-001"
|
|
name: "Use JWT tokens in Authorization header"
|
|
severity: "error"
|
|
description: |
|
|
Authentication must use JWT tokens in Authorization: Bearer header
|
|
pattern:
|
|
file_pattern: "app/api/**/*.py"
|
|
enforcement: "middleware"
|
|
|
|
- id: "AUTH-002"
|
|
name: "Role-based access control with Depends"
|
|
severity: "error"
|
|
description: |
|
|
Use Depends(get_current_admin/vendor/customer) for role checks
|
|
pattern:
|
|
file_pattern: "app/api/v1/**/*.py"
|
|
required: "Depends\\(get_current_"
|
|
|
|
- id: "AUTH-003"
|
|
name: "Never store plain passwords"
|
|
severity: "error"
|
|
description: |
|
|
Always hash passwords with bcrypt before storing
|
|
pattern:
|
|
file_pattern: "app/services/auth_service.py"
|
|
required: "bcrypt"
|
|
|
|
- id: "AUTH-004"
|
|
name: "Vendor context pattern - use appropriate dependency for endpoint type"
|
|
severity: "error"
|
|
description: |
|
|
Two vendor context patterns exist - use the appropriate one:
|
|
|
|
1. SHOP ENDPOINTS (public, no authentication required):
|
|
- Use: vendor: Vendor = Depends(require_vendor_context())
|
|
- Vendor is detected from URL/subdomain/domain
|
|
- File pattern: app/api/v1/shop/**/*.py
|
|
- Mark as public with: # public
|
|
|
|
2. VENDOR API ENDPOINTS (authenticated):
|
|
- Use: current_user.token_vendor_id from JWT token
|
|
- Or use permission dependencies: require_vendor_permission(), require_vendor_owner
|
|
- These dependencies get vendor from token and set request.state.vendor
|
|
- File pattern: app/api/v1/vendor/**/*.py
|
|
|
|
DEPRECATED for vendor APIs:
|
|
- require_vendor_context() - only for shop endpoints
|
|
- getattr(request.state, "vendor", None) without permission dependency
|
|
|
|
See: docs/backend/vendor-in-token-architecture.md
|
|
pattern:
|
|
file_pattern: "app/api/v1/vendor/**/*.py"
|
|
anti_patterns:
|
|
- "require_vendor_context\\(\\)"
|
|
file_pattern: "app/api/v1/shop/**/*.py"
|
|
required_patterns:
|
|
- "require_vendor_context\\(\\)|# public"
|
|
|
|
# ============================================================================
|
|
# CODE QUALITY RULES
|
|
# ============================================================================
|
|
|
|
code_quality_rules:
|
|
|
|
- id: "QUAL-001"
|
|
name: "All code must be formatted with Ruff"
|
|
severity: "error"
|
|
description: |
|
|
Run 'make format' before committing
|
|
enforcement: "pre_commit"
|
|
|
|
- id: "QUAL-002"
|
|
name: "All code must pass Ruff linting"
|
|
severity: "error"
|
|
description: |
|
|
Run 'make lint' before committing
|
|
enforcement: "pre_commit"
|
|
|
|
- id: "QUAL-003"
|
|
name: "Type hints recommended for functions"
|
|
severity: "warning"
|
|
description: |
|
|
Add type hints to function parameters and return types
|
|
pattern:
|
|
file_pattern: "app/**/*.py"
|
|
encouraged: true
|
|
|
|
# ============================================================================
|
|
# VALIDATION SEVERITY LEVELS
|
|
# ============================================================================
|
|
|
|
severity_levels:
|
|
error:
|
|
description: "Critical architectural violation - must be fixed"
|
|
exit_code: 1
|
|
|
|
warning:
|
|
description: "Pattern deviation - should be fixed"
|
|
exit_code: 0
|
|
|
|
info:
|
|
description: "Suggestion for improvement"
|
|
exit_code: 0
|
|
|
|
# ============================================================================
|
|
# IGNORED PATTERNS
|
|
# ============================================================================
|
|
|
|
ignore:
|
|
files:
|
|
- "**/*_test.py"
|
|
- "**/test_*.py"
|
|
- "**/__pycache__/**"
|
|
- "**/migrations/**"
|
|
- "**/alembic/versions/**"
|
|
- "**/node_modules/**"
|
|
- "**/.venv/**"
|
|
- "**/venv/**"
|
|
- ".venv/**"
|
|
- "venv/**"
|
|
- "**/build/**"
|
|
- "**/dist/**"
|
|
|
|
patterns:
|
|
# Allow HTTPException in specific files
|
|
- file: "app/core/exceptions.py"
|
|
pattern: "HTTPException"
|
|
reason: "Exception handling utilities"
|
|
|
|
- file: "app/exceptions/handler.py"
|
|
pattern: "HTTPException"
|
|
reason: "Exception handler converts to HTTP"
|
|
|
|
# ============================================================================
|
|
# DOCUMENTATION
|
|
# ============================================================================
|
|
|
|
documentation:
|
|
architecture: "docs/architecture/overview.md"
|
|
backend: "docs/backend/overview.md"
|
|
frontend: "docs/frontend/overview.md"
|
|
contributing: "docs/development/contributing.md"
|
|
code_quality: "docs/development/code-quality.md"
|