Conversation
| // 다건 조회 (특정 메시지의 첨부파일 목록 등) | ||
| // 사용자가 메시지를 보낼 때 사진 3장을 한 번에 올렸다고 가정해보면 | ||
| // 프론트에서 사진 3장의 상세 정보를 가져오기 위해 다건 조회 | ||
| @RequestMapping(value = "/find",method = RequestMethod.GET) | ||
| public ResponseEntity<BinaryContent> findBinaryContent( | ||
| @RequestParam UUID binaryContentId | ||
| ) { | ||
| BinaryContent content = binaryContentService.findContent(binaryContentId); | ||
| return ResponseEntity.ok(content); | ||
| } |
There was a problem hiding this comment.
RESTFul API에서 GET이 이미 자원에 대한 조회의 의미를 내포하기 때문에 /find 굳이 추가할 필요 없습니다!
There was a problem hiding this comment.
// GET /api/binaryContents - 여러 첨부 파일 조회
@GetMapping
public ResponseEntity<List<BinaryContentResponse>> findAllByIdIn(
@RequestParam("binaryContentIds") List<UUID> binaryContentIds
) {
List<BinaryContentResponse> binaryContents = binaryContentService.findAllByIdIn(binaryContentIds);
return ResponseEntity.ok(binaryContents);
}- 위와 같이 수정하였습니다.
| // 공개 채널을 생성할 수 있다. | ||
| @RequestMapping(value = "/public", method = RequestMethod.POST) | ||
| public ResponseEntity<ChannelResponse> createPublic(@RequestBody PublicChannelCreateRequest request) { | ||
| ChannelResponse response = channelService.createPublic(request); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(response); | ||
| } | ||
|
|
||
| // 비공개 채널을 생성할 수 있다. | ||
| @RequestMapping(value = "/private", method = RequestMethod.POST) | ||
| public ResponseEntity<ChannelResponse> creatPrivate(@RequestBody PrivateChannelCreateRequest request) { | ||
| ChannelResponse response = channelService.createPrivate(request); | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(response); | ||
| } |
There was a problem hiding this comment.
과제에서 요구사항 대로 하신 것 같아 이해하지만, [POST] "/api/channels/public, private" private, public을 Body 값 안에 넣어서 처리하는 방식이 더 깔끔할 것 같긴 합니다. 정말 분리되어야 하면 모르겠지만 거의 구현 코드가 비슷할거거든요
There was a problem hiding this comment.
public/private 여부를 Body 값으로 처리하는 것이 '리소스에 대한 행위는 HTTP 메서드로, 구체적인 특성은 데이터(Body)로'라는 측면에서 더 Restful한 방식이라는 점에 동의합니다.
다만, 이번 과제에서 제시된 API 명세서를 준수하기 위해 현재 구조를 유지하게 되었습니다.
| public record MessageResponse ( | ||
| UUID id, | ||
| String content, | ||
| UUID channelId, | ||
| UUID authorId, | ||
| List<UUID> attachmentIds, | ||
| Instant createAt | ||
| ) {} |
There was a problem hiding this comment.
ResponseDTO 별도로 선언해주신 것 좋네요 👍
| private MessageResponse toResponse(Message message) { | ||
| return new MessageResponse( | ||
| message.getId(), | ||
| message.getContent(), | ||
| message.getChannelId(), | ||
| message.getAuthorId(), | ||
| message.getAttachmentIds(), | ||
| message.getCreatedAt() | ||
| ); | ||
| } |
There was a problem hiding this comment.
ResponseDto로 변환하는 역할이 Service의 책임일까요? 한번 고민해보시면 좋을 것 같습니다!
There was a problem hiding this comment.
DTO 변환 로직은 Controller나 별도의 Mapper로 분리하는 방향으로 개선하는 것이 좋은 이유에 대해서 배울 수 있었습니다.
DTO는 보통 클라이언트에 보여줄 데이터 규격인데, Service가 DTO를 직접 만들면, 화면 요구사항이 바뀔 때마다 Service 코드까지 수정해야 된다는 문제를 인식했습니다.
스프린트 미션 5 제출 시 해당 내용 적용하도록 하겠습니다.
| User user = userRepository.findById(userId) | ||
| .orElseThrow(() -> new NoSuchElementException("해당 유저를 찾을 수 없습니다. id: " + userId)); | ||
|
|
||
| // 새 프로필 이미지가 전달되었다면 저장 - 이미지 수정 안했을 경우 기존 id 유지 | ||
| UUID newProfileImageId = user.getProfileImageId(); | ||
|
|
||
| if(request.profileImage() != null) { | ||
| // 기존에 설정된 프로필 이미지가 있다면 삭제 | ||
| // TODO: 현재 profileId가 null일 경우 기본 프로필 설정으므로 / 기존 설정된 프로필 이미지가 기본 프로필인지 구분하기 위해 | ||
| // 기본 프로필 설정을 null이 아니라 명시적으로 따른 사진으로 지정해 줄 필요성 있어보임 -> entity 수정 필요 | ||
| if(user.getProfileImageId() != null) { | ||
| binaryContentRepository.deleteById(user.getProfileImageId()); | ||
| } | ||
|
|
||
| // 새 이미지 생성 및 저장 | ||
| BinaryContent binaryContent = createBinaryContent(request.profileImage()); | ||
| binaryContentRepository.save(binaryContent); | ||
|
|
||
| // 새 ID로 교체 | ||
| newProfileImageId = binaryContent.getId(); | ||
| } | ||
|
|
||
| // 유저 정보 수정 | ||
| user.update( | ||
| request.username(), | ||
| request.email(), | ||
| request.password(), | ||
| newProfileImageId | ||
| ); | ||
| User updatedUser = userRepository.save(user); | ||
|
|
||
| UserStatus status = userStatusRepository.findByUserId(userId) | ||
| .orElseThrow(() -> new IllegalStateException("유저 상태 데이터가 누락되었습니다. id: " + userId)); | ||
|
|
||
| return toResponse(updatedUser, status); |
There was a problem hiding this comment.
각각 별도의 메서드로 분리할 수 있을 것 같은데 한번 분리해보시죠~!
There was a problem hiding this comment.
@Transactional
@Override
public User update(UUID userId,
UserUpdateRequest userUpdateRequest,
Optional<BinaryContentCreateRequest> optionalProfileCreateRequest) {
// 유저 존재 확인
User user = findByUserId(userId);
String newUsername = userUpdateRequest.newUsername();
String newEmail = userUpdateRequest.newEmail();
// FIX: 본인의 현재 정보와 다를 때만 중복 검사 실시
if (!user.getEmail().equals(newEmail) && userRepository.existsByEmail(newEmail)) {
throw new IllegalArgumentException("이미 사용중인 이메일(email)입니다.: " + newEmail);
}
if (!user.getUsername().equals(newUsername) && userRepository.existsByUsername(newUsername)) {
throw new IllegalArgumentException("이미 존재하는 사용자 이름(username)입니다.: " + newUsername);
}
// 회원가입 시 사진을 등록했다면 기본 프로필로 돌아갈 수 있는 기능 없음
// -> 업데이트 시 파일이 존재하면 기존 사진 삭제 후 새로 저장, 파일이 없으면 기존 사진 유지
UUID nullableProfileId = resolveNullableProfileId(user, optionalProfileCreateRequest);
// user 업데이트 및 저장
user.update(newUsername, newEmail, userUpdateRequest.newPassword(), nullableProfileId);
return userRepository.save(user);
}private UUID resolveNullableProfileId(User user,
Optional<BinaryContentCreateRequest> optionalProfileCreateRequest) {
return optionalProfileCreateRequest
.map(profileRequest -> {
Optional.ofNullable(user.getProfileId())
.ifPresent(binaryContentRepository::deleteById);
BinaryContent binaryContent = new BinaryContent(
profileRequest.fileName(),
profileRequest.contentType(),
profileRequest.bytes()
);
return binaryContentRepository.save(binaryContent).getId();
})
.orElse(user.getProfileId());
}- 전반적으로 메서드 수정하였습니다.
- 일부 별도의 메서드로 분리하였습니다.
[SB] 스프린트 미션 4
🏔️ 프로젝트 마일스톤
📝 요구사항
✏️ 기본 요구사항
컨트롤러 레이어 구현
@RequestMapping만 사용해 구현해보세요.(웹 API 요구사항)
API 테스트
웹 API 요구사항
👤 사용자 관리
🔑 권한 관리
💬 채널 관리
✉️ 메시지 관리
📥 메시지 수신 정보 관리
📁 바이너리 파일 다운로드
✏️ 심화 요구사항
📂 정적 리소스 서빙
/api/user/findAllResponseEntity<List<UserDto>>/api/binaryContent/findbinaryContentIdResponseEntity<BinaryContent>🤖 생성형 AI 활용
🔄 주요 변경사항
🙇🏽♂️ 멘토에게