Skip to content

Conversation

@stoneTiger0912
Copy link
Member

@stoneTiger0912 stoneTiger0912 commented Aug 31, 2025

📝 변경 내용


✅ 체크리스트

  • 코드가 정상적으로 동작함
  • 테스트 코드 통과함
  • 문서(README 등)를 최신화함
  • 코드 스타일 가이드 준수

💬 기타 참고 사항

Summary by CodeRabbit

  • New Features
    • 그룹/이미지 업로드 요청값 유효성 검증 강화 및 오류 메시지 한글화(필수값 누락, 길이 제한, 이미지 URL 등)
    • 그룹 가입 관련 응답 타입 정리 및 DTO 도입으로 응답 가독성 향상
  • Documentation
    • 그룹, 그룹 가입, 이미지 업로드 API에 Swagger/OpenAPI 문서(엔드포인트 설명·예시·응답 스키마) 추가
  • Refactor
    • 저장소 커스텀 구현 도입 및 DTO 기반 조회 전환
    • 권한 초기화 로직 안정성 향상(동시성 대응)

@stoneTiger0912 stoneTiger0912 self-assigned this Aug 31, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 31, 2025

Walkthrough

문서화 인터페이스(ControllerDocs) 추가 및 컨트롤러 시그니처 구현, 그룹 검증 메시지 한글화, 그룹 권한 초기화 보조 로직 및 동시성 예외 처리 추가, GroupJoin 관련 DTO·커스텀 리포지토리·Impl 도입 및 서비스 리팩터링, 이미지 업로드 요청 검증 강화.

Changes

Cohort / File(s) Summary
Docs 인터페이스 도입 (Group)
src/main/java/project/flipnote/group/controller/GroupController.java, src/main/java/project/flipnote/group/controller/docs/GroupControllerDocs.java
GroupControllerGroupControllerDocs 구현. 그룹 CRUD·멤버·목록 관련 OpenAPI 문서 인터페이스 추가.
Docs 인터페이스 도입 (GroupJoin)
src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java, src/main/java/project/flipnote/groupjoin/controller/docs/GroupJoinControllerDocs.java
GroupJoinControllerGroupJoinControllerDocs 구현. 가입신청 CRUD 및 조회 OpenAPI 인터페이스 추가.
Docs 인터페이스 도입 (Image)
src/main/java/project/flipnote/image/controller/ImageUploadController.java, src/main/java/project/flipnote/image/controller/docs/ImageUploadControllerDocs.java
ImageUploadControllerImageUploadControllerDocs 구현. S3 presigned URL API 문서 인터페이스 추가.
검증 메시지 보강 (그룹 요청)
src/main/java/project/flipnote/group/model/GroupCreateRequest.java, src/main/java/project/flipnote/group/model/GroupPutRequest.java
Bean Validation의 한글 커스텀 메시지 추가 및 maxMember@Min/@Max 적용, image@URL 추가.
검증 보강 (이미지 업로드 요청)
src/main/java/project/flipnote/image/model/ImageUploadRequestDto.java
fileName@NotNull 추가(기존 Pattern 유지).
그룹 권한 초기화 보조 로직 추가
src/main/java/project/flipnote/group/service/GroupService.java
getOrCreateGroupPermissions() 추가: 누락된 GroupPermission 생성 후 saveAll, 동시성시 DataIntegrityViolationException 무시 후 재조회. 기존 초기화는 해당 메서드 사용하도록 변경.
GroupJoin DTO 도입 / 시그니처 변경
src/main/java/project/flipnote/groupjoin/model/GroupJoinInfo.java, .../MyGroupJoinInfo.java, .../GroupJoinListResponse.java, .../FindGroupJoinListMeResponse.java, .../GroupJoinRespondRequest.java
엔티티 리스트 대신 DTO(List/List)로 응답 타입 변경. GroupJoinRespondRequest.status@NotNull 추가. 팩토리 메서드 시그니처 업데이트.
GroupJoin 커스텀 리포지토리/Impl
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepository.java, .../GroupJoinRepositoryCustom.java, .../GroupJoinRepositoryImpl.java
GroupJoinRepositoryGroupJoinRepositoryCustom 확장. 엔티티 기반 finder 제거. QueryDSL 기반 DTO 프로젝션 findByGroup(Long)/findByUser(Long) 구현 추가.
GroupJoin 서비스 리팩터링
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java
서비스가 DTO 기반 조회 사용으로 변경됨(Repository의 findByGroup/findByUser 사용). 일부 권한 확인 로직을 ID 기반 조회로 조정.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Controller as GroupJoinController
  participant Service as GroupJoinService
  participant Repo as GroupJoinRepository (Impl)
  participant DB as Database

  Controller->>Service: findGroupJoinList(groupId)
  Service->>Repo: findByGroup(groupId)
  Repo->>DB: SELECT ... (QueryDSL projection) -> DTO(GroupJoinInfo)
  DB-->>Repo: List<GroupJoinInfo)
  Repo-->>Service: List<GroupJoinInfo)
  Service-->>Controller: GroupJoinListResponse.from(dtoList)
