import asyncio import json import logging from typing import Any, Optional from dotenv import load_dotenv load_dotenv() from fastapi import BackgroundTasks, FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse from manager import Manager import esi.api as esi logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI() manager = Manager() async def start_manager() -> None: """Periodically updates the manager in a separate thread to prevent blocking.""" while True: try: logger.info("Starting manager update") await manager.update() logger.info("Manager update completed") except Exception as error: logger.exception("An error occurred during manager update: %s", error) await asyncio.sleep(2) @app.on_event("startup") async def startup_event() -> None: """Initializes the manager update task on startup.""" app.state.manager_task = asyncio.create_task(start_manager()) # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], # Allows requests from specified origin allow_credentials=True, allow_methods=["*"], # Allows all HTTP methods allow_headers=["*"], # Allows all HTTP headers ) @app.get("/") async def read_root() -> dict: """Root endpoint that returns a welcome message.""" return {"Hello": "World"} @app.get("/items/{item_id}") async def read_item(item_id: int, q: Optional[str] = None) -> dict: """Retrieves an item by ID, optionally filtering by query parameter `q`.""" return {"item_id": item_id, "q": q} @app.get("/route") async def read_route() -> Any: """Returns the current route from the manager.""" return manager.route @app.get("/route_info") async def read_route_info() -> Any: """Returns route information from the manager.""" return manager.route_info @app.post("/create_route") async def create_route(background_tasks: BackgroundTasks) -> dict: """Initiates route creation in the background.""" asyncio.create_task(manager.create_route()) return {"status": "Route creation initiated"} @app.get("/data_stream") async def data_stream() -> StreamingResponse: """Streams data updates to the client.""" async def event_stream() -> Any: while True: data = { "route": manager.route, "route_info": manager.route_info, "log": manager.log, } yield f"data: {json.dumps(data)}\n\n" await asyncio.sleep(1) return StreamingResponse(event_stream(), media_type="text/event-stream") @app.get("/logs") async def get_logs() -> Any: """Retrieves logs from the manager.""" return manager.log @app.post("/open_market_window/{type_id}") async def open_market_window(type_id: int) -> dict: """Opens a market window for a given `type_id` using the ESI API.""" await esi.open_market_window(type_id) logger.info("Opened market window for type_id %s", type_id) return {"status": "opened"} @app.get("/settings") async def get_settings() -> dict: """Returns the current ship settings.""" return manager.ship @app.post("/settings") async def update_settings(settings: dict) -> dict: """Updates the ship settings.""" for key, value in settings.items(): if key in manager.ship: manager.ship[key] = value # Re-initialize the graph with new settings from graph import NavigationGraph manager.graph = NavigationGraph(manager.ship) return {"status": "success", "settings": manager.ship} @app.post("/set_waypoints") async def set_waypoints_endpoint() -> dict: """Uses the ESI API to push the calculated route as in-game waypoints""" return await manager.set_waypoints()