Skip to content

Comments

[김진우] Sprint4#103

Open
zinuzanu wants to merge 39 commits intocodeit-bootcamp-spring:김진우from
zinuzanu:sprint4
Open

[김진우] Sprint4#103
zinuzanu wants to merge 39 commits intocodeit-bootcamp-spring:김진우from
zinuzanu:sprint4

Conversation

@zinuzanu
Copy link
Collaborator

@zinuzanu zinuzanu commented Feb 6, 2026

기본 요구사항

컨트롤러 레이어 구현

  • DiscodeitApplication의 테스트 로직은 삭제하세요.

웹 API 요구사항

사용자 관리

  • 사용자를 등록할 수 있다.
  • 사용자 정보를 수정할 수 있다.
  • 사용자를 삭제할 수 있다.
  • 모든 사용자를 조회할 수 있다.
  • 사용자의 온라인 상태를 업데이트할 수 있다.

권한 관리

  • 사용자는 로그인할 수 있다.

채널 관리

  • 공개 채널을 생성할 수 있다.
  • 비공개 채널을 생성할 수 있다.
  • 공개 채널의 정보를 수정할 수 있다.
  • 채널을 삭제할 수 있다.
  • 특정 사용자가 볼 수 있는 모든 채널 목록을 조회할 수 있다.

메시지 관리

  • 메시지를 보낼 수 있다.
  • 메시지를 수정할 수 있다.
  • 메시지를 삭제할 수 있다.
  • 특정 채널의 메시지 목록을 조회할 수 있다.

메시지 수신 정보 관리

  • 특정 채널의 메시지 수신 정보를 생성할 수 있다.
  • 특정 채널의 메시지 수신 정보를 수정할 수 있다.
  • 특정 사용자의 메시지 수신 정보를 조회할 수 있다.

바이너리 파일 다운로드

  • 바이너리 파일을 1개 또는 여러 개 조회할 수 있다.

예외 처리

  • 웹 API의 예외를 전역으로 처리하세요.

API 테스트


심화 요구사항

정적 리소스 서빙

  • 사용자 목록 조회, BinaryContent 파일 조회 API를 다음의 조건을 만족하도록 수정하세요.
  • 사용자 목록 조회
  • BinaryContent 파일 조회
  • 다음의 파일을 활용하여 사용자 목록을 보여주는 화면을 서빙해보세요.
스크린샷 2026-02-10 오후 2 20 31

생성형 AI 활용

  • 생성형 AI (Claude, ChatGPT 등)를 활용해서 위 이미지와 비슷한 화면을 생성 후 서빙해보세요.
스크린샷 2026-02-10 오후 3 08 54

김진우 and others added 30 commits January 9, 2026 11:12
@zinuzanu zinuzanu added the 매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다. label Feb 6, 2026
@zinuzanu zinuzanu self-assigned this Feb 6, 2026
@joonfluence
Copy link
Collaborator

커밋 메시지가 참 보기 좋네요 👍

Comment on lines 132 to 136
// [헬퍼 메서드] 채널 존재 여부를 검증하고 엔티티를 반환 (중복 코드 제거 및 예외 처리 공통화)
private Channel findChannelEntityById(UUID id) {
return channelRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("채널을 찾을 수 없습니다."));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋습니다 👍 사실 여기서 한단계 더 나아가면 ~finder와 같은 네이밍으로 별도 컴포넌트로 빼도 좋습니다. ChannelFinder와 같은 모듈로 분리하여 처리하면 재사용성도 챙길 수 있고 더 깔끔해지거든요. 나중에 한 번 적용해보세요

return convertToResponse(findUserEntityById(id));
User user = findUserEntityById(id);
loadUserStatus(user);
return convertToResponse(user);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResponseDto로 변환하는 역할이 Service의 책임일까요? 한번 고민해보시면 좋을 것 같습니다! 이 로직이 Service 말고 Controller로 빼면 해당 메서드가 하나의 Response로 국한되지 않을 수 있습니다

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 .gitignore로 뺴야 합니다!

import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v1 버저닝을 추가 해주는 것도 좋습니다 (계속 버전이 변화될 수 있기 때문에) 나중에 추가하긴 어렵거든요