Loading
sequenceDiagram
  autonumber
  participant Svc as GroupService
  participant PermRepo as GroupPermissionRepository
  participant DB as Database

  Svc->>PermRepo: findAll()
  PermRepo-->>Svc: existingPermissions
  alt existing < enum.size
    Svc->>PermRepo: saveAll(missingPermissions)
    Note right of Svc: DataIntegrityViolationException 허용(동시성)
    opt on DataIntegrityViolationException
      Svc->>PermRepo: findAll() (재조회)
    end
  end
  Svc->>Svc: initializeGroupPermissions(group) with ensured permissions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • dungbik

Poem

(\_/) 봄바람에 문서 한 장, 퐁당
(•ㅅ•) DTO 모여 춤추네, 쿼리도 경쾌
/つ🥕 권한 씨앗 심고, 예외는 흘려보내
(⌒˘⌒) 이미지도 단단히, 이름은 규칙대로
∠(’▽’)ゞ 토끼가 응원, 머지 성공!


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6e62e24 and 068df6b.

📒 Files selected for processing (2)
  • src/main/java/project/flipnote/group/model/GroupCreateRequest.java (1 hunks)
  • src/main/java/project/flipnote/group/model/GroupPutRequest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/project/flipnote/group/model/GroupPutRequest.java
  • src/main/java/project/flipnote/group/model/GroupCreateRequest.java
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/group-package-and-docs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (4)

177-183: 알림 수신자 ID 매핑 버그: 사용자 ID 대신 그룹 ID를 담고 있음 (Line 179)

receiverIdsgroupMember.getGroup().getId()로 매핑되어 있어, 알림 수신자가 전부 그룹 ID로 전송됩니다. 사용자 ID로 수정해야 합니다.

- .map((groupMember ->  groupMember.getGroup().getId()))
+ .map(groupMember -> groupMember.getUser().getId())

127-134: 중복 가입 검증이 ‘가입신청 존재’만 확인함 — 기존 멤버 여부도 차단 필요

현재 existGroupJoin만 확인하여 기존 그룹 멤버가 다시 신청하는 케이스를 막지 못합니다. 멤버십 존재 여부도 함께 검사해 주세요.

 		//그룹 조회
 		Group group = findGroup(groupId);
 
