Files
bowlingManager/backend/controllers/eventController.js
T
2025-05-07 21:02:50 +09:00

1582 lines
44 KiB
JavaScript

/**
* 이벤트 컨트롤러
* 이벤트 관련 기능을 처리하는 컨트롤러
*/
const { Event, EventParticipant, Member, Club, EventScore, ClubMember, EventTeam, EventTeamMember } = require('../models');
const { Op } = require('sequelize');
const { sequelize } = require('../models');
// 이벤트 목록 조회
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]: '삭제' }
},
include: [
{
model: Club,
attributes: ['id', 'name']
},
{
model: EventParticipant,
attributes: ['id', 'status', 'paymentStatus'],
where: {
status: { [Op.ne]: '취소' }
},
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]: '삭제' }
},
include: [
{
model: Club,
attributes: ['id', 'name', 'location']
},
{
model: EventParticipant,
attributes: ['id', 'status', 'paymentStatus'],
where: { status: { [Op.ne]: '취소' } },
required: false,
include: [
{
model: Member,
attributes: ['id', 'name', 'average']
}
]
}
]
});
if (!event) {
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
}
const formattedEvent = {
...event.toJSON(),
participantCount: event.participants ? event.participants.length : 0,
clubName: event.Club ? event.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 === '취소') 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', 'average']
});
// 4. 참가자 명단 추출 (참가자 테이블 + Member 조인)
const participants = await EventParticipant.findAll({
where: { eventId: event.id, status: { [Op.ne]: '취소' } },
include: [
{ model: Member, attributes: [[sequelize.col('id'), 'memberId'], 'name', 'memberType', 'gender', 'average'] }
]
});
// 5. 참가 신청 가능 명단(아직 참가하지 않은 회원)
// Sequelize 인스턴스에서 dataValues 추출
const allMemberObjs = allMembers.map(m => m.dataValues);
// '취소'가 아닌 status가 하나라도 있으면 참가자로 간주 (중복 제거)
const participantMemberIds = [
...new Set(
participants
.filter(p => p.dataValues.status !== '취소')
.map(p => parseInt(p.dataValues.memberId))
)
];
const availableMembers = allMemberObjs.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', 'average'] }]
}
]
}
]
});
// 점수
const scores = await EventScore.findAll({ where: { eventId: event.id } });
// 팀별 그룹핑
let teamList = [];
if (teams.length > 0) {
teamList = teams.map(team => ({
teamId: team.id,
teamNumber: team.teamNumber,
handicap: team.handicap,
members: (team.EventTeamMembers || []).map(tm => {
const participant = tm.EventParticipant;
const member = participant?.Member;
const scoreObj = scores.find(s => s.participantId === participant?.id);
return {
participantId: participant?.id,
memberId: member?.memberId,
name: member?.name || `참가자 ${participant?.id}`,
gender: member?.gender,
memberType: member?.memberType,
average: member?.average || 0,
handicap: member?.handicap || 0,
status: participant?.status,
paymentStatus: participant?.paymentStatus,
comment: participant?.comment,
isGuest: member?.memberType === '게스트',
score: scoreObj ? scoreObj.score : null
};
})
}));
}
// 참가자 리스트(팀 없는 경우)
let participantList = [];
participantList = participants.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,
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,
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,
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]: '취소' }
},
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]: '취소' }
}
}
);
await event.update({ status: 'deleted' });
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]: '취소' }
}
});
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
});
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;
console.log('Updating score:', {
params: { eventId, scoreId },
body: { gameNumber, score, handicap }
});
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
});
// 업데이트된 레코드 조회
const updatedScore = await EventScore.findOne({
where: {
id: scoreId,
eventId
}
});
res.json({
message: '점수가 수정되었습니다.',
score: updatedScore
});
} catch (error) {
console.error('점수 수정 오류:', error);
res.status(500).json({
message: '점수 수정 중 오류가 발생했습니다.',
error: error.message
});
}
};
// 점수 삭제
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();
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]: '취소' }
}
});
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]: '취소' }
},
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]: '취소' }
},
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]: '취소' }
},
include: [
{
model: Member,
attributes: ['id', 'name', 'email', 'phone', 'memberType', 'average']
}
],
order: [
['createdAt', 'ASC']
]
});
const formattedParticipants = participants.map(participant => ({
...participant.toJSON(),
}));
res.json({
message: '참가자 목록 조회가 완료되었습니다.',
participants: formattedParticipants
});
} catch (error) {
console.error('참가자 목록 조회 오류:', error);
res.status(500).json({ message: '참가자 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
// 이벤트 참가자 등록
exports.addEventParticipant = async (req, res) => {
try {
const {
clubId,
memberId,
status,
paymentStatus
} = 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]: '취소' }
}
});
if (existingParticipant) {
return res.status(400).json({ message: '이미 참가 신청한 사용자입니다.' });
}
// 참가자 수 제한 확인
if (event.maxParticipants > 0) {
const currentParticipants = await EventParticipant.count({
where: {
eventId,
status: { [Op.ne]: '취소' }
}
});
if (currentParticipants >= event.maxParticipants) {
return res.status(400).json({ message: '참가자 수가 초과되었습니다.' });
}
}
const participant = await EventParticipant.create({
eventId,
memberId,
status: status || '참가예정',
paymentStatus: paymentStatus || '미납',
createdAt: new Date(),
updatedAt: new Date()
});
// 참가자 정보 조회
const participantWithDetails = await EventParticipant.findOne({
where: { id: participant.id },
include: [
{
model: Member,
attributes: ['id', 'name']
}
]
});
res.status(201).json({
message: '참가자가 등록되었습니다.',
participant: participantWithDetails
});
} 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 } = 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 },
{
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]: '취소' }
},
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: '참가자 삭제 중 오류가 발생했습니다.' });
}
};