205 lines
9.2 KiB
JavaScript
205 lines
9.2 KiB
JavaScript
import express from 'express';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import sequelize from './database.js';
|
|
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, ClubTeam, TeamDocument, Group,
|
|
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
|
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, MyTischtennisUpdateHistory
|
|
} from './models/index.js';
|
|
import authRoutes from './routes/authRoutes.js';
|
|
import clubRoutes from './routes/clubRoutes.js';
|
|
import diaryRoutes from './routes/diaryRoutes.js';
|
|
import memberRoutes from './routes/memberRoutes.js';
|
|
import participantRoutes from './routes/participantRoutes.js';
|
|
import activityRoutes from './routes/activityRoutes.js';
|
|
import memberNoteRoutes from './routes/memberNoteRoutes.js';
|
|
import diaryTagRoutes from './routes/diaryTagRoutes.js';
|
|
import diaryNoteRoutes from './routes/diaryNoteRoutes.js';
|
|
import diaryMemberRoutes from './routes/diaryMemberRoutes.js';
|
|
import predefinedActivityRoutes from './routes/predefinedActivityRoutes.js';
|
|
import diaryDateActivityRoutes from './routes/diaryDateActivityRoutes.js';
|
|
import diaryMemberActivityRoutes from './routes/diaryMemberActivityRoutes.js';
|
|
import matchRoutes from './routes/matchRoutes.js';
|
|
import Season from './models/Season.js';
|
|
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';
|
|
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 teamDocumentRoutes from './routes/teamDocumentRoutes.js';
|
|
import seasonRoutes from './routes/seasonRoutes.js';
|
|
import schedulerService from './services/schedulerService.js';
|
|
|
|
const app = express();
|
|
const port = process.env.PORT || 3000;
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
app.use(cors({
|
|
origin: true,
|
|
credentials: true,
|
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
allowedHeaders: ['Content-Type', 'Authorization', 'authcode', 'userid']
|
|
}));
|
|
app.use(express.json());
|
|
|
|
// Globale Fehlerbehandlung, damit der Server bei unerwarteten Fehlern nicht hart abstürzt
|
|
process.on('uncaughtException', (err) => {
|
|
console.error('[uncaughtException]', err);
|
|
});
|
|
process.on('unhandledRejection', (reason, promise) => {
|
|
console.error('[unhandledRejection]', reason);
|
|
});
|
|
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/clubs', clubRoutes);
|
|
app.use('/api/clubmembers', memberRoutes);
|
|
app.use('/api/diary', diaryRoutes);
|
|
app.use('/api/participants', participantRoutes);
|
|
app.use('/api/activities', activityRoutes);
|
|
app.use('/api/membernotes', memberNoteRoutes);
|
|
app.use('/api/diarynotes', diaryNoteRoutes);
|
|
app.use('/api/tags', diaryTagRoutes);
|
|
app.use('/api/diarymember', diaryMemberRoutes);
|
|
app.use('/api/predefined-activities', predefinedActivityRoutes);
|
|
app.use('/api/diary-date-activities', diaryDateActivityRoutes);
|
|
app.use('/api/diary-member-activities', diaryMemberActivityRoutes);
|
|
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('/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/team-documents', teamDocumentRoutes);
|
|
app.use('/api/seasons', seasonRoutes);
|
|
|
|
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
|
|
|
// Catch-All Handler für Frontend-Routen (muss nach den API-Routen stehen)
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
|
|
});
|
|
|
|
(async () => {
|
|
try {
|
|
await sequelize.authenticate();
|
|
|
|
// Einmalige Migration: deutsche Spaltennamen -> englische
|
|
const renameColumnIfExists = async (table, from, to, typeSql) => {
|
|
try {
|
|
const [rows] = await sequelize.query(`SHOW COLUMNS FROM \`${table}\` LIKE :col`, { replacements: { col: from } });
|
|
if (Array.isArray(rows) && rows.length > 0) {
|
|
await sequelize.query(`ALTER TABLE \`${table}\` CHANGE \`${from}\` \`${to}\` ${typeSql}`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`[migration] Failed to rename ${table}.${from} -> ${to}:`, e.message);
|
|
}
|
|
};
|
|
|
|
// official_competitions
|
|
await renameColumnIfExists('official_competitions', 'altersklasse_wettbewerb', 'age_class_competition', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'leistungsklasse', 'performance_class', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'startzeit', 'start_time', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'meldeschluss_datum', 'registration_deadline_date', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'meldeschluss_online', 'registration_deadline_online', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'stichtag', 'cutoff_date', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'offen_fuer', 'open_to', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'vorrunde', 'preliminary_round', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'endrunde', 'final_round', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'max_teilnehmer', 'max_participants', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_competitions', 'startgeld', 'entry_fee', 'VARCHAR(255) NULL');
|
|
|
|
// official_tournaments
|
|
await renameColumnIfExists('official_tournaments', 'termin', 'event_date', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_tournaments', 'veranstalter', 'organizer', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_tournaments', 'ausrichter', 'host', 'VARCHAR(255) NULL');
|
|
await renameColumnIfExists('official_tournaments', 'austragungsorte', 'venues', 'TEXT NULL');
|
|
await renameColumnIfExists('official_tournaments', 'konkurrenztypen', 'competition_types', 'TEXT NULL');
|
|
await renameColumnIfExists('official_tournaments', 'meldeschluesse', 'registration_deadlines', 'TEXT NULL');
|
|
|
|
const isDev = process.env.STAGE === 'dev';
|
|
const safeSync = async (model) => {
|
|
try {
|
|
if (isDev) {
|
|
await model.sync({ alter: true });
|
|
} else {
|
|
await model.sync();
|
|
}
|
|
} catch (e) {
|
|
try {
|
|
console.error(`[sync] ${model?.name || 'model'} alter failed:`, e?.message || e);
|
|
await model.sync();
|
|
} catch (e2) {
|
|
console.error(`[sync] fallback failed for ${model?.name || 'model'}:`, e2?.message || e2);
|
|
}
|
|
}
|
|
};
|
|
|
|
await safeSync(User);
|
|
await safeSync(Club);
|
|
await safeSync(UserClub);
|
|
await safeSync(Log);
|
|
await safeSync(Member);
|
|
await safeSync(DiaryDate);
|
|
await safeSync(Participant);
|
|
await safeSync(Activity);
|
|
await safeSync(MemberNote);
|
|
await safeSync(DiaryNote);
|
|
await safeSync(DiaryTag);
|
|
await safeSync(MemberDiaryTag);
|
|
await safeSync(DiaryDateTag);
|
|
await safeSync(DiaryMemberTag);
|
|
await safeSync(DiaryMemberNote);
|
|
await safeSync(PredefinedActivity);
|
|
await safeSync(PredefinedActivityImage);
|
|
await safeSync(DiaryDateActivity);
|
|
await safeSync(DiaryMemberActivity);
|
|
await safeSync(OfficialTournament);
|
|
await safeSync(OfficialCompetition);
|
|
await safeSync(OfficialCompetitionMember);
|
|
await safeSync(Season);
|
|
await safeSync(League);
|
|
await safeSync(Team);
|
|
await safeSync(Location);
|
|
await safeSync(Match);
|
|
await safeSync(Group);
|
|
await safeSync(GroupActivity);
|
|
await safeSync(Tournament);
|
|
await safeSync(TournamentGroup);
|
|
await safeSync(TournamentMember);
|
|
await safeSync(TournamentMatch);
|
|
await safeSync(TournamentResult);
|
|
await safeSync(Accident);
|
|
await safeSync(UserToken);
|
|
await safeSync(MyTischtennis);
|
|
await safeSync(MyTischtennisUpdateHistory);
|
|
|
|
// Start scheduler service
|
|
schedulerService.start();
|
|
|
|
app.listen(port, () => {
|
|
console.log(`Server is running on http://localhost:${port}`);
|
|
console.log('Scheduler service started:');
|
|
console.log(' - Rating updates: 6:00 AM daily');
|
|
console.log(' - Match results fetch: 6:30 AM daily');
|
|
});
|
|
} catch (err) {
|
|
console.error('Unable to synchronize the database:', err);
|
|
}
|
|
})(); |