Files
bowlingManager/backend/routes/adminRoutes.js
T
2025-05-09 10:34:04 +09:00

1396 lines
45 KiB
JavaScript

const express = require('express');
const router = express.Router();
const { Club, Member, User, Feature, ClubFeature, ActivityLog, SubscriptionPlan, Meeting, MeetingParticipant, ClubSubscription } = require('../models');
const clubController = require('../controllers/clubController');
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,
as: '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,
include: [{
model: 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.ClubFeatures.map(feature => ({
...feature.Feature,
enabled: feature.enabled,
expiryDate: feature.expiryDate
}));
response.features = formattedFeatures;
delete response.ClubFeatures;
res.json(response);
await logActivity(req.user.id, 'VIEW_CLUB', `클럽 ${id}의 정보를 조회했습니다.`);
} catch (error) {
console.error('클럽 조회 중 오류:', error);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}
});
// 새 클럽 생성
router.post('/clubs', authenticateJWT, clubController.createClub);
// 클럽 수정
router.put('/clubs/:id', authenticateJWT, clubController.updateClub);
// 클럽 기능 관리
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;