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