Initial commit: waitlist microservice
This commit is contained in:
48
app/routes.py
Normal file
48
app/routes.py
Normal file
@@ -0,0 +1,48 @@
|
||||
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()
|
||||
Reference in New Issue
Block a user