From 4f9761efb0baa23a12a545d5adb78781c6d02ddb Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Fri, 21 Nov 2025 09:31:43 +0100 Subject: [PATCH] Update MyTischtennis model to use LONGTEXT for encrypted fields and enhance TeamManagementView with season change handling and async loading --- .../migrations/check_seasons_and_teams.sql | 62 +++++++++++++ .../check_seasons_and_teams_simple.sql | 30 ++++++ backend/migrations/fix_seasons_and_teams.sql | 92 +++++++++++++++++++ ...my_tischtennis_text_fields_to_longtext.sql | 39 ++++++++ backend/models/MyTischtennis.js | 16 ++-- frontend/src/views/TeamManagementView.vue | 31 +++++-- 6 files changed, 256 insertions(+), 14 deletions(-) create mode 100644 backend/migrations/check_seasons_and_teams.sql create mode 100644 backend/migrations/check_seasons_and_teams_simple.sql create mode 100644 backend/migrations/fix_seasons_and_teams.sql create mode 100644 backend/migrations/update_my_tischtennis_text_fields_to_longtext.sql diff --git a/backend/migrations/check_seasons_and_teams.sql b/backend/migrations/check_seasons_and_teams.sql new file mode 100644 index 0000000..1b2e515 --- /dev/null +++ b/backend/migrations/check_seasons_and_teams.sql @@ -0,0 +1,62 @@ +-- Diagnose-Skript: Prüfe Seasons und Teams auf dem Server +-- Führe diese Queries auf dem Server aus, um das Problem zu identifizieren + +-- 1. Prüfe, ob die season-Tabelle existiert und Daten enthält +SELECT '=== SEASONS ===' as info; +SELECT * FROM `season` ORDER BY `id` DESC; + +-- 2. Prüfe, ob die club_team-Tabelle existiert und welche season_id verwendet wird +SELECT '=== CLUB_TEAMS ===' as info; +SELECT + id, + name, + club_id, + season_id, + league_id, + created_at, + updated_at +FROM `club_team` +ORDER BY `id`; + +-- 3. Prüfe, ob es Teams gibt, die auf nicht-existierende Seasons verweisen +SELECT '=== TEAMS MIT FEHLENDEN SEASONS ===' as info; +SELECT + ct.id, + ct.name, + ct.season_id, + s.season +FROM `club_team` ct +LEFT JOIN `season` s ON ct.season_id = s.id +WHERE s.id IS NULL; + +-- 4. Prüfe, ob es Teams gibt, die keine season_id haben +SELECT '=== TEAMS OHNE SEASON_ID ===' as info; +SELECT + id, + name, + club_id, + season_id +FROM `club_team` +WHERE season_id IS NULL; + +-- 5. Prüfe die Struktur der club_team-Tabelle +SELECT '=== CLUB_TEAM TABELLENSTRUKTUR ===' as info; +DESCRIBE `club_team`; + +-- 6. Prüfe die Struktur der season-Tabelle +SELECT '=== SEASON TABELLENSTRUKTUR ===' as info; +DESCRIBE `season`; + +-- 7. Prüfe Foreign Key Constraints +SELECT '=== FOREIGN KEYS ===' as info; +SELECT + CONSTRAINT_NAME, + TABLE_NAME, + COLUMN_NAME, + REFERENCED_TABLE_NAME, + REFERENCED_COLUMN_NAME +FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE +WHERE TABLE_SCHEMA = DATABASE() + AND (TABLE_NAME = 'club_team' OR TABLE_NAME = 'season') + AND REFERENCED_TABLE_NAME IS NOT NULL; + diff --git a/backend/migrations/check_seasons_and_teams_simple.sql b/backend/migrations/check_seasons_and_teams_simple.sql new file mode 100644 index 0000000..61a8558 --- /dev/null +++ b/backend/migrations/check_seasons_and_teams_simple.sql @@ -0,0 +1,30 @@ +-- Vereinfachtes Diagnose-Skript: Prüfe nur die wichtigsten Punkte + +-- 1. Gibt es Seasons in der Datenbank? +SELECT 'SEASONS:' as check_type, COUNT(*) as count FROM `season`; +SELECT * FROM `season` ORDER BY `id` DESC; + +-- 2. Gibt es Teams in der Datenbank? +SELECT 'CLUB_TEAMS:' as check_type, COUNT(*) as count FROM `club_team`; +SELECT id, name, club_id, season_id, league_id FROM `club_team` ORDER BY `id`; + +-- 3. Haben alle Teams eine season_id? +SELECT 'TEAMS OHNE SEASON_ID:' as check_type, COUNT(*) as count +FROM `club_team` WHERE season_id IS NULL; + +-- 4. Verweisen alle Teams auf existierende Seasons? +SELECT 'TEAMS MIT FEHLENDEN SEASONS:' as check_type, COUNT(*) as count +FROM `club_team` ct +LEFT JOIN `season` s ON ct.season_id = s.id +WHERE s.id IS NULL; + +-- 5. Welche season_id verwenden die Teams? +SELECT 'SEASON_ID VERWENDUNG:' as check_type, season_id, COUNT(*) as team_count +FROM `club_team` +GROUP BY season_id; + +-- 6. Welche Seasons existieren? +SELECT 'EXISTIERENDE SEASONS:' as check_type, id, season +FROM `season` +ORDER BY id; + diff --git a/backend/migrations/fix_seasons_and_teams.sql b/backend/migrations/fix_seasons_and_teams.sql new file mode 100644 index 0000000..d44c24c --- /dev/null +++ b/backend/migrations/fix_seasons_and_teams.sql @@ -0,0 +1,92 @@ +-- Fix-Skript: Behebt häufige Probleme mit Seasons und Teams +-- Führe dieses Skript auf dem Server aus, wenn die Diagnose Probleme zeigt + +-- 1. Stelle sicher, dass die season-Tabelle existiert und die richtige Struktur hat +-- (Falls die Tabelle nicht existiert, wird sie erstellt) +CREATE TABLE IF NOT EXISTS `season` ( + `id` INT NOT NULL AUTO_INCREMENT, + `season` VARCHAR(255) NOT NULL UNIQUE, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- 2. Stelle sicher, dass die club_team-Tabelle die season_id-Spalte hat +-- (Falls die Spalte nicht existiert, wird sie hinzugefügt) +ALTER TABLE `club_team` +ADD COLUMN IF NOT EXISTS `season_id` INT NULL; + +-- 3. Erstelle die Seasons, falls sie fehlen +INSERT IGNORE INTO `season` (`season`) VALUES ('2024/2025'); +INSERT IGNORE INTO `season` (`season`) VALUES ('2025/2026'); + +-- 4. Aktualisiere Teams ohne season_id auf die aktuelle Saison +-- (Verwendet die neueste Saison basierend auf dem aktuellen Datum) +UPDATE `club_team` +SET `season_id` = ( + SELECT `id` FROM `season` + WHERE `season` = ( + CASE + WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1) + ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE())) + END + ) + LIMIT 1 +) +WHERE `season_id` IS NULL; + +-- 5. Falls keine aktuelle Saison existiert, erstelle sie +INSERT IGNORE INTO `season` (`season`) VALUES ( + CASE + WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1) + ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE())) + END +); + +-- 6. Aktualisiere Teams mit ungültigen season_id auf die aktuelle Saison +UPDATE `club_team` ct +LEFT JOIN `season` s ON ct.season_id = s.id +SET ct.season_id = ( + SELECT `id` FROM `season` + WHERE `season` = ( + CASE + WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1) + ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE())) + END + ) + LIMIT 1 +) +WHERE s.id IS NULL; + +-- 7. Füge Foreign Key Constraint hinzu, falls er fehlt +-- (Hinweis: MySQL/MariaDB unterstützt "IF NOT EXISTS" nicht für Constraints, +-- daher müssen wir prüfen, ob der Constraint bereits existiert) +SET @constraint_exists = ( + SELECT COUNT(*) + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = 'club_team' + AND CONSTRAINT_NAME = 'club_team_season_id_foreign_idx' + AND REFERENCED_TABLE_NAME = 'season' +); + +SET @sql = IF(@constraint_exists = 0, + 'ALTER TABLE `club_team` ADD CONSTRAINT `club_team_season_id_foreign_idx` FOREIGN KEY (`season_id`) REFERENCES `season` (`id`) ON DELETE CASCADE ON UPDATE CASCADE', + 'SELECT "Foreign key constraint already exists" as message' +); + +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +-- 8. Zeige die Ergebnisse +SELECT '=== ERGEBNIS ===' as info; +SELECT + ct.id, + ct.name, + ct.season_id, + s.season +FROM `club_team` ct +LEFT JOIN `season` s ON ct.season_id = s.id +ORDER BY ct.id; + diff --git a/backend/migrations/update_my_tischtennis_text_fields_to_longtext.sql b/backend/migrations/update_my_tischtennis_text_fields_to_longtext.sql new file mode 100644 index 0000000..fc977a8 --- /dev/null +++ b/backend/migrations/update_my_tischtennis_text_fields_to_longtext.sql @@ -0,0 +1,39 @@ +-- Migration: Update my_tischtennis table TEXT fields to LONGTEXT for encrypted data +-- Date: 2025-11-21 +-- For MariaDB/MySQL +-- +-- Problem: Encrypted data can be very long, and TEXT fields (max 65KB) are too small +-- Solution: Change to LONGTEXT (max 4GB) for all encrypted fields + +-- Update user_data to LONGTEXT +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `user_data` LONGTEXT NULL; + +-- Update access_token to LONGTEXT +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `access_token` LONGTEXT NULL; + +-- Update refresh_token to LONGTEXT +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `refresh_token` LONGTEXT NULL; + +-- Update cookie to LONGTEXT +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `cookie` LONGTEXT NULL; + +-- Update encrypted_password to LONGTEXT +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `encrypted_password` LONGTEXT NULL; + +-- Update club_id to LONGTEXT (was VARCHAR, but encrypted data can be longer) +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `club_id` LONGTEXT NULL; + +-- Update club_name to LONGTEXT (was VARCHAR, but encrypted data can be longer) +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `club_name` LONGTEXT NULL; + +-- Update fed_nickname to LONGTEXT (was VARCHAR, but encrypted data can be longer) +ALTER TABLE `my_tischtennis` +MODIFY COLUMN `fed_nickname` LONGTEXT NULL; + diff --git a/backend/models/MyTischtennis.js b/backend/models/MyTischtennis.js index 557399b..f361674 100644 --- a/backend/models/MyTischtennis.js +++ b/backend/models/MyTischtennis.js @@ -32,7 +32,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, encryptedPassword: { - type: DataTypes.TEXT, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data allowNull: true, field: 'encrypted_password' }, @@ -49,7 +49,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { field: 'auto_update_ratings' }, accessToken: { - type: DataTypes.TEXT, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data allowNull: true, field: 'access_token', set(value) { @@ -67,7 +67,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, refreshToken: { - type: DataTypes.TEXT, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data allowNull: true, field: 'refresh_token', set(value) { @@ -90,7 +90,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { field: 'expires_at' }, cookie: { - type: DataTypes.TEXT, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data allowNull: true, set(value) { if (value === null || value === undefined) { @@ -107,7 +107,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, userData: { - type: DataTypes.TEXT, // Changed from JSON to TEXT to store encrypted JSON string + type: DataTypes.TEXT('long'), // Use LONGTEXT to support very long encrypted strings allowNull: true, field: 'user_data', set(value) { @@ -132,7 +132,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, clubId: { - type: DataTypes.STRING, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data (can be longer than VARCHAR) allowNull: true, field: 'club_id', set(value) { @@ -150,7 +150,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, clubName: { - type: DataTypes.STRING, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data (can be longer than VARCHAR) allowNull: true, field: 'club_name', set(value) { @@ -168,7 +168,7 @@ const MyTischtennis = sequelize.define('MyTischtennis', { } }, fedNickname: { - type: DataTypes.STRING, + type: DataTypes.TEXT('long'), // Use LONGTEXT for encrypted data (can be longer than VARCHAR) allowNull: true, field: 'fed_nickname', set(value) { diff --git a/frontend/src/views/TeamManagementView.vue b/frontend/src/views/TeamManagementView.vue index 3d750b7..749bd22 100644 --- a/frontend/src/views/TeamManagementView.vue +++ b/frontend/src/views/TeamManagementView.vue @@ -801,9 +801,12 @@ export default { }; const onSeasonChange = (season) => { - currentSeason.value = season; - loadTeams(); - loadLeagues(); + if (season) { + currentSeason.value = season; + selectedSeasonId.value = season.id; + loadTeams(); + loadLeagues(); + } }; // Load scheduler jobs info @@ -849,11 +852,19 @@ export default { }; // Lifecycle - onMounted(() => { + onMounted(async () => { // Lade Ligen beim ersten Laden der Seite (ohne Saison-Filter) - loadLeagues(); + await loadLeagues(); // Lade Job-Informationen - loadSchedulerJobsInfo(); + await loadSchedulerJobsInfo(); + + // Warte kurz, damit SeasonSelector die Saison setzen kann + // Dann lade Teams, falls eine Saison ausgewählt wurde + setTimeout(() => { + if (selectedSeasonId.value && selectedClub.value) { + loadTeams(); + } + }, 100); }); // PDF-Dialog Funktionen @@ -1222,6 +1233,14 @@ export default { } }); + // Watch selectedSeasonId to load teams when season changes + watch(selectedSeasonId, (newSeasonId) => { + if (newSeasonId && selectedClub.value) { + loadTeams(); + loadLeagues(); + } + }); + const validateTeamDocumentFile = async (file, label) => { if (!file) { return false;