diff --git a/backend/services/autoFetchMatchResultsService.js b/backend/services/autoFetchMatchResultsService.js index b0f3eda5..4915b90f 100644 --- a/backend/services/autoFetchMatchResultsService.js +++ b/backend/services/autoFetchMatchResultsService.js @@ -23,10 +23,10 @@ class AutoFetchMatchResultsService { // Find all users with auto-updates enabled const accounts = await MyTischtennis.findAll({ where: { - autoUpdateRatings: true, // Nutze das gleiche Flag - savePassword: true // Must have saved password - }, - attributes: ['id', 'userId', 'email', 'savePassword', 'encryptedPassword', 'accessToken', 'expiresAt', 'cookie'] + autoUpdateRatings: true, + savePassword: true + } + // No attributes restriction — all fields needed for session handling and re-login }); devLog(`Found ${accounts.length} accounts with auto-updates enabled for match results`); @@ -68,30 +68,18 @@ class AutoFetchMatchResultsService { // Check if session is still valid if (!account.accessToken || !account.expiresAt || account.expiresAt < Date.now() / 1000) { - devLog(`Session expired for ${account.email}, attempting re-login`); - - // Try to re-login with stored password - const password = account.getPassword(); - if (!password) { - throw new Error('No stored password available for re-login'); + devLog(`Session expired for ${account.email}, attempting re-login via verifyLogin (incl. Playwright fallback)`); + // verifyLogin handles CAPTCHA via Playwright and persists the session to DB. + await myTischtennisService.verifyLogin(account.userId); + // Reload the account to get the fresh session data written by verifyLogin. + const refreshed = await MyTischtennis.findOne({ where: { userId: account.userId } }); + if (!refreshed?.cookie) { + throw new Error('Re-login via verifyLogin did not produce a valid session'); } - - const loginResult = await myTischtennisClient.login(account.email, password); - if (!loginResult.success) { - if (loginResult.requiresCaptcha) { - throw new Error(`Re-login failed: CAPTCHA erforderlich. Bitte loggen Sie sich einmal direkt auf mytischtennis.de ein, um das CAPTCHA zu lösen.`); - } - throw new Error(`Re-login failed: ${loginResult.error}`); - } - - // Update session data - account.accessToken = loginResult.accessToken; - account.refreshToken = loginResult.refreshToken; - account.expiresAt = loginResult.expiresAt; - account.cookie = loginResult.cookie; - account.savePassword = true; // ensure flag persists when saving - await account.save(); - + account.accessToken = refreshed.accessToken; + account.refreshToken = refreshed.refreshToken; + account.expiresAt = refreshed.expiresAt; + account.cookie = refreshed.cookie; devLog(`Successfully re-logged in for ${account.email}`); } diff --git a/backend/services/myTischtennisService.js b/backend/services/myTischtennisService.js index 8a10cfc8..e427a90d 100644 --- a/backend/services/myTischtennisService.js +++ b/backend/services/myTischtennisService.js @@ -111,9 +111,6 @@ class MyTischtennisService { account.expiresAt = loginResult.expiresAt; account.cookie = loginResult.cookie; account.userData = loginResult.user; - if (loginResult.storageState) { - account.playwrightStorageState = loginResult.storageState; - } // Hole Club-ID und Federation const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie); @@ -130,6 +127,11 @@ class MyTischtennisService { } await account.save(); + + // Save Playwright storage state separately so a missing DB column never breaks the main save. + if (loginResult?.success && loginResult.storageState) { + await this._savePlaywrightStorageState(account, loginResult.storageState); + } } else { // Create new const accountData = { @@ -147,9 +149,6 @@ class MyTischtennisService { accountData.expiresAt = loginResult.expiresAt; accountData.cookie = loginResult.cookie; accountData.userData = loginResult.user; - if (loginResult.storageState) { - accountData.playwrightStorageState = loginResult.storageState; - } // Hole Club-ID const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie); @@ -166,6 +165,10 @@ class MyTischtennisService { account.setPassword(password); await account.save(); } + + if (loginResult?.success && loginResult.storageState) { + await this._savePlaywrightStorageState(account, loginResult.storageState); + } } return { @@ -296,9 +299,6 @@ class MyTischtennisService { account.expiresAt = effectiveLoginResult.expiresAt; account.cookie = effectiveLoginResult.cookie; account.userData = effectiveLoginResult.user; - if (effectiveLoginResult.storageState) { - account.playwrightStorageState = effectiveLoginResult.storageState; - } // Hole Club-ID und Federation const profileResult = await myTischtennisClient.getUserProfile(effectiveLoginResult.cookie); @@ -312,6 +312,11 @@ class MyTischtennisService { } await account.save(); + + // Save Playwright storage state separately so a missing DB column never breaks the main save. + if (effectiveLoginResult.storageState) { + await this._savePlaywrightStorageState(account, effectiveLoginResult.storageState); + } return { success: true, @@ -432,6 +437,22 @@ class MyTischtennisService { console.error('Error logging update attempt:', error); } } + + /** + * Saves the Playwright browser storage state to the account. + * Runs in its own try-catch so a missing DB column never blocks the main session save. + */ + async _savePlaywrightStorageState(account, storageState) { + try { + account.playwrightStorageState = storageState; + await account.save({ fields: ['playwrightStorageState'] }); + console.log('[myTischtennisService] Playwright storage state saved (session cached for future logins)'); + } catch (err) { + // Column likely missing on this server — run: + // ALTER TABLE my_tischtennis ADD COLUMN playwright_storage_state LONGTEXT NULL; + console.warn('[myTischtennisService] Could not save playwright_storage_state (DB column missing?):', err.message); + } + } } export default new MyTischtennisService(); diff --git a/frontend/src/views/TeamManagementView.vue b/frontend/src/views/TeamManagementView.vue index 4459015b..9f068d9c 100644 --- a/frontend/src/views/TeamManagementView.vue +++ b/frontend/src/views/TeamManagementView.vue @@ -1176,6 +1176,7 @@ export default { myTischtennisSuccess.value = ''; try { + const startResponse = await apiClient.post('/mytischtennis/fetch-team-data/async', { clubTeamId: teamToEdit.value.id });