diff --git a/backend/services/officialTournamentService.js b/backend/services/officialTournamentService.js index 494073a3..a7c420e2 100755 --- a/backend/services/officialTournamentService.js +++ b/backend/services/officialTournamentService.js @@ -35,6 +35,14 @@ class OfficialTournamentService { return []; } + normalizeCompetitionName(value) { + return String(value || '') + .toLocaleLowerCase('de') + .replace(/mädchen\s*(\d{1,2})/g, 'm$1') + .replace(/jungen\s*(\d{1,2})/g, 'j$1') + .replace(/[^a-z0-9]/g, ''); + } + async repairTournamentFromSource(clubId, tournament) { let sourceUrl = tournament.sourceUrl; if (!sourceUrl) { @@ -47,19 +55,35 @@ class OfficialTournamentService { const source = new URL(sourceUrl); if (!['www.mytischtennis.de', 'mytischtennis.de'].includes(source.hostname)) return 0; const response = await axios.get(sourceUrl, { timeout: 30000, headers: { 'User-Agent': 'Trainingstagebuch/1.0 (+turnierdetails)' } }); - const competitions = this.extractJsonArray(response.data, 'competitions') - .filter((competition) => competition?.name) + const competitions = this.extractJsonArray(response.data, 'competition_infos') + .filter((competition) => competition?.competition_name) .map((competition) => ({ - ageClassCompetition: String(competition.name).slice(0, 255), - cutoffDate: competition.fed_rank_remarks ? String(competition.fed_rank_remarks).slice(0, 255) : null, + ageClassCompetition: String(competition.competition_name).slice(0, 255), + startTime: [competition.formattedStartDate, String(competition.formattedStartTime || '').replace(/\s*Uhr$/i, '')].filter(Boolean).join(', ') || null, + registrationDeadlineDate: competition.formattedRegistrationEnd || null, + registrationDeadlineOnline: competition.formattedRegistrationEnd || null, + cutoffDate: competition.competition_fed_rank_remarks ? String(competition.competition_fed_rank_remarks).slice(0, 255) : null, + ttrRelevant: competition.ttr_relevant ? 'Ja' : 'Nein', + openTo: Array.isArray(competition.open_for) ? competition.open_for.join(', ') : null, + preliminaryRound: competition.competition_playmode || null, + maxParticipants: competition.competition_player_capacity_total == null ? null : String(competition.competition_player_capacity_total), + entryFee: competition.competition_fee == null ? null : `${competition.competition_fee}${competition.competition_fee_currency === 'euro' ? '€' : ''}`, })); if (!competitions.length) return 0; - const existing = await OfficialCompetition.findAll({ where: { tournamentId: tournament.id }, attributes: ['ageClassCompetition'] }); - const existingNames = new Set(existing.map((competition) => competition.ageClassCompetition)); - const missing = competitions.filter((competition) => !existingNames.has(competition.ageClassCompetition)); - if (missing.length) await OfficialCompetition.bulkCreate(missing.map((competition) => ({ tournamentId: tournament.id, ...competition }))); - return missing.length; + const existing = await OfficialCompetition.findAll({ where: { tournamentId: tournament.id } }); + const byName = new Map(existing.map((competition) => [this.normalizeCompetitionName(competition.ageClassCompetition), competition])); + let repaired = 0; + for (const competition of competitions) { + const existingCompetition = byName.get(this.normalizeCompetitionName(competition.ageClassCompetition)); + if (existingCompetition) { + await existingCompetition.update(competition); + } else { + await OfficialCompetition.create({ tournamentId: tournament.id, ...competition }); + } + repaired += 1; + } + return repaired; } async saveTournamentSuggestion(clubId, suggestionId) { @@ -173,7 +197,8 @@ class OfficialTournamentService { if (!t) return null; let comps = await OfficialCompetition.findAll({ where: { tournamentId: id } }); - if (!comps.length) { + const needsRepair = !comps.length || comps.some((competition) => !competition.startTime || competition.entryFee == null); + if (needsRepair) { try { await this.repairTournamentFromSource(clubId, t); comps = await OfficialCompetition.findAll({ where: { tournamentId: id } });