This commit is contained in:
2025-04-17 23:32:39 +09:00
commit 5b719b6766
105 changed files with 25653 additions and 0 deletions
+221
View File
@@ -0,0 +1,221 @@
/**
* 인증 컨트롤러
* 사용자 인증 관련 기능을 처리하는 컨트롤러
*/
const jwt = require('jsonwebtoken');
const { User, Member } = require('../models');
const { Op } = require('sequelize');
require('dotenv').config();
// 로그인
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// 이메일 필수 확인
if (!email) {
return res.status(400).json({ message: '이메일은 필수 항목입니다.' });
}
// 사용자 조회
const user = await User.findOne({ where: { email } });
// 사용자가 존재하지 않는 경우
if (!user) {
return res.status(401).json({ message: '이메일 또는 비밀번호가 올바르지 않습니다.' });
}
// 비밀번호 검증
const isPasswordValid = await user.validatePassword(password);
if (!isPasswordValid) {
return res.status(401).json({ message: '이메일 또는 비밀번호가 올바르지 않습니다.' });
}
// 마지막 로그인 시간 업데이트
await user.update({ lastLogin: new Date() });
// JWT 토큰 생성
const token = jwt.sign(
{
id: user.id,
email: user.email,
role: user.role,
name: user.name
},
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: process.env.JWT_EXPIRES_IN || '1d' }
);
// 모임장/운영진으로 참여 중인 클럽 ID 조회
const clubMembers = await Member.findAll({
where: {
userId: user.id,
memberType: {
[Op.in]: ['모임장', '운영진']
}
},
attributes: ['clubId']
});
// console.log(`clubMembers: ${JSON.stringify(clubMembers)}`);
// 모임장/운영진 클럽 ID 배열 생성
const clubManagerIds = clubMembers.map(cm => cm.clubId);
let clubId = null;
if(clubManagerIds.length > 0) {
clubId = clubManagerIds[0];
}
// 사용자 정보에서 비밀번호 제외
const userData = {
id: user.id,
name: user.name,
email: user.email,
role: user.role,
status: user.status,
clubId: clubId
};
// console.log(`userData: ${JSON.stringify(userData)}`);
res.json({
token,
user: userData
});
} catch (error) {
console.error('로그인 오류:', error);
res.status(500).json({ message: '로그인 중 오류가 발생했습니다.' });
}
};
// 회원가입
exports.register = async (req, res) => {
try {
const { name, email, password, role, status } = req.body;
// 필수 필드 검증
if (!name || !email || !password) {
return res.status(400).json({ message: '이름, 이메일, 비밀번호는 필수 항목입니다.' });
}
// 이메일 중복 확인
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(400).json({ message: '이미 등록된 이메일입니다.' });
}
// 사용자 생성
const newUser = await User.create({
name,
email,
password,
username: email,
role: role || 'user',
status: status || 'active'
});
// 민감한 정보 제외
const userResponse = {
id: newUser.id,
name: newUser.name,
email: newUser.email,
role: newUser.role,
status: newUser.status,
createdAt: newUser.createdAt
};
res.status(201).json(userResponse);
} catch (error) {
console.error('회원가입 오류:', error);
res.status(500).json({ message: '회원가입 중 오류가 발생했습니다.' });
}
};
// 현재 사용자 정보 조회
exports.getCurrentUser = async (req, res) => {
try {
const userId = req.user.id;
const user = await User.findByPk(userId, {
attributes: { exclude: ['password'] }
});
if (!user) {
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
}
res.json(user);
} catch (error) {
console.error('사용자 정보 조회 오류:', error);
res.status(500).json({ message: '사용자 정보를 조회하는 중 오류가 발생했습니다.' });
}
};
// 프로필 업데이트
exports.updateProfile = async (req, res) => {
try {
const userId = req.user.id;
const { name, email, password, currentPassword } = req.body;
// 사용자 조회
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
}
// 비밀번호 변경이 요청된 경우
if (password && currentPassword) {
// 현재 비밀번호 검증
const isPasswordValid = await user.validatePassword(currentPassword);
if (!isPasswordValid) {
return res.status(401).json({ message: '현재 비밀번호가 올바르지 않습니다.' });
}
}
// 이메일이 변경되는 경우
if (email && email !== user.email) {
// Users 테이블에서 이메일 중복 확인
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(400).json({ message: '이미 사용 중인 이메일입니다.' });
}
// Members 테이블에서 이메일 중복 확인
const existingMember = await Member.findOne({ where: { email } });
if (existingMember) {
return res.status(400).json({ message: '이미 클럽 회원이 사용 중인 이메일입니다.' });
}
// 해당 사용자의 Members 테이블 레코드도 함께 업데이트
await Member.update(
{ email },
{ where: { userId } }
);
}
// 프로필 업데이트
const updateData = {
name: name || user.name,
email: email || user.email,
username: email || user.email // username도 이메일과 동일하게 업데이트
};
// 비밀번호가 제공된 경우에만 업데이트
if (password && currentPassword) {
updateData.password = password;
}
await user.update(updateData);
// 업데이트된 사용자 정보 반환 (비밀번호 제외)
const updatedUser = await User.findByPk(userId, {
attributes: { exclude: ['password'] }
});
res.json(updatedUser);
} catch (error) {
console.error('프로필 업데이트 오류:', error);
res.status(500).json({ message: '프로필 업데이트 중 오류가 발생했습니다.' });
}
};
+925
View File
@@ -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: '통계 데이터를 가져오는 중 오류가 발생했습니다.' });
}
};
File diff suppressed because it is too large Load Diff
+203
View File
@@ -0,0 +1,203 @@
const { Feature, ClubFeature, Club } = require('../models');
const { Op } = require('sequelize');
// 전체 기능 목록 조회
exports.getFeatures = async (req, res) => {
try {
const features = await Feature.findAll({
where: {
status: 'active'
},
attributes: ['id', 'name', 'description', 'price']
});
res.json(features);
} catch (error) {
console.error('기능 목록 조회 오류:', error);
res.status(500).json({ message: '기능 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
// 사용 가능한 기능 목록 조회
exports.getAvailableFeatures = async (req, res) => {
try {
const { clubId } = req.query;
if (!clubId) {
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
}
// 모든 기능 목록 조회
const features = await Feature.findAll({
where: {
status: 'active'
},
attributes: ['id', 'name', 'description', 'price']
});
// 클럽의 활성화된 기능 조회
const activeFeatures = await ClubFeature.findAll({
where: {
clubId,
expiryDate: {
[Op.gt]: new Date()
}
},
attributes: ['featureId']
});
const activeFeatureIds = activeFeatures.map(af => af.featureId);
// 기능 목록에 활성화 상태 추가
const formattedFeatures = features.map(feature => ({
...feature.toJSON(),
isActive: activeFeatureIds.includes(feature.id)
}));
res.json(formattedFeatures);
} catch (error) {
console.error('사용 가능한 기능 목록 조회 오류:', error);
res.status(500).json({ message: '기능 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
// 활성화된 기능 목록 조회
exports.getActiveFeatures = async (req, res) => {
try {
const { clubId } = req.query;
if (!clubId) {
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
}
const activeFeatures = await ClubFeature.findAll({
where: {
clubId,
expiryDate: {
[Op.gt]: new Date()
}
},
include: [{
model: Feature,
as: 'feature',
attributes: ['name', 'description']
}],
attributes: ['id', 'featureId', 'purchaseDate', 'expiryDate']
});
res.json(activeFeatures);
} catch (error) {
console.error('활성화된 기능 목록 조회 오류:', error);
res.status(500).json({ message: '활성화된 기능 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
// 기능 구매 내역 조회
exports.getFeaturePayments = async (req, res) => {
try {
const { clubId } = req.query;
if (!clubId) {
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
}
const payments = await ClubFeature.findAll({
where: {
clubId
},
include: [{
model: Feature,
as: 'feature',
attributes: ['name']
}],
attributes: [
'id',
'featureId',
'purchaseDate',
'expiryDate',
'amount',
'duration',
'status'
],
order: [['purchaseDate', 'DESC']]
});
const formattedPayments = payments.map(payment => ({
id: payment.id,
featureName: payment.feature.name,
purchaseDate: payment.purchaseDate,
expiryDate: payment.expiryDate,
amount: payment.amount,
duration: payment.duration,
status: payment.status
}));
res.json(formattedPayments);
} catch (error) {
console.error('기능 구매 내역 조회 오류:', error);
res.status(500).json({ message: '구매 내역을 가져오는 중 오류가 발생했습니다.' });
}
};
// 기능 구매
exports.purchaseFeature = async (req, res) => {
try {
const { clubId, featureId, duration } = req.body;
if (!clubId) {
return res.status(400).json({ message: '클럽 ID가 필요합니다.' });
}
// 기능 정보 조회
const feature = await Feature.findByPk(featureId);
if (!feature) {
return res.status(404).json({ message: '존재하지 않는 기능입니다.' });
}
// 클럽 정보 조회
const club = await Club.findByPk(clubId);
if (!club) {
return res.status(404).json({ message: '존재하지 않는 클럽입니다.' });
}
// 이미 활성화된 기능인지 확인
const existingFeature = await ClubFeature.findOne({
where: {
clubId,
featureId,
expiryDate: {
[Op.gt]: new Date()
}
}
});
if (existingFeature) {
return res.status(400).json({ message: '이미 활성화된 기능입니다.' });
}
// 구매 금액 계산 (duration에 따른 할인 적용 가능)
const amount = feature.price * duration;
// 기능 구매 기록 생성
const purchaseDate = new Date();
const expiryDate = new Date();
expiryDate.setMonth(expiryDate.getMonth() + duration);
const clubFeature = await ClubFeature.create({
clubId,
featureId,
purchaseDate,
expiryDate,
amount,
duration,
status: 'active'
});
res.json({
message: '기능 구매가 완료되었습니다.',
purchaseInfo: clubFeature
});
} catch (error) {
console.error('기능 구매 오류:', error);
res.status(500).json({ message: '기능 구매 중 오류가 발생했습니다.' });
}
};
+166
View File
@@ -0,0 +1,166 @@
const { Menu, Feature, ClubFeature, ClubSubscription, SubscriptionPlan, Member } = require('../models');
const { Op } = require('sequelize');
/**
* 사용자의 접근 가능한 메뉴 목록을 반환합니다.
*/
exports.getUserMenus = async (req, res) => {
try {
const { clubId, role = 'user' } = req.body;
const userId = req.user.id; // JWT 토큰에서 사용자 ID 가져오기
if (!clubId) {
return res.status(400).json({
success: false,
message: '클럽 ID가 필요합니다.'
});
}
// 멤버 정보 조회
const member = await Member.findOne({
where: {
clubId,
userId
}
});
const menus = await getAccessibleMenus(clubId, role, member);
res.json({
success: true,
data: menus
});
} catch (error) {
console.error('메뉴 조회 중 오류 발생:', error);
res.status(500).json({
success: false,
message: '메뉴 조회 중 오류가 발생했습니다.'
});
}
}
/**
* 사용자 권한과 클럽의 구독/기능 상태에 따라 접근 가능한 메뉴 목록을 조회합니다.
*/
async function getAccessibleMenus(clubId, userRole, member) {
try {
// 1. 클럽의 현재 구독 정보 조회
const clubSubscription = await ClubSubscription.findOne({
where: {
clubId,
status: 'active',
endDate: {
[Op.gt]: new Date()
}
},
include: [{
model: SubscriptionPlan,
attributes: ['name']
}]
});
// 2. 클럽의 활성화된 기능 목록 조회
const activeFeatures = await ClubFeature.findAll({
where: {
clubId,
enabled: true,
[Op.or]: [
{ expiryDate: null },
{ expiryDate: { [Op.gt]: new Date() } }
]
},
include: [{
model: Feature,
as: 'feature',
attributes: ['code']
}]
});
const activeFeatureCodes = activeFeatures.map(f => f.feature.code);
const subscriptionTier = clubSubscription ? clubSubscription.SubscriptionPlan.name.toLowerCase() : 'basic';
// 3. 메뉴 조회 및 필터링
const allMenus = await Menu.findAll({
where: {
visible: true,
[Op.and]: [
// 역할 기반 필터링
{
[Op.or]: [
{ role: userRole },
{ role: 'user' }
]
},
// 구독 등급 기반 필터링
{
[Op.or]: [
{ requiredSubscriptionTier: null },
{ requiredSubscriptionTier: subscriptionTier }
]
}
]
},
include: [{
model: Menu,
as: 'children',
required: false
}],
order: [
['order', 'ASC'],
[{ model: Menu, as: 'children' }, 'order', 'ASC']
]
});
// 4. 기능 코드 기반 필터링 및 메뉴 구조화
const processMenu = (menu) => {
// 기능 코드가 있는 경우 접근 권한 확인
if (menu.featureCode && !activeFeatureCodes.includes(menu.featureCode)) {
return null;
}
const processedMenu = {
id: menu.id,
title: menu.title,
icon: menu.icon,
to: menu.to,
order: menu.order
};
// 하위 메뉴 처리
if (menu.children && menu.children.length > 0) {
const children = menu.children
.map(processMenu)
.filter(child => child !== null);
if (children.length > 0) {
processedMenu.children = children;
}
}
return processedMenu;
};
// 최상위 메뉴만 필터링 (parentId가 null인 항목)
const rootMenus = allMenus
.filter(menu => !menu.parentId)
.map(processMenu)
.filter(menu => menu !== null);
// 구독 관리 메뉴 추가 (모임장, 운영진만 접근 가능)
if (member && (member.memberType === '모임장' || member.memberType === '운영진')) {
rootMenus.push({
id: 'subscription',
title: '구독/결제 관리',
icon: 'pi pi-credit-card',
to: '/club/subscription',
order: 90,
children: []
});
}
return rootMenus;
} catch (error) {
console.error('메뉴 조회 중 오류 발생:', error);
throw error;
}
}
@@ -0,0 +1,217 @@
/**
* 알림 컨트롤러
*
* 볼링 클럽 매니저에서 지원하는 알림 유형:
* 1. 시스템 알림
* - 시스템 업데이트, 유지보수 공지 등
* - 아이콘: pi pi-info-circle
* - 타입: info
*
* 2. 클럽 관련 알림
* - 새 회원 가입, 클럽 정보 변경 등
* - 아이콘: pi pi-users
* - 타입: info 또는 success
*
* 3. 모임/대회 알림
* - 모임 생성, 대회 일정, 참가 신청 등
* - 아이콘: pi pi-calendar 또는 pi pi-flag
* - 타입: info 또는 warning
*
* 4. 점수 관련 알림
* - 새 점수 등록, 평균 점수 갱신 등
* - 아이콘: pi pi-chart-bar
* - 타입: success
*
* 5. 결제/구독 알림
* - 결제 완료, 구독 만료 예정 등
* - 아이콘: pi pi-credit-card 또는 pi pi-clock
* - 타입: success 또는 warning
*
* 6. 오류/경고 알림
* - 시스템 오류, 보안 경고 등
* - 아이콘: pi pi-exclamation-triangle
* - 타입: error 또는 warning
*
* 알림 생성 방법:
* 1. API 엔드포인트를 통한 생성: POST /api/notifications
* 2. 내부 함수를 통한 생성: createSystemNotification()
*
* 알림 필드:
* - user: 알림을 받을 사용자 ID (필수)
* - title: 알림 제목 (필수)
* - message: 알림 내용 (필수)
* - icon: 알림 아이콘 (PrimeIcons 사용)
* - type: 알림 타입 (info, success, warning, error)
* - link: 알림 클릭 시 이동할 링크 (옵션)
* - read: 읽음 상태 (기본값: false)
*/
const User = require('../models/User');
const Notification = require('../models/Notification');
const { validationResult } = require('express-validator');
// 사용자의 알림 목록 조회
exports.getUserNotifications = async (req, res) => {
try {
const userId = req.user.id;
// 사용자의 알림 목록 조회 (최신순으로 정렬)
const notifications = await Notification.findAll({
where: { userId },
order: [['createdAt', 'DESC']],
limit: 10
});
res.status(200).json(notifications);
} catch (error) {
console.error('알림 조회 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
};
// 알림 읽음 표시
exports.markAsRead = async (req, res) => {
try {
const { notificationId } = req.params;
const userId = req.user.id;
const notification = await Notification.findOne({
where: {
id: notificationId,
userId
}
});
if (!notification) {
return res.status(404).json({ message: '알림을 찾을 수 없습니다.' });
}
notification.read = true;
await notification.save();
res.status(200).json({ message: '알림이 읽음 처리되었습니다.' });
} catch (error) {
console.error('알림 읽음 표시 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
};
// 모든 알림 읽음 표시
exports.markAllAsRead = async (req, res) => {
try {
const userId = req.user.id;
await Notification.update(
{ read: true },
{ where: { userId, read: false } }
);
res.status(200).json({ message: '모든 알림이 읽음 처리되었습니다.' });
} catch (error) {
console.error('모든 알림 읽음 표시 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
};
// 알림 생성 (관리자용)
exports.createNotification = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { userId, title, message, icon, type, link } = req.body;
const newNotification = await Notification.create({
userId,
title,
message,
icon: icon || 'pi pi-info-circle',
type: type || 'info',
link,
read: false
});
// Socket.IO를 통해 실시간 알림 전송
const io = req.app.get('io');
if (io) {
io.to(`user:${userId}`).emit('notification', {
id: newNotification.id,
title: newNotification.title,
message: newNotification.message,
icon: newNotification.icon,
type: newNotification.type,
link: newNotification.link,
createdAt: newNotification.createdAt
});
}
res.status(201).json(newNotification);
} catch (error) {
console.error('알림 생성 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
};
// 알림 삭제
exports.deleteNotification = async (req, res) => {
try {
const { notificationId } = req.params;
const userId = req.user.id;
const notification = await Notification.findOne({
where: {
id: notificationId,
userId
}
});
if (!notification) {
return res.status(404).json({ message: '알림을 찾을 수 없습니다.' });
}
await notification.destroy();
res.status(200).json({ message: '알림이 삭제되었습니다.' });
} catch (error) {
console.error('알림 삭제 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
};
// 시스템 알림 생성 (내부 함수)
exports.createSystemNotification = async (userId, title, message, options = {}) => {
try {
const { icon, type, link } = options;
const newNotification = await Notification.create({
userId,
title,
message,
icon: icon || 'pi pi-info-circle',
type: type || 'info',
link,
read: false
});
// Socket.IO를 통해 실시간 알림 전송
const io = global.io;
if (io) {
io.to(`user:${userId}`).emit('notification', {
id: newNotification.id,
title: newNotification.title,
message: newNotification.message,
icon: newNotification.icon,
type: newNotification.type,
link: newNotification.link,
createdAt: newNotification.createdAt
});
}
return newNotification;
} catch (error) {
console.error('시스템 알림 생성 오류:', error);
return null;
}
};
@@ -0,0 +1,237 @@
const { SubscriptionPlan, SubscriptionPlanFeature, ClubSubscription, Feature, Club, User } = require('../models');
// 전체 구독 목록 조회
exports.getClubSubscriptions = async (req, res) => {
try {
const subscriptions = await ClubSubscription.findAll({
include: [
{
model: SubscriptionPlan,
include: [{
model: Feature,
through: { attributes: ['status'] }
}]
},
{
model: Club,
include: [{
model: User,
as: 'owner',
attributes: ['id', 'name', 'email']
}]
}
]
});
res.json(subscriptions);
} catch (error) {
console.error('Error in getClubSubscriptions:', error);
res.status(500).json({ message: '구독 목록 조회 중 오류가 발생했습니다.' });
}
};
// 구독 플랜 목록 조회
exports.getSubscriptionPlans = async (req, res) => {
try {
const plans = await SubscriptionPlan.findAll({
include: [{
model: Feature,
through: { attributes: ['status'] }
}]
});
res.json(plans);
} catch (error) {
console.error('Error in getSubscriptionPlans:', error);
res.status(500).json({ message: '구독 플랜 목록 조회 중 오류가 발생했습니다.' });
}
};
// 구독 플랜 생성
exports.createSubscriptionPlan = async (req, res) => {
try {
const { name, description, maxMembers, price, features } = req.body;
const plan = await SubscriptionPlan.create({
name,
description,
maxMembers,
price
});
if (features && features.length > 0) {
const featureAssociations = features.map(featureId => ({
planId: plan.id,
featureId,
status: 'active'
}));
await SubscriptionPlanFeature.bulkCreate(featureAssociations);
}
const createdPlan = await SubscriptionPlan.findByPk(plan.id, {
include: [{
model: Feature,
through: { attributes: ['status'] }
}]
});
res.status(201).json(createdPlan);
} catch (error) {
console.error('Error in createSubscriptionPlan:', error);
res.status(500).json({ message: '구독 플랜 생성 중 오류가 발생했습니다.' });
}
};
// 구독 플랜 수정
exports.updateSubscriptionPlan = async (req, res) => {
try {
const { id } = req.params;
const { name, description, maxMembers, price, features } = req.body;
const plan = await SubscriptionPlan.findByPk(id);
if (!plan) {
return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' });
}
await plan.update({
name,
description,
maxMembers,
price
});
if (features) {
// 기존 기능 연결 삭제
await SubscriptionPlanFeature.destroy({
where: { planId: id }
});
// 새로운 기능 연결 생성
if (features.length > 0) {
const featureAssociations = features.map(featureId => ({
planId: id,
featureId,
status: 'active'
}));
await SubscriptionPlanFeature.bulkCreate(featureAssociations);
}
}
const updatedPlan = await SubscriptionPlan.findByPk(id, {
include: [{
model: Feature,
through: { attributes: ['status'] }
}]
});
res.json(updatedPlan);
} catch (error) {
console.error('Error in updateSubscriptionPlan:', error);
res.status(500).json({ message: '구독 플랜 수정 중 오류가 발생했습니다.' });
}
};
// 구독 플랜 삭제
exports.deleteSubscriptionPlan = async (req, res) => {
try {
const { id } = req.params;
// 플랜이 존재하는지 확인
const plan = await SubscriptionPlan.findByPk(id);
if (!plan) {
return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' });
}
// 플랜과 연결된 기능 관계 삭제
await SubscriptionPlanFeature.destroy({
where: { planId: id }
});
// 플랜 삭제
await plan.destroy();
res.json({ message: '구독 플랜이 삭제되었습니다.' });
} catch (error) {
console.error('Error in deleteSubscriptionPlan:', error);
res.status(500).json({ message: '구독 플랜 삭제 중 오류가 발생했습니다.' });
}
};
// 클럽 구독 생성/수정
exports.subscribeClub = async (req, res) => {
try {
const { clubId, planId, startDate, expiryDate } = req.body;
// 기존 구독 확인
let subscription = await ClubSubscription.findOne({
where: { clubId, status: 'active' }
});
if (subscription) {
// 기존 구독 만료 처리
await subscription.update({
status: 'expired',
updatedAt: new Date()
});
}
// 새로운 구독 생성
subscription = await ClubSubscription.create({
clubId,
planId,
startDate: new Date(startDate),
expiryDate: new Date(expiryDate),
status: 'active'
});
res.status(201).json(subscription);
} catch (error) {
console.error('Error in subscribeClub:', error);
res.status(500).json({ message: '클럽 구독 처리 중 오류가 발생했습니다.' });
}
};
// 클럽의 현재 구독 정보 조회
exports.getClubSubscription = async (req, res) => {
try {
const { clubId } = req.params;
const subscription = await ClubSubscription.findOne({
where: { clubId, status: 'active' },
include: [{
model: SubscriptionPlan,
include: [{
model: Feature,
through: { attributes: ['status'] }
}]
}]
});
if (!subscription) {
return res.status(404).json({ message: '활성화된 구독 정보가 없습니다.' });
}
res.json(subscription);
} catch (error) {
console.error('Error in getClubSubscription:', error);
res.status(500).json({ message: '구독 정보 조회 중 오류가 발생했습니다.' });
}
};
// 플랜별 구독 목록 조회
exports.getPlanSubscriptions = async (req, res) => {
try {
const { planId } = req.params;
const subscriptions = await ClubSubscription.findAll({
where: { planId },
include: [{
model: Club,
attributes: ['id', 'name']
}]
});
res.json(subscriptions);
} catch (error) {
console.error('Error in getPlanSubscriptions:', error);
res.status(500).json({ message: '플랜별 구독 목록 조회 중 오류가 발생했습니다.' });
}
};
+91
View File
@@ -0,0 +1,91 @@
const User = require('../models/User');
/**
* 전체 사용자 목록 조회
*/
const getAllUsers = async (req, res) => {
try {
const users = await User.findAll({
attributes: {
exclude: ['password']
},
order: [['createdAt', 'DESC']]
});
res.json(users);
} catch (error) {
console.error('사용자 목록 조회 중 오류 발생:', error);
res.status(500).json({ message: '사용자 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
/**
* 단일 사용자 정보 조회
*/
const getUser = async (req, res) => {
try {
const userId = req.params.id;
const user = await User.findByPk(userId, {
attributes: { exclude: ['password'] }
});
if (!user) {
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
}
res.json(user);
} catch (error) {
console.error('사용자 정보 조회 중 오류 발생:', error);
res.status(500).json({ message: '사용자 정보를 가져오는 중 오류가 발생했습니다.' });
}
};
/**
* 사용자 정보 수정
*/
const updateUser = async (req, res) => {
try {
const userId = req.params.id;
const { name, email, role, status } = req.body;
const user = await User.findByPk(userId);
if (!user) {
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
}
// 이메일 중복 확인 (이메일이 변경된 경우에만)
if (email !== user.email) {
const existingUser = await User.findOne({ where: { email } });
if (existingUser) {
return res.status(400).json({ message: '이미 사용 중인 이메일입니다.' });
}
}
// 필수 필드 확인
if (!name || !email || !role || !status) {
return res.status(400).json({ message: '필수 정보가 누락되었습니다.' });
}
await user.update({
name,
email,
role,
status,
updatedAt: new Date()
});
const updatedUser = await User.findByPk(userId, {
attributes: { exclude: ['password'] }
});
res.json(updatedUser);
} catch (error) {
console.error('사용자 정보 수정 중 오류 발생:', error);
res.status(500).json({ message: '사용자 정보를 수정하는 중 오류가 발생했습니다.' });
}
};
module.exports = {
getAllUsers,
getUser,
updateUser
};