indie-status-page/app/api/subscribers.py
IndieStatusBot 902133edd3 feat: indie status page MVP -- FastAPI + SQLite
- 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
2026-04-25 05:00:00 +00:00

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}