diff --git a/backend/controllers/myTischtennisUrlController.js b/backend/controllers/myTischtennisUrlController.js index 802aae9..c35ad54 100644 --- a/backend/controllers/myTischtennisUrlController.js +++ b/backend/controllers/myTischtennisUrlController.js @@ -1,6 +1,7 @@ import myTischtennisUrlParserService from '../services/myTischtennisUrlParserService.js'; import myTischtennisService from '../services/myTischtennisService.js'; import autoFetchMatchResultsService from '../services/autoFetchMatchResultsService.js'; +import apiLogService from '../services/apiLogService.js'; import ClubTeam from '../models/ClubTeam.js'; import League from '../models/League.js'; import Season from '../models/Season.js'; @@ -228,6 +229,10 @@ class MyTischtennisUrlController { throw new HttpError(400, 'clubTeamId is required'); } + if (!userIdOrEmail) { + throw new HttpError(401, 'User-ID fehlt. Bitte melden Sie sich an.'); + } + // Get actual user ID (userid header might be email address) let userId = userIdOrEmail; if (isNaN(userIdOrEmail)) { @@ -246,17 +251,63 @@ class MyTischtennisUrlController { try { session = await myTischtennisService.getSession(userId); console.log('Session found:', !!session); + if (session) { + console.log('Session details:', { + hasCookie: !!session.cookie, + hasAccessToken: !!session.accessToken, + expiresAt: session.expiresAt ? new Date(session.expiresAt * 1000).toISOString() : null + }); + } } catch (sessionError) { console.log('Session invalid, attempting login...', sessionError.message); + console.error('Session error details:', { + message: sessionError.message, + stack: sessionError.stack, + name: sessionError.name + }); // Versuche automatischen Login mit gespeicherten Credentials try { - await myTischtennisService.verifyLogin(userId); + console.log('Attempting automatic login for userId:', userId); + + // Check if account exists and has password + const accountCheck = await myTischtennisService.getAccount(userId); + if (!accountCheck) { + throw new Error('MyTischtennis-Account nicht gefunden'); + } + + console.log('Account found:', { + email: accountCheck.email, + hasPassword: !!accountCheck.encryptedPassword, + hasAccessToken: !!accountCheck.accessToken, + hasCookie: !!accountCheck.cookie + }); + + if (!accountCheck.encryptedPassword) { + throw new Error('Kein Passwort gespeichert. Bitte melden Sie sich in den MyTischtennis-Einstellungen an und speichern Sie Ihr Passwort.'); + } + + console.log('Calling verifyLogin...'); + const verifyResult = await myTischtennisService.verifyLogin(userId); + console.log('verifyLogin result:', verifyResult); + session = await myTischtennisService.getSession(userId); - console.log('Automatic login successful'); + console.log('Automatic login successful, session:', { + hasCookie: !!session?.cookie, + hasAccessToken: !!session?.accessToken + }); } catch (loginError) { - console.error('Automatic login failed:', loginError.message); - throw new HttpError(401, 'MyTischtennis-Session abgelaufen und automatischer Login fehlgeschlagen. Bitte melden Sie sich in den MyTischtennis-Einstellungen an.'); + console.error('Automatic login failed - DETAILED ERROR:', { + message: loginError.message, + stack: loginError.stack, + name: loginError.name, + userId: userId, + response: loginError.response?.data, + status: loginError.response?.status + }); + + const errorMessage = loginError.message || 'Automatischer Login fehlgeschlagen'; + throw new HttpError(401, `MyTischtennis-Session abgelaufen und automatischer Login fehlgeschlagen: ${errorMessage}. Bitte melden Sie sich in den MyTischtennis-Einstellungen an.`); } } @@ -291,7 +342,7 @@ class MyTischtennisUrlController { }); if (!team) { - throw new HttpError(404, 'Team not found'); + throw new HttpError(404, `Team mit ID ${clubTeamId} nicht gefunden`); } console.log('Team data:', { @@ -309,11 +360,74 @@ class MyTischtennisUrlController { } : null }); - if (!team.myTischtennisTeamId || !team.league || !team.league.myTischtennisGroupId) { - throw new HttpError(400, 'Team is not configured for myTischtennis'); + // Verbesserte Validierung mit detaillierten Fehlermeldungen + if (!team.myTischtennisTeamId) { + throw new HttpError(400, `Team "${team.name}" (interne ID: ${team.id}) ist nicht für myTischtennis konfiguriert: myTischtennisTeamId fehlt. Bitte konfigurieren Sie das Team zuerst über die MyTischtennis-URL.`); + } + + // Stelle sicher, dass die myTischtennisTeamId auch wirklich verwendet wird + console.log(`Verwende myTischtennisTeamId: ${team.myTischtennisTeamId} (nicht die interne clubTeamId: ${team.id})`); + + if (!team.league) { + throw new HttpError(400, 'Team ist keiner Liga zugeordnet. Bitte ordnen Sie das Team einer Liga zu.'); + } + + if (!team.league.myTischtennisGroupId) { + throw new HttpError(400, 'Liga ist nicht für myTischtennis konfiguriert: myTischtennisGroupId fehlt. Bitte konfigurieren Sie die Liga zuerst über die MyTischtennis-URL.'); + } + + // Validate season before proceeding + if (!team.league.season || !team.league.season.season) { + throw new HttpError(400, 'Liga ist keiner Saison zugeordnet. Bitte ordnen Sie die Liga einer Saison zu.'); + } + + // Build the URL that will be used - do this early so we can log it even if errors occur + const seasonFull = team.league.season.season; + const seasonParts = seasonFull.split('/'); + const seasonShort = seasonParts.length === 2 + ? `${seasonParts[0].slice(-2)}/${seasonParts[1].slice(-2)}` + : seasonFull; + const seasonStr = seasonShort.replace('/', '--'); + const teamnameEncoded = encodeURIComponent(team.name.replace(/\s/g, '_')); + const myTischtennisUrl = `https://www.mytischtennis.de/click-tt/${team.league.association}/${seasonStr}/ligen/${team.league.groupname}/gruppe/${team.league.myTischtennisGroupId}/mannschaft/${team.myTischtennisTeamId}/${teamnameEncoded}/spielerbilanzen/gesamt`; + + // Log the request to myTischtennis BEFORE making the call + // This ensures we always see what WILL BE sent, even if the call fails + const requestStartTime = Date.now(); + console.log('=== ABOUT TO FETCH FROM MYTISCHTENNIS ==='); + console.log('URL:', myTischtennisUrl); + console.log('myTischtennisTeamId:', team.myTischtennisTeamId); + console.log('clubTeamId:', team.id); + + try { + await apiLogService.logRequest({ + userId: account.userId, + method: 'GET', + path: myTischtennisUrl.replace('https://www.mytischtennis.de', ''), + statusCode: null, + requestBody: JSON.stringify({ + url: myTischtennisUrl, + myTischtennisTeamId: team.myTischtennisTeamId, + clubTeamId: team.id, + teamName: team.name, + leagueName: team.league.name, + association: team.league.association, + groupId: team.league.myTischtennisGroupId, + groupname: team.league.groupname, + season: seasonFull + }), + responseBody: null, + executionTime: null, + errorMessage: 'Request wird ausgeführt...', + logType: 'api_request', + schedulerJobType: 'mytischtennis_fetch' + }); + } catch (logError) { + console.error('Error logging request (non-critical):', logError); } // Fetch data for this specific team + // Note: fetchTeamResults will also log and update with actual response const result = await autoFetchMatchResultsService.fetchTeamResults( { userId: account.userId, @@ -326,32 +440,63 @@ class MyTischtennisUrlController { team ); - // Also fetch and update league table data - let tableUpdateResult = null; + // Also fetch and update league table data + let tableUpdateResult = null; try { await autoFetchMatchResultsService.fetchAndUpdateLeagueTable(account.userId, team.league.id); - tableUpdateResult = 'League table updated successfully'; - console.log('✓ League table updated for league:', team.league.id); - } catch (error) { - console.error('Error fetching league table data:', error); - tableUpdateResult = 'League table update failed: ' + error.message; - // Don't fail the entire request if table update fails - } - - res.json({ - success: true, - message: `${result.fetchedCount} Datensätze abgerufen und verarbeitet`, - data: { - fetchedCount: result.fetchedCount, - teamName: team.name, - tableUpdate: tableUpdateResult + tableUpdateResult = 'League table updated successfully'; + console.log('✓ League table updated for league:', team.league.id); + } catch (error) { + console.error('Error fetching league table data:', error); + tableUpdateResult = 'League table update failed: ' + error.message; + // Don't fail the entire request if table update fails } - }); - } catch (error) { - console.error('Error in fetchTeamData:', error); - console.error('Error stack:', error.stack); - next(error); - } + + res.json({ + success: true, + message: `${result.fetchedCount} Datensätze abgerufen und verarbeitet`, + data: { + fetchedCount: result.fetchedCount, + teamName: team.name, + tableUpdate: tableUpdateResult + } + }); + } catch (error) { + console.error('Error in fetchTeamData:', error); + console.error('Error stack:', error.stack); + + // Update log with error information if we got far enough to build the URL + if (typeof myTischtennisUrl !== 'undefined' && account && team) { + const requestExecutionTime = Date.now() - requestStartTime; + try { + await apiLogService.logRequest({ + userId: account.userId, + method: 'GET', + path: myTischtennisUrl.replace('https://www.mytischtennis.de', ''), + statusCode: 0, + requestBody: JSON.stringify({ + url: myTischtennisUrl, + myTischtennisTeamId: team.myTischtennisTeamId, + clubTeamId: team.id, + teamName: team.name, + leagueName: team.league?.name, + association: team.league?.association, + groupname: team.league?.groupname, + groupId: team.league?.myTischtennisGroupId + }), + responseBody: null, + executionTime: requestExecutionTime, + errorMessage: error.message || String(error), + logType: 'api_request', + schedulerJobType: 'mytischtennis_fetch' + }); + } catch (logError) { + console.error('Error logging failed request:', logError); + } + } + + next(error); + } } /** diff --git a/backend/middleware/requestLoggingMiddleware.js b/backend/middleware/requestLoggingMiddleware.js index 2c69b5b..9a4dfaf 100644 --- a/backend/middleware/requestLoggingMiddleware.js +++ b/backend/middleware/requestLoggingMiddleware.js @@ -41,8 +41,8 @@ export const requestLoggingMiddleware = async (req, res, next) => { const ipAddress = req.ip || req.connection.remoteAddress || req.headers['x-forwarded-for']; const path = req.path || req.url; + // Nur myTischtennis-Requests loggen // Skip logging for non-data endpoints (Status-Checks, Health-Checks, etc.) - // Nur Daten-Abrufe von API-Endpunkten werden geloggt // Exclude any endpoint containing 'status' or root paths if ( path.includes('/status') || @@ -54,6 +54,11 @@ export const requestLoggingMiddleware = async (req, res, next) => { return; } + // Nur myTischtennis-Endpunkte loggen (z.B. /api/mytischtennis/*) + if (!path.includes('/mytischtennis')) { + return; + } + // Get user ID if available (wird von authMiddleware gesetzt) const userId = req.user?.id || null; diff --git a/backend/services/apiLogService.js b/backend/services/apiLogService.js index 210af44..94d30c2 100644 --- a/backend/services/apiLogService.js +++ b/backend/services/apiLogService.js @@ -102,7 +102,13 @@ class ApiLogService { } if (path) { - where.path = { [Op.like]: `%${path}%` }; + // Handle "NOT:" prefix for excluding paths (e.g., "NOT:/mytischtennis") + if (path.startsWith('NOT:')) { + const excludePath = path.substring(4); + where.path = { [Op.not]: { [Op.like]: `%${excludePath}%` } }; + } else { + where.path = { [Op.like]: `%${path}%` }; + } } if (statusCode !== null) { diff --git a/backend/services/autoFetchMatchResultsService.js b/backend/services/autoFetchMatchResultsService.js index 212e3ab..09e5b85 100644 --- a/backend/services/autoFetchMatchResultsService.js +++ b/backend/services/autoFetchMatchResultsService.js @@ -1,5 +1,6 @@ import myTischtennisService from './myTischtennisService.js'; import myTischtennisFetchLogService from './myTischtennisFetchLogService.js'; +import apiLogService from './apiLogService.js'; import myTischtennisClient from '../clients/myTischtennisClient.js'; import MyTischtennis from '../models/MyTischtennis.js'; import ClubTeam from '../models/ClubTeam.js'; @@ -182,6 +183,14 @@ class AutoFetchMatchResultsService { * Fetch results for a specific team */ async fetchTeamResults(account, team) { + // Validate required data + if (!team.league) { + throw new Error('Team league is required'); + } + if (!team.league.season || !team.league.season.season) { + throw new Error('Team league season is required'); + } + const league = team.league; const season = league.season; @@ -207,26 +216,83 @@ class AutoFetchMatchResultsService { const playerStatsUrl = `https://www.mytischtennis.de/click-tt/${league.association}/${seasonStr}/ligen/${league.groupname}/gruppe/${league.myTischtennisGroupId}/mannschaft/${team.myTischtennisTeamId}/${teamnameEncoded}/spielerbilanzen/gesamt?_data=routes%2Fclick-tt%2B%2F%24association%2B%2F%24season%2B%2F%24type%2B%2F%28%24groupname%29.gruppe.%24urlid_.mannschaft.%24teamid.%24teamname%2B%2Fspielerbilanzen.%24filter`; devLog(`Fetching player stats from: ${playerStatsUrl}`); + + const fetchStartTime = Date.now(); + let playerStatsResponse = null; + let fetchExecutionTime = 0; + let responseStatus = 0; + let responseBodyData = null; + let fetchError = null; - const playerStatsResponse = await fetch(playerStatsUrl, { - headers: { - 'Cookie': account.cookie || '', - 'Authorization': `Bearer ${account.accessToken}`, - 'Accept': 'application/json' + try { + playerStatsResponse = await fetch(playerStatsUrl, { + headers: { + 'Cookie': account.cookie || '', + 'Authorization': `Bearer ${account.accessToken}`, + 'Accept': 'application/json' + } + }); + + fetchExecutionTime = Date.now() - fetchStartTime; + responseStatus = playerStatsResponse.status; + + if (playerStatsResponse.ok) { + const playerStatsData = await playerStatsResponse.json(); + responseBodyData = playerStatsData; + + // Log complete response for debugging + console.log('=== PLAYER STATS RESPONSE START ==='); + console.log(JSON.stringify(playerStatsData, null, 2)); + console.log('=== PLAYER STATS RESPONSE END ==='); + + const playerCount = await this.processTeamData(team, playerStatsData); + totalProcessed += playerCount; + devLog(`Processed ${playerCount} player statistics`); + } else { + // Read error response body + try { + responseBodyData = await playerStatsResponse.text(); + } catch (e) { + responseBodyData = `Error reading response: ${e.message}`; + } } - }); - - if (playerStatsResponse.ok) { - const playerStatsData = await playerStatsResponse.json(); - - // Log complete response for debugging - console.log('=== PLAYER STATS RESPONSE START ==='); - console.log(JSON.stringify(playerStatsData, null, 2)); - console.log('=== PLAYER STATS RESPONSE END ==='); - - const playerCount = await this.processTeamData(team, playerStatsData); - totalProcessed += playerCount; - devLog(`Processed ${playerCount} player statistics`); + } catch (error) { + fetchExecutionTime = Date.now() - fetchStartTime; + fetchError = error.message || String(error); + responseStatus = 0; + console.error('Error fetching from myTischtennis:', error); + } + + // Log external request to myTischtennis - IMMER, auch bei Fehlern + try { + await apiLogService.logRequest({ + userId: account.userId, + method: 'GET', + path: playerStatsUrl.replace('https://www.mytischtennis.de', ''), // Relative path for consistency + statusCode: responseStatus || (fetchError ? 0 : 200), + requestBody: JSON.stringify({ + url: playerStatsUrl, + myTischtennisTeamId: team.myTischtennisTeamId, + clubTeamId: team.id, + teamName: team.name, + association: league.association, + groupname: league.groupname, + groupId: league.myTischtennisGroupId + }), + responseBody: responseBodyData ? (typeof responseBodyData === 'string' ? responseBodyData.substring(0, 10000) : JSON.stringify(responseBodyData).substring(0, 10000)) : null, + executionTime: fetchExecutionTime, + errorMessage: fetchError || (playerStatsResponse && !playerStatsResponse.ok ? `myTischtennis API returned ${responseStatus}` : null), + logType: 'api_request', + schedulerJobType: 'mytischtennis_fetch' + }); + } catch (logError) { + console.error('Error logging myTischtennis request:', logError); + // Don't throw - logging failures shouldn't break the main operation + } + + // Re-throw error if fetch failed + if (fetchError) { + throw new Error(`Failed to fetch from myTischtennis: ${fetchError}`); } // Note: Match results are already included in the player stats response above diff --git a/frontend/src/views/LogsView.vue b/frontend/src/views/LogsView.vue index eda727c..5cf1786 100644 --- a/frontend/src/views/LogsView.vue +++ b/frontend/src/views/LogsView.vue @@ -7,6 +7,15 @@