Skip to content

Commit a686741

Browse files
authored
[FEAT] 모임 취소 구현
[FEAT] 모임 취소 구현
2 parents c789b0a + 68fd54f commit a686741

File tree

4 files changed

+231
-3
lines changed

4 files changed

+231
-3
lines changed

src/main/java/team/wego/wegobackend/group/application/service/GroupService.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,37 @@ public GetGroupResponse attendGroup(Long groupId, Long memberId) {
244244
return buildGetGroupResponse(group, memberId);
245245
}
246246

247+
@Transactional
248+
public GetGroupResponse cancelAttendGroup(Long groupId, Long memberId) {
249+
// 0. Group 조회 (soft delete 고려)
250+
Group group = groupRepository.findByIdAndDeletedAtIsNull(groupId)
251+
.orElseThrow(() -> new GroupException(GroupErrorCode.GROUP_NOT_FOUND_BY_ID, groupId));
252+
253+
// 1. member 조회
254+
User member = userRepository.findById(memberId)
255+
.orElseThrow(() -> new GroupException(GroupErrorCode.MEMBER_NOT_FOUND, memberId));
256+
257+
// 2. GroupUser 찾기
258+
GroupUser groupUser = groupUserRepository.findByGroupAndUser(group, member)
259+
.orElseThrow(() -> new GroupException(GroupErrorCode.NOT_ATTEND_GROUP, groupId, memberId));
260+
261+
// 3. HOST는 나갈 수 없음
262+
if (groupUser.getGroupRole() == GroupRole.HOST) {
263+
throw new GroupException(GroupErrorCode.HOST_CANNOT_LEAVE_OWN_GROUP, groupId, memberId);
264+
}
265+
266+
// 4. 이미 나간 상태면 예외
267+
if (groupUser.getStatus() == LEFT) {
268+
throw new GroupException(GroupErrorCode.NOT_ATTEND_GROUP, groupId, memberId);
269+
}
270+
271+
// 5. 참여 취소 (leave)
272+
groupUser.leave();
273+
274+
// 6. 최신 모임 상세 응답 반환
275+
return buildGetGroupResponse(group, memberId);
276+
}
277+
247278

248279
private GetGroupResponse buildGetGroupResponse(Group group, Long currentUserId) {
249280
// 이미지 URL (sortOrder 기준 정렬)

src/main/java/team/wego/wegobackend/group/domain/exception/GroupErrorCode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ public enum GroupErrorCode implements ErrorCode {
1919

2020
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "모임: 회원을 찾을 수 없습니다. 회원 ID: %s"),
2121
ALREADY_ATTEND_GROUP(HttpStatus.BAD_REQUEST, "모임: 이미 참여 중인 모임입니다. 모임 ID: %s 회원 ID: %s"),
22-
GROUP_CAPACITY_EXCEEDED(HttpStatus.BAD_REQUEST, "모임: 모임 최대 참가자 수를 초과했습니다. 모임 ID: %s");
22+
GROUP_CAPACITY_EXCEEDED(HttpStatus.BAD_REQUEST, "모임: 모임 최대 참가자 수를 초과했습니다. 모임 ID: %s"),
23+
NOT_ATTEND_GROUP(HttpStatus.BAD_REQUEST, "모임: 참여한 적 없거나 이미 나간 상태입니다. 모임 ID: %s 회원 ID: %s"),
24+
HOST_CANNOT_LEAVE_OWN_GROUP(HttpStatus.BAD_REQUEST, "모임: HOST는 나갈 수 없습니다. 모임 ID: %s 회원 ID: %s");
2325

2426
private final HttpStatus status;
2527
private final String message;

src/main/java/team/wego/wegobackend/group/presentation/GroupController.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,25 @@ public ResponseEntity<ApiResponse<GetGroupResponse>> attendGroup(
4747
) {
4848
GetGroupResponse response = groupService.attendGroup(groupId, userId);
4949

50-
return ResponseEntity
50+
return ResponseEntity
5151
.status(HttpStatus.OK)
5252
.body(ApiResponse.success(
53-
HttpStatus.OK.value(),response));
53+
HttpStatus.OK.value(), response));
5454
}
5555

56+
5657
// 모임 참여 취소
58+
@PostMapping("/{groupId}/cancel")
59+
public ResponseEntity<ApiResponse<GetGroupResponse>> cancelAttendGroup(
60+
@PathVariable Long groupId,
61+
@RequestParam Long userId // TODO: 나중에 인증 정보에서 꺼내기
62+
) {
63+
GetGroupResponse response = groupService.cancelAttendGroup(groupId, userId);
64+
65+
return ResponseEntity
66+
.status(HttpStatus.OK)
67+
.body(ApiResponse.success(HttpStatus.OK.value(), response));
68+
}
5769

5870
// 모임 상세 조회
5971
@PostMapping("/{groupId}")

