""" Waitlist / email-capture routes for the marketing landing page. Public endpoint: POST /api/v1/waitlist (no auth) — deduplicated by email, persisted in data/waitlist.json (same JSON-file pattern as provider settings). """ import json import logging import threading from datetime import datetime, timezone from typing import List, Optional from fastapi import APIRouter from fastapi.responses import JSONResponse from pydantic import BaseModel, EmailStr from config import config logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/waitlist", tags=["Waitlist"]) WAITLIST_FILE = config.BASE_DIR / "data" / "waitlist.json" _write_lock = threading.Lock() class WaitlistEntry(BaseModel): email: EmailStr interest: Optional[str] = None def _load_waitlist() -> List[dict]: if not WAITLIST_FILE.exists(): return [] try: with open(WAITLIST_FILE, encoding="utf-8") as f: data = json.load(f) return data if isinstance(data, list) else [] except Exception as e: logger.warning(f"Failed to load waitlist: {e}") return [] def _save_waitlist(entries: List[dict]) -> None: WAITLIST_FILE.parent.mkdir(parents=True, exist_ok=True) with open(WAITLIST_FILE, "w", encoding="utf-8") as f: json.dump(entries, f, indent=2) @router.post("", status_code=201, summary="Join the waitlist") async def join_waitlist(entry: WaitlistEntry): email = entry.email.lower().strip() now = datetime.now(timezone.utc).isoformat() with _write_lock: entries = _load_waitlist() for existing in entries: if existing.get("email", "").lower() == email: existing["updated_at"] = now if entry.interest: existing["interest"] = entry.interest _save_waitlist(entries) logger.info(f"Waitlist rejoin: {email}") return JSONResponse( status_code=200, content={ "data": {"email": email, "status": "already_joined"}, "message": "You are already on the list.", }, ) entries.append( { "email": email, "interest": entry.interest, "joined_at": now, "updated_at": now, } ) _save_waitlist(entries) logger.info(f"Waitlist join: {email} (total={len(entries)})") return JSONResponse( status_code=201, content={ "data": {"email": email, "status": "joined"}, "message": "Welcome aboard!", }, ) @router.get("/count", summary="Waitlist count") async def waitlist_count(): return JSONResponse( status_code=200, content={"data": {"count": len(_load_waitlist())}}, )