Skip to content

[백은호] Sprint7 #204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: 백은호
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9fc5239
feat: 개발 및 프로덕션 환경 설정 추가
BackEunHo Jun 23, 2025
0f22106
feat: 로깅 기능 추가 및 API 요청/응답 로깅 구현
BackEunHo Jun 23, 2025
df96c86
feat: 사용자 정의 예외 처리 고도화 및 새로운 예외 클래스 추가
BackEunHo Jun 23, 2025
371824d
feat: 사용자 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
0432f07
feat: 채널 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
e79ab05
feat: 메시지 도메인 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
961e28a
feat: 바이너리 콘텐츠 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
cf7490a
feat: 사용자 상태 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
030b44d
feat: 읽기 상태 관련 예외 클래스 추가
BackEunHo Jun 23, 2025
10b2417
feat: 요청 DTO에 유효성 검사 추가
BackEunHo Jun 23, 2025
8cfea60
feat: 유효성 검사 어노테이션 추가
BackEunHo Jun 23, 2025
4e344a6
feat: 예외 처리 개선 및 서비스 레이어 반영
BackEunHo Jun 23, 2025
ee15a7e
config: Actuator 설정 및 환경별 구성 추가
BackEunHo Jun 24, 2025
b97c4be
feat: 기본 서비스 단위 테스트 추가
BackEunHo Jun 29, 2025
c2441c6
feat: 중복 참가자 예외 처리 추가 및 검증 로직 개선
BackEunHo Jul 1, 2025
4b2c525
feat: BasicBinaryContentService 및 BasicUserStatusService 단위 테스트 추가
BackEunHo Jul 1, 2025
f64f96b
feat: H2 데이터베이스 의존성 추가 및 테스트 환경 설정 파일 생성
BackEunHo Jul 2, 2025
3ba9593
feat: UserStatusRepository에 사용자 ID로 상태 삭제 메서드 추가 및 도메인별 repisitory 테스…
BackEunHo Jul 2, 2025
16a318e
feat: 기본 서비스 단위 테스트 개선 및 중복 코드 제거
BackEunHo Jul 3, 2025
b10a2b1
feat: Controller 레이어 Slice Test 구현
BackEunHo Jul 3, 2025
bbe6b4a
feat: 도메인별 통합 테스트 추가
BackEunHo Jul 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
file-data
storage

### Logs ###
.logs/
*.log

