Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d7624cf
refactor(recruit-board): RecruitBoard 객체 분리
leebs0521 Nov 22, 2024
fea3800
test(recruit-board): RecruitBoard 객체 분리에 따른 테스트
leebs0521 Nov 22, 2024
fa85194
feat(location): Location 생성, 조회, 수정 기능 추가
leebs0521 Nov 25, 2024
9973e1c
test(location): Location 생성, 조회, 수정 기능 테스트
leebs0521 Nov 25, 2024
ac3c60a
feat(recruit-board): RecruitBoard 수정
leebs0521 Nov 25, 2024
a32242a
test(recruit-board): RecruitBoard 수정 기능 테스트
leebs0521 Nov 25, 2024
219d00c
refactor(recruit-board): RecruitBoard 레포지토리 리팩토링
leebs0521 Nov 25, 2024
22d5091
feat(recruit-board): RecruitBoard 조회 기능
leebs0521 Nov 25, 2024
2a021ec
test(recruit-board): RecruitBoard 조회 기능 테스트
leebs0521 Nov 25, 2024
950ca5d
feat(recruit-board): RecruitBoard 수정 기능
leebs0521 Nov 25, 2024
7bb562b
test(recruit-board): RecruitBoard 수정 기능 테스트
leebs0521 Nov 25, 2024
b00b3c5
feat: QueryDsl queryFactory 추가
leebs0521 Nov 25, 2024
0ced1c9
chore: 불필요한 import 제거
leebs0521 Nov 25, 2024
78622a9
feat: 테스트 수정
leebs0521 Nov 25, 2024
0e5e3bf
fix: 테스트 수정
leebs0521 Nov 25, 2024
4601f0e
feat: 논리 삭제 반영
leebs0521 Nov 25, 2024
4f1a9a1
feat: 논리 삭제 테스트
leebs0521 Nov 25, 2024
de47b58
test: 고정된 LocalDateTime 반환 클래스 작성
leebs0521 Nov 25, 2024
921e210
test: LocalDateTimeFixture 으로 변경
leebs0521 Nov 25, 2024
06102da
fix: sonarqube issue fix
leebs0521 Nov 25, 2024
ad45c72
chore: 파일 끝 개행 문자 추가
leebs0521 Nov 25, 2024
4cc9412
fix: region 고정된 값이 들어가는 문제 수정
leebs0521 Nov 25, 2024
e4b30b8
refactor: findByIdOrThrow() -> findById() 대체
leebs0521 Nov 26, 2024
d0747cc
test: findByIdOrThrow() -> findById() 따른 테스트
leebs0521 Nov 26, 2024
4d30604
refactor: isNotWriter 메서드 제거
leebs0521 Nov 26, 2024
7f3d31b
test: isNotWriter 메서드 제거에 따른 테스트
leebs0521 Nov 26, 2024
d6992bc
refactor: VolunteerInfo -> RecruitmentInfo 객체명 변경
leebs0521 Nov 26, 2024
c58f0c2
test: VolunteerInfo -> RecruitmentInfo 변경에 따른 테스트
leebs0521 Nov 26, 2024
c02bd42
chore: 불필요한 import 제거
leebs0521 Nov 26, 2024
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
4 changes: 4 additions & 0 deletions src/main/java/com/somemore/global/common/BaseEntity.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public void prePersist() {
this.deleted = false;
}
}

public void markAsDeleted() {
this.deleted = true;
}
}
14 changes: 11 additions & 3 deletions src/main/java/com/somemore/location/domain/Location.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import static lombok.AccessLevel.PROTECTED;

import com.somemore.global.common.BaseEntity;
import com.somemore.location.dto.request.LocationUpdateRequestDto;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.math.RoundingMode;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -36,7 +38,13 @@ public class Location extends BaseEntity {
@Builder
public Location(String address, BigDecimal latitude, BigDecimal longitude) {
this.address = address;
this.latitude = latitude;
this.longitude = longitude;
this.latitude = latitude.setScale(8, RoundingMode.HALF_UP);
this.longitude = longitude.setScale(8, RoundingMode.HALF_UP);
}
}

