tschunkorder/backend/main.py

162 lines
No EOL
5.2 KiB
Python

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)