Skip to content

Commit 088b4a1

Browse files
authored
Merge pull request #872 from sopt-makers/develop
v2.11.7
2 parents 2e8652e + 9d38b91 commit 088b4a1

5 files changed

Lines changed: 103 additions & 52 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.sopt.makers.crew.main.meeting.v2.dto.response;
2+
3+
import java.time.LocalDateTime;
4+
5+
import org.sopt.makers.crew.main.entity.apply.Apply;
6+
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import jakarta.validation.constraints.NotNull;
9+
import lombok.Getter;
10+
import lombok.RequiredArgsConstructor;
11+
12+
@Getter
13+
@RequiredArgsConstructor
14+
@Schema(description = "모임 같은 파트/기수 신청 정보 조회 dto")
15+
public class ApplyMemberInfoDto {
16+
17+
@Schema(description = "신청 id", example = "3")
18+
@NotNull
19+
private final Integer id;
20+
21+
@Schema(description = "신청 번호", example = "1")
22+
@NotNull
23+
private final Integer applyNumber;
24+
25+
@Schema(description = "신청 타입", example = "0")
26+
@NotNull
27+
private final Integer type;
28+
29+
@Schema(description = "모임 id", example = "13")
30+
@NotNull
31+
private final Integer meetingId;
32+
33+
@Schema(description = "신청자 id", example = "184")
34+
@NotNull
35+
private final Integer userId;
36+
37+
@Schema(description = "신청 날짜 및 시간", example = "2024-10-13T23:59:59")
38+
@NotNull
39+
private final LocalDateTime appliedDate;
40+
41+
@Schema(description = "신청 상태", example = "1")
42+
@NotNull
43+
private final Integer status;
44+
45+
@Schema(description = "신청자 객체", example = "")
46+
@NotNull
47+
private final ApplicantByMeetingDto user;
48+
49+
public static ApplyMemberInfoDto of(Apply apply, Integer applyNumber) {
50+
ApplicantByMeetingDto applicantByMeetingDto = ApplicantByMeetingDto.of(apply.getUser());
51+
52+
return new ApplyMemberInfoDto(apply.getId(), applyNumber, apply.getType().getValue(), apply.getMeetingId(),
53+
apply.getUserId(), apply.getAppliedDate(), apply.getStatus().getValue(), applicantByMeetingDto);
54+
}
55+
}

main/src/main/java/org/sopt/makers/crew/main/meeting/v2/dto/response/MeetingV2GetMeetingPartMembersResponseDto.java

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,12 @@ public record MeetingV2GetMeetingPartMembersResponseDto(
1414
boolean isActiveGeneration,
1515
@Schema(description = "참여 정보 기준 기수", example = "38")
1616
Integer activeGeneration,
17-
@Schema(description = "유저 리스트의 index id", example = "[1, 2]")
18-
List<Integer> memberIds,
19-
@Schema(description = "유저 이름 리스트", example = "[\"이지훈\", \"김효준\"]")
20-
List<String> memberNames,
21-
@Schema(description = "유저 프로필 이미지 리스트", example = "[\"https://example.com/profile.png\", null]")
22-
List<String> memberProfileImages
17+
@Schema(description = "조건에 맞는 신청 정보 목록", example = "")
18+
List<ApplyMemberInfoDto> appliedInfo
2319
) {
2420
public static MeetingV2GetMeetingPartMembersResponseDto of(String part, int participantCount,
25-
boolean isActiveGeneration, Integer activeGeneration, List<Integer> memberIds, List<String> memberNames,
26-
List<String> memberProfileImages) {
21+
boolean isActiveGeneration, Integer activeGeneration, List<ApplyMemberInfoDto> appliedInfo) {
2722
return new MeetingV2GetMeetingPartMembersResponseDto(part, participantCount, isActiveGeneration,
28-
activeGeneration, memberIds, memberNames, memberProfileImages);
23+
activeGeneration, appliedInfo);
2924
}
3025
}

main/src/main/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingParticipationFactory.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
import java.util.List;
66
import java.util.Objects;
7-
import java.util.stream.IntStream;
7+
import java.util.concurrent.atomic.AtomicInteger;
88

