Skip to content

Commit baa0659

Browse files
authored
Feature/188 쪽지 상세 보기 기능 구현 (#193)
* feat: 기관의 자신에게 온 쪽지 상세 내용 확인 기능 쿼리 메서드 구현 - 쿼리 메서드 생성 - mapper record 클래 생성 - 테스트 코드 작성및 검증 완료 * feat: 봉사자의 자신에게 온 쪽지 상세 내용 확인 기능 쿼리 메서드 구현 - 쿼리 메서드 생성 - mapper record 클래스 생성 - 테스트 코드 작성및 검증 완료 * feat: NoSuchElement 예외 추가 - NoSuchElementException 클래스 추가 - 예외메세지 추가 - 전역 예외처리 핸들러에 NoSuchElementException 추가 * refactor: NoSuchElementException 클래스 생성자 리팩토링 - ExceptionMessage enum 값의 내부 메세지가 아닌 ExceptionMessage enum 자체를 이용하여 생성하도록 수정 * feat: 쪽지 상세 조회 기능 서비스 레이어 구현 - 기관의 쪽지 상세보기 메서드 구현 - 봉사자의 쪽지 상세보기 메서드 구현 - 테스트 코드 작성및 검증 완료 * feat: 쪽지 상세 조회 기능 프레젠테이션 레이어 구현 - 컨트롤러 구현 - 테스트 코드 작성및 검증 완료 * fix: 테스트 코드의 날짜 비교 검증 수정 - 날짜 비교 검증 수 * chore: 코드 리뷰 사항 반영 - 컨트롤러 swagger 설명 수정 - 매직 스트링의 상수화
1 parent e48db8e commit baa0659

File tree

13 files changed

+438
-24
lines changed

13 files changed

+438
-24
lines changed

src/main/java/com/somemore/global/exception/ExceptionMessage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public enum ExceptionMessage {
3737
UNAUTHORIZED_NOTIFICATION("해당 알림에 권한이 없습니다."),
3838
VOLUNTEER_APPLY_LIST_MISMATCH("봉사 지원 목록과 요청된 봉사 지원 목록이 일치하지 않습니다."),
3939
RECRUIT_BOARD_ID_MISMATCH("모든 봉사 신청이 동일한 모집글 ID를 가져야 합니다."),
40+
NOT_EXISTS_NOTE("존재하지 않는 쪽지입니다.")
4041

4142
;
4243
private final String message;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.somemore.global.exception;
2+
3+
public class NoSuchElementException extends RuntimeException{
4+
public NoSuchElementException(final ExceptionMessage message) {
5+
super(message.getMessage());
6+
}
7+
}

src/main/java/com/somemore/global/handler/GlobalExceptionHandler.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.somemore.global.exception.BadRequestException;
44
import com.somemore.global.exception.DuplicateException;
55
import com.somemore.global.exception.ImageUploadException;
6+
import com.somemore.global.exception.NoSuchElementException;
67
import org.springframework.http.HttpStatus;
78
import org.springframework.http.ProblemDetail;
89
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -56,4 +57,13 @@ ProblemDetail handleMethodArgumentNotValid(final MethodArgumentNotValidException
5657
return problemDetail;
5758
}
5859

60+
@ExceptionHandler(NoSuchElementException.class)
61+
ProblemDetail handleNoSuchElementException(final NoSuchElementException e) {
62+
63+
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.getMessage());
64+
problemDetail.setTitle("데이터가 존재하지 않음");
65+
66+
return problemDetail;
67+
}
68+
5969
}

src/main/java/com/somemore/note/controller/NoteQueryApiController.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.somemore.auth.annotation.CurrentUser;
44
import com.somemore.global.common.response.ApiResponse;
5+
import com.somemore.note.repository.mapper.NoteDetailViewForCenter;
6+
import com.somemore.note.repository.mapper.NoteDetailViewForVolunteer;
57
import com.somemore.note.repository.mapper.NoteReceiverViewForCenter;
68
import com.somemore.note.repository.mapper.NoteReceiverViewForVolunteer;
79
import com.somemore.note.usecase.NoteQueryUseCase;
@@ -43,4 +45,24 @@ public ApiResponse<Page<NoteReceiverViewForVolunteer>> getNotesByVolunteerId(@Cu
4345
return ApiResponse.ok(200, response, "내 쪽지 조회 성공");
4446
}
4547

