Skip to content

Feat: [FN-323] 가입 신청 응답#12

Merged
stoneTiger0912 merged 7 commits intomainfrom
feat/group-join-response
Feb 28, 2026
Merged

Feat: [FN-323] 가입 신청 응답#12
stoneTiger0912 merged 7 commits intomainfrom
feat/group-join-response

Conversation

@stoneTiger0912
Copy link
Member

@stoneTiger0912 stoneTiger0912 commented Feb 28, 2026

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 그룹 가입 신청에 대한 승인/거절(PATCH) 기능이 추가되었습니다.
    • 가입 응답 결과를 반환하는 응답 형식이 추가되었습니다.
  • 개선사항

    • 그룹 가입 관련 API 경로가 분리·정비되어 관리가 쉬워졌습니다.
    • 그룹 회원 수 제한(최대 회원수) 및 가입 가능 여부 검사, 회원 수 증감 자동 처리가 도입되었습니다.
    • 가입 요청 조회 및 상태 업데이트를 위한 저장·조회 흐름이 강화되었습니다.

@stoneTiger0912 stoneTiger0912 self-assigned this Feb 28, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a734eb2 and 2e2b3cd.

📒 Files selected for processing (3)
  • src/main/java/flipnote/group/adapter/in/web/JoinController.java
  • src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java
  • src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java

Walkthrough

그룹 가입 요청에 대한 응답 기능을 추가합니다. PATCH 엔드포인트로 가입 신청을 수락/거절하며, DTO·커맨드·결과 타입과 use case/서비스, 레포지토리·어댑터 및 엔티티 멤버 수 증감 로직이 추가/수정됩니다.

Changes

Cohort / File(s) Summary
Controller 계층
src/main/java/flipnote/group/adapter/in/web/JoinController.java
기본 경로를 /v1/groups/{groupId}/joins로 변경. 새로운 PATCH /{joinId} 엔드포인트 respondToJoinRequest 추가(JoinRespondUseCase 주입). 기존 POST/GET 매핑의 서브경로 조정.
Domain 모델
src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java, src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java
GroupEntityplusCount()/minusCount() 추가(멤버수 증감, 경계검사). JoinEntity에 id 포함 생성자 시그니처 변경 및 updateStatus(JoinStatus) 추가.
요청/응답 DTO
src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java, src/main/java/flipnote/group/api/dto/response/JoinRespondResponseDto.java
가입 응답 요청/응답 레코드 추가(상태 검증, 결과 변환 헬퍼 포함).
Application 계층 (포트/명령/결과)
src/main/java/flipnote/group/application/port/in/JoinRespondUseCase.java, src/main/java/flipnote/group/application/port/in/command/JoinRespondCommand.java, src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java
JoinRespond 유스케이스 인터페이스와 커맨드·결과 타입 추가(그룹/유저/조인ID/상태 포함).
Application 계층 (서비스)
src/main/java/flipnote/group/application/service/JoinRespondService.java
JoinRespondUseCase 구현 추가. 권한 검사, 조인 조회/상태 변경, 수락 시 용량 체크·멤버 생성 및 저장, 조인 업데이트 로직 포함.
Repository 포트 / 어댑터 변경
src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java, src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java, src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java, src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java, src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java
GroupRepositoryPort에 checkJoinable 추가. JoinRepositoryPort에 findJoin/updateJoin 추가. GroupRepositoryAdapter 구현에 checkJoinable 추가(행 잠금 활용). JoinRepositoryAdapter에 조회·업데이트 메서드 추가. GroupMemberRepositoryAdapter.save에서 GroupRepository 주입 후 memberCount 증가 호출 추가.
퍼시스턴스 레이어 변경
src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java, (삭제) src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java
새 JPA GroupRepository 인터페이스 추가 및 findByIdForUpdate(PESSIMISTIC_WRITE) 선언. 기존 QueryDSL 패키지의 동일 인터페이스 파일 삭제. ChangeGroupService의 import 경로 조정.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Controller as JoinController
    participant Service as JoinRespondService
    participant JoinRepo as JoinRepository
    participant GroupRepo as GroupRepository
    participant GroupMemberRepo as GroupMemberRepository
    participant RoleRepo as GroupRoleRepository

    Client->>Controller: PATCH /v1/groups/{groupId}/joins/{joinId} (status)
    Controller->>Service: joinRespond(command)
    Service->>RoleRepo: checkPermission(JOIN_REQUEST_MANAGE)
    alt 권한 없음
        RoleRepo-->>Service: 권한 없음 에러
        Service-->>Controller: 에러
        Controller-->>Client: 에러 응답
    else 권한 있음
        Service->>JoinRepo: findJoin(joinId)
        alt Join 없음 또는 이미 ACCEPT
            JoinRepo-->>Service: 에러
            Service-->>Controller: 에러
            Controller-->>Client: 에러 응답
        else Join 존재
            alt status == REJECT
                Service->>JoinRepo: updateJoin(status=REJECT)
                JoinRepo-->>Service: updated JoinEntity
            else status == ACCEPT
                Service->>GroupRepo: checkJoinable(groupId)
                alt 그룹 만원
                    GroupRepo-->>Service: false
                    Service-->>Controller: 에러
                    Controller-->>Client: 에러 응답
                else 여유 있음
                    Service->>RoleRepo: findByIdAndRole(MEMBER)
                    Service->>GroupMemberRepo: save(new GroupMember)
                    GroupMemberRepo->>GroupRepo: findByIdForUpdate(groupId)
                    GroupRepo-->>GroupMemberRepo: GroupEntity
                    GroupMemberRepo->>GroupRepo: plusCount() 및 save
                    Service->>JoinRepo: updateJoin(status=ACCEPT)
                    JoinRepo-->>Service: updated JoinEntity
                end
            end
            Service-->>Controller: JoinRespondResult
            Controller-->>Client: 200 OK (JoinRespondResponseDto)
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 새로 온 요청 살피며,
수락은 문을, 거절은 손을,
멤버 수를 톡톡 세어봐요.
당겨진 잠금 아래서,
함께 뛰는 우리 토끼들 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 변경사항의 핵심을 명확하게 반영하고 있습니다. 가입 신청 응답 기능 추가라는 주요 변경사항을 간결하고 구체적으로 설명하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/group-join-response

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 5

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java (1)

