feat(tournaments): add update functionality for official tournaments

- Implemented an API endpoint to update the title of official tournaments, including error handling for non-existent tournaments.
- Enhanced the frontend to allow users to edit tournament titles directly, with input validation and feedback for successful updates or errors.
- Updated the German localization file to include new strings for editing titles and error messages.
This commit is contained in:
Torsten Schulz (local)
2026-03-10 21:54:03 +01:00
parent ea6acd8c6c
commit c13f426b3d
6 changed files with 136 additions and 5 deletions

View File

@@ -10,9 +10,47 @@ import { authenticate } from '../middleware/authMiddleware.js';
const router = express.Router();
/** Domains, deren Links durch den Proxy umgeleitet werden (für Folge-Logs) */
const PROXY_DOMAINS = ['click-tt.de', 'httv.de'];
/**
* Prüft, ob eine URL durch unseren Proxy umgeleitet werden soll
*/
function shouldProxyUrl(href) {
if (!href || typeof href !== 'string') return false;
const h = href.trim();
if (h.startsWith('#') || h.startsWith('javascript:')) return false;
return PROXY_DOMAINS.some(d => h.includes(d));
}
/**
* Schreibt Links im HTML um, sodass Klicks im iframe über unseren Proxy laufen (Folge-Logs).
*/
function rewriteLinksInHtml(html, proxyBaseUrl, pageBaseUrl) {
if (!html || !proxyBaseUrl || !pageBaseUrl) return html;
try {
const base = new URL(pageBaseUrl);
return html.replace(
/<a\s+([^>]*?)href\s*=\s*["']([^"']+)["']([^>]*)>/gi,
(match, before, href, after) => {
let absoluteUrl = href;
if (href.startsWith('/') || !href.startsWith('http')) {
absoluteUrl = new URL(href, base.origin + base.pathname).href;
}
if (!shouldProxyUrl(absoluteUrl)) return match;
const proxyUrl = `${proxyBaseUrl}${proxyBaseUrl.includes('?') ? '&' : '?'}url=${encodeURIComponent(absoluteUrl)}`;
return `<a ${before}href="${proxyUrl}"${after}>`;
}
);
} catch {
return html;
}
}
/**
* GET /api/clicktt/proxy
* Proxy für iframe-Einbettung liefert HTML direkt (ohne Auth, für iframe src).
* Links zu click-tt.de/httv.de werden umgeschrieben, damit Folge-Klicks geloggt werden.
* Query: type (leaguePage|clubInfo|regionMeetings), association, championship, clubId
* ODER: url (vollständige URL, nur click-tt.de/httv.de)
*/
@@ -66,6 +104,12 @@ router.get('/proxy', async (req, res, next) => {
.replace(/<meta[^>]*http-equiv=["']x-frame-options["'][^>]*>/gi, '')
.replace(/<meta[^>]*http-equiv=["']x-content-type-options["'][^>]*>/gi, '');
// Links umschreiben: Klicks im iframe laufen über unseren Proxy → Folge-Logs
const protocol = req.protocol || 'http';
const host = req.get('host') || 'localhost:3005';
const proxyBase = `${protocol}://${host}/api/clicktt/proxy`;
html = rewriteLinksInHtml(html, proxyBase, targetUrl);
res.set({
'Content-Type': 'text/html; charset=utf-8',
'Access-Control-Allow-Origin': '*',

View File

@@ -1,7 +1,7 @@
import express from 'express';
import multer from 'multer';
import { authenticate } from '../middleware/authMiddleware.js';
import { uploadTournamentPdf, getParsedTournament, listOfficialTournaments, deleteOfficialTournament, upsertCompetitionMember, listClubParticipations, updateParticipantStatus } from '../controllers/officialTournamentController.js';
import { uploadTournamentPdf, getParsedTournament, listOfficialTournaments, deleteOfficialTournament, updateOfficialTournament, upsertCompetitionMember, listClubParticipations, updateParticipantStatus } from '../controllers/officialTournamentController.js';
const router = express.Router();
const upload = multer({ storage: multer.memoryStorage() });
@@ -12,6 +12,7 @@ router.get('/:clubId', listOfficialTournaments);
router.get('/:clubId/participations/summary', listClubParticipations);
router.post('/:clubId/upload', upload.single('pdf'), uploadTournamentPdf);
router.get('/:clubId/:id', getParsedTournament);
router.patch('/:clubId/:id', updateOfficialTournament);
router.delete('/:clubId/:id', deleteOfficialTournament);
router.post('/:clubId/:id/participation', upsertCompetitionMember);
router.post('/:clubId/:id/status', updateParticipantStatus);