48+
@Secured("ROLE_CENTER")
49+
@Operation(summary = "기관의 자신에게 온 쪽지 상세 조회")
50+
@GetMapping("/center/{noteId}")
51+
public ApiResponse<NoteDetailViewForCenter> getNoteDetailForCenter(@PathVariable Long noteId) {
52+
53+
NoteDetailViewForCenter response = noteQueryUseCase.getNoteDetailForCenter(noteId);
54+
55+
return ApiResponse.ok(200, response, "쪽지 상세 조회 성공");
56+
}
57+
58+
@Secured("ROLE_VOLUNTEER")
59+
@Operation(summary = "봉사자의 자신에게 온 쪽지 상세 조회")
60+
@GetMapping("/volunteer/{noteId}")
61+
public ApiResponse<NoteDetailViewForVolunteer> getNoteDetailForVolunteer(@PathVariable Long noteId) {
62+
63+
NoteDetailViewForVolunteer response = noteQueryUseCase.getNoteDetailForVolunteer(noteId);
64+
65+
return ApiResponse.ok(200, response, "쪽지 상세 조회 성공");
66+
}
67+
4668
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package com.somemore.note.repository;
22

33
import com.somemore.note.domain.Note;
4+
import com.somemore.note.repository.mapper.NoteDetailViewForCenter;
5+
import com.somemore.note.repository.mapper.NoteDetailViewForVolunteer;
46
import com.somemore.note.repository.mapper.NoteReceiverViewForCenter;
57
import com.somemore.note.repository.mapper.NoteReceiverViewForVolunteer;
68
import org.springframework.data.domain.Page;
79
import org.springframework.data.domain.Pageable;
810

11+
import java.util.Optional;
912
import java.util.UUID;
1013

1114
public interface NoteRepository {
1215
Note save(Note note);
1316
Page<NoteReceiverViewForCenter> findNotesByReceiverIsCenter(UUID centerId, Pageable pageable);
1417
Page<NoteReceiverViewForVolunteer> findNotesByReceiverIsVolunteer(UUID volunteerId, Pageable pageable);
18+
Optional<NoteDetailViewForCenter> findNoteDetailViewReceiverIsCenter(Long noteId);
19+
Optional<NoteDetailViewForVolunteer> findNoteDetailViewReceiverIsVolunteer(Long noteId);
1520
}

src/main/java/com/somemore/note/repository/NoteRepositoryImpl.java

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.somemore.center.domain.QCenter;
88
import com.somemore.note.domain.Note;
99
import com.somemore.note.domain.QNote;
10+
import com.somemore.note.repository.mapper.NoteDetailViewForCenter;
11+
import com.somemore.note.repository.mapper.NoteDetailViewForVolunteer;
1012
import com.somemore.note.repository.mapper.NoteReceiverViewForCenter;
1113
import com.somemore.note.repository.mapper.NoteReceiverViewForVolunteer;
1214
import com.somemore.volunteer.domain.QVolunteer;
@@ -17,6 +19,7 @@
1719
import org.springframework.stereotype.Repository;
1820

1921
import java.util.List;
22+
import java.util.Optional;
2023
import java.util.UUID;
2124