99
import org.sopt.makers.crew.main.entity.apply.Apply;
1010
import org.sopt.makers.crew.main.entity.user.User;
1111
import org.sopt.makers.crew.main.entity.user.vo.UserActivityVO;
1212
import org.sopt.makers.crew.main.global.exception.BadRequestException;
1313
import org.sopt.makers.crew.main.global.util.ActiveGenerationProvider;
14+
import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyMemberInfoDto;
1415
import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2GetMeetingPartMembersResponseDto;
1516
import org.springframework.stereotype.Component;
1617

@@ -31,18 +32,13 @@ public MeetingV2GetMeetingPartMembersResponseDto createMeetingPartMembersRespons
3132
String requestUserPart = requestUserActivity.getPart();
3233
List<Apply> participatingPartApplies = getParticipatingPartApplies(participatingApplies, requestUserActivity,
3334
isActiveGeneration);
34-
List<Integer> memberIds = IntStream.rangeClosed(1, participatingPartApplies.size())
35-
.boxed()
36-
.toList();
37-
List<String> memberNames = participatingPartApplies.stream()
38-
.map(apply -> apply.getUser().getName())
39-
.toList();
40-
List<String> memberProfileImages = participatingPartApplies.stream()
41-
.map(apply -> apply.getUser().getProfileImage())
35+
AtomicInteger applyNumber = new AtomicInteger(1);
36+
List<ApplyMemberInfoDto> appliedInfo = participatingPartApplies.stream()
37+
.map(apply -> ApplyMemberInfoDto.of(apply, applyNumber.getAndIncrement()))
4238
.toList();
4339

4440
return MeetingV2GetMeetingPartMembersResponseDto.of(requestUserPart, participatingPartApplies.size(),
45-
isActiveGeneration, requestUserActivity.getGeneration(), memberIds, memberNames, memberProfileImages);
41+
isActiveGeneration, requestUserActivity.getGeneration(), appliedInfo);
4642
}
4743

