diff --git a/build.gradle b/build.gradle index e765995fd..b9a857960 100644 --- a/build.gradle +++ b/build.gradle @@ -65,6 +65,7 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.6.0' + implementation("org.springframework.retry:spring-retry") // Monitoring implementation group: 'org.springframework.boot', name: 'spring-boot-starter-actuator', version: '3.3.3' diff --git a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiController.java b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiController.java index 7f4f401d9..9bc2fdfed 100644 --- a/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiController.java +++ b/src/main/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiController.java @@ -1,6 +1,8 @@ package com.somemore.domains.recruitboard.controller; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + import com.somemore.domains.recruitboard.dto.request.RecruitBoardCreateRequestDto; import com.somemore.domains.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto; import com.somemore.domains.recruitboard.dto.request.RecruitBoardStatusUpdateRequestDto; @@ -15,16 +17,20 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import java.time.LocalDateTime; -import java.util.UUID; - -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; - @Tag(name = "Recruit Board Command API", description = "봉사 활동 모집글 생성 수정 삭제 API") @RequiredArgsConstructor @RequestMapping("/api") @@ -44,7 +50,6 @@ public ApiResponse createRecruitBoard( @Valid @RequestPart("data") RecruitBoardCreateRequestDto requestDto, @RequestPart(value = "img_file", required = false) MultipartFile image ) { - String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); return ApiResponse.ok( 201, @@ -64,7 +69,6 @@ public ApiResponse updateRecruitBoard( ) { String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image)); updateRecruitBoardUseCase.updateRecruitBoard(requestDto, id, userId, imgUrl); - return ApiResponse.ok("봉사 활동 모집글 수정 성공"); } @@ -76,7 +80,6 @@ public ApiResponse updateRecruitBoardLocation( @PathVariable Long id, @Valid @RequestBody RecruitBoardLocationUpdateRequestDto requestDto ) { - updateRecruitBoardUseCase.updateRecruitBoardLocation(requestDto, id, userId); return ApiResponse.ok("봉사 활동 모집글 위치 수정 성공"); } @@ -89,9 +92,7 @@ public ApiResponse updateRecruitBoardStatus( @PathVariable Long id, @RequestBody RecruitBoardStatusUpdateRequestDto requestDto ) { - LocalDateTime now = LocalDateTime.now(); - updateRecruitBoardUseCase.updateRecruitBoardStatus(requestDto.status(), id, userId, now); - + updateRecruitBoardUseCase.updateRecruitBoardStatus(requestDto.status(), id, userId); return ApiResponse.ok("봉사 활동 모집글 상태 수정 성공"); } @@ -105,5 +106,4 @@ public ApiResponse deleteRecruitBoard( deleteRecruitBoardUseCase.deleteRecruitBoard(userId, id); return ApiResponse.ok("봉사 활동 모집글 삭제 성공"); } - } diff --git a/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoard.java b/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoard.java index 5e12ff5e6..370029688 100644 --- a/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoard.java +++ b/src/main/java/com/somemore/domains/recruitboard/domain/RecruitBoard.java @@ -1,19 +1,25 @@ package com.somemore.domains.recruitboard.domain; +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + import com.somemore.domains.recruitboard.dto.request.RecruitBoardUpdateRequestDto; import com.somemore.global.common.entity.BaseEntity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import java.time.LocalDateTime; -import java.util.UUID; - -import static jakarta.persistence.EnumType.STRING; -import static jakarta.persistence.GenerationType.IDENTITY; -import static lombok.AccessLevel.PROTECTED; - @Getter @NoArgsConstructor(access = PROTECTED) @Entity @@ -42,19 +48,20 @@ public class RecruitBoard extends BaseEntity { @Enumerated(value = STRING) @Column(name = "recruit_status", nullable = false, length = 20) - private RecruitStatus recruitStatus = RecruitStatus.RECRUITING; + private RecruitStatus recruitStatus; @Column(name = "img_url", nullable = false) private String imgUrl; @Builder public RecruitBoard(UUID centerId, Long locationId, String title, String content, - RecruitmentInfo recruitmentInfo, String imgUrl) { + RecruitmentInfo recruitmentInfo, RecruitStatus status, String imgUrl) { this.centerId = centerId; this.locationId = locationId; this.title = title; this.content = content; this.recruitmentInfo = recruitmentInfo; + this.recruitStatus = status; this.imgUrl = imgUrl; } @@ -73,17 +80,23 @@ public void updateWith(String region) { recruitmentInfo.updateWith(region); } - public void changeRecruitStatus(RecruitStatus newStatus, LocalDateTime currentDateTime) { - validateStatusChange(newStatus); - validateChangeDeadline(currentDateTime); - - this.recruitStatus = newStatus; + public void updateRecruitStatus(RecruitStatus status) { + this.recruitStatus = status; } - public boolean isRecruitOpen() { + public boolean isRecruiting() { return this.recruitStatus == RecruitStatus.RECRUITING; } + public boolean isCompleted() { + return this.recruitStatus == RecruitStatus.COMPLETED; + } + + public boolean isUpdatable(LocalDateTime current) { + LocalDateTime deadline = this.recruitmentInfo.getVolunteerStartDateTime().toLocalDate().atStartOfDay(); + return current.isBefore(deadline); + } + private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) { recruitmentInfo.updateWith( dto.region(), @@ -96,24 +109,4 @@ private void updateRecruitmentInfo(RecruitBoardUpdateRequestDto dto) { ); } - public boolean isCompleted() { - return this.recruitStatus == RecruitStatus.COMPLETED; - } - - private void validateStatusChange(RecruitStatus newStatus) { - if (newStatus.isChangeable()) { - return; - } - throw new IllegalArgumentException("상태는 '모집중' 또는 '마감'으로만 변경할 수 있습니다."); - } - - private void validateChangeDeadline(LocalDateTime currentDateTime) { - LocalDateTime volunteerStartDateTime = recruitmentInfo.getVolunteerStartDateTime(); - LocalDateTime deadline = volunteerStartDateTime.toLocalDate().atStartOfDay(); - - if (!currentDateTime.isBefore(deadline)) { - throw new IllegalStateException("봉사 시작 일시 자정 전까지만 상태를 변경할 수 있습니다."); - } - } - } diff --git a/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardCreateRequestDto.java b/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardCreateRequestDto.java index b33d54dd9..9beddc3e9 100644 --- a/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardCreateRequestDto.java +++ b/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardCreateRequestDto.java @@ -1,5 +1,7 @@ package com.somemore.domains.recruitboard.dto.request; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; + import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.somemore.domains.location.dto.request.LocationCreateRequestDto; @@ -10,10 +12,9 @@ import jakarta.validation.constraints.Future; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Builder; - import java.time.LocalDateTime; import java.util.UUID; +import lombok.Builder; @JsonNaming(SnakeCaseStrategy.class) @Builder @@ -69,6 +70,7 @@ public RecruitBoard toEntity(UUID centerId, Long locationId, String imgUrl) { .content(content) .imgUrl(imgUrl) .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) .build(); } } diff --git a/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardUpdateRequestDto.java b/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardUpdateRequestDto.java index 39270be2e..40ebd65f5 100644 --- a/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardUpdateRequestDto.java +++ b/src/main/java/com/somemore/domains/recruitboard/dto/request/RecruitBoardUpdateRequestDto.java @@ -4,12 +4,10 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.somemore.domains.recruitboard.domain.VolunteerCategory; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.Future; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import lombok.Builder; - import java.time.LocalDateTime; +import lombok.Builder; @JsonNaming(SnakeCaseStrategy.class) @Builder @@ -28,11 +26,9 @@ public record RecruitBoardUpdateRequestDto( Integer recruitmentCount, @Schema(description = "봉사 시작 일시", example = "2024-12-20T10:00:00", type = "string") @NotNull(message = "봉사 시작 일시는 필수 값입니다.") - @Future(message = "봉사 시작 일시는 내일부터 가능합니다.") LocalDateTime volunteerStartDateTime, @Schema(description = "봉사 종료 일시", example = "2024-12-20T12:00:00", type = "string") @NotNull(message = "봉사 종료 일시는 필수 값입니다.") - @Future(message = "봉사 종료 일시는 내일부터 가능합니다.") LocalDateTime volunteerEndDateTime, @Schema(description = "봉사 시간", example = "2") @NotNull(message = "봉사 시간는 필수 값입니다.") diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java index 485640563..abc0078cf 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepository.java @@ -1,12 +1,12 @@ package com.somemore.domains.recruitboard.repository; +import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithLocation; -import com.somemore.domains.recruitboard.domain.RecruitBoard; - +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -26,17 +26,20 @@ public interface RecruitBoardRepository { Page findAllNearby(RecruitBoardNearByCondition condition); -// Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); - - Page findAllByCenterId(UUID centerId, RecruitBoardSearchCondition condition); List findNotCompletedIdsByCenterId(UUID centerId); List findAllByIds(List ids); + List findAll(); + + long updateStatusToClosedForDateRange(LocalDateTime startTime, LocalDateTime endTime); + + long updateStatusToCompletedForDateRange(LocalDateTime startTime, LocalDateTime endTime); + +// Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition); // Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition); // void saveDocuments(List recruitBoards); - List findAll(); // void deleteDocument(Long id); } diff --git a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java index 33975eaf0..d18e5a846 100644 --- a/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java +++ b/src/main/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImpl.java @@ -1,5 +1,9 @@ package com.somemore.domains.recruitboard.repository; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; + import com.querydsl.core.types.ConstructorExpression; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Projections; @@ -8,17 +12,17 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.somemore.domains.center.domain.QCenter; import com.somemore.domains.location.domain.QLocation; +import com.somemore.domains.location.utils.GeoUtils; import com.somemore.domains.recruitboard.domain.QRecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardDetail; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithLocation; -import com.somemore.domains.location.utils.GeoUtils; -import com.somemore.domains.recruitboard.domain.RecruitBoard; -import com.somemore.domains.recruitboard.domain.RecruitStatus; -import com.somemore.domains.recruitboard.domain.VolunteerCategory; - +import java.time.LocalDateTime; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -35,8 +39,8 @@ public class RecruitBoardRepositoryImpl implements RecruitBoardRepository { private final RecruitBoardJpaRepository recruitBoardJpaRepository; - // private final RecruitBoardDocumentRepository documentRepository; private final JPAQueryFactory queryFactory; + // private final RecruitBoardDocumentRepository documentRepository; private static final QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; private static final QLocation location = QLocation.location; @@ -66,31 +70,6 @@ public Optional findById(Long id) { return Optional.ofNullable(result); } - @Override - public List findNotCompletedIdsByCenterId(UUID centerId) { - - BooleanExpression exp = centerIdEq(centerId) - .and(isNotCompleted()) - .and(isNotDeleted()); - - return queryFactory - .select(recruitBoard.id) - .from(recruitBoard) - .where(exp) - .fetch(); - } - - @Override - public List findAllByIds(List ids) { - BooleanExpression exp = recruitBoard.id.in(ids) - .and(isNotDeleted()); - - return queryFactory - .selectFrom(recruitBoard) - .where(exp) - .fetch(); - } - @Override public Optional findWithLocationById(Long id) { @@ -170,6 +149,91 @@ public Page findAllNearby(RecruitBoardNearByCondition condit return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); } + @Override + public Page findAllByCenterId(UUID centerId, + RecruitBoardSearchCondition condition) { + + BooleanExpression exp = centerIdEq(centerId) + .and(keywordEq(condition.keyword())) + .and(volunteerCategoryEq(condition.category())) + .and(regionEq(condition.region())) + .and(admittedEq(condition.admitted())) + .and(statusEq(condition.status())) + .and(isNotDeleted()); + + Pageable pageable = condition.pageable(); + + List content = queryFactory + .selectFrom(recruitBoard) + .where(exp) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(toOrderSpecifiers(pageable.getSort())) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(recruitBoard.count()) + .from(recruitBoard) + .where(exp); + + return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); + } + + @Override + public List findNotCompletedIdsByCenterId(UUID centerId) { + + BooleanExpression exp = centerIdEq(centerId) + .and(isNotCompleted()) + .and(isNotDeleted()); + + return queryFactory + .select(recruitBoard.id) + .from(recruitBoard) + .where(exp) + .fetch(); + } + + @Override + public List findAllByIds(List ids) { + BooleanExpression exp = recruitBoard.id.in(ids) + .and(isNotDeleted()); + + return queryFactory + .selectFrom(recruitBoard) + .where(exp) + .fetch(); + } + + @Override + public List findAll() { + return recruitBoardJpaRepository.findAll(); + } + + @Override + public long updateStatusToClosedForDateRange(LocalDateTime startTime, + LocalDateTime endTime) { + return queryFactory.update(recruitBoard) + .set(recruitBoard.recruitStatus, CLOSED) + .where( + statusEq(RECRUITING), + volunteerStartDateTimeBetween(startTime, endTime), + isNotDeleted() + ) + .execute(); + } + + @Override + public long updateStatusToCompletedForDateRange(LocalDateTime startTime, LocalDateTime endTime) { + return queryFactory.update(recruitBoard) + .set(recruitBoard.recruitStatus, COMPLETED) + .where( + statusEq(CLOSED), + volunteerEndDateTimeBetween(startTime, endTime), + isNotDeleted() + ) + .execute(); + } + // @Override // public Page findAllNearbyWithKeyword(RecruitBoardNearByCondition condition) { // QRecruitBoard recruitBoard = QRecruitBoard.recruitBoard; @@ -209,37 +273,8 @@ public Page findAllNearby(RecruitBoardNearByCondition condit // .and(predicate)); // // return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); -// } - - @Override - public Page findAllByCenterId(UUID centerId, - RecruitBoardSearchCondition condition) { - BooleanExpression exp = centerIdEq(centerId) - .and(keywordEq(condition.keyword())) - .and(volunteerCategoryEq(condition.category())) - .and(regionEq(condition.region())) - .and(admittedEq(condition.admitted())) - .and(statusEq(condition.status())) - .and(isNotDeleted()); - - Pageable pageable = condition.pageable(); - - List content = queryFactory - .selectFrom(recruitBoard) - .where(exp) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(toOrderSpecifiers(pageable.getSort())) - .fetch(); - - JPAQuery countQuery = queryFactory - .select(recruitBoard.count()) - .from(recruitBoard) - .where(exp); - - return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne); - } +// } // @Override // public Page findByRecruitBoardsContaining(RecruitBoardSearchCondition condition) { @@ -287,11 +322,6 @@ public Page findAllByCenterId(UUID centerId, // documentRepository.saveAll(recruitBoardDocuments); // } - @Override - public List findAll() { - return recruitBoardJpaRepository.findAll(); - } - // @Override // public void deleteDocument(Long id) { // documentRepository.deleteById(id); @@ -310,7 +340,7 @@ private BooleanExpression isNotDeleted() { } private BooleanExpression isNotCompleted() { - return recruitBoard.recruitStatus.in(RecruitStatus.RECRUITING, RecruitStatus.CLOSED); + return recruitBoard.recruitStatus.in(RECRUITING, CLOSED); } private BooleanExpression keywordEq(String keyword) { @@ -354,6 +384,16 @@ private BooleanExpression locationBetween(RecruitBoardNearByCondition condition) .and(location.longitude.between(minLongitude, maxLongitude)); } + private static BooleanExpression volunteerStartDateTimeBetween(LocalDateTime startTime, + LocalDateTime endTime) { + return recruitBoard.recruitmentInfo.volunteerStartDateTime.between(startTime, endTime); + } + + private static BooleanExpression volunteerEndDateTimeBetween(LocalDateTime startTime, + LocalDateTime endTime) { + return recruitBoard.recruitmentInfo.volunteerEndDateTime.between(startTime, endTime); + } + private OrderSpecifier[] toOrderSpecifiers(Sort sort) { return sort.stream() .map(order -> { diff --git a/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateScheduler.java b/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateScheduler.java new file mode 100644 index 000000000..deb8309e1 --- /dev/null +++ b/src/main/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateScheduler.java @@ -0,0 +1,64 @@ +package com.somemore.domains.recruitboard.scheduler; + +import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; +import jakarta.transaction.Transactional; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.retry.annotation.Backoff; +import org.springframework.retry.annotation.Retryable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@RequiredArgsConstructor +@Transactional +@Component +public class RecruitBoardStatusUpdateScheduler { + + private final RecruitBoardRepository recruitBoardRepository; + private static final String RECRUIT_BOARD_UPDATE_CRON = "0 0 0 * * ?"; + + @Retryable( + retryFor = Exception.class, + maxAttempts = 3, + backoff = @Backoff(delay = 2000) + ) + @Scheduled(cron = RECRUIT_BOARD_UPDATE_CRON) + public void updateRecruitBoardStatusToClosed() { + log.info("봉사 시작일에 해당하는 모집글 상태를 CLOSED로 변경하는 작업 시작"); + LocalDateTime today = LocalDate.now().atStartOfDay(); + LocalDateTime tomorrow = today.plusDays(1); + + try { + long updatedCount = recruitBoardRepository.updateStatusToClosedForDateRange( + today, tomorrow); + log.info("총 {}개의 모집글 상태를 CLOSED로 변경 완료", updatedCount); + } catch (Exception e) { + log.error("봉사 시작일에 해당하는 모집글 상태를 CLOSED로 변경하는 중 오류 발생", e); + throw e; + } + } + + @Retryable( + retryFor = Exception.class, + maxAttempts = 3, + backoff = @Backoff(delay = 2000) + ) + @Scheduled(cron = RECRUIT_BOARD_UPDATE_CRON) + public void updateRecruitBoardStatusToCompleted() { + log.info("봉사 종료일이 지난 모집글 상태를 COMPLETED로 변경하는 작업 시작"); + LocalDateTime today = LocalDate.now().atStartOfDay(); + LocalDateTime yesterday = today.minusDays(1); + + try { + long updatedCount = recruitBoardRepository.updateStatusToCompletedForDateRange(yesterday, today); + log.info("총 {}개의 모집글 상태를 COMPLETED로 변경 완료", updatedCount); + } catch (Exception e) { + log.error("봉사 종료일이 지난 모집글 상태를 COMPLETED로 변경하는 중 오류 발생", e); + throw e; + } + } + +} diff --git a/src/main/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardService.java b/src/main/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardService.java index fe9967994..c8d644864 100644 --- a/src/main/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardService.java +++ b/src/main/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardService.java @@ -8,13 +8,13 @@ import com.somemore.domains.recruitboard.service.validator.RecruitBoardValidator; import com.somemore.domains.recruitboard.usecase.RecruitBoardQueryUseCase; import com.somemore.domains.recruitboard.usecase.UpdateRecruitBoardUseCase; +import java.time.Clock; +import java.time.LocalDateTime; +import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.UUID; - @RequiredArgsConstructor @Transactional @Service @@ -23,32 +23,48 @@ public class UpdateRecruitBoardService implements UpdateRecruitBoardUseCase { private final RecruitBoardQueryUseCase recruitBoardQueryUseCase; private final UpdateLocationUseCase updateLocationUseCase; private final RecruitBoardValidator recruitBoardValidator; + private final Clock clock; @Override - public void updateRecruitBoard(RecruitBoardUpdateRequestDto dto, Long id, UUID centerId, String imgUrl) { - RecruitBoard recruitBoard = recruitBoardQueryUseCase.getById(id); - recruitBoardValidator.validateWriter(recruitBoard, centerId); - recruitBoardValidator.validateRecruitBoardTime(dto.volunteerStartDateTime(), dto.volunteerEndDateTime()); + public void updateRecruitBoard(RecruitBoardUpdateRequestDto dto, Long id, UUID centerId, + String imgUrl) { + RecruitBoard recruitBoard = getRecruitBoard(id); + validateUpdatableAndWriter(recruitBoard, centerId); + + recruitBoardValidator.validateUpdateRecruitBoardTime(recruitBoard.getCreatedAt(), + dto.volunteerStartDateTime(), dto.volunteerEndDateTime()); recruitBoard.updateWith(dto, imgUrl); } @Override - public void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requestDto, Long id, UUID centerId) { - RecruitBoard recruitBoard = recruitBoardQueryUseCase.getById(id); - recruitBoardValidator.validateWriter(recruitBoard, centerId); - - updateLocationUseCase.updateLocation(requestDto.toLocationUpdateRequestDto(), recruitBoard.getLocationId()); + public void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requestDto, Long id, + UUID centerId) { + RecruitBoard recruitBoard = getRecruitBoard(id); + validateUpdatableAndWriter(recruitBoard, centerId); + updateLocationUseCase.updateLocation(requestDto.toLocationUpdateRequestDto(), + recruitBoard.getLocationId()); recruitBoard.updateWith(requestDto.region()); } @Override - public void updateRecruitBoardStatus(RecruitStatus status, Long id, UUID centerId, LocalDateTime currentDateTime) { - RecruitBoard recruitBoard = recruitBoardQueryUseCase.getById(id); - recruitBoardValidator.validateWriter(recruitBoard, centerId); + public void updateRecruitBoardStatus(RecruitStatus status, Long id, UUID centerId) { + RecruitBoard recruitBoard = getRecruitBoard(id); + validateUpdatableAndWriter(recruitBoard, centerId); + recruitBoardValidator.validateRecruitStatus(status); - recruitBoard.changeRecruitStatus(status, currentDateTime); + recruitBoard.updateRecruitStatus(status); } + private void validateUpdatableAndWriter(RecruitBoard recruitBoard, UUID centerId) { + LocalDateTime current = LocalDateTime.now(clock); + recruitBoardValidator.validateUpdatable(recruitBoard, current); + recruitBoardValidator.validateWriter(recruitBoard, centerId); + } + + private RecruitBoard getRecruitBoard(Long id) { + return recruitBoardQueryUseCase.getById(id); + } } + diff --git a/src/main/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidator.java b/src/main/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidator.java index f212a85bb..edb8d7451 100644 --- a/src/main/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidator.java +++ b/src/main/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidator.java @@ -1,26 +1,42 @@ package com.somemore.domains.recruitboard.service.validator; +import static com.somemore.global.exception.ExceptionMessage.INVALID_DEADLINE_RECRUIT_BOARD_UPDATE; +import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_STATUS_UPDATE; +import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME; +import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME_UPDATE; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; + import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; import com.somemore.global.exception.BadRequestException; -import org.springframework.stereotype.Component; - import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.UUID; - -import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME; -import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; +import org.springframework.stereotype.Component; @Component public class RecruitBoardValidator { - public void validateRecruitBoardTime(LocalDateTime startDateTime, LocalDateTime endDateTime) { - if (endDateTime.isAfter(startDateTime)) { + public void validateRecruitBoardTime(LocalDateTime start, LocalDateTime end) { + if (end.isAfter(start)) { return; } throw new BadRequestException(INVALID_RECRUIT_BOARD_TIME); } + public void validateUpdateRecruitBoardTime(LocalDateTime createdAt, LocalDateTime start, + LocalDateTime end) { + validateRecruitBoardTime(start, end); + + LocalDateTime oneDayAfterCreatedAt = createdAt.plusDays(1).truncatedTo(ChronoUnit.DAYS); + if (start.isAfter(oneDayAfterCreatedAt)) { + return; + } + + throw new BadRequestException(INVALID_RECRUIT_BOARD_TIME_UPDATE); + } + public void validateWriter(RecruitBoard recruitBoard, UUID centerId) { if (recruitBoard.isWriter(centerId)) { return; @@ -28,4 +44,20 @@ public void validateWriter(RecruitBoard recruitBoard, UUID centerId) { throw new BadRequestException(UNAUTHORIZED_RECRUIT_BOARD); } + + public void validateUpdatable(RecruitBoard recruitBoard, LocalDateTime current) { + if (recruitBoard.isUpdatable(current)) { + return; + } + + throw new BadRequestException(INVALID_DEADLINE_RECRUIT_BOARD_UPDATE); + } + + public void validateRecruitStatus(RecruitStatus newStatus) { + if (newStatus.isChangeable()) { + return; + } + throw new BadRequestException(INVALID_RECRUIT_BOARD_STATUS_UPDATE); + } + } diff --git a/src/main/java/com/somemore/domains/recruitboard/usecase/UpdateRecruitBoardUseCase.java b/src/main/java/com/somemore/domains/recruitboard/usecase/UpdateRecruitBoardUseCase.java index 3403d7711..968dd74cf 100644 --- a/src/main/java/com/somemore/domains/recruitboard/usecase/UpdateRecruitBoardUseCase.java +++ b/src/main/java/com/somemore/domains/recruitboard/usecase/UpdateRecruitBoardUseCase.java @@ -3,14 +3,15 @@ import com.somemore.domains.recruitboard.domain.RecruitStatus; import com.somemore.domains.recruitboard.dto.request.RecruitBoardLocationUpdateRequestDto; import com.somemore.domains.recruitboard.dto.request.RecruitBoardUpdateRequestDto; - -import java.time.LocalDateTime; import java.util.UUID; public interface UpdateRecruitBoardUseCase { - void updateRecruitBoard(RecruitBoardUpdateRequestDto requestDto, Long id, UUID centerId, String imgUrl); - void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requestDto, Long id, UUID centerId); + void updateRecruitBoard(RecruitBoardUpdateRequestDto dto, Long id, UUID centerId, + String imgUrl); + + void updateRecruitBoardLocation(RecruitBoardLocationUpdateRequestDto requestDto, Long id, + UUID centerId); - void updateRecruitBoardStatus(RecruitStatus status, Long id, UUID centerId, LocalDateTime currentDateTime); + void updateRecruitBoardStatus(RecruitStatus status, Long id, UUID centerId); } diff --git a/src/main/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyService.java b/src/main/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyService.java index 152aa79c6..4a4aa8b30 100644 --- a/src/main/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyService.java +++ b/src/main/java/com/somemore/domains/volunteerapply/service/ApplyVolunteerApplyService.java @@ -45,7 +45,7 @@ public Long apply(VolunteerApplyCreateRequestDto requestDto, UUID volunteerId) { } private void validateCanApply(RecruitBoard board) { - if (board.isRecruitOpen()) { + if (board.isRecruiting()) { return; } throw new BadRequestException(RECRUITMENT_NOT_OPEN); diff --git a/src/main/java/com/somemore/global/config/TimeConfig.java b/src/main/java/com/somemore/global/config/TimeConfig.java new file mode 100644 index 000000000..6d77ddf42 --- /dev/null +++ b/src/main/java/com/somemore/global/config/TimeConfig.java @@ -0,0 +1,14 @@ +package com.somemore.global.config; + +import java.time.Clock; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class TimeConfig { + + @Bean + public Clock clock() { + return Clock.systemDefaultZone(); + } +} diff --git a/src/main/java/com/somemore/global/exception/ExceptionMessage.java b/src/main/java/com/somemore/global/exception/ExceptionMessage.java index 8136f9844..746924eb9 100644 --- a/src/main/java/com/somemore/global/exception/ExceptionMessage.java +++ b/src/main/java/com/somemore/global/exception/ExceptionMessage.java @@ -32,6 +32,9 @@ public enum ExceptionMessage { NOT_EXISTS_RECRUIT_BOARD("존재하지 않는 봉사 모집글입니다."), UNAUTHORIZED_RECRUIT_BOARD("해당 봉사 모집글에 권한이 없습니다."), INVALID_RECRUIT_BOARD_TIME("종료 시간은 시작 시간보다 이후여야 합니다."), + INVALID_RECRUIT_BOARD_TIME_UPDATE("변경할수 없는 봉사 시작/종료 일시입니다."), + INVALID_RECRUIT_BOARD_STATUS_UPDATE("상태는 '모집중' 또는 '마감'으로만 변경할 수 있습니다."), + INVALID_DEADLINE_RECRUIT_BOARD_UPDATE("봉사 시작 일시 자정 전까지 변경할 수 있습니다."), // IMAGE UPLOAD_FAILED("파일 업로드에 실패했습니다."), diff --git a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiControllerTest.java b/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiControllerTest.java index 2384efc5d..1081d5a8c 100644 --- a/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiControllerTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/controller/RecruitBoardCommandApiControllerTest.java @@ -213,7 +213,7 @@ void updateRecruitBoardStatus() throws Exception { status); String requestBody = objectMapper.writeValueAsString(dto); willDoNothing().given(updateRecruitBoardUseCase) - .updateRecruitBoardStatus(any(), any(), any(UUID.class), any(LocalDateTime.class)); + .updateRecruitBoardStatus(any(), any(), any(UUID.class)); // when mockMvc.perform(patch("/api/recruit-board/{id}", 1L) diff --git a/src/test/java/com/somemore/domains/recruitboard/domain/RecruitBoardTest.java b/src/test/java/com/somemore/domains/recruitboard/domain/RecruitBoardTest.java index 4c9b0a4f1..2ef35a2e2 100644 --- a/src/test/java/com/somemore/domains/recruitboard/domain/RecruitBoardTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/domain/RecruitBoardTest.java @@ -1,55 +1,45 @@ package com.somemore.domains.recruitboard.domain; -import com.somemore.domains.recruitboard.dto.request.RecruitBoardUpdateRequestDto; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import java.time.LocalDateTime; -import java.util.UUID; - -import static com.somemore.domains.recruitboard.domain.RecruitStatus.*; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; import static com.somemore.support.fixture.LocalDateTimeFixture.createCurrentDateTime; +import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; import static com.somemore.support.fixture.LocalDateTimeFixture.createUpdateStartDateTime; -import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -class RecruitBoardTest { +import com.somemore.domains.recruitboard.dto.request.RecruitBoardUpdateRequestDto; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; - @DisplayName("봉사 모집글 생성시 모집상태는 모집중이다") - @Test - void createRecruitBoardWithDefaultStatus() { - // given - UUID centerId = UUID.randomUUID(); - RecruitBoard board = createRecruitBoard(centerId); +class RecruitBoardTest { - // when - RecruitStatus recruitStatus = board.getRecruitStatus(); + private UUID centerId; + private RecruitBoard board; - // then - assertThat(board.getCenterId()).isEqualTo(centerId); - assertThat(recruitStatus).isEqualTo(RECRUITING); + @BeforeEach + void setUp() { + centerId = UUID.randomUUID(); + board = createRecruitBoard(centerId); } @DisplayName("봉사 모집글을 업데이트 할 수 있다") @Test void updateRecruitBoard() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard board = createRecruitBoard(centerId); String imgUrl = "https://image.domain.com/updates"; - LocalDateTime startDateTime = createUpdateStartDateTime(); - LocalDateTime endDateTime = startDateTime.plusHours(2); + LocalDateTime updateStartDateTime = createUpdateStartDateTime(); + LocalDateTime updateEndDateTime = updateStartDateTime.plusHours(2); RecruitBoardUpdateRequestDto dto = RecruitBoardUpdateRequestDto.builder() .title("봉사 모집글 작성 수정") .content("봉사 하실분을 모집합니다. 수정
") .recruitmentCount(10) - .volunteerStartDateTime(startDateTime) - .volunteerEndDateTime(endDateTime) + .volunteerStartDateTime(updateStartDateTime) + .volunteerEndDateTime(updateEndDateTime) .volunteerHours(2) .volunteerCategory(OTHER) .admitted(true).build(); @@ -67,8 +57,6 @@ void updateRecruitBoard() { @Test void updateWithRegion() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard board = createRecruitBoard(centerId); String updateRegion = "새로운지역"; // when @@ -83,11 +71,8 @@ void updateWithRegion() { @Test void isWriterWithCorrectCenterId() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); - // when - boolean isWriter = recruitBoard.isWriter(centerId); + boolean isWriter = board.isWriter(centerId); // then assertThat(isWriter).isTrue(); @@ -96,93 +81,78 @@ void isWriterWithCorrectCenterId() { @DisplayName("잘못된 기관 식별 값이 주어지면 잘못된 작성자인 확인할 수있다.") @Test void isNotWriterWithWrongCenterId() { - UUID centerId = UUID.randomUUID(); + // given UUID wrongId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); // when - boolean isWriter = recruitBoard.isWriter(wrongId); + boolean isWriter = board.isWriter(wrongId); // then assertThat(isWriter).isFalse(); } - @DisplayName("모집글 상태를 모집중에서 모집 마감으로 변경할 수 있다") + @DisplayName("모집글 상태를 업데이트 할 수 있다.") @Test - void changeStatusFromRecruitingToClose() { + void updateRecruitStatus() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); - RecruitStatus newStatus = CLOSED; - LocalDateTime currentDateTime = createCurrentDateTime(); + RecruitStatus updateRecruitStatus = CLOSED; // when - recruitBoard.changeRecruitStatus(newStatus, currentDateTime); + board.updateRecruitStatus(updateRecruitStatus); // then - assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus); + assertThat(board.getRecruitStatus()).isEqualTo(updateRecruitStatus); + } - @DisplayName("모집글 상태를 모집마감에서 모집중으로 변경할 수 있다") + @DisplayName("모집중일 경우 True 반환한다.") @Test - void changeStatusFromCloseToRecruiting() { + void isRecruitOpen() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); - LocalDateTime currentDateTime = createCurrentDateTime(); - recruitBoard.changeRecruitStatus(CLOSED, currentDateTime); - RecruitStatus newStatus = RECRUITING; - // when - recruitBoard.changeRecruitStatus(newStatus, currentDateTime); + boolean result = board.isRecruiting(); // then - assertThat(recruitBoard.getRecruitStatus()).isEqualTo(newStatus); + assertThat(result).isTrue(); } - @DisplayName("모집글 상태는 마감으로 변경할 수 없다") + @DisplayName("모집글 수정 가능 여부를 확인할 수 있다.") @Test - void changeStatusWhenInvalidStatus() { + void isUpdatable() { // given - UUID centerId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); - LocalDateTime currentDateTime = createCurrentDateTime(); - - // when & then - assertThatThrownBy(() -> recruitBoard.changeRecruitStatus(COMPLETED, currentDateTime)) - .isInstanceOf(IllegalArgumentException.class); - } - - @DisplayName("봉사 시작일 자정 이후 모집 상태를 변경할 경우 에러가 발생한다") - @ParameterizedTest - @ValueSource(longs = {0, 1}) - void changeStatusWhenDeadLineAfter(Long secondsOffset) { - // given - UUID centerId = UUID.randomUUID(); - RecruitBoard recruitBoard = createRecruitBoard(centerId); - LocalDateTime deadLineDateTime = recruitBoard.getRecruitmentInfo() - .getVolunteerStartDateTime().toLocalDate().atStartOfDay(); - LocalDateTime currentDateTime = deadLineDateTime.plusSeconds(secondsOffset); + // volunteerStartDateTime 내일 13:00:00 + // current 오늘 16:00:00 + LocalDateTime current = createCurrentDateTime(); // when - // then - assertThatThrownBy( - () -> recruitBoard.changeRecruitStatus(CLOSED, currentDateTime) - ).isInstanceOf(IllegalStateException.class); + boolean updatable = board.isUpdatable(current); + // then + assertThat(updatable).isTrue(); } - @DisplayName("모집중일 경우 True 반환한다.") - @Test - void isRecruitOpen() { - // given - RecruitBoard board = createRecruitBoard(); - - // when - boolean result = board.isRecruitOpen(); + public static RecruitBoard createRecruitBoard(UUID centerId) { + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(1); - // then - assertThat(result).isTrue(); + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(startDateTime) + .volunteerEndDateTime(endDateTime) + .volunteerHours(1) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(centerId) + .locationId(1L) + .title("제목") + .content("내용") + .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) + .imgUrl("이미지 링크") + .build(); } - } diff --git a/src/test/java/com/somemore/domains/recruitboard/domain/RecruitmentInfoTest.java b/src/test/java/com/somemore/domains/recruitboard/domain/RecruitmentInfoTest.java index 62c6b1e0b..c6dd15e0b 100644 --- a/src/test/java/com/somemore/domains/recruitboard/domain/RecruitmentInfoTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/domain/RecruitmentInfoTest.java @@ -1,24 +1,29 @@ package com.somemore.domains.recruitboard.domain; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; - import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; import static com.somemore.domains.recruitboard.domain.VolunteerCategory.SAFETY_PREVENTION; import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; import static com.somemore.support.fixture.LocalDateTimeFixture.createUpdateStartDateTime; import static org.assertj.core.api.Assertions.assertThat; +import java.time.LocalDateTime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + class RecruitmentInfoTest { + private RecruitmentInfo recruitmentInfo; + + @BeforeEach + void setUp() { + recruitmentInfo = createRecruitmentInfo(); + } + @DisplayName("봉사 활동 정보를 업데이트 할 수 있다") @Test void updateRecruitmentInfo() { // given - RecruitmentInfo recruitmentInfo = createRecruitmentInfo(); - String region = "서울특별시"; Integer count = 2; VolunteerCategory volunteerCategory = SAFETY_PREVENTION; @@ -28,7 +33,8 @@ void updateRecruitmentInfo() { Boolean admitted = false; // when - recruitmentInfo.updateWith(region, count, volunteerCategory, startDateTime, endDateTime, volunteerHours, admitted); + recruitmentInfo.updateWith(region, count, volunteerCategory, startDateTime, endDateTime, + volunteerHours, admitted); // then assertThat(recruitmentInfo.getRecruitmentCount()).isEqualTo(count); @@ -43,7 +49,6 @@ void updateRecruitmentInfo() { @Test void updateRecruitmentInfoWithRegion() { // given - RecruitmentInfo recruitmentInfo = createRecruitmentInfo(); String updateRegion = "새로운지역"; // when @@ -53,22 +58,19 @@ void updateRecruitmentInfoWithRegion() { assertThat(recruitmentInfo.getRegion()).isEqualTo(updateRegion); } - private static RecruitmentInfo createRecruitmentInfo(LocalDateTime startDateTime, - LocalDateTime endDateTime) { + private static RecruitmentInfo createRecruitmentInfo() { + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(1); + return RecruitmentInfo.builder() .region("경기") .recruitmentCount(1) .volunteerStartDateTime(startDateTime) .volunteerEndDateTime(endDateTime) + .volunteerHours(1) .volunteerCategory(OTHER) .admitted(true) .build(); } - private static RecruitmentInfo createRecruitmentInfo() { - LocalDateTime startDateTime = createStartDateTime(); - LocalDateTime endDateTime = startDateTime.plusHours(1); - return createRecruitmentInfo(startDateTime, endDateTime); - } - } diff --git a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java b/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java index e40667454..5df52ef81 100644 --- a/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/repository/RecruitBoardRepositoryImplTest.java @@ -1,11 +1,21 @@ package com.somemore.domains.recruitboard.repository; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static com.somemore.support.fixture.CenterFixture.createCenter; +import static com.somemore.support.fixture.LocationFixture.createLocation; +import static org.assertj.core.api.Assertions.assertThat; + import com.somemore.domains.center.domain.Center; import com.somemore.domains.center.repository.center.CenterRepository; import com.somemore.domains.location.domain.Location; import com.somemore.domains.location.repository.LocationRepository; import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.domains.recruitboard.domain.VolunteerCategory; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardNearByCondition; import com.somemore.domains.recruitboard.dto.condition.RecruitBoardSearchCondition; @@ -13,6 +23,11 @@ import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithCenter; import com.somemore.domains.recruitboard.repository.mapper.RecruitBoardWithLocation; import com.somemore.support.IntegrationTestSupport; +import jakarta.persistence.EntityManager; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,21 +38,6 @@ import org.springframework.data.domain.Sort; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; -import static com.somemore.domains.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; -import static com.somemore.support.fixture.CenterFixture.createCenter; -import static com.somemore.support.fixture.LocalDateTimeFixture.createCurrentDateTime; -import static com.somemore.support.fixture.LocationFixture.createLocation; -import static com.somemore.support.fixture.RecruitBoardFixture.createCompletedRecruitBoard; -import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; -import static org.assertj.core.api.Assertions.assertThat; - @Transactional class RecruitBoardRepositoryImplTest extends IntegrationTestSupport { @@ -50,66 +50,61 @@ class RecruitBoardRepositoryImplTest extends IntegrationTestSupport { @Autowired private LocationRepository locationRepository; - private final List boards = new ArrayList<>(); + @Autowired + private EntityManager em; - private UUID centerId; + private Location location; + private Center center; + private RecruitBoard board; @BeforeEach void setUp() { - Location location = createLocation(); + location = createLocation(); locationRepository.save(location); - Center center = createCenter(); + center = createCenter(); centerRepository.save(center); - centerId = center.getId(); - - for (int i = 1; i <= 100; i++) { - String title = "제목" + i; - RecruitBoard board = createRecruitBoard(title, center.getId(), location.getId()); - boards.add(board); - } - recruitBoardRepository.saveAll(boards); + + board = createRecruitBoard(center.getId(), location.getId(), RECRUITING); + recruitBoardRepository.save(board); } - @DisplayName("논리 삭제된 데이터를 id로 조회시 빈 Optional 반환된다") + @DisplayName("아이디로 모집글을 조회할 수 있다.") @Test void findById() { // given - RecruitBoard deletedBoard = createRecruitBoard(); - deletedBoard.markAsDeleted(); - recruitBoardRepository.save(deletedBoard); - - Long deletedId = deletedBoard.getId(); + Long id = board.getId(); // when - Optional findBoard = recruitBoardRepository.findById(deletedId); + Optional findBoard = recruitBoardRepository.findById(id); // then - assertThat(findBoard).isEmpty(); + assertThat(findBoard).isNotEmpty(); + assertThat(findBoard.get().getId()).isEqualTo(id); } - @DisplayName("존재하지 않는 아이디로 봉사 모집글과 작성기관을 조회하면 Optional.empty()가 반환된다.") + @DisplayName("아이디로 봉사 모집글과 작성기관을 조회할 수 있다.") @Test - void findWithCenterByIdWithNotExistId() { + void findWithLocationById() { // given - Location location = createLocation("특별한주소"); - locationRepository.save(location); - - RecruitBoard deletedRecruitBoard = createRecruitBoard(location.getId()); - deletedRecruitBoard.markAsDeleted(); - recruitBoardRepository.save(deletedRecruitBoard); + Long id = board.getId(); // when Optional findOne = recruitBoardRepository.findWithLocationById( - deletedRecruitBoard.getId()); + id); // then - assertThat(findOne).isEmpty(); + assertThat(findOne).isNotEmpty(); + RecruitBoardWithLocation boardWithLocation = findOne.get(); + assertThat(boardWithLocation.recruitBoard().getId()).isEqualTo(id); + assertThat(boardWithLocation.address()).isEqualTo(location.getAddress()); + assertThat(boardWithLocation.latitude()).isEqualByComparingTo(location.getLatitude()); + assertThat(boardWithLocation.longitude()).isEqualByComparingTo(location.getLongitude()); } - @DisplayName("조건 없이 모집 게시글을 조회한다. (정렬 포함)") + @DisplayName("조건 없이 모집 게시글을 조회한다.") @Test - void findAllWithCenterWithoutCriteria() { + void findAllWithCenterWithoutCondition() { // given Pageable pageable = getPageable(); @@ -122,23 +117,17 @@ void findAllWithCenterWithoutCriteria() { // then assertThat(result).isNotNull(); - assertThat(result.getTotalElements()).isEqualTo(boards.size()); - assertThat(result.getSize()).isEqualTo(5); - assertThat(result.getNumber()).isZero(); - assertThat(result.getContent()).hasSize(5); - - assertThat(result.getContent().get(0).recruitBoard().getCreatedAt()) - .isAfterOrEqualTo(result.getContent().get(1).recruitBoard().getCreatedAt()); + assertThat(result.getTotalElements()).isEqualTo(1); } @DisplayName("키워드로 조회할 수 있다") @Test void findAllWithCenterByKeyword() { // given - Center center = createCenter(); - centerRepository.save(center); String keyword = "키워드"; - RecruitBoard recruitBoard = createRecruitBoard("키워드 조회 제목", center.getId()); + String title = keyword + " 제목"; + RecruitBoard recruitBoard = createRecruitBoard(center.getId(), title, OTHER, "지역", false, + RECRUITING); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); @@ -153,26 +142,22 @@ void findAllWithCenterByKeyword() { // then assertThat(result).isNotNull(); assertThat(result.getTotalElements()).isEqualTo(1); - assertThat(result.getSize()).isEqualTo(5); - assertThat(result.getNumber()).isZero(); assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getTitle()) - .isEqualTo("키워드 조회 제목"); + .isEqualTo(title); } @DisplayName("봉사활동 유형으로 조회할 수 있다") @Test void findAllWithCenterByCategory() { // given - Center center = createCenter(); - centerRepository.save(center); - - RecruitBoard recruitBoard = createRecruitBoard(ADMINISTRATIVE_SUPPORT, center.getId()); + VolunteerCategory category = ADMINISTRATIVE_SUPPORT; + RecruitBoard recruitBoard = createRecruitBoard(center.getId(), "제목", category, "지역", false, + RECRUITING); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); - VolunteerCategory category = ADMINISTRATIVE_SUPPORT; RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() .category(category) .pageable(pageable) @@ -185,8 +170,6 @@ void findAllWithCenterByCategory() { assertThat(result).isNotNull(); assertThat(result.getTotalElements()).isEqualTo(1); assertThat(result.getSize()).isEqualTo(5); - assertThat(result.getNumber()).isZero(); - assertThat(result.getContent()).hasSize(1); assertThat(result.getContent().getFirst().recruitBoard().getRecruitmentInfo() .getVolunteerCategory()).isEqualTo(category); @@ -196,12 +179,9 @@ void findAllWithCenterByCategory() { @Test void findAllWithCenterByRegion() { // given - Center center = createCenter(); - centerRepository.save(center); - String region = "특수지역"; - RecruitBoard recruitBoard = createRecruitBoard(center.getId()); - recruitBoard.updateWith(region); + RecruitBoard recruitBoard = createRecruitBoard(center.getId(), "제목", OTHER, region, false, + RECRUITING); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); @@ -214,65 +194,25 @@ void findAllWithCenterByRegion() { // when Page result = recruitBoardRepository.findAllWithCenter(condition); - } - - @DisplayName("센터 ID로 완료되지 않은 모집 게시글들의 ID를 조회할 수 있다") - @Test - void findNotCompletedIdsByCenterIds() { - // given - UUID centerId = UUID.randomUUID(); - - RecruitBoard deletedRecruitingBoard = createRecruitBoard(centerId); - deletedRecruitingBoard.markAsDeleted(); - recruitBoardRepository.save(deletedRecruitingBoard); - - RecruitBoard recruitingBoard = createRecruitBoard(centerId); - recruitBoardRepository.save(recruitingBoard); - - RecruitBoard deletedClosedBoard = createRecruitBoard(centerId); - deletedClosedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); - deletedClosedBoard.markAsDeleted(); - recruitBoardRepository.save(deletedClosedBoard); - - RecruitBoard closedBoard = createRecruitBoard(centerId); - closedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); - recruitBoardRepository.save(closedBoard); - - RecruitBoard deletedCompletedRecruitBoard = createCompletedRecruitBoard(); - deletedCompletedRecruitBoard.markAsDeleted(); - recruitBoardRepository.save(deletedCompletedRecruitBoard); - - RecruitBoard completedRecruitBoard = createCompletedRecruitBoard(); - recruitBoardRepository.save(completedRecruitBoard); - - // when - List notCompletedBoardIds = recruitBoardRepository.findNotCompletedIdsByCenterId( - centerId); - // then - assertThat(notCompletedBoardIds) - .hasSize(2) - .doesNotContain(deletedRecruitingBoard.getId()) - .doesNotContain(deletedClosedBoard.getId()) - .doesNotContain(deletedCompletedRecruitBoard.getId()) - .doesNotContain(completedRecruitBoard.getId()); + assertThat(result).hasSize(1); + assertThat(result.getContent().getFirst().recruitBoard().getId()).isEqualTo( + recruitBoard.getId()); } @DisplayName("시간 인증 여부로 조회할 수 있다") @Test void findAllWithCenterByAdmitted() { // given - Center center = createCenter(); - centerRepository.save(center); - - Boolean admitted = false; - RecruitBoard recruitBoard = createRecruitBoard(admitted, center.getId()); + boolean admitted = false; + RecruitBoard recruitBoard = createRecruitBoard(center.getId(), "제목", OTHER, "지역", admitted, + RECRUITING); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() - .admitted(false) + .admitted(admitted) .pageable(pageable) .build(); @@ -294,13 +234,9 @@ void findAllWithCenterByAdmitted() { @Test void findAllWithCenterByStatus() { // given - Center center = createCenter(); - centerRepository.save(center); - RecruitStatus status = CLOSED; - RecruitBoard recruitBoard = createRecruitBoard(center.getId()); - LocalDateTime currentDateTime = createCurrentDateTime(); - recruitBoard.changeRecruitStatus(status, currentDateTime); + RecruitBoard recruitBoard = createRecruitBoard(center.getId(), "제목", OTHER, "지역", true, + status); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); @@ -342,7 +278,7 @@ void findAllNearByLocation() { // then assertThat(result).isNotNull(); - assertThat(result.getTotalElements()).isEqualTo(boards.size()); + assertThat(result.getTotalElements()).isEqualTo(1); assertThat(result.getContent()).isNotEmpty(); } @@ -373,10 +309,8 @@ void findAllNearByLocation_noResult() { @Test void findAllByCenterId() { // given - Center center = createCenter(); - centerRepository.save(center); - - RecruitBoard recruitBoard = createRecruitBoard(center.getId()); + UUID centerId = UUID.randomUUID(); + RecruitBoard recruitBoard = createRecruitBoard(centerId, 1L, RECRUITING); recruitBoardRepository.save(recruitBoard); Pageable pageable = getPageable(); @@ -398,27 +332,51 @@ void findAllByCenterId() { @Test void findAllByCenterIdWhenWrongCenterId() { // given - UUID centerId = UUID.randomUUID(); + UUID wrongId = UUID.randomUUID(); Pageable pageable = getPageable(); RecruitBoardSearchCondition condition = RecruitBoardSearchCondition.builder() .pageable(pageable) .build(); // when - Page results = recruitBoardRepository.findAllByCenterId(centerId, - condition); + Page results = recruitBoardRepository.findAllByCenterId(wrongId, condition); // then assertThat(results).isEmpty(); } + @DisplayName("센터 ID로 완료되지 않은 모집 게시글들의 ID를 조회할 수 있다") + @Test + void findNotCompletedIdsByCenterIds() { + // given + UUID centerId = UUID.randomUUID(); + + RecruitBoard recruitingBoard = createRecruitBoard(centerId, 1L, RECRUITING); + recruitBoardRepository.save(recruitingBoard); + + RecruitBoard closedBoard = createRecruitBoard(centerId, 1L, CLOSED); + recruitBoardRepository.save(closedBoard); + + RecruitBoard completedRecruitBoard = createRecruitBoard(centerId, 1L, COMPLETED); + recruitBoardRepository.save(completedRecruitBoard); + + // when + List notCompletedBoardIds = recruitBoardRepository.findNotCompletedIdsByCenterId( + centerId); + + // then + assertThat(notCompletedBoardIds) + .hasSize(2) + .doesNotContain(completedRecruitBoard.getId()); + } + @DisplayName("아이디 리스트로 모집글을 조회할 수 있다.") @Test void findAllByIds() { // given - RecruitBoard board1 = createRecruitBoard(); - RecruitBoard board2 = createRecruitBoard(); - RecruitBoard board3 = createRecruitBoard(); + RecruitBoard board1 = createRecruitBoard(UUID.randomUUID(), 1L, RECRUITING); + RecruitBoard board2 = createRecruitBoard(UUID.randomUUID(), 1L, RECRUITING); + RecruitBoard board3 = createRecruitBoard(UUID.randomUUID(), 1L, RECRUITING); recruitBoardRepository.saveAll(List.of(board1, board2, board3)); List ids = List.of(board1.getId(), board2.getId(), board3.getId(), 100000L); @@ -430,6 +388,57 @@ void findAllByIds() { assertThat(all).hasSize(3); } + @DisplayName("봉사 시작일 기준으로 모집중 상태 게시글을 모집 완료로 변경한다") + @Test + void updateStatusToClosedForDateRange() { + // given + LocalDateTime today = LocalDateTime.of(2024, 1, 1, 0, 0); // 2024-01-01 00:00:00 + LocalDateTime startDateTime = today.plusHours(12); // 2024-01-01 12:00:00 + LocalDateTime endDateTime = startDateTime.plusHours(2); // 2024-01-01 14:00:00 + LocalDateTime tomorrow = today.plusDays(1); // 2024-01-02 00:00:00 + + RecruitBoard boardOne = createRecruitBoard(startDateTime, endDateTime, RECRUITING); + RecruitBoard boardTwo = createRecruitBoard(startDateTime, endDateTime, RECRUITING); + recruitBoardRepository.saveAll(List.of(boardOne, boardTwo)); + + // when + long updateCnt = recruitBoardRepository.updateStatusToClosedForDateRange(today, tomorrow); + em.clear(); + + // then + assertThat(updateCnt).isEqualTo(2); + RecruitBoard one = recruitBoardRepository.findById(boardOne.getId()).orElseThrow(); + RecruitBoard two = recruitBoardRepository.findById(boardTwo.getId()).orElseThrow(); + assertThat(one.getRecruitStatus()).isEqualTo(CLOSED); + assertThat(two.getRecruitStatus()).isEqualTo(CLOSED); + } + + @DisplayName("봉사 종료일 기준으로 모집완료 상태 게시글을 종료로 변경한다") + @Test + void updateStatusToCompletedForDateRange() { + // given + LocalDateTime now = LocalDateTime.of(2024, 1, 2, 0, 0); // 2024-01-02 00:00:00 + LocalDateTime yesterday = now.minusDays(1); // 2024-01-01 00:00:00 + LocalDateTime startDateTime = yesterday.plusHours(12); // 2024-01-01 12:00:00 + LocalDateTime endDateTime = startDateTime.plusHours(2); // 2024-01-01 14:00:00 + + RecruitBoard boardOne = createRecruitBoard(startDateTime, endDateTime, CLOSED); + RecruitBoard boardTwo = createRecruitBoard(startDateTime, endDateTime, CLOSED); + recruitBoardRepository.saveAll(List.of(boardOne, boardTwo)); + + // when + long updateCnt = recruitBoardRepository.updateStatusToCompletedForDateRange(yesterday, now); + em.clear(); + + // then + assertThat(updateCnt).isEqualTo(2); + + RecruitBoard one = recruitBoardRepository.findById(boardOne.getId()).orElseThrow(); + RecruitBoard two = recruitBoardRepository.findById(boardTwo.getId()).orElseThrow(); + assertThat(one.getRecruitStatus()).isEqualTo(COMPLETED); + assertThat(two.getRecruitStatus()).isEqualTo(COMPLETED); + } + // @DisplayName("모집글을 elastic search index에 저장할 수 있다. (repository)") // @Test // void saveDocuments() { @@ -462,6 +471,79 @@ void findAllByIds() { // recruitBoardRepository.deleteDocument(savedBoard2.getId()); // } + public static RecruitBoard createRecruitBoard(UUID centerId, Long locationId, + RecruitStatus status) { + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(LocalDateTime.now()) + .volunteerEndDateTime(LocalDateTime.now()) + .volunteerHours(10) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(centerId) + .locationId(locationId) + .title("모집글 제목") + .content("모집글 내용") + .imgUrl("이미지 링크") + .recruitmentInfo(recruitmentInfo) + .status(status) + .build(); + } + + public static RecruitBoard createRecruitBoard(UUID centerId, String title, + VolunteerCategory category, + String region, boolean admitted, RecruitStatus status) { + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region(region) + .recruitmentCount(1) + .volunteerStartDateTime(LocalDateTime.now()) + .volunteerEndDateTime(LocalDateTime.now()) + .volunteerHours(10) + .volunteerCategory(category) + .admitted(admitted) + .build(); + + return RecruitBoard.builder() + .centerId(centerId) + .locationId(1L) + .title(title) + .content("모집글 내용") + .imgUrl("이미지 링크") + .recruitmentInfo(recruitmentInfo) + .status(status) + .build(); + } + + private static RecruitBoard createRecruitBoard(LocalDateTime startTime, LocalDateTime endTime, + RecruitStatus status) { + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(startTime) + .volunteerEndDateTime(endTime) + .volunteerHours(10) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(UUID.randomUUID()) + .locationId(1L) + .title("모집글 제목") + .content("모집글 내용") + .imgUrl("이미지 링크") + .recruitmentInfo(recruitmentInfo) + .status(status) + .build(); + } + private Pageable getPageable() { Sort sort = Sort.by(Sort.Order.desc("created_at")); return PageRequest.of(0, 5, sort); diff --git a/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateSchedulerTest.java b/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateSchedulerTest.java new file mode 100644 index 000000000..79b9d2d87 --- /dev/null +++ b/src/test/java/com/somemore/domains/recruitboard/scheduler/RecruitBoardStatusUpdateSchedulerTest.java @@ -0,0 +1,106 @@ +package com.somemore.domains.recruitboard.scheduler; + +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static org.assertj.core.api.Assertions.assertThat; + +import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.RecruitmentInfo; +import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; +import com.somemore.support.IntegrationTestSupport; +import jakarta.persistence.EntityManager; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +class RecruitBoardStatusUpdateSchedulerTest extends IntegrationTestSupport { + + @Autowired + private RecruitBoardRepository recruitBoardRepository; + + @Autowired + private RecruitBoardStatusUpdateScheduler scheduler; + + @Autowired + private EntityManager em; + + @DisplayName("봉사 시작일에 해당하는 모집글 상태를 CLOSED 변경") + @Test + void updateRecruitBoardStatusToClosed() { + // given + LocalDateTime today = LocalDate.now().atStartOfDay(); // today 00:00:00 + LocalDateTime startDateTime = today.plusHours(12); // today 12:00:00 + LocalDateTime endDateTime = startDateTime.plusHours(2); // today 14:00:00 + + RecruitBoard boardOne = createRecruitBoard(startDateTime, endDateTime, RECRUITING); + RecruitBoard boardTwo = createRecruitBoard(startDateTime, endDateTime, RECRUITING); + recruitBoardRepository.saveAll(List.of(boardOne, boardTwo)); + + // when + scheduler.updateRecruitBoardStatusToClosed(); + em.clear(); + + // then + RecruitBoard one = recruitBoardRepository.findById(boardOne.getId()).orElseThrow(); + RecruitBoard two = recruitBoardRepository.findById(boardTwo.getId()).orElseThrow(); + assertThat(one.getRecruitStatus()).isEqualTo(CLOSED); + assertThat(two.getRecruitStatus()).isEqualTo(CLOSED); + } + + @DisplayName("봉사 종료일 기준으로 모집완료 상태 게시글을 종료로 변경한다") + @Test + void updateRecruitBoardStatusToCompleted() { + // given + LocalDateTime today = LocalDate.now().atStartOfDay(); // today 00:00:00 + LocalDateTime yesterday = today.minusDays(1); // yesterday 00:00:00 + LocalDateTime startDateTime = yesterday.plusHours(12); // yesterday 12:00:00 + LocalDateTime endDateTime = startDateTime.plusHours(2); // yesterday 14:00:00 + + RecruitBoard boardOne = createRecruitBoard(startDateTime, endDateTime, CLOSED); + RecruitBoard boardTwo = createRecruitBoard(startDateTime, endDateTime, CLOSED); + recruitBoardRepository.saveAll(List.of(boardOne, boardTwo)); + + // when + scheduler.updateRecruitBoardStatusToCompleted(); + em.clear(); + + // then + RecruitBoard one = recruitBoardRepository.findById(boardOne.getId()).orElseThrow(); + RecruitBoard two = recruitBoardRepository.findById(boardTwo.getId()).orElseThrow(); + assertThat(one.getRecruitStatus()).isEqualTo(COMPLETED); + assertThat(two.getRecruitStatus()).isEqualTo(COMPLETED); + } + + private static RecruitBoard createRecruitBoard(LocalDateTime startTime, LocalDateTime endTime, + RecruitStatus status) { + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(startTime) + .volunteerEndDateTime(endTime) + .volunteerHours(10) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(UUID.randomUUID()) + .locationId(1L) + .title("모집글 제목") + .content("모집글 내용") + .imgUrl("이미지 링크") + .recruitmentInfo(recruitmentInfo) + .status(status) + .build(); + } +} diff --git a/src/test/java/com/somemore/domains/recruitboard/service/DeleteRecruitBoardServiceTest.java b/src/test/java/com/somemore/domains/recruitboard/service/DeleteRecruitBoardServiceTest.java index b53e7c542..45b272ece 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/DeleteRecruitBoardServiceTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/DeleteRecruitBoardServiceTest.java @@ -1,23 +1,23 @@ package com.somemore.domains.recruitboard.service; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; +import static org.assertj.core.api.Assertions.assertThat; + import com.somemore.domains.recruitboard.domain.RecruitBoard; import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.support.IntegrationTestSupport; +import java.time.LocalDateTime; +import java.util.Optional; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Optional; -import java.util.UUID; - -import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; -import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; -import static org.assertj.core.api.Assertions.assertThat; - @Transactional class DeleteRecruitBoardServiceTest extends IntegrationTestSupport { @@ -73,6 +73,7 @@ private static RecruitBoard createRecruitBoard() { .content("봉사모집내용") .imgUrl("https://image.domain.com/links") .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) .build(); } } diff --git a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java index 508a169b5..839ee31ea 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/RecruitBoardQueryServiceTest.java @@ -1,5 +1,14 @@ package com.somemore.domains.recruitboard.service; +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; +import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD; +import static com.somemore.support.fixture.CenterFixture.createCenter; +import static com.somemore.support.fixture.LocationFixture.createLocation; +import static com.somemore.support.fixture.RecruitBoardFixture.createCompletedRecruitBoard; +import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.somemore.domains.center.domain.Center; import com.somemore.domains.center.repository.center.CenterRepository; import com.somemore.domains.location.domain.Location; @@ -15,6 +24,8 @@ import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.global.exception.BadRequestException; import com.somemore.support.IntegrationTestSupport; +import java.util.List; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -25,19 +36,6 @@ import org.springframework.data.domain.Sort; import org.springframework.transaction.annotation.Transactional; -import java.util.List; -import java.util.UUID; - -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_CENTER; -import static com.somemore.global.exception.ExceptionMessage.NOT_EXISTS_RECRUIT_BOARD; -import static com.somemore.support.fixture.CenterFixture.createCenter; -import static com.somemore.support.fixture.LocalDateTimeFixture.createCurrentDateTime; -import static com.somemore.support.fixture.LocationFixture.createLocation; -import static com.somemore.support.fixture.RecruitBoardFixture.createCompletedRecruitBoard; -import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - @Transactional class RecruitBoardQueryServiceTest extends IntegrationTestSupport { @@ -236,12 +234,12 @@ void findNotCompletedIdsByCenterIds() { recruitBoardRepository.save(recruitingBoard); RecruitBoard deletedClosedBoard = createRecruitBoard(centerId); - deletedClosedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + deletedClosedBoard.updateRecruitStatus(RecruitStatus.CLOSED); deletedClosedBoard.markAsDeleted(); recruitBoardRepository.save(deletedClosedBoard); RecruitBoard closedBoard = createRecruitBoard(centerId); - closedBoard.changeRecruitStatus(RecruitStatus.CLOSED, createCurrentDateTime()); + closedBoard.updateRecruitStatus(RecruitStatus.CLOSED); recruitBoardRepository.save(closedBoard); RecruitBoard deletedCompletedRecruitBoard = createCompletedRecruitBoard(); @@ -252,7 +250,8 @@ void findNotCompletedIdsByCenterIds() { recruitBoardRepository.save(completedRecruitBoard); // when - List notCompletedBoardIds = recruitBoardQueryService.getNotCompletedIdsByCenterIds(centerId); + List notCompletedBoardIds = recruitBoardQueryService.getNotCompletedIdsByCenterIds( + centerId); // then assertThat(notCompletedBoardIds) diff --git a/src/test/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardServiceTest.java b/src/test/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardServiceTest.java index 9265db3ee..52fd9285f 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardServiceTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/UpdateRecruitBoardServiceTest.java @@ -1,5 +1,14 @@ package com.somemore.domains.recruitboard.service; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static com.somemore.support.fixture.LocalDateTimeFixture.createCurrentDateTime; +import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; +import static com.somemore.support.fixture.LocalDateTimeFixture.createUpdateStartDateTime; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; + import com.somemore.domains.location.domain.Location; import com.somemore.domains.location.repository.LocationRepository; import com.somemore.domains.recruitboard.domain.RecruitBoard; @@ -10,21 +19,18 @@ import com.somemore.domains.recruitboard.repository.RecruitBoardJpaRepository; import com.somemore.domains.recruitboard.repository.RecruitBoardRepository; import com.somemore.support.IntegrationTestSupport; +import java.math.BigDecimal; +import java.time.Clock; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.UUID; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.transaction.annotation.Transactional; -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.UUID; - -import static com.somemore.domains.recruitboard.domain.VolunteerCategory.ADMINISTRATIVE_SUPPORT; -import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; -import static com.somemore.support.fixture.LocalDateTimeFixture.*; -import static org.assertj.core.api.Assertions.assertThat; - @Transactional class UpdateRecruitBoardServiceTest extends IntegrationTestSupport { @@ -40,7 +46,11 @@ class UpdateRecruitBoardServiceTest extends IntegrationTestSupport { @Autowired private LocationRepository locationRepository; + @SpyBean + private Clock clock; + private RecruitBoard recruitBoard; + private UUID centerId; @BeforeEach @@ -56,6 +66,7 @@ void setUp() { @Test void updateRecruitBoard() { // given + LocalDateTime current = createCurrentDateTime(); LocalDateTime newStartDateTime = createUpdateStartDateTime(); LocalDateTime newEndDateTime = newStartDateTime.plusHours(3); String newImgUrl = "https://image.domain.com/updates"; @@ -71,8 +82,11 @@ void updateRecruitBoard() { .admitted(false) .build(); + setMockClock(current); + // when - updateRecruitBoardService.updateRecruitBoard(dto, recruitBoard.getId(), centerId, newImgUrl); + updateRecruitBoardService.updateRecruitBoard(dto, recruitBoard.getId(), centerId, + newImgUrl); // then RecruitBoard updatedRecruitBoard = recruitBoardRepository.findById(recruitBoard.getId()) @@ -97,6 +111,7 @@ void updateRecruitBoard() { @Test void updateRecruitBoardLocation() { // given + LocalDateTime current = createCurrentDateTime(); RecruitBoardLocationUpdateRequestDto dto = RecruitBoardLocationUpdateRequestDto.builder() .region("새로새로지역지역") .address("새로새로주소주소") @@ -104,6 +119,8 @@ void updateRecruitBoardLocation() { .longitude(BigDecimal.valueOf(127.2222222)) .build(); + setMockClock(current); + // when updateRecruitBoardService.updateRecruitBoardLocation(dto, recruitBoard.getId(), centerId); @@ -127,10 +144,12 @@ void updateRecruitBoardStatus() { // given Long recruitBoardId = recruitBoard.getId(); RecruitStatus newStatus = RecruitStatus.CLOSED; - LocalDateTime currentDateTime = createCurrentDateTime(); + LocalDateTime current = createCurrentDateTime(); + + setMockClock(current); // when - updateRecruitBoardService.updateRecruitBoardStatus(newStatus, recruitBoardId, centerId, currentDateTime); + updateRecruitBoardService.updateRecruitBoardStatus(newStatus, recruitBoardId, centerId); // then RecruitBoard findBoard = recruitBoardRepository.findById(recruitBoardId).orElseThrow(); @@ -144,7 +163,8 @@ private static RecruitBoard createRecruitBoard(UUID centerId, Long locationId) { return createRecruitBoard(centerId, locationId, startDateTime, endDateTime); } - private static RecruitBoard createRecruitBoard(UUID centerId, Long locationId, LocalDateTime startDateTime, LocalDateTime endDateTime) { + private static RecruitBoard createRecruitBoard(UUID centerId, Long locationId, + LocalDateTime startDateTime, LocalDateTime endDateTime) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() .region("경기") @@ -163,6 +183,7 @@ private static RecruitBoard createRecruitBoard(UUID centerId, Long locationId, L .content("봉사모집내용") .imgUrl("https://image.domain.com/links") .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) .build(); } @@ -173,4 +194,10 @@ private static Location createLocation() { .latitude(BigDecimal.valueOf(127.11111)) .build(); } + + private void setMockClock(LocalDateTime current) { + doReturn(current.atZone(ZoneId.systemDefault()).toInstant()) + .when(clock) + .instant(); + } } diff --git a/src/test/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidatorTest.java b/src/test/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidatorTest.java index f684b79ab..212abcf83 100644 --- a/src/test/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidatorTest.java +++ b/src/test/java/com/somemore/domains/recruitboard/service/validator/RecruitBoardValidatorTest.java @@ -1,34 +1,52 @@ package com.somemore.domains.recruitboard.service.validator; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; +import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; +import static com.somemore.global.exception.ExceptionMessage.INVALID_DEADLINE_RECRUIT_BOARD_UPDATE; +import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME; +import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME_UPDATE; +import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; +import static com.somemore.support.fixture.LocalDateTimeFixture.createCurrentDateTime; +import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import com.somemore.domains.recruitboard.domain.RecruitBoard; +import com.somemore.domains.recruitboard.domain.RecruitStatus; +import com.somemore.domains.recruitboard.domain.RecruitmentInfo; import com.somemore.global.exception.BadRequestException; +import com.somemore.global.exception.ExceptionMessage; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.time.LocalDateTime; -import java.util.UUID; - -import static com.somemore.global.exception.ExceptionMessage.INVALID_RECRUIT_BOARD_TIME; -import static com.somemore.global.exception.ExceptionMessage.UNAUTHORIZED_RECRUIT_BOARD; -import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; -import static com.somemore.support.fixture.RecruitBoardFixture.createRecruitBoard; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - class RecruitBoardValidatorTest { private final RecruitBoardValidator validator = new RecruitBoardValidator(); + private RecruitBoard board; + + @BeforeEach + void setUp() { + board = createRecruitBoard(); + } + @DisplayName("봉사 종료 시간이 시작 시간과 같거나 빠르면, 봉사 모집글 생성 시 에러가 발생한다") @ParameterizedTest @ValueSource(longs = {0, -1}) - void createRecruitBoardWithInValidVolunteerTime(long minutesOffset) { + void validateRecruitBoardTimeWhenNotValid(long minutesOffset) { // given - LocalDateTime now = createStartDateTime(); + LocalDateTime now = LocalDateTime.of(2025, 1, 1, 10, 0); LocalDateTime endDateTime = now.plusMinutes(minutesOffset); - // when & then + // when + // then assertThatThrownBy( () -> validator.validateRecruitBoardTime(now, endDateTime)) .isInstanceOf(BadRequestException.class) @@ -36,12 +54,41 @@ void createRecruitBoardWithInValidVolunteerTime(long minutesOffset) { } + @Test + @DisplayName("StartDateTime createdAt + 1일 이후일 경우 예외 없이 통과") + void validateUpdateRecruitBoardTime() { + // given + LocalDateTime createdAt = LocalDateTime.of(2025, 1, 1, 10, 0); // 2025-01-01 10:00:00 + LocalDateTime start = LocalDateTime.of(2025, 1, 2, 10, 0); // 2025-01-02 10:00:00 + LocalDateTime end = LocalDateTime.of(2025, 1, 3, 10, 0); // 2025-01-03 10:00:00 + + // when + // then + assertDoesNotThrow(() -> validator.validateUpdateRecruitBoardTime(createdAt, start, end)); + } + + @Test + @DisplayName("StartDateTime createdAt + 1일 이전일 경우 예외 발생") + void validateUpdateRecruitBoardTimeWhenNotValid() { + // given + LocalDateTime createdAt = LocalDateTime.of(2025, 1, 2, 10, 0); // 2025-01-02 10:00:00 + LocalDateTime start = LocalDateTime.of(2025, 1, 2, 12, 0); // 2025-01-02 12:00:00 + LocalDateTime end = LocalDateTime.of(2025, 1, 2, 14, 0); // 2025-01-02 14:00:00 + + // when + // then + assertThatThrownBy( + () -> validator.validateUpdateRecruitBoardTime(createdAt, start, end)) + .isInstanceOf(BadRequestException.class) + .hasMessage(INVALID_RECRUIT_BOARD_TIME_UPDATE.getMessage()); + + } + @DisplayName("모집글 작성자가 아닌 경우 에러가 발생한다") @Test void validateWriter() { // given UUID wrongCenterId = UUID.randomUUID(); - RecruitBoard board = createRecruitBoard(UUID.randomUUID()); // when // then @@ -51,4 +98,78 @@ void validateWriter() { .hasMessage(UNAUTHORIZED_RECRUIT_BOARD.getMessage()); } + @DisplayName("모집글 업데이트는 봉사 시작 일시 전날까지 가능하다") + @Test + void validateUpdatable() { + // given + LocalDateTime current = createCurrentDateTime(); + + // when + // then + assertDoesNotThrow(() -> validator.validateUpdatable(board, current)); + } + + @DisplayName("봉사 시작 일시 00시 이후 모집글 업데이트시 에러가 발생한다.") + @Test + void validateUpdatableWhenNotValid() { + // given + LocalDateTime wrongDateTime = board.getRecruitmentInfo().getVolunteerStartDateTime(); + + // when + // then + assertThatThrownBy( + () -> validator.validateUpdatable(board, wrongDateTime)) + .isInstanceOf(BadRequestException.class) + .hasMessage(INVALID_DEADLINE_RECRUIT_BOARD_UPDATE.getMessage()); + } + + @DisplayName("모집글 상태는 모집중, 마감으로 변경할 수 있다.") + @Test + void validateRecruitStatus() { + // given + RecruitStatus status = CLOSED; + + // when & then + assertDoesNotThrow(() -> validator.validateRecruitStatus(status)); + } + + @DisplayName("모집글 상태를 종료변경하는 경우 에러가 발생한다.") + @Test + void validateRecruitStatusWhenNotValid() { + // given + RecruitStatus status = COMPLETED; + + // when + // then + assertThatThrownBy( + () -> validator.validateRecruitStatus(status)) + .isInstanceOf(BadRequestException.class) + .hasMessage(ExceptionMessage.INVALID_RECRUIT_BOARD_STATUS_UPDATE.getMessage()); + } + + public static RecruitBoard createRecruitBoard() { + LocalDateTime startDateTime = createStartDateTime(); + LocalDateTime endDateTime = startDateTime.plusHours(1); + + RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() + .region("지역") + .recruitmentCount(1) + .volunteerStartDateTime(startDateTime) + .volunteerEndDateTime(endDateTime) + .volunteerHours(1) + .volunteerCategory(OTHER) + .admitted(true) + .build(); + + return RecruitBoard.builder() + .centerId(UUID.randomUUID()) + .locationId(1L) + .title("제목") + .content("내용") + .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) + .imgUrl("이미지 링크") + .build(); + } + } diff --git a/src/test/java/com/somemore/support/fixture/RecruitBoardFixture.java b/src/test/java/com/somemore/support/fixture/RecruitBoardFixture.java index f28d81538..f7cbc1f40 100644 --- a/src/test/java/com/somemore/support/fixture/RecruitBoardFixture.java +++ b/src/test/java/com/somemore/support/fixture/RecruitBoardFixture.java @@ -11,6 +11,7 @@ import static com.somemore.domains.recruitboard.domain.RecruitStatus.CLOSED; import static com.somemore.domains.recruitboard.domain.RecruitStatus.COMPLETED; +import static com.somemore.domains.recruitboard.domain.RecruitStatus.RECRUITING; import static com.somemore.domains.recruitboard.domain.VolunteerCategory.OTHER; import static com.somemore.support.fixture.LocalDateTimeFixture.createStartDateTime; @@ -27,6 +28,7 @@ public class RecruitBoardFixture { private static final String CONTENT = "봉사모집내용"; private static final String IMG_URL = "https://image.domain.com/links"; private static final VolunteerCategory VOLUNTEER_CATEGORY = OTHER; + private static final RecruitStatus STATUS = RECRUITING; private RecruitBoardFixture() { } @@ -50,6 +52,7 @@ public static RecruitBoard createRecruitBoard() { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -72,6 +75,7 @@ public static RecruitBoard createRecruitBoard(String title) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -94,6 +98,7 @@ public static RecruitBoard createRecruitBoard(String title, UUID centerId, Long .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -116,6 +121,7 @@ public static RecruitBoard createRecruitBoard(String title, UUID centerId) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -138,6 +144,7 @@ public static RecruitBoard createRecruitBoard(VolunteerCategory category, UUID c .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -160,6 +167,7 @@ public static RecruitBoard createRecruitBoard(Boolean admitted, UUID centerId) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -182,6 +190,7 @@ public static RecruitBoard createRecruitBoard(Long locationId) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -204,6 +213,7 @@ public static RecruitBoard createRecruitBoard(UUID centerId) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(RECRUITING) .build(); } @@ -226,11 +236,11 @@ public static RecruitBoard createRecruitBoard(UUID centerId, Long locationId) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } - public static RecruitBoard createRecruitBoard(String region, - VolunteerCategory volunteerCategory) { + public static RecruitBoard createRecruitBoard(String region, VolunteerCategory volunteerCategory) { RecruitmentInfo recruitmentInfo = RecruitmentInfo.builder() .region(region) @@ -249,6 +259,7 @@ public static RecruitBoard createRecruitBoard(String region, .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -271,6 +282,7 @@ public static RecruitBoard createRecruitBoard(Long locationId, String title) { .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -293,6 +305,7 @@ public static RecruitBoard createRecruitBoard(LocalDateTime start, LocalDateTime .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); } @@ -315,6 +328,7 @@ public static RecruitBoard createCompletedRecruitBoard(UUID centerId, .content(CONTENT) .imgUrl(IMG_URL) .recruitmentInfo(recruitmentInfo) + .status(STATUS) .build(); setRecruitStatus(recruitBoard, COMPLETED);