Files
calender_sync/SPEC.md
T
stefankoelle 5343896653 Fix: korrigiere Code-Bugs und Dokumentation
- api/main.py: index() Route übernimmt Request-Objekt korrekt (statt {}),
  calendar_label Query-Parameter implementiert (dynamische WHERE-Klausel)
- docker-compose.yml: DB_BOOTSTRAP/DB_ROOT_USER/DB_ROOT_PASSWORD Variablen
  für calendar-sync Service hinzugefügt (waren dokumentiert, aber nie übergeben)
- .env.example: DB_BOOTSTRAP, API_PORT, TIMEZONE hinzugefügt
- AGENTS.md: Zeilennummer sync.py:91→83, gemischte Deutsch/China-Sprache
  bereinigt
- SPEC.md: --entrypoint→command, API_HOST entfernt (nicht implementiert),
  TIMEZONE hinzugefügt, JSON-Beispiele um timezone-Feld erweitert,
  Docker-Compose-Beispiel und Projektstruktur aktualisiert,
  Search als implementiert markiert
- README.md: TIMEZONE und API_PORT in Konfigtationstabelle,
  timezone im JSON-Beispiel
2026-08-04 19:28:39 +02:00

10 KiB

SPEC.md - Calendar Sync Projekt

1. Projektübersicht

Python-basiertes System zur Synchronisation eines Google Calendar ICS-Feeds nach MariaDB, mit zusätzlichem REST API + Web-Frontend für die Anzeige der nächsten Termine. Läuft als Docker Container im Homelab.

2. Architektur

┌─────────────────────┐      ┌─────────────────────┐
│   calendar-sync     │      │   calendar-api      │
│   (Sync Tool)       │      │   (FastAPI + HTML)  │
│                     │      │                     │
│  - ICS Polling      │      │  - REST API         │
│  - RRULE Expansion  │      │  - Web-Frontend     │
│  - MariaDB Write    │      │  - MariaDB Read     │
└──────────┬──────────┘      └──────────┬──────────┘
           │                            │
           └────────────┬───────────────┘
                        │
               ┌────────▼────────┐
               │     MariaDB     │
               │  calendar_sync  │
               └─────────────────┘

Entscheidung: Gleicher Docker Build (ein Dockerfile), zwei verschiedene Container/Services via docker-compose.yml. Der jeweilige Service wird via command Parameter gesteuert (python sync.py vs. uvicorn api.main:app).

3. Bestehendes System (Sync Tool)

3.1 Funktionen

  • Lädt periodisch einen ICS-Feed von Google Calendar
  • Expandiert wiederkehrende Events (RRULE/EXDATE/RECURRENCE-ID)
  • Schreibt Einzel-Instanzen in MariaDB (calendar_events Tabelle)
  • Soft-Delete: Events werden mit deleted=1 markiert statt gelöscht
  • Optionaler Healthcheck-Ping nach jedem Sync-Durchlauf
  • Optionale Datenbank-Bootstrap (DB + User anlegen)

3.2 Datenbank-Schema (calendar_events)

Feld Typ Beschreibung
id BIGINT AUTO_INCREMENT Primärschlüssel
calendar_label VARCHAR(64) Kalender-Bezeichnung
instance_key VARCHAR(255) SHA1 Hash (UID + RECURRENCE-ID)
uid VARCHAR(255) Originale Event UID
recurrence_id VARCHAR(64) RECURRENCE-ID (nullable)
summary VARCHAR(512) Titel des Events
description TEXT Beschreibung
location VARCHAR(512) Ort
start_at DATETIME Startzeit (naive UTC)
end_at DATETIME Endzeit (naive UTC, nullable)
all_day TINYINT(1) Ganzägiges Event
status VARCHAR(32) CONFIRMED/CANCELLED/TENTATIVE
deleted TINYINT(1) Soft-Delete Flag
last_seen_at DATETIME Letzter Sync-Zeitpunkt
created_at DATETIME Erstellungszeitpunkt
updated_at DATETIME Letzter Update-Zeitpunkt

