"""Public plans endpoint — no auth required. GET /api/v1/plans/public Returns the public-safe view of `plan_billing` joined with `plan_limits.max_users` (exposed as `max_seats`), filtered to `is_public=True AND is_archived=False`, ordered by sort_order ASC, plan ASC. Distinct from `/admin/plan-limits` (admin-only, returns ALL plans including archived/internal). This endpoint exists to power the marketing /pricing page without exposing the rest of the admin-only billing surface. """ from __future__ import annotations from typing import Annotated from fastapi import APIRouter, Depends from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.admin_database import get_admin_db from app.models.plan_billing import PlanBilling from app.models.plan_limits import PlanLimits from app.schemas.billing import PublicPlanResponse router = APIRouter(prefix="/plans", tags=["plans"]) @router.get("/public", response_model=list[PublicPlanResponse]) async def list_public_plans( db: Annotated[AsyncSession, Depends(get_admin_db)], ) -> list[PublicPlanResponse]: """List public, non-archived plans for the marketing /pricing page. Public — no auth. Uses `get_admin_db` because this is a cross-tenant read of the global plan catalog (same pattern as `/config/public`). """ stmt = ( select(PlanBilling, PlanLimits.max_users) .outerjoin(PlanLimits, PlanBilling.plan == PlanLimits.plan) .where(PlanBilling.is_public.is_(True)) .where(PlanBilling.is_archived.is_(False)) .order_by(PlanBilling.sort_order.asc(), PlanBilling.plan.asc()) ) rows = (await db.execute(stmt)).all() return [ PublicPlanResponse( plan=billing.plan, display_name=billing.display_name, description=billing.description, monthly_price_cents=billing.monthly_price_cents, annual_price_cents=billing.annual_price_cents, max_seats=max_users, sort_order=billing.sort_order, is_public=billing.is_public, ) for billing, max_users in rows ]