commit 4e359cf3ef652b158ebc6fdebf53806ad37ee7f9 Author: Jan Felix Wiebe Date: Wed Jul 9 22:03:08 2025 +0200 added restful api diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..88487a8 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,38 @@ +# Virtual Environment +venv/ + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..d636f80 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,220 @@ +# Tschunk Order API + +Eine RESTful API für Tschunk-Bestellungen, entwickelt mit FastAPI. + +## Features + +- Bestellungen mit mehreren Getränken erstellen +- Alle Bestellungen abrufen +- Spezifische Bestellungen löschen +- Unterstützung für verschiedene Tschunk-Varianten +- Wahl zwischen Flora Mate und Club Mate +- Mengenangabe für jedes Getränk (Standard: 1) +- Anmerkungen und Sonderwünsche für jedes Getränk + +## Verfügbare Getränke + +- **Tschunk** +- **Tschunk Hannover-Mische** +- **Tschunk alkoholfreier Rum** +- **Virgin Tschunk** + +## Mate-Sorten + +- **Flora Mate** +- **Club Mate** + +## Installation + +### Voraussetzungen + +- Python 3.8+ installiert + +### Setup + +1. **Virtuelles Environment erstellen und aktivieren:** + +```bash +cd backend + +# Virtuelles Environment erstellen +python -m venv venv + +# Virtuelles Environment aktivieren +# Unter Linux/macOS: +source venv/bin/activate + +# Unter Windows: +# venv\Scripts\activate +``` + +2. **Abhängigkeiten installieren:** + +```bash +pip install -r requirements.txt +``` + +## Verwendung + +### Server starten + +```bash +# Stelle sicher, dass das virtuelle Environment aktiviert ist +source venv/bin/activate # Linux/macOS +# venv\Scripts\activate # Windows + +# Server starten +python main.py +``` + +Oder mit uvicorn: + +```bash +uvicorn main:app --reload +``` + +Der Server läuft dann auf `http://localhost:8000` + +### API-Dokumentation + +Die automatisch generierte API-Dokumentation ist verfügbar unter: +- Swagger UI: `http://localhost:8000/docs` +- ReDoc: `http://localhost:8000/redoc` + +## API-Endpunkte + +### 1. Bestellung erstellen +``` +POST /orders +``` + +Beispiel-Request: +```json +{ + "drinks": [ + { + "drink_type": "Tschunk", + "mate_type": "Club Mate", + "quantity": 2, + "notes": "Bitte extra kalt servieren" + }, + { + "drink_type": "Virgin Tschunk", + "mate_type": "Flora Mate", + "notes": "Ohne Eiswürfel" + }, + { + "drink_type": "Tschunk Hannover-Mische", + "mate_type": "Club Mate", + "quantity": 1 + } + ] +} +``` + +**Hinweise:** +- Das `quantity`-Feld ist optional. Falls nicht angegeben, wird automatisch 1 verwendet. +- Das `notes`-Feld ist optional und kann für Sonderwünsche oder Anmerkungen verwendet werden (max. 500 Zeichen). + +### 2. Alle Bestellungen abrufen +``` +GET /orders +``` + +### 3. Bestellung löschen +``` +DELETE /orders/{order_id} +``` + +### 4. Verfügbare Getränke anzeigen +``` +GET /drinks +``` + +## Beispiel-Verwendung mit curl + +### Bestellung erstellen: +```bash +curl -X POST "http://localhost:8000/orders" \ + -H "Content-Type: application/json" \ + -d '{ + "drinks": [ + { + "drink_type": "Tschunk", + "mate_type": "Club Mate", + "quantity": 3, + "notes": "Bitte mit Limettenscheibe garnieren" + }, + { + "drink_type": "Virgin Tschunk", + "mate_type": "Flora Mate", + "notes": "Allergie gegen Zitrusfrüchte" + } + ] + }' +``` + +### Alle Bestellungen abrufen: +```bash +curl -X GET "http://localhost:8000/orders" +``` + +### Bestellung löschen: +```bash +curl -X DELETE "http://localhost:8000/orders/{order_id}" +``` + +## Testing + +Das Projekt enthält ein Test-Skript, das alle API-Endpunkte testet: + +```bash +# Stelle sicher, dass das virtuelle Environment aktiviert ist +source venv/bin/activate + +# Test-Skript ausführen +python test_api.py +``` + +## Projektstruktur + +``` +backend/ +├── main.py # FastAPI-Anwendung +├── models.py # Pydantic-Modelle +├── database.py # In-Memory-Datenbank +├── test_api.py # Test-Skript +├── requirements.txt # Python-Abhängigkeiten +├── .gitignore # Git-Ignore-Datei +├── venv/ # Virtuelles Environment (wird ignoriert) +└── README.md # Diese Datei +``` + +## Hinweise + +- **Virtuelles Environment**: Das Projekt verwendet ein virtuelles Environment, um Abhängigkeiten zu isolieren +- **In-Memory-Datenbank**: Die API verwendet eine In-Memory-Datenbank, d.h. alle Daten gehen beim Neustart verloren +- **Produktionsumgebung**: Für Produktionsumgebungen sollte eine persistente Datenbank verwendet werden +- **Validierung**: Die API validiert automatisch alle Eingaben mit Pydantic +- **Mengenangabe**: Jedes Getränk kann eine Menge haben (mindestens 1, Standard: 1) +- **Anmerkungen**: Jedes Getränk kann optionale Anmerkungen haben (max. 500 Zeichen) + +## Troubleshooting + +### Virtuelles Environment aktivieren +Falls du eine Fehlermeldung bezüglich fehlender Module erhältst, stelle sicher, dass das virtuelle Environment aktiviert ist: + +```bash +# Status prüfen +which python # Sollte auf venv/bin/python zeigen + +# Falls nicht aktiviert: +source venv/bin/activate +``` + +### Port bereits belegt +Falls Port 8000 bereits belegt ist, kannst du einen anderen Port verwenden: + +```bash +uvicorn main:app --reload --port 8001 +``` \ No newline at end of file diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..eb7c0e1 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,39 @@ +from typing import List, Optional +import uuid +from datetime import datetime +from models import Order, Drink + + +class OrderDatabase: + def __init__(self): + self.orders: dict[str, Order] = {} + + def create_order(self, drinks: List[Drink]) -> Order: + """Create a new order with the given drinks.""" + order_id = str(uuid.uuid4()) + order = Order( + id=order_id, + order_date=datetime.now(), + drinks=drinks + ) + self.orders[order_id] = order + return order + + def get_all_orders(self) -> List[Order]: + """Get all orders.""" + return list(self.orders.values()) + + def get_order_by_id(self, order_id: str) -> Optional[Order]: + """Get a specific order by ID.""" + return self.orders.get(order_id) + + def delete_order(self, order_id: str) -> bool: + """Delete an order by ID. Returns True if successful, False if not found.""" + if order_id in self.orders: + del self.orders[order_id] + return True + return False + + +# Global database instance +db = OrderDatabase() \ No newline at end of file diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..3fd9615 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,62 @@ +from fastapi import FastAPI, HTTPException +from typing import List +from models import CreateOrderRequest, Order, DrinkType, MateType +from database import db + +app = FastAPI( + title="Tschunk Order API", + description="Eine RESTful API für Tschunk-Bestellungen", + version="1.0.0" +) + +@app.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 + """ + if not order_request.drinks: + raise HTTPException(status_code=400, detail="Mindestens ein Getränk muss bestellt werden") + + order = db.create_order(order_request.drinks) + return order + + +@app.get("/orders", response_model=List[Order]) +async def get_all_orders(): + """ + Gibt alle Bestellungen zurück. + """ + orders = db.get_all_orders() + return orders + + +@app.delete("/orders/{order_id}") +async def delete_order(order_id: str): + """ + Löscht eine spezifische Bestellung. + + - **order_id**: ID der zu löschenden Bestellung + """ + success = 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"} + + +@app.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] + } + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) \ No newline at end of file diff --git a/backend/models.py b/backend/models.py new file mode 100644 index 0000000..ae4fb77 --- /dev/null +++ b/backend/models.py @@ -0,0 +1,33 @@ +from pydantic import BaseModel, Field +from typing import List, Optional +from datetime import datetime +from enum import Enum + + +class MateType(str, Enum): + FLORA_MATE = "Flora Mate" + CLUB_MATE = "Club Mate" + + +class DrinkType(str, Enum): + TSCHUNK = "Tschunk" + TSCHUNK_HANNOVER_MISCHE = "Tschunk Hannover-Mische" + TSCHUNK_ALKOHOLFREIER_RUM = "Tschunk alkoholfreier Rum" + VIRGIN_TSCHUNK = "Virgin Tschunk" + + +class Drink(BaseModel): + drink_type: DrinkType + mate_type: MateType + quantity: int = Field(default=1, ge=1, description="Anzahl der bestellten Getränke") + notes: Optional[str] = Field(default=None, max_length=500, description="Sonderwünsche oder Anmerkungen zum Getränk") + + +class Order(BaseModel): + id: str + order_date: datetime + drinks: List[Drink] + + +class CreateOrderRequest(BaseModel): + drinks: List[Drink] \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..9803e74 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +fastapi>=0.115.0 +uvicorn[standard]>=0.32.0 +pydantic>=2.10.0 +python-multipart>=0.0.20 +requests>=2.32.0 \ No newline at end of file diff --git a/backend/test_api.py b/backend/test_api.py new file mode 100644 index 0000000..7a9bd7c --- /dev/null +++ b/backend/test_api.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Test-Skript für die Tschunk Order API +""" + +import requests +import json +from datetime import datetime + +BASE_URL = "http://localhost:8000" + +def test_api(): + """Testet alle API-Endpunkte.""" + + print("🚀 Starte API-Tests...\n") + + # Test 1: Root endpoint + print("1. Teste Root-Endpoint...") + try: + response = requests.get(f"{BASE_URL}/") + print(f"✅ Root endpoint: {response.status_code}") + print(f" Verfügbare Getränke: {response.json()['available_drinks']}") + print(f" Verfügbare Mate-Sorten: {response.json()['available_mate_types']}\n") + except Exception as e: + print(f"❌ Fehler beim Root endpoint: {e}\n") + + # Test 2: Getränke anzeigen + print("2. Teste Getränke-Endpoint...") + try: + response = requests.get(f"{BASE_URL}/drinks") + print(f"✅ Getränke endpoint: {response.status_code}") + print(f" Getränke: {response.json()}\n") + except Exception as e: + print(f"❌ Fehler beim Getränke endpoint: {e}\n") + + # Test 3: Bestellung erstellen (mit quantity und notes) + print("3. Teste Bestellung erstellen (mit Mengenangabe und Sonderwünschen)...") + test_order = { + "drinks": [ + { + "drink_type": "Tschunk", + "mate_type": "Club Mate", + "quantity": 2, + "notes": "Bitte extra kalt servieren" + }, + { + "drink_type": "Virgin Tschunk", + "mate_type": "Flora Mate" + # quantity und notes werden nicht angegeben, sollten automatisch 1 bzw. None sein + }, + { + "drink_type": "Tschunk Hannover-Mische", + "mate_type": "Club Mate", + "quantity": 3, + "notes": "Ohne Eiswürfel, bitte mit Limettenscheibe" + } + ] + } + + try: + response = requests.post( + f"{BASE_URL}/orders", + json=test_order, + headers={"Content-Type": "application/json"} + ) + print(f"✅ Bestellung erstellt: {response.status_code}") + order_data = response.json() + print(f" Bestell-ID: {order_data['id']}") + print(f" Bestelldatum: {order_data['order_date']}") + print(f" Anzahl Getränke: {len(order_data['drinks'])}") + + # Zeige Details der Getränke + for i, drink in enumerate(order_data['drinks']): + notes_info = f" (Anmerkung: {drink['notes']})" if drink.get('notes') else "" + print(f" - Getränk {i+1}: {drink['drink_type']} mit {drink['mate_type']} (Menge: {drink['quantity']}){notes_info}") + + # Speichere Order-ID für späteren Test + order_id = order_data['id'] + print() + except Exception as e: + print(f"❌ Fehler beim Erstellen der Bestellung: {e}") + return + + # Test 4: Alle Bestellungen abrufen + print("4. Teste Alle Bestellungen abrufen...") + try: + response = requests.get(f"{BASE_URL}/orders") + print(f"✅ Alle Bestellungen: {response.status_code}") + orders = response.json() + print(f" Anzahl Bestellungen: {len(orders)}") + for order in orders: + total_quantity = sum(drink['quantity'] for drink in order['drinks']) + drinks_with_notes = sum(1 for drink in order['drinks'] if drink.get('notes')) + print(f" - Bestellung {order['id']}: {len(order['drinks'])} verschiedene Getränke (Gesamtmenge: {total_quantity}, {drinks_with_notes} mit Anmerkungen)") + print() + except Exception as e: + print(f"❌ Fehler beim Abrufen der Bestellungen: {e}\n") + + # Test 5: Bestellung löschen + print("5. Teste Bestellung löschen...") + try: + response = requests.delete(f"{BASE_URL}/orders/{order_id}") + print(f"✅ Bestellung gelöscht: {response.status_code}") + print(f" Nachricht: {response.json()['message']}") + print() + except Exception as e: + print(f"❌ Fehler beim Löschen der Bestellung: {e}\n") + + # Test 6: Bestätige Löschung + print("6. Bestätige Löschung...") + try: + response = requests.get(f"{BASE_URL}/orders") + orders = response.json() + print(f"✅ Verbleibende Bestellungen: {len(orders)}") + print() + except Exception as e: + print(f"❌ Fehler beim Abrufen der Bestellungen: {e}\n") + + print("🎉 API-Tests abgeschlossen!") + +if __name__ == "__main__": + print("=" * 50) + print("Tschunk Order API - Test Suite") + print("=" * 50) + print() + print("⚠️ Stelle sicher, dass der Server auf http://localhost:8000 läuft!") + print() + + # Frage nach Bestätigung + input("Drücke Enter um fortzufahren...") + print() + + test_api() \ No newline at end of file