커뮤니티 가입/탈퇴하기 기능 구현
파일 구조
- community.controller.js
- community.service.js
- community.repository.js
/**
* @swagger
* /api/community/type/join:
* post:
* summary: 커뮤니티 가입 또는 탈퇴
* description: 로그인된 사용자가 특정 커뮤니티에 가입하거나 탈퇴합니다. 가입 시 프로필 타입(BASIC/MULTI) 선택 가능하며, MULTI 선택 시 멀티 프로필을 즉시 생성합니다.
* tags: [Community]
* security: [{ bearerAuth: [] }]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required: [communityId, action]
* properties:
* communityId: { type: integer, example: 3 }
* action:
* type: string
* enum: [join, leave]
* example: join
* profileType:
* type: string
* description: 가입 시 사용할 프로필 타입 (join일 때만 사용)
* enum: [BASIC, MULTI]
* example: BASIC
* multi:
* type: object
* nullable: true
* description: profileType=MULTI일 때 생성할 멀티 프로필 정보
* properties:
* nickname: { type: string, example: "뮤지컬덕후" }
* image: { type: string, nullable: true, example: "https://example.com/image.png" }
* bio: { type: string, nullable: true, example: "배우 덕질은 삶의 활력" }
* responses:
* 200:
* description: 처리 성공
* content:
* application/json:
* schema:
* type: object
* properties:
* success: { type: boolean, example: true }
* message: { type: string, example: "커뮤니티 가입 완료" }
* 400:
* description: 잘못된 요청 또는 처리 실패
*/
router.post("/type/join", authenticateJWT, async (req, res) => {
try {
const userId = req.user?.id;
const { communityId, action, profileType, multi } = req.body;
if (!userId || !communityId || !["join", "leave"].includes(action)) {
return res.status(400).json({
success: false,
message: "userId, communityId, action(join/leave)을 확인하세요.",
});
}
const message = await handleJoinOrLeaveCommunity(
userId,
Number(communityId),
action,
profileType,
multi
);
res.status(200).json({ success: true, message });
} catch (error) {
res.status(400).json({ success: false, message: error.message });
}
});
export const handleJoinOrLeaveCommunity = async (
userId,
communityId,
action,
profileType,
multi
) => {
const isJoined = await checkUserInCommunity(userId, communityId);
if (action === "join") {
if (isJoined) throw new Error("이미 가입된 커뮤니티입니다.");
await prisma.$transaction(async (tx) => {
await insertUserToCommunity(userId, communityId, tx);
if (profileType === "MULTI") {
const dup = await findMultiProfile(communityId, userId, tx);
if (dup) throw new Error("이미 해당 커뮤니티에 멀티프로필이 있습니다.");
const ok = await canCreateAnotherMulti(userId);
if (!ok)
throw new Error(
"무료 회원은 멀티프로필을 5개까지 생성할 수 있습니다."
);
await createCommunityProfileRepository(
{
userId,
communityId,
nickname: multi?.nickname ?? "",
image: multi?.image ?? null,
bio: multi?.bio ?? null,
},
tx
);
}
});
return "커뮤니티 가입 완료";
}
if (action === "leave") {
if (!isJoined) throw new Error("가입되지 않은 커뮤니티입니다.");
await prisma.$transaction(async (tx) => {
const mp = await findMultiProfile(communityId, userId, tx);
if (mp) await deleteCommunityProfileRepository(mp.id, tx);
await deleteUserFromCommunity(userId, communityId, tx);
});
return "커뮤니티 탈퇴 완료";
}
throw new Error("유효하지 않은 요청입니다.");
};
// 커뮤니티별 프로필 타입 전환
export const switchCommunityProfileType = async ({
userId,
communityId,
profileType,
multi,
}) => {
return await prisma.$transaction(async (tx) => {
const current = await findMultiProfile(communityId, userId, tx);
const isMulti = !!current;
if (profileType === "BASIC") {
if (isMulti) await deleteCommunityProfileRepository(current.id, tx);
return { changedTo: "BASIC" };
}
if (profileType === "MULTI") {
if (isMulti) throw new Error("이미 멀티프로필을 사용 중입니다.");
const ok = await canCreateAnotherMulti(userId);
if (!ok)
throw new Error("무료 회원은 멀티프로필을 5개까지 생성할 수 있습니다.");
const created = await createCommunityProfileRepository(
{
userId,
communityId,
nickname: multi?.nickname ?? "",
image: multi?.image ?? null,
bio: multi?.bio ?? null,
},
tx
);
return { changedTo: "MULTI", profile: created };
}
throw new Error("profileType은 BASIC 또는 MULTI여야 합니다.");
});
};
export const checkUserInCommunity = async (
userId,
communityId,
db = prisma
) => {
const record = await db.userCommunity.findFirst({
where: {
userId,
communityId,
},
});
return !!record;
};
돌아가기: 기능별 API 구현 내용 보러가기