mirror of
https://github.com/skoelle/calender_sync.git
synced 2026-09-17 18:20:24 +00:00
demo showcase
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
.env
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
demo/demo.db
|
||||
|
||||
+16
-1
@@ -2,6 +2,22 @@
|
||||
# Licensed under the MIT License. See LICENSE file in project root for details.
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
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):
|
||||
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")
|
||||
@@ -10,7 +26,6 @@ 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,
|
||||
|
||||
+13
-7
@@ -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 ""},
|
||||
)
|
||||
|
||||
@@ -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
@@ -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()
|
||||
Reference in New Issue
Block a user