-		if (existGroupJoin(group, user)) {
+		//기존 멤버인지 우선 확인
+		if (groupMemberRepository.existsByGroup_IdAndUser_Id(group.getId(), user.getId())) {
+			throw new BizException(GroupJoinErrorCode.ALREADY_JOINED_GROUP);
+		}
+		//이미 신청 이력 존재 여부 확인
+		if (existGroupJoin(group, user)) {
 			throw new BizException(GroupJoinErrorCode.ALREADY_JOINED_GROUP);
 		}

139-159: 공개 그룹(신청 불필요) 즉시 가입 처리 누락 — 멤버 추가 필요

applicationRequired == false이면 상태를 ACCEPT로만 저장하고 멤버를 추가하지 않습니다. 즉시 멤버십 생성이 필요합니다.

 		groupJoinRepository.save(groupJoin);
 
-		if (group.getApplicationRequired()) {
+		if (group.getApplicationRequired()) {
 			sendJoinRequestNotification(group, user);
-		}
+		} else {
+			// 공개 그룹: 즉시 멤버 추가
+			GroupMember groupMember = GroupMember.builder()
+				.group(group)
+				.user(user)
+				.role(GroupMemberRole.MEMBER)
+				.build();
+			groupMemberRepository.save(groupMember);
+		}

226-235: 보안 취약점: 다른 그룹의 joinId를 조작 가능 — 그룹 일치 검증 추가 필요

respondToJoinRequest에서 groupIdjoinId 간 소속 그룹 일치 검증이 없습니다. 권한이 있는 A그룹 컨텍스트로 B그룹의 신청을 변경할 수 있습니다.

 		//그룹 가입 신청 조회
 		GroupJoin groupJoin = findGroupJoin(joinId);
 
+		// joinId가 해당 groupId에 속하는지 검증
+		if (!groupJoin.getGroup().getId().equals(groupId)) {
+			throw new BizException(GroupJoinErrorCode.USER_NOT_PERMISSION);
+		}
🧹 Nitpick comments (23)
src/main/java/project/flipnote/image/model/ImageUploadRequestDto.java (2)

11-12: String 필드는 @NotNull 대신 @notblank 권장

프로젝트 전반에서 String 유효성에 @notblank를 일관 사용 중입니다(UserUpdateRequest.nickname, CreateCardSetRequest.name 등). 빈 문자열에 대해 보다 명확한 메시지를 주기 위해 @notblank로 교체를 제안합니다.

-import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.NotBlank;
@@
-	@NotNull(message = "파일 이름을 입력해주세요.")
+	@NotBlank(message = "파일 이름을 입력해주세요.")
 	String fileName

7-12: 확장자 대소문자 허용 및 에러 메시지 품질 개선

현재 정규식은 확장자 소문자만 허용합니다. 클라이언트가 JPG/PNG 대문자를 보낼 수 있으므로 대소문자 무시 플래그를 추가하면 UX가 좋아집니다(해시 부분도 동일 이득). 동시에 공백 문자열은 @notblank에서 걸리고, 형식 오류는 @pattern에서 분리되어 메시지가 명확해집니다.

-	@Pattern(
-		regexp = "^[a-fA-F0-9]{32}\\.(jpg|jpeg|png|gif)$",
-		message = "파일 이름은 32자리 MD5 해시와 jpg/jpeg/png/gif 확장자 형식이어야 합니다."
-	)
+	@Pattern(
+		regexp = "^[a-f0-9]{32}\\.(jpg|jpeg|png|gif)$",
+		flags = { jakarta.validation.constraints.Pattern.Flag.CASE_INSENSITIVE },
+		message = "파일 이름은 32자리 MD5 해시와 jpg/jpeg/png/gif 확장자 형식이어야 합니다."
+	)
src/main/java/project/flipnote/image/controller/ImageUploadController.java (2)

22-29: 필드명 의미 구체화 제안

ImageUploadService를 주입받는 필드명이 fileService로 되어 있어 맥락 파악이 어렵습니다. 일관성과 가독성을 위해 이름을 imageUploadService로 변경을 제안합니다.

-	private final ImageUploadService fileService;
+	private final ImageUploadService imageUploadService;
@@
-		ImageUploadResponseDto res = fileService.getPresignedUrl(req.fileName());
+		ImageUploadResponseDto res = imageUploadService.getPresignedUrl(req.fileName());

21-29: 보안 요구 여부 확인 필요

해당 엔드포인트가 인증이 필요한지 정책 확인 부탁드립니다. 다른 컨트롤러 문서화 패턴(User, Group 등)에서는 보호 API에 @securityrequirement를 명시합니다. 필요하다면 docs 인터페이스에 추가하는 편이 일관됩니다.

src/main/java/project/flipnote/image/controller/docs/ImageUploadControllerDocs.java (2)

15-35: 보호 리소스라면 보안 스키마와 401 응답 명시

이미지 업로드용 Presigned URL 발급은 일반적으로 인증된 사용자 전용입니다. 보호 리소스라면 @securityrequirement와 401 응답을 추가해주세요. 또한 Content에 mediaType을 명시하면 문서 정확도가 올라갑니다.

@@
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
@@
 	@Operation(
 		summary = "이미지 업로드 URL 생성",
-		description = "S3에 이미지를 업로드할 수 있는 Presigned PUT URL을 발급합니다. "
-			+ "파일 이름은 32자리 MD5 + 확장자(jpg|jpeg|png|gif) 형식이어야 합니다."
-	)
+		description = "S3에 이미지를 업로드할 수 있는 Presigned PUT URL을 발급합니다. "
+			+ "파일 이름은 32자리 MD5 + 확장자(jpg|jpeg|png|gif) 형식이어야 합니다.",
+		security = { @SecurityRequirement(name = "access-token") }
+	)
@@
-		content = @Content(schema = @Schema(implementation = ImageUploadRequestDto.class))
+		content = @Content(mediaType = "application/json",
+			schema = @Schema(implementation = ImageUploadRequestDto.class))
 	)
 	@ApiResponses({
 		@ApiResponse(responseCode = "200", description = "URL 발급 성공",
 			content = @Content(schema = @Schema(implementation = ImageUploadResponseDto.class))),
+		@ApiResponse(responseCode = "401", description = "인증 필요 또는 토큰 만료"),
 		@ApiResponse(responseCode = "400", description = "잘못된 요청(파일명 형식 오류 등)"),
 		@ApiResponse(responseCode = "409", description = "이미 존재하는 파일명(CONFLICT_IMAGE)"),
 		@ApiResponse(responseCode = "500", description = "S3 서비스 오류(S3_SERVICE_ERROR)")
 	})
-	public ResponseEntity<ImageUploadResponseDto> getPresignedUrl(ImageUploadRequestDto req);
+	ResponseEntity<ImageUploadResponseDto> getPresignedUrl(ImageUploadRequestDto req);

35-35: interface 메서드의 public 키워드는 불필요

다른 Docs 인터페이스들과의 일관성을 위해 public 제거를 권장합니다(기본이 public).

-	public ResponseEntity<ImageUploadResponseDto> getPresignedUrl(ImageUploadRequestDto req);
+	ResponseEntity<ImageUploadResponseDto> getPresignedUrl(ImageUploadRequestDto req);
src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java (2)

33-41: 조인 신청 리스트 조회: 페이징 고려 권장

그룹 단위 신청 건수가 많아질 수 있으니 Cursor/Paging 응답으로 확장하기 쉽게 인터페이스를 설계해 두는 걸 권장합니다(예: CursorPagingResponse).


68-76: "me" 엔드포인트 경로 컨벤션 정합성 확인

현재 "/groups/joins/me"를 사용 중인데, 기존 초대 조회는 "/group-invitations" 형태입니다. 일관성을 위해 "/group-joins/me" 또는 기존 패턴 중 하나로 정렬하는지 검토해 주세요. 호환성 영향이 있으면 문서에 명시해 주세요.

src/main/java/project/flipnote/groupjoin/model/GroupJoinListResponse.java (1)

3-3: 미사용 import 제거

