Bug Fix Dashboard + Performance Phase 3a #34

Merged
Lars merged 3 commits from develop into main 2026-05-14 11:51:53 +02:00
5 changed files with 55 additions and 2 deletions
Showing only changes of commit 2e105a99b8 - Show all commits

View File

@ -0,0 +1,16 @@
"""Hilfen für direkte Python-Aufrufe von FastAPI-Route-Handlern (ohne Request-Kontext)."""
from __future__ import annotations
from typing import Any
def unwrap_query_default(value: Any) -> Any:
"""
Parameter mit Annotation ``= Query(default=)`` sind im Funktionskörper ``fastapi.params.Query``-Instanzen,
solange FastAPI sie nicht durch echte Werte ersetzt hat (interne Aufrufe, Aggregat-Endpunkte).
"""
try:
from fastapi.params import Query
except ImportError:
return value
return value.default if isinstance(value, Query) else value

View File

@ -19,6 +19,8 @@ from fastapi.responses import FileResponse, Response, StreamingResponse
from pydantic import BaseModel, Field, model_validator from pydantic import BaseModel, Field, model_validator
from psycopg2.extras import Json from psycopg2.extras import Json
from fastapi_param_unwrap import unwrap_query_default
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from club_tenancy import ( from club_tenancy import (
assert_valid_governance_visibility, assert_valid_governance_visibility,
@ -1773,6 +1775,11 @@ def list_exercises(
Optional include_variants für Variantenauswahl in der Trainingsplanung. Optional include_variants für Variantenauswahl in der Trainingsplanung.
Keyset: cursor_updated_at + cursor_id ersetzt große OFFSET-Werte (Sortierung: updated_at DESC, id DESC). Keyset: cursor_updated_at + cursor_id ersetzt große OFFSET-Werte (Sortierung: updated_at DESC, id DESC).
""" """
cursor_updated_at = unwrap_query_default(cursor_updated_at)
cursor_id = unwrap_query_default(cursor_id)
limit = unwrap_query_default(limit)
offset = unwrap_query_default(offset)
profile_id = tenant.profile_id profile_id = tenant.profile_id
c_ts_raw = (cursor_updated_at or "").strip() or None c_ts_raw = (cursor_updated_at or "").strip() or None

View File

@ -10,6 +10,8 @@ from typing import Any, Dict, List, Optional, Tuple
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
from psycopg2.extras import Json as PsycopgJson from psycopg2.extras import Json as PsycopgJson
from fastapi_param_unwrap import unwrap_query_default
from db import get_db, get_cursor, r2d from db import get_db, get_cursor, r2d
from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql from tenant_context import TenantContext, get_tenant_context, library_content_visibility_sql
from club_tenancy import ( from club_tenancy import (
@ -1341,6 +1343,19 @@ def list_training_units(
), ),
tenant: TenantContext = Depends(get_tenant_context), tenant: TenantContext = Depends(get_tenant_context),
): ):
group_id = unwrap_query_default(group_id)
club_id = unwrap_query_default(club_id)
start_date = unwrap_query_default(start_date)
end_date = unwrap_query_default(end_date)
status = unwrap_query_default(status)
assigned_to_me = unwrap_query_default(assigned_to_me)
debrief_pending = unwrap_query_default(debrief_pending)
sort = unwrap_query_default(sort)
limit = unwrap_query_default(limit)
cursor_planned_date = unwrap_query_default(cursor_planned_date)
cursor_planned_time = unwrap_query_default(cursor_planned_time)
cursor_id = unwrap_query_default(cursor_id)
profile_id = tenant.profile_id profile_id = tenant.profile_id
role = tenant.global_role role = tenant.global_role

View File

@ -1,13 +1,15 @@
"""GET /api/dashboard/kpis: Auth (kein DB nötig).""" """GET /api/dashboard/kpis: Auth + interne Aufruf-Hilfen."""
from __future__ import annotations from __future__ import annotations
import os import os
import pytest import pytest
from fastapi import Query
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
os.environ.setdefault("SKIP_DB_MIGRATE", "1") os.environ.setdefault("SKIP_DB_MIGRATE", "1")
from fastapi_param_unwrap import unwrap_query_default
from main import app from main import app
@ -16,6 +18,12 @@ def client() -> TestClient:
return TestClient(app) return TestClient(app)
def test_unwrap_query_default_for_direct_route_calls() -> None:
assert unwrap_query_default(Query(default=None)) is None
assert unwrap_query_default("2026-01-01") == "2026-01-01"
assert unwrap_query_default(7) == 7
def test_dashboard_kpis_unauthenticated_401(client: TestClient) -> None: def test_dashboard_kpis_unauthenticated_401(client: TestClient) -> None:
r = client.get("/api/dashboard/kpis") r = client.get("/api/dashboard/kpis")
assert r.status_code == 401 assert r.status_code == 401

View File

@ -1,6 +1,6 @@
# Shinkan Jinkendo Version Information # Shinkan Jinkendo Version Information
APP_VERSION = "0.8.122" APP_VERSION = "0.8.123"
BUILD_DATE = "2026-05-12" BUILD_DATE = "2026-05-12"
DB_SCHEMA_VERSION = "20260514062" DB_SCHEMA_VERSION = "20260514062"
@ -36,6 +36,13 @@ MODULE_VERSIONS = {
} }
CHANGELOG = [ CHANGELOG = [
{
"version": "0.8.123",
"date": "2026-05-13",
"changes": [
"Fix: GET /api/dashboard/kpis — interne Aufrufe von list_exercises / list_training_units erhielten FastAPI-Query-Defaults statt None; .strip() auf Query-Objekt → 500. unwrap_query_default in beiden Handlern (Hilfsmodul fastapi_param_unwrap.py).",
],
},
{ {
"version": "0.8.122", "version": "0.8.122",
"date": "2026-05-13", "date": "2026-05-13",