feat: Implement member self-service features for profile change requests and event responses
- Added MemberProfileChangeRequest and MemberEventResponse models with associations. - Created API endpoints for member dashboard, profile change requests, event responses, and training attendance updates. - Developed MemberHomeView component to display member-specific information and actions. - Updated routing to include new member dashboard and profile change request functionalities. - Added SQL migration scripts for new database tables related to member self-service. - Enhanced login functionality to redirect members based on their roles and club associations.
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
import { Op } from 'sequelize';
|
||||
import MemberService from "../services/memberService.js";
|
||||
import MemberTransferService from "../services/memberTransferService.js";
|
||||
import clickTtPlayerRegistrationService from "../services/clickTtPlayerRegistrationService.js";
|
||||
import { emitMemberChanged } from '../services/socketService.js';
|
||||
import Member from '../models/Member.js';
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import CalendarEvent from '../models/CalendarEvent.js';
|
||||
import TrainingGroup from '../models/TrainingGroup.js';
|
||||
import ClubCommunicationRecipient from '../models/ClubCommunicationRecipient.js';
|
||||
import ClubCommunicationThread from '../models/ClubCommunicationThread.js';
|
||||
import ClubSepaMandate from '../models/ClubSepaMandate.js';
|
||||
import MemberProfileChangeRequest from '../models/MemberProfileChangeRequest.js';
|
||||
import MemberEventResponse from '../models/MemberEventResponse.js';
|
||||
import Participant from '../models/Participant.js';
|
||||
import DiaryDate from '../models/DiaryDates.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getClubMembers = async(req, res) => {
|
||||
@@ -375,6 +385,155 @@ const linkMemberUser = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getApprovedLinkedMember = async (clubId, userId) => {
|
||||
const clubUser = await UserClub.findOne({ where: { clubId, userId, approved: true } });
|
||||
if (!clubUser) return { error: 'Kein Zugriff auf diesen Verein.', status: 403 };
|
||||
const member = await Member.findOne({ where: { clubId, userId, active: true } });
|
||||
if (!member) return { error: 'Für dieses Konto ist kein aktives Mitgliedsprofil verknüpft.', status: 404 };
|
||||
return { member };
|
||||
};
|
||||
|
||||
const getMemberDashboard = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const userId = Number(req.user?.id);
|
||||
const access = await getApprovedLinkedMember(clubId, userId);
|
||||
if (access.error) return res.status(access.status).json({ error: access.error });
|
||||
const { member } = access;
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const [events, trainingGroups, recipients, sepaMandate, changeRequests, trainingDates] = await Promise.all([
|
||||
CalendarEvent.findAll({
|
||||
where: { clubId, startDate: { [Op.gte]: today }, status: { [Op.notIn]: ['cancelled', 'archived'] } },
|
||||
attributes: ['id', 'title', 'startDate', 'endDate', 'eventType', 'location', 'registrationDeadline'],
|
||||
order: [['startDate', 'ASC']], limit: 5,
|
||||
}),
|
||||
TrainingGroup.findAll({ where: { clubId }, include: [{ model: Member, as: 'members', where: { id: member.id }, attributes: [], through: { attributes: [] } }, { association: 'trainingTimes', required: false }], order: [['name', 'ASC']] }),
|
||||
ClubCommunicationRecipient.findAll({ where: { clubId, memberId: member.id, deliveryStatus: 'sent' }, include: [{ model: ClubCommunicationThread, as: 'thread', attributes: ['id', 'subject', 'sentAt'] }], order: [['deliveredAt', 'DESC']], limit: 5 }),
|
||||
ClubSepaMandate.findOne({ where: { clubId, memberId: member.id, status: 'active', revokedAt: null }, attributes: ['id'] }),
|
||||
MemberProfileChangeRequest.findAll({ where: { clubId, memberId: member.id, status: 'open' }, attributes: ['id', 'requestedChanges', 'createdAt'], order: [['createdAt', 'DESC']] }),
|
||||
DiaryDate.findAll({ where: { clubId, date: { [Op.gte]: today } }, include: [{ model: Member, as: 'participants', where: { id: member.id }, required: false, through: { attributes: ['attendanceStatus'] } }], attributes: ['id', 'date', 'trainingStart', 'trainingEnd'], order: [['date', 'ASC']], limit: 5 }),
|
||||
]);
|
||||
const responses = await MemberEventResponse.findAll({ where: { clubId, memberId: member.id, eventId: events.map((event) => event.id) } });
|
||||
const responseByEvent = new Map(responses.map((response) => [response.eventId, response.status]));
|
||||
res.json({
|
||||
profile: { id: member.id, firstName: member.firstName, lastName: member.lastName, email: member.email, phone: member.phone, street: member.street, postalCode: member.postalCode, city: member.city },
|
||||
membership: { status: member.membershipStatus, type: member.membershipType, joinedOn: member.joinedOn, needsSepaMandate: member.needsSepaMandate, hasActiveSepaMandate: Boolean(sepaMandate) },
|
||||
openProfileChangeRequests: changeRequests,
|
||||
upcomingEvents: events.map((event) => ({ ...event.toJSON(), responseStatus: responseByEvent.get(event.id) || null })),
|
||||
trainingGroups: trainingGroups.map((group) => ({ id: group.id, name: group.name, trainingTimes: group.trainingTimes || [] })),
|
||||
upcomingTrainingDates: trainingDates.map((date) => ({ id: date.id, date: date.date, trainingStart: date.trainingStart, trainingEnd: date.trainingEnd, attendanceStatus: date.participants?.[0]?.Participant?.attendanceStatus || 'present' })),
|
||||
messages: recipients.map((recipient) => ({ id: recipient.thread?.id || recipient.id, subject: recipient.thread?.subject || 'Vereinsnachricht', sentAt: recipient.deliveredAt || recipient.thread?.sentAt || null })),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[getMemberDashboard] - Error:', error);
|
||||
res.status(500).json({ error: 'Persönlicher Bereich konnte nicht geladen werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const createProfileChangeRequest = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const userId = Number(req.user?.id);
|
||||
const access = await getApprovedLinkedMember(clubId, userId);
|
||||
if (access.error) return res.status(access.status).json({ error: access.error });
|
||||
const allowedFields = ['email', 'phone', 'street', 'postalCode', 'city'];
|
||||
const requestedChanges = Object.fromEntries(allowedFields
|
||||
.filter((field) => typeof req.body?.[field] === 'string' && req.body[field].trim() !== '')
|
||||
.map((field) => [field, req.body[field].trim()]));
|
||||
if (!Object.keys(requestedChanges).length) return res.status(400).json({ error: 'Bitte mindestens ein gültiges Kontaktfeld angeben.' });
|
||||
const request = await MemberProfileChangeRequest.create({ clubId, memberId: access.member.id, requestedByUserId: userId, requestedChanges });
|
||||
res.status(201).json({ id: request.id, status: request.status, requestedChanges: request.requestedChanges, createdAt: request.createdAt });
|
||||
} catch (error) {
|
||||
console.error('[createProfileChangeRequest] - Error:', error);
|
||||
res.status(500).json({ error: 'Änderungsanfrage konnte nicht eingereicht werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const respondToCalendarEvent = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const eventId = Number(req.params.eventId);
|
||||
const status = req.body?.status;
|
||||
if (!['attending', 'declined'].includes(status)) return res.status(400).json({ error: 'Ungültige Rückmeldung.' });
|
||||
const access = await getApprovedLinkedMember(clubId, Number(req.user?.id));
|
||||
if (access.error) return res.status(access.status).json({ error: access.error });
|
||||
const event = await CalendarEvent.findOne({ where: { id: eventId, clubId } });
|
||||
if (!event) return res.status(404).json({ error: 'Veranstaltung nicht gefunden.' });
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
if (event.startDate < today || (event.registrationDeadline && event.registrationDeadline < today)) return res.status(409).json({ error: 'Für diese Veranstaltung sind keine Rückmeldungen mehr möglich.' });
|
||||
const [response] = await MemberEventResponse.upsert({ clubId, eventId, memberId: access.member.id, status }, { returning: true });
|
||||
res.json({ eventId, status: response.status });
|
||||
} catch (error) {
|
||||
console.error('[respondToCalendarEvent] - Error:', error);
|
||||
res.status(500).json({ error: 'Rückmeldung konnte nicht gespeichert werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const updateOwnTrainingAttendance = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const diaryDateId = Number(req.params.diaryDateId);
|
||||
const attendanceStatus = req.body?.attendanceStatus;
|
||||
if (!['present', 'excused', 'cancelled'].includes(attendanceStatus)) return res.status(400).json({ error: 'Ungültiger Trainingsstatus.' });
|
||||
const access = await getApprovedLinkedMember(clubId, Number(req.user?.id));
|
||||
if (access.error) return res.status(access.status).json({ error: access.error });
|
||||
const diaryDate = await DiaryDate.findOne({ where: { id: diaryDateId, clubId } });
|
||||
if (!diaryDate) return res.status(404).json({ error: 'Trainingstag nicht gefunden.' });
|
||||
const [participant] = await Participant.findOrCreate({ where: { diaryDateId, memberId: access.member.id }, defaults: { attendanceStatus } });
|
||||
if (!participant.isNewRecord) await participant.update({ attendanceStatus });
|
||||
res.json({ diaryDateId, attendanceStatus: participant.attendanceStatus });
|
||||
} catch (error) {
|
||||
console.error('[updateOwnTrainingAttendance] - Error:', error);
|
||||
res.status(500).json({ error: 'Trainingsrückmeldung konnte nicht gespeichert werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const getEventResponses = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const eventId = Number(req.params.eventId);
|
||||
const responses = await MemberEventResponse.findAll({ where: { clubId, eventId }, include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] }], order: [['updatedAt', 'DESC']] });
|
||||
res.json(responses.map((response) => ({ id: response.id, memberId: response.memberId, status: response.status, updatedAt: response.updatedAt, member: response.member ? { id: response.member.id, firstName: response.member.firstName, lastName: response.member.lastName } : null })));
|
||||
} catch (error) {
|
||||
console.error('[getEventResponses] - Error:', error);
|
||||
res.status(500).json({ error: 'Rückmeldungen konnten nicht geladen werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const getProfileChangeRequests = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const requests = await MemberProfileChangeRequest.findAll({
|
||||
where: { clubId, ...(req.query.status ? { status: req.query.status } : {}) },
|
||||
include: [{ model: Member, as: 'member', attributes: ['id', 'firstName', 'lastName'] }],
|
||||
order: [['createdAt', 'DESC']],
|
||||
});
|
||||
res.json(requests.map((request) => ({ id: request.id, memberId: request.memberId, status: request.status, requestedChanges: request.requestedChanges, reviewNote: request.reviewNote, createdAt: request.createdAt, member: request.member ? { id: request.member.id, firstName: request.member.firstName, lastName: request.member.lastName } : null })));
|
||||
} catch (error) {
|
||||
console.error('[getProfileChangeRequests] - Error:', error);
|
||||
res.status(500).json({ error: 'Änderungsanfragen konnten nicht geladen werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
const reviewProfileChangeRequest = async (req, res) => {
|
||||
try {
|
||||
const clubId = Number(req.params.clubId);
|
||||
const requestId = Number(req.params.requestId);
|
||||
const status = req.body?.status;
|
||||
const reviewNote = typeof req.body?.reviewNote === 'string' ? req.body.reviewNote.trim() : null;
|
||||
if (!['approved', 'rejected'].includes(status)) return res.status(400).json({ error: 'Ungültiger Prüfstatus.' });
|
||||
const request = await MemberProfileChangeRequest.findOne({ where: { id: requestId, clubId, status: 'open' }, include: [{ model: Member, as: 'member' }] });
|
||||
if (!request || !request.member) return res.status(404).json({ error: 'Offene Änderungsanfrage nicht gefunden.' });
|
||||
if (status === 'approved') await request.member.update(request.requestedChanges);
|
||||
await request.update({ status, reviewNote, reviewedByUserId: req.user.id, reviewedAt: new Date() });
|
||||
if (status === 'approved') emitMemberChanged(clubId);
|
||||
res.json({ id: request.id, status: request.status });
|
||||
} catch (error) {
|
||||
console.error('[reviewProfileChangeRequest] - Error:', error);
|
||||
res.status(500).json({ error: 'Änderungsanfrage konnte nicht geprüft werden.' });
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
getClubMembers,
|
||||
getWaitingApprovals,
|
||||
@@ -397,5 +556,12 @@ export {
|
||||
deleteMemberImage,
|
||||
setPrimaryMemberImage,
|
||||
generateMemberGallery,
|
||||
linkMemberUser
|
||||
linkMemberUser,
|
||||
getMemberDashboard,
|
||||
createProfileChangeRequest,
|
||||
respondToCalendarEvent,
|
||||
updateOwnTrainingAttendance,
|
||||
getEventResponses,
|
||||
getProfileChangeRequests,
|
||||
reviewProfileChangeRequest
|
||||
};
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
CREATE TABLE member_profile_change_requests (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
club_id INT NOT NULL,
|
||||
member_id INT NOT NULL,
|
||||
requested_by_user_id INT NOT NULL,
|
||||
requested_changes JSON NOT NULL,
|
||||
status ENUM('open', 'approved', 'rejected') NOT NULL DEFAULT 'open',
|
||||
review_note TEXT NULL,
|
||||
reviewed_by_user_id INT NULL,
|
||||
reviewed_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
INDEX member_profile_change_requests_club_status (club_id, status),
|
||||
INDEX member_profile_change_requests_member_status (member_id, status)
|
||||
);
|
||||
|
||||
CREATE TABLE member_event_responses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
club_id INT NOT NULL,
|
||||
event_id INT NOT NULL,
|
||||
member_id INT NOT NULL,
|
||||
status ENUM('attending', 'declined') NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
UNIQUE KEY member_event_responses_event_member (event_id, member_id),
|
||||
INDEX member_event_responses_club_event (club_id, event_id)
|
||||
);
|
||||
17
backend/models/MemberEventResponse.js
Normal file
17
backend/models/MemberEventResponse.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const MemberEventResponse = sequelize.define('MemberEventResponse', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
clubId: { type: DataTypes.INTEGER, allowNull: false, field: 'club_id' },
|
||||
eventId: { type: DataTypes.INTEGER, allowNull: false, field: 'event_id' },
|
||||
memberId: { type: DataTypes.INTEGER, allowNull: false, field: 'member_id' },
|
||||
status: { type: DataTypes.ENUM('attending', 'declined'), allowNull: false },
|
||||
}, {
|
||||
tableName: 'member_event_responses',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
indexes: [{ unique: true, fields: ['event_id', 'member_id'] }, { fields: ['club_id', 'event_id'] }],
|
||||
});
|
||||
|
||||
export default MemberEventResponse;
|
||||
21
backend/models/MemberProfileChangeRequest.js
Normal file
21
backend/models/MemberProfileChangeRequest.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const MemberProfileChangeRequest = sequelize.define('MemberProfileChangeRequest', {
|
||||
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
clubId: { type: DataTypes.INTEGER, allowNull: false, field: 'club_id' },
|
||||
memberId: { type: DataTypes.INTEGER, allowNull: false, field: 'member_id' },
|
||||
requestedByUserId: { type: DataTypes.INTEGER, allowNull: false, field: 'requested_by_user_id' },
|
||||
requestedChanges: { type: DataTypes.JSON, allowNull: false, field: 'requested_changes' },
|
||||
status: { type: DataTypes.ENUM('open', 'approved', 'rejected'), allowNull: false, defaultValue: 'open' },
|
||||
reviewNote: { type: DataTypes.TEXT, allowNull: true, field: 'review_note' },
|
||||
reviewedByUserId: { type: DataTypes.INTEGER, allowNull: true, field: 'reviewed_by_user_id' },
|
||||
reviewedAt: { type: DataTypes.DATE, allowNull: true, field: 'reviewed_at' },
|
||||
}, {
|
||||
tableName: 'member_profile_change_requests',
|
||||
underscored: true,
|
||||
timestamps: true,
|
||||
indexes: [{ fields: ['club_id', 'status'] }, { fields: ['member_id', 'status'] }],
|
||||
});
|
||||
|
||||
export default MemberProfileChangeRequest;
|
||||
@@ -93,6 +93,8 @@ import ClubCommunicationDeliveryLog from './ClubCommunicationDeliveryLog.js';
|
||||
import ClubCommunicationTemplate from './ClubCommunicationTemplate.js';
|
||||
import ClubDistributionGroup from './ClubDistributionGroup.js';
|
||||
import ClubDistributionGroupMember from './ClubDistributionGroupMember.js';
|
||||
import MemberProfileChangeRequest from './MemberProfileChangeRequest.js';
|
||||
import MemberEventResponse from './MemberEventResponse.js';
|
||||
// Official tournaments relations
|
||||
OfficialTournament.hasMany(OfficialCompetition, { foreignKey: 'tournamentId', as: 'competitions' });
|
||||
OfficialCompetition.belongsTo(OfficialTournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
@@ -500,6 +502,20 @@ ClubSepaMandate.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Member.hasMany(ClubSepaMandate, { foreignKey: 'memberId', as: 'sepaMandates' });
|
||||
ClubSepaMandate.belongsTo(Member, { foreignKey: 'memberId', as: 'member', constraints: false });
|
||||
|
||||
Club.hasMany(MemberProfileChangeRequest, { foreignKey: 'clubId', as: 'memberProfileChangeRequests' });
|
||||
MemberProfileChangeRequest.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Member.hasMany(MemberProfileChangeRequest, { foreignKey: 'memberId', as: 'profileChangeRequests' });
|
||||
MemberProfileChangeRequest.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
|
||||
User.hasMany(MemberProfileChangeRequest, { foreignKey: 'requestedByUserId', as: 'requestedMemberProfileChanges' });
|
||||
MemberProfileChangeRequest.belongsTo(User, { foreignKey: 'requestedByUserId', as: 'requestedByUser' });
|
||||
|
||||
Club.hasMany(MemberEventResponse, { foreignKey: 'clubId', as: 'memberEventResponses' });
|
||||
MemberEventResponse.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
CalendarEvent.hasMany(MemberEventResponse, { foreignKey: 'eventId', as: 'memberResponses' });
|
||||
MemberEventResponse.belongsTo(CalendarEvent, { foreignKey: 'eventId', as: 'event' });
|
||||
Member.hasMany(MemberEventResponse, { foreignKey: 'memberId', as: 'eventResponses' });
|
||||
MemberEventResponse.belongsTo(Member, { foreignKey: 'memberId', as: 'member' });
|
||||
|
||||
Club.hasMany(ClubPaymentClaim, { foreignKey: 'clubId', as: 'paymentClaims' });
|
||||
ClubPaymentClaim.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Member.hasMany(ClubPaymentClaim, { foreignKey: 'memberId', as: 'paymentClaims' });
|
||||
@@ -688,5 +704,7 @@ export {
|
||||
ClubCommunicationDeliveryLog,
|
||||
ClubCommunicationTemplate,
|
||||
ClubDistributionGroup,
|
||||
MemberProfileChangeRequest,
|
||||
MemberEventResponse,
|
||||
ClubDistributionGroupMember,
|
||||
};
|
||||
|
||||
@@ -20,7 +20,14 @@ import {
|
||||
deleteMemberImage,
|
||||
setPrimaryMemberImage,
|
||||
generateMemberGallery,
|
||||
linkMemberUser
|
||||
linkMemberUser,
|
||||
getMemberDashboard,
|
||||
createProfileChangeRequest,
|
||||
respondToCalendarEvent,
|
||||
updateOwnTrainingAttendance,
|
||||
getEventResponses,
|
||||
getProfileChangeRequests,
|
||||
reviewProfileChangeRequest
|
||||
} from '../controllers/memberController.js';
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
@@ -41,6 +48,13 @@ router.get('/get/:id/:showAll', authenticate, authorize('members', 'read'), getC
|
||||
router.get('/gallery/:clubId', authenticate, authorize('members', 'read'), generateMemberGallery);
|
||||
router.post('/set/:id', authenticate, authorize('members', 'write'), setClubMembers);
|
||||
router.put('/link-user/:clubId/:memberId', authenticate, authorize('members', 'write'), linkMemberUser);
|
||||
router.get('/dashboard/:clubId', authenticate, getMemberDashboard);
|
||||
router.post('/profile/:clubId/change-requests', authenticate, createProfileChangeRequest);
|
||||
router.put('/dashboard/:clubId/events/:eventId/response', authenticate, respondToCalendarEvent);
|
||||
router.put('/dashboard/:clubId/training/:diaryDateId/attendance', authenticate, updateOwnTrainingAttendance);
|
||||
router.get('/event-responses/:clubId/:eventId', authenticate, authorize('schedule', 'read'), getEventResponses);
|
||||
router.get('/profile-change-requests/:clubId', authenticate, authorize('members', 'read'), getProfileChangeRequests);
|
||||
router.patch('/profile-change-requests/:clubId/:requestId', authenticate, authorize('members', 'write'), reviewProfileChangeRequest);
|
||||
router.get('/sepa/:clubId/:memberId', authenticate, authorize('members', 'read'), getMemberSepaMandate);
|
||||
router.put('/sepa/:clubId/:memberId', authenticate, authorize('members', 'write'), saveMemberSepaMandate);
|
||||
router.get('/play-interest/:clubId', authenticate, authorize('members', 'read'), getMemberPlayInterests);
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory, MemberPlayInterest,
|
||||
MemberOrder, MemberOrderHistory, MemberGroupPhoto, BillingTemplate, BillingTemplateField, BillingRun, BillingDocument, BillingDocumentValue, BillingUserSetting, FriendlyMatch, TrainingCancellation
|
||||
, FriendlyMatchShared, FriendlyMatchInvitation
|
||||
, CalendarEvent, ClubVenue, ClubRequest, ClubRequestNote, ClubSepaMandate, ClubPaymentClaim, ClubAccount, ClubAccountTransaction, ClubInvoiceParty, ClubInvoice, ClubInvoiceItem, ClubRole, ClubUserRole, ClubCommunicationThread, ClubCommunicationMessage, ClubCommunicationRecipient, ClubCommunicationDeliveryLog, ClubCommunicationTemplate, ClubDistributionGroup, ClubDistributionGroupMember
|
||||
, CalendarEvent, ClubVenue, ClubRequest, ClubRequestNote, ClubSepaMandate, ClubPaymentClaim, ClubAccount, ClubAccountTransaction, ClubInvoiceParty, ClubInvoice, ClubInvoiceItem, ClubRole, ClubUserRole, ClubCommunicationThread, ClubCommunicationMessage, ClubCommunicationRecipient, ClubCommunicationDeliveryLog, ClubCommunicationTemplate, ClubDistributionGroup, ClubDistributionGroupMember, MemberProfileChangeRequest, MemberEventResponse
|
||||
} from './models/index.js';
|
||||
import authRoutes from './routes/authRoutes.js';
|
||||
import clubRoutes from './routes/clubRoutes.js';
|
||||
@@ -662,6 +662,8 @@ app.use((err, req, res, next) => {
|
||||
await safeSync(ClubTeam);
|
||||
await safeSync(TrainingCancellation);
|
||||
await safeSync(CalendarEvent);
|
||||
await safeSync(MemberProfileChangeRequest);
|
||||
await safeSync(MemberEventResponse);
|
||||
await safeSync(TeamDocument);
|
||||
|
||||
// Foreign Keys wieder aktivieren
|
||||
|
||||
Reference in New Issue
Block a user