2225
@RequiredArgsConstructor
@@ -30,6 +33,10 @@ public class NoteRepositoryImpl implements NoteRepository {
3033
private static final QVolunteer volunteer = QVolunteer.volunteer;
3134
private static final QCenter center = QCenter.center;
3235

36+
private static final String SENDER_ID = "senderId";
37+
private static final String SENDER_NAME = "senderName";
38+
private static final String SENDER_PROFILE_IMG_LINK = "senderProfileImgLink";
39+
3340
@Override
3441
public Note save(Note note) {
3542
return noteJpaRepository.save(note);
@@ -38,17 +45,16 @@ public Note save(Note note) {
3845
@Override
3946
public Page<NoteReceiverViewForCenter> findNotesByReceiverIsCenter(UUID centerId, Pageable pageable) {
4047

41-
BooleanExpression activeVolunteer = volunteer.deleted.eq(false);
42-
BooleanExpression condition = note.receiverId.eq(centerId)
43-
.and(note.deleted.eq(false));
48+
BooleanExpression activeVolunteer = isActiveVolunteer();
49+
BooleanExpression condition = isReceiver(centerId);
4450

4551
List<NoteReceiverViewForCenter> results = queryFactory
4652
.select(Projections.constructor(
4753
NoteReceiverViewForCenter.class,
4854
note.id,
4955
note.title,
50-
volunteer.id.as("senderId"),
51-
volunteer.nickname.as("senderName"),
56+
volunteer.id.as(SENDER_ID),
57+
volunteer.nickname.as(SENDER_NAME),
5258
note.isRead
5359
))
5460
.from(note)
@@ -69,17 +75,16 @@ public Page<NoteReceiverViewForCenter> findNotesByReceiverIsCenter(UUID centerId
6975

7076
@Override
7177
public Page<NoteReceiverViewForVolunteer> findNotesByReceiverIsVolunteer(UUID volunteerId, Pageable pageable) {
72-
BooleanExpression activeCenter = center.deleted.eq(false);
73-
BooleanExpression condition = note.receiverId.eq(volunteerId)
74-
.and(note.deleted.eq(false));
78+
BooleanExpression activeCenter = isActiveCenter();
79+
BooleanExpression condition = isReceiver(volunteerId);
7580

7681
List<NoteReceiverViewForVolunteer> results = queryFactory
7782
.select(Projections.constructor(
7883
NoteReceiverViewForVolunteer.class,
7984
note.id,
8085
note.title,
81-
center.id.as("senderId"),
82-
center.name.as("senderName"),
86+
center.id.as(SENDER_ID),
87+
center.name.as(SENDER_NAME),
8388
note.isRead
8489
))
8590
.from(note)
@@ -98,4 +103,65 @@ public Page<NoteReceiverViewForVolunteer> findNotesByReceiverIsVolunteer(UUID vo
98103
return PageableExecutionUtils.getPage(results, pageable, count::fetchOne);
99104
}
100105

106+
@Override
107+
public Optional<NoteDetailViewForCenter> findNoteDetailViewReceiverIsCenter(Long noteId) {
108+
109+
BooleanExpression activeVolunteer = isActiveVolunteer();
110+
111+
NoteDetailViewForCenter result = queryFactory
112+
.select(Projections.constructor(
113+
NoteDetailViewForCenter.class,
114+
note.id,
115+
note.title,
116+
note.content,
117+
volunteer.id.as(SENDER_ID),
118+
volunteer.nickname.as(SENDER_NAME),
119+
volunteer.imgUrl.as(SENDER_PROFILE_IMG_LINK),
120+
note.createdAt
121+
))
122+
.from(note)
123+
.join(volunteer).on(note.senderId.eq(volunteer.id).and(activeVolunteer))
124+
.where(note.id.eq(noteId).and(note.deleted.eq(false)))
125+
.fetchOne();
126+
127+
return Optional.ofNullable(result);
128+
}
129+
130+
@Override
131+
public Optional<NoteDetailViewForVolunteer> findNoteDetailViewReceiverIsVolunteer(Long noteId) {
132+
133+
BooleanExpression activeCenter = isActiveCenter();
134+
135+
NoteDetailViewForVolunteer result = queryFactory
136+
.select(Projections.constructor(
137+
NoteDetailViewForVolunteer.class,
138+
note.id,
139+
note.title,
140+
note.content,
141+
center.id.as(SENDER_ID),
142+
center.name.as(SENDER_NAME),
143+
center.imgUrl.as(SENDER_PROFILE_IMG_LINK),
144+
note.createdAt
145+
))
146+
.from(note)
147+
.join(center).on(note.senderId.eq(center.id).and(activeCenter))
148+
.where(note.id.eq(noteId).and(note.deleted.eq(false)))
149+
.fetchOne();
150+
151+
return Optional.ofNullable(result);
152+
}
153+
154+
private static BooleanExpression isReceiver(UUID receiverId) {
155+
return note.receiverId.eq(receiverId)
156+
.and(note.deleted.eq(false));
157+
}
158+
159+
private BooleanExpression isActiveVolunteer() {
160+
return volunteer.deleted.eq(false);
161+
}
162+
163+
private static BooleanExpression isActiveCenter() {
164+
return center.deleted.eq(false);
165+
}
166+
101167
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.somemore.note.repository.mapper;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.UUID;
8+
9+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
10+
public record NoteDetailViewForCenter(
11+
Long noteId,
12+
String title,
13+
String content,
14+
UUID senderId,
15+
String senderName,
16+
String senderProfileImgLink,
17+
LocalDateTime createdAt
18+
) {
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.somemore.note.repository.mapper;
2+
3+
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
4+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.UUID;
8+
9+
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
10+
public record NoteDetailViewForVolunteer(
11+
Long noteId,
12+
String title,
13+
String content,
14+
UUID senderId,
15+
String senderName,
16+
String senderProfileImgLink,
17+
LocalDateTime createdAt
18+
) {
19+
}

src/main/java/com/somemore/note/service/NoteQueryService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package com.somemore.note.service;
22

3+
import com.somemore.global.exception.NoSuchElementException;
34
import com.somemore.note.repository.NoteRepository;
5+
import com.somemore.note.repository.mapper.NoteDetailViewForCenter;
6+
import com.somemore.note.repository.mapper.NoteDetailViewForVolunteer;
47
import com.somemore.note.repository.mapper.NoteReceiverViewForCenter;
58
import com.somemore.note.repository.mapper.NoteReceiverViewForVolunteer;
69
import com.somemore.note.usecase.NoteQueryUseCase;
@@ -12,6 +15,8 @@
1215

1316
import java.util.UUID;
1417

18+
import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_NOTE;
19+
1520
@RequiredArgsConstructor
1621
@Service
1722
@Transactional(readOnly = true)
@@ -28,4 +33,17 @@ public Page<NoteReceiverViewForCenter> getNotesForCenter(UUID centerId, Pageable
2833
public Page<NoteReceiverViewForVolunteer> getNotesForVolunteer(UUID volunteerId, Pageable pageable) {
2934
return noteRepository.findNotesByReceiverIsVolunteer(volunteerId, pageable);
3035
}
36+
37+
@Override
38+
public NoteDetailViewForCenter getNoteDetailForCenter(Long noteId) {
39+
return noteRepository.findNoteDetailViewReceiverIsCenter(noteId)
40+
.orElseThrow(() -> new NoSuchElementException(NOT_EXISTS_NOTE));
41+
}
42+
43+
@Override
44+
public NoteDetailViewForVolunteer getNoteDetailForVolunteer(Long noteId) {
45+
return noteRepository.findNoteDetailViewReceiverIsVolunteer(noteId)
46+
.orElseThrow(() -> new NoSuchElementException(NOT_EXISTS_NOTE));
47+
}
48+
3149
}

src/main/java/com/somemore/note/usecase/NoteQueryUseCase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.somemore.note.usecase;
22

3+
import com.somemore.note.repository.mapper.NoteDetailViewForCenter;
4+
import com.somemore.note.repository.mapper.NoteDetailViewForVolunteer;
35
import com.somemore.note.repository.mapper.NoteReceiverViewForCenter;
46
import com.somemore.note.repository.mapper.NoteReceiverViewForVolunteer;
57
import org.springframework.data.domain.Page;
@@ -10,4 +12,6 @@
1012
public interface NoteQueryUseCase {
1113
Page<NoteReceiverViewForCenter> getNotesForCenter(UUID centerId, Pageable pageable);
1214
Page<NoteReceiverViewForVolunteer> getNotesForVolunteer(UUID volunteerId, Pageable pageable);
15+
NoteDetailViewForCenter getNoteDetailForCenter(Long noteId);
16+
NoteDetailViewForVolunteer getNoteDetailForVolunteer(Long noteId);
1317
}

0 commit comments

Comments
 (0)