feat(MemberPlayInterest): implement play interest management for members
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 38s
All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 38s
- Added new endpoints to get and set member play interests in the memberController. - Integrated MemberPlayInterest model into the application, establishing relationships with Member and Club models. - Updated memberRoutes to include routes for managing member play interests. - Enhanced memberService to handle play interest retrieval and updates. - Updated localization files to include new terms related to member play interests. - Refactored server.js to include MemberPlayInterest in the synchronization process.
This commit is contained in:
@@ -47,6 +47,43 @@ const setClubMembers = async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getMemberPlayInterests = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const { seasonId, lineupHalf } = req.query;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.getMemberPlayInterests(userToken, Number(clubId), Number(seasonId), String(lineupHalf || ''));
|
||||
res.status(result.status || 500).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[getMemberPlayInterests] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to load member play interests' });
|
||||
}
|
||||
};
|
||||
|
||||
const setMemberPlayInterest = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
const { memberId, seasonId, lineupHalf, interested = true } = req.body;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const normalizedInterested = interested === true || interested === 'true' || interested === 1 || interested === '1';
|
||||
const result = await MemberService.setMemberPlayInterest(
|
||||
userToken,
|
||||
Number(clubId),
|
||||
Number(memberId),
|
||||
Number(seasonId),
|
||||
String(lineupHalf || ''),
|
||||
normalizedInterested
|
||||
);
|
||||
if (result.status === 200) {
|
||||
emitMemberChanged(clubId);
|
||||
}
|
||||
res.status(result.status || 500).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[setMemberPlayInterest] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to save member play interest' });
|
||||
}
|
||||
};
|
||||
|
||||
const uploadMemberImage = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
@@ -290,6 +327,8 @@ export {
|
||||
getClubMembers,
|
||||
getWaitingApprovals,
|
||||
setClubMembers,
|
||||
getMemberPlayInterests,
|
||||
setMemberPlayInterest,
|
||||
uploadMemberImage,
|
||||
getMemberImage,
|
||||
updateRatingsFromMyTischtennis,
|
||||
|
||||
16
backend/migrations/20260415_create_member_play_interest.sql
Normal file
16
backend/migrations/20260415_create_member_play_interest.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- Halbserienbasierte Spielinteressen (pro Mitglied, Club, Saison und Halbserie)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `member_play_interest` (
|
||||
`id` INT NOT NULL AUTO_INCREMENT,
|
||||
`club_id` INT NOT NULL,
|
||||
`member_id` INT NOT NULL,
|
||||
`season_id` INT NOT NULL,
|
||||
`lineup_half` ENUM('first_half', 'second_half') NOT NULL,
|
||||
`interested` TINYINT(1) NOT NULL DEFAULT 1,
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_member_play_interest_half` (`club_id`, `member_id`, `season_id`, `lineup_half`),
|
||||
KEY `idx_member_play_interest_member` (`member_id`),
|
||||
KEY `idx_member_play_interest_season` (`season_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -137,8 +137,7 @@ const Member = sequelize.define('Member', {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
default: false,
|
||||
}
|
||||
,
|
||||
},
|
||||
gender: {
|
||||
type: DataTypes.ENUM('male','female','diverse','unknown'),
|
||||
allowNull: true,
|
||||
|
||||
44
backend/models/MemberPlayInterest.js
Normal file
44
backend/models/MemberPlayInterest.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
|
||||
const MemberPlayInterest = sequelize.define('MemberPlayInterest', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'club_id'
|
||||
},
|
||||
memberId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'member_id'
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
field: 'season_id'
|
||||
},
|
||||
lineupHalf: {
|
||||
type: DataTypes.ENUM('first_half', 'second_half'),
|
||||
allowNull: false,
|
||||
field: 'lineup_half'
|
||||
},
|
||||
interested: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
sequelize,
|
||||
modelName: 'MemberPlayInterest',
|
||||
tableName: 'member_play_interest',
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
export default MemberPlayInterest;
|
||||
@@ -50,6 +50,7 @@ import MemberTransferConfig from './MemberTransferConfig.js';
|
||||
import MemberContact from './MemberContact.js';
|
||||
import MemberImage from './MemberImage.js';
|
||||
import MemberTtrHistory from './MemberTtrHistory.js';
|
||||
import MemberPlayInterest from './MemberPlayInterest.js';
|
||||
import MemberOrder from './MemberOrder.js';
|
||||
import MemberOrderHistory from './MemberOrderHistory.js';
|
||||
import TrainingGroup from './TrainingGroup.js';
|
||||
@@ -96,6 +97,10 @@ MemberNote.belongsTo(Member, { foreignKey: 'memberId' });
|
||||
|
||||
Member.hasMany(MemberTtrHistory, { as: 'ttrHistoryEntries', foreignKey: 'memberId' });
|
||||
MemberTtrHistory.belongsTo(Member, { as: 'member', foreignKey: 'memberId' });
|
||||
Member.hasMany(MemberPlayInterest, { as: 'playInterests', foreignKey: 'memberId' });
|
||||
MemberPlayInterest.belongsTo(Member, { as: 'member', foreignKey: 'memberId' });
|
||||
Club.hasMany(MemberPlayInterest, { as: 'memberPlayInterests', foreignKey: 'clubId' });
|
||||
MemberPlayInterest.belongsTo(Club, { as: 'club', foreignKey: 'clubId' });
|
||||
|
||||
Member.hasMany(MemberOrder, { as: 'orders', foreignKey: 'memberId' });
|
||||
MemberOrder.belongsTo(Member, { as: 'member', foreignKey: 'memberId' });
|
||||
@@ -438,6 +443,7 @@ export {
|
||||
MemberContact,
|
||||
MemberImage,
|
||||
MemberTtrHistory,
|
||||
MemberPlayInterest,
|
||||
MemberOrder,
|
||||
MemberOrderHistory,
|
||||
TrainingGroup,
|
||||
|
||||
@@ -2,6 +2,8 @@ import {
|
||||
getClubMembers,
|
||||
getWaitingApprovals,
|
||||
setClubMembers,
|
||||
getMemberPlayInterests,
|
||||
setMemberPlayInterest,
|
||||
uploadMemberImage,
|
||||
getMemberImage,
|
||||
updateRatingsFromMyTischtennis,
|
||||
@@ -35,6 +37,8 @@ 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.get('/play-interest/:clubId', authenticate, authorize('members', 'read'), getMemberPlayInterests);
|
||||
router.post('/play-interest/:clubId', authenticate, authorize('members', 'write'), setMemberPlayInterest);
|
||||
router.get('/notapproved/:id', authenticate, authorize('members', 'read'), getWaitingApprovals);
|
||||
router.post('/update-ratings/:id', authenticate, authorize('mytischtennis', 'write'), updateRatingsFromMyTischtennis);
|
||||
router.get('/ttr-history/:clubId/:memberId', authenticate, authorize('members', 'read'), getMemberTtrHistory);
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, ClubTeamMember, TeamDocument, Group,
|
||||
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis, ClickTtAccount, MyTischtennisUpdateHistory, MyTischtennisFetchLog, ApiLog, MemberTransferConfig, MemberContact, MemberTtrHistory, MemberPlayInterest
|
||||
, MemberOrder, MemberOrderHistory
|
||||
} from './models/index.js';
|
||||
import authRoutes from './routes/authRoutes.js';
|
||||
@@ -543,6 +543,7 @@ app.use((err, req, res, next) => {
|
||||
await safeSync(MemberTransferConfig);
|
||||
await safeSync(MemberContact);
|
||||
await safeSync(MemberTtrHistory);
|
||||
await safeSync(MemberPlayInterest);
|
||||
await safeSync(MemberOrder);
|
||||
await safeSync(MemberOrderHistory);
|
||||
await safeSync(ClubTeam);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { checkAccess, getUserByToken, hasUserClubAccess } from "../utils/userUti
|
||||
import Member from "../models/Member.js";
|
||||
import MemberImage from "../models/MemberImage.js";
|
||||
import MemberTtrHistory from "../models/MemberTtrHistory.js";
|
||||
import MemberPlayInterest from "../models/MemberPlayInterest.js";
|
||||
import Participant from "../models/Participant.js";
|
||||
import DiaryDate from "../models/DiaryDates.js";
|
||||
import { Op, fn, col } from 'sequelize';
|
||||
@@ -14,6 +15,64 @@ import sharp from 'sharp';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
import { standardizePhoneNumber } from '../utils/phoneUtils.js';
|
||||
class MemberService {
|
||||
async getMemberPlayInterests(userToken, clubId, seasonId, lineupHalf) {
|
||||
await checkAccess(userToken, clubId);
|
||||
if (!seasonId || !['first_half', 'second_half'].includes(String(lineupHalf || ''))) {
|
||||
return {
|
||||
status: 400,
|
||||
response: { error: 'invalidplayinterestparams' }
|
||||
};
|
||||
}
|
||||
|
||||
const rows = await MemberPlayInterest.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
seasonId,
|
||||
lineupHalf,
|
||||
interested: true
|
||||
},
|
||||
attributes: ['memberId', 'seasonId', 'lineupHalf', 'interested']
|
||||
});
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: rows.map((row) => row.toJSON())
|
||||
};
|
||||
}
|
||||
|
||||
async setMemberPlayInterest(userToken, clubId, memberId, seasonId, lineupHalf, interested = true) {
|
||||
await checkAccess(userToken, clubId);
|
||||
if (!memberId || !seasonId || !['first_half', 'second_half'].includes(String(lineupHalf || ''))) {
|
||||
return {
|
||||
status: 400,
|
||||
response: { error: 'invalidplayinterestparams' }
|
||||
};
|
||||
}
|
||||
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId } });
|
||||
if (!member) {
|
||||
return {
|
||||
status: 404,
|
||||
response: { error: 'membernotfound' }
|
||||
};
|
||||
}
|
||||
|
||||
const [row] = await MemberPlayInterest.findOrCreate({
|
||||
where: { clubId, memberId, seasonId, lineupHalf },
|
||||
defaults: { interested: !!interested }
|
||||
});
|
||||
|
||||
if (row.interested !== !!interested) {
|
||||
row.interested = !!interested;
|
||||
await row.save();
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: { result: 'success' }
|
||||
};
|
||||
}
|
||||
|
||||
async getApprovalRequests(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const user = await getUserByToken(userToken);
|
||||
|
||||
Reference in New Issue
Block a user