public void updateWith(LocationUpdateRequestDto requestDto) {
this.address = requestDto.address();
this.latitude = requestDto.latitude().setScale(8, RoundingMode.HALF_UP);
this.longitude = requestDto.longitude().setScale(8, RoundingMode.HALF_UP);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.somemore.location.dto.request;

import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import lombok.Builder;

@JsonNaming(SnakeCaseStrategy.class)
@Builder
public record LocationUpdateRequestDto(
@Schema(description = "도로명 주소", example = "서울특별시 서초구 반포대로 45, 4층(서초동, 명정빌딩)")
@NotBlank(message = "주소는 필수 입력 값입니다.")
String address,
@Schema(description = "주소에 해당하는 위도 정보", example = "37.4845373748015")
@NotNull(message = "위도는 필수 입력 값입니다.")
@DecimalMin(value = "33", message = "위도는 33도 이상이어야 합니다.")
@DecimalMax(value = "39", message = "위도는 38도 이하이어야 합니다.")
BigDecimal latitude,
@Schema(description = "주소에 해당하는 경도 정보", example = "127.010842267696")
@NotNull(message = "경도는 필수 입력 값입니다.")
@DecimalMin(value = "124", message = "경도는 124도 이상이어야 합니다.")
@DecimalMax(value = "132", message = "경도는 132도 이하이어야 합니다.")
BigDecimal longitude
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public interface LocationRepository {

Location save(Location location);

Location saveAndFlush(Location location);

Optional<Location> findById(Long id);

void deleteAllInBatch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public Location save(Location location) {
return locationJpaRepository.save(location);
}

@Override
public Location saveAndFlush(Location location) {
return locationJpaRepository.saveAndFlush(location);
}

@Override
public Optional<Location> findById(Long id) {
return locationJpaRepository.findById(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.somemore.location.service.command;

import com.somemore.location.domain.Location;
import com.somemore.location.dto.request.LocationUpdateRequestDto;
import com.somemore.location.repository.LocationRepository;
import com.somemore.location.usecase.command.UpdateLocationUseCase;
import com.somemore.location.usecase.query.LocationQueryUseCase;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional
@Service
public class UpdateLocationService implements UpdateLocationUseCase {

private final LocationQueryUseCase locationQueryUseCase;
private final LocationRepository locationRepository;

@Override
public void updateLocation(LocationUpdateRequestDto requestDto, Long locationId) {
Location location = locationQueryUseCase.findByIdOrThrow(locationId);
location.updateWith(requestDto);
locationRepository.save(location);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.somemore.location.service.query;

import com.somemore.global.exception.BadRequestException;
import com.somemore.location.domain.Location;
import com.somemore.location.repository.LocationRepository;
import com.somemore.location.usecase.query.LocationQueryUseCase;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class LocationQueryService implements LocationQueryUseCase {

private final LocationRepository locationRepository;

@Override
public Optional<Location> findById(Long id) {
return locationRepository.findById(id);
}

@Override
public Location findByIdOrThrow(Long id) {
return locationRepository.findById(id).orElseThrow(
() -> new BadRequestException("존재하지 않는 위치입니다.")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.somemore.location.usecase.command;

import com.somemore.location.dto.request.LocationUpdateRequestDto;

public interface UpdateLocationUseCase {

void updateLocation(LocationUpdateRequestDto requestDto, Long locationId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.somemore.location.usecase.query;

import com.somemore.location.domain.Location;
import java.util.Optional;

public interface LocationQueryUseCase {

Optional<Location> findById(Long id);

Location findByIdOrThrow(Long id);

}
81 changes: 37 additions & 44 deletions src/main/java/com/somemore/recruitboard/domain/RecruitBoard.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import static lombok.AccessLevel.PROTECTED;

import com.somemore.global.common.BaseEntity;
import com.somemore.recruitboard.dto.request.RecruitBoardUpdateRequestDto;
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.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.UUID;
import lombok.Builder;
Expand Down Expand Up @@ -44,64 +44,57 @@ public class RecruitBoard extends BaseEntity {
@Column(name = "content", nullable = false)
private String content;

@Column(name = "region", nullable = false)
private String region;

@Column(name = "recruitment_count", nullable = false)
private Integer recruitmentCount;

@Column(name = "img_url", nullable = false)
private String imgUrl;
@Embedded
private VolunteerInfo volunteerInfo;

@Enumerated(value = STRING)
@Column(name = "recruit_status", nullable = false, length = 20)
private RecruitStatus recruitStatus = RECRUITING;

@Column(name = "volunteer_start_date_time", nullable = false)
private LocalDateTime volunteerStartDateTime;

@Column(name = "volunteer_end_date_time", nullable = false)
private LocalDateTime volunteerEndDateTime;

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

@Column(name = "admitted", nullable = false)
private Boolean admitted;
@Column(name = "img_url", nullable = false)
private String imgUrl;

@Builder
public RecruitBoard(UUID centerId, Long locationId, String title, String content, String region,
Integer recruitmentCount, String imgUrl, LocalDateTime volunteerStartDateTime,
LocalDateTime volunteerEndDateTime, VolunteerType volunteerType, Boolean admitted) {

validateVolunteerDateTime(volunteerStartDateTime, volunteerEndDateTime);

public RecruitBoard(UUID centerId, Long locationId, String title, String content,
VolunteerInfo volunteerInfo, String imgUrl) {
this.centerId = centerId;
this.locationId = locationId;
this.title = title;
this.content = content;
this.region = region;
this.recruitmentCount = recruitmentCount;
this.volunteerInfo = volunteerInfo;
this.imgUrl = imgUrl;
this.volunteerStartDateTime = volunteerStartDateTime;
this.volunteerEndDateTime = volunteerEndDateTime;
this.volunteerType = volunteerType;
this.admitted = admitted;
}

public LocalTime calculateVolunteerTime() {
Duration duration = Duration.between(volunteerStartDateTime, volunteerEndDateTime);
public LocalTime getVolunteerHours() {
return volunteerInfo.calculateVolunteerTime();
}

public boolean isWriter(UUID centerId) {
return this.centerId.equals(centerId);
}

long hours = duration.toHours();
long minutes = duration.toMinutes() % 60;
public boolean isNotWriter(UUID centerId) {
return !isWriter(centerId);
}

public void updateWith(RecruitBoardUpdateRequestDto dto, String imgUrl) {
updateVolunteerInfo(dto);
this.title = dto.title();
this.content = dto.content();
this.imgUrl = imgUrl;
}

return LocalTime.of((int) hours, (int) minutes);
public void updateWith(String region) {
volunteerInfo.updateWith(region);
}

private void validateVolunteerDateTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
if (endDateTime.isEqual(startDateTime) || endDateTime.isBefore(startDateTime)) {
throw new IllegalArgumentException("종료 시간은 시작 시간보다 이후여야 합니다.");
}
private void updateVolunteerInfo(RecruitBoardUpdateRequestDto dto) {
volunteerInfo.updateWith(
dto.recruitmentCount(),
dto.volunteerType(),
dto.volunteerStartDateTime(),
dto.volunteerEndDateTime(),
dto.admitted()
);
}
}
}
87 changes: 87 additions & 0 deletions src/main/java/com/somemore/recruitboard/domain/VolunteerInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.somemore.recruitboard.domain;

import static jakarta.persistence.EnumType.STRING;
import static java.time.temporal.ChronoUnit.MINUTES;
import static lombok.AccessLevel.PROTECTED;

import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Enumerated;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = PROTECTED)
@Embeddable
public class VolunteerInfo {

@Column(name = "region", nullable = false)
private String region;

@Column(name = "recruitment_count", nullable = false)
private Integer recruitmentCount;

@Column(name = "volunteer_start_date_time", nullable = false)
private LocalDateTime volunteerStartDateTime;

@Column(name = "volunteer_end_date_time", nullable = false)
private LocalDateTime volunteerEndDateTime;

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

@Column(name = "admitted", nullable = false)
private Boolean admitted;

@Builder
public VolunteerInfo(String region, Integer recruitmentCount,
LocalDateTime volunteerStartDateTime, LocalDateTime volunteerEndDateTime,
VolunteerType volunteerType, Boolean admitted) {

validateVolunteerDateTime(volunteerStartDateTime, volunteerEndDateTime);

this.region = region;
this.recruitmentCount = recruitmentCount;
this.volunteerStartDateTime = volunteerStartDateTime.truncatedTo(MINUTES);
this.volunteerEndDateTime = volunteerEndDateTime.truncatedTo(MINUTES);
this.volunteerType = volunteerType;
this.admitted = admitted;
}

public LocalTime calculateVolunteerTime() {
Duration duration = Duration.between(volunteerStartDateTime, volunteerEndDateTime);

long hours = duration.toHours();
long minutes = duration.toMinutes() % 60;

return LocalTime.of((int) hours, (int) minutes);
}

public void updateWith(Integer recruitmentCount, VolunteerType volunteerType,
LocalDateTime volunteerStartDateTime, LocalDateTime volunteerEndDateTime,
Boolean admitted) {

validateVolunteerDateTime(volunteerStartDateTime, volunteerEndDateTime);

this.recruitmentCount = recruitmentCount;
this.volunteerType = volunteerType;
this.volunteerStartDateTime = volunteerStartDateTime.truncatedTo(MINUTES);
this.volunteerEndDateTime = volunteerEndDateTime.truncatedTo(MINUTES);
this.admitted = admitted;
}

public void updateWith(String region) {
this.region = region;
}

private void validateVolunteerDateTime(LocalDateTime startDateTime, LocalDateTime endDateTime) {
if (endDateTime.isEqual(startDateTime) || endDateTime.isBefore(startDateTime)) {
throw new IllegalArgumentException("종료 시간은 시작 시간보다 이후여야 합니다.");
}
}
}
Loading