const { Menu, Feature, ClubFeature, ClubSubscription, SubscriptionPlan, Member } = require('../models'); const { Op } = require('sequelize'); /** * 사용자의 접근 가능한 메뉴 목록을 반환합니다. */ exports.getUserMenus = async (req, res) => { try { const { clubId, role = 'user' } = req.body; const userId = req.user.id; // JWT 토큰에서 사용자 ID 가져오기 if (!clubId) { return res.status(400).json({ success: false, message: '클럽 ID가 필요합니다.' }); } // 멤버 정보 조회 const member = await Member.findOne({ where: { clubId, userId } }); const menus = await getAccessibleMenus(clubId, role, member); res.json({ success: true, data: menus }); } catch (error) { console.error('메뉴 조회 중 오류 발생:', error); res.status(500).json({ success: false, message: '메뉴 조회 중 오류가 발생했습니다.' }); } } /** * 사용자 권한과 클럽의 구독/기능 상태에 따라 접근 가능한 메뉴 목록을 조회합니다. */ async function getAccessibleMenus(clubId, userRole, member) { try { // 1. 클럽의 현재 구독 정보 조회 const clubSubscription = await ClubSubscription.findOne({ where: { clubId, status: 'active', endDate: { [Op.gt]: new Date() } }, include: [{ model: SubscriptionPlan, attributes: ['name'] }] }); // 2. 클럽의 활성화된 기능 목록 조회 const activeFeatures = await ClubFeature.findAll({ where: { clubId, enabled: true, [Op.or]: [ { expiryDate: null }, { expiryDate: { [Op.gt]: new Date() } } ] }, include: [{ model: Feature, as: 'feature', attributes: ['code'] }] }); const activeFeatureCodes = activeFeatures.map(f => f.feature.code); const subscriptionTier = clubSubscription ? clubSubscription.SubscriptionPlan.name.toLowerCase() : 'basic'; // 3. 메뉴 조회 및 필터링 const allMenus = await Menu.findAll({ where: { visible: true, [Op.and]: [ // 역할 기반 필터링 { [Op.or]: [ { role: userRole }, { role: 'user' } ] }, // 구독 등급 기반 필터링 { [Op.or]: [ { requiredSubscriptionTier: null }, { requiredSubscriptionTier: subscriptionTier } ] } ] }, include: [{ model: Menu, as: 'children', required: false }], order: [ ['order', 'ASC'], [{ model: Menu, as: 'children' }, 'order', 'ASC'] ] }); // 4. 기능 코드 기반 필터링 및 메뉴 구조화 const processMenu = (menu) => { // 기능 코드가 있는 경우 접근 권한 확인 if (menu.featureCode && !activeFeatureCodes.includes(menu.featureCode)) { return null; } const processedMenu = { id: menu.id, title: menu.title, icon: menu.icon, to: menu.to, order: menu.order }; // 하위 메뉴 처리 if (menu.children && menu.children.length > 0) { const children = menu.children .map(processMenu) .filter(child => child !== null); if (children.length > 0) { processedMenu.children = children; } } return processedMenu; }; // 최상위 메뉴만 필터링 (parentId가 null인 항목) const rootMenus = allMenus .filter(menu => !menu.parentId) .map(processMenu) .filter(menu => menu !== null); // 구독 관리 메뉴 추가 (모임장, 운영진만 접근 가능) if (member && (member.memberType === '모임장' || member.memberType === '운영진')) { rootMenus.push({ id: 'subscription', title: '구독/결제 관리', icon: 'pi pi-credit-card', to: '/club/subscription', order: 90, children: [] }); } return rootMenus; } catch (error) { console.error('메뉴 조회 중 오류 발생:', error); throw error; } }