diff --git a/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressRequest.java b/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressRequest.java new file mode 100644 index 0000000..ebff93f --- /dev/null +++ b/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressRequest.java @@ -0,0 +1,16 @@ +package com.neighbors.tohero.application.address.dto; + +import com.neighbors.tohero.common.enums.TargetJob; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import org.hibernate.validator.constraints.Length; + +public record SearchAddressRequest ( + @Length(min = 1, max = 50) + @NotBlank + String searchAddress, + + @NotNull + TargetJob targetJob +){ +} diff --git a/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressResponse.java b/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressResponse.java new file mode 100644 index 0000000..db2f3e1 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/application/address/dto/SearchAddressResponse.java @@ -0,0 +1,28 @@ +package com.neighbors.tohero.application.address.dto; + +import com.neighbors.tohero.domain.domain.address.model.Address; + +import java.util.List; + +public record SearchAddressResponse ( + List addressInfos +){ + public record AddressInfo( + long addressId, + String officeName, + String roadAddress + ) { + public static AddressInfo from(Address address) { + return new AddressInfo(address.getAddressId(), address.getOfficeName(), address.getRoadAddress()); + } + } + + public static SearchAddressResponse from(List
searchedAddresses) { + + List addressInfos = searchedAddresses.stream() + .map(AddressInfo::from) + .toList(); + + return new SearchAddressResponse(addressInfos); + } +} diff --git a/src/main/java/com/neighbors/tohero/application/address/service/AddressService.java b/src/main/java/com/neighbors/tohero/application/address/service/AddressService.java new file mode 100644 index 0000000..9f6db17 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/application/address/service/AddressService.java @@ -0,0 +1,54 @@ +package com.neighbors.tohero.application.address.service; + +import com.neighbors.tohero.application.address.dto.SearchAddressRequest; +import com.neighbors.tohero.application.address.dto.SearchAddressResponse; +import com.neighbors.tohero.application.baseResponse.BaseResponse; +import com.neighbors.tohero.application.baseResponse.BaseResponseMessage; +import com.neighbors.tohero.application.baseResponse.BaseResponseStatus; +import com.neighbors.tohero.common.enums.TargetJob; +import com.neighbors.tohero.common.exception.address.AddressException; +import com.neighbors.tohero.domain.domain.address.model.Address; +import com.neighbors.tohero.domain.domain.address.service.GetAddress; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Service +@RequiredArgsConstructor +public class AddressService { + + private final GetAddress getAddress; + + public BaseResponse searchAddress(SearchAddressRequest searchAddressRequest) { + + List
searchedAddresses = getAddressByQuery(searchAddressRequest.searchAddress().trim(), searchAddressRequest.targetJob()); + if(searchedAddresses.isEmpty()) { + throw new AddressException(BaseResponseStatus.NO_RESULT, BaseResponseMessage.일치하는_관할서_정보가_없습니다.getMessage()); + } + + return new BaseResponse( + BaseResponseStatus.OK, + BaseResponseMessage.주소_검색이_성공적으로_응답되었습니다.getMessage(), + SearchAddressResponse.from(searchedAddresses) + ); + } + + private List
getAddressByQuery(String officeName, TargetJob targetJob) { + List
searchedAddressesByOfficeName = getAddress.searchAddressByOfficeName(officeName, targetJob); + List
searchedAddressesByRoadAddress = getAddress.searchAddressByRoadAddress(officeName, targetJob); + + return Stream.concat(searchedAddressesByOfficeName.stream(), searchedAddressesByRoadAddress.stream()) + .collect(Collectors.toMap( + Address::getAddressId, + address -> address, + (existing, replacement) -> existing + )) + .values() + .stream() + .toList(); + } +} diff --git a/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseMessage.java b/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseMessage.java index 0d2b17b..4ff0299 100644 --- a/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseMessage.java +++ b/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseMessage.java @@ -25,7 +25,12 @@ public enum BaseResponseMessage { //main page 메인페이지_조회가_정상적으로_실행되었습니다("메인페이지 조회가 정상적으로 실행되었습니다"), - 메인페이지_편지_무한페이징_조회가_정상적으로_실행되었습니다("메인페이지 편지 무한페이징 조회가 정상적으로 실행되었습니다"); + 메인페이지_편지_무한페이징_조회가_정상적으로_실행되었습니다("메인페이지 편지 무한페이징 조회가 정상적으로 실행되었습니다"), + + //address + 주소_검색_쿼리의_길이는_1부터_50까지만_가능합니다("주소 검색 쿼리의 길이는 1부터 50까지만 가능합니다"), + 일치하는_관할서_정보가_없습니다("일치하는 관할서 정보가 없습니다"), + 주소_검색이_성공적으로_응답되었습니다("주소 검색이 성공적으로 응답되었습니다"); private final String message; diff --git a/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseStatus.java b/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseStatus.java index 23bd58c..a5b3315 100644 --- a/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseStatus.java +++ b/src/main/java/com/neighbors/tohero/application/baseResponse/BaseResponseStatus.java @@ -1,5 +1,5 @@ package com.neighbors.tohero.application.baseResponse; public enum BaseResponseStatus { - OK, BAD_REQUEST, JWT_ERROR; + OK, BAD_REQUEST, JWT_ERROR, NO_RESULT; } diff --git a/src/main/java/com/neighbors/tohero/common/enums/TargetJob.java b/src/main/java/com/neighbors/tohero/common/enums/TargetJob.java new file mode 100644 index 0000000..eb6073b --- /dev/null +++ b/src/main/java/com/neighbors/tohero/common/enums/TargetJob.java @@ -0,0 +1,8 @@ +package com.neighbors.tohero.common.enums; + +import lombok.Getter; + +@Getter +public enum TargetJob { + POLICE_OFFICER, FIRE_FIGHTER +} diff --git a/src/main/java/com/neighbors/tohero/common/exception/address/AddressException.java b/src/main/java/com/neighbors/tohero/common/exception/address/AddressException.java new file mode 100644 index 0000000..3a9c034 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/common/exception/address/AddressException.java @@ -0,0 +1,14 @@ +package com.neighbors.tohero.common.exception.address; + +import com.neighbors.tohero.application.baseResponse.BaseResponseStatus; + +public class AddressException extends RuntimeException{ + private final BaseResponseStatus status; + private final String message; + + public AddressException(BaseResponseStatus status, String message) { + super(message); + this.status = status; + this.message = message; + } +} diff --git a/src/main/java/com/neighbors/tohero/domain/domain/address/model/Address.java b/src/main/java/com/neighbors/tohero/domain/domain/address/model/Address.java new file mode 100644 index 0000000..a130b72 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/domain/domain/address/model/Address.java @@ -0,0 +1,18 @@ +package com.neighbors.tohero.domain.domain.address.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class Address { + private long addressId; + private String officeName; + private String roadAddress; + private String phoneNumber; + private String queryPath; + + public static Address of(long addressId, String officeName, String roadAddress, String phoneNumber, String queryPath){ + return new Address(addressId, officeName, roadAddress, phoneNumber, queryPath); + } +} diff --git a/src/main/java/com/neighbors/tohero/domain/domain/address/service/GetAddress.java b/src/main/java/com/neighbors/tohero/domain/domain/address/service/GetAddress.java new file mode 100644 index 0000000..814afad --- /dev/null +++ b/src/main/java/com/neighbors/tohero/domain/domain/address/service/GetAddress.java @@ -0,0 +1,24 @@ +package com.neighbors.tohero.domain.domain.address.service; + +import com.neighbors.tohero.common.annotaion.DomainService; +import com.neighbors.tohero.common.enums.TargetJob; +import com.neighbors.tohero.domain.domain.address.model.Address; +import com.neighbors.tohero.domain.query.AddressRepository; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +@DomainService +@RequiredArgsConstructor +public class GetAddress { + + private final AddressRepository addressRepository; + + public List
searchAddressByOfficeName(String queryPath, TargetJob targetJob){ + return addressRepository.searchAddressByOfficeName(queryPath, targetJob); + } + + public List
searchAddressByRoadAddress(String queryRoadAddress, TargetJob targetJob){ + return addressRepository.searchAddressByRoadAddress(queryRoadAddress, targetJob); + } +} diff --git a/src/main/java/com/neighbors/tohero/domain/query/AddressRepository.java b/src/main/java/com/neighbors/tohero/domain/query/AddressRepository.java new file mode 100644 index 0000000..a8291db --- /dev/null +++ b/src/main/java/com/neighbors/tohero/domain/query/AddressRepository.java @@ -0,0 +1,11 @@ +package com.neighbors.tohero.domain.query; + +import com.neighbors.tohero.common.enums.TargetJob; +import com.neighbors.tohero.domain.domain.address.model.Address; + +import java.util.List; + +public interface AddressRepository { + List
searchAddressByOfficeName(String queryPath, TargetJob targetJob); + List
searchAddressByRoadAddress(String queryRoadAddress, TargetJob targetJob); +} diff --git a/src/main/java/com/neighbors/tohero/infrastructure/entity/AddressEntity.java b/src/main/java/com/neighbors/tohero/infrastructure/entity/AddressEntity.java index a24b7f3..e8ab6ae 100644 --- a/src/main/java/com/neighbors/tohero/infrastructure/entity/AddressEntity.java +++ b/src/main/java/com/neighbors/tohero/infrastructure/entity/AddressEntity.java @@ -1,10 +1,13 @@ package com.neighbors.tohero.infrastructure.entity; +import com.neighbors.tohero.common.enums.TargetJob; import com.neighbors.tohero.infrastructure.entity.base.BaseEntity; import jakarta.persistence.*; +import lombok.Getter; @Entity @Table(name = "`Address`") +@Getter public class AddressEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -22,4 +25,8 @@ public class AddressEntity extends BaseEntity { @Column(name = "query_path") private String queryPath; + + @Column(name = "target_job") + @Enumerated(value = EnumType.ORDINAL) + private TargetJob targetJob; } diff --git a/src/main/java/com/neighbors/tohero/infrastructure/mapper/AddressMapper.java b/src/main/java/com/neighbors/tohero/infrastructure/mapper/AddressMapper.java new file mode 100644 index 0000000..95bcae6 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/infrastructure/mapper/AddressMapper.java @@ -0,0 +1,18 @@ +package com.neighbors.tohero.infrastructure.mapper; + +import com.neighbors.tohero.domain.domain.address.model.Address; +import com.neighbors.tohero.infrastructure.entity.AddressEntity; +import org.springframework.stereotype.Component; + +@Component +public class AddressMapper { + public Address toDomain(AddressEntity addressEntity) { + return Address.of( + addressEntity.getAddressId(), + addressEntity.getBranchOffice(), + addressEntity.getRoadAddress(), + addressEntity.getPhoneNumber(), + addressEntity.getQueryPath() + ); + } +} diff --git a/src/main/java/com/neighbors/tohero/infrastructure/query/impl/AddressRepositoryImpl.java b/src/main/java/com/neighbors/tohero/infrastructure/query/impl/AddressRepositoryImpl.java new file mode 100644 index 0000000..b42719a --- /dev/null +++ b/src/main/java/com/neighbors/tohero/infrastructure/query/impl/AddressRepositoryImpl.java @@ -0,0 +1,38 @@ +package com.neighbors.tohero.infrastructure.query.impl; + +import com.neighbors.tohero.common.enums.TargetJob; +import com.neighbors.tohero.domain.domain.address.model.Address; +import com.neighbors.tohero.domain.query.AddressRepository; +import com.neighbors.tohero.infrastructure.entity.AddressEntity; +import com.neighbors.tohero.infrastructure.mapper.AddressMapper; +import com.neighbors.tohero.infrastructure.repository.AddressEntityRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class AddressRepositoryImpl implements AddressRepository { + + private final AddressEntityRepository addressEntityRepository; + private final AddressMapper addressMapper; + + @Override + public List
searchAddressByOfficeName(String queryPath, TargetJob targetJob) { + List addressEntities = addressEntityRepository.findAllContainsOfficeName(queryPath,targetJob); + + return addressEntities.stream() + .map(addressMapper::toDomain) + .toList(); + } + + @Override + public List
searchAddressByRoadAddress(String queryRoadAddress, TargetJob targetJob) { + List addressEntities = addressEntityRepository.findAllContainsRoadAddress(queryRoadAddress,targetJob); + + return addressEntities.stream() + .map(addressMapper::toDomain) + .toList(); + } +} diff --git a/src/main/java/com/neighbors/tohero/infrastructure/repository/AddressEntityRepository.java b/src/main/java/com/neighbors/tohero/infrastructure/repository/AddressEntityRepository.java new file mode 100644 index 0000000..74c0d99 --- /dev/null +++ b/src/main/java/com/neighbors/tohero/infrastructure/repository/AddressEntityRepository.java @@ -0,0 +1,24 @@ +package com.neighbors.tohero.infrastructure.repository; + +import com.neighbors.tohero.common.enums.TargetJob; +import com.neighbors.tohero.infrastructure.entity.AddressEntity; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface AddressEntityRepository extends JpaRepository { + + @Query("SELECT a FROM AddressEntity a WHERE a.queryPath LIKE %:queryPath% AND a.targetJob = :targetJob") + List findAllContainsOfficeName( + @Param("queryPath") String queryPath, + @Param("targetJob") TargetJob targetJob + ); + + @Query("SELECT a FROM AddressEntity a WHERE a.roadAddress LIKE %:queryRoadAddress% AND a.targetJob = :targetJob") + List findAllContainsRoadAddress( + @Param("queryRoadAddress") String queryRoadAddress, + @Param("targetJob") TargetJob targetJob + ); +} diff --git a/src/main/java/com/neighbors/tohero/presentation/controller/AddressController.java b/src/main/java/com/neighbors/tohero/presentation/controller/AddressController.java new file mode 100644 index 0000000..64bca9f --- /dev/null +++ b/src/main/java/com/neighbors/tohero/presentation/controller/AddressController.java @@ -0,0 +1,26 @@ +package com.neighbors.tohero.presentation.controller; + +import com.neighbors.tohero.application.address.dto.SearchAddressRequest; +import com.neighbors.tohero.application.address.service.AddressService; +import com.neighbors.tohero.application.baseResponse.BaseResponse; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("") +public class AddressController { + + private final AddressService addressService; + + @GetMapping("/address") + public ResponseEntity searchAddress(@ParameterObject @Validated SearchAddressRequest searchAddressRequest) { + return ResponseEntity.ok() + .body(addressService.searchAddress(searchAddressRequest)); + } +} diff --git a/src/main/java/com/neighbors/tohero/presentation/exception_handler/UserExceptionControllerAdvice.java b/src/main/java/com/neighbors/tohero/presentation/exception_handler/DTOFieldExceptionControllerAdvice.java similarity index 81% rename from src/main/java/com/neighbors/tohero/presentation/exception_handler/UserExceptionControllerAdvice.java rename to src/main/java/com/neighbors/tohero/presentation/exception_handler/DTOFieldExceptionControllerAdvice.java index 34b577f..7f1d2d6 100644 --- a/src/main/java/com/neighbors/tohero/presentation/exception_handler/UserExceptionControllerAdvice.java +++ b/src/main/java/com/neighbors/tohero/presentation/exception_handler/DTOFieldExceptionControllerAdvice.java @@ -15,16 +15,18 @@ import java.util.Map; import static com.neighbors.tohero.application.baseResponse.BaseResponseMessage.유저_이름의_길이는_1부터_5까지만_가능합니다; +import static com.neighbors.tohero.application.baseResponse.BaseResponseMessage.주소_검색_쿼리의_길이는_1부터_50까지만_가능합니다; @Priority(0) @RestControllerAdvice -public class UserExceptionControllerAdvice { +public class DTOFieldExceptionControllerAdvice { private final Map fieldErrorMapper; - public UserExceptionControllerAdvice() { + public DTOFieldExceptionControllerAdvice() { fieldErrorMapper = new HashMap<>(); fieldErrorMapper.put("nickname", 유저_이름의_길이는_1부터_5까지만_가능합니다); + fieldErrorMapper.put("searchAddress", 주소_검색_쿼리의_길이는_1부터_50까지만_가능합니다); } @ResponseStatus(HttpStatus.BAD_REQUEST)