4844
private List<Apply> getParticipatingPartApplies(List<Apply> participatingApplies,

main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ConcurrencyTest.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ class MeetingV2ConcurrencyTest {
6464
@DisplayName("모임 지원 락킹 테스트")
6565
class 모임_지원_락킹_테스트 {
6666
@Test
67-
@DisplayName("동일 사용자가 동시에 여러 신청을 시도할 경우 오직 하나만 성공해야 한다")
68-
void applyMeetingWithLock_WhenMultipleRequestsFromSameUser_ShouldProcessOnlyOne() throws InterruptedException {
67+
@DisplayName("이미 신청한 동일 사용자가 동시에 여러 신청을 시도할 경우 모두 실패해야 한다")
68+
void applyMeetingWithLock_WhenAppliedUserRequestsConcurrently_ShouldRejectAll() throws InterruptedException {
6969
// given
7070
User leader = User.builder()
7171
.name("모임장")
@@ -113,14 +113,18 @@ void applyMeetingWithLock_WhenMultipleRequestsFromSameUser_ShouldProcessOnlyOne(
113113

114114
MeetingV2ApplyMeetingDto applyDto = new MeetingV2ApplyMeetingDto(meeting.getId(), "지원 동기");
115115

116-
int concurrentRequests = 5;
116+
MeetingV2ApplyMeetingResponseDto firstResponse = meetingV2Service.applyEventMeetingWithLock(applyDto,
117+
applicant.getId());
118+
assertThat(firstResponse.getApplyId()).isNotNull();
119+
120+
int concurrentRequests = 4;
117121
ExecutorService executorService = Executors.newFixedThreadPool(concurrentRequests);
118122
CountDownLatch startLatch = new CountDownLatch(1);
119123
CountDownLatch readyLatch = new CountDownLatch(concurrentRequests);
120124
CountDownLatch finishLatch = new CountDownLatch(concurrentRequests);
121125

122-
AtomicInteger successCount = new AtomicInteger(0);
123-
AtomicInteger failCount = new AtomicInteger(0);
126+
AtomicInteger duplicateSuccessCount = new AtomicInteger(0);
127+
AtomicInteger duplicateFailCount = new AtomicInteger(0);
124128

125129
// when
126130
for (int i = 0; i < concurrentRequests; i++) {
@@ -133,19 +137,18 @@ void applyMeetingWithLock_WhenMultipleRequestsFromSameUser_ShouldProcessOnlyOne(
133137
applicant.getId());
134138

135139
if (response != null && response.getApplyId() != null) {
136-
successCount.incrementAndGet();
140+
duplicateSuccessCount.incrementAndGet();
137141
}
138142
} catch (Exception e) {
139-
failCount.incrementAndGet();
143+
duplicateFailCount.incrementAndGet();
140144
} finally {
141145
finishLatch.countDown();
142146
}
143147
});
144148
}
145149

146-
readyLatch.await();
147-
148-
Thread.sleep(5000);
150+
boolean readyInTime = readyLatch.await(5, TimeUnit.SECONDS);
151+
assertThat(readyInTime).isTrue();
149152

150153
startLatch.countDown();
151154
boolean completedInTime = finishLatch.await(10, TimeUnit.SECONDS);
@@ -154,8 +157,8 @@ void applyMeetingWithLock_WhenMultipleRequestsFromSameUser_ShouldProcessOnlyOne(
154157

155158
// then
156159
assertThat(completedInTime).isTrue();
157-
assertThat(successCount.get()).isEqualTo(1);
158-
assertThat(failCount.get()).isEqualTo(concurrentRequests - 1);
160+
assertThat(duplicateSuccessCount.get()).isZero();
161+
assertThat(duplicateFailCount.get()).isEqualTo(concurrentRequests);
159162

160163
List<Apply> allApplies = applyRepository.findAllByMeetingId(meeting.getId());
161164
List<Apply> userApplies = allApplies.stream()

main/src/test/java/org/sopt/makers/crew/main/meeting/v2/service/MeetingV2ServiceTest.java

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2CreateMeetingBodyDto;
5252
import org.sopt.makers.crew.main.meeting.v2.dto.request.MeetingV2UpdateMeetingBodyDto;
5353
import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyInfoDto;
54+
import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyMemberInfoDto;
5455
import org.sopt.makers.crew.main.meeting.v2.dto.response.ApplyWholeInfoDto;
5556
import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingGetApplyListResponseDto;
5657
import org.sopt.makers.crew.main.meeting.v2.dto.response.MeetingV2ApplyMeetingResponseDto;
@@ -152,6 +153,19 @@ private void saveApplyUser(Integer meetingId, Integer userId, String name, Strin
152153
applyRepository.save(apply);
153154
}
154155

156+
private void assertAppliedInfoMembers(MeetingV2GetMeetingPartMembersResponseDto responseDto,
157+
List<Integer> applyNumbers, List<String> memberNames, List<String> memberProfileImages) {
158+
Assertions.assertThat(responseDto.appliedInfo())
159+
.extracting(ApplyMemberInfoDto::getApplyNumber)
160+
.containsExactlyElementsOf(applyNumbers);
161+
Assertions.assertThat(responseDto.appliedInfo())
162+
.extracting(applyInfo -> applyInfo.getUser().getName())
163+
.containsExactlyElementsOf(memberNames);
164+
Assertions.assertThat(responseDto.appliedInfo())
165+
.extracting(applyInfo -> applyInfo.getUser().getProfileImage())
166+
.containsExactlyElementsOf(memberProfileImages);
167+
}
168+
155169
@Nested
156170
class 모임_생성 {
157171
@ParameterizedTest
@@ -1237,9 +1251,7 @@ void nonActiveGenerationUser_getMeetingById_participantPartInfo() {
12371251
Assertions.assertThat(responseDto.participantCount()).isEqualTo(1);
12381252
Assertions.assertThat(responseDto.isActiveGeneration()).isFalse();
12391253
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(33);
1240-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1);
1241-
Assertions.assertThat(responseDto.memberNames()).containsExactly("승인신청자");
1242-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile2.jpg");
1254+
assertAppliedInfoMembers(responseDto, List.of(1), List.of("승인신청자"), List.of("profile2.jpg"));
12431255
}
12441256

12451257
@Test
@@ -1284,9 +1296,7 @@ void activeGenerationServerUser_getMeetingById_backendApplicant_samePart() {
12841296
Assertions.assertThat(responseDto.participantCount()).isEqualTo(1);
12851297
Assertions.assertThat(responseDto.isActiveGeneration()).isTrue();
12861298
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(35);
1287-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1);
1288-
Assertions.assertThat(responseDto.memberNames()).containsExactly("백엔드신청자");
1289-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("backend-profile.jpg");
1299+
assertAppliedInfoMembers(responseDto, List.of(1), List.of("백엔드신청자"), List.of("backend-profile.jpg"));
12901300
}
12911301

12921302
@Test
@@ -1317,9 +1327,7 @@ void honoraryGenerationUser_getMeetingById_latestHonoraryGenerationApplicantInfo
13171327
Assertions.assertThat(responseDto.isActiveGeneration()).isFalse();
13181328
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(37);
13191329
Assertions.assertThat(responseDto.participantCount()).isEqualTo(1);
1320-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1);
1321-
Assertions.assertThat(responseDto.memberNames()).containsExactly("37기신청자");
1322-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile.jpg");
1330+
assertAppliedInfoMembers(responseDto, List.of(1), List.of("37기신청자"), List.of("profile.jpg"));
13231331
}
13241332

13251333
@Test
@@ -1451,9 +1459,7 @@ void activeGenerationUser_getMeetingPartMembers_success() {
14511459
Assertions.assertThat(responseDto.isActiveGeneration()).isTrue();
14521460
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(35);
14531461
Assertions.assertThat(responseDto.participantCount()).isEqualTo(1);
1454-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1);
1455-
Assertions.assertThat(responseDto.memberNames()).containsExactly("35기iOS신청자");
1456-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile.jpg");
1462+
assertAppliedInfoMembers(responseDto, List.of(1), List.of("35기iOS신청자"), List.of("profile.jpg"));
14571463
}
14581464

