Skip to content

Refactor/study to groupmember#48

Closed
khm1102 wants to merge 12 commits intomainfrom
refactor/study-to-groupmember
Closed

Refactor/study to groupmember#48
khm1102 wants to merge 12 commits intomainfrom
refactor/study-to-groupmember

Conversation

@khm1102
Copy link
Contributor

@khm1102 khm1102 commented Mar 1, 2026

🎀 PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

✨ 추가/수정 내용

🎊 PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다. Commit message convention 참고 (Ctrl + 클릭하세요.)
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Copilot AI review requested due to automatic review settings March 1, 2026 05:49
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors “study applications” to use GroupMember/Group as the source of truth (removing the StudyApply domain), and aligns project/study flows so a Group is created at post creation time and then managed (recruitment close, max member updates, soft delete) alongside the post.

Changes:

  • Remove StudyApply entity/repository/DTO/mapper and related service/controller endpoints; shift study join/approval flow to GroupMember statuses.
  • Create a Group immediately when creating Project/Study posts; expose groupId and maxMember in relevant response DTOs and mappings.
  • Introduce soft delete for Group (isDeleted) and update repositories/services to avoid returning deleted groups; adjust view count handling to use Post.viewCount.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/main/java/com/example/cbumanage/utils/StudyApplyMapper.java Removes mapper for deleted StudyApply flow.
src/main/java/com/example/cbumanage/utils/PostMapper.java Updates project/study DTO mappings (author info nullable handling, groupId/maxMember, viewCount source).
src/main/java/com/example/cbumanage/utils/GroupUtil.java Counts ACTIVE members via repository query instead of members.size().
src/main/java/com/example/cbumanage/service/StudyService.java Creates group on study creation; computes isLeader/hasApplied; closes recruitment via group member statuses; soft-deletes linked group.
src/main/java/com/example/cbumanage/service/ProjectService.java Creates group with configurable max members; maps author info; updates group max members; soft-deletes linked group; viewCount moved to Post.
src/main/java/com/example/cbumanage/service/PostService.java Removes unused group lookup when creating reports.
src/main/java/com/example/cbumanage/service/GroupService.java Adds soft-delete-aware group loading, pending rejection helper, recruitment guard, max-member update helper, and study recruiting sync.
src/main/java/com/example/cbumanage/repository/StudyRepository.java Adds findByGroupId for group↔study linkage.
src/main/java/com/example/cbumanage/repository/StudyApplyRepository.java Deletes repository with removal of StudyApply.
src/main/java/com/example/cbumanage/repository/ProjectRepository.java Ensures findByPostId ignores deleted posts; removes join to deleted author relation.
src/main/java/com/example/cbumanage/repository/GroupRepository.java Adds soft-delete-aware finder and updates group lookup by user/status; adds member count query.
src/main/java/com/example/cbumanage/model/enums/StudyApplyStatus.java Deletes enum with removal of StudyApply.
src/main/java/com/example/cbumanage/model/StudyApply.java Deletes entity with removal of StudyApply.
src/main/java/com/example/cbumanage/model/Study.java Makes group mandatory and sets it at creation time.
src/main/java/com/example/cbumanage/model/Project.java Removes redundant author/member and viewCount fields; relies on Post + Group.
src/main/java/com/example/cbumanage/model/Group.java Adds isDeleted soft delete flag and delete method.
src/main/java/com/example/cbumanage/dto/StudyApplyDTO.java Deletes DTO with removal of StudyApply.
src/main/java/com/example/cbumanage/dto/PostDTO.java Adds/updates fields for groupId, maxMember, isLeader, hasApplied.
src/main/java/com/example/cbumanage/controller/StudyController.java Makes study detail optionally authenticated; removes apply endpoints; updates close recruitment API response.
src/main/java/com/example/cbumanage/controller/ProjectController.java Updates docs to reflect configurable max member + group soft delete behavior.
Comments suppressed due to low confidence (3)