src/test/http/group/cancel.http

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
### 회원가입(HOST)
2+
POST http://localhost:8080/api/v1/auth/signup
3+
Content-Type: application/json
4+
5+
{
6+
"email": "[email protected]",
7+
"password": "Test1234!@#",
8+
"nickName": "Beemo",
9+
"phoneNumber": "010-1234-5678"
10+
}
11+
12+
> {%
13+
// 응답 구조에 따라 수정 필요
14+
// 예: { "userId": 1, ... } 라면 아래처럼 유지
15+
client.global.set("userId", response.body.data.userId);
16+
%}
17+
18+
### 로그인
19+
POST http://localhost:8080/api/v1/auth/login
20+
Content-Type: application/json
21+
22+
{
23+
"email": "[email protected]",
24+
"password": "Test1234!@#"
25+
}
26+
27+
> {%
28+
client.global.set("accessToken", response.body.data.accessToken);
29+
%}
30+
31+
### 1-1. 모임 이미지 선 업로드 (png / jpg 2장)
32+
POST http://localhost:8080/api/v1/groups/images/upload?userId={{userId}}
33+
Content-Type: multipart/form-data; boundary=boundary
34+
Authorization: Bearer {{accessToken}}
35+
36+
--boundary
37+
Content-Disposition: form-data; name="images"; filename="img1.png"
38+
Content-Type: image/png
39+
40+
< ../image/resources/img1.png
41+
--boundary
42+
Content-Disposition: form-data; name="images"; filename="img2.jpg"
43+
Content-Type: image/jpeg
44+
45+
< ../image/resources/img2.jpg
46+
--boundary--
47+
48+
> {%
49+
const images = response.body.data.images;
50+
51+
client.global.set("img0_main", images[0].imageUrl440x240);
52+
client.global.set("img0_thumb", images[0].imageUrl100x100);
53+
54+
client.global.set("img1_main", images[1].imageUrl440x240);
55+
client.global.set("img1_thumb", images[1].imageUrl100x100);
56+
%}
57+
58+
### 1-2. 모임 생성 (png/jpg 업로드 결과 URL로 생성)
59+
POST http://localhost:8080/api/v1/groups/create?userId={{userId}}
60+
Content-Type: application/json
61+
Authorization: Bearer {{accessToken}}
62+
63+
64+
{
65+
"title": "강남에서 하는 자바 스터디 - PNG/JPG 테스트",
66+
"location": "서울 강남구",
67+
"locationDetail": "강남역 2번 출구 근처 카페",
68+
"startTime": "2025-12-10T19:00:00",
69+
"tags": [
70+
"자바",
71+
"백엔드",
72+
"스터디"
73+
],
74+
"description": "PNG/JPG 업로드 후 URL을 이용해서 모임을 생성하는 테스트입니다.",
75+
"maxParticipants": 12,
76+
"images": [
77+
{
78+
"sortOrder": 0,
79+
"imageUrl440x240": "{{img0_main}}",
80+
"imageUrl100x100": "{{img0_thumb}}"
81+
},
82+
{
83+
"sortOrder": 1,
84+
"imageUrl440x240": "{{img1_main}}",
85+
"imageUrl100x100": "{{img1_thumb}}"
86+
}
87+
]
88+
}
89+
90+
> {%
91+
// 그룹 ID 저장 (응답 구조에 맞게 조정)
92+
client.global.set("groupId_png_jpg", response.body.data.id);
93+
%}
94+
95+
### 회원가입(MEMBER 1)
96+
POST http://localhost:8080/api/v1/auth/signup
97+
Content-Type: application/json
98+
99+
{
100+
"email": "[email protected]",
101+
"password": "Test1234!@#",
102+
"nickName": "Heemo",
103+
"phoneNumber": "010-1234-5678"
104+
}
105+
106+
> {%
107+
// 응답 구조에 따라 수정 필요
108+
// 예: { "userId": 1, ... } 라면 아래처럼 유지
109+
client.global.set("memberId1", response.body.data.userId);
110+
%}
111+
112+
### 로그인 (MEMBER 1)
113+
POST http://localhost:8080/api/v1/auth/login
114+
Content-Type: application/json
115+
116+
{
117+
"email": "[email protected]",
118+
"password": "Test1234!@#"
119+
}
120+
121+
> {%
122+
client.global.set("accessTokenByMember1", response.body.data.accessToken);
123+
%}
124+
125+
### 모임 참여 (MEMBER 1)
126+
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend
127+
?userId=2
128+
Content-Type: application/json
129+
Authorization: Bearer {{accessTokenByMember1}}
130+
131+
{
132+
133+
}
134+
135+
### 회원가입(MEMBER 2)
136+
POST http://localhost:8080/api/v1/auth/signup
137+
Content-Type: application/json
138+
139+
{
140+
"email": "[email protected]",
141+
"password": "Test1234!@#",
142+
"nickName": "Qeemo",
143+
"phoneNumber": "010-1234-5678"
144+
}
145+
146+
> {%
147+
// 응답 구조에 따라 수정 필요
148+
// 예: { "userId": 1, ... } 라면 아래처럼 유지
149+
client.global.set("memberId1", response.body.data.userId);
150+
%}
151+
152+
### 로그인 (MEMBER 2)
153+
POST http://localhost:8080/api/v1/auth/login
154+
Content-Type: application/json
155+
156+
{
157+
"email": "[email protected]",
158+
"password": "Test1234!@#"
159+
}
160+
161+
> {%
162+
client.global.set("accessTokenByMember2", response.body.data.accessToken);
163+
%}
164+
165+
### 모임 참여 (MEMBER 2)
166+
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/attend
167+
?userId=3
168+
Content-Type: application/json
169+
Authorization: Bearer {{accessTokenByMember2}}
170+
171+
{
172+
173+
}
174+
175+
### 모임 취소 (MEMBER 1)
176+
POST http://localhost:8080/api/v1/groups/{{groupId_png_jpg}}/cancel
177+
?userId=2
178+
Content-Type: application/json
179+
Authorization: Bearer {{accessTokenByMember1}}
180+
181+
{
182+
183+
}

0 commit comments

Comments
 (0)