# app/modules/hosting/routes/api/admin_sites.py """ Admin API routes for hosted site management. """ import logging from math import ceil from fastapi import APIRouter, Depends, Path, Query from sqlalchemy.orm import Session from app.api.deps import get_current_admin_api from app.core.database import get_db from app.modules.hosting.schemas.hosted_site import ( AcceptProposalRequest, GoLiveRequest, HostedSiteCreate, HostedSiteDeleteResponse, HostedSiteDetailResponse, HostedSiteListResponse, HostedSiteResponse, HostedSiteUpdate, SendProposalRequest, ) from app.modules.hosting.services.hosted_site_service import hosted_site_service from app.modules.tenancy.schemas.auth import UserContext router = APIRouter(prefix="/sites") logger = logging.getLogger(__name__) def _to_response(site) -> HostedSiteResponse: """Convert a hosted site model to response schema.""" return HostedSiteResponse( id=site.id, store_id=site.store_id, prospect_id=site.prospect_id, status=site.status.value if hasattr(site.status, "value") else str(site.status), business_name=site.business_name, contact_name=site.contact_name, contact_email=site.contact_email, contact_phone=site.contact_phone, proposal_sent_at=site.proposal_sent_at, proposal_accepted_at=site.proposal_accepted_at, went_live_at=site.went_live_at, proposal_notes=site.proposal_notes, live_domain=site.live_domain, internal_notes=site.internal_notes, created_at=site.created_at, updated_at=site.updated_at, ) @router.get("", response_model=HostedSiteListResponse) def list_sites( page: int = Query(1, ge=1), per_page: int = Query(20, ge=1, le=100), search: str | None = Query(None), status: str | None = Query(None), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """List hosted sites with filters and pagination.""" sites, total = hosted_site_service.get_all( db, page=page, per_page=per_page, search=search, status=status, ) return HostedSiteListResponse( items=[_to_response(s) for s in sites], total=total, page=page, per_page=per_page, pages=ceil(total / per_page) if per_page else 0, ) @router.get("/{site_id}", response_model=HostedSiteDetailResponse) def get_site( site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Get full hosted site detail with services.""" site = hosted_site_service.get_by_id(db, site_id) return site @router.post("", response_model=HostedSiteResponse) def create_site( data: HostedSiteCreate, db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Create a new hosted site (auto-creates Store).""" site = hosted_site_service.create(db, data.model_dump(exclude_none=True)) db.commit() return _to_response(site) @router.post("/from-prospect/{prospect_id}", response_model=HostedSiteResponse) def create_from_prospect( prospect_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Create a hosted site pre-filled from prospect data.""" site = hosted_site_service.create_from_prospect(db, prospect_id) db.commit() return _to_response(site) @router.put("/{site_id}", response_model=HostedSiteResponse) def update_site( data: HostedSiteUpdate, site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Update a hosted site.""" site = hosted_site_service.update(db, site_id, data.model_dump(exclude_none=True)) db.commit() return _to_response(site) @router.delete("/{site_id}", response_model=HostedSiteDeleteResponse) def delete_site( site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Delete a hosted site.""" hosted_site_service.delete(db, site_id) db.commit() return HostedSiteDeleteResponse(message="Hosted site deleted") # ── Lifecycle endpoints ──────────────────────────────────────────────── @router.post("/{site_id}/mark-poc-ready", response_model=HostedSiteResponse) def mark_poc_ready( site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Mark site as POC ready.""" site = hosted_site_service.mark_poc_ready(db, site_id) db.commit() return _to_response(site) @router.post("/{site_id}/send-proposal", response_model=HostedSiteResponse) def send_proposal( data: SendProposalRequest, site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Send proposal to prospect.""" site = hosted_site_service.send_proposal(db, site_id, notes=data.notes) db.commit() return _to_response(site) @router.post("/{site_id}/accept", response_model=HostedSiteResponse) def accept_proposal( data: AcceptProposalRequest, site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Accept proposal: create/link merchant, create subscription.""" site = hosted_site_service.accept_proposal(db, site_id, merchant_id=data.merchant_id) db.commit() return _to_response(site) @router.post("/{site_id}/go-live", response_model=HostedSiteResponse) def go_live( data: GoLiveRequest, site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Go live with a domain.""" site = hosted_site_service.go_live(db, site_id, domain=data.domain) db.commit() return _to_response(site) @router.post("/{site_id}/suspend", response_model=HostedSiteResponse) def suspend_site( site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Suspend a site.""" site = hosted_site_service.suspend(db, site_id) db.commit() return _to_response(site) @router.post("/{site_id}/cancel", response_model=HostedSiteResponse) def cancel_site( site_id: int = Path(...), db: Session = Depends(get_db), current_admin: UserContext = Depends(get_current_admin_api), ): """Cancel a site.""" site = hosted_site_service.cancel(db, site_id) db.commit() return _to_response(site)