Erweitert den MatchReportApiDialog um neue Funktionen zur Verwaltung von Spielberichten. Implementiert eine verbesserte Logik zur Berechnung der Gesamtpunkte und Sätze sowie zur Validierung von Eingaben. Fügt visuelle Hinweise für den Abschlussstatus und Warnungen bei fehlerhaften Eingaben hinzu. Optimiert die Benutzeroberfläche mit neuen CSS-Stilen für eine bessere Benutzererfahrung.

This commit is contained in:
Torsten Schulz (local)
2025-11-12 13:40:55 +01:00
524 changed files with 55207 additions and 17236 deletions

View File

@@ -0,0 +1,21 @@
import express from 'express';
import apiLogController from '../controllers/apiLogController.js';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
const router = express.Router();
// All routes require authentication
router.use(authenticate);
// Get logs - requires permissions or admin
router.get('/', apiLogController.getLogs);
// Get last scheduler executions
router.get('/scheduler/last-executions', apiLogController.getLastSchedulerExecutions);
// Get single log by ID
router.get('/:id', apiLogController.getLogById);
export default router;

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import { getClubs, addClub, getClub, requestClubAccess, getPendingApprovals, approveClubAccess, rejectClubAccess, updateClubSettings } from '../controllers/clubsController.js';
const router = express.Router();
@@ -9,8 +10,8 @@ router.post('/', authenticate, addClub);
router.put('/:clubid/settings', authenticate, updateClubSettings);
router.get('/:clubid', authenticate, getClub);
router.get('/request/:clubid', authenticate, requestClubAccess);
router.get('/pending/:clubid', authenticate, getPendingApprovals);
router.post('/approve', authenticate, approveClubAccess);
router.post('/reject', authenticate, rejectClubAccess);
router.get('/pending/:clubid', authenticate, authorize('approvals', 'read'), getPendingApprovals);
router.post('/approve', authenticate, authorize('approvals', 'write'), approveClubAccess);
router.post('/reject', authenticate, authorize('approvals', 'write'), rejectClubAccess);
export default router;

View File

