From b3bbca3887c9e1e2a2c463cdbe98bc69ac7c686d Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 26 Feb 2026 17:07:54 +0100 Subject: [PATCH] feat(socket): implement match report submission and schedule update events - Added WebSocket events for match report submission and schedule updates, enhancing real-time communication between clients and the server. - Updated matchController to emit schedule updates when match players are modified. - Enhanced nuscoreApiRoutes to emit match report submissions with relevant data for other clients. - Implemented socket service methods for handling incoming match report submissions and schedule updates in the frontend. - Updated MatchReportApiDialog and ScheduleView components to handle new WebSocket events, ensuring data synchronization across clients. --- backend/controllers/matchController.js | 8 +- backend/routes/nuscoreApiRoutes.js | 18 ++++ backend/services/matchService.js | 54 ++++++++++- backend/services/socketService.js | 10 ++ .../src/components/MatchReportApiDialog.vue | 94 +++++++++++++++++++ frontend/src/services/socketService.js | 24 +++++ frontend/src/views/ScheduleView.vue | 75 ++++++++++++++- 7 files changed, 275 insertions(+), 8 deletions(-) diff --git a/backend/controllers/matchController.js b/backend/controllers/matchController.js index 868cbaf6..13c7d989 100644 --- a/backend/controllers/matchController.js +++ b/backend/controllers/matchController.js @@ -1,6 +1,6 @@ import MatchService from '../services/matchService.js'; import fs from 'fs'; - +import { emitScheduleMatchUpdated } from '../services/socketService.js'; import { devLog } from '../utils/logger.js'; export const uploadCSV = async (req, res) => { try { @@ -116,7 +116,11 @@ export const updateMatchPlayers = async (req, res) => { playersPlanned, playersPlayed ); - + + if (result.clubId) { + emitScheduleMatchUpdated(result.clubId, result.id, result.match || null); + } + return res.status(200).json({ message: 'Match players updated successfully', data: result diff --git a/backend/routes/nuscoreApiRoutes.js b/backend/routes/nuscoreApiRoutes.js index b2ccaba5..82423377 100644 --- a/backend/routes/nuscoreApiRoutes.js +++ b/backend/routes/nuscoreApiRoutes.js @@ -1,5 +1,6 @@ import express from 'express'; import fetch from 'node-fetch'; +import { emitMatchReportSubmitted } from '../services/socketService.js'; const router = express.Router(); @@ -256,6 +257,12 @@ router.put('/submit/:uuid', async (req, res) => { 'Cache-Control': 'no-cache, no-store, must-revalidate' }); + const clubId = reportData.clubId; + const matchCode = reportData.gameCode || reportData.code; + if (clubId && (matchCode || uuid)) { + emitMatchReportSubmitted(clubId, matchCode || uuid, reportData); + } + res.json({ success: true, data: responseData, @@ -271,6 +278,17 @@ router.put('/submit/:uuid', async (req, res) => { } }); +// Nur Broadcast: aktueller Spielberichtsentwurf (z. B. Satzergebnisse) an andere Clients senden, ohne bei nuscore zu speichern +router.post('/broadcast-draft', (req, res) => { + const { clubId, gameCode, matchData } = req.body || {}; + if (!clubId || !(gameCode ?? matchData?.gameCode ?? matchData?.code)) { + return res.status(400).json({ error: 'clubId und gameCode erforderlich' }); + } + const code = String(gameCode ?? matchData?.gameCode ?? matchData?.code ?? ''); + emitMatchReportSubmitted(clubId, code, matchData || null); + res.json({ ok: true }); +}); + // Validate Meeting Report API-Endpunkt (für Zwischenspeicherung) router.put('/validate/:uuid', async (req, res) => { const { uuid } = req.params; diff --git a/backend/services/matchService.js b/backend/services/matchService.js index 54e2c302..30f108f5 100644 --- a/backend/services/matchService.js +++ b/backend/services/matchService.js @@ -467,12 +467,56 @@ class MatchService { playersPlanned: plannedList !== null ? plannedList : (match.playersPlanned || []), playersPlayed: playedList !== null ? playedList : (match.playersPlayed || []) }); - + + // Aktualisiertes Match nochmals laden und für WebSocket-Broadcast anreichern (gleiche Struktur wie getMatchesForLeague) + const updated = await Match.findByPk(matchId); + const enriched = { + id: updated.id, + date: updated.date, + time: updated.time, + homeTeamId: updated.homeTeamId, + guestTeamId: updated.guestTeamId, + locationId: updated.locationId, + leagueId: updated.leagueId, + code: updated.code, + homePin: updated.homePin, + guestPin: updated.guestPin, + homeMatchPoints: updated.homeMatchPoints || 0, + guestMatchPoints: updated.guestMatchPoints || 0, + isCompleted: updated.isCompleted || false, + pdfUrl: updated.pdfUrl, + playersReady: updated.playersReady || [], + playersPlanned: updated.playersPlanned || [], + playersPlayed: updated.playersPlayed || [], + homeTeam: { name: 'Unbekannt' }, + guestTeam: { name: 'Unbekannt' }, + location: { name: 'Unbekannt', address: '', city: '', zip: '' }, + leagueDetails: { name: 'Unbekannt' } + }; + if (updated.homeTeamId) { + const homeTeam = await Team.findByPk(updated.homeTeamId, { attributes: ['name'] }); + if (homeTeam) enriched.homeTeam = homeTeam; + } + if (updated.guestTeamId) { + const guestTeam = await Team.findByPk(updated.guestTeamId, { attributes: ['name'] }); + if (guestTeam) enriched.guestTeam = guestTeam; + } + if (updated.locationId) { + const location = await Location.findByPk(updated.locationId, { attributes: ['name', 'address', 'city', 'zip'] }); + if (location) enriched.location = location; + } + if (updated.leagueId) { + const league = await League.findByPk(updated.leagueId, { attributes: ['name'] }); + if (league) enriched.leagueDetails = league; + } + return { - id: match.id, - playersReady: match.playersReady, - playersPlanned: match.playersPlanned, - playersPlayed: match.playersPlayed + id: updated.id, + clubId: updated.clubId, + playersReady: updated.playersReady, + playersPlanned: updated.playersPlanned, + playersPlayed: updated.playersPlayed, + match: enriched }; } diff --git a/backend/services/socketService.js b/backend/services/socketService.js index 7145fff4..943e74e3 100644 --- a/backend/services/socketService.js +++ b/backend/services/socketService.js @@ -225,3 +225,13 @@ export const emitTournamentChanged = (clubId, tournamentId) => { emitToClub(clubId, 'tournament:changed', { tournamentId }); }; +// Event wenn Spielerauswahl (Bereit/Geplant/Gespielt) für ein Match geändert wurde (match = vollständiges angereichertes Match-Objekt) +export const emitScheduleMatchUpdated = (clubId, matchId, match = null) => { + emitToClub(clubId, 'schedule:match:updated', { clubId, matchId, match }); +}; + +// Event wenn Spielbericht (nuscore) abgesendet wurde – matchData = vollständiges Objekt für andere Clients +export const emitMatchReportSubmitted = (clubId, matchCode, matchData = null) => { + emitToClub(clubId, 'schedule:match-report:submitted', { clubId, matchCode, matchData }); +}; + diff --git a/frontend/src/components/MatchReportApiDialog.vue b/frontend/src/components/MatchReportApiDialog.vue index 3ce3cfcb..ad8112b4 100644 --- a/frontend/src/components/MatchReportApiDialog.vue +++ b/frontend/src/components/MatchReportApiDialog.vue @@ -696,6 +696,7 @@