3.3 Environment Variablen

  • ICS_URL (required) - Google Calendar ICS Feed URL
  • CALENDAR_LABEL - Bezeichnung für den Kalender
  • DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD - MariaDB Zugangsdaten
  • SYNC_INTERVAL_MINUTES - Sync Intervall (default: 15)
  • WINDOW_PAST_DAYS, WINDOW_FUTURE_DAYS - Zeitfenster für Events
  • LOG_LEVEL - Logging Level
  • HEALTHCHECK_URL - Optionaler Healthcheck Endpoint
  • DB_BOOTSTRAP, DB_ROOT_USER, DB_ROOT_PASSWORD - Optionales DB Bootstrap

4. Neues System (API + Web-Frontend)

4.1 REST API Endpoints

GET /api/events

Gibt die nächsten N Termine zurück.

Query Parameter:

  • limit (optional, default: 10, max: 50) - Anzahl der Events
  • calendar_label (optional) - Filter nach Kalender
  • search (optional) - Suchbegriff für Event-Titel (LIKE %search%)

Response (JSON):

{
  "events": [
    {
      "id": 123,
      "summary": "Meeting mit Team",
      "description": "...",
      "location": "Raum 101",
      "start_at": "2025-01-15T10:00:00",
      "end_at": "2025-01-15T11:00:00",
       "all_day": false,
       "status": "CONFIRMED",
       "timezone": "Europe/Berlin"
     }
   ],
  "count": 10,
  "query_time": "2025-01-14T14:30:00Z",
  "timezone": "Europe/Berlin"
}

GET /api/events/{id}

Gibt ein einzelnes Event zurück.

GET /api/health

Healthcheck Endpoint für den API Container.

4.2 Web-Frontend

URL: http://localhost:8000/ (oder konfigurierbarer Port)

Funktionen:

  • Zeigt die nächsten 10 Termine in einer übersichtlichen Liste
  • Suchfeld oben (optional, filtert nach Event-Titel)
  • Responsive Design (funktioniert auf Desktop und Handy)
  • Einfaches, cleanes Design ohne Framework (nur HTML + CSS + vanilla JS)

Darstellung pro Event:

  • Datum + Uhrzeit (oder "Ganztägig")
  • Titel (summary)
  • Ort (location) - falls vorhanden
  • Status-Anzeige (Farbcode: grün=CONFIRMED, gelb=TENTATIVE, rot=CANCELLED)

4.3 Technologie-Stack (API)

  • Framework: FastAPI
  • Templating: Jinja2 (server-side rendering)
  • DB-Zugriff: mysql-connector-python (shared get_connection() aus api/database.py)
  • Port: 8000 (konfigurierbar via API_PORT)

4.4 Additional Environment Variablen (API)

  • API_PORT - Port für den API Server (default: 8000)
  • TIMEZONE - Zeitzone für die Anzeige von Zeiten (default: UTC)
  • DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD - Identisch zum Sync

5. Docker Setup

5.1 Dockerfile (erweitert)

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY sync.py .
COPY api/ ./api/

ENV PYTHONUNBUFFERED=1

# Default: Sync Tool
CMD ["python", "sync.py"]

Das Image enthält sowohl das Sync-Tool als auch die API. Der jeweilige Service wird via docker-compose.yml gesteuert.

5.2 Docker Compose (erweitert)

