# app/modules/loyalty/routes/api/merchant.py """ Loyalty module merchant routes. Merchant portal endpoints for full program CRUD: - Get merchant's loyalty program - Create a loyalty program - Update the loyalty program - Delete the loyalty program Authentication: Authorization header (API-only, no cookies for CSRF safety). The user must own at least one active merchant (validated by get_merchant_for_current_user). Auto-discovered by the route system (merchant.py in routes/api/ triggers registration under /api/v1/merchants/loyalty/*). """ import logging from fastapi import APIRouter, Depends from sqlalchemy.orm import Session from app.api.deps import get_merchant_for_current_user from app.core.database import get_db from app.modules.loyalty.schemas import ( ProgramCreate, ProgramResponse, ProgramUpdate, ) from app.modules.loyalty.schemas.program import MerchantStatsResponse from app.modules.loyalty.services import program_service from app.modules.tenancy.models import Merchant logger = logging.getLogger(__name__) ROUTE_CONFIG = { "prefix": "/loyalty", } router = APIRouter() def _build_program_response(program) -> ProgramResponse: """Build a ProgramResponse from a program ORM object.""" response = ProgramResponse.model_validate(program) response.is_stamps_enabled = program.is_stamps_enabled response.is_points_enabled = program.is_points_enabled response.display_name = program.display_name return response # ============================================================================= # Statistics # ============================================================================= @router.get("/stats", response_model=MerchantStatsResponse) def get_stats( merchant: Merchant = Depends(get_merchant_for_current_user), db: Session = Depends(get_db), ): """Get merchant-wide loyalty statistics across all locations.""" stats = program_service.get_merchant_stats(db, merchant.id) return MerchantStatsResponse(**stats) # ============================================================================= # Program CRUD # ============================================================================= @router.get("/program", response_model=ProgramResponse) def get_program( merchant: Merchant = Depends(get_merchant_for_current_user), db: Session = Depends(get_db), ): """Get the merchant's loyalty program.""" program = program_service.require_program_by_merchant(db, merchant.id) return _build_program_response(program) @router.post("/program", response_model=ProgramResponse, status_code=201) def create_program( data: ProgramCreate, merchant: Merchant = Depends(get_merchant_for_current_user), db: Session = Depends(get_db), ): """Create a loyalty program for the merchant.""" program = program_service.create_program(db, merchant.id, data) return _build_program_response(program) @router.patch("/program", response_model=ProgramResponse) def update_program( data: ProgramUpdate, merchant: Merchant = Depends(get_merchant_for_current_user), db: Session = Depends(get_db), ): """Update the merchant's loyalty program.""" program = program_service.require_program_by_merchant(db, merchant.id) program = program_service.update_program(db, program.id, data) return _build_program_response(program) @router.delete("/program", status_code=204) def delete_program( merchant: Merchant = Depends(get_merchant_for_current_user), db: Session = Depends(get_db), ): """Delete the merchant's loyalty program.""" program = program_service.require_program_by_merchant(db, merchant.id) program_service.delete_program(db, program.id) logger.info(f"Merchant {merchant.id} ({merchant.name}) deleted loyalty program")