@@ -6,6 +6,7 @@ import {
updateDiaryDateActivityOrder,
getDiaryDateActivities,
addGroupActivity,
deleteGroupActivity,
} from '../controllers/diaryDateActivityController.js';
import { authenticate } from '../middleware/authMiddleware.js';
@@ -14,6 +15,7 @@ const router = express.Router();
router.use(authenticate);
router.post('/group', addGroupActivity);
router.delete('/group/:clubId/:groupActivityId', deleteGroupActivity);
router.post('/:clubId/', createDiaryDateActivity);
router.put('/:clubId/:id/order', updateDiaryDateActivityOrder);
router.put('/:clubId/:id', updateDiaryDateActivity);

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import {
getDatesForClub,
createDateForClub,
@@ -14,14 +15,14 @@ import {
const router = express.Router();
router.post('/note', authenticate, addDiaryNote);
router.delete('/note/:noteId', authenticate, deleteDiaryNote);
router.post('/tag', authenticate, addDiaryTag);
router.post('/tag/:clubId/add-tag', authenticate, addTagToDiaryDate);
router.delete('/:clubId/tag', authenticate, deleteTagFromDiaryDate);
router.get('/:clubId', authenticate, getDatesForClub);
router.post('/:clubId', authenticate, createDateForClub);
router.put('/:clubId', authenticate, updateTrainingTimes);
router.delete('/:clubId/:dateId', authenticate, deleteDateForClub);
router.post('/note', authenticate, authorize('diary', 'write'), addDiaryNote);
router.delete('/note/:noteId', authenticate, authorize('diary', 'delete'), deleteDiaryNote);
router.post('/tag', authenticate, authorize('diary', 'write'), addDiaryTag);
router.post('/tag/:clubId/add-tag', authenticate, authorize('diary', 'write'), addTagToDiaryDate);
router.delete('/:clubId/tag', authenticate, authorize('diary', 'delete'), deleteTagFromDiaryDate);
router.get('/:clubId', authenticate, authorize('diary', 'read'), getDatesForClub);
router.post('/:clubId', authenticate, authorize('diary', 'write'), createDateForClub);
router.put('/:clubId', authenticate, authorize('diary', 'write'), updateTrainingTimes);
router.delete('/:clubId/:dateId', authenticate, authorize('diary', 'delete'), deleteDateForClub);
export default router;

View File

@@ -1,16 +1,21 @@
import express from 'express';
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague } from '../controllers/matchController.js';
import { uploadCSV, getLeaguesForCurrentSeason, getMatchesForLeagues, getMatchesForLeague, getLeagueTable, fetchLeagueTableFromMyTischtennis, updateMatchPlayers, getPlayerMatchStats } from '../controllers/matchController.js';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import multer from 'multer';
const router = express.Router();
const upload = multer({ dest: 'uploads/' });
router.post('/import', authenticate, upload.single('file'), uploadCSV);
router.get('/leagues/current/:clubId', authenticate, getLeaguesForCurrentSeason);
router.get('/leagues/:clubId/matches/:leagueId', authenticate, getMatchesForLeague);
router.get('/leagues/:clubId/matches', authenticate, getMatchesForLeagues);
router.post('/import', authenticate, authorize('schedule', 'write'), upload.single('file'), uploadCSV);
router.get('/leagues/current/:clubId', authenticate, authorize('schedule', 'read'), getLeaguesForCurrentSeason);
router.get('/leagues/:clubId/matches/:leagueId', authenticate, authorize('schedule', 'read'), getMatchesForLeague);
router.get('/leagues/:clubId/matches', authenticate, authorize('schedule', 'read'), getMatchesForLeagues);
router.get('/leagues/:clubId/table/:leagueId', authenticate, authorize('schedule', 'read'), getLeagueTable);
router.post('/leagues/:clubId/table/:leagueId/fetch', authenticate, authorize('mytischtennis', 'write'), fetchLeagueTableFromMyTischtennis);
router.patch('/:matchId/players', authenticate, authorize('schedule', 'write'), updateMatchPlayers);
router.get('/leagues/:clubId/stats/:leagueId', authenticate, authorize('schedule', 'read'), getPlayerMatchStats);
export default router;

View File

@@ -0,0 +1,13 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { getMemberActivities, getMemberLastParticipations } from '../controllers/memberActivityController.js';
const router = express.Router();
router.use(authenticate);
router.get('/:clubId/:memberId/last-participations', getMemberLastParticipations);
router.get('/:clubId/:memberId', getMemberActivities);
export default router;

View File

@@ -1,6 +1,22 @@
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis } from '../controllers/memberController.js';
import {
getClubMembers,
getWaitingApprovals,
setClubMembers,
uploadMemberImage,
getMemberImage,
updateRatingsFromMyTischtennis,
rotateMemberImage,
transferMembers,
quickUpdateTestMembership,
quickUpdateMemberFormHandedOver,
quickDeactivateMember,
deleteMemberImage,
setPrimaryMemberImage,
generateMemberGallery
} from '../controllers/memberController.js';
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import multer from 'multer';
const router = express.Router();
@@ -8,11 +24,20 @@ const router = express.Router();
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
router.post('/image/:clubId/:memberId', authenticate, upload.single('image'), uploadMemberImage);
router.get('/image/:clubId/:memberId', authenticate, getMemberImage);
router.get('/get/:id/:showAll', authenticate, getClubMembers);
router.post('/set/:id', authenticate, setClubMembers);
router.get('/notapproved/:id', authenticate, getWaitingApprovals);
router.post('/update-ratings/:id', authenticate, updateRatingsFromMyTischtennis);
router.post('/image/:clubId/:memberId', authenticate, authorize('members', 'write'), upload.single('image'), uploadMemberImage);
router.get('/image/:clubId/:memberId/:imageId', authenticate, authorize('members', 'read'), getMemberImage);
router.get('/image/:clubId/:memberId', authenticate, authorize('members', 'read'), getMemberImage);
router.delete('/image/:clubId/:memberId/:imageId', authenticate, authorize('members', 'write'), deleteMemberImage);
router.post('/image/:clubId/:memberId/:imageId/primary', authenticate, authorize('members', 'write'), setPrimaryMemberImage);
router.get('/get/:id/:showAll', authenticate, authorize('members', 'read'), getClubMembers);
router.get('/gallery/:clubId', authenticate, authorize('members', 'read'), generateMemberGallery);
router.post('/set/:id', authenticate, authorize('members', 'write'), setClubMembers);
router.get('/notapproved/:id', authenticate, authorize('members', 'read'), getWaitingApprovals);
router.post('/update-ratings/:id', authenticate, authorize('mytischtennis', 'write'), updateRatingsFromMyTischtennis);
router.post('/rotate-image/:clubId/:memberId/:imageId', authenticate, authorize('members', 'write'), rotateMemberImage);
router.post('/transfer/:id', authenticate, authorize('members', 'write'), transferMembers);
router.post('/quick-update-test-membership/:clubId/:memberId', authenticate, authorize('members', 'write'), quickUpdateTestMembership);
router.post('/quick-update-member-form/:clubId/:memberId', authenticate, authorize('members', 'write'), quickUpdateMemberFormHandedOver);
router.post('/quick-deactivate/:clubId/:memberId', authenticate, authorize('members', 'write'), quickDeactivateMember);
export default router;

