diff --git a/README.md b/README.md index 9fd62fe..ed87ee5 100644 --- a/README.md +++ b/README.md @@ -204,12 +204,15 @@ Zugriff ohne den Reverse-Proxy ist damit nicht möglich. | Methode | Pfad | Beschreibung | |---------|------|--------------| -| `GET` | `/` | Web-UI — zeigt Kontakte des eingeloggten Users (HTML) | -| `GET` | `/contacts/{id}` | Web-UI — Detailseite eines einzelnen Kontakts | +| `GET` | `/` | Dashboard mit Kontaktdaten-Übersicht, letzten Sync-Status und Geburtstagen der nächsten 7 Tage (HTML) | +| `GET` | `/search` | Web-UI -- Suchfunktion, zeigt Kontakte des eingeloggten Users | +| `GET` | `/contacts/{id}` | Web-UI -- Detailseite eines einzelnen Kontakts | | `GET` | `/api/health` | Health Check (`{"status": "ok"}`), kein Login nötig | | `GET` | `/api/contacts` | Kontaktsuche mit Pagination (`?q=...&limit=...&offset=...`) | | `GET` | `/api/contacts/{id}` | Einzelnen Kontakt per ID abrufen | +| `GET` | `/api/contacts/count` | Anzahl der Kontakte des eingeloggten Users | | `GET` | `/api/contacts/birthdays/today` | Heutige Geburtstage | +| `GET` | `/api/contacts/birthdays/upcoming` | Geburtstage der nächsten N Tage (`?days=7`, Default 7) | | `GET` | `/api/sync-runs` | Letzte 50 Sync-Runs (Status, Zeitstempel, Fehler) | Alle Endpunkte (außer `/api/health`) erfordern eine Authentifizierung diff --git a/SPEC.md b/SPEC.md index be5cd93..9beaacd 100644 --- a/SPEC.md +++ b/SPEC.md @@ -240,12 +240,15 @@ geteilt wird. Getrennt ist nur die **Rolle**, in der der Container läuft. | Endpunkt | Beschreibung | |---|---| -| `GET /` | Einfache HTML-Übersicht (Jinja2-Template), zeigt Kontakte des zugeordneten Accounts | +| `GET /` | Dashboard mit Kontaktdaten-Übersicht, letzten Sync-Status und Geburtstagen der nächsten 7 Tage (HTML) | +| `GET /search` | HTML-Übersicht mit Suchfunktion, zeigt Kontakte des zugeordneten Accounts | | `GET /contacts/{id}` | HTML-Detailseite eines einzelnen Kontakts (Jinja2-Template) | | `GET /api/health` | Health-Check ohne Auth-Anforderung | | `GET /api/contacts` | Kontaktliste, Filter `q` (Freitext), Pagination `limit`/`offset` | | `GET /api/contacts/{id}` | Einzelner Kontakt (JSON) | +| `GET /api/contacts/count` | Anzahl der Kontakte des zugeordneten Accounts | | `GET /api/contacts/birthdays/today` | Heutige Geburtstage (kontospezifisch bzw. global für Admins) | +| `GET /api/contacts/birthdays/upcoming` | Geburtstage der nächsten N Tage (Parameter `days`, Default 7) | | `GET /api/sync-runs` | Sync-Historie (kontospezifisch bzw. global für Admins) | ### 12.5 Netzwerkkontext diff --git a/src/api/main.py b/src/api/main.py index 000d14f..89bc7c1 100644 --- a/src/api/main.py +++ b/src/api/main.py @@ -11,15 +11,15 @@ import json import logging from datetime import date -from fastapi import FastAPI, Depends, Query, Request +from fastapi import Depends, FastAPI, Query, Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates -from config import Config import db from api.auth import get_current_user, resolve_account_for_user from api.schemas import ContactListResponse, ContactOut, SyncRunOut +from config import Config logging.basicConfig(level=Config.LOG_LEVEL, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger("api") @@ -129,6 +129,25 @@ def birthdays_today(current_user: str = Depends(get_current_user)): return [_row_to_contact_out(r) for r in rows] +@app.get("/api/contacts/birthdays/upcoming") +def birthdays_upcoming( + days: int = Query(default=7, ge=1, le=90), + current_user: str = Depends(get_current_user), +): + account_name, is_admin = resolve_account_for_user(current_user) + with db.get_connection() as conn: + rows = db.get_upcoming_birthdays(conn, account_name, days) + return {"days": days, "items": rows} + + +@app.get("/api/contacts/count") +def contact_count(current_user: str = Depends(get_current_user)): + account_name, is_admin = resolve_account_for_user(current_user) + with db.get_connection() as conn: + total = db.get_contact_count(conn, account_name) + return {"total": total} + + @app.get("/api/sync-runs", response_model=list[SyncRunOut]) def list_sync_runs(current_user: str = Depends(get_current_user)): account_name, is_admin = resolve_account_for_user(current_user) @@ -153,7 +172,47 @@ def list_sync_runs(current_user: str = Depends(get_current_user)): @app.get("/", response_class=HTMLResponse) -def web_index( +def web_dashboard( + request: Request, + current_user: str = Depends(get_current_user), +): + account_name, is_admin = resolve_account_for_user(current_user) + + with db.get_connection() as conn: + contact_count = db.get_contact_count(conn, account_name) + upcoming_birthdays = db.get_upcoming_birthdays(conn, account_name, 7) + where_clause, params = _account_filter_clause(account_name) + with conn.cursor() as cur: + cur.execute( + f"""SELECT id, account, sync_type, started_at, finished_at, status, + contacts_upserted, contacts_deleted, error_message + FROM sync_runs {where_clause} + ORDER BY started_at DESC + LIMIT 1""", + params, + ) + last_sync = cur.fetchone() + + if last_sync: + last_sync["started_at"] = str(last_sync["started_at"]) + last_sync["finished_at"] = str(last_sync["finished_at"]) if last_sync["finished_at"] else None + + return templates.TemplateResponse( + "dashboard.html", + { + "request": request, + "current_user": current_user, + "is_admin": is_admin, + "account_name": account_name or "alle Accounts", + "contact_count": contact_count, + "upcoming_birthdays": upcoming_birthdays, + "last_sync": last_sync, + }, + ) + + +@app.get("/search", response_class=HTMLResponse) +def web_search( request: Request, search: str | None = Query(default=None), current_user: str = Depends(get_current_user), @@ -215,7 +274,7 @@ def web_contact( if not row: from fastapi.responses import RedirectResponse - return RedirectResponse(url="/", status_code=303) + return RedirectResponse(url="/search", status_code=303) contact = _row_to_contact_out(row) diff --git a/src/api/templates/contact.html b/src/api/templates/contact.html index 724778a..64f3deb 100644 --- a/src/api/templates/contact.html +++ b/src/api/templates/contact.html @@ -182,7 +182,7 @@