feat: Implement friendly match management features
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Added backend support for managing friendly matches including listing, creating, updating, and deleting matches. - Introduced a new database table `friendly_match` with relevant fields for match details. - Created a service layer to handle business logic related to friendly matches. - Developed API routes for friendly match operations with appropriate authentication and authorization. - Added a Vue component for managing participants in friendly matches, allowing selection of members and manual entry of names. - Updated existing tournament editor screens to integrate friendly match functionalities.
This commit is contained in:
70
backend/controllers/friendlyMatchController.js
Normal file
70
backend/controllers/friendlyMatchController.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import FriendlyMatchService from '../services/friendlyMatchService.js';
|
||||
import { emitScheduleMatchUpdated } from '../services/socketService.js';
|
||||
|
||||
function userTokenFrom(req) {
|
||||
return req.headers.authcode;
|
||||
}
|
||||
|
||||
export const listFriendlyMatches = async (req, res) => {
|
||||
try {
|
||||
const matches = await FriendlyMatchService.list(userTokenFrom(req), req.params.clubId);
|
||||
res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.error('[listFriendlyMatches] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Freundschaftsspiele konnten nicht geladen werden' });
|
||||
}
|
||||
};
|
||||
|
||||
export const createFriendlyMatch = async (req, res) => {
|
||||
try {
|
||||
const match = await FriendlyMatchService.create(userTokenFrom(req), req.params.clubId, req.body);
|
||||
emitScheduleMatchUpdated(req.params.clubId, match.id, match);
|
||||
res.status(201).json(match);
|
||||
} catch (error) {
|
||||
console.error('[createFriendlyMatch] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Freundschaftsspiel konnte nicht erstellt werden' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFriendlyMatch = async (req, res) => {
|
||||
try {
|
||||
const match = await FriendlyMatchService.update(userTokenFrom(req), req.params.clubId, req.params.matchId, req.body);
|
||||
emitScheduleMatchUpdated(req.params.clubId, match.id, match);
|
||||
res.status(200).json(match);
|
||||
} catch (error) {
|
||||
console.error('[updateFriendlyMatch] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Freundschaftsspiel konnte nicht gespeichert werden' });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteFriendlyMatch = async (req, res) => {
|
||||
try {
|
||||
const result = await FriendlyMatchService.remove(userTokenFrom(req), req.params.clubId, req.params.matchId);
|
||||
emitScheduleMatchUpdated(req.params.clubId, Number(req.params.matchId), null);
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
console.error('[deleteFriendlyMatch] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Freundschaftsspiel konnte nicht gelöscht werden' });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateFriendlyMatchPlayers = async (req, res) => {
|
||||
try {
|
||||
const match = await FriendlyMatchService.updatePlayers(userTokenFrom(req), req.params.clubId, req.params.matchId, req.body);
|
||||
emitScheduleMatchUpdated(req.params.clubId, match.id, match);
|
||||
res.status(200).json({ message: 'Teilnehmer gespeichert', data: match });
|
||||
} catch (error) {
|
||||
console.error('[updateFriendlyMatchPlayers] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Teilnehmer konnten nicht gespeichert werden' });
|
||||
}
|
||||
};
|
||||
|
||||
export const getFriendlyMatchMembers = async (req, res) => {
|
||||
try {
|
||||
const members = await FriendlyMatchService.members(userTokenFrom(req), req.params.clubId);
|
||||
res.status(200).json(members);
|
||||
} catch (error) {
|
||||
console.error('[getFriendlyMatchMembers] Error:', error);
|
||||
res.status(error.statusCode || 500).json({ error: error.message || 'Mitglieder konnten nicht geladen werden' });
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `friendly_match`
|
||||
ADD COLUMN IF NOT EXISTS `result_details` JSON NULL AFTER `guest_participants`;
|
||||
30
backend/migrations/20260518_create_friendly_match.sql
Normal file
30
backend/migrations/20260518_create_friendly_match.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
CREATE TABLE IF NOT EXISTS `friendly_match` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`club_id` INT NOT NULL,
|
||||
`date` DATE NOT NULL,
|
||||
`time` TIME NULL,
|
||||
`home_team_name` VARCHAR(255) NOT NULL,
|
||||
`guest_team_name` VARCHAR(255) NOT NULL,
|
||||
`location_name` VARCHAR(255) NULL,
|
||||
`location_address` VARCHAR(255) NULL,
|
||||
`location_city` VARCHAR(255) NULL,
|
||||
`location_zip` VARCHAR(32) NULL,
|
||||
`match_system` VARCHAR(120) NOT NULL DEFAULT 'Braunschweiger System',
|
||||
`singles_count` INT NOT NULL DEFAULT 12,
|
||||
`doubles_count` INT NOT NULL DEFAULT 4,
|
||||
`winning_sets` INT NOT NULL DEFAULT 3,
|
||||
`home_match_points` INT NOT NULL DEFAULT 0,
|
||||
`guest_match_points` INT NOT NULL DEFAULT 0,
|
||||
`is_completed` TINYINT(1) NOT NULL DEFAULT 0,
|
||||
`home_participants` JSON NULL,
|
||||
`guest_participants` JSON NULL,
|
||||
`result_details` JSON NULL,
|
||||
`players_ready` JSON NULL,
|
||||
`players_planned` JSON NULL,
|
||||
`players_played` JSON NULL,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_friendly_match_club_date` (`club_id`, `date`),
|
||||
KEY `idx_friendly_match_completed` (`club_id`, `is_completed`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
132
backend/models/FriendlyMatch.js
Normal file
132
backend/models/FriendlyMatch.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const FriendlyMatch = sequelize.define('FriendlyMatch', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'club_id',
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
},
|
||||
time: {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
},
|
||||
homeTeamName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'home_team_name',
|
||||
},
|
||||
guestTeamName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
field: 'guest_team_name',
|
||||
},
|
||||
locationName: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_name',
|
||||
},
|
||||
locationAddress: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_address',
|
||||
},
|
||||
locationCity: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
field: 'location_city',
|
||||
},
|
||||
locationZip: {
|
||||
type: DataTypes.STRING(32),
|
||||
allowNull: true,
|
||||
field: 'location_zip',
|
||||
},
|
||||
matchSystem: {
|
||||
type: DataTypes.STRING(120),
|
||||
allowNull: false,
|
||||
defaultValue: 'Braunschweiger System',
|
||||
field: 'match_system',
|
||||
},
|
||||
singlesCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 12,
|
||||
field: 'singles_count',
|
||||
},
|
||||
doublesCount: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 4,
|
||||
field: 'doubles_count',
|
||||
},
|
||||
winningSets: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 3,
|
||||
field: 'winning_sets',
|
||||
},
|
||||
homeMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'home_match_points',
|
||||
},
|
||||
guestMatchPoints: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: 'guest_match_points',
|
||||
},
|
||||
isCompleted: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
field: 'is_completed',
|
||||
},
|
||||
homeParticipants: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'home_participants',
|
||||
},
|
||||
guestParticipants: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'guest_participants',
|
||||
},
|
||||
resultDetails: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'result_details',
|
||||
},
|
||||
playersReady: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_ready',
|
||||
},
|
||||
playersPlanned: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_planned',
|
||||
},
|
||||
playersPlayed: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'players_played',
|
||||
},
|
||||
}, {
|
||||
tableName: 'friendly_match',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default FriendlyMatch;
|
||||
@@ -57,6 +57,7 @@ import BillingRun from './BillingRun.js';
|
||||
import BillingDocument from './BillingDocument.js';
|
||||
import BillingDocumentValue from './BillingDocumentValue.js';
|
||||
import BillingUserSetting from './BillingUserSetting.js';
|
||||
import FriendlyMatch from './FriendlyMatch.js';
|
||||
import MemberTtrHistory from './MemberTtrHistory.js';
|
||||
import MemberPlayInterest from './MemberPlayInterest.js';
|
||||
import ClickTtAccount from './ClickTtAccount.js';
|
||||
@@ -451,6 +452,7 @@ export {
|
||||
BillingDocument,
|
||||
BillingDocumentValue,
|
||||
BillingUserSetting,
|
||||
FriendlyMatch,
|
||||
MemberTtrHistory,
|
||||
MemberPlayInterest,
|
||||
ClickTtAccount,
|
||||
|
||||
22
backend/routes/friendlyMatchRoutes.js
Normal file
22
backend/routes/friendlyMatchRoutes.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
createFriendlyMatch,
|
||||
deleteFriendlyMatch,
|
||||
getFriendlyMatchMembers,
|
||||
listFriendlyMatches,
|
||||
updateFriendlyMatch,
|
||||
updateFriendlyMatchPlayers,
|
||||
} from '../controllers/friendlyMatchController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get('/:clubId', authenticate, authorize('schedule', 'read'), listFriendlyMatches);
|
||||
router.post('/:clubId', authenticate, authorize('schedule', 'write'), createFriendlyMatch);
|
||||
router.put('/:clubId/:matchId', authenticate, authorize('schedule', 'write'), updateFriendlyMatch);
|
||||
router.delete('/:clubId/:matchId', authenticate, authorize('schedule', 'write'), deleteFriendlyMatch);
|
||||
router.patch('/:clubId/:matchId/players', authenticate, authorize('schedule', 'write'), updateFriendlyMatchPlayers);
|
||||
router.get('/:clubId/members/list', authenticate, authorize('schedule', 'read'), getFriendlyMatchMembers);
|
||||
|
||||
export default router;
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, ClubTeamMember, TeamDocument, Group,
|
||||
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory, MemberPlayInterest,
|
||||
MemberOrder, MemberOrderHistory, MemberGroupPhoto, BillingTemplate, BillingTemplateField, BillingRun, BillingDocument, BillingDocumentValue, BillingUserSetting, TrainingCancellation
|
||||
MemberOrder, MemberOrderHistory, MemberGroupPhoto, BillingTemplate, BillingTemplateField, BillingRun, BillingDocument, BillingDocumentValue, BillingUserSetting, FriendlyMatch, TrainingCancellation
|
||||
, CalendarEvent
|
||||
} from './models/index.js';
|
||||
import authRoutes from './routes/authRoutes.js';
|
||||
@@ -60,6 +60,7 @@ import trainingCancellationRoutes from './routes/trainingCancellationRoutes.js';
|
||||
import memberOrderRoutes from './routes/memberOrderRoutes.js';
|
||||
import memberGroupPhotoRoutes from './routes/memberGroupPhotoRoutes.js';
|
||||
import billingRoutes from './routes/billingRoutes.js';
|
||||
import friendlyMatchRoutes from './routes/friendlyMatchRoutes.js';
|
||||
import calendarRoutes from './routes/calendarRoutes.js';
|
||||
import calendarEventRoutes from './routes/calendarEventRoutes.js';
|
||||
import schedulerService from './services/schedulerService.js';
|
||||
@@ -311,6 +312,7 @@ app.use('/api/training-cancellations', trainingCancellationRoutes);
|
||||
app.use('/api/member-orders', memberOrderRoutes);
|
||||
app.use('/api/member-group-photos', memberGroupPhotoRoutes);
|
||||
app.use('/api/billing', billingRoutes);
|
||||
app.use('/api/friendly-matches', friendlyMatchRoutes);
|
||||
app.use('/api/calendar', calendarRoutes);
|
||||
app.use('/api/calendar-events', calendarEventRoutes);
|
||||
|
||||
@@ -566,6 +568,7 @@ app.use((err, req, res, next) => {
|
||||
await safeSync(BillingDocument);
|
||||
await safeSync(BillingDocumentValue);
|
||||
await safeSync(BillingUserSetting);
|
||||
await safeSync(FriendlyMatch);
|
||||
await safeSync(ClubTeam);
|
||||
await safeSync(TrainingCancellation);
|
||||
await safeSync(CalendarEvent);
|
||||
|
||||
214
backend/services/friendlyMatchService.js
Normal file
214
backend/services/friendlyMatchService.js
Normal file
@@ -0,0 +1,214 @@
|
||||
import FriendlyMatch from '../models/FriendlyMatch.js';
|
||||
import Member from '../models/Member.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
function cleanString(value, fallback = '') {
|
||||
const text = String(value ?? '').trim();
|
||||
return text || fallback;
|
||||
}
|
||||
|
||||
function cleanOptionalString(value) {
|
||||
const text = String(value ?? '').trim();
|
||||
return text || null;
|
||||
}
|
||||
|
||||
function normalizeParticipantList(list) {
|
||||
if (typeof list === 'string') {
|
||||
try {
|
||||
list = JSON.parse(list);
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(list)) return [];
|
||||
return list
|
||||
.map((entry) => {
|
||||
const type = entry?.type === 'member' ? 'member' : 'manual';
|
||||
if (type === 'member') {
|
||||
const memberId = Number.parseInt(entry?.memberId, 10);
|
||||
if (!Number.isInteger(memberId)) return null;
|
||||
return { type, memberId };
|
||||
}
|
||||
const firstName = cleanString(entry?.firstName);
|
||||
const lastName = cleanString(entry?.lastName);
|
||||
if (!firstName && !lastName) return null;
|
||||
return { type, firstName, lastName };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function normalizeArrayValue(value) {
|
||||
if (Array.isArray(value)) return value;
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function normalizeIdList(list) {
|
||||
if (typeof list === 'string') {
|
||||
try {
|
||||
list = JSON.parse(list);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(list)) return null;
|
||||
const seen = new Set();
|
||||
const result = [];
|
||||
for (const value of list) {
|
||||
const id = Number.parseInt(value, 10);
|
||||
if (!Number.isInteger(id) || seen.has(id)) continue;
|
||||
seen.add(id);
|
||||
result.push(id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function toScheduleRow(match) {
|
||||
return {
|
||||
id: match.id,
|
||||
friendlyMatchId: match.id,
|
||||
isFriendly: true,
|
||||
date: match.date,
|
||||
time: match.time,
|
||||
homeTeam: { name: match.homeTeamName },
|
||||
guestTeam: { name: match.guestTeamName },
|
||||
location: {
|
||||
name: match.locationName || 'N/A',
|
||||
address: match.locationAddress || '',
|
||||
city: match.locationCity || '',
|
||||
zip: match.locationZip || '',
|
||||
},
|
||||
leagueDetails: { name: 'Freundschaftsspiel' },
|
||||
homeMatchPoints: match.homeMatchPoints || 0,
|
||||
guestMatchPoints: match.guestMatchPoints || 0,
|
||||
isCompleted: match.isCompleted || false,
|
||||
matchSystem: match.matchSystem,
|
||||
singlesCount: match.singlesCount,
|
||||
doublesCount: match.doublesCount,
|
||||
winningSets: match.winningSets,
|
||||
homeParticipants: normalizeParticipantList(match.homeParticipants),
|
||||
guestParticipants: normalizeParticipantList(match.guestParticipants),
|
||||
resultDetails: normalizeArrayValue(match.resultDetails),
|
||||
playersReady: normalizeArrayValue(match.playersReady),
|
||||
playersPlanned: normalizeArrayValue(match.playersPlanned),
|
||||
playersPlayed: normalizeArrayValue(match.playersPlayed),
|
||||
};
|
||||
}
|
||||
|
||||
class FriendlyMatchService {
|
||||
async list(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const matches = await FriendlyMatch.findAll({
|
||||
where: { clubId },
|
||||
order: [['date', 'ASC'], ['time', 'ASC'], ['id', 'ASC']],
|
||||
});
|
||||
return matches.map(toScheduleRow);
|
||||
}
|
||||
|
||||
async create(userToken, clubId, payload = {}) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const homeTeamName = cleanString(payload.homeTeamName);
|
||||
const guestTeamName = cleanString(payload.guestTeamName);
|
||||
const date = cleanString(payload.date);
|
||||
if (!homeTeamName || !guestTeamName || !date) {
|
||||
throw new HttpError('Datum, Heimteam und Gastteam sind Pflichtfelder.', 400);
|
||||
}
|
||||
|
||||
const match = await FriendlyMatch.create({
|
||||
clubId,
|
||||
date,
|
||||
time: cleanOptionalString(payload.time),
|
||||
homeTeamName,
|
||||
guestTeamName,
|
||||
locationName: cleanOptionalString(payload.locationName),
|
||||
locationAddress: cleanOptionalString(payload.locationAddress),
|
||||
locationCity: cleanOptionalString(payload.locationCity),
|
||||
locationZip: cleanOptionalString(payload.locationZip),
|
||||
matchSystem: cleanString(payload.matchSystem, 'Braunschweiger System'),
|
||||
singlesCount: Number.parseInt(payload.singlesCount, 10) || 12,
|
||||
doublesCount: Number.parseInt(payload.doublesCount, 10) || 4,
|
||||
winningSets: Number.parseInt(payload.winningSets, 10) || 3,
|
||||
homeMatchPoints: Number.parseInt(payload.homeMatchPoints, 10) || 0,
|
||||
guestMatchPoints: Number.parseInt(payload.guestMatchPoints, 10) || 0,
|
||||
isCompleted: Boolean(payload.isCompleted),
|
||||
homeParticipants: normalizeParticipantList(payload.homeParticipants),
|
||||
guestParticipants: normalizeParticipantList(payload.guestParticipants),
|
||||
resultDetails: Array.isArray(payload.resultDetails) ? payload.resultDetails : [],
|
||||
playersReady: [],
|
||||
playersPlanned: [],
|
||||
playersPlayed: [],
|
||||
});
|
||||
return toScheduleRow(match);
|
||||
}
|
||||
|
||||
async update(userToken, clubId, matchId, payload = {}) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const match = await FriendlyMatch.findOne({ where: { id: matchId, clubId } });
|
||||
if (!match) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
|
||||
|
||||
const updates = {};
|
||||
for (const field of ['date', 'time', 'homeTeamName', 'guestTeamName', 'locationName', 'locationAddress', 'locationCity', 'locationZip', 'matchSystem']) {
|
||||
if (Object.prototype.hasOwnProperty.call(payload, field)) {
|
||||
updates[field] = field === 'date' || field === 'homeTeamName' || field === 'guestTeamName' || field === 'matchSystem'
|
||||
? cleanString(payload[field])
|
||||
: cleanOptionalString(payload[field]);
|
||||
}
|
||||
}
|
||||
for (const field of ['singlesCount', 'doublesCount', 'winningSets', 'homeMatchPoints', 'guestMatchPoints']) {
|
||||
if (Object.prototype.hasOwnProperty.call(payload, field)) {
|
||||
updates[field] = Number.parseInt(payload[field], 10) || 0;
|
||||
}
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'isCompleted')) updates.isCompleted = Boolean(payload.isCompleted);
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'homeParticipants')) updates.homeParticipants = normalizeParticipantList(payload.homeParticipants);
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'guestParticipants')) updates.guestParticipants = normalizeParticipantList(payload.guestParticipants);
|
||||
if (Object.prototype.hasOwnProperty.call(payload, 'resultDetails')) {
|
||||
updates.resultDetails = Array.isArray(payload.resultDetails) ? payload.resultDetails : [];
|
||||
}
|
||||
|
||||
await match.update(updates);
|
||||
return toScheduleRow(match);
|
||||
}
|
||||
|
||||
async remove(userToken, clubId, matchId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const deleted = await FriendlyMatch.destroy({ where: { id: matchId, clubId } });
|
||||
if (!deleted) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
async updatePlayers(userToken, clubId, matchId, payload = {}) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const match = await FriendlyMatch.findOne({ where: { id: matchId, clubId } });
|
||||
if (!match) throw new HttpError('Freundschaftsspiel nicht gefunden.', 404);
|
||||
|
||||
const ready = normalizeIdList(payload.playersReady);
|
||||
const planned = normalizeIdList(payload.playersPlanned);
|
||||
const played = normalizeIdList(payload.playersPlayed);
|
||||
await match.update({
|
||||
playersReady: ready ?? (match.playersReady || []),
|
||||
playersPlanned: planned ?? (match.playersPlanned || []),
|
||||
playersPlayed: played ?? (match.playersPlayed || []),
|
||||
});
|
||||
return toScheduleRow(match);
|
||||
}
|
||||
|
||||
async members(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
return Member.findAll({
|
||||
where: { clubId, active: true },
|
||||
attributes: ['id', 'firstName', 'lastName', 'gender'],
|
||||
order: [['lastName', 'ASC'], ['firstName', 'ASC']],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new FriendlyMatchService();
|
||||
Reference in New Issue
Block a user