Integrate member activity management by adding new routes in the backend for member activities. Update MembersView.vue to include a button for opening the activities modal and implement the MemberActivitiesDialog component for displaying member activities. Enhance the UI with new button styles for better user interaction.
208 lines
9.4 KiB
JavaScript
208 lines
9.4 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, MyTischtennisFetchLog
|
|
} 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 memberActivityRoutes from './routes/memberActivityRoutes.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', 'PATCH', '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('/api/member-activities', memberActivityRoutes);
|
|
|
|
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);
|
|
await safeSync(MyTischtennisFetchLog);
|
|
|
|
// 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);
|
|
}
|
|
})(); |