import logging from fastapi import APIRouter, Depends, Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.db import get_db from app.rate_limit import _get_client_ip from app.schemas import WaitlistRequest, WaitlistResponse from app.models import WaitlistEntry logger = logging.getLogger(__name__) router = APIRouter() @router.post("/waitlist", response_model=WaitlistResponse) async def join_waitlist( body: WaitlistRequest, request: Request, db: AsyncSession = Depends(get_db), ) -> WaitlistResponse: """ Add an email to the waitlist. - Honeypot: if `website` field is non-empty, silently succeed (bot trap). - Duplicate emails: idempotent — returns success without error. - Stores the Cloudflare-resolved client IP for analytics (not exposed). """ # Honeypot — bots fill hidden fields; silently "succeed" if body.website: return WaitlistResponse() email = body.email.lower().strip() ip = _get_client_ip(request) # Check for existing entry — idempotent existing = await db.execute( select(WaitlistEntry.id).where(WaitlistEntry.email == email) ) if existing.scalar_one_or_none() is not None: return WaitlistResponse() entry = WaitlistEntry(email=email, ip_address=ip, source="website") db.add(entry) await db.commit() logger.info("New waitlist signup: %s", email[:3] + "***") return WaitlistResponse()