Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
272 lines
10 KiB
YAML
272 lines
10 KiB
YAML
# Architecture Rules - API Endpoint Rules
|
|
# Rules for app/api/v1/**/*.py files
|
|
|
|
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.
|
|
|
|
WHY THIS MATTERS:
|
|
- Type safety: Pydantic validates response structure at runtime
|
|
- Documentation: OpenAPI/Swagger auto-generates accurate docs
|
|
- Contract stability: Schema changes are explicit and reviewable
|
|
- IDE support: Consumers get autocomplete and type hints
|
|
- Prevents bugs: Field name mismatches caught immediately
|
|
|
|
COMMON VIOLATION: Service returns dict, frontend expects different field names.
|
|
Example: Service returns {"total_imports": 5} but frontend expects {"total": 5}.
|
|
With response_model, this mismatch is caught immediately.
|
|
|
|
SCHEMA LOCATION: All response schemas must be defined in models/schema/*.py,
|
|
never inline in endpoint files. This ensures schemas are reusable and discoverable.
|
|
pattern:
|
|
file_pattern:
|
|
- "app/api/v1/**/*.py"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
anti_patterns:
|
|
- "return dict"
|
|
- "-> dict"
|
|
- "return db_object"
|
|
- "return {"
|
|
example_good: |
|
|
# In models/schema/stats.py
|
|
class ImportStatsResponse(BaseModel):
|
|
total: int
|
|
pending: int
|
|
completed: int
|
|
failed: int
|
|
|
|
# In app/api/v1/admin/marketplace.py
|
|
@router.get("/stats", response_model=ImportStatsResponse)
|
|
def get_import_statistics(db: Session = Depends(get_db)):
|
|
return stats_service.get_import_statistics(db)
|
|
example_bad: |
|
|
# WRONG: No response_model, returns raw dict
|
|
@router.get("/stats")
|
|
def get_import_statistics(db: Session = Depends(get_db)):
|
|
return stats_service.get_import_statistics(db) # Returns dict
|
|
|
|
# WRONG: Schema defined inline in endpoint file
|
|
class MyResponse(BaseModel): # Should be in models/schema/
|
|
...
|
|
|
|
@router.get("/data", response_model=MyResponse)
|
|
def get_data(): ...
|
|
|
|
- id: "API-002"
|
|
name: "No Pydantic imports in API endpoint files"
|
|
severity: "error"
|
|
description: |
|
|
API endpoint files must NOT import Pydantic directly (BaseModel, Field, etc.).
|
|
All Pydantic schemas must be defined in models/schema/*.py and imported from there.
|
|
|
|
WHY THIS MATTERS:
|
|
- Separation of concerns: API handles HTTP, schemas handle data structure
|
|
- Discoverability: All schemas in one place for easy review
|
|
- Reusability: Schemas can be shared across endpoints
|
|
- Prevents inline schema definitions that violate API-001
|
|
|
|
WRONG:
|
|
from pydantic import BaseModel, Field
|
|
class MyRequest(BaseModel): # Inline schema
|
|
name: str
|
|
|
|
RIGHT:
|
|
# In models/schema/my_feature.py
|
|
from pydantic import BaseModel
|
|
class MyRequest(BaseModel):
|
|
name: str
|
|
|
|
# In app/api/v1/admin/my_feature.py
|
|
from models.schema.my_feature import MyRequest
|
|
pattern:
|
|
file_pattern:
|
|
- "app/api/v1/**/*.py"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
anti_patterns:
|
|
- "from pydantic import"
|
|
- "from pydantic.main import"
|
|
example_good: |
|
|
# In app/api/v1/admin/vendors.py
|
|
from models.schema.vendor import (
|
|
LetzshopExportRequest,
|
|
LetzshopExportResponse,
|
|
)
|
|
|
|
@router.post("/export", response_model=LetzshopExportResponse)
|
|
def export_products(request: LetzshopExportRequest):
|
|
...
|
|
example_bad: |
|
|
# In app/api/v1/admin/vendors.py
|
|
from pydantic import BaseModel # WRONG: Don't import pydantic here
|
|
|
|
class LetzshopExportRequest(BaseModel): # WRONG: Define in models/schema/
|
|
include_inactive: bool = False
|
|
|
|
- id: "API-003"
|
|
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"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
anti_patterns:
|
|
- "db.add("
|
|
- "db.delete("
|
|
- "db.query("
|
|
|
|
- id: "API-004"
|
|
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 OrionException 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"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
anti_patterns:
|
|
- "raise HTTPException"
|
|
- "raise InvalidTokenException"
|
|
- "raise InsufficientPermissionsException"
|
|
- "if not hasattr\\(current_user.*token_vendor"
|
|
exceptions:
|
|
- "app/exceptions/handler.py"
|
|
|
|
- id: "API-005"
|
|
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-005 - 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"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
required_if_not_public:
|
|
- "Depends(get_current_"
|
|
auto_exclude_files:
|
|
- "*/auth.py"
|
|
public_markers:
|
|
- "# public"
|
|
- "# noqa: api-005"
|
|
|
|
- id: "API-006"
|
|
name: "Multi-tenant endpoints must scope queries to vendor_id"
|
|
severity: "error"
|
|
description: |
|
|
All queries in vendor/storefront contexts must filter by vendor_id.
|
|
Use request.state.vendor_id from middleware.
|
|
pattern:
|
|
file_pattern:
|
|
- "app/api/v1/vendor/**/*.py"
|
|
- "app/modules/*/routes/api/store*.py"
|
|
- "app/api/v1/storefront/**/*.py"
|
|
- "app/modules/*/routes/api/storefront*.py"
|
|
discouraged_patterns:
|
|
- "db.query(.*).all()"
|
|
|
|
- id: "API-007"
|
|
name: "API endpoints must NOT import database models directly"
|
|
severity: "error"
|
|
description: |
|
|
API endpoints must follow the layered architecture: Routes → Services → Models.
|
|
Routes should NEVER import database models directly.
|
|
|
|
WHY THIS MATTERS:
|
|
- Layered architecture: Each layer has clear responsibilities
|
|
- Testability: Routes can be tested without database
|
|
- Flexibility: Database schema changes don't affect route signatures
|
|
- Type safety: Schemas define the API contract, not database structure
|
|
|
|
For dependency injection (e.g., current user/customer), use schemas instead
|
|
of database models as return types.
|
|
|
|
WRONG:
|
|
from models.database.customer import Customer
|
|
from models.database.order import Order
|
|
from app.modules.customers.models.customer import Customer
|
|
|
|
@router.get("/orders")
|
|
def get_orders(customer: Customer = Depends(get_current_customer)):
|
|
...
|
|
|
|
RIGHT:
|
|
from app.modules.customers.schemas import CustomerContext
|
|
from app.modules.orders.services import order_service
|
|
|
|
@router.get("/orders")
|
|
def get_orders(customer: CustomerContext = Depends(get_current_customer)):
|
|
return order_service.get_orders(db, customer.id, customer.vendor_id)
|
|
|
|
ALLOWED IMPORTS IN API ROUTES:
|
|
- Schemas: from models.schema.* or from app.modules.*.schemas
|
|
- Services: from app.services.* or from app.modules.*.services
|
|
- Dependencies: from app.api.deps
|
|
- Database session: from app.core.database import get_db
|
|
|
|
NOT ALLOWED:
|
|
- from models.database.*
|
|
- from app.modules.*.models.*
|
|
pattern:
|
|
file_pattern:
|
|
- "app/api/**/*.py"
|
|
- "app/modules/*/routes/api/**/*.py"
|
|
anti_patterns:
|
|
- "from models\\.database\\."
|
|
- "from app\\.modules\\.[a-z_]+\\.models\\."
|
|
exclude_files:
|
|
- "app/api/deps.py" # Dependencies may need model access for queries
|