From 84ff4e126ef4134fd142e89da58453c3ad7541ca Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 3 Nov 2025 12:03:34 +0100 Subject: [PATCH] Enhance MyTischtennisUrlController with ratings update and improve apiLogService truncation limits Added functionality in MyTischtennisUrlController to update (Q)TTR ratings for clubs based on user authentication. Enhanced error handling for ratings updates to provide clearer feedback. Updated apiLogService to increase truncation limits for request and response bodies, accommodating larger API JSON payloads, ensuring better logging accuracy. --- .../controllers/myTischtennisUrlController.js | 14 ++- backend/services/apiLogService.js | 8 +- backend/services/memberService.js | 102 +++++++++++++++--- 3 files changed, 105 insertions(+), 19 deletions(-) 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);