feat: implement user linking for members and update database schema for user associations
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 53s

This commit is contained in:
Torsten Schulz (local)
2026-07-22 23:54:45 +02:00
parent 46079fc16e
commit 3ac4039097
12 changed files with 319 additions and 18 deletions

View File

@@ -2,6 +2,8 @@ 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 { devLog } from '../utils/logger.js';
const getClubMembers = async(req, res) => {
@@ -351,6 +353,28 @@ const transferMembers = async (req, res) => {
}
};
const linkMemberUser = async (req, res) => {
try {
const clubId = Number(req.params.clubId);
const memberId = Number(req.params.memberId);
const userId = Number(req.body?.userId);
if (!clubId || !memberId || !userId) return res.status(400).json({ error: 'Mitglied, Verein und Benutzer sind erforderlich.' });
const [member, clubUser, existing] = await Promise.all([
Member.findOne({ where: { id: memberId, clubId } }),
UserClub.findOne({ where: { clubId, userId, approved: true } }),
Member.findOne({ where: { userId } }),
]);
if (!member || !clubUser) return res.status(404).json({ error: 'Mitglied oder freigegebener Vereinsbenutzer wurde nicht gefunden.' });
if (existing && Number(existing.id) !== memberId) return res.status(409).json({ error: 'Dieses Benutzerkonto ist bereits einem Mitglied zugeordnet.' });
await member.update({ userId });
emitMemberChanged(clubId);
res.json({ memberId, userId });
} catch (error) {
console.error('[linkMemberUser] - Error:', error);
res.status(500).json({ error: 'Mitglied konnte nicht verknüpft werden.' });
}
};
export {
getClubMembers,
getWaitingApprovals,
@@ -372,5 +396,6 @@ export {
requestClickTtPlayerRegistration,
deleteMemberImage,
setPrimaryMemberImage,
generateMemberGallery
generateMemberGallery,
linkMemberUser
};

View File

@@ -0,0 +1,4 @@
ALTER TABLE member
ADD COLUMN user_id INT NULL,
ADD UNIQUE KEY member_user_id_unique (user_id),
ADD CONSTRAINT member_user_id_fk FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL;

View File

@@ -123,6 +123,13 @@ const Member = sequelize.define('Member', {
type: DataTypes.INTEGER,
allowNull: false,
},
userId: {
type: DataTypes.INTEGER,
allowNull: true,
unique: true,
field: 'user_id',
comment: 'Linked login account for personal member features',
},
membershipStatus: {
type: DataTypes.STRING,
allowNull: false,

View File

@@ -223,6 +223,9 @@ User.hasMany(UserClub, { foreignKey: 'userId' });
UserClub.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
Club.hasMany(UserClub, { foreignKey: 'clubId' });
User.hasOne(Member, { foreignKey: 'userId', as: 'memberProfile' });
Member.belongsTo(User, { foreignKey: 'userId', as: 'user' });
Group.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDateGroup' });
DiaryDate.hasMany(Group, { foreignKey: 'diaryDateId', as: 'groupsDiaryDate' });

View File

@@ -19,7 +19,8 @@ import {
requestClickTtPlayerRegistration,
deleteMemberImage,
setPrimaryMemberImage,
generateMemberGallery
generateMemberGallery,
linkMemberUser
} from '../controllers/memberController.js';
import express from 'express';
import { authenticate } from '../middleware/authMiddleware.js';
@@ -39,6 +40,7 @@ router.post('/image/:clubId/:memberId/:imageId/primary', authenticate, authorize
router.get('/get/:id/:showAll', authenticate, authorize('members', 'read'), getClubMembers);
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('/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);