GroupJoin 엔티티 import는 더 이상 사용되지 않습니다. 제거해 주세요(정적 분석/빌드 경고 예방).

-import project.flipnote.groupjoin.entity.GroupJoin;
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryCustom.java (1)

8-12: 조회 규모 대비 확장성 옵션 노출 검토

현재 단순 List 반환입니다. 운영 규모를 고려해 정렬/커서/페이지 크기 인자를 받는 오버로드를 추가하거나, 최소한 정렬 기준(예: id desc)을 명세해 두는 것을 권장합니다. 인터페이스 변경이 부담되면 구현체에서 일관된 정렬을 강제하고 Javadoc으로 문서화해 주세요.

src/main/java/project/flipnote/groupjoin/model/FindGroupJoinListMeResponse.java (1)

5-5: 미사용 import 제거

GroupJoin import는 사용되지 않습니다. 정리해 주세요.

-import project.flipnote.groupjoin.entity.GroupJoin;
src/main/java/project/flipnote/groupjoin/model/MyGroupJoinInfo.java (1)

12-14: 사소한 포맷팅 수정 제안

from 내부 인자 사이 공백 누락이 있습니다. 자동 포매터가 없다면 아래처럼 정리해 주세요.

- return new MyGroupJoinInfo(groupJoinId, groupId, groupName,joinIntro,status);
+ return new MyGroupJoinInfo(groupJoinId, groupId, groupName, joinIntro, status);
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (2)

201-206: 목록 조회에 상태 필터 추가 고려 (PENDING 중심)

운영 관점에서 가입신청 리스트는 보통 PENDING 위주로 보게 됩니다. 레포지토리에 상태 조건(예: PENDING) 추가하는 것을 권장합니다.

- List<GroupJoinInfo> groupJoins = findGroupJoins(group);
+ List<GroupJoinInfo> groupJoins = groupJoinRepository.findByGroupAndStatusPending(group.getId());

(필요 시 GroupJoinRepositoryCustom에 전용 메서드 추가)


288-291: 중복 로직 정리: 헬퍼 메서드 활용

이미 findMyGroupJoins(UserProfile)가 존재합니다. 동일 로직을 재사용해 가독성을 높이세요.

- List<MyGroupJoinInfo> groupJoins = groupJoinRepository.findByUser(user.getId());
+ List<MyGroupJoinInfo> groupJoins = findMyGroupJoins(user);
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryImpl.java (1)

10-10: 불필요한 import 제거 제안

GroupMemberInfo는 사용되지 않습니다. 정리해 주세요.

-import project.flipnote.group.model.GroupMemberInfo;
src/main/java/project/flipnote/groupjoin/controller/docs/GroupJoinControllerDocs.java (6)

23-25: 태그/보안 선언은 적절. 다만 문서 언어/네이밍 일관화 권장

다른 Docs 인터페이스(예: Group Invitation)는 영문 태그명을 사용합니다. 이 파일만 국문 태그를 쓰면 탐색/분류가 흐트러질 수 있어 전반 언어 전략을 통일하는 편이 좋습니다.


26-40: joinRequest: operationId 부여, 201 Location 헤더/에러 예시 명확화

API 식별 안정성을 위해 operationId를 추가하고, 생성(201)에 Location 헤더, 403/409의 예시 페이로드를 ExampleObject로 명시하면 Swagger UI 가독성이 좋아집니다.

아래와 같이 보강 제안:

 @Operation(
   summary = "가입 신청 요청",
   description = "공개 그룹에 대해 가입 신청을 생성합니다. 그룹 정책에 따라 PENDING 또는 즉시 ACCEPT로 저장됩니다."
+  , operationId = "joinRequest"
 )
 @ApiResponses({
-  @ApiResponse(responseCode = "201", description = "가입 신청 생성 성공",
-    content = @Content(schema = @Schema(implementation = GroupJoinResponse.class))),
+  @ApiResponse(responseCode = "201", description = "가입 신청 생성 성공",
+    content = @Content(schema = @Schema(implementation = GroupJoinResponse.class)),
+    headers = {
+      @io.swagger.v3.oas.annotations.headers.Header(
+        name = "Location",
+        description = "생성된 가입 신청 리소스 위치",
+        schema = @Schema(example = "/groups/123/join-requests/456")
+      )
+    }
+  ),
   @ApiResponse(responseCode = "400", description = "잘못된 요청 (검증 실패 등)"),
   @ApiResponse(responseCode = "401", description = "인증 실패"),
-  @ApiResponse(responseCode = "403", description = "비공개 그룹 → GROUP_JOIN_003",
-    content = @Content(schema = @Schema(example = "{\"code\":\"GROUP_JOIN_003\",\"message\":\"그룹이 비공개입니다.\"}"))),
+  @ApiResponse(responseCode = "403", description = "비공개 그룹 → GROUP_JOIN_003",
+    content = @Content(
+      mediaType = "application/json",
+      examples = {
+        @io.swagger.v3.oas.annotations.media.ExampleObject(
+          name = "GROUP_JOIN_003",
+          value = "{\"code\":\"GROUP_JOIN_003\",\"message\":\"그룹이 비공개입니다.\"}"
+        )
+      }
+    )
+  ),
   @ApiResponse(responseCode = "404", description = "그룹 없음"),
-  @ApiResponse(responseCode = "409", description = "이미 가입 신청 존재 → GROUP_JOIN_005 / 정원 초과 → GROUP_JOIN_006",
-    content = @Content(schema = @Schema(example = "{\"code\":\"GROUP_JOIN_005\",\"message\":\"이미 신청한 그룹입니다.\"}")))
+  @ApiResponse(responseCode = "409", description = "이미 가입 신청 존재 → GROUP_JOIN_005 / 정원 초과 → GROUP_JOIN_006",
+    content = @Content(
+      mediaType = "application/json",
+      examples = {
+        @io.swagger.v3.oas.annotations.media.ExampleObject(
+          name = "이미 신청",
+          value = "{\"code\":\"GROUP_JOIN_005\",\"message\":\"이미 신청한 그룹입니다.\"}"
+        ),
+        @io.swagger.v3.oas.annotations.media.ExampleObject(
+          name = "정원 초과",
+          value = "{\"code\":\"GROUP_JOIN_006\",\"message\":\"그룹 정원이 가득 찼습니다.\"}"
+        )
+      }
+    )
+  )
 })

