- 8 DB models (services, incidents, monitors, subscribers, etc.) - Full CRUD API for services, incidents, monitors - Public status page with live data - Incident detail page with timeline - API key authentication - Uptime monitoring scheduler - 13 tests passing - TECHNICAL_DESIGN.md with full spec
84 lines
No EOL
2.5 KiB
Python
84 lines
No EOL
2.5 KiB
Python
"""Subscribers API endpoints."""
|
|
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
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
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@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,
|
|
"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(
|
|
email: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
api_key: str = Depends(verify_api_key),
|
|
):
|
|
"""Add a new subscriber."""
|
|
import uuid
|
|
|
|
subscriber = Subscriber(
|
|
email=email,
|
|
confirm_token=str(uuid.uuid4()),
|
|
)
|
|
db.add(subscriber)
|
|
await db.flush()
|
|
await db.refresh(subscriber)
|
|
return {
|
|
"id": subscriber.id,
|
|
"email": subscriber.email,
|
|
"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} |