Skip to content

Commit 6a52fd7

Browse files
bikoojuCoen90coen주현준
authored
✨ 모임 이미지 s3에서 삭제 및 내가 가입한/생성한 모임 리스트 조회
* ✨ 모임 수정 삭제 api 구현 및 모임 수정 api 로직 수정 * ♻️ 주석 정리 * feat: 레디스 관련 설정 추가 및 일부 수정 (#19) * feat: 레디스 설정 추가 * feat: Redis Properties 개발 * refactor: categories 리스트 형식으로 받도록 변경 * refactor: categories 리스트 형식으로 받도록 변경 * feat: redis 관련 설정 변경 및 예시 코드 추가 * feat: redis key를 위한 상수 클래스 추가 * feat: redis 비밀번호 추가 * refactor: User 관련 수정 * docs: 주석 제거 * docs: 불필요한 주석 제거 * chore: optimize import * refactor: command, enums 위치 변경 * refactor: CustomUserDetails interface 생성하여 핸들링하도록 변경 * refactor: 현재 유저 닉네임과 같은 경우 예외 발생하지 않도록 변경 * fix: 유저 에러 코드 수정 * fix: 검증로직 보완 * refactor: 중복 로직 재사용 * refactor: 잘못 응답된 UserDetails 수정 * fix: 미인가 상태에서 security filter 통과하는 오류 수정 --------- * ♻️ 코드 리팩토링 * ♻️ Redis Password 삭제 * ♻️ Redis Password 삭제 * ♻️ 코드 리팩토링 * ♻️ Auth 삭제 및 fetch join List 2개 오류로 platformUrl 조인 삭제 * refactor: 이미지 업로드 변경 (비동기 -> 동기) (#26) * ✨ 내가 생성한 모임과 가입한 모임 리스트 조회 기능 구현 * ♻️ 내가 생성한/가입한 모임 api는 인증 필수 * ♻️ 코드 리팩토링 * ♻️ 간단한 페이징 처리 응답 dto로 적용 * ♻️ 게시글 수정시 삭제할 이미지 필드 보내게끔 수정 및 이미지 삭제시 s3와 DB에서 삭제 * ♻️ fetch join으로 추가쿼리 발생하지 않게 수정 * ♻️ feature/crew -> temp로 pr (#28) * ♻️ 현재 참여자수 필드 추가 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ♻️ null이 아닌 경우 없으므로 null 조건 삭제 * ♻️ if 한 줄이어도 중괄호 사용으로 수정 * ♻️ 허용 엔드포인트 수정 * ♻️ 코드 리팩토링 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ✨ 모임 가입 API 구현 * ♻️ 모임 가입 API 코드 수정 * ♻️ entity에 currentMemberCount 추가 및 에러코드 추가 * ♻️ 에러코드 추가 * ♻️ 모임가입 API 적용하여 특정 모임과 모임리스트 조회 처리 * ♻️ 도메인 swagger와 프론트 배포주소 CORS 허용 (#20) * ✨ 모임 수정 api 구현 및 에러코드 추가 * ✨ 모임 수정 삭제 api 구현 및 모임 수정 api 로직 수정 * ♻️ 주석 정리 * ♻️ 코드 리팩토링 * ♻️ Redis Password 삭제 * ♻️ Redis Password 삭제 * ♻️ 코드 리팩토링 * ♻️ Auth 삭제 및 fetch join List 2개 오류로 platformUrl 조인 삭제 * ✨ 모임 가입, 수정, 삭제 api 구현 및 redis 설정과 일부 수정 (#23) * ♻️ 도메인 swagger와 프론트 배포주소 CORS 허용 * feat: 레디스 관련 설정 추가 및 일부 수정 (#19) * feat: 레디스 설정 추가 * feat: Redis Properties 개발 * refactor: categories 리스트 형식으로 받도록 변경 * refactor: categories 리스트 형식으로 받도록 변경 * feat: redis 관련 설정 변경 및 예시 코드 추가 * feat: redis key를 위한 상수 클래스 추가 * feat: redis 비밀번호 추가 * refactor: User 관련 수정 * docs: 주석 제거 * docs: 불필요한 주석 제거 * chore: optimize import * refactor: command, enums 위치 변경 * refactor: CustomUserDetails interface 생성하여 핸들링하도록 변경 * refactor: 현재 유저 닉네임과 같은 경우 예외 발생하지 않도록 변경 * fix: 유저 에러 코드 수정 * fix: 검증로직 보완 * refactor: 중복 로직 재사용 * refactor: 잘못 응답된 UserDetails 수정 * fix: 미인가 상태에서 security filter 통과하는 오류 수정 --------- * ✨ 모임 가입 , 모임 수정, 삭제 api 구현 (#21) * ♻️ 현재 참여자수 필드 추가 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ♻️ null이 아닌 경우 없으므로 null 조건 삭제 * ♻️ if 한 줄이어도 중괄호 사용으로 수정 * ♻️ 허용 엔드포인트 수정 * ♻️ 코드 리팩토링 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ✨ 모임 가입 API 구현 * ♻️ 모임 가입 API 코드 수정 * ♻️ entity에 currentMemberCount 추가 및 에러코드 추가 * ♻️ 에러코드 추가 * ♻️ 모임가입 API 적용하여 특정 모임과 모임리스트 조회 처리 * ✨ 모임 수정 api 구현 및 에러코드 추가 * ✨ 모임 수정 삭제 api 구현 및 모임 수정 api 로직 수정 * ♻️ 주석 정리 * ♻️ 코드 리팩토링 * ♻️ Redis Password 삭제 * ♻️ Redis Password 삭제 * ♻️ 코드 리팩토링 --------- * ♻️ Auth 삭제 및 fetch join List 2개 오류로 platformUrl 조인 삭제 (#22) * ♻️ 현재 참여자수 필드 추가 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ♻️ null이 아닌 경우 없으므로 null 조건 삭제 * ♻️ if 한 줄이어도 중괄호 사용으로 수정 * ♻️ 허용 엔드포인트 수정 * ♻️ 코드 리팩토링 * ♻️ 특정 모임 및 모임 리스트 조회 토큰 없이도 조회 가능하게 수정 * ♻️ 필요한 api만 허용 * ✨ 모임 가입 API 구현 * ♻️ 모임 가입 API 코드 수정 * ♻️ entity에 currentMemberCount 추가 및 에러코드 추가 * ♻️ 에러코드 추가 * ♻️ 모임가입 API 적용하여 특정 모임과 모임리스트 조회 처리 * ✨ 모임 수정 api 구현 및 에러코드 추가 * ✨ 모임 수정 삭제 api 구현 및 모임 수정 api 로직 수정 * ♻️ 주석 정리 * ♻️ 코드 리팩토링 * ♻️ Redis Password 삭제 * ♻️ Redis Password 삭제 * ♻️ 코드 리팩토링 * ♻️ Auth 삭제 및 fetch join List 2개 오류로 platformUrl 조인 삭제 --------- --------- * ♻️ 페이징에서 EntityGraph와 GROUP BY 함께 쓰면 모임 멤버 컬렉션이 비어지는 문제 해결 * ✨ 내가 생성한 모임과 가입한 모임 리스트 조회 기능 구현 * ♻️ 내가 생성한/가입한 모임 api는 인증 필수 * ♻️ 코드 리팩토링 * ♻️ 간단한 페이징 처리 응답 dto로 적용 * ♻️ 게시글 수정시 삭제할 이미지 필드 보내게끔 수정 및 이미지 삭제시 s3와 DB에서 삭제 * ♻️ fetch join으로 추가쿼리 발생하지 않게 수정 --------- --------- Co-authored-by: Coen90 <81370558+Coen90@users.noreply.github.com> Co-authored-by: coen <coen@mrblue.com> Co-authored-by: coen <bht9011@gmail.com> Co-authored-by: 주현준 <hjjo@dsmentoring.com>
1 parent b78b82c commit 6a52fd7

File tree

16 files changed

+200
-57
lines changed

16 files changed

+200
-57
lines changed

src/main/java/org/codeit/roomunion/auth/config/SecurityConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class SecurityConfig {
2828
private static final String[] PUBLIC_ENDPOINTS = {"/v1/users/sign-up", "/v1/auth/login", "/v2/auth/login", "/v1/auth/email/send", "/v1/auth/email/verify", "/v1/auth/email/extend"};
2929
private static final String[] PUBLIC_GET_ENDPOINTS = {"/v1/meetings", "/v1/meetings/*"};
3030
private static final String[] SWAGGER_ENDPOINTS = {"/swagger-ui/**", "/v3/api-docs/**"};
31+
private static final String[] AUTH_REQUIRED_GET_ENDPOINTS = {"/v1/meetings/mine"};
3132

3233
private final CorsConfig corsConfig;
3334
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@@ -54,6 +55,7 @@ private static void authorizeHttpRequests(AuthorizeHttpRequestsConfigurer<HttpSe
5455
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
5556
.requestMatchers(SWAGGER_ENDPOINTS).permitAll()
5657
.requestMatchers(PUBLIC_ENDPOINTS).permitAll()
58+
.requestMatchers(HttpMethod.GET, AUTH_REQUIRED_GET_ENDPOINTS).authenticated()
5759
.requestMatchers(HttpMethod.GET, PUBLIC_GET_ENDPOINTS).permitAll()
5860
.anyRequest()
5961
.authenticated();
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.codeit.roomunion.common.adapter.in.web.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import java.util.List;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import org.springframework.data.domain.Page;
8+
9+
@Getter
10+
@Builder
11+
@Schema(title = "SimplePageResponse : 간단한 페이지 응답 DTO", description = "페이지 번호와 크기, 데이터 리스트만 포함된 최소 페이징 응답 DTO")
12+
public class SimplePageResponse<T> {
13+
14+
@Schema(description = "응답 데이터 리스트", example = "[...]")
15+
private List<T> content;
16+
17+
@Schema(description = "현재 페이지 번호(0부터 시작)", example = "0")
18+
private int page;
19+
20+
@Schema(description = "페이지당 데이터 개수", example = "10")
21+
private int size;
22+
23+
public static <T> SimplePageResponse<T> from(Page<T> page) {
24+
return SimplePageResponse.<T>builder()
25+
.content(page.getContent())
26+
.page(page.getNumber())
27+
.size(page.getSize())
28+
.build();
29+
}
30+
}

src/main/java/org/codeit/roomunion/common/adapter/out/s3/AmazonS3Manager.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package org.codeit.roomunion.common.adapter.out.s3;
22

33

4+
import java.net.URI;
45
import lombok.RequiredArgsConstructor;
56
import lombok.extern.slf4j.Slf4j;
67
import org.codeit.roomunion.common.config.S3.S3Properties;
78
import org.springframework.stereotype.Component;
89
import org.springframework.web.multipart.MultipartFile;
910
import software.amazon.awssdk.core.sync.RequestBody;
1011
import software.amazon.awssdk.services.s3.S3Client;
12+
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
1113
import software.amazon.awssdk.services.s3.model.GetUrlRequest;
1214
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
1315

@@ -46,4 +48,41 @@ public String uploadFile(String keyName, MultipartFile file) {
4648
throw new RuntimeException("S3 업로드 중 오류 발생", e);
4749
}
4850
}
51+
52+
public void deleteObjectByUrl(String url) {
53+
try {
54+
String bucket = s3Properties.getBucket();
55+
56+
String key = extractKeyFromUrl(url);
57+
58+
if (key == null || key.isBlank()) {
59+
log.warn("S3 삭제 실패: key 추출 불가 (url: {})", url);
60+
return;
61+
}
62+
DeleteObjectRequest request = DeleteObjectRequest.builder()
63+
.bucket(bucket)
64+
.key(key)
65+
.build();
66+
67+
s3Client.deleteObject(request);
68+
} catch (Exception e) {
69+
log.error("S3 객체 삭제 중 오류 발생: {}", url, e);
70+
}
71+
}
72+
73+
private String extractKeyFromUrl(String fileUrl) {
74+
try {
75+
URI uri = URI.create(fileUrl);
76+
String path = uri.getPath(); // "/community/uuid123"
77+
if (path == null || path.isBlank()) {
78+
return null;
79+
}
80+
81+
// 앞의 "/" 제거 후 반환
82+
return path.startsWith("/") ? path.substring(1) : path;
83+
} catch (Exception e) {
84+
log.error("S3 URL 파싱 오류: {}", fileUrl, e);
85+
return null;
86+
}
87+
}
4988
}

src/main/java/org/codeit/roomunion/meeting/adapter/in/web/MeetingController.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import java.util.HashMap;
77
import java.util.Map;
88
import lombok.RequiredArgsConstructor;
9+
import org.apache.coyote.Response;
910
import org.codeit.roomunion.auth.domain.model.CustomUserDetails;
11+
import org.codeit.roomunion.common.adapter.in.web.response.SimplePageResponse;
1012
import org.codeit.roomunion.meeting.adapter.in.web.request.CreateMeetingRequest;
1113
import org.codeit.roomunion.meeting.adapter.in.web.request.UpdateMeetingRequest;
1214
import org.codeit.roomunion.meeting.adapter.in.web.response.MeetingResponse;
@@ -16,6 +18,7 @@
1618
import org.codeit.roomunion.meeting.domain.command.MeetingCreateCommand;
1719
import org.codeit.roomunion.meeting.domain.command.MeetingUpdateCommand;
1820
import org.codeit.roomunion.meeting.domain.model.MeetingCategory;
21+
import org.codeit.roomunion.meeting.domain.model.MeetingRole;
1922
import org.codeit.roomunion.meeting.domain.model.MeetingSort;
2023
import org.springframework.data.domain.Page;
2124
import org.springframework.http.MediaType;
@@ -58,7 +61,7 @@ public ResponseEntity<MeetingResponse> getMeeting(
5861

5962
@Operation(summary = "전체/카테고리 모임 리스트 조회(토큰 없이도 가능)", description = "전체/카테고리별 조회 + 정렬(최신순/사람많은 순) + 페이징처리. 로그인 시 isJoined 계산, 비로그인은 false")
6063
@GetMapping
61-
public ResponseEntity<Page<MeetingResponse>> getMeetingList(
64+
public ResponseEntity<SimplePageResponse<MeetingResponse>> getMeetingList(
6265
@AuthenticationPrincipal CustomUserDetails userDetails,
6366
@RequestParam(required = false) MeetingCategory category,
6467
@RequestParam(defaultValue = "LATEST") MeetingSort sort,
@@ -67,7 +70,7 @@ public ResponseEntity<Page<MeetingResponse>> getMeetingList(
6770
) {
6871
Page<Meeting> meetings = meetingQueryUseCase.search(category, sort, page, size, userDetails);
6972
Page<MeetingResponse> response = meetings.map(MeetingResponse::from);
70-
return ResponseEntity.ok(response);
73+
return ResponseEntity.ok(SimplePageResponse.from(response));
7174
}
7275

7376
@Operation(summary = "특정 모임 가입하기", description = "meetingId로 특정 모임 가입 API. 중복 가입 X")
@@ -76,7 +79,7 @@ public ResponseEntity<MeetingResponse> join(
7679
@AuthenticationPrincipal CustomUserDetails userDetails,
7780
@PathVariable Long meetingId
7881
) {
79-
Meeting meeting = meetingCommandUseCase.join(meetingId, userDetails.getUser().getId());
82+
Meeting meeting = meetingCommandUseCase.join(meetingId, userDetails);
8083
return ResponseEntity.ok(MeetingResponse.from(meeting));
8184
}
8285

@@ -89,7 +92,7 @@ public ResponseEntity<MeetingResponse> updateMeeting(
8992
@RequestPart(value = "image", required = false) MultipartFile image
9093
) {
9194
MeetingUpdateCommand command = request.toCommand();
92-
Meeting updatedMeeting = meetingCommandUseCase.update(meetingId, userDetails.getUser().getId(), command, image);
95+
Meeting updatedMeeting = meetingCommandUseCase.update(meetingId, userDetails, command, image);
9396
return ResponseEntity.ok(MeetingResponse.from(updatedMeeting));
9497
}
9598

@@ -99,12 +102,25 @@ public ResponseEntity<Map<String, String>> deleteMeeting(
99102
@PathVariable Long meetingId,
100103
@AuthenticationPrincipal CustomUserDetails userDetails
101104
) {
102-
meetingCommandUseCase.deleteMeeting(meetingId, userDetails.getUser().getId());
105+
meetingCommandUseCase.deleteMeeting(meetingId, userDetails);
103106

104107
Map<String, String> response = new HashMap<>();
105108
response.put("message", "모임이 성공적으로 삭제되었습니다.");
106109
return ResponseEntity.ok(response);
107110
}
108111

112+
@Operation(summary = "내가 생성한/가입한 모임 리스트 조회 (역할 필터 필수)", description = "role=HOST(내가 생성), role=MEMBER(내가 가입)")
113+
@GetMapping("/mine")
114+
public ResponseEntity<SimplePageResponse<MeetingResponse>> getMyMeetings(
115+
@AuthenticationPrincipal CustomUserDetails userDetails,
116+
@RequestParam MeetingRole role,
117+
@RequestParam(defaultValue = "0") int page,
118+
@RequestParam(defaultValue = "10") int size
119+
) {
120+
Page<Meeting> meetings = meetingQueryUseCase.getMyMeetings(role, page, size, userDetails);
121+
Page<MeetingResponse> response = meetings.map(MeetingResponse::from);
122+
return ResponseEntity.ok(SimplePageResponse.from(response));
123+
}
124+
109125

110126
}

src/main/java/org/codeit/roomunion/meeting/adapter/in/web/request/UpdateMeetingRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,16 @@ public class UpdateMeetingRequest {
3838
@Schema(example = "[\"https://zoom.us/12345\", \"https://discord.gg/abcde\"]")
3939
private List<@NotBlank String> platformURL;
4040

41+
private String removeImageUrl;
42+
4143
public MeetingUpdateCommand toCommand() {
4244
return MeetingUpdateCommand.builder()
4345
.name(this.name)
4446
.description(this.description)
4547
.category(this.category)
4648
.maxMemberCount(this.maxMemberCount)
4749
.platformURL(this.platformURL)
50+
.removeImageUrl(this.removeImageUrl)
4851
.build();
4952
}
5053
}

src/main/java/org/codeit/roomunion/meeting/adapter/out/persistence/MeetingRepositoryImpl.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ public Page<Meeting> search(MeetingCategory category, MeetingSort sort, int page
9090
return resultPage.map(e -> e.toDomain().withJoined(joinedIds.contains(e.getId())));
9191
}
9292

93+
@Override
94+
public Page<Meeting> findMyMeetings(MeetingRole role, int page, int size, Long currentUserId) {
95+
PageRequest pageable = PageRequest.of(page, size);
96+
97+
Page<MeetingEntity> resultPage = meetingJpaRepository.findByUserAndRole(currentUserId, role, pageable);
98+
99+
return resultPage.map(e -> e.toDomain().withJoined(true));
100+
101+
}
102+
93103
@Override
94104
public Meeting findByIdWithJoined(Long meetingId, Long currentUserId) {
95105
MeetingEntity entity = meetingJpaRepository.findByIdWithMembers(meetingId)
@@ -111,12 +121,12 @@ public Meeting findByIdWithJoined(Long meetingId, Long currentUserId) {
111121
private boolean isUserJoined(Long currentUserId, MeetingEntity entity) {
112122
if (currentUserId == 0L) return false;
113123
return entity.getMeetingMembers().stream() // 멤버 리스트 순회
114-
.anyMatch(mm -> Objects.equals(mm.getUser().getId(), currentUserId)); // anyMatch : 조건에 맞으면 true (모임 유저의 id와 currentUserId 비교)
124+
.anyMatch(meetingMemberEntity -> Objects.equals(meetingMemberEntity.getUser().getId(), currentUserId)); // anyMatch : 조건에 맞으면 true (모임 유저의 id와 currentUserId 비교)
115125
}
116126

117127
@Override
118128
public boolean isMeetingMember(Long meetingId, Long userId) {
119-
return meetingMemberJpaRepository.existsByMeetingIdAndUserId(meetingId, userId);
129+
return meetingMemberJpaRepository.existsByMeeting_IdAndUser_Id(meetingId, userId);
120130
}
121131

122132
@Override
@@ -125,13 +135,13 @@ public boolean isHostMember(Long meetingId, Long userId) {
125135
}
126136

127137
@Override
128-
public Meeting insertMember(Long meetingId, Long userId, MeetingRole role) {
129-
MeetingEntity meeting = meetingJpaRepository.findById(meetingId)
138+
public Meeting insertMember(Long meetingId, Long userId) {
139+
MeetingEntity meeting = meetingJpaRepository.findWithLockById(meetingId)
130140
.orElseThrow(() -> new CustomException(MeetingErrorCode.MEETING_NOT_FOUND));
131141
UserEntity user = userJpaRepository.findById(userId)
132142
.orElseThrow(() -> new CustomException(UserErrorCode.USER_NOT_FOUND));
133143

134-
MeetingMemberEntity memberEntity = MeetingMemberEntity.of(meeting, user, role);
144+
MeetingMemberEntity memberEntity = MeetingMemberEntity.of(meeting, user);
135145
meeting.addMember(memberEntity);
136146
return meeting.toDomain().withJoined(true);
137147
}

src/main/java/org/codeit/roomunion/meeting/adapter/out/persistence/entity/MeetingEntity.java

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
import java.time.LocalDateTime;
1818
import java.util.ArrayList;
1919
import java.util.List;
20+
import org.hibernate.annotations.OnDelete;
21+
import org.hibernate.annotations.OnDeleteAction;
2022

2123
@Entity
2224
@Table(
@@ -59,12 +61,14 @@ public class MeetingEntity {
5961

6062
@OneToMany(mappedBy = "meeting", cascade = CascadeType.ALL, orphanRemoval = true)
6163
@Builder.Default
64+
@OnDelete(action = OnDeleteAction.CASCADE) // DB에 ON DELETE CASCADE 설정할시 자식들에 delete 쿼리 안보냄
6265
private List<MeetingMemberEntity> meetingMembers = new ArrayList<>();
6366

6467
@ElementCollection
6568
@CollectionTable(name = "meeting_platform_urls", joinColumns = @JoinColumn(name = "meeting_id"))
6669
@Column(name = "platform_url", length = 500)
6770
@Builder.Default
71+
@OnDelete(action = OnDeleteAction.CASCADE)
6872
private List<String> platformUrls = new ArrayList<>();
6973

7074
@Column(nullable = false)
@@ -120,21 +124,11 @@ public Meeting toDomain() {
120124
}
121125

122126
public void addMember(MeetingMemberEntity member) {
123-
this.meetingMembers.add(member);
124-
increaseMemberCount();
125-
}
126-
127-
public void increaseMemberCount() {
128-
if (this.currentMemberCount >= this.maxMemberCount) {
127+
if (this.meetingMembers.size() >= this.maxMemberCount) {
129128
throw new CustomException(MeetingErrorCode.MEETING_MEMBER_LIMIT_REACHED);
130129
}
131-
this.currentMemberCount++;
132-
}
133-
134-
public void decreaseMemberCount() {
135-
if (this.currentMemberCount > 0) {
136-
this.currentMemberCount--;
137-
}
130+
this.meetingMembers.add(member);
131+
this.currentMemberCount = this.meetingMembers.size();
138132
}
139133

140134
public void applyFromDomain(Meeting meeting) {
@@ -143,7 +137,7 @@ public void applyFromDomain(Meeting meeting) {
143137
this.meetingImage = meeting.getMeetingImage();
144138
this.category = meeting.getCategory();
145139
this.maxMemberCount = meeting.getMaxMemberCount();
146-
this.currentMemberCount = meeting.getCurrentMemberCount();
140+
this.currentMemberCount = this.meetingMembers.size();
147141

148142
this.platformUrls.clear();
149143
this.platformUrls.addAll(meeting.getPlatformURL());

src/main/java/org/codeit/roomunion/meeting/adapter/out/persistence/entity/MeetingMemberEntity.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ public boolean isHost() {
4646
return this.meetingRole == MeetingRole.HOST;
4747
}
4848

49-
public static MeetingMemberEntity of(MeetingEntity meeting, UserEntity user, MeetingRole role) {
49+
public static MeetingMemberEntity of(MeetingEntity meeting, UserEntity user) {
5050
return MeetingMemberEntity.builder()
5151
.meeting(meeting)
5252
.user(user)
53-
.meetingRole(role)
53+
.meetingRole(MeetingRole.MEMBER)
5454
.build();
5555
}
5656

src/main/java/org/codeit/roomunion/meeting/adapter/out/persistence/jpa/MeetingJpaRepository.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package org.codeit.roomunion.meeting.adapter.out.persistence.jpa;
22

3+
import jakarta.persistence.LockModeType;
4+
import java.util.List;
35
import org.codeit.roomunion.meeting.adapter.out.persistence.entity.MeetingEntity;
46
import org.codeit.roomunion.meeting.domain.model.MeetingCategory;
7+
import org.codeit.roomunion.meeting.domain.model.MeetingRole;
58
import org.springframework.data.domain.Page;
69
import org.springframework.data.domain.Pageable;
710
import org.springframework.data.jpa.repository.EntityGraph;
811
import org.springframework.data.jpa.repository.JpaRepository;
12+
import org.springframework.data.jpa.repository.Lock;
913
import org.springframework.data.jpa.repository.Query;
1014
import org.springframework.data.repository.query.Param;
1115

12-
import java.util.List;
1316
import java.util.Optional;
1417

1518

@@ -62,4 +65,30 @@ Page<MeetingEntity> findByCategoryOrderByMemberCountDesc(
6265
""")
6366
List<MeetingEntity> findAllWithPlatformUrlsByIdIn(@Param("ids") List<Long> ids);
6467

68+
69+
@EntityGraph(attributePaths = {"meetingMembers", "meetingMembers.user"})
70+
@Query("""
71+
select m
72+
from MeetingEntity m
73+
join m.meetingMembers mm
74+
where mm.user.id = :userId
75+
and mm.meetingRole = :role
76+
order by m.createdAt desc
77+
""")
78+
Page<MeetingEntity> findByUserAndRole(
79+
@Param("userId") Long userId,
80+
@Param("role") MeetingRole role,
81+
Pageable pageable
82+
);
83+
84+
@Lock(LockModeType.PESSIMISTIC_WRITE)
85+
@Query("""
86+
select m from MeetingEntity m
87+
left join fetch m.meetingMembers mm
88+
left join fetch mm.user
89+
where m.id = :id
90+
""")
91+
Optional<MeetingEntity> findWithLockById(@Param("id") Long id);
92+
93+
6594
}

src/main/java/org/codeit/roomunion/meeting/adapter/out/persistence/jpa/MeetingMemberJpaRepository.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,8 @@ boolean existsMeetingByHostAndName(@Param("userId") Long userId,
3535

3636
int countByMeetingId(Long meetingId);
3737

38-
@Query("""
39-
select case when exists (
40-
select 1
41-
from MeetingMemberEntity mm
42-
where mm.meeting.id = :meetingId
43-
and mm.user.id = :userId
44-
) then true else false end
45-
""")
46-
boolean existsByMeetingIdAndUserId(@Param("meetingId") Long meetingId,
47-
@Param("userId") Long userId);
38+
// MeetingMemberEntity에 meeting_id, user_id로 매핑되어서 메소드명 이렇게 지어야함
39+
boolean existsByMeeting_IdAndUser_Id(Long meetingId, Long userId);
4840

4941
@Query("""
5042
select case when exists (

0 commit comments

Comments
 (0)