추가 import 필요:

+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.ExampleObject;

41-50: 파라미터 순서 일관화: 경로/본문 → 인증 주체(Principal) 순

프로젝트 내 다른 Docs(예: GroupInvitation*)와 동일하게 경로/본문 파라미터를 먼저, 인증 주체를 마지막에 두면 가독성과 일관성이 좋아집니다.

예시 수정:

 ResponseEntity<GroupJoinResponse> joinRequest(
-  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple,
-  @Parameter(description = "그룹 ID", example = "123") Long groupId,
+  @Parameter(description = "그룹 ID", example = "123") Long groupId,
   @io.swagger.v3.oas.annotations.parameters.RequestBody(
     description = "가입 신청 요청 데이터",
     required = true,
     content = @Content(schema = @Schema(implementation = GroupJoinRequest.class))
   )
-  @Valid GroupJoinRequest req
+  @Valid GroupJoinRequest req,
+  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple
 );

 ResponseEntity<GroupJoinListResponse> findGroupJoinList(
-  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple,
-  @Parameter(description = "그룹 ID", example = "123") Long groupId
+  @Parameter(description = "그룹 ID", example = "123") Long groupId,
+  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple
 );

 ResponseEntity<GroupJoinRespondResponse> respondToJoinRequest(
-  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple,
   @Parameter(description = "그룹 ID", example = "123") Long groupId,
   @Parameter(description = "가입신청 ID", example = "456") Long joinId,
   @io.swagger.v3.oas.annotations.parameters.RequestBody(
     description = "승인/거절 상태",
     required = true,
     content = @Content(schema = @Schema(implementation = GroupJoinRespondRequest.class))
   )
-  @Valid GroupJoinRespondRequest req
+  @Valid GroupJoinRespondRequest req,
+  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple
 );

