diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java index 988747fc..abbf0a78 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java @@ -33,9 +33,8 @@ import gg.agit.konect.domain.club.dto.ClubPositionUpdateRequest; import gg.agit.konect.domain.club.dto.ClubPositionsResponse; import gg.agit.konect.domain.club.dto.ClubProfileUpdateRequest; -import gg.agit.konect.domain.club.dto.ClubRecruitmentCreateRequest; import gg.agit.konect.domain.club.dto.ClubRecruitmentResponse; -import gg.agit.konect.domain.club.dto.ClubRecruitmentUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubRecruitmentUpsertRequest; import gg.agit.konect.domain.club.dto.ClubsResponse; import gg.agit.konect.domain.club.dto.MyManagedClubResponse; import gg.agit.konect.domain.club.dto.MemberPositionChangeRequest; @@ -334,29 +333,11 @@ ResponseEntity getRecruitments( @UserId Integer userId ); - @Operation(summary = "동아리 모집 정보를 생성한다.", description = """ - 동아리 회장만 모집 공고를 생성할 수 있습니다. - 한 동아리당 하나의 모집 공고만 생성 가능합니다. - - ## 에러 - - INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다. - - INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다. - - INVALID_RECRUITMENT_PERIOD (400): 모집 시작일은 모집 마감일보다 이전이어야 합니다. - - FORBIDDEN_CLUB_RECRUITMENT_CREATE (403): 동아리 모집 공고를 생성할 권한이 없습니다. - - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. - - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. - - ALREADY_EXIST_CLUB_RECRUITMENT (409): 이미 동아리 모집 공고가 존재합니다. - """) - @PostMapping("/{clubId}/recruitments") - ResponseEntity createRecruitment( - @RequestBody @Valid ClubRecruitmentCreateRequest request, - @PathVariable(name = "clubId") Integer clubId, - @UserId Integer userId - ); + @Operation(summary = "동아리 모집 정보를 생성/수정한다.", description = """ + 요청 값을 기준으로 동아리 모집 공고를 저장합니다. + - 모집 공고가 없으면 생성 + - 모집 공고가 있으면 수정 - @Operation(summary = "동아리 모집 정보를 수정한다.", description = """ - 동아리 회장 또는 부회장만 모집 공고를 수정할 수 있습니다. - ## 에러 - INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다. - INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다. @@ -364,11 +345,10 @@ ResponseEntity createRecruitment( - FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다. - NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다. - NOT_FOUND_USER (404): 유저를 찾을 수 없습니다. - - NOT_FOUND_CLUB_RECRUITMENT (404): 동아리 모집 공고를 찾을 수 없습니다. """) @PutMapping("/{clubId}/recruitments") - ResponseEntity updateRecruitment( - @Valid @RequestBody ClubRecruitmentUpdateRequest request, + ResponseEntity upsertRecruitment( + @Valid @RequestBody ClubRecruitmentUpsertRequest request, @PathVariable(name = "clubId") Integer clubId, @UserId Integer userId ); diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubController.java index aed79034..3a373533 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubController.java @@ -29,9 +29,8 @@ import gg.agit.konect.domain.club.dto.ClubPositionUpdateRequest; import gg.agit.konect.domain.club.dto.ClubPositionsResponse; import gg.agit.konect.domain.club.dto.ClubProfileUpdateRequest; -import gg.agit.konect.domain.club.dto.ClubRecruitmentCreateRequest; import gg.agit.konect.domain.club.dto.ClubRecruitmentResponse; -import gg.agit.konect.domain.club.dto.ClubRecruitmentUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubRecruitmentUpsertRequest; import gg.agit.konect.domain.club.dto.ClubsResponse; import gg.agit.konect.domain.club.dto.MyManagedClubResponse; import gg.agit.konect.domain.club.dto.MemberPositionChangeRequest; @@ -253,22 +252,12 @@ public ResponseEntity getRecruitments( } @Override - public ResponseEntity createRecruitment( - @RequestBody @Valid ClubRecruitmentCreateRequest request, + public ResponseEntity upsertRecruitment( + @Valid @RequestBody ClubRecruitmentUpsertRequest request, @PathVariable(name = "clubId") Integer clubId, @UserId Integer userId ) { - clubService.createRecruitment(clubId, userId, request); - return ResponseEntity.ok().build(); - } - - @Override - public ResponseEntity updateRecruitment( - @Valid @RequestBody ClubRecruitmentUpdateRequest request, - @PathVariable(name = "clubId") Integer clubId, - @UserId Integer userId - ) { - clubService.updateRecruitment(clubId, userId, request); + clubService.upsertRecruitment(clubId, userId, request); return ResponseEntity.noContent().build(); } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpdateRequest.java deleted file mode 100644 index 51fd900f..00000000 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpdateRequest.java +++ /dev/null @@ -1,50 +0,0 @@ -package gg.agit.konect.domain.club.dto; - -import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; - -import java.time.LocalDate; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonFormat; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -public record ClubRecruitmentUpdateRequest( - @Schema(description = "모집 시작일", example = "2025.11.30", requiredMode = REQUIRED) - @JsonFormat(pattern = "yyyy.MM.dd") - LocalDate startDate, - - @Schema(description = "모집 마감일", example = "2025.12.31", requiredMode = REQUIRED) - @JsonFormat(pattern = "yyyy.MM.dd") - LocalDate endDate, - - @NotNull(message = "상시 모집 여부는 필수 입력입니다.") - @Schema(description = "상시 모집 여부", example = "false", requiredMode = REQUIRED) - Boolean isAlwaysRecruiting, - - @NotEmpty(message = "모집 공고 내용은 필수 입력입니다.") - @Schema(description = "모집 공고 내용", example = "BCSD 2025학년도 2학기 신입 부원 모집...", requiredMode = REQUIRED) - String content, - - @NotNull(message = "모집 공고 이미지 리스트는 필수 입력입니다.") - @Valid - @Schema(description = "모집 공고 이미지 리스트", requiredMode = REQUIRED) - List images -) { - public record InnerClubRecruitmentImageRequest( - @NotEmpty(message = "모집 공고 이미지 URL은 필수 입력입니다.") - @Schema(description = "모집 공고 이미지 URL", example = "https://example.com/image.png", requiredMode = REQUIRED) - String url - ) { - - } - - public List getImageUrls() { - return images.stream() - .map(InnerClubRecruitmentImageRequest::url) - .toList(); - } -} diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentCreateRequest.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java similarity index 97% rename from src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentCreateRequest.java rename to src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java index 1bcd2f4c..c807a28f 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentCreateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubRecruitmentUpsertRequest.java @@ -13,7 +13,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -public record ClubRecruitmentCreateRequest( +public record ClubRecruitmentUpsertRequest( @Schema(description = "모집 시작일", example = "2025.11.30", requiredMode = REQUIRED) @JsonFormat(pattern = "yyyy.MM.dd") LocalDate startDate, diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubService.java index ec72beef..0a6b1668 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubService.java @@ -38,9 +38,8 @@ import gg.agit.konect.domain.club.dto.ClubMembersResponse; import gg.agit.konect.domain.club.dto.ClubMembershipsResponse; import gg.agit.konect.domain.club.dto.ClubProfileUpdateRequest; -import gg.agit.konect.domain.club.dto.ClubRecruitmentCreateRequest; import gg.agit.konect.domain.club.dto.ClubRecruitmentResponse; -import gg.agit.konect.domain.club.dto.ClubRecruitmentUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubRecruitmentUpsertRequest; import gg.agit.konect.domain.club.dto.ClubsResponse; import gg.agit.konect.domain.club.dto.MyManagedClubResponse; import gg.agit.konect.domain.club.enums.ClubPositionGroup; @@ -524,64 +523,46 @@ private void deleteQuestions( } @Transactional - public void createRecruitment(Integer clubId, Integer userId, ClubRecruitmentCreateRequest request) { + public void upsertRecruitment(Integer clubId, Integer userId, ClubRecruitmentUpsertRequest request) { Club club = clubRepository.getById(clubId); - User user = userRepository.getById(userId); - - if (!hasClubManageAccess(clubId, userId, PRESIDENT_ALLOWED_GROUPS)) { - throw CustomException.of(FORBIDDEN_CLUB_RECRUITMENT_CREATE); - } - - if (clubRecruitmentRepository.existsByClubId(clubId)) { - throw CustomException.of(ALREADY_EXIST_CLUB_RECRUITMENT); - } - - ClubRecruitment clubRecruitment = ClubRecruitment.of( - request.startDate(), - request.endDate(), - request.isAlwaysRecruiting(), - request.content(), - club - ); - List imageUrls = request.getImageUrls(); - for (int index = 0; index < imageUrls.size(); index++) { - ClubRecruitmentImage clubRecruitmentImage = ClubRecruitmentImage.of( - imageUrls.get(index), - index, - clubRecruitment - ); - clubRecruitment.addImage(clubRecruitmentImage); - } - - clubRecruitmentRepository.save(clubRecruitment); - } - - @Transactional - public void updateRecruitment(Integer clubId, Integer userId, ClubRecruitmentUpdateRequest request) { - clubRepository.getById(clubId); userRepository.getById(userId); if (!hasClubManageAccess(clubId, userId, MANAGER_ALLOWED_GROUPS)) { throw CustomException.of(FORBIDDEN_CLUB_MANAGER_ACCESS); } - ClubRecruitment clubRecruitment = clubRecruitmentRepository.getByClubId(clubId); - clubRecruitment.update( - request.startDate(), - request.endDate(), - request.isAlwaysRecruiting(), - request.content() - ); + ClubRecruitment clubRecruitment = clubRecruitmentRepository.findByClubId(clubId) + .orElseGet(() -> ClubRecruitment.of( + request.startDate(), + request.endDate(), + request.isAlwaysRecruiting(), + request.content(), + club + )); + + if (clubRecruitment.getId() != null) { + clubRecruitment.update( + request.startDate(), + request.endDate(), + request.isAlwaysRecruiting(), + request.content() + ); + + clubRecruitment.getImages().clear(); + } - clubRecruitment.getImages().clear(); List imageUrls = request.getImageUrls(); for (int index = 0; index < imageUrls.size(); index++) { - ClubRecruitmentImage newImage = ClubRecruitmentImage.of( + ClubRecruitmentImage image = ClubRecruitmentImage.of( imageUrls.get(index), index, clubRecruitment ); - clubRecruitment.addImage(newImage); + clubRecruitment.addImage(image); + } + + if (clubRecruitment.getId() == null) { + clubRecruitmentRepository.save(clubRecruitment); } }