Implement cross-club friendly match concept with invitations and shared matches
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 49s

- Added controllers for handling friendly match invitations and shared matches.
- Created migration scripts for `friendly_match_invitation` and `friendly_match_shared` tables.
- Developed models for `FriendlyMatchInvitation` and `FriendlyMatchShared`.
- Established routes for managing invitations and shared matches.
- Implemented services for business logic related to invitations and shared matches.
- Documented the concept plan for the new feature including API endpoints and data models.
This commit is contained in:
Torsten Schulz (local)
2026-05-30 17:50:35 +02:00
parent 359527eb5b
commit 0ff67dae80
21 changed files with 1795 additions and 17 deletions

View File

@@ -0,0 +1,69 @@
import friendlyMatchSharedService from '../services/friendlyMatchSharedService.js';
import {
emitFriendlyInvitationAccepted,
emitFriendlyInvitationCreated,
emitFriendlyInvitationDeclined,
emitFriendlySharedMatchUpdated,
} from '../services/socketService.js';
function userTokenFrom(req) {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.slice(7);
}
return req.headers.authcode || authHeader;
}
export const createFriendlyMatchInvitation = async (req, res) => {
try {
const invitation = await friendlyMatchSharedService.createInvitation(userTokenFrom(req), req.params.clubId, req.body);
emitFriendlyInvitationCreated(invitation.fromClubId, invitation.toClubId, invitation);
res.status(201).json(invitation);
} catch (error) {
console.error('[createFriendlyMatchInvitation] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Einladung konnte nicht erstellt werden.' });
}
};
export const listIncomingFriendlyMatchInvitations = async (req, res) => {
try {
const items = await friendlyMatchSharedService.listIncomingInvitations(userTokenFrom(req), req.params.clubId);
res.status(200).json(items);
} catch (error) {
console.error('[listIncomingFriendlyMatchInvitations] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Eingehende Einladungen konnten nicht geladen werden.' });
}
};
export const listOutgoingFriendlyMatchInvitations = async (req, res) => {
try {
const items = await friendlyMatchSharedService.listOutgoingInvitations(userTokenFrom(req), req.params.clubId);
res.status(200).json(items);
} catch (error) {
console.error('[listOutgoingFriendlyMatchInvitations] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Ausgehende Einladungen konnten nicht geladen werden.' });
}
};
export const acceptFriendlyMatchInvitation = async (req, res) => {
try {
const result = await friendlyMatchSharedService.acceptInvitation(userTokenFrom(req), req.params.clubId, req.params.invitationId);
emitFriendlyInvitationAccepted(result.invitation.fromClubId, result.invitation.toClubId, result.invitation);
emitFriendlySharedMatchUpdated(result.sharedMatch.homeClubId, result.sharedMatch.guestClubId, result.sharedMatch);
res.status(200).json(result);
} catch (error) {
console.error('[acceptFriendlyMatchInvitation] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Einladung konnte nicht angenommen werden.' });
}
};
export const declineFriendlyMatchInvitation = async (req, res) => {
try {
const invitation = await friendlyMatchSharedService.declineInvitation(userTokenFrom(req), req.params.clubId, req.params.invitationId);
emitFriendlyInvitationDeclined(invitation.fromClubId, invitation.toClubId, invitation.id);
res.status(200).json({ success: true, id: invitation.id });
} catch (error) {
console.error('[declineFriendlyMatchInvitation] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Einladung konnte nicht abgelehnt werden.' });
}
};

View File

@@ -0,0 +1,86 @@
import friendlyMatchSharedService from '../services/friendlyMatchSharedService.js';
import {
emitFriendlySharedMatchDeleted,
emitFriendlySharedMatchUpdated,
} from '../services/socketService.js';
function userTokenFrom(req) {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
return authHeader.slice(7);
}
return req.headers.authcode || authHeader;
}
export const findSharedFriendlyMatches = async (req, res) => {
try {
const { clubId, name, date, startTime } = req.query;
const matches = await friendlyMatchSharedService.findByNameDateStartTime(userTokenFrom(req), clubId, {
name,
date,
startTime,
});
res.status(200).json(matches);
} catch (error) {
console.error('[findSharedFriendlyMatches] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Suche nach Freundschaftsspielen fehlgeschlagen.' });
}
};
export const listSharedFriendlyMatches = async (req, res) => {
try {
const data = await friendlyMatchSharedService.listShared(userTokenFrom(req), req.params.clubId);
res.status(200).json(data);
} catch (error) {
console.error('[listSharedFriendlyMatches] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Gemeinsame Freundschaftsspiele konnten nicht geladen werden.' });
}
};
export const updateSharedFriendlyMatch = async (req, res) => {
try {
const match = await friendlyMatchSharedService.updateShared(
userTokenFrom(req),
req.params.clubId,
req.params.matchId,
req.body,
);
emitFriendlySharedMatchUpdated(match.homeClubId, match.guestClubId, match);
res.status(200).json(match);
} catch (error) {
console.error('[updateSharedFriendlyMatch] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Gemeinsames Freundschaftsspiel konnte nicht gespeichert werden.' });
}
};
export const updateSharedFriendlyMatchPlayers = async (req, res) => {
try {
const match = await friendlyMatchSharedService.updateSharedPlayers(
userTokenFrom(req),
req.params.clubId,
req.params.matchId,
req.body,
);
emitFriendlySharedMatchUpdated(match.homeClubId, match.guestClubId, match);
res.status(200).json({ message: 'Teilnehmer gespeichert', data: match });
} catch (error) {
console.error('[updateSharedFriendlyMatchPlayers] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Teilnehmer konnten nicht gespeichert werden.' });
}
};
export const deleteSharedFriendlyMatch = async (req, res) => {
try {
const match = await friendlyMatchSharedService.getSharedById(
userTokenFrom(req),
req.params.clubId,
req.params.matchId,
);
const result = await friendlyMatchSharedService.removeShared(userTokenFrom(req), req.params.clubId, req.params.matchId);
emitFriendlySharedMatchDeleted(match.homeClubId, match.guestClubId, Number(req.params.matchId));
res.status(200).json(result);
} catch (error) {
console.error('[deleteSharedFriendlyMatch] Error:', error);
res.status(error.statusCode || 500).json({ error: error.message || 'Gemeinsames Freundschaftsspiel konnte nicht geloescht werden.' });
}
};