baton/backend/main.py
2026-03-20 20:44:00 +02:00

114 lines
3.1 KiB
Python

from __future__ import annotations
import asyncio
import logging
from contextlib import asynccontextmanager
from typing import Any
from fastapi import Depends, FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from backend import config, db, telegram
from backend.middleware import verify_webhook_secret
from backend.models import (
RegisterRequest,
RegisterResponse,
SignalRequest,
SignalResponse,
)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
aggregator = telegram.SignalAggregator(interval=10)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
await db.init_db()
logger.info("Database initialized")
await telegram.set_webhook(url=config.WEBHOOK_URL, secret=config.WEBHOOK_SECRET)
logger.info("Webhook registered")
task = asyncio.create_task(aggregator.run())
logger.info("Aggregator started")
yield
# Shutdown
aggregator.stop()
await aggregator.flush()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
logger.info("Aggregator stopped, final flush done")
app = FastAPI(lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=[config.FRONTEND_ORIGIN],
allow_methods=["POST"],
allow_headers=["Content-Type"],
)
@app.post("/api/register", response_model=RegisterResponse)
async def register(body: RegisterRequest) -> RegisterResponse:
result = await db.register_user(uuid=body.uuid, name=body.name)
return RegisterResponse(user_id=result["user_id"], uuid=result["uuid"])
@app.post("/api/signal", response_model=SignalResponse)
async def signal(body: SignalRequest) -> SignalResponse:
geo = body.geo
lat = geo.lat if geo else None
lon = geo.lon if geo else None
accuracy = geo.accuracy if geo else None
signal_id = await db.save_signal(
user_uuid=body.user_id,
timestamp=body.timestamp,
lat=lat,
lon=lon,
accuracy=accuracy,
)
user_name = await db.get_user_name(body.user_id)
await aggregator.add_signal(
user_uuid=body.user_id,
user_name=user_name,
timestamp=body.timestamp,
geo={"lat": lat, "lon": lon, "accuracy": accuracy} if geo else None,
signal_id=signal_id,
)
return SignalResponse(status="ok", signal_id=signal_id)
@app.post("/api/webhook/telegram")
async def webhook_telegram(
request: Request,
_: None = Depends(verify_webhook_secret),
) -> dict[str, Any]:
update = await request.json()
message = update.get("message", {})
text = message.get("text", "")
if text.startswith("/start"):
tg_user = message.get("from", {})
tg_user_id = str(tg_user.get("id", ""))
first_name = tg_user.get("first_name", "")
last_name = tg_user.get("last_name", "")
name = (first_name + " " + last_name).strip() or tg_user_id
if tg_user_id:
await db.register_user(uuid=tg_user_id, name=name)
logger.info("Telegram /start: registered user %s", tg_user_id)
return {"ok": True}