56-59: ⚠️ Potential issue | 🔴 Critical

무한 재귀 호출로 인한 StackOverflowError 발생

findAllByCursorAndCreatedUserId 메서드가 groupRepository에 위임하지 않고 자기 자신을 호출하고 있습니다. 이는 런타임에 StackOverflowError를 발생시킵니다.

🐛 수정 제안
 `@Override`
 public List<GroupInfo> findAllByCursorAndCreatedUserId(Long cursorId, Category category, int size, Long userId) {
-  return findAllByCursorAndCreatedUserId(cursorId, category, size, userId);
+  return groupRepository.findAllByCursorAndCreatedUserId(cursorId, category, size, userId);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java`
around lines 56 - 59, The method findAllByCursorAndCreatedUserId currently calls
itself causing infinite recursion; change the implementation to delegate to the
injected groupRepository (e.g., call
groupRepository.findAllByCursorAndCreatedUserId(cursorId, category, size,
userId)) and return its result, ensuring you reference the existing
groupRepository field and keep the method signature intact.
🧹 Nitpick comments (1)
src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java (1)

61-63: 상태 전이 검증 누락

updateStatus 메서드에서 유효한 상태 전이인지 검증하지 않습니다. 예를 들어 이미 ACCEPTED된 요청을 다시 PENDING이나 REJECTED로 변경하는 것이 허용될 수 있습니다. 비즈니스 로직에 따라 유효하지 않은 상태 전이를 방지하는 검증이 필요할 수 있습니다.

♻️ 상태 전이 검증 추가 예시
 public void updateStatus(JoinStatus status) {
+    if (this.status != JoinStatus.PENDING) {
+        throw new IllegalStateException("이미 처리된 가입 신청입니다.");
+    }
     this.status = status;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java` around lines
61 - 63, Add validation to JoinEntity.updateStatus to enforce allowed state
transitions: determine the allowed transitions in the JoinStatus enum (or a
transition matrix) and before assigning this.status = status check current
this.status against the target status; if the transition is invalid, throw an
IllegalStateException (or a domain-specific exception) with a clear message.
Locate JoinEntity.updateStatus and refer to JoinStatus values to implement the
transition rules and tests that reject transitions like ACCEPTED ->
PENDING/REJECTED if those are disallowed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/flipnote/group/adapter/in/web/JoinController.java`:
- Line 78: The method findGroupJoinList is returning HttpStatus.CREATED for a
GET/listing endpoint; change the response to use HttpStatus.OK (or
ResponseEntity.ok(...)) instead of HttpStatus.CREATED so the controller returns
200 for successful GETs; locate the return in JoinController.findGroupJoinList
and update the ResponseEntity.status(HttpStatus.CREATED).body(res) to use
HttpStatus.OK or ResponseEntity.ok(res).

In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java`:
- Around line 31-37: The code calls groupRepository.findById(...) inside
GroupMemberRepositoryAdapter and then groupEntity.plusCount(), which obscures
whether the entity was locked earlier in JoinRespondService.joinRespond() via
checkJoinable(); update the method to either accept the already-locked
GroupEntity from checkJoinable() (pass the entity through instead of
re-querying) or replace findById(...) with the explicit locked lookup method
(e.g., groupRepository.findByIdForUpdate(...)) so the locking intent is clear;
keep using groupEntity.plusCount() and rely on JPA dirty checking (no explicit
save needed) but ensure the code consistently uses the locked entity or locked
finder to avoid ambiguity.

In `@src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java`:
- Around line 6-9: JoinRespondRequestDto currently accepts any JoinStatus
(PENDING, CANCEL, etc.) and these invalid states are being treated as ACCEPT;
restrict allowed values by adding a validation annotation on the status
component (e.g., create and apply a custom constraint like `@AllowedJoinStatus`
that only permits JoinStatus.ACCEPT and JoinStatus.REJECT) and implement a
ConstraintValidator that checks the JoinStatus enum value; update the record
declaration JoinRespondRequestDto( `@NotNull` `@AllowedJoinStatus` JoinStatus status
) so only ACCEPT/REJECT are accepted.

In
`@src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java`:
- Around line 3-6: JoinRespondResult currently depends on
adapter.out.entity.JoinEntity which violates hexagonal layering; replace that
dependency by using a domain model or an application-level DTO: change the
record JoinRespondResult to carry a domain type (e.g., domain.model.Join or your
Domain Join aggregate) or a new DTO (e.g., JoinResponse/JoinDto placed under
application.port.in.result) and update the places that construct
JoinRespondResult (factories/mappers) to map from JoinEntity to the domain model
or DTO (create a mapper method that converts JoinEntity -> domain Join or
JoinResponse). Ensure all imports reference the new domain/DTO type instead of
flipnote.group.adapter.out.entity.JoinEntity and update callers that construct
or consume JoinRespondResult accordingly.

In `@src/main/java/flipnote/group/application/service/JoinRespondService.java`:
- Around line 50-52: The status validation in JoinRespondService currently only
blocks ACCEPT; update the logic in the method performing the check (where
join.getStatus() is inspected) to allow transitions only when the current status
is PENDING: if status != JoinStatus.PENDING, throw an appropriate exception
(e.g., IllegalStateException) with a clear message indicating the current status
so callers know why the operation is invalid; ensure both ACCEPT and REJECT (and
any other non-PENDING states) are handled by this validation before proceeding
with accept/reject flows.

---

Outside diff comments:
In
`@src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java`:
- Around line 56-59: The method findAllByCursorAndCreatedUserId currently calls
itself causing infinite recursion; change the implementation to delegate to the
injected groupRepository (e.g., call
groupRepository.findAllByCursorAndCreatedUserId(cursorId, category, size,
userId)) and return its result, ensuring you reference the existing
groupRepository field and keep the method signature intact.

---

Nitpick comments:
In `@src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java`:
- Around line 61-63: Add validation to JoinEntity.updateStatus to enforce
allowed state transitions: determine the allowed transitions in the JoinStatus
enum (or a transition matrix) and before assigning this.status = status check
current this.status against the target status; if the transition is invalid,
throw an IllegalStateException (or a domain-specific exception) with a clear
message. Locate JoinEntity.updateStatus and refer to JoinStatus values to
implement the transition rules and tests that reject transitions like ACCEPTED
-> PENDING/REJECTED if those are disallowed.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c94c35 and a734eb2.

📒 Files selected for processing (17)
  • src/main/java/flipnote/group/adapter/in/web/JoinController.java
  • src/main/java/flipnote/group/adapter/out/entity/GroupEntity.java
  • src/main/java/flipnote/group/adapter/out/entity/JoinEntity.java
  • src/main/java/flipnote/group/adapter/out/persistence/GroupMemberRepositoryAdapter.java
  • src/main/java/flipnote/group/adapter/out/persistence/GroupRepositoryAdapter.java
  • src/main/java/flipnote/group/adapter/out/persistence/JoinRepositoryAdapter.java
  • src/main/java/flipnote/group/api/dto/request/JoinRespondRequestDto.java
  • src/main/java/flipnote/group/api/dto/response/JoinRespondResponseDto.java
  • src/main/java/flipnote/group/application/port/in/JoinRespondUseCase.java
  • src/main/java/flipnote/group/application/port/in/command/JoinRespondCommand.java
  • src/main/java/flipnote/group/application/port/in/result/JoinRespondResult.java
  • src/main/java/flipnote/group/application/port/out/GroupRepositoryPort.java
  • src/main/java/flipnote/group/application/port/out/JoinRepositoryPort.java
  • src/main/java/flipnote/group/application/service/ChangeGroupService.java
  • src/main/java/flipnote/group/application/service/JoinRespondService.java
  • src/main/java/flipnote/group/infrastructure/persistence/jpa/GroupRepository.java
  • src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java
💤 Files with no reviewable changes (1)
  • src/main/java/flipnote/group/infrastructure/persistence/querydsl/GroupRepository.java

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant