Erweitert die Backend- und Frontend-Funktionalität zur Unterstützung von Teams und Saisons. Fügt neue Routen für Team- und Club-Team-Management hinzu, aktualisiert die Match- und Team-Modelle zur Berücksichtigung von Saisons, und implementiert die Saison-Auswahl in der Benutzeroberfläche. Optimiert die Logik zur Abfrage von Ligen und Spielen basierend auf der ausgewählten Saison.

This commit is contained in:
Torsten Schulz (local)
2025-10-01 22:47:13 +02:00
parent f8f4d23c4e
commit a6493990d3
23 changed files with 2309 additions and 105 deletions

View File

@@ -0,0 +1,137 @@
import ClubTeamService from '../services/clubTeamService.js';
import { getUserByToken } from '../utils/userUtils.js';
import { devLog } from '../utils/logger.js';
export const getClubTeams = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { seasonid: seasonId } = req.query;
devLog('[getClubTeams] - Getting club teams for club:', clubId, 'season:', seasonId);
const user = await getUserByToken(token);
// Check if user has access to this club
const clubTeams = await ClubTeamService.getAllClubTeamsByClub(clubId, seasonId);
devLog('[getClubTeams] - Found club teams:', clubTeams.length);
res.status(200).json(clubTeams);
} catch (error) {
console.error('[getClubTeams] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const getClubTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubteamid: clubTeamId } = req.params;
devLog('[getClubTeam] - Getting club team:', clubTeamId);
const user = await getUserByToken(token);
const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
if (!clubTeam) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json(clubTeam);
} catch (error) {
console.error('[getClubTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const createClubTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { name, leagueId, seasonId } = req.body;
devLog('[createClubTeam] - Creating club team:', { name, clubId, leagueId, seasonId });
const user = await getUserByToken(token);
if (!name) {
return res.status(400).json({ error: "missingname" });
}
const clubTeamData = {
name,
clubId: parseInt(clubId),
leagueId: leagueId ? parseInt(leagueId) : null,
seasonId: seasonId ? parseInt(seasonId) : null
};
const newClubTeam = await ClubTeamService.createClubTeam(clubTeamData);
devLog('[createClubTeam] - Club team created with ID:', newClubTeam.id);
res.status(201).json(newClubTeam);
} catch (error) {
console.error('[createClubTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const updateClubTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubteamid: clubTeamId } = req.params;
const { name, leagueId, seasonId } = req.body;
devLog('[updateClubTeam] - Updating club team:', clubTeamId, { name, leagueId, seasonId });
const user = await getUserByToken(token);
const updateData = {};
if (name !== undefined) updateData.name = name;
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
const success = await ClubTeamService.updateClubTeam(clubTeamId, updateData);
if (!success) {
return res.status(404).json({ error: "notfound" });
}
const updatedClubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
res.status(200).json(updatedClubTeam);
} catch (error) {
console.error('[updateClubTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const deleteClubTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubteamid: clubTeamId } = req.params;
devLog('[deleteClubTeam] - Deleting club team:', clubTeamId);
const user = await getUserByToken(token);
const success = await ClubTeamService.deleteClubTeam(clubTeamId);
if (!success) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json({ message: "Club team deleted successfully" });
} catch (error) {
console.error('[deleteClubTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const getLeagues = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { seasonid: seasonId } = req.query;
devLog('[getLeagues] - Getting leagues for club:', clubId, 'season:', seasonId);
const user = await getUserByToken(token);
const leagues = await ClubTeamService.getLeaguesByClub(clubId, seasonId);
devLog('[getLeagues] - Found leagues:', leagues.length);
res.status(200).json(leagues);
} catch (error) {
console.error('[getLeagues] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};

View File

@@ -25,7 +25,8 @@ export const getLeaguesForCurrentSeason = async (req, res) => {
devLog(req.headers, req.params);
const { authcode: userToken } = req.headers;
const { clubId } = req.params;
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId);
const { seasonid: seasonId } = req.query;
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId, seasonId);
return res.status(200).json(leagues);
} catch (error) {
console.error('Error retrieving leagues:', error);
@@ -37,7 +38,8 @@ export const getMatchesForLeagues = async (req, res) => {
try {
const { authcode: userToken } = req.headers;
const { clubId } = req.params;
const matches = await MatchService.getMatchesForLeagues(userToken, clubId);
const { seasonid: seasonId } = req.query;
const matches = await MatchService.getMatchesForLeagues(userToken, clubId, seasonId);
return res.status(200).json(matches);
} catch (error) {
console.error('Error retrieving matches:', error);

View File

@@ -0,0 +1,111 @@
import SeasonService from '../services/seasonService.js';
import { getUserByToken } from '../utils/userUtils.js';
import { devLog } from '../utils/logger.js';
export const getSeasons = async (req, res) => {
try {
const { authcode: token } = req.headers;
devLog('[getSeasons] - Getting all seasons');
const user = await getUserByToken(token);
const seasons = await SeasonService.getAllSeasons();
devLog('[getSeasons] - Found seasons:', seasons.length);
res.status(200).json(seasons);
} catch (error) {
console.error('[getSeasons] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const getCurrentSeason = async (req, res) => {
try {
const { authcode: token } = req.headers;
devLog('[getCurrentSeason] - Getting current season');
const user = await getUserByToken(token);
const season = await SeasonService.getOrCreateCurrentSeason();
devLog('[getCurrentSeason] - Current season:', season.season);
res.status(200).json(season);
} catch (error) {
console.error('[getCurrentSeason] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const createSeason = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { season } = req.body;
devLog('[createSeason] - Creating season:', season);
const user = await getUserByToken(token);
if (!season) {
return res.status(400).json({ error: "missingseason" });
}
// Validiere Saison-Format (z.B. "2023/2024")
const seasonRegex = /^\d{4}\/\d{4}$/;
if (!seasonRegex.test(season)) {
return res.status(400).json({ error: "invalidseasonformat" });
}
const newSeason = await SeasonService.createSeason(season);
devLog('[createSeason] - Season created with ID:', newSeason.id);
res.status(201).json(newSeason);
} catch (error) {
console.error('[createSeason] - Error:', error);
if (error.message === 'Season already exists') {
res.status(409).json({ error: "alreadyexists" });
} else {
res.status(500).json({ error: "internalerror" });
}
}
};
export const getSeason = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { seasonid: seasonId } = req.params;
devLog('[getSeason] - Getting season:', seasonId);
const user = await getUserByToken(token);
const season = await SeasonService.getSeasonById(seasonId);
if (!season) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json(season);
} catch (error) {
console.error('[getSeason] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const deleteSeason = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { seasonid: seasonId } = req.params;
devLog('[deleteSeason] - Deleting season:', seasonId);
const user = await getUserByToken(token);
const success = await SeasonService.deleteSeason(seasonId);
if (!success) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json({ message: "deleted" });
} catch (error) {
console.error('[deleteSeason] - Error:', error);
if (error.message === 'Season is used by teams' || error.message === 'Season is used by leagues') {
res.status(409).json({ error: "seasoninuse" });
} else {
res.status(500).json({ error: "internalerror" });
}
}
};

View File

@@ -0,0 +1,139 @@
import TeamService from '../services/teamService.js';
import { getUserByToken } from '../utils/userUtils.js';
import { devLog } from '../utils/logger.js';
export const getTeams = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { seasonid: seasonId } = req.query;
devLog('[getTeams] - Getting teams for club:', clubId, 'season:', seasonId);
const user = await getUserByToken(token);
// Check if user has access to this club
const teams = await TeamService.getAllTeamsByClub(clubId, seasonId);
devLog('[getTeams] - Found teams:', teams.length);
res.status(200).json(teams);
} catch (error) {
console.error('[getTeams] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const getTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { teamid: teamId } = req.params;
devLog('[getTeam] - Getting team:', teamId);
const user = await getUserByToken(token);
const team = await TeamService.getTeamById(teamId);
if (!team) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json(team);
} catch (error) {
console.error('[getTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const createTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { name, leagueId, seasonId } = req.body;
devLog('[createTeam] - Creating team:', { name, clubId, leagueId, seasonId });
const user = await getUserByToken(token);
if (!name) {
return res.status(400).json({ error: "missingname" });
}
const teamData = {
name,
clubId: parseInt(clubId),
leagueId: leagueId ? parseInt(leagueId) : null,
seasonId: seasonId ? parseInt(seasonId) : null
};
const newTeam = await TeamService.createTeam(teamData);
devLog('[createTeam] - Team created with ID:', newTeam.id);
res.status(201).json(newTeam);
} catch (error) {
console.error('[createTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const updateTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { teamid: teamId } = req.params;
const { name, leagueId, seasonId } = req.body;
devLog('[updateTeam] - Updating team:', teamId, { name, leagueId, seasonId });
const user = await getUserByToken(token);
const updateData = {};
if (name !== undefined) updateData.name = name;
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
const success = await TeamService.updateTeam(teamId, updateData);
if (!success) {
return res.status(404).json({ error: "notfound" });
}
const updatedTeam = await TeamService.getTeamById(teamId);
res.status(200).json(updatedTeam);
} catch (error) {
console.error('[updateTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const deleteTeam = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { teamid: teamId } = req.params;
devLog('[deleteTeam] - Deleting team:', teamId);
const user = await getUserByToken(token);
const success = await TeamService.deleteTeam(teamId);
if (!success) {
return res.status(404).json({ error: "notfound" });
}
res.status(200).json({ message: "deleted" });
} catch (error) {
console.error('[deleteTeam] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};
export const getLeagues = async (req, res) => {
try {
const { authcode: token } = req.headers;
const { clubid: clubId } = req.params;
const { seasonid: seasonId } = req.query;
devLog('[getLeagues] - Getting leagues for club:', clubId, 'season:', seasonId);
const user = await getUserByToken(token);
const leagues = await TeamService.getLeaguesByClub(clubId, seasonId);
devLog('[getLeagues] - Found leagues:', leagues.length);
res.status(200).json(leagues);
} catch (error) {
console.error('[getLeagues] - Error:', error);
res.status(500).json({ error: "internalerror" });
}
};

View File

@@ -0,0 +1,44 @@
-- Migration: Add season_id to teams table
-- First, add the column as nullable
ALTER TABLE `team` ADD COLUMN `season_id` INT NULL;
-- Get or create current season
SET @current_season_id = (
SELECT id FROM `season`
WHERE season = (
CASE
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
END
)
LIMIT 1
);
-- If no season exists, create it
INSERT IGNORE INTO `season` (season) VALUES (
CASE
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
END
);
-- Get the season ID again (in case we just created it)
SET @current_season_id = (
SELECT id FROM `season`
WHERE season = (
CASE
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
END
)
LIMIT 1
);
-- Update all existing teams to use the current season
UPDATE `team` SET `season_id` = @current_season_id WHERE `season_id` IS NULL;
-- Now make the column NOT NULL and add the foreign key constraint
ALTER TABLE `team` MODIFY COLUMN `season_id` INT NOT NULL;
ALTER TABLE `team` ADD CONSTRAINT `team_season_id_foreign_idx`
FOREIGN KEY (`season_id`) REFERENCES `season` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,54 @@
import { DataTypes } from 'sequelize';
import sequelize from '../database.js';
import Club from './Club.js';
import League from './League.js';
import Season from './Season.js';
const ClubTeam = sequelize.define('ClubTeam', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
allowNull: false,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
clubId: {
type: DataTypes.INTEGER,
allowNull: false,
references: {
model: Club,
key: 'id',
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
leagueId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: League,
key: 'id',
},
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
seasonId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: Season,
key: 'id',
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
}, {
underscored: true,
tableName: 'club_team',
timestamps: true,
});
export default ClubTeam;

View File

@@ -3,7 +3,6 @@ import sequelize from '../database.js';
import Club from './Club.js';
import League from './League.js';
import Team from './Team.js';
import Season from './Season.js';
import Location from './Location.js';
const Match = sequelize.define('Match', {
@@ -21,14 +20,6 @@ const Match = sequelize.define('Match', {
type: DataTypes.TIME,
allowNull: true,
},
seasonId: {
type: DataTypes.INTEGER,
references: {
model: Season,
key: 'id',
},
allowNull: false,
},
locationId: {
type: DataTypes.INTEGER,
references: {

View File

@@ -1,6 +1,8 @@
import { DataTypes } from 'sequelize';
import sequelize from '../database.js';
import Club from './Club.js';
import League from './League.js';
import Season from './Season.js';
const Team = sequelize.define('Team', {
id: {
@@ -23,6 +25,26 @@ const Team = sequelize.define('Team', {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
leagueId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: League,
key: 'id',
},
onDelete: 'SET NULL',
onUpdate: 'CASCADE',
},
seasonId: {
type: DataTypes.INTEGER,
allowNull: true,
references: {
model: Season,
key: 'id',
},
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
},
}, {
underscored: true,
tableName: 'team',

View File

@@ -19,6 +19,7 @@ import DiaryDateActivity from './DiaryDateActivity.js';
import Match from './Match.js';
import League from './League.js';
import Team from './Team.js';
import ClubTeam from './ClubTeam.js';
import Season from './Season.js';
import Location from './Location.js';
import Group from './Group.js';
@@ -118,8 +119,21 @@ Team.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Club.hasMany(League, { foreignKey: 'clubId', as: 'leagues' });
League.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Match.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
Season.hasMany(Match, { foreignKey: 'seasonId', as: 'matches' });
League.hasMany(Team, { foreignKey: 'leagueId', as: 'teams' });
Team.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
Season.hasMany(Team, { foreignKey: 'seasonId', as: 'teams' });
Team.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
// ClubTeam relationships
Club.hasMany(ClubTeam, { foreignKey: 'clubId', as: 'clubTeams' });
ClubTeam.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
League.hasMany(ClubTeam, { foreignKey: 'leagueId', as: 'clubTeams' });
ClubTeam.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
Season.hasMany(ClubTeam, { foreignKey: 'seasonId', as: 'clubTeams' });
ClubTeam.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
Match.belongsTo(Location, { foreignKey: 'locationId', as: 'location' });
Location.hasMany(Match, { foreignKey: 'locationId', as: 'matches' });
@@ -231,6 +245,7 @@ export {
Match,
League,
Team,
ClubTeam,
Group,
GroupActivity,
Tournament,

View File

@@ -0,0 +1,32 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import {
getClubTeams,
getClubTeam,
createClubTeam,
updateClubTeam,
deleteClubTeam,
getLeagues
} from '../controllers/clubTeamController.js';
const router = express.Router();
// Get all club teams for a club
router.get('/club/:clubid', authenticate, getClubTeams);
// Create a new club team
router.post('/club/:clubid', authenticate, createClubTeam);
// Get leagues for a club
router.get('/leagues/:clubid', authenticate, getLeagues);
// Get a specific club team
router.get('/:clubteamid', authenticate, getClubTeam);
// Update a club team
router.put('/:clubteamid', authenticate, updateClubTeam);
// Delete a club team
router.delete('/:clubteamid', authenticate, deleteClubTeam);
export default router;

View File

@@ -0,0 +1,28 @@
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
import {
getSeasons,
getCurrentSeason,
createSeason,
getSeason,
deleteSeason
} from '../controllers/seasonController.js';
const router = express.Router();
// Get all seasons
router.get('/', authenticate, getSeasons);
// Get current season (creates if not exists)
router.get('/current', authenticate, getCurrentSeason);
// Get a specific season
router.get('/:seasonid', authenticate, getSeason);
// Create a new season
router.post('/', authenticate, createSeason);
// Delete a season
router.delete('/:seasonid', authenticate, deleteSeason);
export default router;

View File

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

View File

@@ -6,7 +6,7 @@ import cors from 'cors';
import {
User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, Group,
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, Group,
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis
} from './models/index.js';
@@ -34,6 +34,9 @@ import accidentRoutes from './routes/accidentRoutes.js';
import trainingStatsRoutes from './routes/trainingStatsRoutes.js';
import officialTournamentRoutes from './routes/officialTournamentRoutes.js';
import myTischtennisRoutes from './routes/myTischtennisRoutes.js';
import teamRoutes from './routes/teamRoutes.js';
import clubTeamRoutes from './routes/clubTeamRoutes.js';
import seasonRoutes from './routes/seasonRoutes.js';
const app = express();
const port = process.env.PORT || 3000;
@@ -79,6 +82,9 @@ app.use('/api/accident', accidentRoutes);
app.use('/api/training-stats', trainingStatsRoutes);
app.use('/api/official-tournaments', officialTournamentRoutes);
app.use('/api/mytischtennis', myTischtennisRoutes);
app.use('/api/teams', teamRoutes);
app.use('/api/club-teams', clubTeamRoutes);
app.use('/api/seasons', seasonRoutes);
app.use(express.static(path.join(__dirname, '../frontend/dist')));

View File

@@ -0,0 +1,192 @@
import ClubTeam from '../models/ClubTeam.js';
import League from '../models/League.js';
import Season from '../models/Season.js';
import SeasonService from './seasonService.js';
import { devLog } from '../utils/logger.js';
class ClubTeamService {
/**
* Holt alle ClubTeams für einen Verein, optional gefiltert nach Saison.
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
* @param {number} clubId - Die ID des Vereins.
* @param {number|null} seasonId - Optionale Saison-ID.
* @returns {Promise<Array<ClubTeam>>} Eine Liste von ClubTeams.
*/
static async getAllClubTeamsByClub(clubId, seasonId = null) {
try {
devLog('[ClubTeamService.getAllClubTeamsByClub] - Getting club teams for club:', clubId, 'season:', seasonId);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
seasonId = currentSeason.id;
}
const clubTeams = await ClubTeam.findAll({
where: { clubId, seasonId },
order: [['name', 'ASC']]
});
// Manuelle Datenanreicherung für Liga und Saison
const enrichedClubTeams = [];
for (const clubTeam of clubTeams) {
const enrichedTeam = {
id: clubTeam.id,
name: clubTeam.name,
clubId: clubTeam.clubId,
leagueId: clubTeam.leagueId,
seasonId: clubTeam.seasonId,
createdAt: clubTeam.createdAt,
updatedAt: clubTeam.updatedAt,
league: { name: 'Unbekannt' },
season: { season: 'Unbekannt' }
};
// Lade Liga-Daten
if (clubTeam.leagueId) {
const league = await League.findByPk(clubTeam.leagueId, { attributes: ['name'] });
if (league) enrichedTeam.league = league;
}
// Lade Saison-Daten
if (clubTeam.seasonId) {
const season = await Season.findByPk(clubTeam.seasonId, { attributes: ['season'] });
if (season) enrichedTeam.season = season;
}
enrichedClubTeams.push(enrichedTeam);
}
devLog('[ClubTeamService.getAllClubTeamsByClub] - Found club teams:', enrichedClubTeams.length);
return enrichedClubTeams;
} catch (error) {
console.error('[ClubTeamService.getAllClubTeamsByClub] - Error:', error);
throw error;
}
}
/**
* Holt ein ClubTeam anhand seiner ID
* @param {number} clubTeamId - Die ID des ClubTeams
* @returns {Promise<ClubTeam|null>} Das ClubTeam oder null, wenn nicht gefunden
*/
static async getClubTeamById(clubTeamId) {
try {
devLog('[ClubTeamService.getClubTeamById] - Getting club team:', clubTeamId);
const clubTeam = await ClubTeam.findByPk(clubTeamId, {
include: [
{
model: League,
as: 'league',
attributes: ['id', 'name']
},
{
model: Season,
as: 'season',
attributes: ['id', 'season']
}
]
});
devLog('[ClubTeamService.getClubTeamById] - Found club team:', clubTeam ? 'yes' : 'no');
return clubTeam;
} catch (error) {
console.error('[ClubTeamService.getClubTeamById] - Error:', error);
throw error;
}
}
/**
* Erstellt ein neues ClubTeam.
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison zugewiesen.
* @param {object} clubTeamData - Die Daten des neuen ClubTeams (name, clubId, optional leagueId, seasonId).
* @returns {Promise<ClubTeam>} Das erstellte ClubTeam.
*/
static async createClubTeam(clubTeamData) {
try {
devLog('[ClubTeamService.createClubTeam] - Creating club team:', clubTeamData);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!clubTeamData.seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
clubTeamData.seasonId = currentSeason.id;
}
const clubTeam = await ClubTeam.create(clubTeamData);
devLog('[ClubTeamService.createClubTeam] - Club team created with ID:', clubTeam.id);
return clubTeam;
} catch (error) {
console.error('[ClubTeamService.createClubTeam] - Error:', error);
throw error;
}
}
/**
* Aktualisiert ein bestehendes ClubTeam.
* @param {number} clubTeamId - Die ID des zu aktualisierenden ClubTeams.
* @param {object} updateData - Die zu aktualisierenden Daten.
* @returns {Promise<boolean>} True, wenn das ClubTeam aktualisiert wurde, sonst false.
*/
static async updateClubTeam(clubTeamId, updateData) {
try {
devLog('[ClubTeamService.updateClubTeam] - Updating club team:', clubTeamId, updateData);
const [updatedRowsCount] = await ClubTeam.update(updateData, {
where: { id: clubTeamId }
});
devLog('[ClubTeamService.updateClubTeam] - Updated rows:', updatedRowsCount);
return updatedRowsCount > 0;
} catch (error) {
console.error('[ClubTeamService.updateClubTeam] - Error:', error);
throw error;
}
}
/**
* Löscht ein ClubTeam.
* @param {number} clubTeamId - Die ID des zu löschenden ClubTeams.
* @returns {Promise<boolean>} True, wenn das ClubTeam gelöscht wurde, sonst false.
*/
static async deleteClubTeam(clubTeamId) {
try {
devLog('[ClubTeamService.deleteClubTeam] - Deleting club team:', clubTeamId);
const deletedRows = await ClubTeam.destroy({
where: { id: clubTeamId }
});
devLog('[ClubTeamService.deleteClubTeam] - Deleted rows:', deletedRows);
return deletedRows > 0;
} catch (error) {
console.error('[ClubTeamService.deleteClubTeam] - Error:', error);
throw error;
}
}
/**
* Holt alle Ligen für einen Verein, optional gefiltert nach Saison.
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
* @param {number} clubId - Die ID des Vereins.
* @param {number|null} seasonId - Optionale Saison-ID.
* @returns {Promise<Array<League>>} Eine Liste von Ligen.
*/
static async getLeaguesByClub(clubId, seasonId = null) {
try {
devLog('[ClubTeamService.getLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
seasonId = currentSeason.id;
}
const leagues = await League.findAll({
where: { clubId, seasonId },
attributes: ['id', 'name', 'seasonId'],
order: [['name', 'ASC']]
});
devLog('[ClubTeamService.getLeaguesByClub] - Found leagues:', leagues.length);
return leagues;
} catch (error) {
console.error('[ClubTeamService.getLeaguesByClub] - Error:', error);
throw error;
}
}
}
export default ClubTeamService;

View File

@@ -0,0 +1,104 @@
import League from '../models/League.js';
import Season from '../models/Season.js';
import SeasonService from './seasonService.js';
import { devLog } from '../utils/logger.js';
class LeagueService {
static async getAllLeaguesByClub(clubId, seasonId = null) {
try {
devLog('[LeagueService.getAllLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
seasonId = currentSeason.id;
}
const leagues = await League.findAll({
where: { clubId, seasonId },
include: [
{
model: Season,
as: 'season',
attributes: ['id', 'season']
}
],
order: [['name', 'ASC']]
});
devLog('[LeagueService.getAllLeaguesByClub] - Found leagues:', leagues.length);
return leagues;
} catch (error) {
console.error('[LeagueService.getAllLeaguesByClub] - Error:', error);
throw error;
}
}
static async getLeagueById(leagueId) {
try {
devLog('[LeagueService.getLeagueById] - Getting league:', leagueId);
const league = await League.findByPk(leagueId, {
include: [
{
model: Season,
as: 'season',
attributes: ['id', 'season']
}
]
});
devLog('[LeagueService.getLeagueById] - Found league:', league ? 'yes' : 'no');
return league;
} catch (error) {
console.error('[LeagueService.getLeagueById] - Error:', error);
throw error;
}
}
static async createLeague(leagueData) {
try {
devLog('[LeagueService.createLeague] - Creating league:', leagueData);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!leagueData.seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
leagueData.seasonId = currentSeason.id;
}
const league = await League.create(leagueData);
devLog('[LeagueService.createLeague] - League created with ID:', league.id);
return league;
} catch (error) {
console.error('[LeagueService.createLeague] - Error:', error);
throw error;
}
}
static async updateLeague(leagueId, updateData) {
try {
devLog('[LeagueService.updateLeague] - Updating league:', leagueId, updateData);
const [updatedRowsCount] = await League.update(updateData, {
where: { id: leagueId }
});
devLog('[LeagueService.updateLeague] - Updated rows:', updatedRowsCount);
return updatedRowsCount > 0;
} catch (error) {
console.error('[LeagueService.updateLeague] - Error:', error);
throw error;
}
}
static async deleteLeague(leagueId) {
try {
devLog('[LeagueService.deleteLeague] - Deleting league:', leagueId);
const deletedRowsCount = await League.destroy({
where: { id: leagueId }
});
devLog('[LeagueService.deleteLeague] - Deleted rows:', deletedRowsCount);
return deletedRowsCount > 0;
} catch (error) {
console.error('[LeagueService.deleteLeague] - Error:', error);
throw error;
}
}
}
export default LeagueService;

View File

@@ -7,6 +7,7 @@ import Season from '../models/Season.js';
import Location from '../models/Location.js';
import League from '../models/League.js';
import Team from '../models/Team.js';
import SeasonService from './seasonService.js';
import { checkAccess } from '../utils/userUtils.js';
import { Op } from 'sequelize';
@@ -22,8 +23,7 @@ class MatchService {
seasonStartYear = currentYear - 1;
}
const seasonEndYear = seasonStartYear + 1;
const seasonEndYearString = seasonEndYear.toString().slice(-2);
return `${seasonStartYear}/${seasonEndYearString}`;
return `${seasonStartYear}/${seasonEndYear}`;
}
async importCSV(userToken, clubId, filePath) {
@@ -58,7 +58,6 @@ class MatchService {
},
});
matches.push({
seasonId: season.id,
date: parsedDate,
time: row['Termin'].split(' ')[1],
homeTeamId: homeTeamId,
@@ -72,7 +71,14 @@ class MatchService {
if (seasonString) {
season = await Season.findOne({ where: { season: seasonString } });
if (season) {
await Match.destroy({ where: { clubId, seasonId: season.id } });
// Lösche alle Matches für Ligen dieser Saison
const leagues = await League.findAll({
where: { seasonId: season.id, clubId }
});
const leagueIds = leagues.map(league => league.id);
if (leagueIds.length > 0) {
await Match.destroy({ where: { clubId, leagueId: leagueIds } });
}
}
}
const result = await Match.bulkCreate(matches);
@@ -99,33 +105,28 @@ class MatchService {
}
async getLeaguesForCurrentSeason(userToken, clubId) {
async getLeaguesForCurrentSeason(userToken, clubId, seasonId = null) {
await checkAccess(userToken, clubId);
const seasonString = this.generateSeasonString();
const season = await Season.findOne({
where: {
season: {
[Op.like]: `%${seasonString}%`
}
// Verwende SeasonService für korrekte Saison-Verwaltung
let season;
if (!seasonId) {
season = await SeasonService.getOrCreateCurrentSeason();
} else {
season = await SeasonService.getSeasonById(seasonId);
if (!season) {
throw new Error('Season not found');
}
});
if (!season) {
await Season.create({ season: seasonString });
throw new Error('Season not found');
}
try {
const leagues = await League.findAll({
include: [{
model: Match,
as: 'leagueMatches',
where: {
seasonId: season.id,
clubId: clubId
},
attributes: [],
}],
where: {
clubId: clubId,
seasonId: season.id
},
attributes: ['id', 'name'],
group: ['League.id'],
order: [['name', 'ASC']]
});
return leagues;
} catch (error) {
@@ -134,48 +135,66 @@ class MatchService {
}
}
async getMatchesForLeagues(userToken, clubId) {
async getMatchesForLeagues(userToken, clubId, seasonId = null) {
await checkAccess(userToken, clubId);
const seasonString = this.generateSeasonString();
const season = await Season.findOne({
where: {
season: {
[Op.like]: `%${seasonString}%`
}
// Wenn keine Saison angegeben, verwende die aktuelle
let season;
if (!seasonId) {
season = await SeasonService.getOrCreateCurrentSeason();
} else {
season = await SeasonService.getSeasonById(seasonId);
if (!season) {
throw new Error('Season not found');
}
});
if (!season) {
throw new Error('Season not found');
}
const matches = await Match.findAll({
where: {
seasonId: season.id,
clubId: clubId,
},
include: [
{
model: League,
as: 'leagueDetails',
attributes: ['name'],
},
{
model: Team,
as: 'homeTeam', // Assuming your associations are set correctly
attributes: ['name'],
},
{
model: Team,
as: 'guestTeam',
attributes: ['name'],
},
{
model: Location,
as: 'location',
attributes: ['name', 'address', 'city', 'zip'],
}
]
}
});
return matches;
// Filtere Matches nach Liga-Saison und lade Daten manuell
const enrichedMatches = [];
for (const match of matches) {
// Lade Liga-Daten
const league = await League.findByPk(match.leagueId, { attributes: ['name', 'seasonId'] });
if (!league || league.seasonId !== season.id) {
continue; // Skip matches from other seasons
}
const enrichedMatch = {
id: match.id,
date: match.date,
time: match.time,
homeTeamId: match.homeTeamId,
guestTeamId: match.guestTeamId,
locationId: match.locationId,
leagueId: match.leagueId,
homeTeam: { name: 'Unbekannt' },
guestTeam: { name: 'Unbekannt' },
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
leagueDetails: { name: league.name }
};
if (match.homeTeamId) {
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
}
if (match.guestTeamId) {
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
}
if (match.locationId) {
const location = await Location.findByPk(match.locationId, {
attributes: ['name', 'address', 'city', 'zip']
});
if (location) enrichedMatch.location = location;
}
enrichedMatches.push(enrichedMatch);
}
return enrichedMatches;
}
async getMatchesForLeague(userToken, clubId, leagueId) {
@@ -193,34 +212,50 @@ class MatchService {
}
const matches = await Match.findAll({
where: {
seasonId: season.id,
clubId: clubId,
leagueId: leagueId
},
include: [
{
model: League,
as: 'leagueDetails',
attributes: ['name'],
},
{
model: Team,
as: 'homeTeam',
attributes: ['name'],
},
{
model: Team,
as: 'guestTeam',
attributes: ['name'],
},
{
model: Location,
as: 'location',
attributes: ['name', 'address', 'city', 'zip'],
}
]
}
});
return matches;
// Lade Team- und Location-Daten manuell
const enrichedMatches = [];
for (const match of matches) {
const enrichedMatch = {
id: match.id,
date: match.date,
time: match.time,
homeTeamId: match.homeTeamId,
guestTeamId: match.guestTeamId,
locationId: match.locationId,
leagueId: match.leagueId,
homeTeam: { name: 'Unbekannt' },
guestTeam: { name: 'Unbekannt' },
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
leagueDetails: { name: 'Unbekannt' }
};
if (match.homeTeamId) {
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
}
if (match.guestTeamId) {
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
}
if (match.locationId) {
const location = await Location.findByPk(match.locationId, {
attributes: ['name', 'address', 'city', 'zip']
});
if (location) enrichedMatch.location = location;
}
if (match.leagueId) {
const league = await League.findByPk(match.leagueId, { attributes: ['name'] });
if (league) enrichedMatch.leagueDetails = league;
}
enrichedMatches.push(enrichedMatch);
}
return enrichedMatches;
}
}

View File

@@ -0,0 +1,160 @@
import Season from '../models/Season.js';
import { devLog } from '../utils/logger.js';
class SeasonService {
/**
* Ermittelt die aktuelle Saison basierend auf dem aktuellen Datum
* @returns {string} Saison im Format "2023/2024"
*/
static getCurrentSeasonString() {
const now = new Date();
const currentYear = now.getFullYear();
const currentMonth = now.getMonth() + 1; // getMonth() ist 0-basiert
// Ab 1. Juli: neue Saison beginnt
if (currentMonth >= 7) {
return `${currentYear}/${currentYear + 1}`;
} else {
return `${currentYear - 1}/${currentYear}`;
}
}
/**
* Holt oder erstellt die aktuelle Saison
* @returns {Promise<Season>} Die aktuelle Saison
*/
static async getOrCreateCurrentSeason() {
try {
const currentSeasonString = this.getCurrentSeasonString();
devLog('[SeasonService.getOrCreateCurrentSeason] - Current season string:', currentSeasonString);
// Versuche die aktuelle Saison zu finden
let season = await Season.findOne({
where: { season: currentSeasonString }
});
// Falls nicht vorhanden, erstelle sie
if (!season) {
devLog('[SeasonService.getOrCreateCurrentSeason] - Creating new season:', currentSeasonString);
season = await Season.create({
season: currentSeasonString
});
}
devLog('[SeasonService.getOrCreateCurrentSeason] - Season found/created:', season.id);
return season;
} catch (error) {
console.error('[SeasonService.getOrCreateCurrentSeason] - Error:', error);
throw error;
}
}
/**
* Holt alle verfügbaren Saisons
* @returns {Promise<Array<Season>>} Alle Saisons sortiert nach Name
*/
static async getAllSeasons() {
try {
devLog('[SeasonService.getAllSeasons] - Getting all seasons');
const seasons = await Season.findAll({
order: [['season', 'DESC']] // Neueste zuerst
});
devLog('[SeasonService.getAllSeasons] - Found seasons:', seasons.length);
return seasons;
} catch (error) {
console.error('[SeasonService.getAllSeasons] - Error:', error);
throw error;
}
}
/**
* Erstellt eine neue Saison
* @param {string} seasonString - Saison im Format "2023/2024"
* @returns {Promise<Season>} Die erstellte Saison
*/
static async createSeason(seasonString) {
try {
devLog('[SeasonService.createSeason] - Creating season:', seasonString);
// Prüfe ob Saison bereits existiert
const existingSeason = await Season.findOne({
where: { season: seasonString }
});
if (existingSeason) {
throw new Error('Season already exists');
}
const season = await Season.create({
season: seasonString
});
devLog('[SeasonService.createSeason] - Season created with ID:', season.id);
return season;
} catch (error) {
console.error('[SeasonService.createSeason] - Error:', error);
throw error;
}
}
/**
* Holt eine Saison nach ID
* @param {number} seasonId - Die Saison-ID
* @returns {Promise<Season|null>} Die Saison oder null
*/
static async getSeasonById(seasonId) {
try {
devLog('[SeasonService.getSeasonById] - Getting season:', seasonId);
const season = await Season.findByPk(seasonId);
devLog('[SeasonService.getSeasonById] - Found season:', season ? 'yes' : 'no');
return season;
} catch (error) {
console.error('[SeasonService.getSeasonById] - Error:', error);
throw error;
}
}
/**
* Löscht eine Saison (nur wenn keine Teams/Ligen damit verknüpft sind)
* @param {number} seasonId - Die Saison-ID
* @returns {Promise<boolean>} True wenn gelöscht, false wenn nicht möglich
*/
static async deleteSeason(seasonId) {
try {
devLog('[SeasonService.deleteSeason] - Deleting season:', seasonId);
// Prüfe ob Saison verwendet wird
const season = await Season.findByPk(seasonId, {
include: [
{ association: 'teams' },
{ association: 'leagues' }
]
});
if (!season) {
return false;
}
// Prüfe ob Saison verwendet wird
if (season.teams && season.teams.length > 0) {
throw new Error('Season is used by teams');
}
if (season.leagues && season.leagues.length > 0) {
throw new Error('Season is used by leagues');
}
await Season.destroy({
where: { id: seasonId }
});
devLog('[SeasonService.deleteSeason] - Season deleted');
return true;
} catch (error) {
console.error('[SeasonService.deleteSeason] - Error:', error);
throw error;
}
}
}
export default SeasonService;

View File

@@ -0,0 +1,144 @@
import Team from '../models/Team.js';
import League from '../models/League.js';
import Club from '../models/Club.js';
import Season from '../models/Season.js';
import SeasonService from './seasonService.js';
import { devLog } from '../utils/logger.js';
class TeamService {
static async getAllTeamsByClub(clubId, seasonId = null) {
try {
devLog('[TeamService.getAllTeamsByClub] - Getting teams for club:', clubId, 'season:', seasonId);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
seasonId = currentSeason.id;
}
const teams = await Team.findAll({
where: { clubId, seasonId },
include: [
{
model: League,
as: 'league',
attributes: ['id', 'name']
},
{
model: Season,
as: 'season',
attributes: ['id', 'season']
}
],
order: [['name', 'ASC']]
});
devLog('[TeamService.getAllTeamsByClub] - Found teams:', teams.length);
return teams;
} catch (error) {
console.error('[TeamService.getAllTeamsByClub] - Error:', error);
throw error;
}
}
static async getTeamById(teamId) {
try {
devLog('[TeamService.getTeamById] - Getting team:', teamId);
const team = await Team.findByPk(teamId, {
include: [
{
model: League,
as: 'league',
attributes: ['id', 'name']
},
{
model: Club,
as: 'club',
attributes: ['id', 'name']
},
{
model: Season,
as: 'season',
attributes: ['id', 'season']
}
]
});
devLog('[TeamService.getTeamById] - Found team:', team ? 'yes' : 'no');
return team;
} catch (error) {
console.error('[TeamService.getTeamById] - Error:', error);
throw error;
}
}
static async createTeam(teamData) {
try {
devLog('[TeamService.createTeam] - Creating team:', teamData);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!teamData.seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
teamData.seasonId = currentSeason.id;
}
const team = await Team.create(teamData);
devLog('[TeamService.createTeam] - Team created with ID:', team.id);
return team;
} catch (error) {
console.error('[TeamService.createTeam] - Error:', error);
throw error;
}
}
static async updateTeam(teamId, updateData) {
try {
devLog('[TeamService.updateTeam] - Updating team:', teamId, updateData);
const [updatedRowsCount] = await Team.update(updateData, {
where: { id: teamId }
});
devLog('[TeamService.updateTeam] - Updated rows:', updatedRowsCount);
return updatedRowsCount > 0;
} catch (error) {
console.error('[TeamService.updateTeam] - Error:', error);
throw error;
}
}
static async deleteTeam(teamId) {
try {
devLog('[TeamService.deleteTeam] - Deleting team:', teamId);
const deletedRowsCount = await Team.destroy({
where: { id: teamId }
});
devLog('[TeamService.deleteTeam] - Deleted rows:', deletedRowsCount);
return deletedRowsCount > 0;
} catch (error) {
console.error('[TeamService.deleteTeam] - Error:', error);
throw error;
}
}
static async getLeaguesByClub(clubId, seasonId = null) {
try {
devLog('[TeamService.getLeaguesByClub] - Getting leagues for club:', clubId, 'season:', seasonId);
// Wenn keine Saison angegeben, verwende die aktuelle
if (!seasonId) {
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
seasonId = currentSeason.id;
}
const leagues = await League.findAll({
where: { clubId, seasonId },
attributes: ['id', 'name', 'seasonId'],
order: [['name', 'ASC']]
});
devLog('[TeamService.getLeaguesByClub] - Found leagues:', leagues.length);
return leagues;
} catch (error) {
console.error('[TeamService.getLeaguesByClub] - Error:', error);
throw error;
}
}
}
export default TeamService;