"""Subscribers API endpoints with tier enforcement. When adding a subscriber to an organization, the org's subscriber limit is checked against the org's tier. """ from uuid import UUID from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel, Field from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_db, verify_api_key from app.models.models import Subscriber from app.models.saas_models import Organization from app.services.tier_enforcement import enforce_subscriber_limit router = APIRouter() class SubscriberCreate(BaseModel): email: str = Field(..., max_length=255) organization_id: str | None = None # Optional: for tier enforcement @router.get("/") async def list_subscribers(db: AsyncSession = Depends(get_db)): """List all subscribers.""" result = await db.execute(select(Subscriber)) subscribers = result.scalars().all() return [ { "id": s.id, "email": s.email, "organization_id": s.organization_id, "is_confirmed": s.is_confirmed, "created_at": s.created_at.isoformat() if s.created_at else None, } for s in subscribers ] @router.post("/", status_code=status.HTTP_201_CREATED) async def create_subscriber( data: SubscriberCreate, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Add a new subscriber. If organization_id is provided, tier enforcement is applied to ensure the org hasn't exceeded its subscriber limit. """ org = None if data.organization_id: result = await db.execute( select(Organization).where(Organization.id == data.organization_id) ) org = result.scalar_one_or_none() if org is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Organization '{data.organization_id}' not found", ) # Tier enforcement when org context is provided if org is not None: await enforce_subscriber_limit(db, org) import uuid subscriber = Subscriber( email=data.email, confirm_token=str(uuid.uuid4()), organization_id=data.organization_id, ) db.add(subscriber) await db.flush() await db.refresh(subscriber) return { "id": subscriber.id, "email": subscriber.email, "organization_id": subscriber.organization_id, "confirm_token": subscriber.confirm_token, } @router.delete("/{subscriber_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_subscriber( subscriber_id: UUID, db: AsyncSession = Depends(get_db), api_key: str = Depends(verify_api_key), ): """Remove a subscriber.""" result = await db.execute(select(Subscriber).where(Subscriber.id == str(subscriber_id))) subscriber = result.scalar_one_or_none() if not subscriber: raise HTTPException(status_code=404, detail="Subscriber not found") await db.delete(subscriber) @router.post("/{subscriber_id}/confirm") async def confirm_subscriber( subscriber_id: UUID, token: str, db: AsyncSession = Depends(get_db), ): """Confirm a subscriber's email address.""" result = await db.execute(select(Subscriber).where(Subscriber.id == str(subscriber_id))) subscriber = result.scalar_one_or_none() if not subscriber: raise HTTPException(status_code=404, detail="Subscriber not found") if subscriber.confirm_token != token: raise HTTPException(status_code=400, detail="Invalid confirmation token") subscriber.is_confirmed = True subscriber.confirm_token = None await db.flush() return {"message": "Subscriber confirmed", "email": subscriber.email}