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;