"""JH Photomaton -- Interface de gestion (Les Sapins Du Web).""" import asyncio import logging from contextlib import asynccontextmanager from pathlib import Path from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.staticfiles import StaticFiles from starlette.middleware.sessions import SessionMiddleware from backend.services.config_service import ConfigService from backend.services.event_service import EventService from backend.services.led_service import LEDService from backend.services.button_service import ButtonService from backend.services.photobooth_service import PhotoboothService from backend.services.printer_service import PrinterService from backend.services.system_service import SystemService from backend.utils.ws_manager import WSManager from backend.api import ( gallery, admin_api, admin_gallery_api, leds_api, system_api, print_api, actions_api, webhooks, event_api, ) logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) logger = logging.getLogger(__name__) BASE_DIR = Path(__file__).parent @asynccontextmanager async def lifespan(app: FastAPI): config_svc = ConfigService(BASE_DIR / "config" / "settings.yaml") config = config_svc.load() ws_manager = WSManager() photobooth_svc = PhotoboothService(config) system_svc = SystemService() printer_svc = PrinterService(config) event_svc = EventService() await printer_svc.init_db(BASE_DIR / "data" / "print_queue.db") await event_svc.init_db(BASE_DIR / "data" / "events.db") from datetime import datetime as _dt ev = config.event if not ev.started_at: ev.started_at = _dt.now().timestamp() await event_svc.ensure_event(ev.slug, ev.name, ev.started_at) led_svc = LEDService(config) led_svc.start() loop = asyncio.get_event_loop() led_svc.set_loop(loop) led_svc.set_on_change(ws_manager.broadcast) button_svc = ButtonService(config, led_svc, photobooth_svc, ws_manager, loop) button_svc.start() app.state.config = config app.state.config_service = config_svc app.state.ws_manager = ws_manager app.state.led_service = led_svc app.state.button_service = button_svc app.state.photobooth_service = photobooth_svc app.state.printer_service = printer_svc app.state.system_service = system_svc app.state.event_service = event_svc stats_task = asyncio.create_task(_system_stats_loop(system_svc, ws_manager)) logger.info("JH Photomaton demarre -- port %d", config.app.port) led_svc.play("idle") yield stats_task.cancel() button_svc.stop() led_svc.stop() await photobooth_svc.close() await printer_svc.close() await event_svc.close() logger.info("JH Photomaton arrete") async def _system_stats_loop(sys_svc: SystemService, ws: WSManager): while True: try: stats = sys_svc.get_stats() await ws.broadcast({"type": "system_stats", "data": stats}) except Exception as e: logger.debug("Stats loop: %s", e) await asyncio.sleep(5) app = FastAPI( title="JH Photomaton", description="Interface de gestion du photomaton -- Les Sapins Du Web", version="1.0.0", lifespan=lifespan, ) app.add_middleware( SessionMiddleware, secret_key="jh-photomaton-session-2026", max_age=86400, ) app.mount("/static", StaticFiles(directory=BASE_DIR / "frontend" / "static"), name="static") app.include_router(gallery.router) app.include_router(admin_api.router) app.include_router(admin_gallery_api.router) app.include_router(leds_api.router, prefix="/api") app.include_router(system_api.router, prefix="/api") app.include_router(print_api.router, prefix="/api") app.include_router(actions_api.router, prefix="/api") app.include_router(webhooks.router, prefix="/api") app.include_router(event_api.router, prefix="/api") @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket): ws_manager: WSManager = websocket.app.state.ws_manager await ws_manager.connect(websocket) try: led = websocket.app.state.led_service sys_svc = websocket.app.state.system_service await ws_manager.send(websocket, {"type": "connected", "led_effect": led.current_effect}) await ws_manager.send(websocket, {"type": "system_stats", "data": sys_svc.get_stats()}) except Exception: pass try: while True: data = await websocket.receive_text() import json try: msg = json.loads(data) if msg.get("type") == "ping": await ws_manager.send(websocket, {"type": "pong"}) except Exception: pass except WebSocketDisconnect: ws_manager.disconnect(websocket) if __name__ == "__main__": import uvicorn cfg = ConfigService(BASE_DIR / "config" / "settings.yaml").load() uvicorn.run("main:app", host=cfg.app.host, port=cfg.app.port, reload=cfg.app.debug, log_level="info")