Skip to content

Commit 6344643

Browse files
authored
Merge branch 'main' into feature/9-get-center-prefer-item
2 parents c9dbfb6 + 125efc7 commit 6344643

File tree

10 files changed

+193
-34
lines changed

10 files changed

+193
-34
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ out/
3636
### VS Code ###
3737
.vscode/
3838

39+
### resources
40+
/src/main/generated/
3941

4042
### env file ###
41-
*.env
43+
*.env

build.gradle

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ dependencies {
3737
runtimeOnly 'com.h2database:h2'
3838
runtimeOnly 'com.mysql:mysql-connector-j'
3939

40+
// Querydsl
41+
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
42+
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
43+
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
44+
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
45+
4046
// OAuth & Security
4147
implementation 'org.springframework.boot:spring-boot-starter-oauth2-authorization-server'
4248
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
@@ -58,6 +64,24 @@ dependencies {
5864
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
5965
}
6066

67+
// Querydsl 설정
68+
def generated = 'src/main/generated'
69+
70+
// QClass 파일 생성 위치를 지정
71+
tasks.withType(JavaCompile).configureEach {
72+
options.getGeneratedSourceOutputDirectory().set(file(generated))
73+
}
74+
75+
// java source set 에 querydsl QClass 위치 추가
76+
sourceSets {
77+
main.java.srcDirs += [ generated ]
78+
}
79+
80+
// gradle clean 시에 QClass 디렉토리 삭제
81+
clean {
82+
delete file(generated)
83+
}
84+
6185
tasks.named('test') {
6286
useJUnitPlatform()
6387
jvmArgs("-XX:+EnableDynamicAgentLoading")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.somemore.global.configure;
2+
3+
import com.querydsl.jpa.impl.JPAQueryFactory;
4+
import jakarta.persistence.EntityManager;
5+
import jakarta.persistence.PersistenceContext;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
9+
@Configuration
10+
public class QueryDslConfig {
11+
12+
@PersistenceContext
13+
private EntityManager em;
14+
15+
@Bean
16+
public JPAQueryFactory jpaQueryFactory() {
17+
return new JPAQueryFactory(em);
18+
}
19+
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.somemore.location.repository;
2+
3+
import com.somemore.location.domain.Location;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface LocationJpaRepository extends JpaRepository<Location, Long> {
7+
8+
}
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package com.somemore.location.repository;
22

33
import com.somemore.location.domain.Location;
4-
import org.springframework.data.jpa.repository.JpaRepository;
4+
import java.util.Optional;
55

6-
public interface LocationRepository extends JpaRepository<Location, Long> {
6+
public interface LocationRepository {
77

8+
Location save(Location location);
9+
10+
Optional<Location> findById(Long id);
11+
12+
void deleteAllInBatch();
813
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.somemore.location.repository;
2+
3+
import com.querydsl.jpa.impl.JPAQueryFactory;
4+
import com.somemore.location.domain.Location;
5+
import java.util.Optional;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Repository;
8+
9+
@RequiredArgsConstructor
10+
@Repository
11+
public class LocationRepositoryImpl implements LocationRepository {
12+
13+
private final LocationJpaRepository locationJpaRepository;
14+
private final JPAQueryFactory queryFactory;
15+
16+
@Override
17+
public Location save(Location location) {
18+
return locationJpaRepository.save(location);
19+
}
20+
21+
@Override
22+
public Optional<Location> findById(Long id) {
23+
return locationJpaRepository.findById(id);
24+
}
25+
26+
@Override
27+
public void deleteAllInBatch() {
28+
locationJpaRepository.deleteAllInBatch();
29+
}
30+
}

src/main/java/com/somemore/recruitboard/domain/RecruitBoard.java

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import jakarta.persistence.Id;
1414
import jakarta.persistence.Lob;
1515
import jakarta.persistence.Table;
16+
import java.time.Duration;
1617
import java.time.LocalDateTime;
1718
import java.time.LocalTime;
1819
import java.util.UUID;
@@ -56,33 +57,51 @@ public class RecruitBoard extends BaseEntity {
5657
@Column(name = "recruit_status", nullable = false, length = 20)
5758
private RecruitStatus recruitStatus = RECRUITING;
5859

59-
@Column(name = "volunteer_date", nullable = false)
60-
private LocalDateTime volunteerDate;
60+
@Column(name = "volunteer_start_date_time", nullable = false)
61+
private LocalDateTime volunteerStartDateTime;
62+
63+
@Column(name = "volunteer_end_date_time", nullable = false)
64+
private LocalDateTime volunteerEndDateTime;
6165

6266
@Enumerated(value = STRING)
6367
@Column(name = "volunteer_type", nullable = false, length = 30)
6468
private VolunteerType volunteerType;
6569

66-
@Column(name = "volunteer_hours", nullable = false)
67-
private LocalTime volunteerHours;
68-
6970
@Column(name = "admitted", nullable = false)
7071
private Boolean admitted;
7172

7273
@Builder
7374
public RecruitBoard(UUID centerId, Long locationId, String title, String content, String region,
74-
Integer recruitmentCount, String imgUrl, LocalDateTime volunteerDate,
75-
VolunteerType volunteerType, LocalTime volunteerHours, Boolean admitted) {
75+
Integer recruitmentCount, String imgUrl, LocalDateTime volunteerStartDateTime,
76+
LocalDateTime volunteerEndDateTime, VolunteerType volunteerType, Boolean admitted) {
77+
78+
validateVolunteerDateTime(volunteerStartDateTime, volunteerEndDateTime);
79+
7680
this.centerId = centerId;
7781
this.locationId = locationId;
7882
this.title = title;
7983
this.content = content;
8084
this.region = region;
8185
this.recruitmentCount = recruitmentCount;
8286
this.imgUrl = imgUrl;
83-
this.volunteerDate = volunteerDate;
87+
this.volunteerStartDateTime = volunteerStartDateTime;
88+
this.volunteerEndDateTime = volunteerEndDateTime;
8489
this.volunteerType = volunteerType;
85-
this.volunteerHours = volunteerHours;
8690
this.admitted = admitted;
8791
}
92+
93+
public LocalTime calculateVolunteerTime() {
94+
Duration duration = Duration.between(volunteerStartDateTime, volunteerEndDateTime);
95+
96+
long hours = duration.toHours();
97+
long minutes = duration.toMinutes() % 60;
98+
99+
return LocalTime.of((int) hours, (int) minutes);
100+
}
101+
102+
private void validateVolunteerDateTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
103+
if (endDateTime.isEqual(startDateTime) || endDateTime.isBefore(startDateTime)) {
104+
throw new IllegalArgumentException("종료 시간은 시작 시간보다 이후여야 합니다.");
105+
}
106+
}
88107
}

src/main/java/com/somemore/recruitboard/dto/request/RecruitBoardCreateRequestDto.java

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@
88
import io.swagger.v3.oas.annotations.media.Schema;
99
import jakarta.validation.constraints.NotBlank;
1010
import jakarta.validation.constraints.NotNull;
11-
import jakarta.validation.constraints.Positive;
1211
import java.time.LocalDateTime;
13-
import java.time.LocalTime;
1412
import java.util.UUID;
1513
import lombok.Builder;
1614

@@ -29,24 +27,22 @@ public record RecruitBoardCreateRequestDto(
2927
@Schema(description = "예상 모집 인원", example = "4")
3028
@NotNull(message = "예상 모집 인원은 필수 값입니다.")
3129
Integer recruitmentCount,
32-
@Schema(description = "봉사 일시", example = "2024-11-20T10:00:00")
33-
@NotNull(message = "봉사 일시는 필수 값입니다.")
34-
LocalDateTime volunteerDate,
30+
@Schema(description = "봉사 시작 일시", example = "2024-11-20T10:00:00")
31+
@NotNull(message = "봉사 시작 일시는 필수 값입니다.")
32+
LocalDateTime volunteerStartDateTime,
33+
@Schema(description = "봉사 종료 일시", example = "2024-11-20T12:00:00")
34+
@NotNull(message = "봉사 종료 일시는 필수 값입니다.")
35+
LocalDateTime volunteerEndDateTime,
3536
@Schema(description = "봉사 활동 유형", example = "ENVIRONMENTAL_PROTECTION")
3637
@NotNull(message = "봉사 활동 유형은 필수 값입니다.")
3738
VolunteerType volunteerType,
38-
@Schema(description = "봉사 시간(시)", example = "1")
39-
@Positive(message = "봉사 시간(시)은 1이상 이어야 합니다.")
40-
Integer volunteerHours,
41-
@Schema(description = "봉사 시간(분)", example = "30")
42-
@Positive(message = "봉사 시간(분)은 1이상 이어야 합니다.")
43-
Integer volunteerMinutes,
4439
@Schema(description = "봉사 시간 인정 여부", example = "true")
4540
@NotNull(message = "시간 인정 여부는 필수 값입니다.")
4641
Boolean admitted,
4742
@NotNull(message = "위치 정보는 필수 값입니다.")
4843
LocationCreateRequestDto location
49-
){
44+
) {
45+
5046
public RecruitBoard toEntity(UUID centerId, Long locationId, String imgUrl) {
5147
return RecruitBoard.builder()
5248
.centerId(centerId)
@@ -56,9 +52,9 @@ public RecruitBoard toEntity(UUID centerId, Long locationId, String imgUrl) {
5652
.region(region)
5753
.recruitmentCount(recruitmentCount)
5854
.imgUrl(imgUrl)
59-
.volunteerDate(volunteerDate)
55+
.volunteerStartDateTime(volunteerStartDateTime)
56+
.volunteerEndDateTime(volunteerEndDateTime)
6057
.volunteerType(volunteerType)
61-
.volunteerHours(LocalTime.of(volunteerHours, volunteerMinutes))
6258
.admitted(admitted)
6359
.build();
6460
}

src/test/java/com/somemore/recruitboard/domain/RecruitBoardTest.java

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@
33
import static com.somemore.recruitboard.domain.RecruitStatus.RECRUITING;
44
import static com.somemore.recruitboard.domain.VolunteerType.OTHER;
55
import static org.assertj.core.api.Assertions.assertThat;
6+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
67

78
import java.time.LocalDateTime;
89
import java.time.LocalTime;
910
import java.util.UUID;
1011
import org.junit.jupiter.api.DisplayName;
1112
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.params.ParameterizedTest;
14+
import org.junit.jupiter.params.provider.ValueSource;
1215

1316
class RecruitBoardTest {
1417

1518
@DisplayName("봉사 모집글 생성시 모집상태는 모집중이다")
1619
@Test
17-
void CreateRecruitBoardWithDefaultStatus() {
20+
void createRecruitBoardWithDefaultStatus() {
1821
// given
1922
RecruitBoard board = RecruitBoard.builder()
2023
.centerId(UUID.randomUUID())
@@ -24,9 +27,9 @@ void CreateRecruitBoardWithDefaultStatus() {
2427
.region("경기")
2528
.recruitmentCount(10)
2629
.imgUrl("https://image.domain.com/links")
27-
.volunteerDate(LocalDateTime.now())
30+
.volunteerStartDateTime(LocalDateTime.now())
31+
.volunteerEndDateTime(LocalDateTime.now().plusHours(1))
2832
.volunteerType(OTHER)
29-
.volunteerHours(LocalTime.of(7, 30))
3033
.admitted(true)
3134
.build();
3235

@@ -36,4 +39,59 @@ void CreateRecruitBoardWithDefaultStatus() {
3639
// then
3740
assertThat(recruitStatus).isEqualTo(RECRUITING);
3841
}
42+
43+
@DisplayName("봉사 종료 시간이 시작 시간과 같거나 빠르면, 봉사 모집글 생성 시 에러가 발생한다")
44+
@ParameterizedTest
45+
@ValueSource(longs = {0, -1})
46+
void createRecruitBoardWithInValidVolunteerTime(long secondsOffset) {
47+
// given
48+
LocalDateTime now = LocalDateTime.now();
49+
LocalDateTime endDateTime = now.plusSeconds(secondsOffset);
50+
51+
// when & then
52+
assertThatThrownBy(
53+
() -> RecruitBoard.builder()
54+
.centerId(UUID.randomUUID())
55+
.locationId(1L)
56+
.title("봉사모집제목")
57+
.content("봉사모집내용")
58+
.region("경기")
59+
.recruitmentCount(10)
60+
.imgUrl("https://image.domain.com/links")
61+
.volunteerStartDateTime(now)
62+
.volunteerEndDateTime(endDateTime)
63+
.volunteerType(VolunteerType.OTHER)
64+
.admitted(true)
65+
.build()
66+
).isInstanceOf(IllegalArgumentException.class);
67+
}
68+
69+
@DisplayName("봉사 시간을 계산할 수 있다")
70+
@Test
71+
void testCalculateVolunteerTime() {
72+
// given
73+
int hours = 3;
74+
LocalDateTime startDateTime = LocalDateTime.now();
75+
LocalDateTime endDateTime = startDateTime.plusHours(hours);
76+
77+
RecruitBoard board = RecruitBoard.builder()
78+
.centerId(UUID.randomUUID())
79+
.locationId(1L)
80+
.title("봉사모집제목")
81+
.content("봉사모집내용")
82+
.region("경기")
83+
.recruitmentCount(10)
84+
.imgUrl("https://image.domain.com/links")
85+
.volunteerStartDateTime(startDateTime)
86+
.volunteerEndDateTime(endDateTime)
87+
.volunteerType(OTHER)
88+
.admitted(true)
89+
.build();
90+
91+
// when
92+
LocalTime volunteerTime = board.calculateVolunteerTime();
93+
94+
// then
95+
assertThat(volunteerTime).isEqualTo(LocalTime.of(hours, 0));
96+
}
3997
}

src/test/java/com/somemore/recruitboard/service/command/CreateRecruitBoardServiceTest.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.somemore.recruitboard.repository.RecruitBoardRepository;
1212
import java.math.BigDecimal;
1313
import java.time.LocalDateTime;
14-
import java.time.LocalTime;
1514
import java.util.Optional;
1615
import java.util.UUID;
1716
import org.junit.jupiter.api.AfterEach;
@@ -51,10 +50,9 @@ void createRecruitBoardWithDto() {
5150
.content("봉사 하실분을 모집합니다. <br>")
5251
.region("지역")
5352
.recruitmentCount(10)
54-
.volunteerDate(LocalDateTime.now())
53+
.volunteerStartDateTime(LocalDateTime.now())
54+
.volunteerEndDateTime(LocalDateTime.now().plusHours(2))
5555
.volunteerType(VolunteerType.OTHER)
56-
.volunteerHours(1)
57-
.volunteerMinutes(30)
5856
.admitted(true)
5957
.location(locationDto)
6058
.build();
@@ -72,7 +70,6 @@ void createRecruitBoardWithDto() {
7270
assertThat(recruitBoard.get().getId()).isEqualTo(saveId);
7371
assertThat(recruitBoard.get().getCenterId()).isEqualTo(centerId);
7472
assertThat(recruitBoard.get().getImgUrl()).isEqualTo(imgUrl);
75-
assertThat(recruitBoard.get().getVolunteerHours()).isEqualTo(LocalTime.of(1, 30));
7673
}
7774

7875
}

0 commit comments

Comments
 (0)