feat(TTRPreview): add standalone TTR preview endpoint and enhance loading logic in MatchReportApiDialog
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 58s
This commit is contained in:
@@ -216,20 +216,23 @@ export const getMatchPlayers = async (req, res) => {
|
||||
export const getTtrPreviewPlayers = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const match = await Match.findByPk(req.params.matchId);
|
||||
if (!match) return res.status(404).json({ error: 'Spiel nicht gefunden' });
|
||||
await checkAccess(userToken, match.clubId);
|
||||
const isStandalonePreview = req.params.matchId === 'standalone';
|
||||
const match = isStandalonePreview ? null : await Match.findByPk(req.params.matchId);
|
||||
if (!isStandalonePreview && !match) return res.status(404).json({ error: 'Spiel nicht gefunden' });
|
||||
const clubId = isStandalonePreview ? Number(req.body?.clubId) : match.clubId;
|
||||
if (!Number.isInteger(clubId) || clubId <= 0) return res.status(400).json({ error: 'Verein fehlt für die TTR-Vorschau' });
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
// The product uses German match dates; do not roll the cache over at
|
||||
// UTC midnight while a late-evening match is still in progress.
|
||||
const snapshotDate = new Intl.DateTimeFormat('sv-SE', { timeZone: 'Europe/Berlin' }).format(new Date());
|
||||
const [cachedRows] = await sequelize.query(
|
||||
'SELECT payload FROM match_ttr_preview_cache WHERE match_id = :matchId AND snapshot_date = :snapshotDate LIMIT 1',
|
||||
{ replacements: { matchId: match.id, snapshotDate } }
|
||||
);
|
||||
if (cachedRows.length) {
|
||||
const payload = typeof cachedRows[0].payload === 'string' ? JSON.parse(cachedRows[0].payload) : cachedRows[0].payload;
|
||||
return res.json({ ...payload, cached: true, snapshotDate });
|
||||
if (match) {
|
||||
const [cachedRows] = await sequelize.query(
|
||||
'SELECT payload FROM match_ttr_preview_cache WHERE match_id = :matchId AND snapshot_date = :snapshotDate LIMIT 1',
|
||||
{ replacements: { matchId: match.id, snapshotDate } }
|
||||
);
|
||||
if (cachedRows.length) {
|
||||
const payload = typeof cachedRows[0].payload === 'string' ? JSON.parse(cachedRows[0].payload) : cachedRows[0].payload;
|
||||
return res.json({ ...payload, cached: true, snapshotDate });
|
||||
}
|
||||
}
|
||||
|
||||
const sides = ['home', 'guest'];
|
||||
@@ -283,11 +286,13 @@ export const getTtrPreviewPlayers = async (req, res) => {
|
||||
const value = resolved.get(`${side}:${normalizeTtrName(playerName(player))}`);
|
||||
return { side, name: playerName(player), nuLigaPersonId: player?.nuLigaPersonId || null, ttr: value?.ttr ?? null, adjustmentConstant: value?.adjustmentConstant ?? null, source: value?.source || 'unavailable' };
|
||||
}) };
|
||||
await sequelize.query(
|
||||
`INSERT INTO match_ttr_preview_cache (match_id, snapshot_date, payload, created_at, updated_at)
|
||||
VALUES (:matchId, :snapshotDate, :payload, NOW(), NOW())`,
|
||||
{ replacements: { matchId: match.id, snapshotDate, payload: JSON.stringify(payload) } }
|
||||
);
|
||||
if (match) {
|
||||
await sequelize.query(
|
||||
`INSERT INTO match_ttr_preview_cache (match_id, snapshot_date, payload, created_at, updated_at)
|
||||
VALUES (:matchId, :snapshotDate, :payload, NOW(), NOW())`,
|
||||
{ replacements: { matchId: match.id, snapshotDate, payload: JSON.stringify(payload) } }
|
||||
);
|
||||
}
|
||||
return res.json({ ...payload, cached: false, snapshotDate });
|
||||
} catch (error) {
|
||||
console.error('[getTtrPreviewPlayers]', error);
|
||||
|
||||
@@ -17,6 +17,7 @@ router.get('/leagues/:clubId/table/:leagueId', authenticate, authorize('schedule
|
||||
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, authorize('mytischtennis', 'write'), fetchLeagueTableFromMyTischtennis);
|
||||
router.get('/:clubId/players', authenticate, authorize('schedule', 'read'), getMatchPlayers);
|
||||
router.patch('/:matchId/players', authenticate, authorize('schedule', 'write'), updateMatchPlayers);
|
||||
router.post('/ttr-preview/standalone', authenticate, authorize('schedule', 'read'), getTtrPreviewPlayers);
|
||||
router.post('/:matchId/ttr-preview', authenticate, authorize('schedule', 'read'), getTtrPreviewPlayers);
|
||||
router.get('/leagues/:clubId/stats/:leagueId', authenticate, authorize('schedule', 'read'), getPlayerMatchStats);
|
||||
|
||||
|
||||
@@ -3337,33 +3337,65 @@ Wir wünschen den Spielen einen schönen, spannenden und fairen Verlauf und begr
|
||||
await this.loadTtrPreview();
|
||||
},
|
||||
async loadTtrPreview() {
|
||||
if (!this.canOpenTtrPreview || !this.match?.id || !this.meetingDetails) return;
|
||||
if (!this.canOpenTtrPreview || !this.meetingDetails) return;
|
||||
this.ttrLoading = true;
|
||||
this.ttrError = '';
|
||||
try {
|
||||
if (this.standaloneTest) {
|
||||
const response = await apiClient.post('/matches/ttr-preview/standalone', {
|
||||
clubId: this.$store.getters.currentClub,
|
||||
homePlayers: this.ttrLineupPlayers('home'),
|
||||
guestPlayers: this.ttrLineupPlayers('guest'),
|
||||
homeTeamName: this.meetingData?.homeTeamname,
|
||||
guestTeamName: this.meetingData?.guestTeamname,
|
||||
});
|
||||
const resolvedByKey = new Map((response.data?.players || []).map((player) => [`${player.side}:${this.normalizeTtrName(player.name)}`, player]));
|
||||
const players = ['home', 'guest'].flatMap((side) => this.ttrLineupPlayers(side).map((player, index) => ({
|
||||
side,
|
||||
name: [player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '),
|
||||
nuLigaPersonId: player.nuLigaPersonId || null,
|
||||
...resolvedByKey.get(`${side}:${this.normalizeTtrName([player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '))}`),
|
||||
// Test-Aufstellungen sind Platzhalternamen. Nur nicht
|
||||
// über die API auflösbare Spieler erhalten Testwerte.
|
||||
ttr: Number.isFinite(Number(resolvedByKey.get(`${side}:${this.normalizeTtrName([player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '))}`)?.ttr))
|
||||
? Number(resolvedByKey.get(`${side}:${this.normalizeTtrName([player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '))}`)?.ttr)
|
||||
: (side === 'home' ? 1320 - index * 23 : 1345 - index * 19),
|
||||
adjustmentConstant: resolvedByKey.get(`${side}:${this.normalizeTtrName([player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '))}`)?.adjustmentConstant || 16,
|
||||
source: resolvedByKey.get(`${side}:${this.normalizeTtrName([player.firstname || player.firstName, player.lastname || player.lastName].filter(Boolean).join(' '))}`)?.source || 'test',
|
||||
})));
|
||||
this.applyTtrPreviewPlayers(players);
|
||||
return;
|
||||
}
|
||||
if (!this.match?.id) {
|
||||
this.ttrError = 'Für diesen Spielbericht fehlt die gespeicherte Spiel-ID.';
|
||||
return;
|
||||
}
|
||||
const response = await apiClient.post(`/matches/${this.match.id}/ttr-preview`, {
|
||||
homePlayers: this.ttrLineupPlayers('home'),
|
||||
guestPlayers: this.ttrLineupPlayers('guest'),
|
||||
homeTeamName: this.meetingData?.homeTeamname,
|
||||
guestTeamName: this.meetingData?.guestTeamname,
|
||||
});
|
||||
this.ttrPlayers = Array.isArray(response.data?.players) ? response.data.players : [];
|
||||
const index = new Map(this.ttrPlayers.map((player) => [`${player.side}:${this.normalizeTtrName(player.name)}`, player]));
|
||||
const snapshots = {};
|
||||
this.results.forEach((match, resultIndex) => {
|
||||
if (this.isTtrDouble(match)) return;
|
||||
const home = index.get(`home:${this.normalizeTtrName(match.homeName)}`);
|
||||
const guest = index.get(`guest:${this.normalizeTtrName(match.guestName)}`);
|
||||
if (!Number.isFinite(Number(home?.ttr)) || !Number.isFinite(Number(guest?.ttr))) return;
|
||||
snapshots[resultIndex] = { homeTtr: Number(home.ttr), guestTtr: Number(guest.ttr), homeAdjustmentConstant: Number(home.adjustmentConstant || 16), guestAdjustmentConstant: Number(guest.adjustmentConstant || 16) };
|
||||
});
|
||||
this.ttrSnapshots = snapshots;
|
||||
this.applyTtrPreviewPlayers(Array.isArray(response.data?.players) ? response.data.players : []);
|
||||
} catch (error) {
|
||||
this.ttrError = error.response?.data?.error || 'TTR-Werte konnten nicht geladen werden.';
|
||||
} finally {
|
||||
this.ttrLoading = false;
|
||||
}
|
||||
},
|
||||
applyTtrPreviewPlayers(players) {
|
||||
this.ttrPlayers = players;
|
||||
const index = new Map(this.ttrPlayers.map((player) => [`${player.side}:${this.normalizeTtrName(player.name)}`, player]));
|
||||
const snapshots = {};
|
||||
this.results.forEach((match, resultIndex) => {
|
||||
if (this.isTtrDouble(match)) return;
|
||||
const home = index.get(`home:${this.normalizeTtrName(match.homeName)}`);
|
||||
const guest = index.get(`guest:${this.normalizeTtrName(match.guestName)}`);
|
||||
if (!Number.isFinite(Number(home?.ttr)) || !Number.isFinite(Number(guest?.ttr))) return;
|
||||
snapshots[resultIndex] = { homeTtr: Number(home.ttr), guestTtr: Number(guest.ttr), homeAdjustmentConstant: Number(home.adjustmentConstant || 16), guestAdjustmentConstant: Number(guest.adjustmentConstant || 16) };
|
||||
});
|
||||
this.ttrSnapshots = snapshots;
|
||||
},
|
||||
isTtrDouble(match) {
|
||||
const label = String(match?.label || '').toUpperCase();
|
||||
return label.includes('D') || String(match?.homeName || '').includes(' / ') || String(match?.guestName || '').includes(' / ');
|
||||
|
||||
@@ -2999,7 +2999,10 @@ export default {
|
||||
|
||||
this.fetchingTeamData = true;
|
||||
try {
|
||||
const response = await apiClient.post('/scheduler/match_results', {
|
||||
// A manual click must not depend on the scheduler's
|
||||
// autoUpdateRatings/savePassword flags. This endpoint fetches
|
||||
// precisely the selected ClubTeam and persists its results.
|
||||
const response = await apiClient.post('/mytischtennis/fetch-team-data', {
|
||||
clubTeamId: this.selectedTeam.id
|
||||
});
|
||||
const result = response?.data;
|
||||
@@ -3014,6 +3017,7 @@ export default {
|
||||
}
|
||||
|
||||
const fetchedCount =
|
||||
result?.data?.fetchedCount ??
|
||||
result?.result?.totalFetched ??
|
||||
result?.result?.fetchedCount ??
|
||||
result?.result?.data?.fetchedCount ??
|
||||
|
||||
Reference in New Issue
Block a user