demo showcase

This commit is contained in:
2026-08-10 16:47:13 +02:00
parent 7bfd7c0ff3
commit 946c2832ea
5 changed files with 195 additions and 22 deletions
+2
View File
@@ -1,3 +1,5 @@
.env
__pycache__/
*.pyc
.venv/
demo/demo.db
+30 -15
View File
@@ -2,21 +2,36 @@
# Licensed under the MIT License. See LICENSE file in project root for details.
import os
import mysql.connector
import sqlite3
DB_HOST = os.environ.get("DB_HOST", "mariadb.fritz.box")
DB_PORT = int(os.environ.get("DB_PORT", "3306"))
DB_NAME = os.environ.get("DB_NAME", "calendar_sync")
DB_USER = os.environ["DB_USER"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
DB_BACKEND = os.environ.get("DB_BACKEND", "mysql")
if DB_BACKEND == "sqlite":
DB_PATH = os.environ.get("DB_PATH", "demo.db")
def get_connection(autocommit: bool = True):
return mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
user=DB_USER,
password=DB_PASSWORD,
database=DB_NAME,
autocommit=autocommit,
)
def get_connection(autocommit: bool = True):
conn = sqlite3.connect(DB_PATH)
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA foreign_keys=ON")
conn.row_factory = sqlite3.Row
if autocommit:
conn.isolation_level = None
return conn
else:
import mysql.connector
DB_HOST = os.environ.get("DB_HOST", "mariadb.fritz.box")
DB_PORT = int(os.environ.get("DB_PORT", "3306"))
DB_NAME = os.environ.get("DB_NAME", "calendar_sync")
DB_USER = os.environ["DB_USER"]
DB_PASSWORD = os.environ["DB_PASSWORD"]
def get_connection(autocommit: bool = True):
return mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
user=DB_USER,
password=DB_PASSWORD,
database=DB_NAME,
autocommit=autocommit,
)
+13 -7
View File
@@ -12,10 +12,13 @@ from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from api.database import get_connection
from api.database import get_connection, DB_BACKEND
log = logging.getLogger("calendar-api")
PH = "?" if DB_BACKEND == "sqlite" else "%s"
NOW_SQL = "datetime('now')" if DB_BACKEND == "sqlite" else "NOW()"
app = FastAPI(title="Calendar Sync API")
templates = Jinja2Templates(directory=Path(__file__).parent / "templates")
@@ -35,6 +38,8 @@ def to_local_timezone(dt: datetime) -> datetime:
"""Konvertiere naive UTC datetime zu Benutzer-Zeitzone."""
if dt is None:
return None
if isinstance(dt, str):
dt = datetime.fromisoformat(dt)
return dt.replace(tzinfo=timezone.utc).astimezone(TIMEZONE)
@@ -84,19 +89,19 @@ def fetch_events(limit: int = 10, search: str | None = None, calendar_label: str
try:
cur = conn.cursor()
try:
conditions = ["deleted = 0", "start_at >= NOW()"]
conditions = [f"deleted = 0", f"start_at >= {NOW_SQL}"]
params: list = []
if search:
conditions.append("summary LIKE %s")
conditions.append(f"summary LIKE {PH}")
params.append(f"%{search}%")
if calendar_label:
conditions.append("calendar_label = %s")
conditions.append(f"calendar_label = {PH}")
params.append(calendar_label)
params.append(limit)
where = " WHERE " + " AND ".join(conditions)
cur.execute(
f"SELECT {SELECT_COLUMNS} FROM calendar_events "
f"{where} ORDER BY start_at ASC LIMIT %s",
f"{where} ORDER BY start_at ASC LIMIT {PH}",
tuple(params),
)
return cur.fetchall()
@@ -141,7 +146,7 @@ def get_event(event_id: int):
try:
cur.execute(
f"SELECT {SELECT_COLUMNS} FROM calendar_events "
"WHERE id = %s AND deleted = 0",
f"WHERE id = {PH} AND deleted = 0",
(event_id,),
)
row = cur.fetchone()
@@ -186,6 +191,7 @@ def index(
})
return templates.TemplateResponse(
request,
"index.html",
{"request": request, "events": events, "search": search or ""},
{"events": events, "search": search or ""},
)
Executable
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
VENV_DIR="$SCRIPT_DIR/.venv"
DB_PATH="$SCRIPT_DIR/demo/demo.db"
echo "=== Calendar Sync Demo ==="
# Venv erstellen
if [ ! -d "$VENV_DIR" ]; then
echo "Erstelle virtuelles Umfeld..."
python3 -m venv "$VENV_DIR"
fi
source "$VENV_DIR/bin/activate"
# Dependencies installieren (ohne mysql-connector)
pip install -q --upgrade pip
pip install -q fastapi uvicorn[standard] jinja2 icalendar recurring-ical-events
# Demo-Datenbank erstellen
echo "Erstelle Demo-Datenbank mit 4 Terminen..."
python "$SCRIPT_DIR/demo/seed.py"
# API starten
echo ""
echo "Starte API auf http://localhost:8000"
echo " Web-UI: http://localhost:8000/"
echo " API: http://localhost:8000/api/events"
echo " Health: http://localhost:8000/api/health"
echo ""
echo "Druecke Ctrl+C zum Beenden."
echo ""
cd "$SCRIPT_DIR"
DB_BACKEND=sqlite DB_PATH="$DB_PATH" \
exec python -m uvicorn api.main:app --host 0.0.0.0 --port 8000
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Erstellt eine lokale SQLite Demo-Datenbank mit 4 Beispiel-Terminen."""
import os
import sqlite3
from datetime import datetime, timedelta
DB_PATH = os.environ.get("DB_PATH", os.path.join(os.path.dirname(__file__), "demo.db"))
SCHEMA = """
CREATE TABLE IF NOT EXISTS calendar_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
calendar_label TEXT NOT NULL,
instance_key TEXT NOT NULL,
uid TEXT NOT NULL,
recurrence_id TEXT,
summary TEXT,
description TEXT,
location TEXT,
start_at TEXT NOT NULL,
end_at TEXT,
all_day INTEGER NOT NULL DEFAULT 0,
status TEXT DEFAULT 'CONFIRMED',
deleted INTEGER NOT NULL DEFAULT 0,
last_seen_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (calendar_label, instance_key)
);
CREATE INDEX IF NOT EXISTS idx_start ON calendar_events(start_at);
CREATE INDEX IF NOT EXISTS idx_deleted ON calendar_events(deleted);
"""
def seed():
if os.path.exists(DB_PATH):
os.remove(DB_PATH)
conn = sqlite3.connect(DB_PATH)
conn.executescript(SCHEMA)
now = datetime.now().replace(second=0, microsecond=0, tzinfo=None)
today = now.replace(hour=0, minute=0, second=0)
events = [
{
"calendar_label": "demo",
"instance_key": f"team-daily-{today.strftime('%Y%m%d')}",
"uid": "team-daily-001@demo",
"summary": "Team Daily",
"description": "Tägliches Standup-Meeting mit dem gesamten Team.",
"location": "Besprechungsraum A / Zoom",
"start_at": (today + timedelta(hours=14)).strftime("%Y-%m-%d %H:%M:%S"),
"end_at": (today + timedelta(hours=15)).strftime("%Y-%m-%d %H:%M:%S"),
"all_day": 0,
"status": "CONFIRMED",
},
{
"calendar_label": "demo",
"instance_key": f"yoga-{today.strftime('%Y%m%d')}",
"uid": "yoga-002@demo",
"summary": "Yoga Kurs",
"description": "Wöchentlicher Yoga-Kurs in der Volkshochschule.",
"location": "Volkshochschule, Raum 204",
"start_at": (today + timedelta(hours=18, minutes=30)).strftime("%Y-%m-%d %H:%M:%S"),
"end_at": (today + timedelta(hours=20)).strftime("%Y-%m-%d %H:%M:%S"),
"all_day": 0,
"status": "CONFIRMED",
},
{
"calendar_label": "demo",
"instance_key": f"deadline-{(today + timedelta(days=1)).strftime('%Y%m%d')}",
"uid": "deadline-003@demo",
"summary": "Projekt-Deadline",
"description": "Abgabe des Projektberichts an die Geschäftsleitung.",
"location": "",
"start_at": (today + timedelta(days=1)).strftime("%Y-%m-%d 00:00:00"),
"end_at": (today + timedelta(days=1)).strftime("%Y-%m-%d 23:59:59"),
"all_day": 1,
"status": "TENTATIVE",
},
{
"calendar_label": "demo",
"instance_key": f"arzt-{(today + timedelta(days=2)).strftime('%Y%m%d')}",
"uid": "arzt-004@demo",
"summary": "Arzttermin",
"description": "Routineuntersuchung beim Hausarzt. Bitte Impfpass mitbringen.",
"location": "Dr. Müller, Gesundheitsstraße 12",
"start_at": (today + timedelta(days=2, hours=10)).strftime("%Y-%m-%d %H:%M:%S"),
"end_at": (today + timedelta(days=2, hours=11, minutes=30)).strftime("%Y-%m-%d %H:%M:%S"),
"all_day": 0,
"status": "CONFIRMED",
},
]
conn.executemany(
"""INSERT INTO calendar_events
(calendar_label, instance_key, uid, summary, description, location,
start_at, end_at, all_day, status, last_seen_at)
VALUES
(:calendar_label, :instance_key, :uid, :summary, :description, :location,
:start_at, :end_at, :all_day, :status, datetime('now'))""",
events,
)
conn.commit()
conn.close()
print(f"Demo-Datenbank erstellt: {DB_PATH}")
print(f"4 Demo-Events eingefuegt.")
if __name__ == "__main__":
seed()