-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: 동아리 관리(지원서/회비/모집공고) 관련 #266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package gg.agit.konect.domain.club.controller; | ||
|
|
||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PatchMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
|
|
||
| import gg.agit.konect.domain.club.dto.ClubSettingsResponse; | ||
| import gg.agit.konect.domain.club.dto.ClubSettingsUpdateRequest; | ||
| import gg.agit.konect.global.auth.annotation.UserId; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
|
|
||
| @Tag(name = "(Normal) Club - Settings: 동아리 설정 관리") | ||
| @RequestMapping("/clubs") | ||
| public interface ClubSettingsApi { | ||
|
|
||
| @Operation(summary = "동아리 설정 정보를 조회한다.", description = """ | ||
| 동아리 설정 관리 화면에 필요한 정보를 조회합니다. | ||
|
|
||
| 토글 상태(모집공고, 지원서, 회비)와 각 항목의 요약 정보를 반환합니다. | ||
| - 모집공고: 모집 기간 정보 | ||
| - 지원서: 문항 개수 | ||
| - 회비: 금액, 은행, 계좌번호, 예금주 | ||
|
|
||
| 요약 정보가 설정되지 않은 경우 해당 필드는 null로 반환됩니다. | ||
|
|
||
| ## 에러 | ||
| - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. | ||
| - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. | ||
| - FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다. | ||
| """) | ||
| @GetMapping("/{clubId}/settings") | ||
| ResponseEntity<ClubSettingsResponse> getSettings( | ||
| @PathVariable(name = "clubId") Integer clubId, | ||
| @UserId Integer userId | ||
| ); | ||
|
|
||
| @Operation(summary = "동아리 설정을 변경한다.", description = """ | ||
| 동아리의 토글 설정(모집공고, 지원서, 회비 활성화 여부)을 변경합니다. | ||
|
|
||
| 요청에 포함된 필드만 업데이트됩니다. (PATCH 방식) | ||
| - isRecruitmentEnabled: 모집공고 활성화 여부 | ||
| - isApplicationEnabled: 지원서 활성화 여부 | ||
| - isFeeEnabled: 회비 활성화 여부 | ||
|
|
||
| ## 에러 | ||
| - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. | ||
| - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. | ||
| - FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다. | ||
| """) | ||
| @PatchMapping("/{clubId}/settings") | ||
| ResponseEntity<ClubSettingsResponse> updateSettings( | ||
| @PathVariable(name = "clubId") Integer clubId, | ||
| @Valid @RequestBody ClubSettingsUpdateRequest request, | ||
| @UserId Integer userId | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package gg.agit.konect.domain.club.controller; | ||
|
|
||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import gg.agit.konect.domain.club.dto.ClubSettingsResponse; | ||
| import gg.agit.konect.domain.club.dto.ClubSettingsUpdateRequest; | ||
| import gg.agit.konect.domain.club.service.ClubSettingsService; | ||
| import gg.agit.konect.global.auth.annotation.UserId; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/clubs") | ||
| public class ClubSettingsController implements ClubSettingsApi { | ||
|
|
||
| private final ClubSettingsService clubSettingsService; | ||
|
|
||
| @Override | ||
| public ResponseEntity<ClubSettingsResponse> getSettings( | ||
| @PathVariable(name = "clubId") Integer clubId, | ||
| @UserId Integer userId | ||
| ) { | ||
| ClubSettingsResponse response = clubSettingsService.getSettings(clubId, userId); | ||
| return ResponseEntity.ok(response); | ||
| } | ||
|
|
||
| @Override | ||
| public ResponseEntity<ClubSettingsResponse> updateSettings( | ||
| @PathVariable(name = "clubId") Integer clubId, | ||
| @Valid @RequestBody ClubSettingsUpdateRequest request, | ||
| @UserId Integer userId | ||
| ) { | ||
| ClubSettingsResponse response = clubSettingsService.updateSettings(clubId, userId, request); | ||
| return ResponseEntity.ok(response); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,19 +19,15 @@ public record ClubFeeInfoResponse( | |
| String accountNumber, | ||
|
|
||
| @Schema(description = "예금주", example = "BCSD", requiredMode = REQUIRED) | ||
| String accountHolder, | ||
|
|
||
| @Schema(description = "회비 납부 필요 여부", example = "true", requiredMode = REQUIRED) | ||
| Boolean isFeeRequired | ||
| String accountHolder | ||
|
Comment on lines
19
to
+22
|
||
| ) { | ||
| public static ClubFeeInfoResponse of(Club club, Integer bankId, String bankName) { | ||
| return new ClubFeeInfoResponse( | ||
| club.getFeeAmount(), | ||
| bankId, | ||
| bankName, | ||
| club.getFeeAccountNumber(), | ||
| club.getFeeAccountHolder(), | ||
| club.getIsFeeRequired() | ||
| club.getFeeAccountHolder() | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package gg.agit.konect.domain.club.dto; | ||
|
|
||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; | ||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| @Schema(description = "동아리 설정 관리 응답") | ||
| public record ClubSettingsResponse( | ||
| @Schema(description = "모집공고 활성화 여부", example = "true", requiredMode = REQUIRED) | ||
| Boolean isRecruitmentEnabled, | ||
|
|
||
| @Schema(description = "지원서 활성화 여부", example = "true", requiredMode = REQUIRED) | ||
| Boolean isApplicationEnabled, | ||
|
|
||
| @Schema(description = "회비 활성화 여부", example = "false", requiredMode = REQUIRED) | ||
| Boolean isFeeEnabled, | ||
|
|
||
| @Schema(description = "모집공고 요약 정보", requiredMode = NOT_REQUIRED) | ||
| RecruitmentSummary recruitment, | ||
|
|
||
| @Schema(description = "지원서 요약 정보", requiredMode = NOT_REQUIRED) | ||
| ApplicationSummary application, | ||
|
|
||
| @Schema(description = "회비 요약 정보", requiredMode = NOT_REQUIRED) | ||
| FeeSummary fee | ||
| ) { | ||
| @Schema(description = "모집공고 요약") | ||
| public record RecruitmentSummary( | ||
| @Schema(description = "모집 시작일", example = "2026.02.02", requiredMode = NOT_REQUIRED) | ||
| @JsonFormat(pattern = "yyyy.MM.dd") | ||
| LocalDate startDate, | ||
|
|
||
| @Schema(description = "모집 종료일", example = "2027.02.02", requiredMode = NOT_REQUIRED) | ||
| @JsonFormat(pattern = "yyyy.MM.dd") | ||
| LocalDate endDate, | ||
|
|
||
| @Schema(description = "상시 모집 여부", example = "false", requiredMode = REQUIRED) | ||
| Boolean isAlwaysRecruiting | ||
| ) { | ||
| } | ||
|
|
||
| @Schema(description = "지원서 요약") | ||
| public record ApplicationSummary( | ||
| @Schema(description = "문항 개수", example = "3", requiredMode = REQUIRED) | ||
| Integer questionCount | ||
| ) { | ||
| } | ||
|
|
||
| @Schema(description = "회비 요약") | ||
| public record FeeSummary( | ||
| @Schema(description = "회비 금액", example = "3만원", requiredMode = NOT_REQUIRED) | ||
| String amount, | ||
|
|
||
| @Schema(description = "은행 고유 ID", example = "1", requiredMode = NOT_REQUIRED) | ||
| Integer bankId, | ||
|
|
||
| @Schema(description = "은행명", example = "국민은행", requiredMode = NOT_REQUIRED) | ||
| String bankName, | ||
|
|
||
| @Schema(description = "계좌번호", example = "123-456-7890", requiredMode = NOT_REQUIRED) | ||
| String accountNumber, | ||
|
|
||
| @Schema(description = "예금주", example = "BCSD", requiredMode = NOT_REQUIRED) | ||
| String accountHolder | ||
| ) { | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package gg.agit.konect.domain.club.dto; | ||
|
|
||
| import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| @Schema(description = "동아리 설정 변경 요청") | ||
| public record ClubSettingsUpdateRequest( | ||
| @Schema(description = "모집공고 활성화 여부", example = "true", requiredMode = NOT_REQUIRED) | ||
| Boolean isRecruitmentEnabled, | ||
|
|
||
| @Schema(description = "지원서 활성화 여부", example = "true", requiredMode = NOT_REQUIRED) | ||
| Boolean isApplicationEnabled, | ||
|
|
||
| @Schema(description = "회비 활성화 여부", example = "false", requiredMode = NOT_REQUIRED) | ||
| Boolean isFeeEnabled | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,6 @@ | |
| import static jakarta.persistence.GenerationType.IDENTITY; | ||
| import static lombok.AccessLevel.PROTECTED; | ||
|
|
||
| import java.time.LocalDate; | ||
|
|
||
| import org.springframework.util.StringUtils; | ||
|
|
||
| import gg.agit.konect.domain.club.dto.ClubCreateRequest; | ||
|
|
@@ -77,8 +75,14 @@ public class Club extends BaseEntity { | |
| @Column(name = "fee_account_holder", length = 100) | ||
| private String feeAccountHolder; | ||
|
|
||
| @Column(name = "fee_deadline") | ||
| private LocalDate feeDeadline; | ||
| @Column(name = "is_fee_required") | ||
| private Boolean isFeeRequired; | ||
|
|
||
| @Column(name = "is_recruitment_enabled") | ||
| private Boolean isRecruitmentEnabled; | ||
|
|
||
| @Column(name = "is_application_enabled") | ||
| private Boolean isApplicationEnabled; | ||
|
Comment on lines
+78
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: git ls-files | grep -i club | head -20Repository: BCSDLab/KONECT_BACK_END Length of output: 1575 🏁 Script executed: find . -type f -name "Club.java" | head -5Repository: BCSDLab/KONECT_BACK_END Length of output: 125 🏁 Script executed: find . -type f -name "*ClubSettingsResponse*"Repository: BCSDLab/KONECT_BACK_END Length of output: 139 🏁 Script executed: cat -n src/main/java/gg/agit/konect/domain/club/model/Club.java | head -200Repository: BCSDLab/KONECT_BACK_END Length of output: 7881 🏁 Script executed: cat -n src/main/java/gg/agit/konect/domain/club/dto/ClubSettingsResponse.javaRepository: BCSDLab/KONECT_BACK_END Length of output: 3021 🏁 Script executed: rg -n "ClubSettingsResponse" --type java -B 3 -A 10 src/main/java/gg/agit/konect/domain/club/service/Repository: BCSDLab/KONECT_BACK_END Length of output: 7813 🏁 Script executed: rg -n "getIsRecruitmentEnabled|getIsApplicationEnabled|getIsFeeRequired" --type java src/main/java/gg/agit/konect/domain/club/Repository: BCSDLab/KONECT_BACK_END Length of output: 1042 🏁 Script executed: cat -n src/main/java/gg/agit/konect/domain/club/service/ClubSettingsService.java | sed -n '32,90p'Repository: BCSDLab/KONECT_BACK_END Length of output: 2712 🏁 Script executed: rg -n "club\.get(IsRecruitmentEnabled|IsApplicationEnabled|IsFeeRequired)" --type javaRepository: BCSDLab/KONECT_BACK_END Length of output: 1042 🏁 Script executed: rg -n "\.isRecruitmentEnabled()|\.isApplicationEnabled()|\.isFeeRequired()" --type java -A 2 -B 2Repository: BCSDLab/KONECT_BACK_END Length of output: 2451 🏁 Script executed: cat -n src/main/java/gg/agit/konect/domain/club/service/ClubApplicationService.java | sed -n '185,200p'Repository: BCSDLab/KONECT_BACK_END Length of output: 824 Initialize boolean flags in the factory method or set column defaults. The new boolean fields ( 🛠️ Proposed fix: Initialize in factory method public static Club of(ClubCreateRequest request, University university) {
return Club.builder()
.name(request.name())
.description(request.description())
.introduce(request.introduce())
.imageUrl(request.imageUrl())
.location(request.location())
.clubCategory(request.clubCategory())
.university(university)
+ .isFeeRequired(false)
+ .isRecruitmentEnabled(false)
+ .isApplicationEnabled(false)
.build();
}🤖 Prompt for AI Agents |
||
|
|
||
| @OneToOne(mappedBy = "club", fetch = LAZY, cascade = ALL, orphanRemoval = true) | ||
| private ClubRecruitment clubRecruitment; | ||
|
|
@@ -97,7 +101,9 @@ private Club( | |
| String feeBank, | ||
| String feeAccountNumber, | ||
| String feeAccountHolder, | ||
| LocalDate feeDeadline, | ||
| Boolean isFeeRequired, | ||
| Boolean isRecruitmentEnabled, | ||
| Boolean isApplicationEnabled, | ||
| ClubRecruitment clubRecruitment | ||
| ) { | ||
| this.id = id; | ||
|
|
@@ -112,7 +118,9 @@ private Club( | |
| this.feeBank = feeBank; | ||
| this.feeAccountNumber = feeAccountNumber; | ||
| this.feeAccountHolder = feeAccountHolder; | ||
| this.feeDeadline = feeDeadline; | ||
| this.isFeeRequired = isFeeRequired; | ||
| this.isRecruitmentEnabled = isRecruitmentEnabled; | ||
| this.isApplicationEnabled = isApplicationEnabled; | ||
| this.clubRecruitment = clubRecruitment; | ||
| } | ||
|
|
||
|
|
@@ -132,19 +140,18 @@ public void replaceFeeInfo( | |
| String feeAmount, | ||
| String feeBank, | ||
| String feeAccountNumber, | ||
| String feeAccountHolder, | ||
| LocalDate feeDeadline | ||
| String feeAccountHolder | ||
| ) { | ||
| if (isFeeInfoEmpty(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline)) { | ||
| if (isFeeInfoEmpty(feeAmount, feeBank, feeAccountNumber, feeAccountHolder)) { | ||
| clearFeeInfo(); | ||
| return; | ||
| } | ||
|
|
||
| if (!isFeeInfoComplete(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline)) { | ||
| if (!isFeeInfoComplete(feeAmount, feeBank, feeAccountNumber, feeAccountHolder)) { | ||
| throw CustomException.of(INVALID_REQUEST_BODY); | ||
| } | ||
|
|
||
| updateFeeInfo(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline); | ||
| updateFeeInfo(feeAmount, feeBank, feeAccountNumber, feeAccountHolder); | ||
| } | ||
|
|
||
| public void updateInfo(String description, String imageUrl, String location, String introduce) { | ||
|
|
@@ -159,53 +166,62 @@ public void updateBasicInfo(String name, ClubCategory clubCategory) { // 어드 | |
| this.clubCategory = clubCategory; | ||
| } | ||
|
|
||
| public void updateSettings( | ||
| Boolean isRecruitmentEnabled, | ||
| Boolean isApplicationEnabled, | ||
| Boolean isFeeRequired | ||
| ) { | ||
| if (isRecruitmentEnabled != null) { | ||
| this.isRecruitmentEnabled = isRecruitmentEnabled; | ||
| } | ||
| if (isApplicationEnabled != null) { | ||
| this.isApplicationEnabled = isApplicationEnabled; | ||
| } | ||
| if (isFeeRequired != null) { | ||
| this.isFeeRequired = isFeeRequired; | ||
| } | ||
| } | ||
|
|
||
| private boolean isFeeInfoEmpty( | ||
| String feeAmount, | ||
| String feeBank, | ||
| String feeAccountNumber, | ||
| String feeAccountHolder, | ||
| LocalDate feeDeadline | ||
| String feeAccountHolder | ||
| ) { | ||
| return !StringUtils.hasText(feeAmount) | ||
| && !StringUtils.hasText(feeBank) | ||
| && !StringUtils.hasText(feeAccountNumber) | ||
| && !StringUtils.hasText(feeAccountHolder) | ||
| && feeDeadline == null; | ||
| && !StringUtils.hasText(feeAccountHolder); | ||
| } | ||
|
|
||
| private boolean isFeeInfoComplete( | ||
| String feeAmount, | ||
| String feeBank, | ||
| String feeAccountNumber, | ||
| String feeAccountHolder, | ||
| LocalDate feeDeadline | ||
| String feeAccountHolder | ||
| ) { | ||
| return StringUtils.hasText(feeAmount) | ||
| && StringUtils.hasText(feeBank) | ||
| && StringUtils.hasText(feeAccountNumber) | ||
| && StringUtils.hasText(feeAccountHolder) | ||
| && feeDeadline != null; | ||
| && StringUtils.hasText(feeAccountHolder); | ||
| } | ||
|
|
||
| private void updateFeeInfo( | ||
| String feeAmount, | ||
| String feeBank, | ||
| String feeAccountNumber, | ||
| String feeAccountHolder, | ||
| LocalDate feeDeadline | ||
| String feeAccountHolder | ||
| ) { | ||
| this.feeAmount = feeAmount; | ||
| this.feeBank = feeBank; | ||
| this.feeAccountNumber = feeAccountNumber; | ||
| this.feeAccountHolder = feeAccountHolder; | ||
| this.feeDeadline = feeDeadline; | ||
| } | ||
|
|
||
| private void clearFeeInfo() { | ||
| this.feeAmount = null; | ||
| this.feeBank = null; | ||
| this.feeAccountNumber = null; | ||
| this.feeAccountHolder = null; | ||
| this.feeDeadline = null; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClubFeeInfoReplaceRequest에서 isFeeRequired 입력이 제거되면서, 기존 클라이언트가 해당 필드를 포함해 요청할 경우 Jackson 설정에 따라 400(unknown property)로 실패할 수 있습니다. 하위 호환이 필요하면 요청 DTO에 unknown property 무시 설정(@JsonIgnoreProperties(ignoreUnknown = true))을 추가하거나, 일정 기간 구 DTO를 병행 지원하는 방안을 고려해주세요.