started tournament implementation
This commit is contained in:
133
backend/controllers/tournamentController.js
Normal file
133
backend/controllers/tournamentController.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import tournamentService from "../services/tournamentService.js";
|
||||
|
||||
export const getTournaments = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const clubId = req.params.clubId;
|
||||
try {
|
||||
const tournaments = await tournamentService.getTournaments(token, clubId);
|
||||
res.status(200).json(tournaments);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const addTournament = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentName, date } = req.body;
|
||||
try {
|
||||
const tournament = await tournamentService.addTournament(token, clubId, tournamentName, date);
|
||||
res.status(200).json(tournament);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const addParticipant = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, participant: participantId } = req.body;
|
||||
try {
|
||||
await tournamentService.addParticipant(token, clubId, tournamentId, participantId);
|
||||
const participants = await tournamentService.getParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const getParticipants = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
try {
|
||||
const participants = await tournamentService.getParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const setModus = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, type, numberOfGroups } = req.body;
|
||||
try {
|
||||
await tournamentService.setModus(token, clubId, tournamentId, type, numberOfGroups);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const createGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
try {
|
||||
await tournamentService.createGroups(token, clubId, tournamentId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const fillGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.body;
|
||||
try {
|
||||
const updatedMembers = await tournamentService.fillGroups(token, clubId, tournamentId);
|
||||
res.status(200).json(updatedMembers);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const getGroups = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.query;
|
||||
try {
|
||||
const groups = await tournamentService.getGroupsWithParticipants(token, clubId, tournamentId);
|
||||
res.status(200).json(groups);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
export const getTournament = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.params;
|
||||
try {
|
||||
const tournament = await tournamentService.getTournament(token, clubId, tournamentId);
|
||||
res.status(200).json(tournament);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const getTournamentMatches = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId } = req.params;
|
||||
try {
|
||||
const matches = await tournamentService.getTournamentMatches(token, clubId, tournamentId);
|
||||
res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
export const addMatchResult = async (req, res) => {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubId, tournamentId, matchId, result } = req.body;
|
||||
|
||||
try {
|
||||
await tournamentService.addMatchResult(token, clubId, tournamentId, matchId, result);
|
||||
res.status(200).json({ message: "Result added successfully" });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
};
|
||||
37
backend/models/Tournament.js
Normal file
37
backend/models/Tournament.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const Tournament = sequelize.define('Tournament', {
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
date: {
|
||||
type: DataTypes.DATEONLY,
|
||||
allowNull: false,
|
||||
},
|
||||
type: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
bestOfEndroundSize: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
numberOfGroups: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: 0,
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default Tournament;
|
||||
21
backend/models/TournamentGroup.js
Normal file
21
backend/models/TournamentGroup.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const TournamentGroup = sequelize.define('TournamentGroup', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
tournamentId : {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament_group',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TournamentGroup;
|
||||
31
backend/models/TournamentMatch.js
Normal file
31
backend/models/TournamentMatch.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const TournamentMatch = sequelize.define('TournamentMatch', {
|
||||
tournamentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
groupId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
},
|
||||
round: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
player1Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
player2Id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament_match',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TournamentMatch;
|
||||
26
backend/models/TournamentMember.js
Normal file
26
backend/models/TournamentMember.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const TournamentMember = sequelize.define('TournamentMember', {
|
||||
tournamentId: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: false,
|
||||
allowNull: true
|
||||
},
|
||||
groupId : {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: false,
|
||||
allowNull: true
|
||||
},
|
||||
clubMemberId: {
|
||||
type: DataTypes.INTEGER,
|
||||
autoIncrement: false,
|
||||
allowNull: false
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament_member',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TournamentMember;
|
||||
24
backend/models/TournamentResult.js
Normal file
24
backend/models/TournamentResult.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const TournamentResult = sequelize.define('TournamentResult', {
|
||||
matchId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
set: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
pointsPlayer1: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
pointsPlayer2: {
|
||||
type: DataTypes.INTEGER,
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'tournament_result',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TournamentResult;
|
||||
@@ -21,6 +21,11 @@ import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
import Group from './Group.js';
|
||||
import GroupActivity from './GroupActivity.js';
|
||||
import Tournament from './Tournament.js';
|
||||
import TournamentGroup from './TournamentGroup.js';
|
||||
import TournamentMember from './TournamentMember.js';
|
||||
import TournamentMatch from './TournamentMatch.js';
|
||||
import TournamentResult from './TournamentResult.js';
|
||||
|
||||
User.hasMany(Log, { foreignKey: 'userId' });
|
||||
Log.belongsTo(User, { foreignKey: 'userId' });
|
||||
@@ -114,6 +119,43 @@ DiaryDateTag.belongsTo(DiaryTag, { foreignKey: 'tagId', as: 'tag' });
|
||||
DiaryMemberTag.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDates' });
|
||||
DiaryDate.hasMany(DiaryMemberTag, { foreignKey: 'diaryDateId', as: 'diaryMemberTags' });
|
||||
|
||||
Tournament.belongsTo(Club, { foreignKey: 'clubId', as: 'tournamentclub' });
|
||||
Club.hasMany(Tournament, { foreignKey: 'clubId', as: 'tournaments' });
|
||||
|
||||
TournamentGroup.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournaments' });
|
||||
Tournament.hasMany(TournamentGroup, { foreignKey: 'tournamentId', as: 'tournamentGroups' });
|
||||
|
||||
TournamentMember.belongsTo(TournamentGroup, {
|
||||
foreignKey: 'groupId',
|
||||
targetKey: 'id',
|
||||
as: 'group',
|
||||
constraints: false
|
||||
});
|
||||
TournamentGroup.hasMany(TournamentMember, {
|
||||
foreignKey: 'groupId',
|
||||
as: 'tournamentGroupMembers'
|
||||
});
|
||||
|
||||
TournamentMember.belongsTo(Member, { foreignKey: 'clubMemberId', as: 'member' });
|
||||
Member.hasMany(TournamentMember, { foreignKey: 'clubMemberId', as: 'tournamentGroupMembers' });
|
||||
|
||||
TournamentMember.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
Tournament.hasMany(TournamentMember, { foreignKey: 'tournamentId', as: 'tournamentMembers' });
|
||||
|
||||
TournamentMatch.belongsTo(Tournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
Tournament.hasMany(TournamentMatch, { foreignKey: 'tournamentId', as: 'tournamentMatches' });
|
||||
|
||||
TournamentMatch.belongsTo(TournamentGroup, { foreignKey: 'groupId', as: 'group' });
|
||||
TournamentGroup.hasMany(TournamentMatch, { foreignKey: 'groupId', as: 'tournamentMatches' });
|
||||
|
||||
TournamentResult.belongsTo(TournamentMatch, { foreignKey: 'matchId', as: 'match' });
|
||||
TournamentMatch.hasMany(TournamentResult, { foreignKey: 'matchId', as: 'tournamentResults' });
|
||||
|
||||
TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player1Id', as: 'player1' });
|
||||
TournamentMatch.belongsTo(TournamentMember, { foreignKey: 'player2Id', as: 'player2' });
|
||||
TournamentMember.hasMany(TournamentMatch, { foreignKey: 'player1Id', as: 'player1Matches' });
|
||||
TournamentMember.hasMany(TournamentMatch, { foreignKey: 'player2Id', as: 'player2Matches' });
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
@@ -137,4 +179,9 @@ export {
|
||||
Team,
|
||||
Group,
|
||||
GroupActivity,
|
||||
Tournament,
|
||||
TournamentGroup,
|
||||
TournamentMember,
|
||||
TournamentMatch,
|
||||
TournamentResult,
|
||||
};
|
||||
|
||||
31
backend/routes/tournamentRoutes.js
Normal file
31
backend/routes/tournamentRoutes.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import express from 'express';
|
||||
import {
|
||||
getTournaments,
|
||||
addTournament,
|
||||
addParticipant,
|
||||
getParticipants,
|
||||
setModus,
|
||||
createGroups,
|
||||
fillGroups,
|
||||
getGroups,
|
||||
getTournament,
|
||||
getTournamentMatches,
|
||||
addMatchResult,
|
||||
} from '../controllers/tournamentController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/participant', authenticate, addParticipant);
|
||||
router.post('/participants', authenticate, getParticipants);
|
||||
router.post('/modus', authenticate, setModus);
|
||||
router.put('/groups', authenticate, createGroups);
|
||||
router.post('/groups', authenticate, fillGroups);
|
||||
router.get('/groups', authenticate, getGroups);
|
||||
router.post('/match/result', authenticate, addMatchResult);
|
||||
router.get('/matches/:clubId/:tournamentId', authenticate, getTournamentMatches);
|
||||
router.get('/:clubId/:tournamentId', authenticate, getTournament);
|
||||
router.get('/:clubId', authenticate, getTournaments);
|
||||
router.post('/', authenticate, addTournament);
|
||||
|
||||
export default router;
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, DiaryDateActivity, Match, League, Team, Group,
|
||||
GroupActivity
|
||||
GroupActivity,
|
||||
Tournament,
|
||||
TournamentGroup,
|
||||
TournamentMatch,
|
||||
TournamentResult,
|
||||
TournamentMember
|
||||
} from './models/index.js';
|
||||
import authRoutes from './routes/authRoutes.js';
|
||||
import clubRoutes from './routes/clubRoutes.js';
|
||||
@@ -27,6 +32,7 @@ import Location from './models/Location.js';
|
||||
import groupRoutes from './routes/groupRoutes.js';
|
||||
import diaryDateTagRoutes from './routes/diaryDateTagRoutes.js';
|
||||
import sessionRoutes from './routes/sessionRoutes.js';
|
||||
import tournamentRoutes from './routes/tournamentRoutes.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
@@ -53,6 +59,7 @@ app.use('/api/matches', matchRoutes);
|
||||
app.use('/api/group', groupRoutes);
|
||||
app.use('/api/diarydatetags', diaryDateTagRoutes);
|
||||
app.use('/api/session', sessionRoutes);
|
||||
app.use('/api/tournament', tournamentRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
@@ -88,6 +95,11 @@ app.get('*', (req, res) => {
|
||||
await Match.sync({ alter: true });
|
||||
await Group.sync({ alter: true });
|
||||
await GroupActivity.sync({ alter: true });
|
||||
await Tournament.sync({ alter: true });
|
||||
await TournamentGroup.sync({ alter: true });
|
||||
await TournamentMember.sync({ alter: true });
|
||||
await TournamentMatch.sync({ alter: true });
|
||||
await TournamentResult.sync({ alter: true });
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
|
||||
249
backend/services/tournamentService.js
Normal file
249
backend/services/tournamentService.js
Normal file
@@ -0,0 +1,249 @@
|
||||
import Club from "../models/Club.js";
|
||||
import Member from "../models/Member.js";
|
||||
import Tournament from "../models/Tournament.js";
|
||||
import TournamentGroup from "../models/TournamentGroup.js";
|
||||
import TournamentMatch from "../models/TournamentMatch.js";
|
||||
import TournamentMember from "../models/TournamentMember.js";
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
|
||||
class TournamentService {
|
||||
async getTournaments(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const tournaments = await Tournament.findAll(
|
||||
{
|
||||
where: { clubId },
|
||||
order: [['date', 'DESC']],
|
||||
attributes: ['id', 'name', 'date']
|
||||
}
|
||||
);
|
||||
return JSON.parse(JSON.stringify(tournaments));
|
||||
}
|
||||
|
||||
async addTournament(userToken, clubId, tournamentName, date) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const club = await Club.findByPk(clubId);
|
||||
await Tournament.create({
|
||||
name: tournamentName,
|
||||
date: date,
|
||||
clubId: club.id,
|
||||
bestOfEndroundSize: 0,
|
||||
type: '',
|
||||
name: '',
|
||||
});
|
||||
return await this.getTournaments(userToken, clubId);
|
||||
}
|
||||
|
||||
async addParticipant(token, clubId, tournamentId, participantId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const participant = TournamentMember.findAll({
|
||||
where: { tournamentId: tournamentId, groupId: participantId, clubMemberId: participantId },
|
||||
});
|
||||
if (participant) {
|
||||
throw new Error('Participant already exists');
|
||||
}
|
||||
await TournamentMember.create({
|
||||
tournamentId: tournamentId,
|
||||
groupId: participantId,
|
||||
clubMemberId: participantId,
|
||||
});
|
||||
}
|
||||
|
||||
async getParticipants(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
return await TournamentMember.findAll({
|
||||
where: {
|
||||
tournamentId: tournamentId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'lastName', 'firstName'],
|
||||
order: [['firstName', 'ASC'], ['lastName', 'ASC']],
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
async setModus(token, clubId, tournamentId, type, numberOfGroups) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
await tournament.update({ type, numberOfGroups });
|
||||
}
|
||||
|
||||
async createGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const existingGroups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
const desiredGroupCount = tournament.numberOfGroups;
|
||||
if (existingGroups.length < desiredGroupCount) {
|
||||
const missingGroups = desiredGroupCount - existingGroups.length;
|
||||
for (let i = 0; i < missingGroups; i++) {
|
||||
await TournamentGroup.create({ tournamentId });
|
||||
}
|
||||
} else if (existingGroups.length > desiredGroupCount) {
|
||||
existingGroups.sort((a, b) => a.id - b.id);
|
||||
const groupsToRemove = existingGroups.slice(desiredGroupCount);
|
||||
for (const group of groupsToRemove) {
|
||||
await group.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fillGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
if (!groups || groups.length === 0) {
|
||||
throw new Error('No groups available. Please create groups first.');
|
||||
}
|
||||
const members = await TournamentMember.findAll({ where: { tournamentId } });
|
||||
if (!members || members.length === 0) {
|
||||
throw new Error('No tournament members found.');
|
||||
}
|
||||
await TournamentMatch.destroy({ where: { tournamentId } });
|
||||
const shuffledMembers = [...members];
|
||||
for (let i = shuffledMembers.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[shuffledMembers[i], shuffledMembers[j]] = [shuffledMembers[j], shuffledMembers[i]];
|
||||
}
|
||||
const numberOfGroups = groups.length;
|
||||
for (let i = 0; i < shuffledMembers.length; i++) {
|
||||
const groupAssignment = groups[i % numberOfGroups].id;
|
||||
await shuffledMembers[i].update({ groupId: groupAssignment });
|
||||
}
|
||||
for (const group of groups) {
|
||||
const groupMembers = await TournamentMember.findAll({ where: { groupId: group.id } });
|
||||
for (let i = 0; i < groupMembers.length; i++) {
|
||||
for (let j = i + 1; j < groupMembers.length; j++) {
|
||||
await TournamentMatch.create({
|
||||
tournamentId: tournamentId,
|
||||
groupId: group.id,
|
||||
round: 'group',
|
||||
player1Id: groupMembers[i].id,
|
||||
player2Id: groupMembers[j].id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return await TournamentMember.findAll({ where: { tournamentId } });
|
||||
}
|
||||
|
||||
async getGroups(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({ where: { tournamentId } });
|
||||
return groups;
|
||||
}
|
||||
|
||||
async getGroupsWithParticipants(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findByPk(tournamentId);
|
||||
if (!tournament || tournament.clubId != clubId) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const groups = await TournamentGroup.findAll({
|
||||
where: { tournamentId },
|
||||
include: [
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'tournamentGroupMembers',
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'firstName', 'lastName']
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
return groups.map(group => ({
|
||||
groupId: group.id,
|
||||
participants: group.tournamentGroupMembers.map(p => ({
|
||||
id: p.id,
|
||||
name: `${p.member.firstName} ${p.member.lastName}`
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
async getTournament(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findOne({
|
||||
where: { id: tournamentId, clubId },
|
||||
});
|
||||
if (!tournament) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
return tournament;
|
||||
}
|
||||
|
||||
async getTournamentMatches(token, clubId, tournamentId) {
|
||||
await checkAccess(token, clubId);
|
||||
const tournament = await Tournament.findOne({
|
||||
where: { id: tournamentId, clubId },
|
||||
});
|
||||
if (!tournament) {
|
||||
throw new Error('Tournament not found');
|
||||
}
|
||||
const matches = await TournamentMatch.findAll({
|
||||
where: { tournamentId },
|
||||
include: [
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'player1',
|
||||
include: {
|
||||
model: Member,
|
||||
as: 'member',
|
||||
}
|
||||
},
|
||||
{
|
||||
model: TournamentMember,
|
||||
as: 'player2',
|
||||
include: {
|
||||
model: Member,
|
||||
as: 'member',
|
||||
}
|
||||
}
|
||||
],
|
||||
order: [['id', 'ASC']]
|
||||
});
|
||||
return matches;
|
||||
}
|
||||
|
||||
async addMatchResult(token, clubId, tournamentId, matchId, result) {
|
||||
await checkAccess(token, clubId);
|
||||
|
||||
const match = await TournamentMatch.findByPk(matchId);
|
||||
if (!match || match.tournamentId != tournamentId) {
|
||||
throw new Error("Match not found or doesn't belong to this tournament");
|
||||
}
|
||||
|
||||
match.result = result;
|
||||
await match.save();
|
||||
}
|
||||
}
|
||||
|
||||
export default new TournamentService();
|
||||
@@ -22,6 +22,7 @@
|
||||
<a href="/diary">Tagebuch</a>
|
||||
<a href="/pending-approvals">Freigaben</a>
|
||||
<a href="/schedule">Spielpläne</a>
|
||||
<a href="/tournaments">Turniere</a>
|
||||
</div>
|
||||
|
||||
<div class="logout-btn">
|
||||
|
||||
@@ -9,6 +9,7 @@ import MembersView from './views/MembersView.vue';
|
||||
import DiaryView from './views/DiaryView.vue';
|
||||
import PendingApprovalsView from './views/PendingApprovalsView.vue';
|
||||
import ScheduleView from './views/ScheduleView.vue';
|
||||
import TournamentsView from './views/TournamentsView.vue';
|
||||
|
||||
const routes = [
|
||||
{ path: '/register', component: Register },
|
||||
@@ -21,6 +22,7 @@ const routes = [
|
||||
{ path: '/diary', component: DiaryView },
|
||||
{ path: '/pending-approvals', component: PendingApprovalsView},
|
||||
{ path: '/schedule', component: ScheduleView},
|
||||
{ path: '/tournaments', component: TournamentsView },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -1004,23 +1004,11 @@ export default {
|
||||
},
|
||||
|
||||
playBellSound() {
|
||||
this.bellSound.play()
|
||||
.then(() => {
|
||||
console.log("Bell sound played successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error playing bell sound:", error);
|
||||
});
|
||||
this.bellSound.play();
|
||||
},
|
||||
|
||||
playThumbSound() {
|
||||
this.thumbSound.play()
|
||||
.then(() => {
|
||||
console.log("Thumb sound played successfully");
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error playing thumb sound:", error);
|
||||
});
|
||||
this.thumbSound.play();
|
||||
},
|
||||
|
||||
calculateIntermediateTimes() {
|
||||
|
||||
289
frontend/src/views/TournamentsView.vue
Normal file
289
frontend/src/views/TournamentsView.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Turnier</h2>
|
||||
<div>
|
||||
<div>
|
||||
<h3>Datum</h3>
|
||||
<div>
|
||||
<select v-model="selectedDate">
|
||||
<option value="new">Neues Turnier</option>
|
||||
<option v-for="date in dates" :key="date.id" :value="date.id">
|
||||
{{ new Date(date.date).toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<template v-if="selectedDate === 'new'">
|
||||
<div>
|
||||
<input type="date" v-model="newDate" />
|
||||
<button @click="createTournament">Erstellen</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div>
|
||||
<h3>Turnier</h3>
|
||||
<div>
|
||||
<div>
|
||||
<label>
|
||||
<input type="checkbox" v-model="isGroupTournament">
|
||||
Spielen in Gruppen
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Teilnehmer</h4>
|
||||
<ul>
|
||||
<li v-for="participant in participants" :key="participant.id">
|
||||
{{ participant.member.firstName }} {{ participant.member.lastName }}
|
||||
</li>
|
||||
</ul>
|
||||
<select v-model="selectedMember">
|
||||
<option v-for="member in clubMembers" :key="member.id" :value="member.id">
|
||||
{{ member.firstName }} {{ member.lastName }}
|
||||
</option>
|
||||
</select>
|
||||
<button type="button" @click="addParticipant">Hinzufügen</button>
|
||||
</div>
|
||||
<div v-if="isGroupTournament && participants.length > 1">
|
||||
<label>
|
||||
Anzahl Gruppen:
|
||||
<input type="number" v-model="numberOfGroups">
|
||||
</label>
|
||||
<button @click="createGroups">Gruppen erstellen</button>
|
||||
<button @click="randomizeGroups">Zufällig verteilen</button>
|
||||
</div>
|
||||
<div v-if="groups && groups.length > 0">
|
||||
<h4>Gruppen</h4>
|
||||
<ul class="groupoverview">
|
||||
<li v-for="group in groups" :key="group.groupId">
|
||||
<h4>Gruppe {{ group.groupId }}</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Spielername</th>
|
||||
<th>Bilanz</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="participant in group.participants" :key="participant.id">
|
||||
<td>{{ participant.name }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Begegnung</th>
|
||||
<th>Sätze</th>
|
||||
<th>Ergebnis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="match in matches" :key="match.id">
|
||||
<td>{{ match.groupId ? "Gr " + match.groupId : match.round }}</td>
|
||||
<td>{{ getPlayerName(match.player1) }} - {{ getPlayerName(match.player2) }}</td>
|
||||
<td><input size="5" type="text" v-model="match.result" @keyup.enter="saveMatchResult(match, match.result)" /></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import apiClient from '../apiClient';
|
||||
|
||||
export default {
|
||||
name: 'TournamentsView',
|
||||
computed: {
|
||||
...mapGetters(['isAuthenticated', 'currentClub', 'clubs']),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedDate: 'new',
|
||||
newDate: '',
|
||||
dates: [],
|
||||
participants: [],
|
||||
selectedMember: null,
|
||||
clubMembers: [],
|
||||
numberOfGroups: 1,
|
||||
isGroupTournament: false,
|
||||
groups: [],
|
||||
matches: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selectedDate: async function (newVal) {
|
||||
if (newVal !== 'new') {
|
||||
try {
|
||||
const groupResponse = await apiClient.get(`/tournament/${this.currentClub}/${newVal}`);
|
||||
this.isGroupTournament = groupResponse.data.type === 'groups';
|
||||
const participantsResponse = await apiClient.post('/tournament/participants', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: newVal,
|
||||
});
|
||||
this.participants = participantsResponse.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
await this.fetchGroups();
|
||||
}
|
||||
},
|
||||
isGroupTournament: async function (newVal) {
|
||||
if (newVal) {
|
||||
this.numberOfGroups = 2;
|
||||
} else {
|
||||
this.numberOfGroups = 1;
|
||||
}
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: newVal ? 'groups' : 'bestOf',
|
||||
numberOfGroups: this.numberOfGroups,
|
||||
});
|
||||
},
|
||||
numberOfGroups: async function (newVal) {
|
||||
await apiClient.post('/tournament/modus', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
type: this.isGroupTournament ? 'groups' : 'bestOf',
|
||||
numberOfGroups: newVal,
|
||||
});
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
if (!this.isAuthenticated) {
|
||||
this.$router.push('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const responseDates = await apiClient.get(`/tournament/${this.currentClub}`);
|
||||
this.dates = responseDates.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching tournaments:', error);
|
||||
}
|
||||
|
||||
try {
|
||||
const responseMembers = await apiClient.get(`/clubmembers/get/${this.currentClub}/false`);
|
||||
this.clubMembers = responseMembers.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching club members:', error);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createTournament() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament', {
|
||||
clubId: this.currentClub,
|
||||
name: this.newDate,
|
||||
date: this.newDate,
|
||||
});
|
||||
this.dates = response.data;
|
||||
this.newDate = '';
|
||||
} catch (error) {
|
||||
console.error('Error creating tournament:', error);
|
||||
}
|
||||
},
|
||||
async addParticipant() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/participant', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
participant: this.selectedMember,
|
||||
});
|
||||
this.participants = response.data;
|
||||
} catch (error) {
|
||||
console.error('Error adding participant:', error);
|
||||
}
|
||||
},
|
||||
async createGroups() {
|
||||
await apiClient.put('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
});
|
||||
await this.fetchGroups();
|
||||
},
|
||||
async randomizeGroups() {
|
||||
try {
|
||||
const response = await apiClient.post('/tournament/groups', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error randomizing groups:', error);
|
||||
}
|
||||
await this.fetchGroups();
|
||||
},
|
||||
async fetchGroups() {
|
||||
try {
|
||||
const response = await apiClient.get('/tournament/groups', {
|
||||
params: {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate
|
||||
}
|
||||
});
|
||||
this.groups = response.data;
|
||||
const matchesResponse = await apiClient.get(`/tournament/matches/${this.currentClub}/${this.selectedDate}`);
|
||||
this.matches = matchesResponse.data;
|
||||
console.log(this.matches);
|
||||
} catch (error) {
|
||||
console.error('Error fetching groups:', error);
|
||||
}
|
||||
},
|
||||
getPlayerName(player) {
|
||||
return player.member.firstName + ' ' + player.member.lastName;
|
||||
},
|
||||
async saveMatchResult(match, result) {
|
||||
try {
|
||||
await apiClient.post('/tournament/match/result', {
|
||||
clubId: this.currentClub,
|
||||
tournamentId: this.selectedDate,
|
||||
matchId: match.matchId,
|
||||
result: result,
|
||||
});
|
||||
match.result = result; // Aktualisiere das Match in der UI
|
||||
} catch (error) {
|
||||
console.error('Error saving match result:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tournaments {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
.groupoverview {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
justify-content: left;
|
||||
padding: 0;
|
||||
}
|
||||
.groupoverview li {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user