14591465
@Test
@@ -1474,9 +1480,8 @@ void serverUser_getMeetingPartMembers_backendApplicant_samePart() {
14741480
Assertions.assertThat(responseDto.isActiveGeneration()).isFalse();
14751481
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(33);
14761482
Assertions.assertThat(responseDto.participantCount()).isEqualTo(2);
1477-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1, 2);
1478-
Assertions.assertThat(responseDto.memberNames()).containsExactly("승인신청자", "33기백엔드신청자");
1479-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile2.jpg", "profile.jpg");
1483+
assertAppliedInfoMembers(responseDto, List.of(1, 2), List.of("승인신청자", "33기백엔드신청자"),
1484+
List.of("profile2.jpg", "profile.jpg"));
14801485
}
14811486

14821487
@Test
@@ -1497,9 +1502,8 @@ void planUser_getMeetingPartMembers_pmApplicant_samePart() {
14971502
Assertions.assertThat(responseDto.isActiveGeneration()).isFalse();
14981503
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(33);
14991504
Assertions.assertThat(responseDto.participantCount()).isEqualTo(2);
1500-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1, 2);
1501-
Assertions.assertThat(responseDto.memberNames()).containsExactly("승인신청자", "PM신청자");
1502-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile2.jpg", "profile.jpg");
1505+
assertAppliedInfoMembers(responseDto, List.of(1, 2), List.of("승인신청자", "PM신청자"),
1506+
List.of("profile2.jpg", "profile.jpg"));
15031507
}
15041508

15051509
@Test
@@ -1526,9 +1530,7 @@ void webUser_getMeetingPartMembers_frontendApplicant_samePart() {
15261530
Assertions.assertThat(responseDto.isActiveGeneration()).isTrue();
15271531
Assertions.assertThat(responseDto.activeGeneration()).isEqualTo(35);
15281532
Assertions.assertThat(responseDto.participantCount()).isEqualTo(1);
1529-
Assertions.assertThat(responseDto.memberIds()).containsExactly(1);
1530-
Assertions.assertThat(responseDto.memberNames()).containsExactly("프론트엔드신청자");
1531-
Assertions.assertThat(responseDto.memberProfileImages()).containsExactly("profile.jpg");
1533+
assertAppliedInfoMembers(responseDto, List.of(1), List.of("프론트엔드신청자"), List.of("profile.jpg"));
15321534
}
15331535

15341536
@Test

0 commit comments

Comments
 (0)