### IntelliJ IDEA ###
.idea
.idea/modules.xml
Expand Down
8 changes: 4 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ dependencies {
implementation 'org.springframework.data:spring-data-commons'
implementation 'org.postgresql:postgresql'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.6'

// MapStruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final'

implementation 'org.mapstruct:mapstruct:1.5.5.Final' // MapStruct
implementation 'org.springframework.boot:spring-boot-starter-actuator' // Actuator

compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation 'com.h2database:h2'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;

import com.sprint.mission.discodeit.controller.api.AuthApi;
import com.sprint.mission.discodeit.dto.mapper.ResponseMapper;
Expand All @@ -24,7 +25,7 @@ public class AuthController implements AuthApi {
private final AuthService authService;

@PostMapping(path = "login")
public ResponseEntity<UserResponse> login(@RequestBody LoginRequest loginRequest) {
public ResponseEntity<UserResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
User user = authService.login(loginRequest);
UserResponse userResponse = ResponseMapper.toResponse(user);
return ResponseEntity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.sprint.mission.discodeit.service.BinaryContentService;
import com.sprint.mission.discodeit.storage.BinaryContentStorage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
Expand All @@ -19,6 +20,7 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/binaryContents")
@Slf4j
public class BinaryContentController implements BinaryContentApi {

private final BinaryContentService binaryContentService;
Expand All @@ -27,8 +29,14 @@ public class BinaryContentController implements BinaryContentApi {

@GetMapping(path = "{binaryContentId}")
public ResponseEntity<BinaryContentResponse> find(@PathVariable("binaryContentId") UUID binaryContentId) {
log.debug("바이너리 콘텐츠 조회 API 요청 - ID: {}", binaryContentId);

BinaryContent binaryContent = binaryContentService.find(binaryContentId);
BinaryContentResponse response = ResponseMapper.toResponse(binaryContent);

log.debug("바이너리 콘텐츠 조회 API 응답 - ID: {}, 파일명: {}, 크기: {} bytes",
response.id(), response.fileName(), response.size());

return ResponseEntity
.status(HttpStatus.OK)
.body(response);
Expand All @@ -37,19 +45,36 @@ public ResponseEntity<BinaryContentResponse> find(@PathVariable("binaryContentId
@GetMapping
public ResponseEntity<List<BinaryContentResponse>> findAllByIdIn(
@RequestParam("binaryContentIds") List<UUID> binaryContentIds) {
log.debug("다중 바이너리 콘텐츠 조회 API 요청 - ID 개수: {}", binaryContentIds.size());

List<BinaryContent> binaryContents = binaryContentService.findAllByIdIn(binaryContentIds);
List<BinaryContentResponse> responses = binaryContents.stream()
.map(ResponseMapper::toResponse)
.toList();

log.debug("다중 바이너리 콘텐츠 조회 API 응답 - 요청: {}, 응답: {}",
binaryContentIds.size(), responses.size());

return ResponseEntity
.status(HttpStatus.OK)
.body(responses);
}

@GetMapping(path = "{binaryContentId}/download")
public ResponseEntity<?> download(@PathVariable("binaryContentId") UUID binaryContentId) {
log.info("파일 다운로드 API 요청 - ID: {}", binaryContentId);

BinaryContent binaryContent = binaryContentService.find(binaryContentId);
BinaryContentDto binaryContentDto = mapperFacade.toDto(binaryContent);
return binaryContentStorage.download(binaryContentDto);

log.info("파일 다운로드 시작 - ID: {}, 파일명: {}, 크기: {} bytes",
binaryContentDto.id(), binaryContentDto.fileName(), binaryContentDto.size());

ResponseEntity<?> response = binaryContentStorage.download(binaryContentDto);

log.info("파일 다운로드 완료 - ID: {}, 파일명: {}",
binaryContentDto.id(), binaryContentDto.fileName());

return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ public class ChannelController implements ChannelApi {
@PostMapping(path = "public")
@Transactional
public ResponseEntity<ChannelDto> create(@Valid @RequestBody PublicChannelCreateRequest request) {
log.info("공개 채널 생성 API 요청 - 이름: {}, 설명: {}", request.name(), request.description());

Channel createdChannel = channelService.create(request);
ChannelDto response = mapperFacade.toDto(createdChannel);

log.info("공개 채널 생성 API 응답 - 채널 ID: {}, 이름: {}", response.id(), response.name());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(response);
Expand All @@ -44,9 +47,13 @@ public ResponseEntity<ChannelDto> create(@Valid @RequestBody PublicChannelCreate
@PostMapping(path = "private")
@Transactional
public ResponseEntity<ChannelDto> create(@Valid @RequestBody PrivateChannelCreateRequest request) {
log.info("비공개 채널 생성 API 요청 - 참가자 수: {}", request.participantIds().size());

Channel createdChannel = channelService.create(request);
ChannelDto response = mapperFacade.toDto(createdChannel);

log.info("비공개 채널 생성 API 응답 - 채널 ID: {}, 참가자 수: {}",
response.id(), response.participants().size());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(response);
Expand All @@ -56,27 +63,37 @@ public ResponseEntity<ChannelDto> create(@Valid @RequestBody PrivateChannelCreat
public ResponseEntity<ChannelResponse> update(
@PathVariable("channelId") UUID channelId,
@Valid @RequestBody PublicChannelUpdateRequest request) {
log.info("채널 수정 API 요청 - 채널 ID: {}, 새 이름: {}", channelId, request.newName());

Channel updatedChannel = channelService.update(channelId, request);
ChannelResponse response = ResponseMapper.toResponse(updatedChannel);

log.info("채널 수정 API 응답 - 채널 ID: {}, 수정된 이름: {}",
response.id(), response.name());
return ResponseEntity
.status(HttpStatus.OK)
.body(response);
}

@DeleteMapping(path = "{channelId}")
public ResponseEntity<Void> delete(@PathVariable("channelId") UUID channelId) {
log.info("채널 삭제 API 요청 - 채널 ID: {}", channelId);

channelService.delete(channelId);

log.info("채널 삭제 API 완료 - 채널 ID: {}", channelId);
return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.build();
}

@GetMapping
public ResponseEntity<List<ChannelDto>> findAll(@RequestParam("userId") UUID userId) {
log.debug("사용자 채널 목록 API 요청 - 사용자 ID: {}", userId);

List<ChannelDto> channels = channelService.findAllByUserId(userId);

log.debug("사용자 채널 목록 API 응답 - 사용자 ID: {}, 채널 수: {}", userId, channels.size());
return ResponseEntity
.status(HttpStatus.OK)
.body(channels);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
import com.sprint.mission.discodeit.dto.request.MessageUpdateRequest;
import com.sprint.mission.discodeit.dto.response.PageResponse;
import com.sprint.mission.discodeit.service.MessageService;
import com.sprint.mission.discodeit.exception.CustomException;
import com.sprint.mission.discodeit.exception.binarycontent.FileUploadFailedException;
import com.sprint.mission.discodeit.common.Constants;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
Expand All @@ -28,6 +29,7 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/messages")
@Slf4j
public class MessageController implements MessageApi {

private final MessageService messageService;
Expand All @@ -36,21 +38,35 @@ public class MessageController implements MessageApi {
public ResponseEntity<MessageDto> create(
@Valid @RequestPart("messageCreateRequest") MessageCreateRequest messageCreateRequest,
@RequestPart(value = "attachments", required = false) List<MultipartFile> attachments) {

int attachmentCount = Optional.ofNullable(attachments).map(List::size).orElse(0);
log.info("메시지 생성 API 요청 - 채널 ID: {}, 작성자 ID: {}, 첨부파일 수: {}",
messageCreateRequest.channelId(), messageCreateRequest.authorId(), attachmentCount);

List<BinaryContentCreateRequest> attachmentRequests = Optional.ofNullable(attachments)
.map(files -> files.stream()
.map(file -> {
try {
log.debug("첨부파일 처리 - 파일명: {}, 크기: {} bytes",
file.getOriginalFilename(), file.getSize());
return new BinaryContentCreateRequest(
file.getOriginalFilename(),
file.getContentType(),
file.getBytes());
} catch (IOException e) {
throw new CustomException.BinaryContentNotFoundException("첨부파일을 읽는 중 오류가 발생했습니다: " + e.getMessage());
log.error("첨부파일 읽기 실패 - 파일명: {}, 오류: {}",
file.getOriginalFilename(), e.getMessage());
throw FileUploadFailedException.withCause(e);
}
})
.toList())
.orElse(new ArrayList<>());

MessageDto createdMessage = messageService.create(messageCreateRequest, attachmentRequests);

log.info("메시지 생성 API 응답 - 메시지 ID: {}, 채널 ID: {}, 작성자 ID: {}",
createdMessage.id(), createdMessage.channelId(), createdMessage.author().id());

return ResponseEntity
.status(HttpStatus.CREATED)
.body(createdMessage);
Expand All @@ -59,15 +75,26 @@ public ResponseEntity<MessageDto> create(
@PatchMapping(path = "{messageId}")
public ResponseEntity<MessageDto> update(@PathVariable("messageId") UUID messageId,
@Valid @RequestBody MessageUpdateRequest request) {
log.info("메시지 수정 API 요청 - 메시지 ID: {}", messageId);

MessageDto updatedMessage = messageService.update(messageId, request);

log.info("메시지 수정 API 응답 - 메시지 ID: {}, 작성자 ID: {}",
updatedMessage.id(), updatedMessage.author().id());

return ResponseEntity
.status(HttpStatus.OK)
.body(updatedMessage);
}

@DeleteMapping(path = "{messageId}")
public ResponseEntity<Void> delete(@PathVariable("messageId") UUID messageId) {
log.info("메시지 삭제 API 요청 - 메시지 ID: {}", messageId);

messageService.delete(messageId);

log.info("메시지 삭제 API 완료 - 메시지 ID: {}", messageId);

return ResponseEntity
.status(HttpStatus.NO_CONTENT)
.build();
Expand All @@ -78,7 +105,16 @@ public ResponseEntity<PageResponse<MessageDto>> findAllByChannelId(
@RequestParam("channelId") UUID channelId,
@RequestParam(value = "cursor", required = false) String cursor,
@PageableDefault(size = Constants.Pagination.DEFAULT_PAGE_SIZE, sort = Constants.Pagination.DEFAULT_SORT_FIELD) Pageable pageable) {
PageResponse<MessageDto> messages = messageService.findAllByChannelIdWithCursorPaging(channelId, cursor, pageable);

log.debug("메시지 목록 API 요청 - 채널 ID: {}, 커서: {}, 페이지 크기: {}",
channelId, cursor, pageable.getPageSize());

PageResponse<MessageDto> messages = messageService.findAllByChannelIdWithCursorPaging(channelId, cursor,
pageable);

log.debug("메시지 목록 API 응답 - 채널 ID: {}, 메시지 수: {}, 다음 페이지 존재: {}",
channelId, messages.content().size(), messages.hasNext());

return ResponseEntity
.status(HttpStatus.OK)
.body(messages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;

import java.util.List;
import java.util.UUID;
Expand All @@ -23,7 +24,7 @@ public class ReadStatusController implements ReadStatusApi {
private final ReadStatusService readStatusService;

@PostMapping
public ResponseEntity<ReadStatusResponse> create(@RequestBody ReadStatusCreateRequest request) {
public ResponseEntity<ReadStatusResponse> create(@Valid @RequestBody ReadStatusCreateRequest request) {
ReadStatus createdReadStatus = readStatusService.create(request);
ReadStatusResponse response = ResponseMapper.toResponse(createdReadStatus);
return ResponseEntity
Expand All @@ -33,7 +34,7 @@ public ResponseEntity<ReadStatusResponse> create(@RequestBody ReadStatusCreateRe

@PatchMapping(path = "{readStatusId}")
public ResponseEntity<ReadStatusResponse> update(@PathVariable("readStatusId") UUID readStatusId,
@RequestBody ReadStatusUpdateRequest request) {
@Valid @RequestBody ReadStatusUpdateRequest request) {
ReadStatus updatedReadStatus = readStatusService.update(readStatusId, request);
ReadStatusResponse response = ResponseMapper.toResponse(updatedReadStatus);
return ResponseEntity
Expand Down
Loading