Comment on lines +19 to +23
@RequestMapping(value = "/find", method = RequestMethod.GET)
public ResponseEntity<BinaryContent> find(@RequestParam("binaryContentId") UUID binaryContentId) {
BinaryContent content = binaryContentService.findEntityById(binaryContentId);
return ResponseEntity.ok(content);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 ResponseDTO 별도로 선언해주는게 더 좋을 것 같습니다!

Comment on lines +19 to +29
// TODO: 채널 생성자 정보가 없으므로 로직 추가 예정. (채널을 누가 만들었는지 모름)
@RequestMapping(value = "/public", method = RequestMethod.POST)
public ChannelDto.Response createPublicChannel(@RequestBody ChannelDto.CreatePublicRequest createPublicRequest) {
return channelService.createPublicChannel(createPublicRequest);
}

// TODO: UUID 형식이 맞으면 비공개 채널 생성이 가능한 상태, 추후 전역 처리 시 검증 로직 추가 예정
@RequestMapping(value = "/private", method = RequestMethod.POST)
public ChannelDto.Response createPrivateChannel(@RequestBody ChannelDto.CreatePrivateRequest createPrivateRequest) {
return channelService.createPrivateChannel(createPrivateRequest);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

과제에서 요구사항 대로 하신 것 같아 이해하지만, [POST] "/api/channels/public, private" private, public을 Body 값 안에 넣어서 처리하는 방식이 더 깔끔할 것 같긴 합니다. 정말 분리되어야 하면 모르겠지만 거의 구현 코드가 비슷할거거든요

Comment on lines +23 to +27
// 유저 등록
@RequestMapping(method = RequestMethod.POST)
public UserDto.Response create(@RequestBody UserDto.CreateRequest request) {
return userService.create(request);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResponseDTO 별도로 선언해주신 것 좋네요 👍

Comment on lines +6 to +58
@Getter
public enum ErrorCode {
// Global (0~99)
INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "G001", "잘못된 요청입니다."),
REQUIRED_PARAMETER_MISSING(HttpStatus.BAD_REQUEST, "G002", "필수 입력 값이 누락되었습니다."),
PATH_ID_MISMATCH(HttpStatus.BAD_REQUEST, "G003", "요청 경로의 ID와 데이터의 ID가 일치하지 않습니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "G004", "서버 내부 오류가 발생했습니다."),

// System (100~199)
DIRECTORY_CREATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "S101", "디렉토리 생성에 실패했습니다."),
FILE_SAVE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S102", "파일 저장 중 오류가 발생했습니다."),
FILE_DELETE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S103", "파일 삭제 중 오류가 발생했습니다."),
BINARY_CONTENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "S104", "첨부된 파일 정보를 찾을 수 없습니다."),

// Auth (200~299)
LOGIN_FAILED(HttpStatus.UNAUTHORIZED, "A201", "아이디 또는 비밀번호가 일치하지 않습니다."),

// User (300~399)
INVALID_USERNAME(HttpStatus.BAD_REQUEST, "U301", "이름은 공백 없이, 2~10자 사이여야 합니다."),
INVALID_EMAIL(HttpStatus.BAD_REQUEST, "U302", "이메일은 공백 없이 필수 입력 사항입니다."),
INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "U303", "비밀번호는 공백 없이 8자 이상이어야 합니다."),
USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "U304", "존재하지 않은 사용자입니다."),
DUPLICATE_EMAIL(HttpStatus.BAD_REQUEST, "U305", "이미 존재하는 이메일입니다."),
DUPLICATE_USERNAME(HttpStatus.BAD_REQUEST, "U306", "이미 존재하는 이름입니다."),

// Channel (400~499)
INVALID_CHANNEL_NAME(HttpStatus.BAD_REQUEST, "C401", "채널 이름은 공백없이, 2~15자 사이여야 합니다."),
CHANNEL_NOT_FOUND(HttpStatus.BAD_REQUEST, "C402", "채널을 찾을 수 없습니다."),
ALREADY_IN_CHANNEL(HttpStatus.CONFLICT, "C403", "이미 채널에 참여 중인 유저입니다."),
NOT_A_MEMBER(HttpStatus.BAD_REQUEST, "C404", "채널 멤버가 아니면 요청을 수행할 수 없습니다."),
PRIVATE_CHANNEL_NOT_UPDATABLE(HttpStatus.BAD_REQUEST, "C405", "비공개 채널은 수정할 수 없습니다."),

// Message (500~599)
EMPTY_MESSAGE_CONTENT(HttpStatus.BAD_REQUEST, "M501", "메세지 내용을 입력해주세요."),
MESSAGE_TOO_LONG(HttpStatus.BAD_REQUEST, "M502", "메세지는 500자 이하로 작성해주세요."),
MESSAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "M503", "존재하지 않는 메세지입니다."),

// Status (600~699)
USER_STATUS_NOT_FOUND(HttpStatus.BAD_REQUEST, "STAT601", "유저 상태 정보를 찾을 수 없습니다."),
USER_STATUS_ALREADY_EXISTS(HttpStatus.CONFLICT, "STAT602", "해당 유저의 상태 정보가 이미 존재합니다."),
READ_STATUS_ALREADY_EXISTS(HttpStatus.CONFLICT, "STAT603", "이미 존재하는 읽음 상태입니다."),
READ_STATUS_NOT_FOUND(HttpStatus.BAD_REQUEST, "STAT604", "읽음 상태 정보를 찾을 수 없습니다.");

private final HttpStatus status;
private final String code;
private final String message;

ErrorCode(HttpStatus status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}
} No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +34 to +46
List<UUID> attachmentIds = new ArrayList<>();
if (request.attachments() != null && binaryContentRepository != null) {
request.attachments().forEach(att -> {
BinaryContent content = new BinaryContent(
UUID.randomUUID(),
att.fileName(),
att.data(),
Instant.now()
);
binaryContentRepository.save(content);
attachmentIds.add(content.getId());
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 실제 DB가 연동됐다면 attachments 수 만큼 save가 실행되고 insert 문이 실행됐을겁니다. 한번에 호출하여 처리할 방법은 없을지 고민해보시죠!

Copy link
Collaborator

@joonfluence joonfluence left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

매운맛🔥 뒤는 없습니다. 그냥 필터 없이 말해주세요. 책임은 제가 집니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants