Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
28d2446
refactor: RecruitBoard 리팩토링
leebs0521 Jan 13, 2025
a660ef2
test(recruit-board): RecruitBoard 리팩토링 테스트
leebs0521 Jan 13, 2025
48a7ce1
refactor(recruit-board): RecruitBoard 수정 검증 리팩토링
leebs0521 Jan 13, 2025
7ea6c6c
test(recruit-board): RecruitBoard 수정 검증 리팩토링 테스트
leebs0521 Jan 13, 2025
983cbff
refactor(recruit-board): RecruitBoard 수정 검증 리팩토링
leebs0521 Jan 13, 2025
0a50031
test(recruit-board): RecruitBoard 수정 검증 리팩토링 테스트
leebs0521 Jan 13, 2025
94077aa
refactor(recruit-board): RecruitBoard 수정 검증 변경에 따른 리팩토링
leebs0521 Jan 13, 2025
55c40ab
test(recruit-board): RecruitBoard 수정 검증 변경에 따른 리팩토링 테스트
leebs0521 Jan 13, 2025
9a2cc8a
chore: retry 종속성 추가
leebs0521 Jan 14, 2025
0545ba6
feat(recruit-board): 모집글 상태 스케쥴링
leebs0521 Jan 14, 2025
140852a
test(recruit-board): 모집글 상태 스케쥴링 테스트
leebs0521 Jan 14, 2025
0a1766f
refactor(recruit-board): sonarqube 이슈 해결
leebs0521 Jan 14, 2025
a6f10de
fix(recruit-board): sonarqube 이슈 해결
leebs0521 Jan 14, 2025
38f4f04
fix(recruit-board): sonarqube 이슈 해결
leebs0521 Jan 14, 2025
7cc768b
refactor(recruit-board): 스케쥴러 retryable 리팩토링
leebs0521 Jan 14, 2025
6a346e0
fix(recruit-board): 오타 수정
leebs0521 Jan 14, 2025
97c9e20
refactor(recruit-board): 스케쥴링 작업 중 에러 발생시 로깅 처리
leebs0521 Jan 14, 2025
b8b4c81
feat: TimeConfig 추가
leebs0521 Jan 15, 2025
e085aed
refactor(recruit-board): 시간 검증 로직 변경
leebs0521 Jan 15, 2025
373638b
test(recruit-board): 시간 검증 로직 변경 테스트
leebs0521 Jan 15, 2025
8793b85
refactor(recruit-board): 스케쥴링 로직 리팩토링
leebs0521 Jan 15, 2025
b8a0fa9
test(recruit-board): 스케쥴링 로직 리팩토링 테스트
leebs0521 Jan 15, 2025
838b558
refactor(recruit-board): 스케줄 시간 상수화
leebs0521 Jan 15, 2025
28e808b
refactor(recruit-board): sonar qube 이슈 해결
leebs0521 Jan 15, 2025
ed81cf7
refactor(recruit-board): 메서드명 명확하게 변경
leebs0521 Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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")
Expand All @@ -44,7 +50,6 @@ public ApiResponse<Long> 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,
Expand All @@ -64,7 +69,6 @@ public ApiResponse<String> updateRecruitBoard(
) {
String imgUrl = imageUploadUseCase.uploadImage(new ImageUploadRequestDto(image));
updateRecruitBoardUseCase.updateRecruitBoard(requestDto, id, userId, imgUrl);

return ApiResponse.ok("봉사 활동 모집글 수정 성공");
}

Expand All @@ -76,7 +80,6 @@ public ApiResponse<String> updateRecruitBoardLocation(
@PathVariable Long id,
@Valid @RequestBody RecruitBoardLocationUpdateRequestDto requestDto
) {

updateRecruitBoardUseCase.updateRecruitBoardLocation(requestDto, id, userId);
return ApiResponse.ok("봉사 활동 모집글 위치 수정 성공");
}
Expand All @@ -89,9 +92,7 @@ public ApiResponse<String> 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("봉사 활동 모집글 상태 수정 성공");
}

Expand All @@ -105,5 +106,4 @@ public ApiResponse<String> deleteRecruitBoard(
deleteRecruitBoardUseCase.deleteRecruitBoard(userId, id);
return ApiResponse.ok("봉사 활동 모집글 삭제 성공");
}

}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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(),
Expand All @@ -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("봉사 시작 일시 자정 전까지만 상태를 변경할 수 있습니다.");
}
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -69,6 +70,7 @@ public RecruitBoard toEntity(UUID centerId, Long locationId, String imgUrl) {
.content(content)
.imgUrl(imgUrl)
.recruitmentInfo(recruitmentInfo)
.status(RECRUITING)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = "봉사 시간는 필수 값입니다.")
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -26,17 +26,20 @@ public interface RecruitBoardRepository {

Page<RecruitBoardDetail> findAllNearby(RecruitBoardNearByCondition condition);

// Page<RecruitBoardDetail> findAllNearbyWithKeyword(RecruitBoardNearByCondition condition);


Page<RecruitBoard> findAllByCenterId(UUID centerId, RecruitBoardSearchCondition condition);

List<Long> findNotCompletedIdsByCenterId(UUID centerId);

List<RecruitBoard> findAllByIds(List<Long> ids);

List<RecruitBoard> findAll();

long updateStatusToClosedForDateRange(LocalDateTime startTime, LocalDateTime endTime);

long updateStatusToCompletedForDateRange(LocalDateTime startTime, LocalDateTime endTime);

// Page<RecruitBoardDetail> findAllNearbyWithKeyword(RecruitBoardNearByCondition condition);
// Page<RecruitBoardWithCenter> findByRecruitBoardsContaining(RecruitBoardSearchCondition condition);
// void saveDocuments(List<RecruitBoard> recruitBoards);
List<RecruitBoard> findAll();
// void deleteDocument(Long id);
}
Loading
Loading