from __future__ import annotations import asyncio import logging from contextlib import asynccontextmanager from datetime import datetime, timezone 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) # v2.0 feature — отключено в v1 (ADR-004) @asynccontextmanager async def lifespan(app: FastAPI): # Startup await db.init_db() logger.info("Database initialized") if config.WEBHOOK_ENABLED: await telegram.set_webhook(url=config.WEBHOOK_URL, secret=config.WEBHOOK_SECRET) logger.info("Webhook registered") # v2.0 feature — агрегатор отключён в v1 (ADR-004) # 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) ts = datetime.fromtimestamp(body.timestamp / 1000, tz=timezone.utc) name = user_name or body.user_id[:8] geo_info = ( f"📍 {lat}, {lon} (±{accuracy}м)" if geo else "Без геолокации" ) text = ( f"🚨 Сигнал от {name}\n" f"⏰ {ts.strftime('%H:%M:%S')} UTC\n" f"{geo_info}" ) await telegram.send_message(text) 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}