services:
  calendar-sync:
    image: ghcr.io/skoelle/calender_sync:latest
    container_name: calendar-sync
    restart: unless-stopped
    command: ["python", "sync.py"]
    environment:
      - ICS_URL=${ICS_URL}
      - CALENDAR_LABEL=${CALENDAR_LABEL:-privat}
      - DB_HOST=${DB_HOST:-mariadb.fritz.box}
      - DB_PORT=${DB_PORT:-3306}
      - DB_NAME=${DB_NAME:-calendar_sync}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - SYNC_INTERVAL_MINUTES=${SYNC_INTERVAL_MINUTES:-15}
      - WINDOW_PAST_DAYS=${WINDOW_PAST_DAYS:-90}
      - WINDOW_FUTURE_DAYS=${WINDOW_FUTURE_DAYS:-365}
      - LOG_LEVEL=${LOG_LEVEL:-INFO}
      - HEALTHCHECK_URL=${HEALTHCHECK_URL:-}
      - DB_BOOTSTRAP=${DB_BOOTSTRAP:-false}
      - DB_ROOT_USER=${DB_ROOT_USER:-}
      - DB_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-}
    networks:
      - docker-backend

  calendar-api:
    image: ghcr.io/skoelle/calender_sync:latest
    container_name: calendar-api
    restart: unless-stopped
    command: ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
    ports:
      - "${API_PORT:-8000}:8000"
    environment:
      - DB_HOST=${DB_HOST:-mariadb.fritz.box}
      - DB_PORT=${DB_PORT:-3306}
      - DB_NAME=${DB_NAME:-calendar_sync}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - LOG_LEVEL=${LOG_LEVEL:-INFO}
      - TIMEZONE=${TIMEZONE:-UTC}
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    networks:
      - docker-backend

networks:
  docker-backend:
    external: true

6. Projektstruktur (Zielstruktur)

.
├── sync.py                 # Hauptskript Sync Tool
├── api/
│   ├── __init__.py
│   ├── main.py             # FastAPI App + Routes
│   ├── database.py         # DB Connection Pool (shared)
│   └── templates/
│       └── index.html      # Jinja2 Template für Web-Frontend
├── requirements.txt        # Python Dependencies (erweitert)
├── Dockerfile              # Docker Image Definition (erweitert)
├── docker-compose.yml      # Docker Compose Konfiguration (erweitert)
├── mariadb-setup.sql       # Manuelles DB-Setup Script
├── .env.example            # Beispiel-Umgebungsvariablen (erweitert)
├── SPEC.md                 # Diese Spezifikation
├── PLAN.md                 # Implementierungsplan
├── AGENTS.md               # Richtlinien für AI-Agenten
└── .github/workflows/      # CI/CD (Docker Build + Push)

7. Dependencies (requirements.txt)

requests==2.32.3
icalendar==6.1.0
recurring-ical-events==3.4.1
mysql-connector-python==9.1.0
fastapi==0.115.0
uvicorn[standard]==0.30.0
jinja2==3.1.4

8. Design-Entscheidungen

Thema Entscheidung
API Authentifizierung Keine (nur Homelab intern)
Caching Kein (SQL Query bei jedem Request)
Auto-Refresh Frontend Kein (manueller Reload)
CORS Kein (Same-Origin via Jinja2 Templates)
Multi-Sync Nicht benötigt
iCal Export Nicht benötigt
Benachrichtigungen Nicht benötigt
Dark Mode Nicht benötigt
Search Optionaler Suchbegriff auf Event-Titel
DB Bootstrap Bleibt in sync.py
Template Styling Einfaches CSS, kein Framework
Logging Nur Errors + Request Log auf INFO Level

9. Testing

9.1 Unit Tests (optional, später)

  • test_to_naive_utc() - Zeitkonvertierung
  • test_instance_key_for() - Key Generation
  • API Endpoint Tests mit httpx + pytest

9.2 Integration Tests (optional, später)

  • Sync Tool → DB → API → Response validieren

10. CI/CD

Bestehender GitHub Actions Workflow erweitern:

  • Build einmal für beide Services
  • Optional: Separater Tag für API-only Image

11. Future Enhancements (nicht im Scope)

  • Kalender-Filter UI (nach calendar_label)
  • Suchfunktion nach Event-Titel (implementiert)