1765 lines
51 KiB
JavaScript
1765 lines
51 KiB
JavaScript
/**
|
|
* 이벤트 컨트롤러
|
|
* 이벤트 관련 기능을 처리하는 컨트롤러
|
|
*/
|
|
const { Event, EventParticipant, Member, Club, EventScore, ClubMember, EventTeam, EventTeamMember } = require('../models');
|
|
const { Op } = require('sequelize');
|
|
const { sequelize } = require('../models');
|
|
const averageService = require('../services/averageService');
|
|
|
|
// 이벤트 목록 조회
|
|
exports.getAllEvents = async (req, res) => {
|
|
try {
|
|
const { clubId } = req.body;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const events = await Event.findAll({
|
|
where: {
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
},
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
},
|
|
{
|
|
model: EventParticipant,
|
|
attributes: ['id', 'status', 'paymentStatus'],
|
|
where: {
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
required: false,
|
|
include: [{
|
|
model: Member,
|
|
attributes: ['id', 'name', 'memberType', 'gender']
|
|
}]
|
|
}
|
|
],
|
|
order: [['startDate', 'ASC']]
|
|
});
|
|
|
|
const formattedEvents = events.map(event => {
|
|
const eventData = event.toJSON();
|
|
return {
|
|
...eventData,
|
|
participantCount: eventData.participants ? eventData.participants.length : 0,
|
|
clubName: eventData.Club ? eventData.Club.name : ''
|
|
};
|
|
});
|
|
|
|
res.json(formattedEvents);
|
|
} catch (error) {
|
|
console.error('이벤트 목록 조회 오류:', error);
|
|
res.status(500).json({ message: '이벤트 목록을 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 특정 이벤트 조회
|
|
exports.getEventById = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
|
|
if (!eventId) {
|
|
return res.status(400).json({ message: '이벤트 ID가 필요합니다.' });
|
|
}
|
|
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
},
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name', 'location']
|
|
},
|
|
{
|
|
model: EventParticipant,
|
|
attributes: ['id', 'status', 'paymentStatus'],
|
|
where: { status: { [Op.ne]: 'canceled' } },
|
|
required: false,
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name']
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
const eventData = event.toJSON();
|
|
|
|
// 참가자들의 동적 에버리지 계산
|
|
if (eventData.participants && eventData.participants.length > 0) {
|
|
const participantsWithDynamicAverage = await Promise.all(
|
|
eventData.participants.map(async (participant) => {
|
|
if (participant.Member && participant.Member.id) {
|
|
try {
|
|
const averageData = await averageService.calculateMemberAverageByClubSetting(
|
|
participant.Member.id,
|
|
eventData.clubId
|
|
);
|
|
|
|
return {
|
|
...participant,
|
|
Member: {
|
|
...participant.Member,
|
|
average: averageData.average
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error(`회원 ${participant.Member.id}의 에버리지 계산 오류:`, error);
|
|
return {
|
|
...participant,
|
|
Member: {
|
|
...participant.Member,
|
|
average: 0
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return participant;
|
|
})
|
|
);
|
|
|
|
eventData.participants = participantsWithDynamicAverage;
|
|
}
|
|
|
|
const formattedEvent = {
|
|
...eventData,
|
|
participantCount: eventData.participants ? eventData.participants.length : 0,
|
|
clubName: eventData.Club ? eventData.Club.name : ''
|
|
};
|
|
|
|
res.json(formattedEvent);
|
|
} catch (error) {
|
|
console.error('이벤트 조회 오류:', error);
|
|
res.status(500).json({ message: '이벤트 정보를 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 새 이벤트 생성
|
|
exports.createEvent = async (req, res) => {
|
|
try {
|
|
const {
|
|
clubId,
|
|
title,
|
|
description,
|
|
startDate,
|
|
endDate,
|
|
location,
|
|
maxParticipants,
|
|
eventType,
|
|
entryFee,
|
|
gameCount,
|
|
laneCount,
|
|
status
|
|
} = req.body;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const club = await Club.findOne({
|
|
where: {
|
|
id: clubId,
|
|
status: 'active'
|
|
}
|
|
});
|
|
|
|
if (!club) {
|
|
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
|
}
|
|
const endDateVal = endDate ? new Date(endDate) : null;
|
|
// 랜덤 해시 생성 함수
|
|
const generateRandomHash = async () => {
|
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
let hash;
|
|
let exists = true;
|
|
while (exists) {
|
|
hash = Array.from({ length: 16 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
// 중복 체크
|
|
exists = await Event.findOne({ where: { publicHash: hash } });
|
|
}
|
|
return hash;
|
|
};
|
|
|
|
const publicHash = await generateRandomHash();
|
|
const accessPassword = req.body.accessPassword || null;
|
|
|
|
const event = await Event.create({
|
|
clubId,
|
|
title,
|
|
description,
|
|
startDate: startDate ? startDate : null,
|
|
endDate: endDateVal,
|
|
location,
|
|
maxParticipants: maxParticipants || 0,
|
|
eventType: eventType || '일반',
|
|
entryFee: entryFee || 0,
|
|
gameCount: gameCount || 3,
|
|
laneCount: laneCount || 1,
|
|
status: status || '활성',
|
|
publicHash,
|
|
accessPassword
|
|
});
|
|
|
|
const createdEvent = await Event.findOne({
|
|
where: { id: event.id },
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}
|
|
]
|
|
});
|
|
|
|
res.status(201).json({
|
|
message: '이벤트가 생성되었습니다.',
|
|
event: {
|
|
...createdEvent.toJSON(),
|
|
participantCount: 0,
|
|
clubName: createdEvent.Club ? createdEvent.Club.name : ''
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('이벤트 생성 오류:', error);
|
|
res.status(500).json({ message: '이벤트 생성 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 공개 이벤트 조회 및 비밀번호 인증
|
|
exports.getEventByPublicHash = async (req, res) => {
|
|
const { publicHash } = req.params;
|
|
const { password } = req.body || {};
|
|
|
|
try {
|
|
// 1. 이벤트 조회
|
|
const event = await Event.findOne({
|
|
where: { publicHash },
|
|
include: [{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}]
|
|
});
|
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
if (event.status === 'canceled') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
|
|
|
// 2. 비밀번호 필요 여부 및 인증
|
|
if (event.accessPassword) {
|
|
if (!password) return res.status(401).json({ needPassword: true, message: '비밀번호 필요' });
|
|
if (event.accessPassword !== password) return res.status(401).json({ needPassword: true, message: '비밀번호 불일치' });
|
|
}
|
|
|
|
// 3. 전체 회원 명단 추출 (clubId 사용, clubId는 응답에 포함 X)
|
|
const allMembers = await Member.findAll({
|
|
where: { clubId: event.clubId },
|
|
attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender']
|
|
});
|
|
|
|
// 전체 회원의 동적 에버리지 계산
|
|
const allMembersWithAverage = await Promise.all(
|
|
allMembers.map(async (member) => {
|
|
try {
|
|
const averageData = await averageService.calculateMemberAverageByClubSetting(
|
|
member.dataValues.memberId,
|
|
event.clubId
|
|
);
|
|
return {
|
|
...member.dataValues,
|
|
average: averageData.average
|
|
};
|
|
} catch (error) {
|
|
console.error(`회원 ${member.dataValues.memberId}의 에버리지 계산 오류:`, error);
|
|
return {
|
|
...member.dataValues,
|
|
average: 0
|
|
};
|
|
}
|
|
})
|
|
);
|
|
console.log('allMembersWithAverage', allMembersWithAverage);
|
|
|
|
// 4. 참가자 명단 추출 (참가자 테이블 + Member 조인)
|
|
const participants = await EventParticipant.findAll({
|
|
where: { eventId: event.id, status: { [Op.ne]: 'canceled' } },
|
|
include: [
|
|
{ model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender'] }
|
|
]
|
|
});
|
|
|
|
// 참가자들의 동적 에버리지 계산
|
|
const participantsWithAverage = await Promise.all(
|
|
participants.map(async (participant) => {
|
|
if (participant.Member && participant.Member.dataValues.memberId) {
|
|
try {
|
|
const averageData = await averageService.calculateMemberAverageByClubSetting(
|
|
participant.Member.dataValues.memberId,
|
|
event.clubId
|
|
);
|
|
|
|
// 기존 객체 복사
|
|
const participantObj = {...participant.dataValues};
|
|
participantObj.Member = {
|
|
...participant.Member.dataValues,
|
|
average: averageData.average
|
|
};
|
|
|
|
return participantObj;
|
|
} catch (error) {
|
|
console.error(`참가자 회원 ${participant.Member.dataValues.memberId}의 에버리지 계산 오류:`, error);
|
|
const participantObj = {...participant.dataValues};
|
|
participantObj.Member = {
|
|
...participant.Member.dataValues,
|
|
average: 0
|
|
};
|
|
return participantObj;
|
|
}
|
|
}
|
|
return participant.dataValues;
|
|
})
|
|
);
|
|
// 5. 참가 신청 가능 명단(아직 참가하지 않은 회원)
|
|
// '취소'가 아닌 status가 하나라도 있으면 참가자로 간주 (중복 제거)
|
|
const participantMemberIds = [
|
|
...new Set(
|
|
participantsWithAverage
|
|
.filter(p => p.status !== '취소')
|
|
.map(p => parseInt(p.memberId))
|
|
)
|
|
];
|
|
const availableMembers = allMembersWithAverage.filter(m => !participantMemberIds.includes(parseInt(m.memberId)));
|
|
// 6. 팀 정보 등 추가 조회 (기존 코드 유지)
|
|
const teams = await EventTeam.findAll({
|
|
where: { eventId: event.id },
|
|
order: [['teamNumber', 'ASC']],
|
|
include: [
|
|
{
|
|
model: EventTeamMember,
|
|
include: [
|
|
{
|
|
model: EventParticipant,
|
|
include: [{ model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'handicap'] }]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
});
|
|
|
|
// 점수
|
|
const scores = await EventScore.findAll({ where: { eventId: event.id } });
|
|
|
|
// 팀별 그룹핑
|
|
let teamList = [];
|
|
if (teams.length > 0) {
|
|
// 팀 리스트 처리
|
|
const teamsWithMembers = [];
|
|
for (const team of teams) {
|
|
const membersWithAverage = [];
|
|
for (const tm of (team.EventTeamMembers || [])) {
|
|
const participant = tm.EventParticipant;
|
|
const member = participant?.Member;
|
|
const scoreObj = scores.find(s => s.participantId === participant?.id);
|
|
|
|
// 회원의 동적 에버리지 계산
|
|
let dynamicAverage = 0;
|
|
if (member && member.memberId) {
|
|
try {
|
|
const averageData = await averageService.calculateMemberAverageByClubSetting(
|
|
member.memberId,
|
|
event.clubId
|
|
);
|
|
dynamicAverage = averageData.average;
|
|
} catch (error) {
|
|
console.error(`팀 멤버 ${member.memberId}의 에버리지 계산 오류:`, error);
|
|
}
|
|
}
|
|
|
|
membersWithAverage.push({
|
|
participantId: participant?.id,
|
|
memberId: member?.memberId,
|
|
name: member?.name || `참가자 ${participant?.id}`,
|
|
gender: member?.gender,
|
|
memberType: member?.memberType,
|
|
average: dynamicAverage,
|
|
handicap: member?.handicap || 0,
|
|
status: participant?.status,
|
|
paymentStatus: participant?.paymentStatus,
|
|
comment: participant?.comment,
|
|
isGuest: member?.memberType === '게스트',
|
|
score: scoreObj ? scoreObj.score : null
|
|
});
|
|
}
|
|
|
|
teamsWithMembers.push({
|
|
teamId: team.id,
|
|
teamNumber: team.teamNumber,
|
|
handicap: team.handicap,
|
|
members: membersWithAverage
|
|
});
|
|
}
|
|
|
|
teamList = teamsWithMembers;
|
|
}
|
|
|
|
// 참가자 리스트(팀 없는 경우)
|
|
let participantList = [];
|
|
// 참가자들의 동적 에버리지 계산
|
|
participantList = participantsWithAverage.map(p => {
|
|
const member = p.Member;
|
|
const scoreObj = scores.find(s => s.participantId === p.id);
|
|
return {
|
|
participantId: p.id,
|
|
memberId: p.memberId,
|
|
name: member?.name || `참가자 ${p.id}`,
|
|
gender: member?.gender,
|
|
memberType: member?.memberType,
|
|
handicap: member?.handicap || 0,
|
|
average: member?.average || 0, // 이미 participantsWithAverage에서 동적 계산된 값
|
|
status: p.status,
|
|
paymentStatus: p.paymentStatus,
|
|
comment: p.comment,
|
|
isGuest: member?.memberType === '게스트',
|
|
score: scoreObj ? scoreObj.score : null
|
|
};
|
|
});
|
|
// 반환 데이터
|
|
res.json({
|
|
clubName: event.Club.name,
|
|
event: {
|
|
id: event.id,
|
|
title: event.title,
|
|
description: event.description,
|
|
eventType: event.eventType,
|
|
startDate: event.startDate,
|
|
endDate: event.endDate,
|
|
location: event.location,
|
|
gameCount: event.gameCount,
|
|
registrationDeadline: event.registrationDeadline,
|
|
maxParticipants: event.maxParticipants,
|
|
participantFee: event.participantFee,
|
|
status: event.status
|
|
},
|
|
teams: teamList,
|
|
participants: participantList,
|
|
availableMembers,
|
|
needPassword: !!event.accessPassword
|
|
});
|
|
} catch (e) {
|
|
console.error('공개 이벤트 조회 오류:', e);
|
|
res.status(500).json({ message: '이벤트 정보를 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 공개 URL(publicHash) 재생성
|
|
exports.regeneratePublicHash = async (req, res) => {
|
|
const { id } = req.params;
|
|
try {
|
|
const event = await Event.findOne({ where: { id } });
|
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
// 랜덤 해시 생성 (중복 없는 값)
|
|
const generateRandomHash = async () => {
|
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
let hash;
|
|
let exists = true;
|
|
while (exists) {
|
|
hash = Array.from({ length: 16 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
|
|
exists = await Event.findOne({ where: { publicHash: hash } });
|
|
}
|
|
return hash;
|
|
};
|
|
const newHash = await generateRandomHash();
|
|
await event.update({ publicHash: newHash });
|
|
res.json({ publicHash: newHash });
|
|
} catch (e) {
|
|
console.error('공개 URL 재생성 오류:', e);
|
|
res.status(500).json({ message: '공개 URL 재생성 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 공개 참가자 등록/수정
|
|
exports.registerPublicParticipant = async (req, res) => {
|
|
const { publicHash } = req.params;
|
|
const { participantId, memberId, status, paymentStatus, comment } = req.body || {};
|
|
try {
|
|
const event = await Event.findOne({ where: { publicHash } });
|
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
|
// 기존 참가자 있는지 검사(이름+연락처 기준)
|
|
let member = await Member.findOne({ where: { id:memberId } });
|
|
// if (!member) {
|
|
// member = await Member.create({ memberId, memberType: '일반' });
|
|
// }
|
|
let participant = await EventParticipant.findOne({ where: { id: participantId, memberId: member.id } });
|
|
if (!participant) {
|
|
participant = await EventParticipant.create({ eventId: event.id, memberId: member.id, comment, status, paymentStatus });
|
|
} else {
|
|
await participant.update({ comment, status, paymentStatus });
|
|
}
|
|
res.json({ message: '참가 신청/수정이 완료되었습니다.' });
|
|
} catch (e) {
|
|
console.error('공개 참가자 등록 오류:', e);
|
|
res.status(500).json({ message: '참가 신청/수정 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 공개 게스트 추가
|
|
exports.addPublicGuest = async (req, res) => {
|
|
const { publicHash } = req.params;
|
|
const { name, password } = req.body || {};
|
|
try {
|
|
const event = await Event.findOne({ where: { publicHash } });
|
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
if (event.status === '취소') return res.status(403).json({ message: '취소된 이벤트입니다.' });
|
|
if (event.accessPassword) {
|
|
if (!password || event.accessPassword !== password) return res.status(401).json({ message: '비밀번호가 올바르지 않습니다.' });
|
|
}
|
|
// 게스트 멤버 생성
|
|
const member = await Member.create({ name, memberType: '게스트' });
|
|
await EventParticipant.create({ eventId: event.id, memberId: member.id });
|
|
res.json({ message: '게스트가 추가되었습니다.' });
|
|
} catch (e) {
|
|
console.error('공개 게스트 추가 오류:', e);
|
|
res.status(500).json({ message: '게스트 추가 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 공개 점수 수정
|
|
exports.updatePublicScore = async (req, res) => {
|
|
const { publicHash } = req.params;
|
|
const { participantId, score, password } = req.body || {};
|
|
try {
|
|
const event = await Event.findOne({ where: { publicHash } });
|
|
if (!event) return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
if (event.status !== '준비' && event.status !== '활성') return res.status(403).json({ message: '점수 수정이 불가능한 상태입니다.' });
|
|
if (event.accessPassword) {
|
|
if (!password || event.accessPassword !== password) return res.status(401).json({ message: '비밀번호가 올바르지 않습니다.' });
|
|
}
|
|
let eventScore = await EventScore.findOne({ where: { eventId: event.id, participantId } });
|
|
if (!eventScore) {
|
|
eventScore = await EventScore.create({ eventId: event.id, participantId, score });
|
|
} else {
|
|
await eventScore.update({ score });
|
|
}
|
|
res.json({ message: '점수가 저장되었습니다.' });
|
|
} catch (e) {
|
|
console.error('공개 점수 수정 오류:', e);
|
|
res.status(500).json({ message: '점수 저장 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 이벤트 정보 업데이트
|
|
exports.updateEvent = async (req, res) => {
|
|
try {
|
|
const {
|
|
clubId,
|
|
title,
|
|
description,
|
|
startDate,
|
|
location,
|
|
maxParticipants,
|
|
eventType,
|
|
entryFee,
|
|
gameCount,
|
|
laneCount,
|
|
status,
|
|
registrationDeadline,
|
|
publicHash,
|
|
accessPassword
|
|
} = req.body;
|
|
const eventId = req.body.id;
|
|
let endDate = req.body.endDate;
|
|
if (!eventId || !clubId) {
|
|
return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
if(endDate == 'Invalid Date') {
|
|
endDate = null;
|
|
}
|
|
const updateData = {
|
|
title,
|
|
description,
|
|
startDate: startDate ? startDate : event.startDate,
|
|
endDate: endDate || null,
|
|
location,
|
|
maxParticipants: maxParticipants || event.maxParticipants,
|
|
eventType: eventType || event.eventType,
|
|
entryFee: entryFee !== undefined ? entryFee : event.entryFee,
|
|
gameCount: gameCount || event.gameCount,
|
|
laneCount: laneCount || event.laneCount,
|
|
status: status || event.status,
|
|
registrationDeadline: registrationDeadline || event.registrationDeadline,
|
|
publicHash: publicHash || event.publicHash,
|
|
accessPassword: accessPassword || null
|
|
};
|
|
await event.update(updateData);
|
|
|
|
const updatedEvent = await Event.findOne({
|
|
where: { id: eventId },
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
},
|
|
{
|
|
model: EventParticipant,
|
|
attributes: ['id', 'status'],
|
|
where: {
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
required: false
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!updatedEvent) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.json({
|
|
message: '이벤트가 수정되었습니다.',
|
|
event: {
|
|
...updatedEvent.toJSON(),
|
|
participantCount: updatedEvent.participants ? updatedEvent.participants.length : 0,
|
|
clubName: updatedEvent.Club ? updatedEvent.Club.name : ''
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('이벤트 수정 오류:', error);
|
|
res.status(500).json({ message: '이벤트 수정 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 이벤트 삭제 (소프트 삭제)
|
|
exports.deleteEvent = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
const { clubId } = req.body;
|
|
|
|
console.log(eventId, clubId, req.body, req.params);
|
|
if (!eventId || !clubId) {
|
|
return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
},
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 참가자 정보도 함께 삭제 처리
|
|
await EventParticipant.update(
|
|
{ status: '취소' },
|
|
{
|
|
where: {
|
|
eventId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
}
|
|
}
|
|
);
|
|
|
|
await event.update({ status: '삭제' });
|
|
|
|
res.json({
|
|
message: '이벤트가 삭제되었습니다.',
|
|
event: {
|
|
id: event.id,
|
|
title: event.title,
|
|
clubId: event.clubId,
|
|
clubName: event.Club ? event.Club.name : ''
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('이벤트 삭제 오류:', error);
|
|
res.status(500).json({ message: '이벤트 삭제 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 이벤트 점수 조회
|
|
exports.getEventScores = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
|
|
if (!eventId) {
|
|
return res.status(400).json({ message: '이벤트 ID가 필요합니다.' });
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
const scores = await EventScore.findAll({
|
|
where: { eventId },
|
|
include: [
|
|
{
|
|
model: EventParticipant,
|
|
attributes: ['id', 'status'],
|
|
include: [{
|
|
model: Member,
|
|
attributes: ['id', 'name']
|
|
}]
|
|
}
|
|
],
|
|
order: [
|
|
['gameNumber', 'ASC'],
|
|
['score', 'DESC']
|
|
]
|
|
});
|
|
|
|
return res.status(200).json(scores);
|
|
} catch (error) {
|
|
console.error('점수 조회 오류:', error);
|
|
res.status(500).json({ message: '점수 조회 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 점수 등록
|
|
exports.addEventScore = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
const {
|
|
participantId,
|
|
gameNumber,
|
|
score,
|
|
handicap = 0
|
|
} = req.body;
|
|
|
|
if (!eventId || !participantId || !gameNumber || score === undefined) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID, 참가자 ID, 게임 번호, 점수가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 참가자 확인
|
|
const participant = await EventParticipant.findOne({
|
|
where: {
|
|
id: participantId,
|
|
eventId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
}
|
|
});
|
|
|
|
if (!participant) {
|
|
return res.status(400).json({ message: '이벤트 참가자가 아닙니다.' });
|
|
}
|
|
|
|
// 게임 번호 유효성 검사
|
|
if (gameNumber < 1 || gameNumber > event.gameCount) {
|
|
return res.status(400).json({
|
|
message: `게임 번호는 1부터 ${event.gameCount}까지만 입력 가능합니다.`
|
|
});
|
|
}
|
|
|
|
// 점수 유효성 검사
|
|
if (score < 0 || score > 300) {
|
|
return res.status(400).json({
|
|
message: '점수는 0에서 300 사이여야 합니다.'
|
|
});
|
|
}
|
|
|
|
// 동일한 게임의 점수가 이미 있는지 확인
|
|
const existingScore = await EventScore.findOne({
|
|
where: {
|
|
eventId,
|
|
participantId,
|
|
gameNumber
|
|
}
|
|
});
|
|
|
|
if (existingScore) {
|
|
return res.status(400).json({
|
|
message: '해당 게임의 점수가 이미 등록되어 있습니다.'
|
|
});
|
|
}
|
|
|
|
// 점수 등록
|
|
const totalScore = score + handicap;
|
|
const newScore = await EventScore.create({
|
|
eventId,
|
|
participantId,
|
|
gameNumber,
|
|
score,
|
|
handicap,
|
|
totalScore
|
|
});
|
|
|
|
// 참가자 평균 점수 업데이트
|
|
const allScores = await EventScore.findAll({
|
|
where: {
|
|
eventId,
|
|
participantId
|
|
}
|
|
});
|
|
|
|
const totalGames = allScores.length;
|
|
const averageScore = Math.round(allScores.reduce((sum, s) => sum + s.score, 0) / totalGames);
|
|
|
|
await participant.update({
|
|
games: totalGames,
|
|
average: averageScore
|
|
});
|
|
|
|
// 점수 등록 후 에버리지 캐시 무효화
|
|
await averageService.invalidateMemberAverageCache(participant.memberId);
|
|
|
|
res.status(201).json({
|
|
message: '점수가 등록되었습니다.',
|
|
score: {
|
|
id: newScore.id,
|
|
eventId: newScore.eventId,
|
|
participantId: newScore.participantId,
|
|
gameNumber: newScore.gameNumber,
|
|
score: newScore.score,
|
|
handicap: newScore.handicap,
|
|
totalScore: newScore.totalScore,
|
|
createdAt: newScore.createdAt
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('점수 등록 오류:', error);
|
|
res.status(500).json({ message: '점수 등록 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 점수 수정
|
|
exports.updateEventScore = async (req, res) => {
|
|
try {
|
|
const eventId = Number(req.params.id);
|
|
const scoreId = Number(req.params.scoreId);
|
|
const {
|
|
gameNumber,
|
|
score,
|
|
handicap = 0
|
|
} = req.body;
|
|
|
|
|
|
if (!eventId || !scoreId || !gameNumber || score === undefined || score === null) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID, 점수 ID, 게임 번호, 점수가 필요합니다.',
|
|
received: { eventId, scoreId, gameNumber, score }
|
|
});
|
|
}
|
|
|
|
// 점수 레코드 확인
|
|
const scoreRecord = await EventScore.findOne({
|
|
where: {
|
|
id: scoreId,
|
|
eventId
|
|
}
|
|
});
|
|
|
|
// 점수 레코드가 없으면 참가자 ID를 가져와서 새로 생성
|
|
if (!scoreRecord) {
|
|
// 기존 점수에서 참가자 ID 가져오기
|
|
const existingScore = await EventScore.findOne({
|
|
where: { eventId },
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
if (!existingScore) {
|
|
return res.status(404).json({
|
|
message: '참가자 정보를 찾을 수 없습니다.'
|
|
});
|
|
}
|
|
|
|
// 새로운 게임 점수 생성
|
|
const newScore = await EventScore.create({
|
|
eventId,
|
|
participantId: existingScore.participantId,
|
|
gameNumber,
|
|
score,
|
|
handicap,
|
|
totalScore: score + handicap
|
|
});
|
|
|
|
return res.json({
|
|
message: '새로운 게임 점수가 등록되었습니다.',
|
|
score: newScore
|
|
});
|
|
}
|
|
|
|
// 기존 레코드 업데이트
|
|
await scoreRecord.update({
|
|
gameNumber,
|
|
score,
|
|
handicap,
|
|
totalScore: score + handicap
|
|
});
|
|
|
|
// 캐시 무효화 추가
|
|
await averageService.invalidateMemberAverageCache(scoreRecord.participantId);
|
|
|
|
// 업데이트된 레코드 조회
|
|
const updatedScore = await EventScore.findOne({
|
|
where: {
|
|
id: scoreId,
|
|
eventId
|
|
}
|
|
});
|
|
|
|
res.json({
|
|
message: '점수가 수정되었습니다.',
|
|
score: updatedScore
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('점수 수정 오류:', error, JSON.stringify(error, Object.getOwnPropertyNames(error)));
|
|
res.status(500).json({
|
|
message: '점수 수정 중 오류가 발생했습니다.',
|
|
error: error.message || error.toString() || error
|
|
});
|
|
}
|
|
};
|
|
|
|
// 점수 삭제
|
|
exports.deleteEventScore = async (req, res) => {
|
|
try {
|
|
const { eventId, clubId, scoreId } = req.body;
|
|
|
|
if (!eventId || !clubId || !scoreId) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID, 클럽 ID, 점수 ID가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 점수 존재 여부 확인
|
|
const score = await EventScore.findOne({
|
|
where: {
|
|
id: scoreId,
|
|
eventId
|
|
},
|
|
include: [
|
|
{
|
|
model: EventParticipant,
|
|
attributes: ['id', 'status'],
|
|
include: [{
|
|
model: Member,
|
|
attributes: ['id', 'name']
|
|
}]
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!score) {
|
|
return res.status(404).json({ message: '점수를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await score.destroy();
|
|
|
|
// 점수 삭제 시 해당 회원의 에버리지 캡시 무효화
|
|
if (score.participant && score.participant.Member) {
|
|
try {
|
|
await averageService.invalidateMemberAverageCache(score.participant.Member.id);
|
|
console.log(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 완료`);
|
|
} catch (error) {
|
|
console.error(`회원 ${score.participant.Member.id}의 에버리지 캡시 무효화 오류:`, error);
|
|
}
|
|
}
|
|
|
|
res.json({
|
|
message: '점수가 삭제되었습니다.',
|
|
score: {
|
|
id: score.id,
|
|
userId: score.userId,
|
|
userName: score.participant.Member ? score.participant.Member.name : '알 수 없음',
|
|
gameNumber: score.gameNumber,
|
|
frameNumber: score.frameNumber
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('점수 삭제 오류:', error);
|
|
res.status(500).json({ message: '점수 삭제 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자의 모든 점수 삭제
|
|
exports.deleteParticipantScores = async (req, res) => {
|
|
try {
|
|
const eventId = Number(req.params.id);
|
|
const participantId = Number(req.params.participantId);
|
|
|
|
if (!eventId || !participantId) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID와 참가자 ID가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
// 점수 삭제
|
|
await EventScore.destroy({
|
|
where: {
|
|
eventId,
|
|
participantId
|
|
}
|
|
});
|
|
|
|
res.json({
|
|
message: '점수가 삭제되었습니다.'
|
|
});
|
|
} catch (error) {
|
|
console.error('점수 삭제 오류:', error);
|
|
res.status(500).json({
|
|
message: '점수 삭제 중 오류가 발생했습니다.'
|
|
});
|
|
}
|
|
};
|
|
|
|
// 이벤트 통계 조회
|
|
exports.getEventStats = async (req, res) => {
|
|
try {
|
|
const eventId = parseInt(req.params.id);
|
|
const { clubId } = req.query;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const stats = await EventScore.findAll({
|
|
where: { eventId },
|
|
attributes: [
|
|
'userId',
|
|
[sequelize.fn('AVG', sequelize.col('score')), 'averageScore'],
|
|
[sequelize.fn('MAX', sequelize.col('score')), 'highScore'],
|
|
[sequelize.fn('COUNT', sequelize.literal('CASE WHEN "isStrike" = true THEN 1 END')), 'strikeCount'],
|
|
[sequelize.fn('COUNT', sequelize.literal('CASE WHEN "isSpare" = true THEN 1 END')), 'spareCount']
|
|
],
|
|
include: [{
|
|
model: EventParticipant,
|
|
attributes: [],
|
|
include: [{
|
|
model: Member,
|
|
attributes: ['name']
|
|
}]
|
|
}],
|
|
group: ['userId', 'participant.id', 'participant.Member.id', 'participant.Member.name']
|
|
});
|
|
|
|
res.json(stats);
|
|
} catch (error) {
|
|
console.error('통계 조회 오류:', error);
|
|
res.status(500).json({ message: '통계 조회 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 캘린더용 이벤트 조회
|
|
exports.getCalendarEvents = async (req, res) => {
|
|
try {
|
|
const { clubId } = req.query;
|
|
const { start, end } = req.query;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const where = {
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
};
|
|
|
|
if (start && end) {
|
|
where.startDate = {
|
|
[Op.between]: [new Date(start), new Date(end)]
|
|
};
|
|
}
|
|
|
|
const events = await Event.findAll({
|
|
where,
|
|
attributes: [
|
|
'id',
|
|
'title',
|
|
'description',
|
|
'startDate',
|
|
'endDate',
|
|
'location',
|
|
'eventType',
|
|
'status'
|
|
],
|
|
order: [['startDate', 'ASC']]
|
|
});
|
|
|
|
const calendarEvents = events.map(event => ({
|
|
id: event.id,
|
|
title: event.title,
|
|
start: event.startDate,
|
|
end: event.endDate,
|
|
description: event.description,
|
|
location: event.location,
|
|
eventType: event.eventType,
|
|
status: event.status
|
|
}));
|
|
|
|
res.json(calendarEvents);
|
|
} catch (error) {
|
|
console.error('캘린더 이벤트 조회 오류:', error);
|
|
res.status(500).json({ message: '캘린더 이벤트 조회 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 이벤트 참가 신청
|
|
exports.registerForEvent = async (req, res) => {
|
|
try {
|
|
const eventId = parseInt(req.params.id);
|
|
const { clubId } = req.query;
|
|
const { userId, teamNumber } = req.body;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 이미 등록된 참가자인지 확인
|
|
const existingParticipant = await EventParticipant.findOne({
|
|
where: {
|
|
eventId,
|
|
userId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
}
|
|
});
|
|
|
|
if (existingParticipant) {
|
|
return res.status(400).json({ message: '이미 등록된 참가자입니다.' });
|
|
}
|
|
|
|
const participant = await EventParticipant.create({
|
|
eventId,
|
|
userId,
|
|
teamNumber,
|
|
status: 'registered',
|
|
registeredAt: new Date()
|
|
});
|
|
|
|
res.status(201).json(participant);
|
|
} catch (error) {
|
|
console.error('참가 신청 오류:', error);
|
|
res.status(500).json({ message: '참가 신청 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자 제거
|
|
exports.removeParticipant = async (req, res) => {
|
|
try {
|
|
const { id: eventId, userId } = req.params;
|
|
const { clubId } = req.query;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const participant = await EventParticipant.findOne({
|
|
where: {
|
|
eventId,
|
|
userId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
include: [{
|
|
model: Event,
|
|
where: {
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
}]
|
|
});
|
|
|
|
if (!participant) {
|
|
return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await participant.update({ status: '취소' });
|
|
res.json({ message: '참가자가 제거되었습니다.' });
|
|
} catch (error) {
|
|
console.error('참가자 제거 오류:', error);
|
|
res.status(500).json({ message: '참가자 제거 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자 상태 업데이트
|
|
exports.updateParticipantStatus = async (req, res) => {
|
|
try {
|
|
const { id: eventId, userId } = req.params;
|
|
const { clubId } = req.query;
|
|
const { status, attendance, paymentStatus, paymentAmount } = req.body;
|
|
|
|
if (!clubId) {
|
|
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
const participant = await EventParticipant.findOne({
|
|
where: {
|
|
eventId,
|
|
userId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
include: [{
|
|
model: Event,
|
|
where: {
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
}]
|
|
});
|
|
|
|
if (!participant) {
|
|
return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await participant.update({
|
|
status,
|
|
attendance,
|
|
paymentStatus,
|
|
paymentAmount
|
|
});
|
|
|
|
res.json(participant);
|
|
} catch (error) {
|
|
console.error('참가자 상태 업데이트 오류:', error);
|
|
res.status(500).json({ message: '참가자 상태 업데이트 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 클럽별 이벤트 조회
|
|
exports.getEventsByClub = async (req, res) => {
|
|
try {
|
|
const clubId = parseInt(req.params.clubId);
|
|
|
|
const events = await Event.findAll({
|
|
where: {
|
|
clubId,
|
|
status: 'active'
|
|
},
|
|
order: [['startDate', 'ASC']]
|
|
});
|
|
|
|
res.json(events);
|
|
} catch (error) {
|
|
console.error('클럽별 이벤트 조회 오류:', error);
|
|
res.status(500).json({ message: '클럽별 이벤트 목록을 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 사용자별 참가 이벤트 조회
|
|
exports.getUserEvents = async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
|
|
const events = await Event.findAll({
|
|
include: [
|
|
{
|
|
model: EventParticipant,
|
|
where: { userId },
|
|
attributes: []
|
|
},
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}
|
|
],
|
|
order: [['startDate', 'ASC']]
|
|
});
|
|
|
|
res.json(events);
|
|
} catch (error) {
|
|
console.error('사용자별 이벤트 조회 오류:', error);
|
|
res.status(500).json({ message: '사용자별 이벤트 목록을 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 이벤트 참가자 목록 조회
|
|
exports.getEventParticipants = async (req, res) => {
|
|
try {
|
|
const { eventId, clubId } = req.body;
|
|
|
|
if (!eventId || !clubId) {
|
|
return res.status(400).json({ message: '이벤트 ID와 클럽 ID가 필요합니다.' });
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
const participants = await EventParticipant.findAll({
|
|
where: {
|
|
eventId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name', 'email', 'phone', 'memberType', 'gender']
|
|
}
|
|
],
|
|
order: [
|
|
['createdAt', 'ASC']
|
|
]
|
|
});
|
|
|
|
// 참가자들의 동적 에버리지 계산
|
|
const participantsWithAverage = await Promise.all(
|
|
participants.map(async (participant) => {
|
|
const participantData = participant.toJSON();
|
|
if (participantData.Member && participantData.Member.id) {
|
|
try {
|
|
const averageData = await averageService.calculateMemberAverageByClubSetting(
|
|
participantData.Member.id,
|
|
clubId
|
|
);
|
|
|
|
return {
|
|
...participantData,
|
|
Member: {
|
|
...participantData.Member,
|
|
average: averageData.average
|
|
}
|
|
};
|
|
} catch (error) {
|
|
console.error(`회원 ${participantData.Member.id}의 에버리지 계산 오류:`, error);
|
|
return {
|
|
...participantData,
|
|
Member: {
|
|
...participantData.Member,
|
|
average: 0
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return participantData;
|
|
})
|
|
);
|
|
|
|
res.json({
|
|
message: '참가자 목록 조회가 완료되었습니다.',
|
|
participants: participantsWithAverage
|
|
});
|
|
} catch (error) {
|
|
console.error('참가자 목록 조회 오류:', error);
|
|
res.status(500).json({ message: '참가자 목록을 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자 정보 핵심 update 함수 (중복 제거)
|
|
async function updateEventParticipantCore({ id, eventId, memberId, clubId, status, paymentStatus, comment }) {
|
|
const [updatedCount] = await EventParticipant.update(
|
|
{ status, paymentStatus, comment },
|
|
{
|
|
where: {
|
|
id,
|
|
eventId,
|
|
memberId
|
|
}
|
|
}
|
|
);
|
|
return updatedCount;
|
|
}
|
|
|
|
// 이벤트 참가자 등록 (이미 있으면 update, 없으면 create)
|
|
exports.addEventParticipant = async (req, res) => {
|
|
try {
|
|
const {
|
|
clubId,
|
|
memberId,
|
|
status,
|
|
paymentStatus,
|
|
comment
|
|
} = req.body;
|
|
const eventId = req.params.id;
|
|
if (!eventId || !clubId || !memberId) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID, 클럽 ID, 사용자 ID가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 클럽 회원 확인
|
|
const member = await Member.findOne({
|
|
where: {
|
|
id: memberId,
|
|
clubId,
|
|
status: 'active'
|
|
}
|
|
});
|
|
|
|
if (!member) {
|
|
return res.status(400).json({ message: '클럽 회원이 아닙니다.' });
|
|
}
|
|
|
|
// 이미 참가 신청했는지 확인
|
|
const existingParticipant = await EventParticipant.findOne({
|
|
where: {
|
|
eventId,
|
|
memberId,
|
|
status: { [Op.ne]: 'canceled' }
|
|
}
|
|
});
|
|
|
|
let participant;
|
|
let isUpdate = false;
|
|
if (existingParticipant) {
|
|
// 이미 있으면 update (핵심 update 함수 활용)
|
|
await updateEventParticipantCore({
|
|
id: existingParticipant.id,
|
|
eventId,
|
|
memberId,
|
|
clubId,
|
|
status: status || existingParticipant.status,
|
|
paymentStatus: paymentStatus || existingParticipant.paymentStatus,
|
|
comment: comment || existingParticipant.comment
|
|
});
|
|
participant = await EventParticipant.findOne({
|
|
where: { id: existingParticipant.id },
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name']
|
|
}
|
|
]
|
|
});
|
|
isUpdate = true;
|
|
} else {
|
|
// 없으면 create
|
|
participant = await EventParticipant.create({
|
|
eventId,
|
|
memberId,
|
|
status: status || '참가예정',
|
|
paymentStatus: paymentStatus || '미납',
|
|
comment: comment || null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
});
|
|
participant = await EventParticipant.findOne({
|
|
where: { id: participant.id },
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name']
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
res.status(201).json({
|
|
message: isUpdate ? '기존 참가자 정보를 수정했습니다.' : '참가자가 등록되었습니다.',
|
|
participant
|
|
});
|
|
} catch (error) {
|
|
console.error('참가자 등록/수정 오류:', error);
|
|
res.status(500).json({ message: '참가자 등록/수정 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자 정보 수정
|
|
exports.updateEventParticipant = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
const { id, clubId, memberId, status, paymentStatus, comment } = req.body;
|
|
|
|
if (!eventId || !clubId || !memberId || !id) {
|
|
return res.status(400).json({ message: '이벤트 ID, 클럽 ID, 사용자 ID가 필요합니다.' });
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
clubId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 참가자 정보 수정
|
|
const [updatedCount] = await EventParticipant.update(
|
|
{ status, paymentStatus, comment },
|
|
{
|
|
where: {
|
|
id,
|
|
eventId,
|
|
memberId
|
|
}
|
|
}
|
|
);
|
|
|
|
if (updatedCount === 0) {
|
|
return res.status(404).json({ message: '수정할 참가자 정보를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 수정된 참가자 정보 조회
|
|
const updatedParticipant = await EventParticipant.findOne({
|
|
where: { id },
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name', 'email', 'phone', 'memberType']
|
|
}
|
|
]
|
|
});
|
|
|
|
res.json({
|
|
message: '참가자 정보가 수정되었습니다.',
|
|
participant: {
|
|
...updatedParticipant.toJSON(),
|
|
userName: updatedParticipant.Member ? updatedParticipant.Member.name : '알 수 없음',
|
|
userEmail: updatedParticipant.Member ? updatedParticipant.Member.email : '',
|
|
userPhone: updatedParticipant.Member ? updatedParticipant.Member.phone : '',
|
|
userType: updatedParticipant.Member ? updatedParticipant.Member.memberType : ''
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('참가자 정보 수정 오류:', error);
|
|
res.status(500).json({ message: '참가자 정보 수정 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// [팀 생성 기능] 이벤트별 팀 목록 조회
|
|
exports.getEventTeams = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
if (!eventId) {
|
|
return res.status(400).json({ message: '이벤트 ID가 필요합니다.' });
|
|
}
|
|
const teams = await EventTeam.findAll({
|
|
where: { eventId },
|
|
include: [
|
|
{
|
|
model: EventTeamMember,
|
|
include: [
|
|
{
|
|
model: EventParticipant,
|
|
include: [{ model: Member }]
|
|
}
|
|
]
|
|
}
|
|
],
|
|
order: [['teamNumber', 'ASC']]
|
|
});
|
|
const result = teams.map(team => ({
|
|
id: team.id,
|
|
teamNumber: team.teamNumber,
|
|
handicap: team.handicap,
|
|
members: (team.EventTeamMembers || [])
|
|
.sort((a, b) => a.order - b.order)
|
|
.map(tm => ({
|
|
id: tm.eventParticipantId,
|
|
name: tm.EventParticipant?.Member?.name || `참가자 ${tm.eventParticipantId}`,
|
|
order: tm.order
|
|
}))
|
|
}));
|
|
res.json({ teams: result });
|
|
} catch (error) {
|
|
console.error('팀 목록 조회 오류:', error);
|
|
res.status(500).json({ message: '팀 목록을 가져오는 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// [팀 생성 기능] 이벤트별 팀 배정 저장
|
|
exports.createOrUpdateEventTeams = async (req, res) => {
|
|
const eventId = req.params.id;
|
|
const { teams } = req.body; // [{teamNumber, handicap, members:[id]}]
|
|
|
|
if (!eventId || !Array.isArray(teams)) {
|
|
return res.status(400).json({ message: '이벤트 ID와 팀 정보가 필요합니다.' });
|
|
}
|
|
|
|
try {
|
|
await sequelize.transaction(async (t) => {
|
|
// 기존 팀 및 팀멤버 데이터 삭제
|
|
await EventTeam.destroy({ where: { eventId }, transaction: t });
|
|
await EventTeamMember.destroy({
|
|
where: {}, // eventId로만 삭제하려면, eventTeamId로 팀 id 목록을 먼저 조회해서 삭제
|
|
transaction: t
|
|
});
|
|
|
|
// 새 팀 및 팀멤버 데이터 저장
|
|
for (const team of teams) {
|
|
// 팀 생성
|
|
const createdTeam = await EventTeam.create({
|
|
eventId,
|
|
teamNumber: team.teamNumber,
|
|
handicap: team.handicap || 0
|
|
}, { transaction: t });
|
|
|
|
// 팀 멤버(순서 포함) 저장
|
|
if (Array.isArray(team.members) && team.members.length > 0) {
|
|
const teamMemberRows = team.members
|
|
.filter(m => m.id != null)
|
|
.map(m => ({
|
|
eventTeamId: createdTeam.id,
|
|
eventParticipantId: m.id,
|
|
order: m.order ?? 1
|
|
}));
|
|
if (teamMemberRows.length > 0) {
|
|
await EventTeamMember.bulkCreate(teamMemberRows, { transaction: t });
|
|
}
|
|
}
|
|
}
|
|
});
|
|
res.json({ message: '팀 배정이 저장되었습니다.' });
|
|
} catch (error) {
|
|
console.error('팀 배정 저장 오류:', error);
|
|
res.status(500).json({ message: '팀 배정 저장 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|
|
|
|
// 참가자 삭제 (소프트 삭제)
|
|
exports.deleteEventParticipant = async (req, res) => {
|
|
try {
|
|
const eventId = req.params.id;
|
|
const { id } = req.body;
|
|
|
|
if (!eventId || !id) {
|
|
return res.status(400).json({
|
|
message: '이벤트 ID, 참가자 ID가 필요합니다.'
|
|
});
|
|
}
|
|
|
|
// 이벤트 존재 여부 확인
|
|
const event = await Event.findOne({
|
|
where: {
|
|
id: eventId,
|
|
status: { [Op.ne]: 'deleted' }
|
|
}
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 참가자 존재 여부 확인
|
|
const participant = await EventParticipant.findOne({
|
|
where: {
|
|
eventId,
|
|
id,
|
|
status: { [Op.ne]: 'canceled' }
|
|
},
|
|
include: [
|
|
{
|
|
model: Member,
|
|
attributes: ['id', 'name', 'memberType']
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!participant) {
|
|
return res.status(404).json({ message: '참가자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 참가자의 점수도 함께 삭제
|
|
await EventScore.destroy({
|
|
where: {
|
|
eventId,
|
|
participantId: id
|
|
}
|
|
});
|
|
|
|
await participant.update({ status: '취소' });
|
|
|
|
res.json({
|
|
message: '참가자가 삭제되었습니다.',
|
|
participant: {
|
|
id: participant.id
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('참가자 삭제 오류:', error);
|
|
res.status(500).json({ message: '참가자 삭제 중 오류가 발생했습니다.' });
|
|
}
|
|
};
|