feat(tournament): add cleanup logic for orphaned matches

- Implemented a new method to clean up orphaned matches where at least one player no longer exists, enhancing data integrity in tournament management.
- Added a corresponding route and frontend functionality to trigger the cleanup process, allowing users to easily remove invalid match records.
- Updated localization strings to support the new feature, ensuring clarity in the user interface.
This commit is contained in:
Torsten Schulz (local)
2026-01-31 00:16:23 +01:00
parent 3fc1760b2c
commit 10e6d74d93
6 changed files with 76 additions and 0 deletions

View File

@@ -420,6 +420,19 @@ export const resetMatches = async (req, res) => {
}
};
export const cleanupOrphanedMatches = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId, tournamentId } = req.body;
try {
const result = await tournamentService.cleanupOrphanedMatches(token, clubId, tournamentId);
emitTournamentChanged(clubId, tournamentId);
res.status(200).json(result);
} catch (err) {
console.error(err);
res.status(500).json({ error: err.message });
}
};
export const removeParticipant = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId, tournamentId, participantId } = req.body;

View File

@@ -19,6 +19,7 @@ import {
manualAssignGroups,
resetGroups,
resetMatches,
cleanupOrphanedMatches,
removeParticipant,
updateParticipantSeeded,
setParticipantGaveUp,
@@ -61,6 +62,7 @@ router.put('/participant/:clubId/:tournamentId/:participantId/gave-up', authenti
router.post('/modus', authenticate, setModus);
router.post('/groups/reset', authenticate, resetGroups);
router.post('/matches/reset', authenticate, resetMatches);
router.post('/matches/cleanup-orphaned', authenticate, cleanupOrphanedMatches);
router.put('/groups', authenticate, createGroups);
router.post('/groups/create', authenticate, createGroupsPerClass);
router.post('/groups', authenticate, fillGroups);

View File

@@ -3201,6 +3201,38 @@ Ve // 2. Neues Turnier anlegen
await TournamentMatch.destroy({ where });
}
/**
* Entfernt Matches, bei denen mindestens ein Spieler nicht mehr existiert
* (z.B. gelöscht bevor die Aufräum-Logik beim Teilnehmerlöschen eingeführt wurde).
*/
async cleanupOrphanedMatches(userToken, clubId, tournamentId) {
await checkAccess(userToken, clubId);
const tournament = await Tournament.findByPk(tournamentId);
if (!tournament || tournament.clubId != clubId) {
throw new Error('Turnier nicht gefunden');
}
const members = await TournamentMember.findAll({ where: { tournamentId }, attributes: ['id'] });
const externals = await ExternalTournamentParticipant.findAll({ where: { tournamentId }, attributes: ['id'] });
const validIds = new Set([
...members.map(m => m.id),
...externals.map(e => e.id)
]);
const matches = await TournamentMatch.findAll({ where: { tournamentId } });
let deletedCount = 0;
for (const m of matches) {
const p1Exists = !m.player1Id || validIds.has(m.player1Id);
const p2Exists = !m.player2Id || validIds.has(m.player2Id);
if (!p1Exists || !p2Exists) {
await TournamentResult.destroy({ where: { matchId: m.id } });
await m.destroy();
deletedCount++;
}
}
return { deletedCount };
}
async removeParticipant(userToken, clubId, tournamentId, participantId) {
await checkAccess(userToken, clubId);