151 lines
5.5 KiB
Python
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))
|