refactor(myTischtennis): streamline session management and enhance login flow

- Updated AutoFetchMatchResultsService to utilize verifyLogin for session re-establishment, improving reliability and handling of CAPTCHA challenges.
- Refactored MyTischtennisService to save Playwright storage state separately, ensuring robustness in session persistence and preventing failures due to missing DB columns.
- Minor adjustments in TeamManagementView to enhance async data fetching logic.
This commit is contained in:
Torsten Schulz (local)
2026-03-05 10:02:43 +01:00
parent 27665a45df
commit dd93755e6b
3 changed files with 46 additions and 36 deletions

View File

@@ -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}`);
}

View File

@@ -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();

View File

@@ -1176,6 +1176,7 @@ export default {
myTischtennisSuccess.value = '';
try {
const startResponse = await apiClient.post('/mytischtennis/fetch-team-data/async', {
clubTeamId: teamToEdit.value.id
});