diff --git a/backend/config.js b/backend/config.js index 3910300..2db1943 100644 --- a/backend/config.js +++ b/backend/config.js @@ -1,13 +1,18 @@ import dotenv from 'dotenv'; +import path from 'path'; +import { fileURLToPath } from 'url'; -dotenv.config(); +// Ensure .env is loaded from the backend folder (not dependent on process.cwd()) +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +dotenv.config({ path: path.resolve(__dirname, '.env') }); export const development = { username: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || 'hitomisan', database: process.env.DB_NAME || 'trainingdiary', - host: process.env.DB_HOST, - dialect: process.env.DB_DIALECT, + host: process.env.DB_HOST || 'localhost', + dialect: process.env.DB_DIALECT || 'mysql', define: { freezeTableName: true, underscored: true, diff --git a/backend/controllers/myTischtennisUrlController.js b/backend/controllers/myTischtennisUrlController.js index c35ad54..88a22f1 100644 --- a/backend/controllers/myTischtennisUrlController.js +++ b/backend/controllers/myTischtennisUrlController.js @@ -54,7 +54,6 @@ class MyTischtennisUrlController { ); } } catch (error) { - console.error('Error fetching additional team data:', error); // Continue with parsed data only } } @@ -107,7 +106,6 @@ class MyTischtennisUrlController { account.accessToken ); } catch (error) { - console.error('Error fetching team data:', error); } } @@ -245,67 +243,26 @@ class MyTischtennisUrlController { } // Get myTischtennis session (similar to memberService.updateRatingsFromMyTischtennis) - console.log('Fetching session for userId:', userId, '(from header:', userIdOrEmail, ')'); let session; 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 { - 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); - + await myTischtennisService.verifyLogin(userId); session = await myTischtennisService.getSession(userId); - console.log('Automatic login successful, session:', { - hasCookie: !!session?.cookie, - hasAccessToken: !!session?.accessToken - }); } catch (loginError) { - 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.`); } @@ -318,13 +275,7 @@ class MyTischtennisUrlController { throw new HttpError(404, 'MyTischtennis-Account nicht verknüpft. Bitte verknüpfen Sie Ihren Account in den MyTischtennis-Einstellungen.'); } - console.log('Using session:', { - email: account.email, - hasCookie: !!session.cookie, - hasAccessToken: !!session.accessToken, - expiresAt: new Date(session.expiresAt * 1000) - }); - + // Get team with league and season const team = await ClubTeam.findByPk(clubTeamId, { include: [ @@ -345,29 +296,11 @@ class MyTischtennisUrlController { throw new HttpError(404, `Team mit ID ${clubTeamId} nicht gefunden`); } - console.log('Team data:', { - id: team.id, - name: team.name, - myTischtennisTeamId: team.myTischtennisTeamId, - hasLeague: !!team.league, - leagueData: team.league ? { - id: team.league.id, - name: team.league.name, - myTischtennisGroupId: team.league.myTischtennisGroupId, - association: team.league.association, - groupname: team.league.groupname, - hasSeason: !!team.league.season - } : null - }); - // 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.'); } @@ -394,11 +327,6 @@ class MyTischtennisUrlController { // 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, @@ -423,7 +351,7 @@ class MyTischtennisUrlController { schedulerJobType: 'mytischtennis_fetch' }); } catch (logError) { - console.error('Error logging request (non-critical):', logError); + // Silent fail - logging errors shouldn't break the request } // Fetch data for this specific team @@ -445,9 +373,7 @@ class MyTischtennisUrlController { 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 } @@ -462,8 +388,6 @@ class MyTischtennisUrlController { } }); } 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) { @@ -491,7 +415,7 @@ class MyTischtennisUrlController { schedulerJobType: 'mytischtennis_fetch' }); } catch (logError) { - console.error('Error logging failed request:', logError); + // Silent fail - logging errors shouldn't break the request } } diff --git a/backend/services/apiLogService.js b/backend/services/apiLogService.js index 94d30c2..bf0f3bc 100644 --- a/backend/services/apiLogService.js +++ b/backend/services/apiLogService.js @@ -54,14 +54,37 @@ class ApiLogService { */ async logSchedulerExecution(jobType, success, message, executionTime = null, errorMessage = null) { try { + // Allow optional counts object in message by accepting message as string or object + const truncate = (str, maxLen = 10000) => { + if (!str) return null; + const strVal = typeof str === 'string' ? str : JSON.stringify(str); + return strVal.length > maxLen ? strVal.substring(0, maxLen) + '... (truncated)' : strVal; + }; + + // If message is an object with details, keep it; otherwise wrap into an object + let responseObj = null; + try { + if (typeof message === 'object' && message !== null) { + responseObj = message; + } else { + responseObj = { message }; + } + } catch (e) { + responseObj = { message: String(message) }; + } + + // If executionTime or errorMessage present, add to response object for visibility + if (executionTime !== null) responseObj.executionTime = executionTime; + if (errorMessage) responseObj.errorMessage = errorMessage; + await ApiLog.create({ userId: null, method: 'SCHEDULER', path: `/scheduler/${jobType}`, statusCode: success ? 200 : 500, - responseBody: message, + responseBody: truncate(JSON.stringify(responseObj)), executionTime, - errorMessage, + errorMessage: truncate(errorMessage, 5000), logType: 'scheduler', schedulerJobType: jobType }); diff --git a/backend/services/autoFetchMatchResultsService.js b/backend/services/autoFetchMatchResultsService.js index 09e5b85..170ddf5 100644 --- a/backend/services/autoFetchMatchResultsService.js +++ b/backend/services/autoFetchMatchResultsService.js @@ -36,14 +36,20 @@ class AutoFetchMatchResultsService { return; } - // Process each account + // Process each account and collect summaries + const summaries = []; for (const account of accounts) { - await this.processAccount(account); + const summary = await this.processAccount(account); + summaries.push({ userId: account.userId, ...summary }); } devLog('Automatic match results fetch completed'); + // Return overall summary including per-account counts + const totalFetched = summaries.reduce((acc, s) => acc + (s.fetchedCount || 0), 0); + return { success: true, totalFetched, summaries }; } catch (error) { console.error('Error in automatic match results fetch:', error); + throw error; } } @@ -85,13 +91,13 @@ class AutoFetchMatchResultsService { devLog(`Successfully re-logged in for ${account.email}`); } - // Perform match results fetch - const fetchResult = await this.fetchMatchResults(account); - fetchedCount = fetchResult.fetchedCount || 0; + // Perform match results fetch + const fetchResult = await this.fetchMatchResults(account); + fetchedCount = fetchResult.fetchedCount || 0; - success = true; - message = `Successfully fetched ${fetchedCount} match results`; - devLog(`Fetched ${fetchedCount} match results for ${account.email}`); + success = true; + message = `Successfully fetched ${fetchedCount} match results`; + devLog(`Fetched ${fetchedCount} match results for ${account.email}`); } catch (error) { success = false; @@ -116,7 +122,10 @@ class AutoFetchMatchResultsService { } ); - devLog(`Match results fetch for ${account.email}: ${success ? 'SUCCESS' : 'FAILED'} (${executionTime}ms)`); + devLog(`Match results fetch for ${account.email}: ${success ? 'SUCCESS' : 'FAILED'} (${executionTime}ms)`); + + // Return a summary for scheduler + return { success, message, fetchedCount, errorDetails, executionTime }; } /** @@ -236,14 +245,12 @@ class AutoFetchMatchResultsService { fetchExecutionTime = Date.now() - fetchStartTime; responseStatus = playerStatsResponse.status; - if (playerStatsResponse.ok) { + 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 ==='); + // Avoid dumping full JSON to console; use devLog for compact info + devLog(`Received player stats for team ${team.name} - balancesheet entries: ${Array.isArray(playerStatsData.data?.balancesheet) ? playerStatsData.data.balancesheet.length : 0}`); const playerCount = await this.processTeamData(team, playerStatsData); totalProcessed += playerCount; @@ -824,7 +831,7 @@ class AutoFetchMatchResultsService { // Fetch table data from MyTischtennis const tableUrl = `https://www.mytischtennis.de/click-tt/${league.association}/${seasonStr}/ligen/${league.groupname}/gruppe/${league.myTischtennisGroupId}/tabelle/gesamt?_data=routes%2Fclick-tt%2B%2F%24association%2B%2F%24season%2B%2F%24type%2B%2F%24groupname.gruppe.%24urlid%2B%2Ftabelle.%24filter`; - console.log(`[fetchAndUpdateLeagueTable] Fetching table from URL: ${tableUrl}`); + devLog(`[fetchAndUpdateLeagueTable] Fetching table from URL: ${tableUrl}`); const response = await myTischtennisClient.authenticatedRequest(tableUrl, session.cookie, { method: 'GET' }); diff --git a/backend/services/autoUpdateRatingsService.js b/backend/services/autoUpdateRatingsService.js index 0900cbb..2f58884 100644 --- a/backend/services/autoUpdateRatingsService.js +++ b/backend/services/autoUpdateRatingsService.js @@ -29,12 +29,16 @@ class AutoUpdateRatingsService { return; } - // Process each account + // Process each account and collect summaries + const summaries = []; for (const account of accounts) { - await this.processAccount(account); + const summary = await this.processAccount(account); + summaries.push({ userId: account.userId, ...summary }); } devLog('Automatic rating updates completed'); + const totalUpdated = summaries.reduce((acc, s) => acc + (s.updatedCount || 0), 0); + return { success: true, totalUpdated, summaries }; } catch (error) { console.error('Error in automatic rating updates:', error); } @@ -118,6 +122,9 @@ class AutoUpdateRatingsService { updatedCount, executionTime ); + + // Return summary for scheduler + return { success, message, updatedCount, errorDetails, executionTime }; } /** diff --git a/backend/services/schedulerService.js b/backend/services/schedulerService.js index 76aa87f..439b254 100644 --- a/backend/services/schedulerService.js +++ b/backend/services/schedulerService.js @@ -33,14 +33,16 @@ class SchedulerService { let errorMessage = null; try { - await autoUpdateRatingsService.executeAutomaticUpdates(); + // Let the service return details including counts if available + const result = await autoUpdateRatingsService.executeAutomaticUpdates(); const executionTime = Date.now() - startTime; success = true; - message = 'Rating updates completed successfully'; + // result may include updatedCount or a summary object + const messageObj = result && typeof result === 'object' ? result : { message: 'Rating updates completed successfully' }; console.log(`[${new Date().toISOString()}] CRON: Rating updates completed successfully`); - // Log to ApiLog - await apiLogService.logSchedulerExecution('rating_updates', true, message, executionTime, null); + // Log to ApiLog with rich details + await apiLogService.logSchedulerExecution('rating_updates', true, messageObj, executionTime, null); } catch (error) { const executionTime = Date.now() - startTime; success = false; @@ -49,7 +51,7 @@ class SchedulerService { console.error('Stack trace:', error.stack); // Log to ApiLog - await apiLogService.logSchedulerExecution('rating_updates', false, 'Rating updates failed', executionTime, errorMessage); + await apiLogService.logSchedulerExecution('rating_updates', false, { message: 'Rating updates failed' }, executionTime, errorMessage); } }, { scheduled: false, // Don't start automatically @@ -72,14 +74,15 @@ class SchedulerService { let errorMessage = null; try { - await autoFetchMatchResultsService.executeAutomaticFetch(); + // Execute and capture returned summary (should include counts) + const result = await autoFetchMatchResultsService.executeAutomaticFetch(); const executionTime = Date.now() - startTime; success = true; - message = 'Match results fetch completed successfully'; + const messageObj = result && typeof result === 'object' ? result : { message: 'Match results fetch completed successfully' }; console.log(`[${new Date().toISOString()}] CRON: Match results fetch completed successfully`); - // Log to ApiLog - await apiLogService.logSchedulerExecution('match_results', true, message, executionTime, null); + // Log to ApiLog with rich details (including counts if present) + await apiLogService.logSchedulerExecution('match_results', true, messageObj, executionTime, null); } catch (error) { const executionTime = Date.now() - startTime; success = false; @@ -88,7 +91,7 @@ class SchedulerService { console.error('Stack trace:', error.stack); // Log to ApiLog - await apiLogService.logSchedulerExecution('match_results', false, 'Match results fetch failed', executionTime, errorMessage); + await apiLogService.logSchedulerExecution('match_results', false, { message: 'Match results fetch failed' }, executionTime, errorMessage); } }, { scheduled: false, // Don't start automatically