mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
import logging
|
|
from contextlib import asynccontextmanager
|
|
from pathlib import Path
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from app.config import setup_logging
|
|
from app.database import db
|
|
from app.frontend_static import register_frontend_missing_fallback, register_frontend_static_routes
|
|
from app.radio import RadioDisconnectedError, radio_manager
|
|
from app.radio_sync import (
|
|
stop_message_polling,
|
|
stop_periodic_advert,
|
|
stop_periodic_sync,
|
|
)
|
|
from app.routers import (
|
|
channels,
|
|
contacts,
|
|
health,
|
|
messages,
|
|
packets,
|
|
radio,
|
|
read_state,
|
|
settings,
|
|
statistics,
|
|
ws,
|
|
)
|
|
|
|
setup_logging()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Manage database and radio connection lifecycle."""
|
|
await db.connect()
|
|
logger.info("Database connected")
|
|
|
|
try:
|
|
await radio_manager.connect()
|
|
logger.info("Connected to radio")
|
|
await radio_manager.post_connect_setup()
|
|
except Exception as e:
|
|
logger.warning("Failed to connect to radio on startup: %s", e)
|
|
|
|
# Always start connection monitor (even if initial connection failed)
|
|
await radio_manager.start_connection_monitor()
|
|
|
|
yield
|
|
|
|
logger.info("Shutting down")
|
|
await radio_manager.stop_connection_monitor()
|
|
await stop_message_polling()
|
|
await stop_periodic_advert()
|
|
await stop_periodic_sync()
|
|
if radio_manager.meshcore:
|
|
await radio_manager.meshcore.stop_auto_message_fetching()
|
|
await radio_manager.disconnect()
|
|
await db.disconnect()
|
|
|
|
|
|
def _get_version() -> str:
|
|
"""Read version from pyproject.toml so it stays in sync automatically."""
|
|
try:
|
|
pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
for line in pyproject.read_text().splitlines():
|
|
if line.startswith("version = "):
|
|
return line.split('"')[1]
|
|
except Exception:
|
|
pass
|
|
return "0.0.0"
|
|
|
|
|
|
app = FastAPI(
|
|
title="RemoteTerm for MeshCore API",
|
|
description="API for interacting with MeshCore mesh radio networks",
|
|
version=_get_version(),
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
|
|
@app.exception_handler(RadioDisconnectedError)
|
|
async def radio_disconnected_handler(request: Request, exc: RadioDisconnectedError):
|
|
"""Return 503 when a radio disconnect race occurs during an operation."""
|
|
return JSONResponse(status_code=503, content={"detail": "Radio not connected"})
|
|
|
|
|
|
# API routes - all prefixed with /api for production compatibility
|
|
app.include_router(health.router, prefix="/api")
|
|
app.include_router(radio.router, prefix="/api")
|
|
app.include_router(contacts.router, prefix="/api")
|
|
app.include_router(channels.router, prefix="/api")
|
|
app.include_router(messages.router, prefix="/api")
|
|
app.include_router(packets.router, prefix="/api")
|
|
app.include_router(read_state.router, prefix="/api")
|
|
app.include_router(settings.router, prefix="/api")
|
|
app.include_router(statistics.router, prefix="/api")
|
|
app.include_router(ws.router, prefix="/api")
|
|
|
|
# Serve frontend static files in production
|
|
FRONTEND_DIR = Path(__file__).parent.parent / "frontend" / "dist"
|
|
if not register_frontend_static_routes(app, FRONTEND_DIR):
|
|
register_frontend_missing_fallback(app)
|