-public ResponseEntity<Void> groupJoinDelete(
-  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple,
-  @Parameter(description = "그룹 ID", required = true, example = "123") Long groupId,
-  @Parameter(description = "가입신청 ID", required = true, example = "456") Long joinId
+ResponseEntity<Void> groupJoinDelete(
+  @Parameter(description = "그룹 ID", required = true, example = "123") Long groupId,
+  @Parameter(description = "가입신청 ID", required = true, example = "456") Long joinId,
+  @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple
 );

-public ResponseEntity<FindGroupJoinListMeResponse> findGroupJoinMe(
+ResponseEntity<FindGroupJoinListMeResponse> findGroupJoinMe(
   @Parameter(hidden = true) @AuthenticationPrincipal AuthPrinciple authPrinciple
 );

Also applies to: 64-67, 82-92, 105-109, 121-123


73-81: respondToJoinRequest: 오류 응답 예시 통일(403/404) 제안

해당 메서드에서는 403/404의 예시가 누락되어 있어, 다른 엔드포인트와 동일하게 ExampleObject로 명시하면 좋습니다.

   @ApiResponses({
     @ApiResponse(responseCode = "200", description = "응답 처리 성공",
       content = @Content(schema = @Schema(implementation = GroupJoinRespondResponse.class))),
     @ApiResponse(responseCode = "401", description = "인증 실패"),
-    @ApiResponse(responseCode = "403", description = "권한 없음 → GROUP_JOIN_002"),
-    @ApiResponse(responseCode = "404", description = "가입신청 없음 → GROUP_JOIN_004"),
+    @ApiResponse(responseCode = "403", description = "권한 없음 → GROUP_JOIN_002",
+      content = @Content(
+        mediaType = "application/json",
+        examples = @io.swagger.v3.oas.annotations.media.ExampleObject(
+          name = "GROUP_JOIN_002",
+          value = "{\"code\":\"GROUP_JOIN_002\",\"message\":\"그룹 내 권한이 없습니다.\"}"
+        )
+      )
+    ),
+    @ApiResponse(responseCode = "404", description = "가입신청 없음 → GROUP_JOIN_004",
+      content = @Content(
+        mediaType = "application/json",
+        examples = @io.swagger.v3.oas.annotations.media.ExampleObject(
+          name = "GROUP_JOIN_004",
+          value = "{\"code\":\"GROUP_JOIN_004\",\"message\":\"가입신청을 찾을 수 없습니다.\"}"
+        )
+      )
+    ),

94-99: 문서 표현 일관성(‘가입 신청’ vs ‘가입신청’ 표기)

요약/설명에서 띄어쓰기 표기가 혼재되어 있습니다. 한 가지로 통일하면 검색성과 가독성이 좋아집니다. 예: 모두 ‘가입 신청’로 통일.

Also applies to: 111-116


105-109: interface 메서드의 public 키워드 불필요 — 제거로 일관성 확보

Java interface 메서드는 암묵적으로 public입니다. 일부 메서드만 public을 명시해 스타일이 섞여 있어 제거 권장(위 파라미터 순서 정리 diff에 포함).

Also applies to: 121-123

src/main/java/project/flipnote/group/service/GroupService.java (1)

114-139: 권한 시드 동시성 처리 👍 + 로그/미세 최적화 제안

  • getOrCreateGroupPermissions로 누락 enum을 채우고 경합을 DataIntegrityViolationException으로 흡수하는 접근 괜찮습니다.
  • 예외를 완전히 무시하면 원인 추적이 어려우니 debug 로그를 남기길 권장합니다.
  • Set 구축 시 EnumSet을 쓰면 메모리/성능에 유리합니다.

제안 diff:

@@
-       Set<GroupPermissionStatus> existing = all.stream()
-           .map(GroupPermission::getName)
-           .collect(Collectors.toSet());
+       Set<GroupPermissionStatus> existing = all.stream()
+           .map(GroupPermission::getName)
+           .collect(Collectors.toCollection(() -> java.util.EnumSet.noneOf(GroupPermissionStatus.class)));
@@
-       if (!missing.isEmpty()) {
+       if (!missing.isEmpty()) {
            try {
                groupPermissionRepository.saveAll(missing);
-           } catch (DataIntegrityViolationException ignore) {
-               // 다른 트랜잭션이 먼저 넣은 경우: 무시하고 재조회
+           } catch (DataIntegrityViolationException e) {
+               // 다른 트랜잭션이 먼저 넣은 경우: 무시하고 재조회
+               log.debug("GroupPermission 동시 생성 경합 감지: {}", e.getMessage());
            }
            all = groupPermissionRepository.findAll();
        }
        return all;

Also applies to: 184-184

src/main/java/project/flipnote/group/controller/docs/GroupControllerDocs.java (1)

187-197: 미사용 더미 파라미터는 제거하거나 숨김 처리 권장

sortBy/order가 실제로 사용되지 않는다면 문서에서 제거하거나 hidden=true로 숨겨 노이즈를 줄이는 것이 좋습니다. deprecated만으론 UI에 계속 노출됩니다.

예시(제거):

-       @Parameter(
-           name = "sortBy",
-           description = "(더미) 현재 미사용",
-           example = "string",
-           deprecated = true
-       ),
-       @Parameter(
-           name = "order",
-           description = "(더미) 현재 미사용",
-           example = "string",
-           deprecated = true
-       )

또는 숨김 처리:

-       @Parameter(name = "sortBy", description = "(더미) 현재 미사용", example = "string", deprecated = true),
-       @Parameter(name = "order", description = "(더미) 현재 미사용", example = "string", deprecated = true)
+       @Parameter(name = "sortBy", hidden = true, deprecated = true),
+       @Parameter(name = "order", hidden = true, deprecated = true)

Also applies to: 235-245

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b467330 and 6e62e24.

📒 Files selected for processing (19)
  • src/main/java/project/flipnote/group/controller/GroupController.java (2 hunks)
  • src/main/java/project/flipnote/group/controller/docs/GroupControllerDocs.java (1 hunks)
  • src/main/java/project/flipnote/group/model/GroupCreateRequest.java (1 hunks)
  • src/main/java/project/flipnote/group/model/GroupPutRequest.java (1 hunks)
  • src/main/java/project/flipnote/group/service/GroupService.java (3 hunks)
  • src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/controller/docs/GroupJoinControllerDocs.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/model/FindGroupJoinListMeResponse.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/model/GroupJoinInfo.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/model/GroupJoinListResponse.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/model/GroupJoinRespondRequest.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/model/MyGroupJoinInfo.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepository.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryCustom.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryImpl.java (1 hunks)
  • src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (5 hunks)
  • src/main/java/project/flipnote/image/controller/ImageUploadController.java (1 hunks)
  • src/main/java/project/flipnote/image/controller/docs/ImageUploadControllerDocs.java (1 hunks)
  • src/main/java/project/flipnote/image/model/ImageUploadRequestDto.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (17)
src/main/java/project/flipnote/groupjoin/model/GroupJoinInfo.java (1)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
  • Getter (22-65)
src/main/java/project/flipnote/group/model/GroupCreateRequest.java (3)
src/main/java/project/flipnote/group/model/GroupInvitationCreateRequest.java (1)
  • GroupInvitationCreateRequest (6-10)
src/main/java/project/flipnote/group/entity/Group.java (2)
  • Getter (30-119)
  • changeGroup (110-118)
src/main/java/project/flipnote/cardset/model/CreateCardSetRequest.java (1)
  • CreateCardSetRequest (12-30)
src/main/java/project/flipnote/image/model/ImageUploadRequestDto.java (5)
src/main/java/project/flipnote/cardset/model/CardSetUpdateRequest.java (1)
  • CardSetUpdateRequest (13-36)
src/main/java/project/flipnote/auth/model/PasswordResetRequest.java (1)
  • PasswordResetRequest (6-14)
src/main/java/project/flipnote/cardset/model/CreateCardSetRequest.java (1)
  • CreateCardSetRequest (12-30)
src/main/java/project/flipnote/user/model/UserUpdateRequest.java (1)
  • UserUpdateRequest (8-25)
src/main/java/project/flipnote/group/model/GroupInvitationCreateRequest.java (1)
  • GroupInvitationCreateRequest (6-10)
src/main/java/project/flipnote/groupjoin/model/GroupJoinRespondRequest.java (3)
src/main/java/project/flipnote/group/model/GroupInvitationRespondRequest.java (1)
  • GroupInvitationRespondRequest (6-17)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (2)
  • Getter (22-65)
  • updateStatus (62-64)
src/main/java/project/flipnote/groupjoin/model/GroupJoinResponse.java (1)
  • GroupJoinResponse (5-11)
src/main/java/project/flipnote/groupjoin/model/MyGroupJoinInfo.java (2)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (2)
  • Getter (22-65)
  • updateStatus (62-64)
src/main/java/project/flipnote/groupjoin/model/GroupJoinResponse.java (1)
  • GroupJoinResponse (5-11)
src/main/java/project/flipnote/groupjoin/model/GroupJoinListResponse.java (1)
src/main/java/project/flipnote/group/model/FindGroupMemberResponse.java (1)
  • FindGroupMemberResponse (5-11)
src/main/java/project/flipnote/group/controller/docs/GroupControllerDocs.java (2)
src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (3)
  • Tag (13-28)
  • Operation (16-19)
  • Operation (24-27)
src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java (1)
  • AuthControllerDocs (19-53)
src/main/java/project/flipnote/group/service/GroupService.java (4)
src/main/java/project/flipnote/group/entity/GroupPermission.java (2)
  • Entity (12-29)
  • Builder (25-28)
src/main/java/project/flipnote/group/entity/GroupRolePermission.java (2)
  • Entity (11-41)
  • Builder (35-40)
src/main/java/project/flipnote/group/repository/GroupRolePermissionRepository.java (1)
  • Repository (13-18)
src/main/java/project/flipnote/group/repository/GroupPermissionRepository.java (1)
  • Repository (9-12)
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryImpl.java (2)
src/main/java/project/flipnote/group/repository/GroupMemberRepositoryImpl.java (1)
  • RequiredArgsConstructor (13-35)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
  • Getter (22-65)
src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java (2)
src/main/java/project/flipnote/image/controller/ImageUploadController.java (1)
  • RestController (18-31)
src/main/java/project/flipnote/group/controller/GroupController.java (1)
  • RequiredArgsConstructor (32-109)
src/main/java/project/flipnote/image/controller/docs/ImageUploadControllerDocs.java (3)
src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java (3)
  • AuthControllerDocs (19-53)
  • Operation (36-37)
  • Operation (21-22)
src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (1)
  • Tag (13-28)
src/main/java/project/flipnote/user/controller/docs/UserControllerDocs.java (1)
  • Tag (14-28)
src/main/java/project/flipnote/image/controller/ImageUploadController.java (3)
src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java (1)
  • RestController (15-77)
src/main/java/project/flipnote/group/controller/GroupController.java (1)
  • RequiredArgsConstructor (32-109)
src/main/java/project/flipnote/auth/controller/docs/AuthControllerDocs.java (1)
  • AuthControllerDocs (19-53)
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepository.java (4)
src/main/java/project/flipnote/group/repository/GroupMemberRepository.java (1)
  • Repository (14-29)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
  • Getter (22-65)
src/main/java/project/flipnote/group/repository/GroupMemberRepositoryImpl.java (1)
  • RequiredArgsConstructor (13-35)
src/main/java/project/flipnote/group/repository/GroupMemberRepositoryCustom.java (1)
  • GroupMemberRepositoryCustom (7-9)
src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryCustom.java (3)
src/main/java/project/flipnote/group/repository/GroupRepositoryCustom.java (1)
  • GroupRepositoryCustom (8-12)
src/main/java/project/flipnote/group/repository/GroupMemberRepositoryCustom.java (2)
  • GroupMemberRepositoryCustom (7-9)
  • findGroupMembers (8-8)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
  • Getter (22-65)
src/main/java/project/flipnote/groupjoin/controller/docs/GroupJoinControllerDocs.java (2)
src/main/java/project/flipnote/group/controller/docs/GroupInvitationControllerDocs.java (3)
  • Tag (13-28)
  • Operation (24-27)
  • Operation (16-19)
src/main/java/project/flipnote/group/controller/docs/GroupInvitationQueryControllerDocs.java (2)
  • Tag (14-29)
  • Operation (24-28)
src/main/java/project/flipnote/group/model/GroupPutRequest.java (3)
src/main/java/project/flipnote/group/entity/Group.java (1)
  • changeGroup (110-118)
src/main/java/project/flipnote/group/model/GroupPutResponse.java (1)
  • GroupPutResponse (8-40)
src/test/java/project/flipnote/group/service/GroupServiceTest.java (1)
  • Test (362-403)
src/main/java/project/flipnote/groupjoin/service/GroupJoinService.java (2)
src/main/java/project/flipnote/groupjoin/entity/GroupJoin.java (1)
  • Getter (22-65)
src/test/java/project/flipnote/groupjoin/service/GroupJoinServiceTest.java (1)
  • ExtendWith (35-283)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (13)
src/main/java/project/flipnote/image/controller/ImageUploadController.java (1)

21-21: 문서 인터페이스 구현 일치 — 잘 반영됨

컨트롤러가 docs 인터페이스를 구현하도록 정리된 점 좋습니다. Swagger 스펙과 시그니처 동기화에 도움이 됩니다.

src/main/java/project/flipnote/groupjoin/controller/GroupJoinController.java (1)

18-18: Docs 인터페이스 구현 LGTM

컨트롤러가 GroupJoinControllerDocs를 구현하도록 한 방향성 좋습니다. 시그니처/매핑 일치만 유지해 주세요.

src/main/java/project/flipnote/groupjoin/model/GroupJoinListResponse.java (1)

8-11: DTO로의 전환 LGTM

엔티티 노출을 List로 치환한 점 적절합니다. 팩토리 메서드도 간결합니다.

src/main/java/project/flipnote/groupjoin/model/FindGroupJoinListMeResponse.java (1)

8-12: 내 신청 리스트 DTO 전환 LGTM

엔티티 노출 최소화 방향에 부합합니다.

src/main/java/project/flipnote/groupjoin/model/GroupJoinInfo.java (2)

12-14: 정적 팩토리 메서드 from 구성 적절합니다

레코드 컴포넌트 순서와 일치하며, 불변 DTO로 사용하기에 적합합니다.


5-11: UserProfile 엔티티에 nickname 필드 존재 확인 완료
src/main/java/project/flipnote/user/entity/UserProfile.java 39행에 private String nickname;으로 선언되어 있어 QueryDSL 프로젝션 그대로 사용 가능합니다.

src/main/java/project/flipnote/groupjoin/model/MyGroupJoinInfo.java (1)

5-11: 레코드 구성 적절

필드 구성이 의도와 일치하며 DTO 용도로 적합합니다.

src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepository.java (1)

13-15: 커스텀 레포지토리 확장 구조 적절

읽기 전용 DTO 프로젝션으로 전환된 설계가 서비스 단 단순화를 돕습니다. existsByGroup_idAndUser_id 유지도 합리적입니다.

src/main/java/project/flipnote/groupjoin/repository/GroupJoinRepositoryImpl.java (1)

31-33: UserProfile.nickname 필드 사용 검증 필요

프로젝션에 groupJoin.user.nickname을 사용합니다. UserProfile에 해당 필드가 없으면 QueryDSL 코드 생성/실행 시 오류가 납니다. name 등 실제 필드명과 DTO(GroupJoinInfo.nickname)를 일치시키세요.

위 파일의 검증 스크립트(첫 파일 코멘트)를 재사용해 필드 존재를 확인해 주세요.

src/main/java/project/flipnote/groupjoin/controller/docs/GroupJoinControllerDocs.java (2)

1-124: 전체적으로 품질 양호

보안 스키마 적용, Principal hidden 처리, 요청 본문 @Valid, 상태코드 정의가 잘 정리되어 있습니다.


26-92: 확인 완료: Docs와 컨트롤러 구현 일치
모든 메서드 시그니처, @PathVariable 이름·순서, 에러코드 정의가 Docs 인터페이스와 일치합니다.

src/main/java/project/flipnote/group/controller/GroupController.java (1)

21-21: 문서 인터페이스 구현 방향 좋습니다

컨트롤러가 GroupControllerDocs를 구현하도록 바꾼 점 좋습니다. 시그니처 불일치를 컴파일 타임에 막고, springdoc가 인터페이스의 어노테이션을 반영해 스웨거 스펙을 생성할 수 있습니다.

Also applies to: 35-35

src/main/java/project/flipnote/group/controller/docs/GroupControllerDocs.java (1)

169-173: cursor 파라미터명은 실제 DTO 필드명(cursor)과 일치하므로 변경 불필요

Likely an incorrect or invalid review comment.

@stoneTiger0912 stoneTiger0912 merged commit a06d0ea into develop Aug 31, 2025
3 checks passed
@stoneTiger0912 stoneTiger0912 deleted the refactor/group-package-and-docs branch August 31, 2025 13:12
@stoneTiger0912 stoneTiger0912 added the enhancement New feature or request label Sep 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants