1
1
package org .orury .client .crew .application ;
2
2
3
3
import lombok .RequiredArgsConstructor ;
4
+ import org .apache .logging .log4j .util .Strings ;
5
+ import org .orury .client .global .image .ImageAsyncStore ;
4
6
import org .orury .common .error .code .CrewErrorCode ;
5
7
import org .orury .common .error .exception .BusinessException ;
8
+ import org .orury .common .util .AgeUtils ;
6
9
import org .orury .domain .crew .domain .*;
7
10
import org .orury .domain .crew .domain .dto .CrewDto ;
11
+ import org .orury .domain .crew .domain .dto .CrewGender ;
8
12
import org .orury .domain .crew .domain .entity .Crew ;
13
+ import org .orury .domain .crew .domain .entity .CrewApplication ;
9
14
import org .orury .domain .crew .domain .entity .CrewMember ;
10
15
import org .orury .domain .crew .domain .entity .CrewMemberPK ;
11
16
import org .orury .domain .global .constants .NumberConstants ;
12
17
import org .orury .domain .global .image .ImageStore ;
18
+ import org .orury .domain .meeting .domain .MeetingMemberStore ;
19
+ import org .orury .domain .meeting .domain .MeetingStore ;
13
20
import org .orury .domain .user .domain .UserReader ;
14
21
import org .orury .domain .user .domain .dto .UserDto ;
15
22
import org .orury .domain .user .domain .entity .User ;
19
26
import org .springframework .transaction .annotation .Transactional ;
20
27
import org .springframework .web .multipart .MultipartFile ;
21
28
29
+ import java .time .LocalDate ;
22
30
import java .util .LinkedList ;
23
31
import java .util .List ;
24
32
import java .util .Objects ;
@@ -34,8 +42,13 @@ public class CrewServiceImpl implements CrewService {
34
42
private final CrewTagStore crewTagStore ;
35
43
private final CrewMemberReader crewMemberReader ;
36
44
private final CrewMemberStore crewMemberStore ;
45
+ private final CrewApplicationReader crewApplicationReader ;
46
+ private final CrewApplicationStore crewApplicationStore ;
47
+ private final MeetingStore meetingStore ;
48
+ private final MeetingMemberStore meetingMemberStore ;
37
49
private final UserReader userReader ;
38
50
private final ImageStore imageStore ;
51
+ private final ImageAsyncStore imageAsyncStore ;
39
52
40
53
41
54
@ Override
@@ -51,7 +64,7 @@ public CrewDto getCrewDtoById(Long crewId) {
51
64
@ Transactional
52
65
public void createCrew (CrewDto crewDto , MultipartFile file ) {
53
66
validateCrewParticipationCount (crewDto .userDto ().id ());
54
- var icon = imageStore .upload (CREW , file );
67
+ var icon = imageAsyncStore .upload (CREW , file );
55
68
Crew crew = crewStore .save (crewDto .toEntity (icon ));
56
69
crewTagStore .addTags (crew , crewDto .tags ());
57
70
crewMemberStore .addCrewMember (crew .getId (), crew .getUser ().getId ());
@@ -96,27 +109,203 @@ public List<String> getUserImagesByCrew(CrewDto crewDto) {
96
109
@ Override
97
110
@ Transactional (readOnly = true )
98
111
public boolean existCrewMember (CrewMemberPK crewMemberPK ) {
99
- return crewReader .existCrewMember (crewMemberPK );
112
+ return crewMemberReader .existsByCrewMemberPK (crewMemberPK );
113
+ }
114
+
115
+ @ Override
116
+ @ Transactional
117
+ public void updateCrewInfo (CrewDto oldCrew , CrewDto newCrew , Long userId ) {
118
+ validateCrewCreator (oldCrew .userDto ().id (), userId );
119
+ if (newCrew .capacity () < oldCrew .memberCount ())
120
+ throw new BusinessException (CrewErrorCode .MEMBER_OVERFLOW );
121
+ validateAgeOfMembers (oldCrew , newCrew ); // TODO: 최고연령 변경 안 해도, 새해가 됨에 따라 연령 걸리는 멤버도 존재할듯..?
122
+ validateGenderOfMembers (oldCrew , newCrew );
123
+ validatePermissionRequired (oldCrew , newCrew );
124
+
125
+ Crew crew = crewStore .save (newCrew .toEntity ());
126
+ crewTagStore .updateTags (oldCrew .tags (), newCrew .tags (), crew );
100
127
}
101
128
102
129
@ Override
103
130
@ Transactional
104
131
public void updateCrewImage (CrewDto crewDto , MultipartFile imageFile , Long userId ) {
105
132
validateCrewCreator (crewDto .userDto ().id (), userId );
106
133
var oldImage = crewDto .icon ();
107
- var newImage = imageStore .upload (CREW , imageFile );
134
+ var newImage = imageAsyncStore .upload (CREW , imageFile );
108
135
crewStore .save (crewDto .toEntity (newImage ));
109
136
imageStore .delete (CREW , oldImage );
110
137
}
111
138
139
+ @ Override
140
+ @ Transactional
141
+ public void deleteCrew (CrewDto crewDto , Long userId ) {
142
+ validateCrewCreator (crewDto .userDto ().id (), userId );
143
+ crewStore .delete (crewDto .toEntity ());
144
+ imageStore .delete (CREW , crewDto .icon ());
145
+ }
146
+
147
+ @ Override
148
+ @ Transactional
149
+ public void applyCrew (CrewDto crewDto , UserDto userDto , String answer ) {
150
+ // 이미 크루원인 경우
151
+ if (crewMemberReader .existsByCrewIdAndUserId (crewDto .id (), userDto .id ()))
152
+ throw new BusinessException (CrewErrorCode .ALREADY_MEMBER );
153
+
154
+ // 크루 참여 검증 (멤버로 참여한 크루 + 신청한 크루 <= 5)
155
+ validateCrewParticipationCount (crewDto .userDto ().id ());
156
+
157
+ // 지원하는 크루 연령기준에 만족하지 않는 경우
158
+ if (!meetAgeCriteria (userDto .birthday (), crewDto ))
159
+ throw new BusinessException (CrewErrorCode .AGE_FORBIDDEN );
160
+ // 지원하는 크루 성별기준에 만족하지 않는 경우
161
+ if (!meetGenderCriteria (userDto .gender (), crewDto ))
162
+ throw new BusinessException (CrewErrorCode .GENDER_FORBIDDEN );
163
+
164
+ // 지원하는 크루가 즉시 가입인 경우
165
+ if (!crewDto .permissionRequired ()) {
166
+ crewMemberStore .addCrewMember (crewDto .id (), userDto .id ());
167
+ return ;
168
+ }
169
+
170
+ // 지원하는 크루가 답변이 필수인 경우 && 제출한 답변이 비어있는 경우
171
+ if (crewDto .answerRequired () && Strings .isBlank (answer ))
172
+ throw new BusinessException (CrewErrorCode .EMPTY_ANSWER );
173
+
174
+ // 크루 가입신청 저장
175
+ crewApplicationStore .save (crewDto , userDto , answer );
176
+ }
177
+
178
+ @ Override
179
+ @ Transactional
180
+ public void withdrawApplication (CrewDto crewDto , Long userId ) {
181
+ validateApplication (crewDto .id (), userId );
182
+ crewApplicationStore .delete (crewDto .id (), userId );
183
+ }
184
+
185
+ @ Override
186
+ @ Transactional
187
+ public void approveApplication (CrewDto crewDto , Long applicantId , Long userId ) {
188
+ validateCrewCreator (crewDto .userDto ().id (), userId );
189
+ validateApplication (crewDto .id (), applicantId );
190
+ crewApplicationStore .approve (crewDto .id (), applicantId );
191
+ }
192
+
193
+ @ Override
194
+ @ Transactional
195
+ public void disapproveApplication (CrewDto crewDto , Long applicantId , Long userId ) {
196
+ validateCrewCreator (crewDto .userDto ().id (), userId );
197
+ validateApplication (crewDto .id (), applicantId );
198
+ crewApplicationStore .delete (crewDto .id (), applicantId );
199
+ }
200
+
201
+ @ Override
202
+ @ Transactional
203
+ public void leaveCrew (CrewDto crewDto , Long userId ) {
204
+ if (Objects .equals (crewDto .userDto ().id (), userId ))
205
+ throw new BusinessException (CrewErrorCode .CREATOR_DELETE_FORBIDDEN );
206
+ removeMember (crewDto .id (), userId );
207
+ }
208
+
209
+ @ Override
210
+ @ Transactional
211
+ public void expelMember (CrewDto crewDto , Long memberId , Long userId ) {
212
+ validateCrewCreator (crewDto .userDto ().id (), userId );
213
+ if (Objects .equals (crewDto .userDto ().id (), memberId ))
214
+ throw new BusinessException (CrewErrorCode .CREATOR_DELETE_FORBIDDEN );
215
+ removeMember (crewDto .id (), memberId );
216
+ }
217
+
218
+ private void removeMember (Long crewId , Long memberId ) {
219
+ validateCrewMember (crewId , memberId );
220
+
221
+ meetingMemberStore .removeAllByUserIdAndCrewId (memberId , crewId );
222
+ meetingStore .deleteAllByUserIdAndCrewId (memberId , crewId );
223
+
224
+ crewMemberStore .subtractCrewMember (crewId , memberId );
225
+ }
226
+
227
+ private boolean meetAgeCriteria (LocalDate userBirthday , CrewDto crewDto ) {
228
+ int age = AgeUtils .calculateAge (userBirthday );
229
+ return AgeUtils .isInRange (age , crewDto .minAge (), crewDto .maxAge ());
230
+ }
231
+
232
+ private boolean meetGenderCriteria (int userGender , CrewDto crewDto ) {
233
+ return crewDto .gender ().getUserCode () == userGender || crewDto .gender () == CrewGender .ANY ;
234
+ }
235
+
112
236
private void validateCrewCreator (Long crewCreatorId , Long userId ) {
113
237
if (!Objects .equals (crewCreatorId , userId ))
114
238
throw new BusinessException (CrewErrorCode .FORBIDDEN );
115
239
}
116
240
241
+ private void validateCrewMember (Long crewId , Long userId ) {
242
+ if (!crewMemberReader .existsByCrewIdAndUserId (crewId , userId ))
243
+ throw new BusinessException (CrewErrorCode .NOT_CREW_MEMBER );
244
+ }
245
+
246
+ private void validateApplication (Long crewId , Long applicantId ) {
247
+ if (!crewApplicationReader .existsByCrewIdAndUserId (crewId , applicantId ))
248
+ throw new BusinessException (CrewErrorCode .NOT_FOUND_APPLICATION );
249
+ }
250
+
117
251
private void validateCrewParticipationCount (Long userId ) {
118
- int participationCount = crewMemberReader .countByUserId (userId );
119
- if (NumberConstants .MAXIMUM_CREW_PARTICIPATION <= participationCount )
252
+ int participatingCrewCount = crewMemberReader .countByUserId (userId );
253
+ int applyingCrewCount = crewApplicationReader .countByUserId (userId );
254
+ if (NumberConstants .MAXIMUM_CREW_PARTICIPATION <= participatingCrewCount + applyingCrewCount )
120
255
throw new BusinessException (CrewErrorCode .MAXIMUM_PARTICIPATION );
121
256
}
257
+
258
+ private void validateAgeOfMembers (CrewDto oldCrew , CrewDto newCrew ) {
259
+ if (newCrew .minAge () <= oldCrew .minAge () && oldCrew .maxAge () <= newCrew .maxAge ())
260
+ return ;
261
+
262
+ List <User > members = crewMemberReader .getMembersByCrewId (oldCrew .id ());
263
+ boolean outOfAgeRange = members .stream ()
264
+ .map (User ::getBirthday )
265
+ .anyMatch (birthday -> !meetAgeCriteria (birthday , newCrew ));
266
+ if (outOfAgeRange ) throw new BusinessException (CrewErrorCode .AGE_FORBIDDEN );
267
+ }
268
+
269
+ private void validateGenderOfMembers (CrewDto oldCrew , CrewDto newCrew ) {
270
+ if (oldCrew .gender () == newCrew .gender () || newCrew .gender () == CrewGender .ANY )
271
+ return ;
272
+
273
+ List <User > members = crewMemberReader .getMembersByCrewId (oldCrew .id ());
274
+ boolean differentGender = members .stream ()
275
+ .map (User ::getGender )
276
+ .anyMatch (gender -> !meetGenderCriteria (gender , newCrew ));
277
+ if (differentGender ) throw new BusinessException (CrewErrorCode .GENDER_FORBIDDEN );
278
+ }
279
+
280
+ private void validatePermissionRequired (CrewDto oldCrew , CrewDto newCrew ) {
281
+ // 크루 PermissionRequired가 변경되지 않는 경우 || PermitionRequired false -> true 로 변경되는 경우
282
+ if (oldCrew .permissionRequired () == newCrew .permissionRequired () || newCrew .permissionRequired ())
283
+ return ;
284
+
285
+ // 현재 크루에 지원정보 모두 가져옴.
286
+ List <CrewApplication > applications = crewApplicationReader .findAllByCrewId (oldCrew .id ());
287
+
288
+ // 유효한 지원정보 필터링
289
+ List <CrewApplication > validApplications = applications .stream ()
290
+ .filter (crewApplication -> {
291
+ User user = userReader .getUserById (crewApplication .getCrewApplicationPK ().getUserId ());
292
+ return meetAgeCriteria (user .getBirthday (), newCrew )
293
+ && meetGenderCriteria (user .getGender (), newCrew );
294
+ }).toList ();
295
+
296
+ // 변경하고자하는 정원보다 '크루멤버 수 + 유효한 지원자 수'가 더 큰 경우
297
+ if (newCrew .capacity () < oldCrew .memberCount () + validApplications .size ())
298
+ throw new BusinessException (CrewErrorCode .APPLICATION_OVERFLOW );
299
+
300
+ // 유효한 지원자들에 대해 승인
301
+ validApplications .stream ()
302
+ .map (CrewApplication ::getCrewApplicationPK )
303
+ .forEach (crewApplicationStore ::approve );
304
+
305
+ // 유효하지 않은 지원자들에 대해 거절
306
+ applications .removeAll (validApplications );
307
+ applications .stream ()
308
+ .map (CrewApplication ::getCrewApplicationPK )
309
+ .forEach (crewApplicationStore ::delete );
310
+ }
122
311
}
0 commit comments