# 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
