162 lines
No EOL
5.2 KiB
Python
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) |