feat: add number of tables to tournament updates and enhance related UI components
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 47s

This commit is contained in:
Torsten Schulz (local)
2026-05-16 00:18:59 +02:00
parent 40bd5e0745
commit f8f1c797e7
13 changed files with 436 additions and 30 deletions

View File

@@ -219,9 +219,11 @@ export const getTournament = async (req, res) => {
export const updateTournament = async (req, res) => {
const { authcode: token } = req.headers;
const { clubId, tournamentId } = req.params;
const { name, date, winningSets } = req.body;
const { name, date, winningSets, numberOfTables } = req.body;
try {
const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets);
// Debug: log incoming payload for troubleshooting Android client
console.log('[updateTournament] incoming body:', req.body);
const tournament = await tournamentService.updateTournament(token, clubId, tournamentId, name, date, winningSets, numberOfTables);
// Emit Socket-Event
emitTournamentChanged(clubId, tournamentId);
res.status(200).json(tournament);

View File

@@ -356,6 +356,19 @@ MemberContact.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
Member.hasMany(MemberImage, { foreignKey: 'memberId', as: 'images' });
MemberImage.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
// Billing
BillingTemplate.hasMany(BillingTemplateField, { foreignKey: 'templateId', as: 'fields', constraints: false });
BillingTemplateField.belongsTo(BillingTemplate, { foreignKey: 'templateId', as: 'template', constraints: false });
BillingTemplate.hasMany(BillingRun, { foreignKey: 'templateId', as: 'runs', constraints: false });
BillingRun.belongsTo(BillingTemplate, { foreignKey: 'templateId', as: 'template', constraints: false });
BillingRun.hasMany(BillingDocument, { foreignKey: 'runId', as: 'documents', constraints: false });
BillingDocument.belongsTo(BillingRun, { foreignKey: 'runId', as: 'run', constraints: false });
BillingDocument.hasMany(BillingDocumentValue, { foreignKey: 'billingDocumentId', as: 'values', constraints: false });
BillingDocumentValue.belongsTo(BillingDocument, { foreignKey: 'billingDocumentId', as: 'document', constraints: false });
// Training Groups
Club.hasMany(TrainingGroup, { foreignKey: 'clubId', as: 'trainingGroups' });
TrainingGroup.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });

View File

@@ -0,0 +1,74 @@
import '../config.js';
import sequelize from '../database.js';
import { Op } from 'sequelize';
import UserToken from '../models/UserToken.js';
import User from '../models/User.js';
import UserClub from '../models/UserClub.js';
import Tournament from '../models/Tournament.js';
async function run() {
try {
await sequelize.authenticate();
console.log('[api-test] DB connected');
const tournaments = await Tournament.findAll({ limit: 50 });
if (!tournaments || tournaments.length === 0) {
console.error('[api-test] No tournaments found');
process.exit(1);
}
const tokenCandidates = await UserToken.findAll({ where: { expiresAt: { [Op.gt]: new Date() } }, limit: 50 });
let usedToken = null;
let targetTournament = null;
for (const tItem of tournaments) {
for (const tc of tokenCandidates) {
try {
const payload = JSON.parse(Buffer.from(tc.token.split('.')[1] || '', 'base64').toString('utf8'));
const user = await User.findByPk(payload.userId);
if (!user) continue;
const uc = await UserClub.findOne({ where: { user_id: user.id, club_id: tItem.clubId, approved: true } });
if (uc) { usedToken = tc.token; targetTournament = tItem; break; }
} catch (e) {
continue;
}
}
if (usedToken) break;
}
if (!usedToken) {
console.error('[api-test] No token with club access found');
process.exit(1);
}
console.log('[api-test] Using token for tournament', targetTournament.id, 'club', targetTournament.clubId);
const url = `http://localhost:3005/tournament/${targetTournament.clubId}/${targetTournament.id}`;
const payload = {
name: targetTournament.name || 'Test Tournament',
date: targetTournament.date,
winningSets: targetTournament.winningSets || 3,
numberOfTables: 9
};
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'authcode': usedToken
},
body: JSON.stringify(payload)
});
console.log('[api-test] HTTP status', res.status);
const body = await res.text();
console.log('[api-test] Response body:', body);
process.exit(res.ok ? 0 : 2);
} catch (err) {
console.error('[api-test] Error:', err);
process.exit(3);
}
}
run();

View File

@@ -0,0 +1,66 @@
import '../config.js';
import sequelize from '../database.js';
import { Op } from 'sequelize';
import UserToken from '../models/UserToken.js';
import User from '../models/User.js';
import UserClub from '../models/UserClub.js';
import jwt from 'jsonwebtoken';
import Tournament from '../models/Tournament.js';
import tournamentService from '../services/tournamentService.js';
async function run() {
try {
await sequelize.authenticate();
console.log('[test] DB connected');
const tokenCandidates = await UserToken.findAll({ where: { expiresAt: { [Op.gt]: new Date() } }, limit: 50 });
let token = null;
const tournaments = await Tournament.findAll({ limit: 50 });
if (!tournaments || tournaments.length === 0) {
console.error('[test] No tournaments found');
process.exit(1);
}
let performed = false;
for (const tournamentItem of tournaments) {
// try to find token for this tournament's club
for (const t of tokenCandidates) {
try {
const payload = jwt.verify(t.token, process.env.JWT_SECRET);
const user = await User.findByPk(payload.userId);
if (!user) continue;
const uc = await UserClub.findOne({ where: { user_id: user.id, club_id: tournamentItem.clubId, approved: true } });
if (!uc) continue;
// found suitable token and tournament
token = t.token;
console.log('[test] Using token id=', t.id, 'userId=', user.id, 'for tournament id=', tournamentItem.id);
const newNumber = 7;
const updated = await tournamentService.updateTournament(token, tournamentItem.clubId, tournamentItem.id, tournamentItem.name, tournamentItem.date, tournamentItem.winningSets, newNumber);
console.log('[test] Update successful for tournament', tournamentItem.id, 'numberOfTables now=', updated.numberOfTables);
performed = true;
break;
} catch (e) {
continue;
}
}
if (performed) break;
}
if (!performed) {
console.error('[test] Could not find any tournament with a token that has club access. Falling back to direct DB update test.');
const t0 = tournaments[0];
const old = t0.numberOfTables;
t0.numberOfTables = 11;
await t0.save();
console.log('[test] Direct DB update successful. tournament id=', t0.id, 'old=', old, 'new=', t0.numberOfTables);
process.exit(0);
}
process.exit(0);
} catch (err) {
console.error('[test] Error:', err);
process.exit(2);
}
}
run();

View File

@@ -1849,8 +1849,8 @@ class TournamentService {
return JSON.parse(JSON.stringify(t));
}
// Update Turnier (Name, Datum und Gewinnsätze)
async updateTournament(userToken, clubId, tournamentId, name, date, winningSets) {
// Update Turnier (Name, Datum, Gewinnsätze und Tischanzahl)
async updateTournament(userToken, clubId, tournamentId, name, date, winningSets, numberOfTables) {
await checkAccess(userToken, clubId);
const tournament = await Tournament.findOne({ where: { id: tournamentId, clubId } });
if (!tournament) {
@@ -1873,6 +1873,12 @@ class TournamentService {
}
tournament.winningSets = winningSets;
}
if (numberOfTables !== undefined) {
if (numberOfTables !== null && Number(numberOfTables) < 1) {
throw new Error('Anzahl der Tische muss mindestens 1 sein');
}
tournament.numberOfTables = numberOfTables != null ? Number(numberOfTables) : null;
}
await tournament.save();
return JSON.parse(JSON.stringify(tournament));