init
This commit is contained in:
@@ -0,0 +1,925 @@
|
||||
/**
|
||||
* 클럽 컨트롤러
|
||||
* 클럽 관련 기능을 처리하는 컨트롤러
|
||||
*/
|
||||
const Club = require('../models/Club');
|
||||
const User = require('../models/User');
|
||||
const Member = require('../models/Member');
|
||||
const ClubFeature = require('../models/ClubFeature');
|
||||
const Event = require('../models/Event');
|
||||
const EventParticipant = require('../models/EventParticipant');
|
||||
const EventScore = require('../models/EventScore');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// 관리자 권한 체크 함수
|
||||
const isAdmin = (user) => {
|
||||
return user && (user.role === 'admin' || user.role === 'superadmin');
|
||||
};
|
||||
|
||||
// 모든 클럽 조회
|
||||
exports.getAllClubs = async (req, res) => {
|
||||
try {
|
||||
const clubs = await Club.findAll({
|
||||
where: { status: 'active' },
|
||||
include: [
|
||||
{
|
||||
model: Member,
|
||||
where: { memberType: '모임장' },
|
||||
required: false,
|
||||
attributes: ['id', 'name', 'email', 'phone', 'memberType']
|
||||
}
|
||||
],
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
const formattedClubs = clubs.map(club => {
|
||||
const clubData = club.toJSON();
|
||||
// 모임장 정보 추가
|
||||
const ownerMember = clubData.Members && clubData.Members.length > 0 ? clubData.Members[0] : null;
|
||||
|
||||
return {
|
||||
...clubData,
|
||||
ownerName: ownerMember ? ownerMember.name : '알 수 없음',
|
||||
ownerEmail: ownerMember ? ownerMember.email : '',
|
||||
ownerPhone: ownerMember ? ownerMember.phone : '',
|
||||
ownerMemberType: ownerMember ? ownerMember.memberType : ''
|
||||
};
|
||||
});
|
||||
|
||||
res.json(formattedClubs);
|
||||
} catch (error) {
|
||||
console.error('클럽 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '클럽 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 특정 클럽 조회
|
||||
exports.getClubById = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
console.log(`clubId: ${clubId}`);
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: "유효하지 않은 클럽 ID입니다." });
|
||||
}
|
||||
|
||||
const club = await Club.findByPk(clubId);
|
||||
// console.log(`club: ${club}`);
|
||||
if (club) {
|
||||
// 클럽 소유자 정보 조회
|
||||
const owner = await Member.findOne({where: {userId: club.ownerId}}, {
|
||||
attributes: ['name', 'email', 'memberType']
|
||||
});
|
||||
|
||||
const clubData = club.toJSON();
|
||||
const formattedClub = {
|
||||
...clubData,
|
||||
ownerEmail: owner ? owner.email : '',
|
||||
ownerName: owner ? owner.name : '',
|
||||
ownerMemberType: owner ? owner.memberType : ''
|
||||
};
|
||||
|
||||
res.json(formattedClub);
|
||||
} else {
|
||||
res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('클럽 조회 오류:', error);
|
||||
res.status(500).json({ message: '클럽 정보를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 새 클럽 생성
|
||||
exports.createClub = async (req, res) => {
|
||||
try {
|
||||
const { name, location, description, ownerEmail, contact, features, sendInvite } = req.body;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!name || !ownerEmail) {
|
||||
return res.status(400).json({ message: '클럽 이름과 모임장 이메일은 필수 항목입니다.' });
|
||||
}
|
||||
|
||||
// 이메일로 사용자 조회
|
||||
const owner = await User.findOne({ where: { email: ownerEmail } });
|
||||
|
||||
if (!owner) {
|
||||
return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 클럽 생성
|
||||
const newClub = await Club.create({
|
||||
name,
|
||||
location,
|
||||
description,
|
||||
contact,
|
||||
ownerId: owner.id,
|
||||
memberCount: 1, // 모임장 포함
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
// 클럽 모임장을 클럽 멤버로 추가
|
||||
await Member.create({
|
||||
name: owner.name,
|
||||
gender: '남성',
|
||||
userId: owner.id,
|
||||
clubId: newClub.id,
|
||||
memberType: '모임장',
|
||||
joinDate: new Date(),
|
||||
status: 'active'
|
||||
});
|
||||
|
||||
// 선택된 기능 추가
|
||||
if (features && features.length > 0) {
|
||||
const featurePromises = features.map(featureId =>
|
||||
ClubFeature.create({
|
||||
clubId: newClub.id,
|
||||
featureId,
|
||||
enabled: true
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(featurePromises);
|
||||
}
|
||||
|
||||
// 초대 이메일 발송 로직 (sendInvite가 true인 경우)
|
||||
if (sendInvite) {
|
||||
// TODO: 초대 이메일 발송 로직 구현
|
||||
console.log(`${ownerEmail}에게 초대 이메일 발송 예정`);
|
||||
}
|
||||
|
||||
res.status(201).json(newClub);
|
||||
} catch (error) {
|
||||
console.error('클럽 생성 오류:', error);
|
||||
res.status(500).json({ message: '클럽을 생성하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 정보 업데이트
|
||||
exports.updateClub = async (req, res) => {
|
||||
try {
|
||||
const { clubId, name, location, description, contact, ownerEmail, status } = req.body;
|
||||
|
||||
const club = await Club.findByPk(clubId);
|
||||
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 이메일이 변경된 경우 새 모임장 확인
|
||||
let newOwnerId = club.ownerId;
|
||||
let ownerChanged = false;
|
||||
|
||||
if (ownerEmail && ownerEmail !== club.ownerEmail) {
|
||||
const newOwner = await User.findOne({ where: { email: ownerEmail } });
|
||||
if (!newOwner) {
|
||||
return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' });
|
||||
}
|
||||
newOwnerId = newOwner.id;
|
||||
ownerChanged = true;
|
||||
}
|
||||
|
||||
// 클럽 정보 업데이트
|
||||
await club.update({
|
||||
name: name || club.name,
|
||||
location: location || club.location,
|
||||
description: description !== undefined ? description : club.description,
|
||||
contact: contact !== undefined ? contact : club.contact,
|
||||
status: status || club.status,
|
||||
ownerId: newOwnerId
|
||||
});
|
||||
|
||||
// 모임장이 변경된 경우 Members 테이블 업데이트
|
||||
if (ownerChanged) {
|
||||
// 기존 모임장을 정회원으로 변경
|
||||
await Member.update(
|
||||
{ memberType: '정회원' },
|
||||
{
|
||||
where: {
|
||||
clubId: clubId,
|
||||
userId: club.ownerId,
|
||||
memberType: '모임장'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 새 모임장 확인 및 업데이트
|
||||
const newOwnerMember = await Member.findOne({
|
||||
where: {
|
||||
clubId: clubId,
|
||||
userId: newOwnerId
|
||||
}
|
||||
});
|
||||
|
||||
if (newOwnerMember) {
|
||||
// 기존 회원인 경우 모임장으로 변경
|
||||
await newOwnerMember.update({ memberType: '모임장' });
|
||||
} else {
|
||||
// 새로운 사용자 정보 가져오기
|
||||
const newOwnerUser = await User.findByPk(newOwnerId);
|
||||
if (!newOwnerUser) {
|
||||
return res.status(404).json({ message: '새 모임장 사용자 정보를 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 새 회원으로 모임장 추가
|
||||
await Member.create({
|
||||
userId: newOwnerId,
|
||||
clubId: clubId,
|
||||
name: newOwnerUser.name || ownerEmail.split('@')[0], // 이름이 없는 경우 이메일 아이디 사용
|
||||
gender: '남성', // 성별 정보가 없는 경우 기본값
|
||||
memberType: '모임장',
|
||||
joinDate: new Date(),
|
||||
status: 'active'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json(club);
|
||||
} catch (error) {
|
||||
console.error('클럽 정보 업데이트 오류:', error);
|
||||
res.status(500).json({ message: '클럽 정보를 업데이트하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 삭제
|
||||
exports.deleteClub = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
// 클럽 존재 여부 확인
|
||||
const club = await Club.findByPk(clubId);
|
||||
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 소프트 삭제 (상태만 변경)
|
||||
await club.update({ status: 'inactive' });
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('클럽 삭제 오류:', error);
|
||||
res.status(500).json({ message: '클럽을 삭제하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 기능 관리
|
||||
exports.manageClubFeatures = async (req, res) => {
|
||||
try {
|
||||
const { clubId, features } = req.body;
|
||||
|
||||
// 클럽 존재 여부 확인
|
||||
const club = await Club.findByPk(clubId);
|
||||
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 클럽 기능 업데이트
|
||||
await club.update({ features });
|
||||
|
||||
res.json({
|
||||
id: club.id,
|
||||
features: club.features,
|
||||
updatedAt: club.updatedAt
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('클럽 기능 관리 오류:', error);
|
||||
res.status(500).json({ message: '클럽 기능을 업데이트하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 회원 목록 조회
|
||||
exports.getClubMembers = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
// 클럽 존재 여부 확인
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
const members = await Member.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: {
|
||||
[Op.ne]: 'deleted'
|
||||
}
|
||||
},
|
||||
order: [
|
||||
['memberType', 'ASC'],
|
||||
['name', 'ASC']
|
||||
]
|
||||
});
|
||||
|
||||
// 응답 데이터 형식화
|
||||
const formattedMembers = members.map(member => {
|
||||
const memberData = member.toJSON();
|
||||
return {
|
||||
id: memberData.id,
|
||||
name: memberData.name,
|
||||
gender: memberData.gender,
|
||||
memberType: memberData.memberType,
|
||||
handicap: memberData.handicap,
|
||||
average: memberData.average,
|
||||
games: memberData.games,
|
||||
phone: memberData.phone,
|
||||
email: memberData.user ? memberData.user.email : memberData.email,
|
||||
joinDate: memberData.joinDate,
|
||||
status: memberData.status
|
||||
};
|
||||
});
|
||||
|
||||
res.json(formattedMembers);
|
||||
} catch (error) {
|
||||
console.error('클럽 회원 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '클럽 회원 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 회원 추가
|
||||
exports.addClubMember = async (req, res) => {
|
||||
try {
|
||||
const { clubId, userId, name, gender, memberType, email, phone } = req.body;
|
||||
|
||||
if (!clubId || !name || !gender) {
|
||||
return res.status(400).json({ message: '필수 정보가 누락되었습니다.' });
|
||||
}
|
||||
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 회원 생성
|
||||
const newMember = await Member.create({
|
||||
clubId,
|
||||
userId,
|
||||
name,
|
||||
gender,
|
||||
memberType: memberType || '준회원',
|
||||
email,
|
||||
phone
|
||||
});
|
||||
|
||||
// 클럽의 회원 수 업데이트
|
||||
const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } });
|
||||
await club.update({ memberCount });
|
||||
|
||||
res.status(201).json(newMember);
|
||||
} catch (error) {
|
||||
console.error('클럽 회원 추가 오류:', error);
|
||||
res.status(500).json({ message: '회원을 추가하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 회원 정보 수정
|
||||
exports.updateClubMember = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId, name, gender, memberType, email, phone, status } = req.body;
|
||||
|
||||
if (!clubId || !memberId) {
|
||||
return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' });
|
||||
}
|
||||
|
||||
const member = await Member.findOne({
|
||||
where: {
|
||||
id: memberId,
|
||||
clubId: clubId
|
||||
}
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
return res.status(404).json({ message: '회원을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 모임장은 한 명만 존재할 수 있음
|
||||
if (memberType === '모임장') {
|
||||
const existingOwner = await Member.findOne({
|
||||
where: {
|
||||
clubId: clubId,
|
||||
memberType: '모임장',
|
||||
status: 'active',
|
||||
id: { [Op.ne]: memberId }
|
||||
}
|
||||
});
|
||||
|
||||
if (existingOwner) {
|
||||
return res.status(400).json({ message: '이미 다른 모임장이 존재합니다.' });
|
||||
}
|
||||
}
|
||||
|
||||
// 회원 정보 업데이트
|
||||
await member.update({
|
||||
name: name || member.name,
|
||||
gender: gender || member.gender,
|
||||
memberType: memberType || member.memberType,
|
||||
email: email || member.email,
|
||||
phone: phone || member.phone,
|
||||
status: status || member.status
|
||||
});
|
||||
|
||||
// 클럽의 회원 수 업데이트
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (club) {
|
||||
const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } });
|
||||
await club.update({ memberCount });
|
||||
}
|
||||
|
||||
res.json(member);
|
||||
} catch (error) {
|
||||
console.error('클럽 회원 정보 수정 오류:', error);
|
||||
res.status(500).json({ message: '회원 정보를 수정하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 회원 삭제
|
||||
exports.deleteClubMember = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.body;
|
||||
|
||||
if (!clubId || !memberId) {
|
||||
return res.status(400).json({ message: '잘못된 클럽 또는 회원 ID입니다.' });
|
||||
}
|
||||
|
||||
const member = await Member.findOne({
|
||||
where: {
|
||||
id: memberId,
|
||||
clubId: clubId
|
||||
}
|
||||
});
|
||||
|
||||
if (!member) {
|
||||
return res.status(404).json({ message: '회원을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
// 회원 상태를 deleted로 변경
|
||||
await member.update({ status: 'deleted' });
|
||||
|
||||
// 클럽의 회원 수 감소
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (club) {
|
||||
const memberCount = await Member.count({ where: { clubId: clubId, status: 'active' } });
|
||||
await club.update({ memberCount });
|
||||
}
|
||||
|
||||
res.json({ message: '회원이 성공적으로 삭제되었습니다.' });
|
||||
} catch (error) {
|
||||
console.error('클럽 회원 삭제 오류:', error);
|
||||
res.status(500).json({ message: '회원을 삭제하는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 회원 통계 조회
|
||||
exports.getMemberStats = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const members = await Member.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
const stats = {
|
||||
totalCount: members.length,
|
||||
regularCount: members.filter(m => m.memberType === '정회원').length,
|
||||
associateCount: members.filter(m => m.memberType === '준회원').length
|
||||
};
|
||||
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
console.error('회원 통계 조회 오류:', error);
|
||||
res.status(500).json({ message: '회원 통계를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 다음 모임 정보 조회
|
||||
exports.getNextMeeting = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const nextMeeting = await Event.findOne({
|
||||
where: {
|
||||
clubId,
|
||||
startDate: { [Op.gt]: new Date() },
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: EventParticipant,
|
||||
as: 'participants',
|
||||
where: {
|
||||
status: { [Op.ne]: '취소' }
|
||||
},
|
||||
required: false
|
||||
}],
|
||||
order: [['startDate', 'ASC']]
|
||||
});
|
||||
|
||||
if (!nextMeeting) {
|
||||
return res.json(null);
|
||||
}
|
||||
|
||||
const meetingData = {
|
||||
date: nextMeeting.startDate,
|
||||
participantsCount: nextMeeting.participants ? nextMeeting.participants.length : 0,
|
||||
title: nextMeeting.title,
|
||||
location: nextMeeting.location
|
||||
};
|
||||
|
||||
res.json(meetingData);
|
||||
} catch (error) {
|
||||
console.error('다음 모임 정보 조회 오류:', error);
|
||||
res.status(500).json({ message: '다음 모임 정보를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 최근 모임 정보 조회
|
||||
exports.getLastMeeting = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const lastMeeting = await Event.findOne({
|
||||
where: {
|
||||
clubId,
|
||||
startDate: { [Op.lt]: new Date() },
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: EventParticipant,
|
||||
as: 'participants',
|
||||
where: {
|
||||
status: { [Op.ne]: '취소' }
|
||||
},
|
||||
required: false
|
||||
}],
|
||||
order: [['startDate', 'DESC']]
|
||||
});
|
||||
|
||||
if (!lastMeeting) {
|
||||
return res.json(null);
|
||||
}
|
||||
|
||||
const meetingData = {
|
||||
date: lastMeeting.startDate,
|
||||
participantsCount: lastMeeting.participants ? lastMeeting.participants.length : 0,
|
||||
title: lastMeeting.title,
|
||||
games: lastMeeting.gameCount || 0
|
||||
};
|
||||
|
||||
res.json(meetingData);
|
||||
} catch (error) {
|
||||
console.error('최근 모임 정보 조회 오류:', error);
|
||||
res.status(500).json({ message: '최근 모임 정보를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 점수 통계 조회
|
||||
exports.getScoreStats = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
// 최근 3개월 내의 이벤트 점수만 집계
|
||||
const threeMonthsAgo = new Date();
|
||||
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
|
||||
|
||||
const events = await Event.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
startDate: { [Op.gte]: threeMonthsAgo },
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: EventParticipant,
|
||||
as: 'participants',
|
||||
include: [{
|
||||
model: EventScore,
|
||||
as: 'scores'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
let allScores = [];
|
||||
let totalGames = 0;
|
||||
|
||||
events.forEach(event => {
|
||||
event.participants.forEach(participant => {
|
||||
if (participant.scores && participant.scores.length > 0) {
|
||||
allScores = allScores.concat(participant.scores.map(score => score.score));
|
||||
totalGames += participant.scores.length;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const stats = {
|
||||
topGame: allScores.length > 0 ? Math.max(...allScores) : 0,
|
||||
averageScore: allScores.length > 0 ? Math.round(allScores.reduce((a, b) => a + b, 0) / allScores.length) : 0,
|
||||
totalGames: totalGames
|
||||
};
|
||||
|
||||
res.json(stats);
|
||||
} catch (error) {
|
||||
console.error('점수 통계 조회 오류:', error);
|
||||
res.status(500).json({ message: '점수 통계를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 상위 회원 목록 조회
|
||||
exports.getTopMembers = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const topMembers = await Member.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active',
|
||||
games: { [Op.gt]: 0 } // 최소 1게임 이상 플레이한 회원
|
||||
},
|
||||
order: [
|
||||
['average', 'DESC']
|
||||
],
|
||||
limit: 5,
|
||||
attributes: ['id', 'name', 'average', 'games', 'handicap', 'memberType']
|
||||
});
|
||||
|
||||
res.json(topMembers);
|
||||
} catch (error) {
|
||||
console.error('상위 회원 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '상위 회원 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 최근 모임 목록 조회
|
||||
exports.getRecentMeetings = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const recentMeetings = await Event.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active',
|
||||
startDate: { [Op.lt]: new Date() }
|
||||
},
|
||||
include: [{
|
||||
model: EventParticipant,
|
||||
as: 'participants',
|
||||
where: {
|
||||
status: { [Op.ne]: '취소' }
|
||||
},
|
||||
required: false
|
||||
}],
|
||||
order: [['startDate', 'DESC']],
|
||||
limit: 5
|
||||
});
|
||||
|
||||
const formattedMeetings = recentMeetings.map(meeting => ({
|
||||
date: meeting.startDate,
|
||||
title: meeting.title,
|
||||
participants: meeting.participants ? meeting.participants.length : 0,
|
||||
games: meeting.gameCount || 0
|
||||
}));
|
||||
|
||||
res.json(formattedMeetings);
|
||||
} catch (error) {
|
||||
console.error('최근 모임 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '최근 모임 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 사용자 클럽 목록 조회
|
||||
exports.getUserClubs = async (req, res) => {
|
||||
try {
|
||||
// 사용자가 속한 클럽 목록 조회 (Member 테이블 조인)
|
||||
// admin이면 모든 클럽을, 아니면 자신의 클럽만 조회
|
||||
const hasAdminAccess = isAdmin(req.user);
|
||||
|
||||
const queryOptions = {
|
||||
where: { status: 'active' }
|
||||
};
|
||||
|
||||
// admin이 아닌 경우에만 Member 조건 추가
|
||||
if (!hasAdminAccess) {
|
||||
queryOptions.include = [{
|
||||
model: Member,
|
||||
where: {
|
||||
userId: req.user.id
|
||||
},
|
||||
attributes: ['memberType']
|
||||
}];
|
||||
}
|
||||
|
||||
// order 옵션 추가
|
||||
queryOptions.order = [['createdAt', 'DESC']];
|
||||
const clubs = await Club.findAll(queryOptions);
|
||||
|
||||
// 응답 데이터 가공
|
||||
const formattedClubs = clubs.map(club => {
|
||||
const clubData = club.toJSON();
|
||||
let memberType = '';
|
||||
|
||||
// admin이 아닌 경우에만 Members가 있음
|
||||
if (!hasAdminAccess && clubData.Members && clubData.Members.length > 0) {
|
||||
memberType = clubData.Members[0].memberType;
|
||||
} else if (hasAdminAccess) {
|
||||
memberType = 'admin';
|
||||
}
|
||||
|
||||
return {
|
||||
id: clubData.id,
|
||||
name: clubData.name,
|
||||
location: clubData.location,
|
||||
memberType
|
||||
};
|
||||
});
|
||||
|
||||
res.json(formattedClubs);
|
||||
} catch (error) {
|
||||
console.error('클럽 목록 조회 오류:', error);
|
||||
res.status(500).json({ message: '클럽 목록을 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
|
||||
// 클럽 통계 조회
|
||||
exports.getStatistics = async (req, res) => {
|
||||
try {
|
||||
const { clubId, days } = req.body;
|
||||
|
||||
if (!clubId) {
|
||||
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
|
||||
}
|
||||
|
||||
const startDate = new Date();
|
||||
startDate.setDate(startDate.getDate() - (days || 90)); // 기본값: 90일
|
||||
|
||||
// 1. 회원 통계
|
||||
const members = await Member.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 이벤트 및 참가자 통계
|
||||
const events = await Event.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
startDate: { [Op.gte]: startDate },
|
||||
status: 'active'
|
||||
},
|
||||
include: [{
|
||||
model: EventParticipant,
|
||||
as: 'participants',
|
||||
include: [{
|
||||
model: EventScore,
|
||||
as: 'scores'
|
||||
}]
|
||||
}]
|
||||
});
|
||||
|
||||
// 3. 점수 데이터 수집
|
||||
let allScores = [];
|
||||
let totalParticipants = 0;
|
||||
let totalGames = 0;
|
||||
|
||||
events.forEach(event => {
|
||||
if (event.participants) {
|
||||
totalParticipants += event.participants.length;
|
||||
event.participants.forEach(participant => {
|
||||
if (participant.scores) {
|
||||
allScores = allScores.concat(participant.scores.map(score => score.score));
|
||||
totalGames += participant.scores.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 점수 분포 계산
|
||||
const scoreDistribution = Array(10).fill(0);
|
||||
allScores.forEach(score => {
|
||||
const index = Math.floor(score / 30);
|
||||
if (index >= 0 && index < 10) {
|
||||
scoreDistribution[index]++;
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 참석률 추이 계산
|
||||
const attendanceTrend = events.map(event => ({
|
||||
date: event.startDate,
|
||||
attendance: Math.round((event.participants ? event.participants.length : 0) / members.length * 100)
|
||||
}));
|
||||
|
||||
// 6. 회원 순위 계산
|
||||
const memberRankings = await Member.findAll({
|
||||
where: {
|
||||
clubId,
|
||||
status: 'active',
|
||||
games: { [Op.gt]: 0 }
|
||||
},
|
||||
order: [['average', 'DESC']],
|
||||
limit: 10,
|
||||
attributes: ['id', 'name', 'average', 'games', 'handicap', 'memberType']
|
||||
});
|
||||
|
||||
// 각 회원의 참석률 계산
|
||||
const memberAttendance = {};
|
||||
events.forEach(event => {
|
||||
event.participants.forEach(participant => {
|
||||
memberAttendance[participant.memberId] = memberAttendance[participant.memberId] || { attended: 0, total: 0 };
|
||||
memberAttendance[participant.memberId].attended++;
|
||||
memberAttendance[participant.memberId].total = events.length;
|
||||
});
|
||||
});
|
||||
|
||||
const rankingsWithAttendance = memberRankings.map(member => ({
|
||||
...member.toJSON(),
|
||||
attendance: memberAttendance[member.id]
|
||||
? Math.round(memberAttendance[member.id].attended / memberAttendance[member.id].total * 100)
|
||||
: 0
|
||||
}));
|
||||
|
||||
// 7. 모임별 통계
|
||||
const meetingStats = events.map(event => ({
|
||||
date: event.startDate,
|
||||
title: event.title,
|
||||
participants: event.participants ? event.participants.length : 0,
|
||||
games: event.gameCount || 0,
|
||||
averageScore: event.participants && event.participants.length > 0
|
||||
? Math.round(event.participants.reduce((sum, p) =>
|
||||
sum + (p.scores ? p.scores.reduce((s, score) => s + score.score, 0) / p.scores.length : 0),
|
||||
0) / event.participants.length)
|
||||
: 0
|
||||
}));
|
||||
|
||||
// 8. 응답 데이터 구성
|
||||
const response = {
|
||||
summary: {
|
||||
totalMembers: members.length,
|
||||
activeMembers: members.filter(m => m.games > 0).length,
|
||||
averageAttendance: events.length > 0
|
||||
? Math.round(totalParticipants / (events.length * members.length) * 100)
|
||||
: 0,
|
||||
highestGame: allScores.length > 0 ? Math.max(...allScores) : 0,
|
||||
averageScore: allScores.length > 0
|
||||
? Math.round(allScores.reduce((a, b) => a + b, 0) / allScores.length)
|
||||
: 0,
|
||||
totalGames: totalGames,
|
||||
totalMeetings: events.length,
|
||||
averageParticipants: events.length > 0
|
||||
? Math.round(totalParticipants / events.length)
|
||||
: 0,
|
||||
averageGamesPerMeeting: events.length > 0
|
||||
? Math.round(totalGames / events.length)
|
||||
: 0,
|
||||
averageHandicap: members.length > 0
|
||||
? Math.round(members.reduce((sum, m) => sum + m.handicap, 0) / members.length)
|
||||
: 0,
|
||||
highestHandicap: members.length > 0
|
||||
? Math.max(...members.map(m => m.handicap))
|
||||
: 0,
|
||||
lowestHandicap: members.length > 0
|
||||
? Math.min(...members.map(m => m.handicap))
|
||||
: 0
|
||||
},
|
||||
scoreDistribution: scoreDistribution.map((count, index) => ({
|
||||
range: `${index * 30}-${(index + 1) * 30 - 1}`,
|
||||
count
|
||||
})),
|
||||
attendanceTrend,
|
||||
memberRankings: rankingsWithAttendance,
|
||||
meetingStats
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('통계 데이터 조회 오류:', error);
|
||||
res.status(500).json({ message: '통계 데이터를 가져오는 중 오류가 발생했습니다.' });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user