View File

@@ -0,0 +1,14 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import { getConfig, saveConfig, deleteConfig } from '../controllers/memberTransferConfigController.js';
const router = express.Router();
router.get('/:id', authenticate, authorize('members', 'read'), getConfig);
router.post('/:id', authenticate, authorize('members', 'write'), saveConfig);
router.put('/:id', authenticate, authorize('members', 'write'), saveConfig);
router.delete('/:id', authenticate, authorize('members', 'write'), deleteConfig);
export default router;

View File

@@ -1,23 +1,25 @@
import express from 'express';
import myTischtennisController from '../controllers/myTischtennisController.js';
import myTischtennisUrlController from '../controllers/myTischtennisUrlController.js';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
const router = express.Router();
// All routes require authentication
router.use(authenticate);
// GET /api/mytischtennis/account - Get account
// GET /api/mytischtennis/account - Get account (alle dürfen lesen)
router.get('/account', myTischtennisController.getAccount);
// GET /api/mytischtennis/status - Check status
// GET /api/mytischtennis/status - Check status (alle dürfen lesen)
router.get('/status', myTischtennisController.getStatus);
// POST /api/mytischtennis/account - Create or update account
// POST /api/mytischtennis/account - Create or update account (alle dürfen bearbeiten)
router.post('/account', myTischtennisController.upsertAccount);
// DELETE /api/mytischtennis/account - Delete account
router.delete('/account', myTischtennisController.deleteAccount);
// DELETE /api/mytischtennis/account - Delete account (nur Admin)
router.delete('/account', authorize('mytischtennis_admin', 'write'), myTischtennisController.deleteAccount);
// POST /api/mytischtennis/verify - Verify login
router.post('/verify', myTischtennisController.verifyLogin);
@@ -25,5 +27,29 @@ router.post('/verify', myTischtennisController.verifyLogin);
// GET /api/mytischtennis/session - Get stored session
router.get('/session', myTischtennisController.getSession);
// GET /api/mytischtennis/update-history - Get update ratings history
router.get('/update-history', myTischtennisController.getUpdateHistory);
// GET /api/mytischtennis/fetch-logs - Get fetch logs
router.get('/fetch-logs', myTischtennisController.getFetchLogs);
// GET /api/mytischtennis/latest-fetches - Get latest successful fetches
router.get('/latest-fetches', myTischtennisController.getLatestFetches);
// POST /api/mytischtennis/parse-url - Parse myTischtennis URL
router.post('/parse-url', myTischtennisUrlController.parseUrl);
// POST /api/mytischtennis/configure-team - Configure team from URL
router.post('/configure-team', myTischtennisUrlController.configureTeam);
// POST /api/mytischtennis/configure-league - Configure league from table URL
router.post('/configure-league', myTischtennisUrlController.configureLeague);
// POST /api/mytischtennis/fetch-team-data - Manually fetch team data
router.post('/fetch-team-data', myTischtennisUrlController.fetchTeamData);
// GET /api/mytischtennis/team-url/:teamId - Get myTischtennis URL for team
router.get('/team-url/:teamId', myTischtennisUrlController.getTeamUrl);
export default router;

View File

@@ -1,5 +1,5 @@
import express from 'express';
import { getParticipants, addParticipant, removeParticipant } from '../controllers/participantController.js';
import { getParticipants, addParticipant, removeParticipant, updateParticipantGroup } from '../controllers/participantController.js';
import { authenticate } from '../middleware/authMiddleware.js';
const router = express.Router();
@@ -7,5 +7,6 @@ const router = express.Router();
router.get('/:dateId', authenticate, getParticipants);
router.post('/add', authenticate, addParticipant);
router.post('/remove', authenticate, removeParticipant);
router.put('/:dateId/:memberId/group', authenticate, updateParticipantGroup);
export default router;

View File

