Skip to content

Commit 22d63e0

Browse files
authored
Merge pull request Kernel360#386 from GBGreenBravo/feature/#383_Crew_APIs
Feat : 크루 관련 API 구현
2 parents 0205aef + 7d0e81c commit 22d63e0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3489
-61
lines changed

orury-client/src/main/java/org/orury/client/crew/application/CrewFacade.java

+45-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package org.orury.client.crew.application;
22

33
import lombok.RequiredArgsConstructor;
4-
import org.orury.client.crew.interfaces.request.CrewCreateRequest;
4+
import org.orury.client.crew.interfaces.request.CrewRequest;
55
import org.orury.client.crew.interfaces.response.CrewResponse;
66
import org.orury.client.crew.interfaces.response.CrewsResponse;
77
import org.orury.client.user.application.UserService;
88
import org.orury.domain.crew.domain.dto.CrewDto;
99
import org.orury.domain.crew.domain.entity.CrewMemberPK;
10+
import org.orury.domain.user.domain.dto.UserDto;
1011
import org.springframework.data.domain.Page;
1112
import org.springframework.data.domain.PageRequest;
1213
import org.springframework.stereotype.Component;
@@ -22,7 +23,7 @@ public class CrewFacade {
2223
private final CrewService crewService;
2324
private final UserService userService;
2425

25-
public void createCrew(CrewCreateRequest request, MultipartFile image, Long userId) {
26+
public void createCrew(CrewRequest request, MultipartFile image, Long userId) {
2627
var userDto = userService.getUserDtoById(userId);
2728
var crewDto = request.toDto(userDto);
2829
crewService.createCrew(crewDto, image);
@@ -55,11 +56,53 @@ public CrewResponse getCrewByCrewId(Long userId, Long crewId) {
5556
return CrewResponse.of(crewDto, isApply);
5657
}
5758

59+
public void updateCrewInfo(Long crewId, CrewRequest request, Long userId) {
60+
var oldCrewDto = crewService.getCrewDtoById(crewId);
61+
var newCrewDto = request.toDto(oldCrewDto);
62+
crewService.updateCrewInfo(oldCrewDto, newCrewDto, userId);
63+
}
64+
5865
public void updateCrewImage(Long crewId, MultipartFile image, Long userId) {
5966
CrewDto crewDto = crewService.getCrewDtoById(crewId);
6067
crewService.updateCrewImage(crewDto, image, userId);
6168
}
6269

70+
public void deleteCrew(Long crewId, Long userId) { // TODO: 삭제 유예(약 7일?)는 구현 필요.
71+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
72+
crewService.deleteCrew(crewDto, userId);
73+
}
74+
75+
public void applyCrew(Long crewId, Long userId, String answer) {
76+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
77+
UserDto userDto = userService.getUserDtoById(userId);
78+
crewService.applyCrew(crewDto, userDto, answer);
79+
}
80+
81+
public void withdrawApplication(Long crewId, Long userId) {
82+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
83+
crewService.withdrawApplication(crewDto, userId);
84+
}
85+
86+
public void approveApplication(Long crewId, Long applicantId, Long userId) {
87+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
88+
crewService.approveApplication(crewDto, applicantId, userId);
89+
}
90+
91+
public void disapproveApplication(Long crewId, Long applicantId, Long userId) {
92+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
93+
crewService.disapproveApplication(crewDto, applicantId, userId);
94+
}
95+
96+
public void leaveCrew(Long crewId, Long userId) {
97+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
98+
crewService.leaveCrew(crewDto, userId);
99+
}
100+
101+
public void expelMember(Long crewId, Long memberId, Long userId) {
102+
CrewDto crewDto = crewService.getCrewDtoById(crewId);
103+
crewService.expelMember(crewDto, memberId, userId);
104+
}
105+
63106
private Page<CrewsResponse> convertCrewDtosToCrewsResponses(Page<CrewDto> crewDtos) {
64107
return crewDtos.map(crewDto -> {
65108
List<String> userImages = crewService.getUserImagesByCrew(crewDto);

orury-client/src/main/java/org/orury/client/crew/application/CrewService.java

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.orury.domain.crew.domain.dto.CrewDto;
44
import org.orury.domain.crew.domain.entity.CrewMemberPK;
5+
import org.orury.domain.user.domain.dto.UserDto;
56
import org.springframework.data.domain.Page;
67
import org.springframework.data.domain.Pageable;
78
import org.springframework.web.multipart.MultipartFile;
@@ -23,5 +24,21 @@ public interface CrewService {
2324

2425
boolean existCrewMember(CrewMemberPK crewMemberPK);
2526

27+
void updateCrewInfo(CrewDto oldCrew, CrewDto newCrew, Long userId);
28+
2629
void updateCrewImage(CrewDto crewDto, MultipartFile image, Long userId);
30+
31+
void deleteCrew(CrewDto crewDto, Long userId);
32+
33+
void applyCrew(CrewDto crewDto, UserDto userDto, String answer);
34+
35+
void withdrawApplication(CrewDto crewDto, Long userId);
36+
37+
void approveApplication(CrewDto crewDto, Long applicantId, Long userId);
38+
39+
void disapproveApplication(CrewDto crewDto, Long applicantId, Long userId);
40+
41+
void leaveCrew(CrewDto crewDto, Long userId);
42+
43+
void expelMember(CrewDto crewDto, Long memberId, Long userId);
2744
}

orury-client/src/main/java/org/orury/client/crew/application/CrewServiceImpl.java

+194-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
package org.orury.client.crew.application;
22

33
import lombok.RequiredArgsConstructor;
4+
import org.apache.logging.log4j.util.Strings;
5+
import org.orury.client.global.image.ImageAsyncStore;
46
import org.orury.common.error.code.CrewErrorCode;
57
import org.orury.common.error.exception.BusinessException;
8+
import org.orury.common.util.AgeUtils;
69
import org.orury.domain.crew.domain.*;
710
import org.orury.domain.crew.domain.dto.CrewDto;
11+
import org.orury.domain.crew.domain.dto.CrewGender;
812
import org.orury.domain.crew.domain.entity.Crew;
13+
import org.orury.domain.crew.domain.entity.CrewApplication;
914
import org.orury.domain.crew.domain.entity.CrewMember;
1015
import org.orury.domain.crew.domain.entity.CrewMemberPK;
1116
import org.orury.domain.global.constants.NumberConstants;
1217
import org.orury.domain.global.image.ImageStore;
18+
import org.orury.domain.meeting.domain.MeetingMemberStore;
19+
import org.orury.domain.meeting.domain.MeetingStore;
1320
import org.orury.domain.user.domain.UserReader;
1421
import org.orury.domain.user.domain.dto.UserDto;
1522
import org.orury.domain.user.domain.entity.User;
@@ -19,6 +26,7 @@
1926
import org.springframework.transaction.annotation.Transactional;
2027
import org.springframework.web.multipart.MultipartFile;
2128

29+
import java.time.LocalDate;
2230
import java.util.LinkedList;
2331
import java.util.List;
2432
import java.util.Objects;
@@ -34,8 +42,13 @@ public class CrewServiceImpl implements CrewService {
3442
private final CrewTagStore crewTagStore;
3543
private final CrewMemberReader crewMemberReader;
3644
private final CrewMemberStore crewMemberStore;
45+
private final CrewApplicationReader crewApplicationReader;
46+
private final CrewApplicationStore crewApplicationStore;
47+
private final MeetingStore meetingStore;
48+
private final MeetingMemberStore meetingMemberStore;
3749
private final UserReader userReader;
3850
private final ImageStore imageStore;
51+
private final ImageAsyncStore imageAsyncStore;
3952

4053

4154
@Override
@@ -51,7 +64,7 @@ public CrewDto getCrewDtoById(Long crewId) {
5164
@Transactional
5265
public void createCrew(CrewDto crewDto, MultipartFile file) {
5366
validateCrewParticipationCount(crewDto.userDto().id());
54-
var icon = imageStore.upload(CREW, file);
67+
var icon = imageAsyncStore.upload(CREW, file);
5568
Crew crew = crewStore.save(crewDto.toEntity(icon));
5669
crewTagStore.addTags(crew, crewDto.tags());
5770
crewMemberStore.addCrewMember(crew.getId(), crew.getUser().getId());
@@ -96,27 +109,203 @@ public List<String> getUserImagesByCrew(CrewDto crewDto) {
96109
@Override
97110
@Transactional(readOnly = true)
98111
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);
100127
}
101128

102129
@Override
103130
@Transactional
104131
public void updateCrewImage(CrewDto crewDto, MultipartFile imageFile, Long userId) {
105132
validateCrewCreator(crewDto.userDto().id(), userId);
106133
var oldImage = crewDto.icon();
107-
var newImage = imageStore.upload(CREW, imageFile);
134+
var newImage = imageAsyncStore.upload(CREW, imageFile);
108135
crewStore.save(crewDto.toEntity(newImage));
109136
imageStore.delete(CREW, oldImage);
110137
}
111138

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+
112236
private void validateCrewCreator(Long crewCreatorId, Long userId) {
113237
if (!Objects.equals(crewCreatorId, userId))
114238
throw new BusinessException(CrewErrorCode.FORBIDDEN);
115239
}
116240

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+
117251
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)
120255
throw new BusinessException(CrewErrorCode.MAXIMUM_PARTICIPATION);
121256
}
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+
}
122311
}

0 commit comments

Comments
 (0)