const Event = require('../models/Event'); const EventParticipants = require('../models/EventParticipants'); const {Op} = require('sequelize'); const moment = require('moment'); // 날짜 포맷팅 라이브러리 const UserEvent = require('../models/UserEvent.js'); const {redisClient, capCheckLuaScript} = require("../datastore/redis"); const UserCrew = require("../models/UserCrew"); // 한 페이지당 기본 아이템 수 const itemsPerPage = 10; const countKeyPattern = 'event:${eventID}:participant_count'; // 이벤트 목록 조회 (단일 엔드포인트) exports.getEvents = async (req, res) => { console.log('이벤트 목록 조회 컨트롤러 실행'); // 로그 확인용 try { const {sportTypeId, regionID, eventDate, page = 1} = req.query; // 쿼리 파라미터 가져오기 const offset = (page - 1) * itemsPerPage; // 시작 지점 계산 const where = {}; // 쿼리 파라미터 조건 추가 if (sportTypeId) where.sportTypeId = sportTypeId; if (regionID) where.regionID = regionID; if (eventDate) where.eventDate = eventDate; // 이벤트 데이터 가져오기 const {rows: events, count: total} = await Event.findAndCountAll({ where, limit: itemsPerPage, offset, }); // 응답 데이터 반환 res.status(200).json({ events, total, // 조건에 맞는 총 이벤트 개수 itemsPerPage, // 페이지당 이벤트 수 }); } catch (error) { console.error('이벤트 목록 조회 중 오류:', error); res.status(500).json({error: '이벤트 목록 조회 중 오류가 발생했습니다.'}); } }; // 이벤트 생성 컨트롤러 exports.createEvent = async (req, res) => { try { const { crewID, regionID, name, sportTypeId, eventDate, capacity, feeCondition, } = req.body; const userID = req.user.userID; // TODO: 클라이언트에게 누락된 필드 정보를 명시적으로 전달하지 못하는 패턴입니다. // 추후 리팩터링 시 누락된 필드를 클라이언트에 명확히 알려주는 로직으로 개선할 필요가 있습니다. if (!regionID || !sportTypeId) { return res.status(400).json({error: '필수 필드가 누락되었습니다.'}); } const newEvent = await Event.create({ crewID, regionID, name, sportTypeId, eventDate, capacity, feeCondition, userID, }); res.status(201).json(newEvent); } catch (error) { console.error('이벤트 생성 중 오류:', error); res.status(500).json({error: '이벤트 생성 중 오류가 발생했습니다.'}); } }; // 특정 이벤트 조회 컨트롤러 exports.getEventById = async (req, res) => { try { const {eventID} = req.params; // 이벤트 정보 조회 const event = await Event.findByPk(eventID, { attributes: [ 'eventID', 'regionID', 'name', 'sportTypeId', 'eventDate', 'capacity', 'feeCondition', 'userID', 'createdDate', ], }); if (!event) { return res.status(404).json({error: '해당 이벤트가 존재하지 않습니다.'}); } // 현재 참여한 인원 수 계산 const currentMemberCount = redisClient.get(countKeyPattern).then((result) => { return parseInt(result, 10); }).catch((error) => { console.error('이벤트 참여자 수 조회중 오류', error); return UserEvent.count({ where: {eventID: event.eventID}, }); }); // 이벤트 데이터에 현재 참여한 인원 수 추가 const response = { ...event.toJSON(), currentMemberCount, // 현재 참여한 인원 수 }; res.status(200).json(response); } catch (error) { console.error('이벤트 조회 중 오류:', error); res.status(500).json({error: '이벤트 조회 중 오류가 발생했습니다.'}); } }; // 이벤트 수정 컨트롤러 exports.updateEvent = async (req, res) => { try { const {eventID} = req.params; const updateData = req.body; const userID = req.user.userID; const event = await Event.findByPk(eventID); if (!event) { return res.status(404).json({error: '해당 이벤트가 존재하지 않습니다.'}); } if (event.userID !== userID) { return res.status(403).json({error: '이벤트 수정 권한이 없습니다.'}); } await event.update(updateData); res.status(200).json({updatedEvent: event}); } catch (error) { console.error('이벤트 수정 중 오류:', error); res.status(500).json({error: '이벤트 수정 중 오류가 발생했습니다.'}); } }; // 이벤트 삭제 컨트롤러 exports.deleteEvent = async (req, res) => { try { const {eventID} = req.params; const userID = req.user.userID; const event = await Event.findByPk(eventID); if (!event) { return res.status(404).json({error: '해당 이벤트가 존재하지 않습니다.'}); } if (event.userID !== userID) { return res.status(403).json({error: '이벤트 삭제 권한이 없습니다.'}); } await event.destroy(); res.status(200).json({message: '이벤트가 성공적으로 삭제되었습니다.'}); } catch (error) { console.error('이벤트 삭제 중 오류:', error); res.status(500).json({error: '이벤트 삭제 중 오류가 발생했습니다.'}); } }; // 이벤트 참여 컨트롤러 exports.participateEvent = async (req, res) => { const {eventID} = req.params; const userID = req.user.userID; if (!eventID || !userID) { return res.status(400).json({error: '필수 데이터가 누락되었습니다.'}); } const event = await Event.findByPk(eventID); if (!event) { return res.status(404).json({error: '해당 이벤트가 존재하지 않습니다.'}); } const existingParticipant = await EventParticipants.findOne({where: {eventID, userID}}); if (existingParticipant) { return res.status(409).json({error: '이미 이벤트에 참여 중입니다.'}); } const capacity = parseInt(event.capacity, 10); try { const result = await redisClient.sendCommand([ 'EVAL', capCheckLuaScript, '1', countKeyPattern, capacity.toString() ]); // 인원이 초과하지 않은 경우, 참여 진행. if (result === 1) { const newParticipant = await EventParticipants.create({ userID, eventID, participationDate: new Date(), status: 'attending', }); res.status(201).json(newParticipant); } else { return res.status(409).json({error: '이벤트의 최대 참여 인원 수를 초과했습니다.'}); } } catch (error) { // 참여 실패시 가입 카운트를 감소시킴. await redisClient.decr(countKeyPattern); console.error('이벤트 참여 중 오류:', error); res.status(500).json({error: '이벤트 참여 중 오류가 발생했습니다.'}); } }; // 이벤트 참여 취소 컨트롤러 exports.cancelParticipation = async (req, res) => { try { const {eventID} = req.params; // URL에서 eventID 가져오기 const userID = req.user.userID; // 필수 데이터 검증 if (!eventID || !userID) { return res.status(400).json({error: '필수 데이터가 누락되었습니다.'}); } // 이벤트 존재 여부 확인 const event = await Event.findByPk(eventID); if (!event) { return res.status(404).json({error: '해당 이벤트가 존재하지 않습니다.'}); } // 참여 기록 확인 const participant = await EventParticipants.findOne({where: {eventID, userID}}); if (!participant) { return res.status(404).json({error: '참여 기록이 존재하지 않습니다.'}); } // 상태 변경 (참여 취소) participant.status = 'canceled'; await participant.save(); // 참여 인원 카운트 감소 await redisClient.decr(countKeyPattern); res.status(200).json({ message: '이벤트 참여가 성공적으로 취소되었습니다.', participant, }); } catch (error) { console.error('이벤트 참여 취소 중 오류:', error); res.status(500).json({error: '이벤트 참여 취소 중 오류가 발생했습니다.'}); } };