From 6578e4bf503f8185503a4db8d052426e9060c1e0 Mon Sep 17 00:00:00 2001 From: mingrammer <mingrammer@gmail.com> Date: Mon, 9 Dec 2024 10:54:50 +0900 Subject: [PATCH] fix: add missing endpoint --- .../apiserver/controllers/crewController.js | 656 ++++++++++-------- webapp/backend/apiserver/routes/crew.js | 3 + 2 files changed, 355 insertions(+), 304 deletions(-) diff --git a/webapp/backend/apiserver/controllers/crewController.js b/webapp/backend/apiserver/controllers/crewController.js index 7be619a..ef2eb1e 100644 --- a/webapp/backend/apiserver/controllers/crewController.js +++ b/webapp/backend/apiserver/controllers/crewController.js @@ -3,7 +3,7 @@ const Event = require('../models/Event.js'); const User = require('../models/User.js'); const UserCrew = require('../models/UserCrew.js'); const UserEvent = require('../models/UserEvent.js'); -const { redisClient, capCheckLuaScript } = require('../datastore/redis.js'); +const {redisClient, capCheckLuaScript} = require('../datastore/redis.js'); // 한 페이지당 기본 아이템 수 const itemsPerPage = 10; @@ -12,374 +12,422 @@ const countKeyPattern = 'crew:${crewID}:member_count'; // 크루 생성 컨트롤러 exports.createCrew = async (req, res) => { - try { - const { regionID, name, sportTypeId, capacity, fee_krw, description } = req.body; + try { + const {regionID, name, sportTypeId, capacity, fee_krw, description} = req.body; + + const userID = req.user.userID; // 현재 사용자 ID 가져오기 + + // 필수 필드 확인 + if (!regionID || !name || !sportTypeId) { + return res.status(400).json({error: '필수 필드가 누락되었습니다.'}); + } + + // 새 크루 생성 + const newCrew = await Crew.create({ + regionID, + name, + sportTypeId, + capacity, + fee_krw, + description, + }); - const userID = req.user.userID; // 현재 사용자 ID 가져오기 + // 크루장으로 UserCrew 테이블에 추가 + await UserCrew.create({ + crewID: newCrew.crewID, + userID: userID, + role: 'Leader', // 생성자는 자동으로 크루장이 됨 + }); - // 필수 필드 확인 - if (!regionID || !name || !sportTypeId) { - return res.status(400).json({ error: '필수 필드가 누락되었습니다.' }); + res.status(201).json({ + crew: newCrew, + message: '크루가 성공적으로 생성되었고 크루장으로 등록되었습니다.', + }); + } catch (error) { + console.error('크루 생성 중 오류:', error); + res.status(500).json({error: '크루 생성 중 오류가 발생했습니다.'}); } - - // 새 크루 생성 - const newCrew = await Crew.create({ - regionID, - name, - sportTypeId, - capacity, - fee_krw, - description, - }); - - // 크루장으로 UserCrew 테이블에 추가 - await UserCrew.create({ - crewID: newCrew.crewID, - userID: userID, - role: 'Leader', // 생성자는 자동으로 크루장이 됨 - }); - - res.status(201).json({ - crew: newCrew, - message: '크루가 성공적으로 생성되었고 크루장으로 등록되었습니다.', - }); - } catch (error) { - console.error('크루 생성 중 오류:', error); - res.status(500).json({ error: '크루 생성 중 오류가 발생했습니다.' }); - } }; -// 크루 가입 컨트롤러 -exports.joinCrew = async (req, res) => { - const {crewID} = req.params; - const {role} = req.body; - - const userID = req.user.userID; - - if (!crewID) { - return res.status(400).json({error: '필수 데이터가 누락되었습니다.'}); - } - - const crew = await Crew.findByPk(crewID); - if (!crew) { - return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); - } - - const existingUserCrew = await UserCrew.findOne({where: {crewID, userID}}); - if (existingUserCrew) { - return res.status(409).json({error: '이미 해당 크루에 가입되어 있습니다.'}); - } - - const capacity = parseInt(crew.capacity, 10); - - try { - const result = await redisClient.sendCommand([ - 'EVAL', - capCheckLuaScript, - '1', - countKeyPattern, - capacity.toString() - ]); - - // 인원이 초과하지 않은 경우, 가입 진행. - if (result === 1) { - const newUserCrew = await UserCrew.create({ - crewID, - userID, - role: role || 'General', - }); - - return res.status(201).json({ - crewID: newUserCrew.crewID, - userID: newUserCrew.userID, - role: newUserCrew.role, - }); - } else { - return res.status(409).json({error: '크루의 최대 인원 수를 초과했습니다.'}); - } - } catch (error) { - // 가입 실패시 가입 카운트를 감소시킴. - await redisClient.decr(countKeyPattern); +exports.listCrewMembers = async (req, res) => { + try { + const {crewID} = req.params; + + // 해당 크루 정보 조회 + const crew = await Crew.findByPk(crewID, { + attributes: ['crewID', 'name'], // 필요한 크루 정보만 가져옴 + }); - console.error('크루 가입 중 오류:', error); + if (!crew) { + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); + } + + // 해당 크루에 가입된 유저 목록과 역할 정보 조회 + const users = await User.findAll({ + include: [ + { + model: UserCrew, + as: 'userCrews', // 관계에 대해 명시적으로 선언 + where: {crewID}, + attributes: ['role'], // UserCrew의 역할 정보 포함 + }, + ], + attributes: ['userID', 'name', 'email'], // 유저 정보만 반환 + }); + + // 역할 정보를 포함한 멤버 리스트 생성 + const members = users.map((user) => { + const {userID, name, email} = user; + const role = user.userCrews[0]?.role || 'Unknown'; // UserCrew 테이블에서 역할 정보 가져오기 + return {userID, name, email, role}; + }); - res.status(500).json({error: '크루 가입 중 오류가 발생했습니다.'}); - } + // 응답 데이터 구성 + const response = { + crewID: crew.crewID, + name: crew.name, + currentMemberCount: members.length, + members, + }; + + res.status(200).json(response); + } catch (error) { + console.error('크루 멤버 조회 중 오류:', error); + res.status(500).json({error: '크루 멤버 조회 중 오류가 발생했습니다.'}); + } }; -// 크루 탈퇴 컨트롤러 -exports.leaveCrew = async (req, res) => { - try { - const { crewID } = req.params; +// 크루 가입 컨트롤러 +exports.joinCrew = async (req, res) => { + const {crewID} = req.params; + const {role} = req.body; const userID = req.user.userID; if (!crewID) { - return res.status(400).json({ error: '필수 데이터가 누락되었습니다.' }); + return res.status(400).json({error: '필수 데이터가 누락되었습니다.'}); } const crew = await Crew.findByPk(crewID); if (!crew) { - return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' }); + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); } - const userCrew = await UserCrew.findOne({ where: { crewID, userID } }); - if (!userCrew) { - return res.status(404).json({ error: '해당 사용자가 크루에 가입되어 있지 않습니다.' }); + const existingUserCrew = await UserCrew.findOne({where: {crewID, userID}}); + if (existingUserCrew) { + return res.status(409).json({error: '이미 해당 크루에 가입되어 있습니다.'}); } - await userCrew.destroy(); - // 가입 인원 카운트 감소 - await redisClient.decr(countKeyPattern); + const capacity = parseInt(crew.capacity, 10); - res.status(200).json({}); - } catch (error) { - console.error('크루 탈퇴 중 오류:', error); - res.status(500).json({ error: '크루 탈퇴 중 오류가 발생했습니다.' }); - } + try { + const result = await redisClient.sendCommand([ + 'EVAL', + capCheckLuaScript, + '1', + countKeyPattern, + capacity.toString() + ]); + + // 인원이 초과하지 않은 경우, 가입 진행. + if (result === 1) { + const newUserCrew = await UserCrew.create({ + crewID, + userID, + role: role || 'General', + }); + + return res.status(201).json({ + crewID: newUserCrew.crewID, + userID: newUserCrew.userID, + role: newUserCrew.role, + }); + } else { + return res.status(409).json({error: '크루의 최대 인원 수를 초과했습니다.'}); + } + } catch (error) { + // 가입 실패시 가입 카운트를 감소시킴. + await redisClient.decr(countKeyPattern); + + console.error('크루 가입 중 오류:', error); + + res.status(500).json({error: '크루 가입 중 오류가 발생했습니다.'}); + } }; -// 크루 조회 컨트롤러 + 가입한 인원 필드 추가 -exports.getCrew = async (req, res) => { - try { - const { crewID } = req.params; +// 크루 탈퇴 컨트롤러 +exports.leaveCrew = async (req, res) => { + try { + const {crewID} = req.params; - // crewID로 크루 정보 조회 - const crew = await Crew.findByPk(crewID); + const userID = req.user.userID; - if (!crew) { - return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' }); + if (!crewID) { + return res.status(400).json({error: '필수 데이터가 누락되었습니다.'}); + } + + const crew = await Crew.findByPk(crewID); + if (!crew) { + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); + } + + const userCrew = await UserCrew.findOne({where: {crewID, userID}}); + if (!userCrew) { + return res.status(404).json({error: '해당 사용자가 크루에 가입되어 있지 않습니다.'}); + } + + await userCrew.destroy(); + // 가입 인원 카운트 감소 + await redisClient.decr(countKeyPattern); + + res.status(200).json({}); + } catch (error) { + console.error('크루 탈퇴 중 오류:', error); + res.status(500).json({error: '크루 탈퇴 중 오류가 발생했습니다.'}); } +}; - let currentMemberCount; +// 크루 조회 컨트롤러 + 가입한 인원 필드 추가 +exports.getCrew = async (req, res) => { try { - const redisResult = await redisClient.get(countKeyPattern); - currentMemberCount = redisResult ? parseInt(redisResult, 10) : null; - } catch (error) { - console.error('크루 가입 수 조회중 오류 (Redis)', error); + const {crewID} = req.params; + + // crewID로 크루 정보 조회 + const crew = await Crew.findByPk(crewID); + + if (!crew) { + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); + } + + let currentMemberCount; + try { + const redisResult = await redisClient.get(countKeyPattern); + currentMemberCount = redisResult ? parseInt(redisResult, 10) : null; + } catch (error) { + console.error('크루 가입 수 조회중 오류 (Redis)', error); + + // Redis 조회 실패 시 DB에서 직접 카운트 조회 + currentMemberCount = await UserCrew.count({ + where: {crewID}, + }); + } + + // 크루 데이터에 currentMemberCount 추가 + const response = { + ...crew.toJSON(), // 크루 데이터를 JSON 형식으로 변환 + currentMemberCount, // 현재 가입한 인원 수 + }; - // Redis 조회 실패 시 DB에서 직접 카운트 조회 - currentMemberCount = await UserCrew.count({ - where: { crewID }, - }); + res.status(200).json(response); + } catch (error) { + console.error('크루 조회 중 오류:', error); + res.status(500).json({error: '크루 조회 중 오류가 발생했습니다.'}); } - - // 크루 데이터에 currentMemberCount 추가 - const response = { - ...crew.toJSON(), // 크루 데이터를 JSON 형식으로 변환 - currentMemberCount, // 현재 가입한 인원 수 - }; - - res.status(200).json(response); - } catch (error) { - console.error('크루 조회 중 오류:', error); - res.status(500).json({ error: '크루 조회 중 오류가 발생했습니다.' }); - } }; // 크루 목록 조회 컨트롤러 exports.getCrews = async (req, res) => { - try { - const { regionID, sportTypeId, page = 1 } = req.query; - const offset = (page - 1) * itemsPerPage; - - const where = {}; - if (regionID) where.regionID = regionID; - if (sportTypeId) where.sportTypeId = sportTypeId; - - const { rows: crews, count: total } = await Crew.findAndCountAll({ - where, - limit: itemsPerPage, - offset, - }); - - res.status(200).json({ - crews, - total, - itemsPerPage, - }); - } catch (error) { - console.error('크루 조회 중 오류:', error); - res.status(500).json({ error: '크루 조회 중 오류가 발생했습니다.' }); - } + try { + const {regionID, sportTypeId, page = 1} = req.query; + const offset = (page - 1) * itemsPerPage; + + const where = {}; + if (regionID) where.regionID = regionID; + if (sportTypeId) where.sportTypeId = sportTypeId; + + const {rows: crews, count: total} = await Crew.findAndCountAll({ + where, + limit: itemsPerPage, + offset, + }); + + res.status(200).json({ + crews, + total, + itemsPerPage, + }); + } catch (error) { + console.error('크루 조회 중 오류:', error); + res.status(500).json({error: '크루 조회 중 오류가 발생했습니다.'}); + } }; // 크루 수정 컨트롤러 exports.updateCrew = async (req, res) => { - try { - const { crewID } = req.params; - const { name, capacity, fee_krw, description } = req.body; - const userID = req.user.userID; // 현재 사용자 ID 가져오기 + try { + const {crewID} = req.params; + const {name, capacity, fee_krw, description} = req.body; + const userID = req.user.userID; // 현재 사용자 ID 가져오기 + + if (!name || !capacity || !fee_krw || !description) { + return res.status(400).json({error: '모든 필드가 필수입니다.'}); + } + + // 크루 조회 + const crew = await Crew.findByPk(crewID); + if (!crew) { + return res.status(404).json({error: '해당 크루를 찾을 수 없습니다.'}); + } + + // 사용자 역할 확인 + const userCrew = await UserCrew.findOne({ + where: {crewID, userID}, + }); - if (!name || !capacity || !fee_krw || !description) { - return res.status(400).json({ error: '모든 필드가 필수입니다.' }); - } + if (!userCrew || userCrew.role !== 'Leader') { + return res.status(403).json({error: '수정 권한이 없습니다. (크루장이 아닙니다.)'}); + } - // 크루 조회 - const crew = await Crew.findByPk(crewID); - if (!crew) { - return res.status(404).json({ error: '해당 크루를 찾을 수 없습니다.' }); - } - - // 사용자 역할 확인 - const userCrew = await UserCrew.findOne({ - where: { crewID, userID }, - }); + // 크루 업데이트 + await crew.update({ + name, + capacity, + fee_krw, + description, + }); - if (!userCrew || userCrew.role !== 'Leader') { - return res.status(403).json({ error: '수정 권한이 없습니다. (크루장이 아닙니다.)' }); + res.status(200).json({updatedCrew: crew}); + } catch (error) { + console.error('크루 수정 중 오류:', error); + res.status(500).json({error: '크루 수정 중 오류가 발생했습니다.'}); } - - // 크루 업데이트 - await crew.update({ - name, - capacity, - fee_krw, - description, - }); - - res.status(200).json({ updatedCrew: crew }); - } catch (error) { - console.error('크루 수정 중 오류:', error); - res.status(500).json({ error: '크루 수정 중 오류가 발생했습니다.' }); - } }; // 크루 삭제 컨트롤러 exports.deleteCrew = async (req, res) => { - try { - const { crewID } = req.params; - const userID = req.user.userID; // 현재 사용자 ID 가져오기 + try { + const {crewID} = req.params; + const userID = req.user.userID; // 현재 사용자 ID 가져오기 + + // 크루 조회 + const crew = await Crew.findByPk(crewID); + if (!crew) { + return res.status(404).json({error: '해당 크루를 찾을 수 없습니다.'}); + } + + // 사용자 역할 확인 + const userCrew = await UserCrew.findOne({ + where: {crewID, userID}, + }); - // 크루 조회 - const crew = await Crew.findByPk(crewID); - if (!crew) { - return res.status(404).json({ error: '해당 크루를 찾을 수 없습니다.' }); - } + if (!userCrew || userCrew.role !== 'Leader') { + return res.status(403).json({error: '삭제 권한이 없습니다. (크루장이 아닙니다.)'}); + } - // 사용자 역할 확인 - const userCrew = await UserCrew.findOne({ - where: { crewID, userID }, - }); + // 크루 삭제 + await crew.destroy(); - if (!userCrew || userCrew.role !== 'Leader') { - return res.status(403).json({ error: '삭제 권한이 없습니다. (크루장이 아닙니다.)' }); + res.status(200).json({message: '크루가 성공적으로 삭제되었습니다.'}); + } catch (error) { + console.error('크루 삭제 중 오류:', error); + res.status(500).json({error: '크루 삭제 중 오류가 발생했습니다.'}); } - - // 크루 삭제 - await crew.destroy(); - - res.status(200).json({ message: '크루가 성공적으로 삭제되었습니다.' }); - } catch (error) { - console.error('크루 삭제 중 오류:', error); - res.status(500).json({ error: '크루 삭제 중 오류가 발생했습니다.' }); - } }; // 특정 크루의 이벤트 조회 exports.getEventsByCrew = async (req, res) => { - try { - const { crewID } = req.params; - const { page = 1 } = req.query; - const offset = (page - 1) * itemsPerPage; - - // 크루 정보 확인 - const crew = await Crew.findByPk(crewID); - if (!crew) { - return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' }); - } + try { + const {crewID} = req.params; + const {page = 1} = req.query; + const offset = (page - 1) * itemsPerPage; + + // 크루 정보 확인 + const crew = await Crew.findByPk(crewID); + if (!crew) { + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); + } + + // 이벤트 목록 및 총 개수 조회 + const {rows: events, count: total} = await Event.findAndCountAll({ + where: {crewID}, + limit: itemsPerPage, + offset, + attributes: [ + 'eventID', + 'regionID', + 'name', + 'sportTypeId', + 'eventDate', + 'capacity', + 'feeCondition', + 'userID', + 'createdDate', + ], + }); - // 이벤트 목록 및 총 개수 조회 - const { rows: events, count: total } = await Event.findAndCountAll({ - where: { crewID }, - limit: itemsPerPage, - offset, - attributes: [ - 'eventID', - 'regionID', - 'name', - 'sportTypeId', - 'eventDate', - 'capacity', - 'feeCondition', - 'userID', - 'createdDate', - ], - }); - - // 각 이벤트에 현재 참여한 인원 수 추가 - const eventsWithMemberCount = await Promise.all( - events.map(async (event) => { - const currentMemberCount = await UserEvent.count({ - where: { eventID: event.eventID }, + // 각 이벤트에 현재 참여한 인원 수 추가 + const eventsWithMemberCount = await Promise.all( + events.map(async (event) => { + const currentMemberCount = await UserEvent.count({ + where: {eventID: event.eventID}, + }); + return { + ...event.toJSON(), + currentMemberCount, // 현재 참여한 인원 수 + }; + }) + ); + + // 응답 데이터 생성 + res.status(200).json({ + events: eventsWithMemberCount, + total, + per_page: itemsPerPage, }); - return { - ...event.toJSON(), - currentMemberCount, // 현재 참여한 인원 수 - }; - }) - ); - - // 응답 데이터 생성 - res.status(200).json({ - events: eventsWithMemberCount, - total, - per_page: itemsPerPage, - }); - } catch (error) { - console.error('특정 크루의 이벤트 조회 중 오류:', error); - res.status(500).json({ error: '특정 크루의 이벤트 조회 중 오류가 발생했습니다.' }); - } + } catch (error) { + console.error('특정 크루의 이벤트 조회 중 오류:', error); + res.status(500).json({error: '특정 크루의 이벤트 조회 중 오류가 발생했습니다.'}); + } }; // 특정 크루에 가입한 유저 조회 exports.getCrewMembers = async (req, res) => { - try { - const { crewID } = req.params; + try { + const {crewID} = req.params; - // 해당 크루 정보 조회 - const crew = await Crew.findByPk(crewID, { - attributes: ['crewID', 'name'], // 필요한 크루 정보만 가져옴 - }); + // 해당 크루 정보 조회 + const crew = await Crew.findByPk(crewID, { + attributes: ['crewID', 'name'], // 필요한 크루 정보만 가져옴 + }); - if (!crew) { - return res.status(404).json({ error: '해당 크루가 존재하지 않습니다.' }); - } + if (!crew) { + return res.status(404).json({error: '해당 크루가 존재하지 않습니다.'}); + } + + // 해당 크루에 가입된 유저 목록과 역할 정보 조회 + const users = await User.findAll({ + include: [ + { + model: UserCrew, + as: 'userCrews', // 관계에 대해 명시적으로 선언 + where: {crewID}, + attributes: ['role'], // UserCrew의 역할 정보 포함 + }, + ], + attributes: ['userID', 'name', 'email'], // 유저 정보만 반환 + }); - // 해당 크루에 가입된 유저 목록과 역할 정보 조회 - const users = await User.findAll({ - include: [ - { - model: UserCrew, - as: 'userCrews', // 관계에 대해 명시적으로 선언 - where: { crewID }, - attributes: ['role'], // UserCrew의 역할 정보 포함 - }, - ], - attributes: ['userID', 'name', 'email'], // 유저 정보만 반환 - }); - - // 역할 정보를 포함한 멤버 리스트 생성 - const members = users.map((user) => { - const { userID, name, email } = user; - const role = user.userCrews[0]?.role || 'Unknown'; // UserCrew 테이블에서 역할 정보 가져오기 - return { userID, name, email, role }; - }); - - // 응답 데이터 구성 - const response = { - crewID: crew.crewID, - name: crew.name, - currentMemberCount: members.length, - members, - }; - - res.status(200).json(response); - } catch (error) { - console.error('크루 멤버 조회 중 오류:', error); - res.status(500).json({ error: '크루 멤버 조회 중 오류가 발생했습니다.' }); - } + // 역할 정보를 포함한 멤버 리스트 생성 + const members = users.map((user) => { + const {userID, name, email} = user; + const role = user.userCrews[0]?.role || 'Unknown'; // UserCrew 테이블에서 역할 정보 가져오기 + return {userID, name, email, role}; + }); + + // 응답 데이터 구성 + const response = { + crewID: crew.crewID, + name: crew.name, + currentMemberCount: members.length, + members, + }; + + res.status(200).json(response); + } catch (error) { + console.error('크루 멤버 조회 중 오류:', error); + res.status(500).json({error: '크루 멤버 조회 중 오류가 발생했습니다.'}); + } }; diff --git a/webapp/backend/apiserver/routes/crew.js b/webapp/backend/apiserver/routes/crew.js index 45507da..e7f13cc 100644 --- a/webapp/backend/apiserver/routes/crew.js +++ b/webapp/backend/apiserver/routes/crew.js @@ -25,6 +25,9 @@ router.get('/', crewController.getCrews); // 특정 크루의 이벤트 조회 API router.get('/:crewID/events', crewController.getEventsByCrew); +// 크루 멤버 조회 API +router.get('/:crewID/members', authMiddleware, crewController.listCrewMembers); + // 크루 멤버 추가 (가입) API router.post('/:crewID/members', authMiddleware, crewController.joinCrew); -- GitLab