from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect, APIRouter from fastapi.responses import Response from typing import List from models import CreateOrderRequest, Order, DrinkType, MateType from database import db from websocket_manager import websocket_manager from prometheus_metrics import drink_metrics, CONTENT_TYPE_LATEST import json app = FastAPI( title="Tschunk Order API", description="Eine RESTful API für Tschunk-Bestellungen mit WebSocket-Unterstützung und Prometheus-Metriken", version="1.0.0" ) # API Router mit /api Prefix api_router = APIRouter(prefix="/api") # Datenbank-Referenz im WebSocketManager setzen websocket_manager.set_database(db) @app.websocket("/api/ws") async def websocket_endpoint(websocket: WebSocket): """ WebSocket-Endpunkt für Echtzeit-Updates der Bestellungen. Clients erhalten automatisch Updates bei: - Neuen Bestellungen - Gelöschten Bestellungen Nachrichten-Formate: - order_created: {"type": "order_created", "order": {...}} - order_deleted: {"type": "order_deleted", "order_id": "..."} - all_orders: {"type": "all_orders", "orders": [...]} - create_order: {"type": "create_order", "drinks": [...]} - get_all_orders: {"type": "get_all_orders"} - delete_order: {"type": "delete_order", "order_id": "..."} """ await websocket_manager.connect(websocket) try: # Sende alle aktuellen Bestellungen beim Verbinden all_orders = [order.model_dump() for order in db.get_all_orders()] await websocket_manager.broadcast_all_orders(all_orders) # Halte die Verbindung aufrecht und warte auf Nachrichten while True: # Warte auf Nachrichten vom Client data = await websocket.receive_text() try: # Versuche JSON zu parsen message = json.loads(data) message_type = message.get("type") if message_type == "ping": await websocket.send_text("pong") elif message_type == "create_order": await websocket_manager.handle_create_order(websocket, message) elif message_type == "get_all_orders": await websocket_manager.handle_get_all_orders(websocket) elif message_type == "delete_order": await websocket_manager.handle_delete_order(websocket, message) else: print(f"Unbekannte Nachrichtenart: {message_type}") except json.JSONDecodeError: # Fallback für einfache Textnachrichten (z.B. "ping") if data == "ping": await websocket.send_text("pong") else: print(f"Ungültige JSON-Nachricht: {data}") except WebSocketDisconnect: websocket_manager.disconnect(websocket) except Exception as e: print(f"WebSocket-Fehler: {e}") websocket_manager.disconnect(websocket) @api_router.post("/orders", response_model=Order, status_code=201) async def create_order(order_request: CreateOrderRequest): """ Erstellt eine neue Bestellung. - **drinks**: Liste der Getränke mit Typ und Mate-Sorte Alle verbundenen WebSocket-Clients erhalten automatisch ein Update. """ if not order_request.drinks: raise HTTPException(status_code=400, detail="Mindestens ein Getränk muss bestellt werden") order = await db.create_order(order_request.drinks) return order @api_router.get("/orders", response_model=List[Order]) async def get_all_orders(): """ Gibt alle Bestellungen zurück. """ orders = db.get_all_orders() return orders @api_router.delete("/orders/{order_id}") async def delete_order(order_id: str): """ Löscht eine spezifische Bestellung. - **order_id**: ID der zu löschenden Bestellung Alle verbundenen WebSocket-Clients erhalten automatisch ein Update. """ success = await db.delete_order(order_id) if not success: raise HTTPException(status_code=404, detail="Bestellung nicht gefunden") return {"message": f"Bestellung {order_id} wurde erfolgreich gelöscht"} @api_router.get("/drinks") async def get_available_drinks(): """ Gibt alle verfügbaren Getränketypen zurück. """ return { "drink_types": [drink.value for drink in DrinkType], "mate_types": [mate.value for mate in MateType] } @api_router.get("/stats/drinks") async def get_drink_statistics(): """ Gibt die Statistiken der verkauften Getränke zurück. """ return drink_metrics.get_stats() @app.get("/metrics") async def get_prometheus_metrics(): """ Prometheus-Metriken-Endpunkt. Gibt die Metriken im Prometheus-Format zurück: - tschunk_drinks_sold_total: Anzahl verkaufter Getränke nach Typ und Mate-Sorte """ return Response( content=drink_metrics.get_metrics(), media_type=CONTENT_TYPE_LATEST ) # API Router zur App hinzufügen app.include_router(api_router) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)