@@ -0,0 +1,41 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize, requireAdmin } from '../middleware/authorizationMiddleware.js';
import permissionController from '../controllers/permissionController.js';
const router = express.Router();
// Middleware to disable caching for permission routes
const noCache = (req, res, next) => {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private');
res.set('Pragma', 'no-cache');
res.set('Expires', '0');
next();
};
// Apply no-cache to all routes
router.use(noCache);
// Get available roles (no club context needed)
router.get('/roles/available', authenticate, permissionController.getAvailableRoles);
// Get permission structure (no club context needed)
router.get('/structure/all', authenticate, permissionController.getPermissionStructure);
// Get current user's permissions for a club (no authorization check - needed to load permissions)
router.get('/:clubId', authenticate, permissionController.getUserPermissions);
// Get all club members with their permissions (admin only)
router.get('/:clubId/members', authenticate, authorize('permissions', 'read'), permissionController.getClubMembersWithPermissions);
// Update user role (admin only)
router.put('/:clubId/user/:userId/role', authenticate, authorize('permissions', 'write'), permissionController.updateUserRole);
// Update user permissions (admin only)
router.put('/:clubId/user/:userId/permissions', authenticate, authorize('permissions', 'write'), permissionController.updateUserPermissions);
// Update user status (admin only)
router.put('/:clubId/user/:userId/status', authenticate, authorize('permissions', 'write'), permissionController.updateUserStatus);
export default router;

View File

@@ -10,6 +10,7 @@ import {
} from '../controllers/predefinedActivityController.js';
import multer from 'multer';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import { uploadPredefinedActivityImage, deletePredefinedActivityImage } from '../controllers/predefinedActivityImageController.js';
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
import path from 'path';
@@ -20,14 +21,14 @@ const upload = multer({ storage: multer.memoryStorage() });
router.post('/', authenticate, createPredefinedActivity);
router.get('/', authenticate, getAllPredefinedActivities);
router.get('/search/query', authenticate, searchPredefinedActivities);
router.post('/merge', authenticate, mergePredefinedActivities);
router.post('/deduplicate', authenticate, deduplicatePredefinedActivities);
router.get('/:id', authenticate, getPredefinedActivityById);
router.put('/:id', authenticate, updatePredefinedActivity);
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
router.put('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
router.delete('/:id/image/:imageId', authenticate, deletePredefinedActivityImage);
router.get('/search/query', authenticate, searchPredefinedActivities);
router.post('/merge', authenticate, mergePredefinedActivities);
router.post('/deduplicate', authenticate, deduplicatePredefinedActivities);
router.get('/:id/image/:imageId', async (req, res) => {
try {
const { id, imageId } = req.params;

View File

@@ -1,9 +1,38 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import sessionController from '../controllers/sessionController.js';
import schedulerService from '../services/schedulerService.js';
const router = express.Router();
router.get('/status', authenticate, sessionController.checkSession);
// Manual trigger endpoints for testing
router.post('/trigger-rating-updates', authenticate, async (req, res) => {
try {
const result = await schedulerService.triggerRatingUpdates();
res.json(result);
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
});
router.post('/trigger-match-fetch', authenticate, async (req, res) => {
try {
const result = await schedulerService.triggerMatchResultsFetch();
res.json(result);
} catch (error) {
res.status(500).json({ success: false, message: error.message });
}
});
router.get('/scheduler-status', authenticate, (req, res) => {
const status = schedulerService.getStatus();
const nextRatingUpdate = schedulerService.getNextRatingUpdateTime();
res.json({
...status,
nextRatingUpdate
});
});
export default router;

View File

@@ -1,5 +1,6 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import { authorize } from '../middleware/authorizationMiddleware.js';
import {
getTeams,
getTeam,
@@ -12,21 +13,21 @@ import {
const router = express.Router();
// Get all teams for a club
router.get('/club/:clubid', authenticate, getTeams);
router.get('/club/:clubid', authenticate, authorize('teams', 'read'), getTeams);
// Get leagues for a club
router.get('/leagues/:clubid', authenticate, getLeagues);
router.get('/leagues/:clubid', authenticate, authorize('teams', 'read'), getLeagues);
// Get a specific team
router.get('/:teamid', authenticate, getTeam);
router.get('/:teamid', authenticate, authorize('teams', 'read'), getTeam);
// Create a new team
router.post('/club/:clubid', authenticate, createTeam);
router.post('/club/:clubid', authenticate, authorize('teams', 'write'), createTeam);
// Update a team
router.put('/:teamid', authenticate, updateTeam);
router.put('/:teamid', authenticate, authorize('teams', 'write'), updateTeam);
// Delete a team
router.delete('/:teamid', authenticate, deleteTeam);
router.delete('/:teamid', authenticate, authorize('teams', 'delete'), deleteTeam);
export default router;