Desi-Clans/backend/app/routers/public.py
2026-10-08 02:52:58 +05:30

151 lines
5.5 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.responses import HTMLResponse, PlainTextResponse, RedirectResponse
from sqlalchemy.orm import Session, joinedload
from app.config import settings
from app.database import get_db
from app.models import EventEdition, EventType, NewsletterSubscriber, OutboundClick, PlatformLink
from app.schemas import EventEditionPublic, PlatformLinkPublic, SiteConfigPublic, SubscribeRequest
from app.services.alerts import maybe_notify_click_spike
from app.services.calendar import build_ics
from app.services.editions import edition_to_public
from app.services.outbound import validate_outbound_url
from app.services.share_preview import render_share_html
router = APIRouter(prefix="/api/public", tags=["public"])
TAGLINE = "Bengaluru's home for Holi, Garba, Dandiya & bhajan nights — across the city's best venues."
@router.get("/health")
def health():
return {"status": "ok", "city": "Bengaluru"}
@router.get("/config", response_model=SiteConfigPublic)
def site_config(db: Session = Depends(get_db)):
links = (
db.query(PlatformLink)
.filter(PlatformLink.is_active.is_(True))
.order_by(PlatformLink.sort_order, PlatformLink.id)
.all()
)
editions = (
db.query(EventEdition)
.options(joinedload(EventEdition.media))
.filter(EventEdition.is_published.is_(True))
.order_by(EventEdition.is_featured.desc(), EventEdition.sort_order, EventEdition.event_date.desc().nullslast())
.all()
)
featured = next((e for e in editions if e.is_featured), editions[0] if editions else None)
recent = [edition_to_public(e) for e in editions[:6]]
return SiteConfigPublic(
city="Bengaluru",
tagline=TAGLINE,
platform_links=[PlatformLinkPublic.model_validate(l) for l in links],
featured_edition=edition_to_public(featured) if featured else None,
recent_editions=recent,
)
@router.get("/editions", response_model=list[EventEditionPublic])
def list_editions(
db: Session = Depends(get_db),
event_type: EventType | None = Query(default=None),
):
q = (
db.query(EventEdition)
.options(joinedload(EventEdition.media))
.filter(EventEdition.is_published.is_(True))
)
if event_type is not None:
q = q.filter(EventEdition.event_type == event_type)
editions = q.order_by(EventEdition.event_date.desc().nullslast(), EventEdition.id.desc()).all()
return [edition_to_public(e) for e in editions]
@router.get("/editions/{slug}", response_model=EventEditionPublic)
def get_edition(slug: str, db: Session = Depends(get_db)):
edition = (
db.query(EventEdition)
.options(joinedload(EventEdition.media))
.filter(EventEdition.slug == slug, EventEdition.is_published.is_(True))
.first()
)
if not edition:
raise HTTPException(status_code=404, detail="Edition not found")
return edition_to_public(edition)
@router.get("/go")
def outbound_redirect(
request: Request,
url: str = Query(..., min_length=8, max_length=512),
kind: str = Query(default="other", max_length=64),
edition_id: int | None = Query(default=None),
db: Session = Depends(get_db),
):
try:
destination = validate_outbound_url(url)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid destination URL")
db.add(
OutboundClick(
link_kind=kind[:64],
destination_url=destination,
edition_id=edition_id,
referrer=request.headers.get("referer"),
user_agent=(request.headers.get("user-agent") or "")[:512],
)
)
db.commit()
maybe_notify_click_spike(db)
return RedirectResponse(destination, status_code=302)
@router.post("/subscribe")
def subscribe(body: SubscribeRequest, db: Session = Depends(get_db)):
existing = db.query(NewsletterSubscriber).filter(NewsletterSubscriber.email == body.email).first()
if existing:
return {"ok": True, "message": "Already on the list — we'll see you in Bengaluru."}
db.add(NewsletterSubscriber(email=body.email, neighborhood=body.neighborhood))
db.commit()
return {"ok": True, "message": "You're in! We'll email you when the next edition drops."}
@router.get("/editions/{slug}/calendar.ics", response_class=PlainTextResponse)
def edition_calendar(slug: str, db: Session = Depends(get_db)):
edition = (
db.query(EventEdition)
.filter(EventEdition.slug == slug, EventEdition.is_published.is_(True))
.first()
)
if not edition:
raise HTTPException(status_code=404, detail="Edition not found")
page_url = f"{settings.public_site_url.rstrip('/')}/events/{slug}"
return PlainTextResponse(
build_ics(edition, page_url),
media_type="text/calendar",
headers={"Content-Disposition": f'attachment; filename="{slug}.ics"'},
)
def _share_html_for_slug(slug: str, db: Session) -> str:
edition = (
db.query(EventEdition)
.options(joinedload(EventEdition.media))
.filter(EventEdition.slug == slug, EventEdition.is_published.is_(True))
.first()
)
if not edition:
raise HTTPException(status_code=404, detail="Edition not found")
return render_share_html(edition_to_public(edition), slug)
@router.get("/share/events/{slug}", response_class=HTMLResponse)
def share_event_preview_api(slug: str, db: Session = Depends(get_db)):
return HTMLResponse(_share_html_for_slug(slug, db))