Conversation
activeMemberCount 수락된 멤버들 수만 나오도록 변경
There was a problem hiding this comment.
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
StudyApplyentity/repository/DTO/mapper and related service/controller endpoints; shift study join/approval flow toGroupMemberstatuses. - Create a
Groupimmediately when creating Project/Study posts; exposegroupIdandmaxMemberin 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 usePost.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.Valuewas 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
GroupRepositoryis injected/stored inProjectServicebut 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
maxMemberis a primitiveintwith no validation. If the client omits it, it will default to0, which can create a group with an invalid max size. Consider usingInteger+@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.
| @Schema(description = "모집 여부 상태 변경", example = "false") | ||
| private boolean recruiting; | ||
|
|
||
| @Schema(description = "모집 마감 기한 변경") | ||
| private LocalDate deadline; | ||
|
|
||
| @Schema(description = "최대 모집 인원 변경") | ||
| private int maxMember; | ||
| } |
There was a problem hiding this comment.
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).
| @Comment("그룹 삭제 여부(Soft Delete") | ||
| public Boolean isDeleted = false; |
There was a problem hiding this comment.
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).
| @Comment("그룹 삭제 여부(Soft Delete") | |
| public Boolean isDeleted = false; | |
| @Comment("그룹 삭제 여부(Soft Delete)") | |
| @Column(nullable = false) | |
| private boolean isDeleted = false; |
| @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; |
There was a problem hiding this comment.
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.
| //최대 모집 인원을 변경합니다. | ||
| public void updateGroupMaxMember(Long groupId, int maxMember){ | ||
| Group group = groupRepository.findByIdAndIsDeletedFalse(groupId) | ||
| .orElseThrow(()-> new CustomException(ErrorCode.NOT_FOUND,"그룹을 찾을 수 없습니다.")); | ||
| group.changeMaxActiveMembers(maxMember); | ||
| } |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
💡 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()); |
There was a problem hiding this comment.
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 👍 / 👎.
| Boolean hasApplied = groupService.hasAppliedToGroup( | ||
| study.getGroup() != null ? study.getGroup().getId() : null, userId); |
There was a problem hiding this comment.
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 👍 / 👎.
|
conflict 해결 후 다시 올려주세용 |
🎀 PR 유형
어떤 변경 사항이 있나요?
✨ 추가/수정 내용
🎊 PR Checklist
PR이 다음 요구 사항을 충족하는지 확인하세요.