diff --git a/backend/db_init.py b/backend/db_init.py index 6714613..af6b9c9 100644 --- a/backend/db_init.py +++ b/backend/db_init.py @@ -32,13 +32,13 @@ def wait_for_postgres(max_retries=30): try: conn = get_connection() conn.close() - print("✓ PostgreSQL ready") + print("[OK] PostgreSQL ready") return True except OperationalError: print(f" Waiting for PostgreSQL... (attempt {i}/{max_retries})") time.sleep(2) - print(f"✗ PostgreSQL not ready after {max_retries} attempts") + print(f"[FAIL] PostgreSQL not ready after {max_retries} attempts") return False def check_table_exists(table_name="profiles"): @@ -71,10 +71,10 @@ def load_schema(schema_file="/app/schema.sql"): conn.commit() cur.close() conn.close() - print("✓ Schema loaded from schema.sql") + print("[OK] Schema loaded from schema.sql") return True except Exception as e: - print(f"✗ Error loading schema: {e}") + print(f"[FAIL] Error loading schema: {e}") return False def get_profile_count(): @@ -146,10 +146,10 @@ def apply_migration(filepath, filename): conn.commit() cur.close() conn.close() - print(f" ✓ Applied: {filename}") + print(f" [OK] Applied: {filename}") return True except Exception as e: - print(f" ✗ Failed to apply {filename}: {e}") + print(f" [FAIL] Failed to apply {filename}: {e}") return False def run_migrations(migrations_dir="/app/migrations"): @@ -158,7 +158,7 @@ def run_migrations(migrations_dir="/app/migrations"): import re if not os.path.exists(migrations_dir): - print("✓ No migrations directory found") + print("[OK] No migrations directory found") return True # Ensure migration tracking table exists @@ -174,7 +174,7 @@ def run_migrations(migrations_dir="/app/migrations"): migration_files = [f for f in all_files if migration_pattern.match(os.path.basename(f))] if not migration_files: - print("✓ No migration files found") + print("[OK] No migration files found") return True # Apply pending migrations @@ -185,7 +185,7 @@ def run_migrations(migrations_dir="/app/migrations"): pending.append((filepath, filename)) if not pending: - print(f"✓ All {len(applied)} migrations already applied") + print(f"[OK] All {len(applied)} migrations already applied") return True print(f" Found {len(pending)} pending migration(s)...") @@ -211,12 +211,12 @@ if __name__ == "__main__": if not load_schema(): sys.exit(1) else: - print("✓ Schema already exists") + print("[OK] Schema already exists") # Run migrations print("\nRunning database migrations...") if not run_migrations(): - print("✗ Migration failed") + print("[FAIL] Migration failed") sys.exit(1) # Check for migration @@ -232,14 +232,14 @@ if __name__ == "__main__": from migrate_to_postgres import main as migrate migrate() except Exception as e: - print(f"✗ Migration failed: {e}") + print(f"[FAIL] Migration failed: {e}") sys.exit(1) elif os.path.exists(sqlite_db) and profile_count > 0: - print(f"⚠ SQLite DB exists but PostgreSQL already has {profile_count} profiles") + print(f"[WARN] SQLite DB exists but PostgreSQL already has {profile_count} profiles") print(" Skipping migration (already migrated)") elif not os.path.exists(sqlite_db): - print("✓ No SQLite database found (fresh install or already migrated)") + print("[OK] No SQLite database found (fresh install or already migrated)") else: - print("✓ No migration needed") + print("[OK] No migration needed") - print("\n✓ Database initialization complete") + print("\n[OK] Database initialization complete") diff --git a/backend/main.py b/backend/main.py index d7a0ab9..a03c0b3 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,20 +21,20 @@ from version import APP_VERSION, BUILD_DATE, DB_SCHEMA_VERSION, MODULE_VERSIONS # Run database migrations before API start — halbes Schema ist schlimmer als kein Start # Lokal ohne DB / nur Tests: SKIP_DB_MIGRATE=1 if os.getenv("SKIP_DB_MIGRATE", "").strip().lower() in ("1", "true", "yes"): - print("⚠ SKIP_DB_MIGRATE=1 — Migrationen wurden übersprungen (nur für Entwicklung ohne DB)") + print("[SKIP_DB_MIGRATE] Migrationen uebersprungen (nur fuer Entwicklung ohne DB)") else: try: import run_migrations rc = run_migrations.main() if rc != 0: - print(f"✗ Datenbank-Migration fehlgeschlagen (Exit-Code {rc}). Start abgebrochen.") + print(f"[FAIL] Datenbank-Migration fehlgeschlagen (Exit-Code {rc}). Start abgebrochen.") sys.exit(1) - print("✓ Database migrations completed") + print("[OK] Database migrations completed") except SystemExit: raise except Exception as e: - print(f"✗ Migration-Laufzeitfehler: {e}") + print(f"[FAIL] Migration-Laufzeitfehler: {e}") sys.exit(1) from routers.auth import limiter as auth_rate_limiter diff --git a/backend/migrations/039_club_membership_rbac.sql b/backend/migrations/039_club_membership_rbac.sql index 700187e..feee278 100644 --- a/backend/migrations/039_club_membership_rbac.sql +++ b/backend/migrations/039_club_membership_rbac.sql @@ -49,8 +49,9 @@ ON CONFLICT (profile_id, club_id) DO NOTHING; INSERT INTO club_members (profile_id, club_id, status) SELECT DISTINCT elem::int, t.club_id, 'active' FROM training_groups t, -LATERAL jsonb_array_elements_text(COALESCE(t.co_trainer_ids, '[]'::jsonb)) AS elem -WHERE jsonb_array_length(COALESCE(t.co_trainer_ids, '[]'::jsonb)) > 0 +LATERAL jsonb_array_elements_text(t.co_trainer_ids) AS elem +WHERE jsonb_typeof(t.co_trainer_ids) = 'array' + AND jsonb_array_length(t.co_trainer_ids) > 0 ON CONFLICT (profile_id, club_id) DO NOTHING; INSERT INTO club_member_roles (club_member_id, role_code) diff --git a/backend/run_migrations.py b/backend/run_migrations.py index bd5f5d8..b68d5a5 100644 --- a/backend/run_migrations.py +++ b/backend/run_migrations.py @@ -49,14 +49,14 @@ def get_db_connection(): password=p["password"], ) conn.autocommit = False - print(f"✓ Connected to database: {p['dbname']}") + print(f"[OK] Connected to database: {p['dbname']}") return conn except psycopg2.OperationalError: if i < max_retries - 1: print(f"Waiting for database... ({i+1}/{max_retries})") time.sleep(2) else: - print(f"✗ Failed to connect to database after {max_retries} attempts") + print(f"[FAIL] Failed to connect to database after {max_retries} attempts") raise @@ -72,7 +72,7 @@ def init_migrations_table(conn): """ ) conn.commit() - print("✓ schema_migrations initialisiert") + print("[OK] schema_migrations initialisiert") _LEADING_DIGITS = re.compile(r"^(\d+)") @@ -190,7 +190,7 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool: if shutil.which("psql"): ok, diag = _run_file_with_psql(filepath) if not ok: - print(f" ✗ psql fehlgeschlagen:\n{diag or '(kein Output)'}") + print(f" [FAIL] psql fehlgeschlagen:\n{diag or '(kein Output)'}") conn.rollback() return False detail_suffix = "(psql -1)" @@ -199,7 +199,7 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool: with open(filepath, "r", encoding="utf-8") as fh: body = fh.read() except OSError as e: - print(f" ✗ kann Datei nicht lesen: {e}") + print(f" [FAIL] kann Datei nicht lesen: {e}") conn.rollback() return False @@ -207,7 +207,7 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool: with conn.cursor() as cur: if not statements: print( - f" ⚠ keine ausführbaren Statements (leer?) — " + f" [WARN] keine ausführbaren Statements (leer?) — " f"Eintrag trotzdem: {migration_name}" ) else: @@ -217,12 +217,12 @@ def run_migration(conn, migration_name: str, filepath: str) -> bool: _record_migration(conn, migration_name) conn.commit() - print(f" ✓ {migration_name} erfolgreich {detail_suffix}") + print(f" [OK] {migration_name} erfolgreich {detail_suffix}") return True except Exception as e: conn.rollback() - print(f" ✗ {migration_name}: {e}") + print(f" [FAIL] {migration_name}: {e}") return False @@ -243,7 +243,7 @@ def main(): pending = get_pending(conn, migrations_dir) if not pending: - print("✓ Keine ausstehenden Migrationen — Schema aktuell.") + print("[OK] Keine ausstehenden Migrationen — Schema aktuell.") conn.close() return 0 @@ -262,17 +262,17 @@ def main(): print("\n" + "=" * 60) if failed: - print(f"✗ Abbruch nach: {failed}") + print(f"[FAIL] Abbruch nach: {failed}") print(" (Bereits erfolgreiche Dateien dieser Session sind committed.)") print("=" * 60) return 1 - print(f"✓ {len(pending)} Migration(s) angewendet — Schema aktuell.") + print(f"[OK] {len(pending)} Migration(s) angewendet — Schema aktuell.") print("=" * 60) return 0 except Exception as e: - print(f"\n✗ Fehler: {e}") + print(f"\n[FAIL] Fehler: {e}") return 1 diff --git a/backend/version.py b/backend/version.py index e1c7330..0539da1 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.12" +APP_VERSION = "0.8.14" BUILD_DATE = "2026-05-05" DB_SCHEMA_VERSION = "20260505039" @@ -23,6 +23,20 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.14", + "date": "2026-05-05", + "changes": [ + "DB 039 Fix: Co-Trainer-Backfill nur wenn co_trainer_ids ein JSON-Array ist (vermeidet jsonb_array_length auf Nicht-Array)", + ], + }, + { + "version": "0.8.13", + "date": "2026-05-05", + "changes": [ + "Fix: Startup unter Windows (cp1252) — Emoji/Sonderzeichen in print durch ASCII ([OK]/[FAIL]/[WARN]) ersetzt (main, run_migrations, db_init)", + ], + }, { "version": "0.8.12", "date": "2026-05-05", diff --git a/frontend/src/version.js b/frontend/src/version.js index d96640c..efcbfce 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,6 +1,6 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.8.12" +export const APP_VERSION = "0.8.14" export const BUILD_DATE = "2026-05-05" export const PAGE_VERSIONS = {