src/main/java/com/example/cbumanage/model/Group.java:13

  • Unused import software.amazon.awssdk.services.s3.endpoints.internal.Value was added and is not referenced in this entity. Please remove it to keep the model clean and avoid confusing dependencies.
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import software.amazon.awssdk.services.s3.endpoints.internal.Value;

src/main/java/com/example/cbumanage/service/ProjectService.java:52

  • GroupRepository is injected/stored in ProjectService but is not used anywhere in this class. If it’s not needed, remove the field and constructor parameter to keep the service dependencies minimal (and avoid unused-bean wiring).
    private final ProjectRepository projectRepository;
    private final PostRepository postRepository;
    private final CbuMemberRepository cbuMemberRepository;
    private final GroupRepository groupRepository;
    private final PostMapper postMapper;
    private final PostService postService;
    private final GroupService groupService;

    @Autowired
    public ProjectService(ProjectRepository projectRepository,
                          PostRepository postRepository,
                          CbuMemberRepository cbuMemberRepository,
                          GroupRepository groupRepository,
                          PostMapper postMapper,
                          PostService postService,
                          GroupService groupService
    ) {
        this.projectRepository = projectRepository;
        this.postRepository = postRepository;
        this.cbuMemberRepository = cbuMemberRepository;
        this.groupRepository = groupRepository;
        this.postMapper = postMapper;
        this.postService = postService;
        this.groupService= groupService;

src/main/java/com/example/cbumanage/dto/PostDTO.java:361

  • maxMember is a primitive int with no validation. If the client omits it, it will default to 0, which can create a group with an invalid max size. Consider using Integer + @NotNull (if required) and add validation like @Min(1) (and possibly an upper bound) to prevent invalid group sizes.
        @Schema(description = "모집 마감 기한")
        private LocalDate deadline;

        @Schema(description = "최대 모집 인원",example="10")
        private int maxMember;


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 472 to 480
@Schema(description = "모집 여부 상태 변경", example = "false")
private boolean recruiting;

@Schema(description = "모집 마감 기한 변경")
private LocalDate deadline;

@Schema(description = "최대 모집 인원 변경")
private int maxMember;
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

For a PATCH-style update DTO, maxMember being a primitive int means a missing JSON field becomes 0, which is indistinguishable from an intentional value and can corrupt data when used for updates. Make this field nullable (Integer) and validate/update conditionally (e.g., @Min(1) when present).

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +58
@Comment("그룹 삭제 여부(Soft Delete")
public Boolean isDeleted = false;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Group now introduces isDeleted for soft delete, but it is declared as public Boolean without a @Column(nullable = false) default at the DB level. Existing rows could end up with NULL, causing findByIdAndIsDeletedFalse / queries to stop matching them. Prefer private boolean isDeleted with @Column(nullable = false) and a DB default/backfill; also fix the comment text (missing closing parenthesis).

Suggested change
@Comment("그룹 삭제 여부(Soft Delete")
public Boolean isDeleted = false;
@Comment("그룹 삭제 여부(Soft Delete)")
@Column(nullable = false)
private boolean isDeleted = false;

Copilot uses AI. Check for mistakes.
Comment on lines 56 to 60
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id")
@Schema(description = "모집 마감 후 생성된 그룹")
@Comment("모집 마감 후 생성된 그룹 FK")
@JoinColumn(name = "group_id", nullable = false)
@Schema(description = "게시글 생성 시 자동 생성된 그룹")
@Comment("게시글 생성 시 자동 생성된 그룹 FK")
private Group group;
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Study.group is now nullable = false. If there are existing study rows without a linked group (from before this change), schema update/migration will fail or runtime reads may break. Ensure there is a migration/backfill strategy (set a DB default / create groups for existing studies) and consider whether the service should defensively handle legacy null values during rollout.

Copilot uses AI. Check for mistakes.
Comment on lines +199 to 204
//최대 모집 인원을 변경합니다.
public void updateGroupMaxMember(Long groupId, int maxMember){
Group group = groupRepository.findByIdAndIsDeletedFalse(groupId)
.orElseThrow(()-> new CustomException(ErrorCode.NOT_FOUND,"그룹을 찾을 수 없습니다."));
group.changeMaxActiveMembers(maxMember);
}
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

updateGroupMaxMember modifies a managed entity but the method itself is not transactional. Depending on Spring Data transaction boundaries this can result in the max member change not being persisted reliably. Make this method @Transactional (or call it only from a transactional context) and validate maxMember (e.g., >= current ACTIVE member count and >= 1) before applying the change.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to 129
groupService.updateGroupMaxMember(project.getGroup().getId(), req.getMaxMember());
if (project.getGroup() != null) {
GroupRecruitmentStatus status = req.isRecruiting() ? GroupRecruitmentStatus.OPEN : GroupRecruitmentStatus.CLOSED;
groupService.updateGroupRecruitment(project.getGroup().getId(), userId, status);
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

updatePostProject always calls updateGroupMaxMember(..., req.getMaxMember()). Since maxMember in PostProjectUpdateRequestDTO is a primitive int, an omitted field in a PATCH request will deserialize to 0 and unintentionally set the group max to 0. Make maxMember nullable (e.g., Integer) and only update when provided, with validation to prevent setting it below the current ACTIVE member count.

Suggested change
groupService.updateGroupMaxMember(project.getGroup().getId(), req.getMaxMember());
if (project.getGroup() != null) {
GroupRecruitmentStatus status = req.isRecruiting() ? GroupRecruitmentStatus.OPEN : GroupRecruitmentStatus.CLOSED;
groupService.updateGroupRecruitment(project.getGroup().getId(), userId, status);
Group group = project.getGroup();
if (group != null) {
// maxMember in the PATCH request may default to 0 when omitted (primitive int).
// Only update the group's max member when a positive value is explicitly provided.
if (req.getMaxMember() > 0) {
groupService.updateGroupMaxMember(group.getId(), req.getMaxMember());
}
GroupRecruitmentStatus status = req.isRecruiting() ? GroupRecruitmentStatus.OPEN : GroupRecruitmentStatus.CLOSED;
groupService.updateGroupRecruitment(group.getId(), userId, status);

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 085e7854dd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

orElseThrow(()->new CustomException(ErrorCode.NOT_FOUND,"해당 게시글을 찾을 수 없습니다."));
PostDTO.ProjectUpdateDTO projectUpdateDTO = postMapper.toPostProjectUpdateDTO(req);
updateProject(projectUpdateDTO, project);
groupService.updateGroupMaxMember(project.getGroup().getId(), req.getMaxMember());

Choose a reason for hiding this comment

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

P1 Badge Preserve existing max member on partial project updates

PATCH /post/project/{postId} now always applies req.getMaxMember() to the linked group, but the request DTO uses a primitive int for maxMember, so omitted fields deserialize as 0. Any client sending a partial update without maxMember will unintentionally reset maxActiveMembers to zero, breaking recruitment limits for that project group; gate this write behind explicit field presence (e.g., nullable Integer) before updating the group.

Useful? React with 👍 / 👎.

Comment on lines +62 to +63
Boolean hasApplied = groupService.hasAppliedToGroup(
study.getGroup() != null ? study.getGroup().getId() : null, userId);

Choose a reason for hiding this comment

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

P2 Badge Return a non-member state for anonymous study detail requests

Study detail was changed to allow unauthenticated access, but when userId is absent this call passes null into hasAppliedToGroup, which returns null and is forwarded to hasApplied. In the new DTO contract, hasApplied = null means “already joined,” so anonymous viewers can be misclassified as 가입완료 in clients that follow the documented branching rules; anonymous should map to a distinct non-member state (or be disambiguated with another field).

Useful? React with 👍 / 👎.

@BongSeongEun
Copy link
Contributor

conflict 해결 후 다시 올려주세용

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.

4 participants