diff --git a/backend/controllers/myTischtennisUrlController.js b/backend/controllers/myTischtennisUrlController.js index a5fefe0..33db5e2 100644 --- a/backend/controllers/myTischtennisUrlController.js +++ b/backend/controllers/myTischtennisUrlController.js @@ -1,5 +1,6 @@ import myTischtennisUrlParserService from '../services/myTischtennisUrlParserService.js'; import myTischtennisService from '../services/myTischtennisService.js'; +import MemberService from '../services/memberService.js'; import autoFetchMatchResultsService from '../services/autoFetchMatchResultsService.js'; import apiLogService from '../services/apiLogService.js'; import ClubTeam from '../models/ClubTeam.js'; @@ -383,13 +384,24 @@ class MyTischtennisUrlController { // Don't fail the entire request if table update fails } + // Additionally update (Q)TTR ratings for the club + let ratingsUpdate = null; + try { + // Use already resolved userId instead of authcode to avoid header dependency + const ratingsResult = await MemberService.updateRatingsFromMyTischtennisByUserId(userId, team.clubId); + ratingsUpdate = ratingsResult?.response?.message || `Ratings update status: ${ratingsResult?.status}`; + } catch (ratingsErr) { + ratingsUpdate = 'Ratings update failed: ' + (ratingsErr.message || String(ratingsErr)); + } + res.json({ success: true, message: `${result.fetchedCount} Datensätze abgerufen und verarbeitet`, data: { fetchedCount: result.fetchedCount, teamName: team.name, - tableUpdate: tableUpdateResult + tableUpdate: tableUpdateResult, + ratingsUpdate } }); } catch (error) { diff --git a/backend/services/apiLogService.js b/backend/services/apiLogService.js index bf0f3bc..6cae02b 100644 --- a/backend/services/apiLogService.js +++ b/backend/services/apiLogService.js @@ -22,8 +22,8 @@ class ApiLogService { schedulerJobType = null } = options; - // Truncate long fields - const truncate = (str, maxLen = 10000) => { + // Truncate long fields (raise limits to fit typical API JSON bodies) + const truncate = (str, maxLen = 64000) => { if (!str) return null; const strVal = typeof str === 'string' ? str : JSON.stringify(str); return strVal.length > maxLen ? strVal.substring(0, maxLen) + '... (truncated)' : strVal; @@ -34,8 +34,8 @@ class ApiLogService { method, path, statusCode, - requestBody: truncate(requestBody), - responseBody: truncate(responseBody), + requestBody: truncate(requestBody, 64000), + responseBody: truncate(responseBody, 64000), executionTime, errorMessage: truncate(errorMessage, 5000), ipAddress, diff --git a/backend/services/memberService.js b/backend/services/memberService.js index 1060ffc..49c18bd 100644 --- a/backend/services/memberService.js +++ b/backend/services/memberService.js @@ -1,6 +1,5 @@ import UserClub from "../models/UserClub.js"; -import { checkAccess } from "../utils/userUtils.js"; -import { getUserByToken } from "../utils/userUtils.js"; +import { checkAccess, getUserByToken, hasUserClubAccess } from "../utils/userUtils.js"; import Member from "../models/Member.js"; import path from 'path'; import fs from 'fs'; @@ -141,26 +140,21 @@ class MemberService { } } - async updateRatingsFromMyTischtennis(userToken, clubId) { - await checkAccess(userToken, clubId); - - const user = await getUserByToken(userToken); - + async _updateRatingsInternal(userId, clubId) { const myTischtennisService = (await import('./myTischtennisService.js')).default; const myTischtennisClient = (await import('../clients/myTischtennisClient.js')).default; - try { // 1. myTischtennis-Session abrufen oder Login durchführen let session; try { - session = await myTischtennisService.getSession(user.id); + session = await myTischtennisService.getSession(userId); } catch (sessionError) { console.log('[updateRatingsFromMyTischtennis] - Session invalid, attempting login...', sessionError.message); // Versuche automatischen Login mit gespeicherten Credentials try { - await myTischtennisService.verifyLogin(user.id); - const freshSession = await myTischtennisService.getSession(user.id); + await myTischtennisService.verifyLogin(userId); + const freshSession = await myTischtennisService.getSession(userId); session = { cookie: freshSession.cookie, accessToken: freshSession.accessToken, @@ -183,7 +177,7 @@ class MemberService { } } - const account = await myTischtennisService.getAccount(user.id); + const account = await myTischtennisService.getAccount(userId); if (!account) { console.error('[updateRatingsFromMyTischtennis] - No account found!'); @@ -193,7 +187,7 @@ class MemberService { message: 'Kein myTischtennis-Account gefunden.', updated: 0, errors: [], - debug: { userId: user.id } + debug: { userId } } }; } @@ -219,22 +213,80 @@ class MemberService { }; } - // 2. Ranglisten vom Verein abrufen + // 2. Ranglisten vom Verein abrufen (Logging hinzufügen) // TTR (aktuell) + try { + await (await import('./apiLogService.js')).default.logRequest({ + userId, + method: 'GET', + path: `/rankings/andro-rangliste?club=${account.clubId}&fed=${account.fedNickname}¤t=yes`, + statusCode: null, + requestBody: null, + responseBody: null, + executionTime: null, + errorMessage: 'TTR-Rangliste wird abgerufen...', + logType: 'api_request', + schedulerJobType: 'mytischtennis_rankings' + }); + } catch {} + const ttrStart = Date.now(); const rankingsCurrent = await myTischtennisClient.getClubRankings( session.cookie, account.clubId, account.fedNickname, 'yes' ); + try { + await (await import('./apiLogService.js')).default.logRequest({ + userId, + method: 'GET', + path: `/rankings/andro-rangliste?club=${account.clubId}&fed=${account.fedNickname}¤t=yes`, + statusCode: rankingsCurrent.success ? 200 : 500, + requestBody: null, + responseBody: JSON.stringify(rankingsCurrent), + executionTime: Date.now() - ttrStart, + errorMessage: rankingsCurrent.success ? null : 'TTR-Rangliste Fehler', + logType: 'api_request', + schedulerJobType: 'mytischtennis_rankings' + }); + } catch {} // QTTR (Quartalswert) + try { + await (await import('./apiLogService.js')).default.logRequest({ + userId, + method: 'GET', + path: `/rankings/andro-rangliste?club=${account.clubId}&fed=${account.fedNickname}¤t=no`, + statusCode: null, + requestBody: null, + responseBody: null, + executionTime: null, + errorMessage: 'QTTR-Rangliste wird abgerufen...', + logType: 'api_request', + schedulerJobType: 'mytischtennis_rankings' + }); + } catch {} + const qttrStart = Date.now(); const rankingsQuarter = await myTischtennisClient.getClubRankings( session.cookie, account.clubId, account.fedNickname, 'no' ); + try { + await (await import('./apiLogService.js')).default.logRequest({ + userId, + method: 'GET', + path: `/rankings/andro-rangliste?club=${account.clubId}&fed=${account.fedNickname}¤t=no`, + statusCode: rankingsQuarter.success ? 200 : 500, + requestBody: null, + responseBody: JSON.stringify(rankingsQuarter), + executionTime: Date.now() - qttrStart, + errorMessage: rankingsQuarter.success ? null : 'QTTR-Rangliste Fehler', + logType: 'api_request', + schedulerJobType: 'mytischtennis_rankings' + }); + } catch {} if (!rankingsCurrent.success) { return { @@ -367,6 +419,28 @@ class MemberService { } } + async updateRatingsFromMyTischtennis(userToken, clubId) { + await checkAccess(userToken, clubId); + const user = await getUserByToken(userToken); + return this._updateRatingsInternal(user.id, clubId); + } + + async updateRatingsFromMyTischtennisByUserId(userId, clubId) { + // Zugriff prüfen: User muss Zugriff auf den Club haben + const allowed = await hasUserClubAccess(userId, clubId); + if (!allowed) { + return { + status: 403, + response: { + message: 'noaccess', + updated: 0, + errors: ['noaccess'] + } + }; + } + return this._updateRatingsInternal(userId, clubId); + } + async rotateMemberImage(userToken, clubId, memberId, direction) { try { await checkAccess(userToken, clubId);