1444 lines
47 KiB
JavaScript
1444 lines
47 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { Club, Member, User, Feature, ClubFeature, ActivityLog, SubscriptionPlan, Meeting, MeetingParticipant, ClubSubscription } = require('../models');
|
|
const { sequelize } = require('../models/index');
|
|
const { Op } = require('sequelize');
|
|
const { authenticateJWT, authorizeRoles } = require('../middleware/authMiddleware');
|
|
|
|
// 관리자 인증 미들웨어
|
|
const adminMiddleware = (req, res, next) => {
|
|
if (req.user && (req.user.role === 'admin' || req.user.role === 'superadmin')) {
|
|
next();
|
|
} else {
|
|
res.status(403).json({ message: '관리자 권한이 필요합니다.' });
|
|
}
|
|
};
|
|
|
|
// 활동 로그 기록 함수
|
|
const logActivity = async (userId, type, description) => {
|
|
try {
|
|
await ActivityLog.create({
|
|
userId,
|
|
type,
|
|
description
|
|
});
|
|
} catch (error) {
|
|
console.error('활동 로그 기록 중 오류:', error);
|
|
}
|
|
};
|
|
|
|
// 모든 관리자 라우트에 인증과 admin 권한 검사 미들웨어 적용
|
|
router.use(authenticateJWT);
|
|
router.use(authorizeRoles('admin', 'superadmin'));
|
|
|
|
// 대시보드 데이터 조회
|
|
router.get('/dashboard', async (req, res) => {
|
|
try {
|
|
const [
|
|
totalUsers,
|
|
totalClubs,
|
|
activeSubscriptions,
|
|
recentUsers,
|
|
recentClubs,
|
|
recentSubscriptions
|
|
] = await Promise.all([
|
|
// 전체 사용자 수
|
|
User.count(),
|
|
|
|
// 전체 클럽 수
|
|
Club.count(),
|
|
|
|
// 활성 구독 수
|
|
ClubSubscription.count({
|
|
where: {
|
|
status: 'active'
|
|
}
|
|
}),
|
|
|
|
// 최근 가입한 사용자
|
|
User.findAll({
|
|
limit: 5,
|
|
order: [['createdAt', 'DESC']],
|
|
attributes: ['id', 'username', 'name', 'email', 'role', 'createdAt']
|
|
}),
|
|
|
|
// 최근 생성된 클럽
|
|
Club.findAll({
|
|
limit: 5,
|
|
order: [['createdAt', 'DESC']],
|
|
attributes: ['id', 'name', 'memberCount', 'createdAt'],
|
|
include: [{
|
|
model: User,
|
|
as: 'owner',
|
|
attributes: ['id', 'name']
|
|
}]
|
|
}),
|
|
|
|
// 최근 구독 내역
|
|
ClubSubscription.findAll({
|
|
limit: 5,
|
|
order: [['createdAt', 'DESC']],
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
},
|
|
{
|
|
model: SubscriptionPlan,
|
|
attributes: ['id', 'name', 'price']
|
|
}
|
|
]
|
|
})
|
|
]);
|
|
|
|
// 월별 통계 데이터 (최근 6개월)
|
|
const sixMonthsAgo = new Date();
|
|
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5);
|
|
|
|
const [userStats, clubStats, subscriptionStats] = await Promise.all([
|
|
// 월별 사용자 가입 통계
|
|
User.findAll({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: sixMonthsAgo
|
|
}
|
|
},
|
|
attributes: [
|
|
[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'],
|
|
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
|
],
|
|
group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')],
|
|
order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']]
|
|
}),
|
|
|
|
// 월별 클럽 생성 통계
|
|
Club.findAll({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: sixMonthsAgo
|
|
}
|
|
},
|
|
attributes: [
|
|
[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'],
|
|
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
|
],
|
|
group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')],
|
|
order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']]
|
|
}),
|
|
|
|
// 월별 구독 통계
|
|
ClubSubscription.findAll({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: sixMonthsAgo
|
|
}
|
|
},
|
|
attributes: [
|
|
[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'month'],
|
|
[sequelize.fn('COUNT', sequelize.col('id')), 'count']
|
|
],
|
|
group: [sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m')],
|
|
order: [[sequelize.fn('DATE_FORMAT', sequelize.col('createdAt'), '%Y-%m'), 'ASC']]
|
|
})
|
|
]);
|
|
|
|
res.json({
|
|
summary: {
|
|
totalUsers,
|
|
totalClubs,
|
|
activeSubscriptions
|
|
},
|
|
recent: {
|
|
users: recentUsers,
|
|
clubs: recentClubs,
|
|
subscriptions: recentSubscriptions.map(sub => ({
|
|
...sub.toJSON(),
|
|
expiryDate: sub.endDate
|
|
}))
|
|
},
|
|
stats: {
|
|
users: userStats,
|
|
clubs: clubStats,
|
|
subscriptions: subscriptionStats
|
|
}
|
|
});
|
|
|
|
await logActivity(req.user.id, 'VIEW_DASHBOARD', '관리자 대시보드를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('대시보드 데이터 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 통계 정보 가져오기
|
|
router.get('/clubs/stats', async (req, res) => {
|
|
try {
|
|
// 전체 클럽 수 조회
|
|
const totalCount = await Club.count();
|
|
|
|
// 활성 클럽 수 조회 (status가 'active'인 클럽)
|
|
const activeCount = await Club.count({
|
|
where: { status: 'active' }
|
|
});
|
|
|
|
// 구독 상태별 클럽 수 조회
|
|
const subscriptionStats = await ClubSubscription.findAll({
|
|
attributes: ['status', [sequelize.fn('COUNT', sequelize.col('id')), 'count']],
|
|
group: ['status']
|
|
});
|
|
|
|
// 최근 생성된 클럽 5개 조회
|
|
const recentClubs = await Club.findAll({
|
|
attributes: ['id', 'name', 'memberCount', 'createdAt', 'status'],
|
|
order: [['createdAt', 'DESC']],
|
|
limit: 5,
|
|
include: [{
|
|
model: User,
|
|
as: 'owner',
|
|
attributes: ['name', 'email']
|
|
}]
|
|
});
|
|
|
|
const subscriptions = {
|
|
active: 0,
|
|
expired: 0,
|
|
suspended: 0
|
|
};
|
|
|
|
subscriptionStats.forEach(stat => {
|
|
subscriptions[stat.status] = parseInt(stat.getDataValue('count'));
|
|
});
|
|
|
|
res.json({
|
|
totalCount,
|
|
activeCount,
|
|
subscriptions,
|
|
recentClubs: recentClubs.map(club => ({
|
|
...club.toJSON(),
|
|
isActive: club.status === 'active'
|
|
}))
|
|
});
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'VIEW_STATS', '클럽 통계 정보를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('클럽 통계 정보 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 통계 가져오기
|
|
router.get('/clubs/stats', async (req, res) => {
|
|
try {
|
|
// 전체 클럽 수
|
|
const totalCount = await Club.count();
|
|
|
|
// 활성 클럽 수 (status가 active인 클럽)
|
|
const activeCount = await Club.count({
|
|
where: { status: 'active' }
|
|
});
|
|
|
|
// 최근 가입한 클럽 수 (최근 7일)
|
|
const newCount = await Club.count({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: new Date(new Date() - 7 * 24 * 60 * 60 * 1000)
|
|
}
|
|
}
|
|
});
|
|
|
|
// 평균 회원 수
|
|
const avgMemberCount = await Club.findOne({
|
|
attributes: [
|
|
[sequelize.fn('AVG', sequelize.col('memberCount')), 'avgMembers']
|
|
],
|
|
raw: true
|
|
});
|
|
|
|
res.json({
|
|
totalCount,
|
|
activeCount,
|
|
newCount,
|
|
averageMemberCount: Math.round(avgMemberCount.avgMembers || 0)
|
|
});
|
|
|
|
await logActivity(req.user.id, 'VIEW_CLUB_STATS', '클럽 통계를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('클럽 통계 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 기능 통계 가져오기
|
|
router.get('/features/stats', async (req, res) => {
|
|
try {
|
|
// 전체 기능 수
|
|
const totalCount = await Feature.count();
|
|
|
|
// 활성화된 기능 수 (status가 active인 기능)
|
|
const activeCount = await Feature.count({
|
|
where: { status: 'active' }
|
|
});
|
|
|
|
// 클럽별 기능 사용 현황
|
|
const usageStats = await ClubFeature.findAll({
|
|
attributes: [
|
|
'featureId',
|
|
[sequelize.fn('COUNT', sequelize.col('ClubFeature.id')), 'usageCount']
|
|
],
|
|
include: [{
|
|
model: Feature,
|
|
attributes: ['name']
|
|
}],
|
|
group: ['featureId', 'Feature.id', 'Feature.name'],
|
|
raw: true
|
|
});
|
|
|
|
// 최근 추가된 기능 (최근 30일)
|
|
const newCount = await Feature.count({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: new Date(new Date() - 30 * 24 * 60 * 60 * 1000)
|
|
}
|
|
}
|
|
});
|
|
|
|
res.json({
|
|
totalCount,
|
|
activeCount,
|
|
newCount,
|
|
usageStats: usageStats.map(stat => ({
|
|
featureName: stat['Feature.name'],
|
|
usageCount: parseInt(stat.usageCount)
|
|
}))
|
|
});
|
|
|
|
await logActivity(req.user.id, 'VIEW_FEATURE_STATS', '기능 통계를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('기능 통계 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 사용자 목록 가져오기
|
|
router.get('/users', async (req, res) => {
|
|
try {
|
|
const users = await User.findAll({
|
|
attributes: ['id', 'name', 'email', 'role', 'createdAt'],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
res.json(users);
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'VIEW_USERS', '사용자 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('사용자 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 특정 사용자 조회
|
|
router.get('/users/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const user = await User.findByPk(id, {
|
|
attributes: ['id', 'username', 'name', 'email', 'role', 'status', 'createdAt']
|
|
});
|
|
|
|
if (!user) {
|
|
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.json(user);
|
|
await logActivity(req.user.id, 'VIEW_USER', `사용자 ${id}의 정보를 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('사용자 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 사용자 정보 수정
|
|
router.put('/users/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { username, name, email, role, status } = req.body;
|
|
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 관리자는 자신의 role을 변경할 수 없음
|
|
if (req.user.id === parseInt(id) && role && role !== user.role) {
|
|
return res.status(403).json({ message: '자신의 권한을 변경할 수 없습니다.' });
|
|
}
|
|
|
|
await user.update({
|
|
username,
|
|
name,
|
|
email,
|
|
role,
|
|
status
|
|
});
|
|
|
|
const updatedUser = await User.findByPk(id, {
|
|
attributes: ['id', 'username', 'name', 'email', 'role', 'status', 'createdAt']
|
|
});
|
|
|
|
res.json(updatedUser);
|
|
await logActivity(req.user.id, 'UPDATE_USER', `사용자 ${id}의 정보를 수정했습니다.`);
|
|
} catch (error) {
|
|
console.error('사용자 수정 중 오류:', error);
|
|
if (error.name === 'SequelizeUniqueConstraintError') {
|
|
return res.status(400).json({ message: '이미 사용 중인 이메일 또는 사용자명입니다.' });
|
|
}
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 사용자 삭제
|
|
router.delete('/users/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// 자기 자신은 삭제할 수 없음
|
|
if (req.user.id === parseInt(id)) {
|
|
return res.status(403).json({ message: '자신의 계정은 삭제할 수 없습니다.' });
|
|
}
|
|
|
|
const user = await User.findByPk(id);
|
|
if (!user) {
|
|
return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await user.destroy();
|
|
|
|
res.json({ message: '사용자가 삭제되었습니다.' });
|
|
await logActivity(req.user.id, 'DELETE_USER', `사용자 ${id}를 삭제했습니다.`);
|
|
} catch (error) {
|
|
console.error('사용자 삭제 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 목록 가져오기
|
|
router.get('/clubs', async (req, res) => {
|
|
try {
|
|
const clubs = await Club.findAll({
|
|
attributes: [
|
|
'id',
|
|
'name',
|
|
'location',
|
|
'createdAt',
|
|
[sequelize.literal('(SELECT COUNT(*) FROM Members WHERE Members.clubId = Club.id)'), 'memberCount']
|
|
],
|
|
include: [{
|
|
model: User,
|
|
as: 'owner',
|
|
attributes: ['id', 'name', 'email']
|
|
}],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
res.json(clubs);
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'VIEW_CLUBS', '클럽 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('클럽 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 목록 가져오기
|
|
router.get('/subscriptions', async (req, res) => {
|
|
try {
|
|
const subscriptions = await ClubSubscription.findAll({
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
},
|
|
{
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}
|
|
],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
const formattedSubscriptions = subscriptions.map(subscription => {
|
|
const data = subscription.toJSON();
|
|
data.expiryDate = data.endDate;
|
|
delete data.endDate;
|
|
return data;
|
|
});
|
|
|
|
res.json(formattedSubscriptions);
|
|
|
|
await logActivity(req.user.id, 'VIEW_SUBSCRIPTIONS', '구독 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('구독 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 통계 가져오기
|
|
router.get('/subscriptions/stats', async (req, res) => {
|
|
try {
|
|
// 전체 구독 수
|
|
const totalSubscriptions = await ClubSubscription.count();
|
|
|
|
// 활성 구독 수
|
|
const activeSubscriptions = await ClubSubscription.count({
|
|
where: { status: 'active' }
|
|
});
|
|
|
|
// 만료된 구독 수
|
|
const expiredSubscriptions = await ClubSubscription.count({
|
|
where: { status: 'expired' }
|
|
});
|
|
|
|
// 플랜별 구독 통계
|
|
const planStats = await ClubSubscription.findAll({
|
|
attributes: [
|
|
'planId',
|
|
[sequelize.fn('COUNT', sequelize.col('ClubSubscription.id')), 'count']
|
|
],
|
|
include: [{
|
|
model: SubscriptionPlan,
|
|
attributes: ['name', 'price']
|
|
}],
|
|
group: ['planId', 'SubscriptionPlan.id', 'SubscriptionPlan.name', 'SubscriptionPlan.price'],
|
|
raw: true
|
|
});
|
|
|
|
// 월별 구독 통계 (최근 6개월)
|
|
const sixMonthsAgo = new Date();
|
|
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5);
|
|
|
|
const monthlyStats = await ClubSubscription.findAll({
|
|
attributes: [
|
|
[sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m'), 'month'],
|
|
[sequelize.fn('COUNT', sequelize.col('ClubSubscription.id')), 'count']
|
|
],
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: sixMonthsAgo
|
|
}
|
|
},
|
|
group: [sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m')],
|
|
order: [[sequelize.fn('DATE_FORMAT', sequelize.col('ClubSubscription.createdAt'), '%Y-%m'), 'ASC']],
|
|
raw: true
|
|
});
|
|
|
|
// 총 매출 계산
|
|
const totalRevenue = planStats.reduce((sum, stat) => {
|
|
return sum + (stat['SubscriptionPlan.price'] * parseInt(stat.count));
|
|
}, 0);
|
|
|
|
res.json({
|
|
summary: {
|
|
totalSubscriptions,
|
|
activeSubscriptions,
|
|
expiredSubscriptions,
|
|
totalRevenue
|
|
},
|
|
planStats: planStats.map(stat => ({
|
|
planName: stat['SubscriptionPlan.name'],
|
|
planPrice: stat['SubscriptionPlan.price'],
|
|
count: parseInt(stat.count),
|
|
revenue: stat['SubscriptionPlan.price'] * parseInt(stat.count)
|
|
})),
|
|
monthlyStats: monthlyStats.map(stat => ({
|
|
month: stat.month,
|
|
count: parseInt(stat.count)
|
|
}))
|
|
});
|
|
|
|
await logActivity(req.user.id, 'VIEW_SUBSCRIPTION_STATS', '구독 통계를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('구독 통계 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 이벤트 목록 가져오기
|
|
router.get('/events', async (req, res) => {
|
|
try {
|
|
const events = await Meeting.findAll({
|
|
include: [Club],
|
|
order: [['date', 'DESC']]
|
|
});
|
|
|
|
const formattedEvents = events.map(event => ({
|
|
id: event.id,
|
|
title: event.title,
|
|
date: event.date,
|
|
status: event.status,
|
|
teamCount: event.teamCount,
|
|
gameCount: event.gameCount,
|
|
participantCount: event.participantCount,
|
|
clubName: event.Club.name,
|
|
location: event.location
|
|
}));
|
|
|
|
res.json(formattedEvents);
|
|
|
|
await logActivity(req.user.id, 'VIEW_EVENTS', '이벤트 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('이벤트 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 이벤트 참가자 목록 가져오기
|
|
router.get('/events/:id/participants', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const event = await Meeting.findByPk(id, {
|
|
include: [{
|
|
model: MeetingParticipant,
|
|
include: [Member]
|
|
}]
|
|
});
|
|
|
|
if (!event) {
|
|
return res.status(404).json({ message: '이벤트를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
const participants = event.MeetingParticipants.map(p => ({
|
|
id: p.id,
|
|
memberId: p.memberId,
|
|
memberName: p.Member.name,
|
|
teamNumber: p.teamNumber,
|
|
status: p.status,
|
|
attendance: p.attendance,
|
|
paymentStatus: p.paymentStatus,
|
|
registeredAt: p.createdAt
|
|
}));
|
|
|
|
res.json(participants);
|
|
|
|
await logActivity(req.user.id, 'VIEW_EVENT_PARTICIPANTS', `이벤트 참가자 목록을 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('이벤트 참가자 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 회원 목록 조회
|
|
router.get('/clubs/:id/members', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const members = await Member.findAll({
|
|
where: { clubId: id },
|
|
include: [{
|
|
model: User,
|
|
attributes: ['id', 'name', 'email']
|
|
}],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
res.json(members);
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'VIEW_CLUB_MEMBERS', `클럽 ${id}의 회원 목록을 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('클럽 회원 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 회원 추가
|
|
router.post('/clubs/:id/members', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, gender, memberType, userId } = req.body;
|
|
|
|
const member = await Member.create({
|
|
clubId: id,
|
|
userId,
|
|
name,
|
|
gender,
|
|
memberType,
|
|
joinDate: new Date(),
|
|
status: 'active'
|
|
});
|
|
|
|
res.status(201).json(member);
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'ADD_CLUB_MEMBER', `클럽 ${id}에 새 회원을 추가했습니다.`);
|
|
} catch (error) {
|
|
console.error('클럽 회원 추가 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 회원 정보 수정
|
|
router.put('/clubs/:id/members/:memberId', async (req, res) => {
|
|
try {
|
|
const { id, memberId } = req.params;
|
|
const updateData = req.body;
|
|
|
|
const member = await Member.findOne({
|
|
where: {
|
|
id: memberId,
|
|
clubId: id
|
|
}
|
|
});
|
|
|
|
if (!member) {
|
|
return res.status(404).json({ message: '회원을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await member.update(updateData);
|
|
|
|
res.json(member);
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'UPDATE_CLUB_MEMBER', `클럽 ${id}의 회원 정보를 수정했습니다.`);
|
|
} catch (error) {
|
|
console.error('클럽 회원 정보 수정 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 회원 삭제
|
|
router.delete('/clubs/:id/members/:memberId', async (req, res) => {
|
|
try {
|
|
const { id, memberId } = req.params;
|
|
|
|
const member = await Member.findOne({
|
|
where: {
|
|
id: memberId,
|
|
clubId: id
|
|
}
|
|
});
|
|
|
|
if (!member) {
|
|
return res.status(404).json({ message: '회원을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await member.destroy();
|
|
|
|
res.json({ message: '회원이 삭제되었습니다.' });
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'DELETE_CLUB_MEMBER', `클럽 ${id}의 회원을 삭제했습니다.`);
|
|
} catch (error) {
|
|
console.error('클럽 회원 삭제 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 활동 로그 목록 조회
|
|
router.get('/activity-logs', async (req, res) => {
|
|
try {
|
|
const page = parseInt(req.query.page) || 1;
|
|
const limit = parseInt(req.query.limit) || 20;
|
|
const offset = (page - 1) * limit;
|
|
|
|
const logs = await ActivityLog.findAndCountAll({
|
|
include: [{
|
|
model: User,
|
|
attributes: ['username', 'name']
|
|
}],
|
|
order: [['createdAt', 'DESC']],
|
|
limit,
|
|
offset
|
|
});
|
|
|
|
res.json({
|
|
logs: logs.rows.map(log => ({
|
|
id: log.id,
|
|
type: log.type,
|
|
description: log.description,
|
|
createdAt: log.createdAt,
|
|
user: {
|
|
username: log.User.username,
|
|
name: log.User.name
|
|
}
|
|
})),
|
|
totalPages: Math.ceil(logs.count / limit),
|
|
currentPage: page,
|
|
totalItems: logs.count
|
|
});
|
|
|
|
await logActivity(req.user.id, 'VIEW_ACTIVITY_LOGS', '활동 로그를 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('활동 로그 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 플랜 목록 조회
|
|
router.get('/subscriptions/plans', async (req, res) => {
|
|
try {
|
|
const plans = await SubscriptionPlan.findAll({
|
|
include: [{
|
|
model: Feature,
|
|
through: { attributes: [] }
|
|
}],
|
|
order: [['price', 'ASC']]
|
|
});
|
|
|
|
res.json(plans);
|
|
|
|
await logActivity(req.user.id, 'VIEW_SUBSCRIPTION_PLANS', '구독 플랜 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('구독 플랜 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 플랜 생성
|
|
router.post('/subscriptions/plans', async (req, res) => {
|
|
try {
|
|
const { name, description, price, duration, features } = req.body;
|
|
|
|
const plan = await SubscriptionPlan.create({
|
|
name,
|
|
description,
|
|
price,
|
|
duration
|
|
});
|
|
|
|
if (features && features.length > 0) {
|
|
await plan.setFeatures(features);
|
|
}
|
|
|
|
res.status(201).json(plan);
|
|
|
|
await logActivity(req.user.id, 'CREATE_SUBSCRIPTION_PLAN', `새 구독 플랜 "${name}"을(를) 생성했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 플랜 생성 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 플랜 수정
|
|
router.put('/subscriptions/plans/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, price, duration, features } = req.body;
|
|
|
|
const plan = await SubscriptionPlan.findByPk(id);
|
|
if (!plan) {
|
|
return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await plan.update({
|
|
name,
|
|
description,
|
|
price,
|
|
duration
|
|
});
|
|
|
|
if (features) {
|
|
await plan.setFeatures(features);
|
|
}
|
|
|
|
res.json(plan);
|
|
|
|
await logActivity(req.user.id, 'UPDATE_SUBSCRIPTION_PLAN', `구독 플랜 "${name}"을(를) 수정했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 플랜 수정 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 플랜 삭제
|
|
router.delete('/subscriptions/plans/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const plan = await SubscriptionPlan.findByPk(id);
|
|
if (!plan) {
|
|
return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await plan.destroy();
|
|
|
|
res.json({ message: '구독 플랜이 삭제되었습니다.' });
|
|
|
|
await logActivity(req.user.id, 'DELETE_SUBSCRIPTION_PLAN', `구독 플랜 "${plan.name}"을(를) 삭제했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 플랜 삭제 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 기능 목록 조회
|
|
router.get('/features', async (req, res) => {
|
|
try {
|
|
const features = await Feature.findAll({
|
|
order: [['name', 'ASC']]
|
|
});
|
|
|
|
res.json(features);
|
|
|
|
await logActivity(req.user.id, 'VIEW_FEATURES', '기능 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('기능 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 기능 생성
|
|
router.post('/features', async (req, res) => {
|
|
try {
|
|
const { name, description, category } = req.body;
|
|
|
|
const feature = await Feature.create({
|
|
name,
|
|
description,
|
|
category
|
|
});
|
|
|
|
res.status(201).json(feature);
|
|
|
|
await logActivity(req.user.id, 'CREATE_FEATURE', `새 기능 "${name}"을(를) 생성했습니다.`);
|
|
} catch (error) {
|
|
console.error('기능 생성 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 기능 수정
|
|
router.put('/features/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, category } = req.body;
|
|
|
|
const feature = await Feature.findByPk(id);
|
|
if (!feature) {
|
|
return res.status(404).json({ message: '기능을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await feature.update({
|
|
name,
|
|
description,
|
|
category
|
|
});
|
|
|
|
res.json(feature);
|
|
|
|
await logActivity(req.user.id, 'UPDATE_FEATURE', `기능 "${name}"을(를) 수정했습니다.`);
|
|
} catch (error) {
|
|
console.error('기능 수정 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 기능 삭제
|
|
router.delete('/features/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const feature = await Feature.findByPk(id);
|
|
if (!feature) {
|
|
return res.status(404).json({ message: '기능을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
await feature.destroy();
|
|
|
|
res.json({ message: '기능이 삭제되었습니다.' });
|
|
|
|
await logActivity(req.user.id, 'DELETE_FEATURE', `기능 "${feature.name}"을(를) 삭제했습니다.`);
|
|
} catch (error) {
|
|
console.error('기능 삭제 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 생성
|
|
router.post('/subscriptions', async (req, res) => {
|
|
try {
|
|
const { clubId, planId, startDate, expiryDate, status } = req.body;
|
|
console.log('구독 생성 요청:', { clubId, planId, startDate, expiryDate, status });
|
|
|
|
// 트랜잭션 시작
|
|
const result = await sequelize.transaction(async (t) => {
|
|
// 클럽과 플랜이 존재하는지 확인
|
|
const [club, plan] = await Promise.all([
|
|
Club.findByPk(clubId, { transaction: t }),
|
|
SubscriptionPlan.findByPk(planId, {
|
|
include: [Feature],
|
|
transaction: t
|
|
})
|
|
]);
|
|
|
|
if (!club) {
|
|
throw new Error('클럽을 찾을 수 없습니다.');
|
|
}
|
|
|
|
if (!plan) {
|
|
throw new Error('구독 플랜을 찾을 수 없습니다.');
|
|
}
|
|
|
|
// 기존 활성 구독이 있는지 확인
|
|
const existingSubscription = await ClubSubscription.findOne({
|
|
where: {
|
|
clubId,
|
|
status: 'active'
|
|
},
|
|
transaction: t
|
|
});
|
|
|
|
if (existingSubscription) {
|
|
throw new Error('이미 활성화된 구독이 있습니다.');
|
|
}
|
|
|
|
// 구독 생성
|
|
const subscription = await ClubSubscription.create({
|
|
clubId,
|
|
planId,
|
|
startDate: startDate || new Date(),
|
|
endDate: expiryDate,
|
|
status: status || 'active',
|
|
price: plan.price
|
|
}, { transaction: t });
|
|
|
|
// 플랜의 기능들을 클럽에 연결
|
|
if (plan.Features && plan.Features.length > 0) {
|
|
const features = plan.Features.map(feature => ({
|
|
clubId,
|
|
featureId: feature.id,
|
|
enabled: true,
|
|
expiryDate,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
}));
|
|
|
|
await ClubFeature.bulkCreate(features, {
|
|
transaction: t,
|
|
fields: ['clubId', 'featureId', 'enabled', 'expiryDate', 'createdAt', 'updatedAt']
|
|
});
|
|
}
|
|
|
|
return { subscription, features: plan.Features };
|
|
});
|
|
|
|
// 응답 데이터 구성
|
|
const response = result.subscription.toJSON();
|
|
response.expiryDate = response.endDate;
|
|
delete response.endDate;
|
|
response.features = result.features;
|
|
|
|
res.status(201).json(response);
|
|
|
|
await logActivity(req.user.id, 'CREATE_SUBSCRIPTION', `클럽 ${clubId}의 새 구독을 생성했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 생성 중 오류 발생:', error);
|
|
console.error('오류 상세:', error.stack);
|
|
res.status(error.message.includes('찾을 수 없습니다') ? 404 :
|
|
error.message.includes('이미 활성화된') ? 400 : 500)
|
|
.json({ message: error.message || '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 수정
|
|
router.put('/subscriptions/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { planId, startDate, expiryDate, status } = req.body;
|
|
|
|
const subscription = await ClubSubscription.findByPk(id, {
|
|
include: [Club, SubscriptionPlan]
|
|
});
|
|
|
|
if (!subscription) {
|
|
return res.status(404).json({ message: '구독을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 플랜이 변경되는 경우 새 플랜 확인
|
|
let newPlan = null;
|
|
if (planId && planId !== subscription.planId) {
|
|
newPlan = await SubscriptionPlan.findByPk(planId);
|
|
if (!newPlan) {
|
|
return res.status(404).json({ message: '구독 플랜을 찾을 수 없습니다.' });
|
|
}
|
|
}
|
|
|
|
// 구독 정보 업데이트
|
|
await subscription.update({
|
|
planId: planId || subscription.planId,
|
|
startDate: startDate || subscription.startDate,
|
|
endDate: expiryDate || subscription.endDate,
|
|
status: status || subscription.status,
|
|
price: newPlan ? newPlan.price : subscription.price
|
|
});
|
|
|
|
// 플랜이 변경된 경우 기능도 업데이트
|
|
if (newPlan) {
|
|
const planFeatures = await newPlan.getFeatures();
|
|
await subscription.Club.setFeatures(planFeatures);
|
|
}
|
|
|
|
// 업데이트된 구독 정보 조회
|
|
const updatedSubscription = await ClubSubscription.findByPk(id, {
|
|
include: [{
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}]
|
|
});
|
|
|
|
// endDate를 expiryDate로 변환
|
|
const result = updatedSubscription.toJSON();
|
|
result.expiryDate = result.endDate;
|
|
delete result.endDate;
|
|
|
|
res.json(result);
|
|
|
|
await logActivity(req.user.id, 'UPDATE_SUBSCRIPTION', `구독 ${id}를 수정했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 수정 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 삭제
|
|
router.delete('/subscriptions/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const subscription = await ClubSubscription.findByPk(id);
|
|
if (!subscription) {
|
|
return res.status(404).json({ message: '구독을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 구독과 관련된 기능들을 제거
|
|
const club = await Club.findByPk(subscription.clubId);
|
|
if (club) {
|
|
await club.setFeatures([]);
|
|
}
|
|
|
|
await subscription.destroy();
|
|
|
|
res.json({ message: '구독이 삭제되었습니다.' });
|
|
|
|
await logActivity(req.user.id, 'DELETE_SUBSCRIPTION', `구독 ${id}를 삭제했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 삭제 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 구독 목록 조회
|
|
router.get('/subscriptions', async (req, res) => {
|
|
try {
|
|
const subscriptions = await ClubSubscription.findAll({
|
|
include: [{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}, {
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
const formattedSubscriptions = subscriptions.map(subscription => {
|
|
const data = subscription.toJSON();
|
|
data.expiryDate = data.endDate;
|
|
delete data.endDate;
|
|
return data;
|
|
});
|
|
|
|
res.json(formattedSubscriptions);
|
|
|
|
await logActivity(req.user.id, 'VIEW_SUBSCRIPTIONS', '구독 목록을 조회했습니다.');
|
|
} catch (error) {
|
|
console.error('구독 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 특정 구독 조회
|
|
router.get('/subscriptions/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const subscription = await ClubSubscription.findByPk(id, {
|
|
include: [{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
}, {
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}]
|
|
});
|
|
|
|
if (!subscription) {
|
|
return res.status(404).json({ message: '구독을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
res.json(subscription);
|
|
|
|
await logActivity(req.user.id, 'VIEW_SUBSCRIPTION', `구독 ${id}를 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('구독 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 특정 플랜의 구독 목록 조회
|
|
router.get('/subscriptions/plans/:planId/subscriptions', async (req, res) => {
|
|
try {
|
|
const { planId } = req.params;
|
|
|
|
const subscriptions = await ClubSubscription.findAll({
|
|
where: {
|
|
planId
|
|
},
|
|
include: [
|
|
{
|
|
model: Club,
|
|
attributes: ['id', 'name']
|
|
},
|
|
{
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}
|
|
],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
const formattedSubscriptions = subscriptions.map(subscription => {
|
|
const data = subscription.toJSON();
|
|
data.expiryDate = data.endDate;
|
|
delete data.endDate;
|
|
return data;
|
|
});
|
|
|
|
res.json(formattedSubscriptions);
|
|
|
|
await logActivity(req.user.id, 'VIEW_PLAN_SUBSCRIPTIONS', `플랜 ${planId}의 구독 목록을 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('플랜별 구독 목록 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 상세 조회
|
|
router.get('/clubs/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const club = await Club.findByPk(id, {
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'owner',
|
|
attributes: ['id', 'name', 'email']
|
|
},
|
|
{
|
|
model: ClubFeature,
|
|
as: 'features',
|
|
include: [{
|
|
model: Feature,
|
|
as: 'feature',
|
|
attributes: ['id', 'name', 'code', 'description']
|
|
}]
|
|
},
|
|
{
|
|
model: ClubSubscription,
|
|
where: {
|
|
status: 'active'
|
|
},
|
|
required: false,
|
|
include: [{
|
|
model: SubscriptionPlan,
|
|
include: [Feature]
|
|
}]
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!club) {
|
|
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 응답 데이터 구성
|
|
const response = club.toJSON();
|
|
|
|
// 활성 구독이 있는 경우 구독 정보 포맷팅
|
|
if (response.ClubSubscriptions && response.ClubSubscriptions.length > 0) {
|
|
const subscription = response.ClubSubscriptions[0];
|
|
response.activeSubscription = {
|
|
id: subscription.id,
|
|
startDate: subscription.startDate,
|
|
expiryDate: subscription.endDate,
|
|
plan: subscription.SubscriptionPlan
|
|
};
|
|
}
|
|
delete response.ClubSubscriptions;
|
|
|
|
// 기능 정보 포맷팅
|
|
const formattedFeatures = response.features.map(feature => ({
|
|
...feature.feature,
|
|
enabled: feature.enabled,
|
|
expiryDate: feature.expiryDate
|
|
}));
|
|
response.features = formattedFeatures;
|
|
|
|
res.json(response);
|
|
await logActivity(req.user.id, 'VIEW_CLUB', `클럽 ${id}의 정보를 조회했습니다.`);
|
|
} catch (error) {
|
|
console.error('클럽 조회 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 수정
|
|
router.put('/clubs/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { name, description, ownerEmail, location } = req.body;
|
|
|
|
// 클럽 존재 여부 확인
|
|
const club = await Club.findByPk(id);
|
|
if (!club) {
|
|
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 새 모임장 정보 확인
|
|
const newOwner = await User.findOne({ where: { email: ownerEmail } });
|
|
if (!newOwner) {
|
|
return res.status(404).json({ message: '모임장으로 지정할 사용자를 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 트랜잭션 시작
|
|
const t = await sequelize.transaction();
|
|
|
|
try {
|
|
// 클럽 정보 업데이트
|
|
await club.update({
|
|
name,
|
|
description,
|
|
location,
|
|
ownerId: newOwner.id
|
|
}, { transaction: t });
|
|
|
|
// 활동 로그 기록
|
|
await logActivity(req.user.id, 'UPDATE_CLUB', `클럽 "${name}"의 정보를 수정했습니다.`);
|
|
|
|
await t.commit();
|
|
|
|
// 업데이트된 클럽 정보 조회
|
|
const updatedClub = await Club.findByPk(id, {
|
|
include: [{
|
|
model: User,
|
|
as: 'owner',
|
|
attributes: ['id', 'name', 'email']
|
|
}]
|
|
});
|
|
|
|
res.json(updatedClub);
|
|
} catch (error) {
|
|
await t.rollback();
|
|
throw error;
|
|
}
|
|
} catch (error) {
|
|
console.error('클럽 수정 중 오류:', error);
|
|
res.status(500).json({ message: '클럽 수정 중 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 클럽 기능 관리
|
|
router.put('/clubs/:id/features', async (req, res) => {
|
|
const t = await sequelize.transaction();
|
|
|
|
try {
|
|
const { id } = req.params;
|
|
const { features } = req.body;
|
|
|
|
const club = await Club.findByPk(id, { transaction: t });
|
|
if (!club) {
|
|
await t.rollback();
|
|
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
|
|
}
|
|
|
|
// 기존 기능 목록 조회
|
|
const existingFeatures = await ClubFeature.findAll({
|
|
where: { clubId: id },
|
|
transaction: t
|
|
});
|
|
|
|
// 기능 업데이트 또는 생성
|
|
for (const feature of features) {
|
|
const existingFeature = existingFeatures.find(ef => ef.featureId === feature.id);
|
|
|
|
if (existingFeature) {
|
|
await existingFeature.update({
|
|
enabled: feature.enabled,
|
|
expiryDate: feature.expiryDate
|
|
}, { transaction: t });
|
|
} else {
|
|
await ClubFeature.create({
|
|
clubId: id,
|
|
featureId: feature.id,
|
|
enabled: feature.enabled,
|
|
expiryDate: feature.expiryDate
|
|
}, { transaction: t });
|
|
}
|
|
}
|
|
|
|
// 제거된 기능 처리
|
|
const featureIds = features.map(f => f.id);
|
|
await ClubFeature.destroy({
|
|
where: {
|
|
clubId: id,
|
|
featureId: {
|
|
[Op.notIn]: featureIds
|
|
}
|
|
},
|
|
transaction: t
|
|
});
|
|
|
|
await t.commit();
|
|
|
|
// 업데이트된 클럽 정보 조회
|
|
const updatedClub = await Club.findByPk(id, {
|
|
include: [{
|
|
model: Feature,
|
|
through: {
|
|
attributes: ['enabled', 'expiryDate']
|
|
}
|
|
}]
|
|
});
|
|
|
|
const response = updatedClub.toJSON();
|
|
response.features = response.Features.map(feature => ({
|
|
...feature,
|
|
enabled: feature.ClubFeature.enabled,
|
|
expiryDate: feature.ClubFeature.expiryDate
|
|
}));
|
|
delete response.Features;
|
|
|
|
res.json(response);
|
|
await logActivity(req.user.id, 'UPDATE_CLUB_FEATURES', `클럽 ${id}의 기능을 수정했습니다.`);
|
|
} catch (error) {
|
|
await t.rollback();
|
|
console.error('클럽 기능 수정 중 오류:', error);
|
|
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
|
|
}
|
|
});
|
|
|
|
// 에러 처리 미들웨어
|
|
const errorHandler = (err, req, res, next) => {
|
|
console.error('에러 발생:', err);
|
|
res.status(500).json({
|
|
message: '서버 오류가 발생했습니다.',
|
|
error: process.env.NODE_ENV === 'development' ? err.message : undefined
|
|
});
|
|
};
|
|
|
|
router.use(errorHandler);
|
|
|
|
module.exports = router;
|