diff --git a/src/main/java/com/souzip/adapter/webapi/admin/AdminLocationApi.java b/src/main/java/com/souzip/adapter/webapi/admin/AdminLocationApi.java index 343992c1..68df71b4 100644 --- a/src/main/java/com/souzip/adapter/webapi/admin/AdminLocationApi.java +++ b/src/main/java/com/souzip/adapter/webapi/admin/AdminLocationApi.java @@ -59,7 +59,6 @@ public SuccessResponse> getCities( @PostMapping("/cities") public SuccessResponse createCity(@Valid @RequestBody CityCreateRequest request) { adminLocationModifier.createCity(request); - return SuccessResponse.of("도시가 추가되었습니다."); } @@ -70,7 +69,6 @@ public SuccessResponse updateCity( @Valid @RequestBody CityUpdateRequest request ) { adminLocationModifier.updateCity(cityId, request); - return SuccessResponse.of("도시 정보가 수정되었습니다."); } @@ -78,7 +76,6 @@ public SuccessResponse updateCity( @DeleteMapping("/cities/{cityId}") public SuccessResponse deleteCity(@PathVariable Long cityId) { adminLocationModifier.deleteCity(cityId); - return SuccessResponse.of("도시가 삭제되었습니다."); } @@ -91,4 +88,4 @@ public SuccessResponse updateCityPriority( adminLocationModifier.updateCityPriority(cityId, priority); return SuccessResponse.of("우선순위가 업데이트되었습니다."); } -} \ No newline at end of file +} diff --git a/src/main/java/com/souzip/application/admin/AdminLocationModifyService.java b/src/main/java/com/souzip/application/admin/AdminLocationModifyService.java index c74cf100..062e0026 100644 --- a/src/main/java/com/souzip/application/admin/AdminLocationModifyService.java +++ b/src/main/java/com/souzip/application/admin/AdminLocationModifyService.java @@ -1,7 +1,8 @@ package com.souzip.application.admin; import com.souzip.application.admin.provided.AdminLocationModifier; -import com.souzip.application.admin.required.CityCommandPort; +import com.souzip.domain.city.application.command.*; +import com.souzip.domain.city.application.port.CityManagementPort; import com.souzip.domain.city.entity.CityCreateRequest; import com.souzip.domain.city.entity.CityUpdateRequest; import lombok.RequiredArgsConstructor; @@ -13,25 +14,37 @@ @Service public class AdminLocationModifyService implements AdminLocationModifier { - private final CityCommandPort cityCommandPort; + private final CityManagementPort cityManagementPort; @Override public void createCity(CityCreateRequest request) { - cityCommandPort.createCity(request); + cityManagementPort.createCity(new CreateCityCommand( + request.nameEn(), + request.nameKr(), + request.coordinate().getLatitude().doubleValue(), + request.coordinate().getLongitude().doubleValue(), + request.countryId() + )); } @Override public void updateCity(Long cityId, CityUpdateRequest request) { - cityCommandPort.updateCity(cityId, request); + cityManagementPort.updateCity(new UpdateCityCommand( + cityId, + request.nameEn(), + request.nameKr(), + request.coordinate().getLatitude().doubleValue(), + request.coordinate().getLongitude().doubleValue() + )); } @Override public void deleteCity(Long cityId) { - cityCommandPort.deleteCity(cityId); + cityManagementPort.deleteCity(new DeleteCityCommand(cityId)); } @Override public void updateCityPriority(Long cityId, Integer priority) { - cityCommandPort.updateCityPriority(cityId, priority); + cityManagementPort.updateCityPriority(new UpdateCityPriorityCommand(cityId, priority)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/souzip/application/admin/AdminLocationQueryService.java b/src/main/java/com/souzip/application/admin/AdminLocationQueryService.java index 0e4f54a0..f07e0b7b 100644 --- a/src/main/java/com/souzip/application/admin/AdminLocationQueryService.java +++ b/src/main/java/com/souzip/application/admin/AdminLocationQueryService.java @@ -1,10 +1,10 @@ package com.souzip.application.admin; import com.souzip.application.admin.provided.AdminLocationFinder; -import com.souzip.application.admin.required.CityQueryPort; -import com.souzip.application.admin.required.CountryQueryPort; import com.souzip.domain.city.entity.City; +import com.souzip.domain.city.repository.CityRepository; import com.souzip.domain.country.entity.Country; +import com.souzip.domain.country.repository.CountryRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -18,16 +18,22 @@ @Service public class AdminLocationQueryService implements AdminLocationFinder { - private final CityQueryPort cityQueryPort; - private final CountryQueryPort countryQueryPort; + private final CityRepository cityRepository; + private final CountryRepository countryRepository; @Override public Page getCities(Long countryId, String keyword, Pageable pageable) { - return cityQueryPort.getCities(countryId, keyword, pageable); + if (keyword == null || keyword.isBlank()) { + return cityRepository.findByCountryIdWithPaging(countryId, pageable); + } + return cityRepository.searchByKeyword(countryId, keyword, pageable); } @Override public List getCountries(String keyword) { - return countryQueryPort.getCountries(keyword); + if (keyword == null || keyword.isBlank()) { + return countryRepository.findAllByOrderByNameKrAsc(); + } + return countryRepository.findByKeywordOrderByNameKrAsc(keyword); } -} \ No newline at end of file +} diff --git a/src/main/java/com/souzip/application/admin/provided/AdminLocationFinder.java b/src/main/java/com/souzip/application/admin/provided/AdminLocationFinder.java index bcb731ac..5b876460 100644 --- a/src/main/java/com/souzip/application/admin/provided/AdminLocationFinder.java +++ b/src/main/java/com/souzip/application/admin/provided/AdminLocationFinder.java @@ -11,4 +11,4 @@ public interface AdminLocationFinder { Page getCities(Long countryId, String keyword, Pageable pageable); List getCountries(String keyword); -} \ No newline at end of file +} diff --git a/src/main/java/com/souzip/application/admin/provided/AdminLocationModifier.java b/src/main/java/com/souzip/application/admin/provided/AdminLocationModifier.java index 72244cf6..d8e345cd 100644 --- a/src/main/java/com/souzip/application/admin/provided/AdminLocationModifier.java +++ b/src/main/java/com/souzip/application/admin/provided/AdminLocationModifier.java @@ -11,4 +11,4 @@ public interface AdminLocationModifier { void deleteCity(Long cityId); void updateCityPriority(Long cityId, Integer priority); -} \ No newline at end of file +} diff --git a/src/main/java/com/souzip/application/admin/required/CityCommandPort.java b/src/main/java/com/souzip/application/admin/required/CityCommandPort.java deleted file mode 100644 index 3f278e54..00000000 --- a/src/main/java/com/souzip/application/admin/required/CityCommandPort.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.souzip.application.admin.required; - -import com.souzip.domain.city.entity.CityCreateRequest; -import com.souzip.domain.city.entity.CityUpdateRequest; -import org.springframework.stereotype.Component; - -@Component -public interface CityCommandPort { - void createCity(CityCreateRequest request); - - void updateCity(Long cityId, CityUpdateRequest request); - - void deleteCity(Long cityId); - - void updateCityPriority(Long cityId, Integer priority); -} \ No newline at end of file diff --git a/src/main/java/com/souzip/application/admin/required/CityQueryPort.java b/src/main/java/com/souzip/application/admin/required/CityQueryPort.java deleted file mode 100644 index b880d982..00000000 --- a/src/main/java/com/souzip/application/admin/required/CityQueryPort.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.souzip.application.admin.required; - -import com.souzip.domain.city.entity.City; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Component; - -@Component -public interface CityQueryPort { - Page getCities(Long countryId, String keyword, Pageable pageable); -} \ No newline at end of file diff --git a/src/main/java/com/souzip/application/admin/required/CountryQueryPort.java b/src/main/java/com/souzip/application/admin/required/CountryQueryPort.java deleted file mode 100644 index 4739dd77..00000000 --- a/src/main/java/com/souzip/application/admin/required/CountryQueryPort.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.souzip.application.admin.required; - -import com.souzip.domain.country.entity.Country; -import org.springframework.stereotype.Component; - -import java.util.List; - -@Component -public interface CountryQueryPort { - List getCountries(String keyword); -} \ No newline at end of file diff --git a/src/main/java/com/souzip/domain/city/application/command/CityCommandService.java b/src/main/java/com/souzip/domain/city/application/command/CityCommandService.java index 08eb790e..23e250ad 100644 --- a/src/main/java/com/souzip/domain/city/application/command/CityCommandService.java +++ b/src/main/java/com/souzip/domain/city/application/command/CityCommandService.java @@ -1,95 +1,100 @@ -package com.souzip.domain.city.application.command; + package com.souzip.domain.city.application.command; -import com.souzip.domain.city.application.port.CityManagementPort; -import com.souzip.domain.city.entity.City; -import com.souzip.domain.city.event.CityCreatedEvent; -import com.souzip.domain.city.event.CityDeletedEvent; -import com.souzip.domain.city.event.CityPriorityUpdatedEvent; -import com.souzip.domain.city.repository.CityRepository; -import com.souzip.domain.city.service.CityPriorityDomainService; -import com.souzip.domain.country.entity.Country; -import com.souzip.domain.country.repository.CountryRepository; -import com.souzip.global.exception.BusinessException; -import com.souzip.global.exception.ErrorCode; -import java.math.BigDecimal; -import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; + import com.souzip.domain.city.application.port.CityManagementPort; + import com.souzip.domain.city.entity.City; + import com.souzip.domain.city.event.CityCreatedEvent; + import com.souzip.domain.city.event.CityDeletedEvent; + import com.souzip.domain.city.event.CityPriorityUpdatedEvent; + import com.souzip.domain.city.repository.CityRepository; + import com.souzip.domain.city.service.CityPriorityDomainService; + import com.souzip.domain.country.entity.Country; + import com.souzip.domain.country.repository.CountryRepository; + import com.souzip.global.exception.BusinessException; + import com.souzip.global.exception.ErrorCode; + import java.math.BigDecimal; + import lombok.RequiredArgsConstructor; + import org.springframework.context.ApplicationEventPublisher; + import org.springframework.stereotype.Service; + import org.springframework.transaction.annotation.Transactional; -@RequiredArgsConstructor -@Service -public class CityCommandService implements CityManagementPort { + @RequiredArgsConstructor + @Service + public class CityCommandService implements CityManagementPort { - private final CityRepository cityRepository; - private final CountryRepository countryRepository; - private final ApplicationEventPublisher eventPublisher; - private final CityPriorityDomainService cityPriorityDomainService; + private final CityRepository cityRepository; + private final CountryRepository countryRepository; + private final ApplicationEventPublisher eventPublisher; + private final CityPriorityDomainService cityPriorityDomainService; - @Transactional - @Override - public void createCity(CreateCityCommand command) { - Country country = findCountryById(command.countryId()); - City city = City.create( - command.nameEn(), - command.nameKr(), - BigDecimal.valueOf(command.latitude()), - BigDecimal.valueOf(command.longitude()), - country - ); - cityRepository.save(city); + @Transactional + @Override + public void createCity(CreateCityCommand command) { + Country country = findCountryById(command.countryId()); + City city = City.create( + command.nameEn(), + command.nameKr(), + BigDecimal.valueOf(command.latitude()), + BigDecimal.valueOf(command.longitude()), + country + ); + cityRepository.save(city); - eventPublisher.publishEvent(CityCreatedEvent.of( - city.getId(), - country.getId() - )); - } + eventPublisher.publishEvent(CityCreatedEvent.of( + city.getId(), + country.getId() + )); + } - @Transactional - @Override - public void updateCity(UpdateCityCommand command) { - City city = findCityById(command.cityId()); - city.updateName(command.nameEn(), command.nameKr()); - } + @Transactional + @Override + public void updateCity(UpdateCityCommand command) { + City city = findCityById(command.cityId()); + city.update( + command.nameEn(), + command.nameKr(), + BigDecimal.valueOf(command.latitude()), + BigDecimal.valueOf(command.longitude()) + ); + } - @Transactional - @Override - public void deleteCity(DeleteCityCommand command) { - City city = findCityById(command.cityId()); - cityRepository.delete(city); + @Transactional + @Override + public void deleteCity(DeleteCityCommand command) { + City city = findCityById(command.cityId()); + cityRepository.delete(city); - eventPublisher.publishEvent(CityDeletedEvent.of(city.getId())); - } + eventPublisher.publishEvent(CityDeletedEvent.of(city.getId())); + } - @Transactional - @Override - public void updateCityPriority(UpdateCityPriorityCommand command) { - City city = findCityByIdWithLock(command.cityId()); - Integer oldPriority = city.getPriority(); - Long countryId = city.getCountry().getId(); + @Transactional + @Override + public void updateCityPriority(UpdateCityPriorityCommand command) { + City city = findCityByIdWithLock(command.cityId()); + Integer oldPriority = city.getPriority(); + Long countryId = city.getCountry().getId(); - cityPriorityDomainService.adjustPriorities(city.getId(), oldPriority, command.newPriority(), countryId); - city.updatePriority(command.newPriority()); + cityPriorityDomainService.adjustPriorities(city.getId(), oldPriority, command.newPriority(), countryId); + city.updatePriority(command.newPriority()); - eventPublisher.publishEvent(CityPriorityUpdatedEvent.of( - city.getId(), - oldPriority, - command.newPriority() - )); - } + eventPublisher.publishEvent(CityPriorityUpdatedEvent.of( + city.getId(), + oldPriority, + command.newPriority() + )); + } - private City findCityByIdWithLock(Long cityId) { - return cityRepository.findByIdWithLock(cityId) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "도시를 찾을 수 없습니다.")); - } + private City findCityByIdWithLock(Long cityId) { + return cityRepository.findByIdWithLock(cityId) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "도시를 찾을 수 없습니다.")); + } - private City findCityById(Long cityId) { - return cityRepository.findById(cityId) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "도시를 찾을 수 없습니다.")); - } + private City findCityById(Long cityId) { + return cityRepository.findById(cityId) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "도시를 찾을 수 없습니다.")); + } - private Country findCountryById(Long countryId) { - return countryRepository.findById(countryId) - .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "나라를 찾을 수 없습니다.")); + private Country findCountryById(Long countryId) { + return countryRepository.findById(countryId) + .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "나라를 찾을 수 없습니다.")); + } } -} diff --git a/src/main/java/com/souzip/domain/city/application/command/UpdateCityCommand.java b/src/main/java/com/souzip/domain/city/application/command/UpdateCityCommand.java index a848b411..0beede36 100644 --- a/src/main/java/com/souzip/domain/city/application/command/UpdateCityCommand.java +++ b/src/main/java/com/souzip/domain/city/application/command/UpdateCityCommand.java @@ -3,6 +3,8 @@ public record UpdateCityCommand( Long cityId, String nameEn, - String nameKr + String nameKr, + Double latitude, + Double longitude ) { } diff --git a/src/main/java/com/souzip/domain/city/entity/City.java b/src/main/java/com/souzip/domain/city/entity/City.java index 6d116753..5fa3460d 100644 --- a/src/main/java/com/souzip/domain/city/entity/City.java +++ b/src/main/java/com/souzip/domain/city/entity/City.java @@ -62,9 +62,11 @@ public void updatePriority(Integer priority) { this.priority = priority; } - public void updateName(String nameEn, String nameKr) { + public void update(String nameEn, String nameKr, BigDecimal latitude, BigDecimal longitude) { this.nameEn = nameEn; this.nameKr = nameKr; + this.latitude = latitude; + this.longitude = longitude; } private void validatePriority(Integer priority) { diff --git a/src/main/java/com/souzip/domain/country/application/query/CountryQueryService.java b/src/main/java/com/souzip/domain/country/application/query/CountryQueryService.java index 6a5dbc68..8199d4df 100644 --- a/src/main/java/com/souzip/domain/country/application/query/CountryQueryService.java +++ b/src/main/java/com/souzip/domain/country/application/query/CountryQueryService.java @@ -1,6 +1,6 @@ package com.souzip.domain.country.application.query; -import com.souzip.domain.country.application.port.CountryAdminPort.CountryAdminResult; +import com.souzip.domain.country.application.port.CountryAdminPort; import com.souzip.domain.country.entity.Country; import com.souzip.domain.country.repository.CountryRepository; import java.util.List; @@ -11,10 +11,11 @@ @Transactional(readOnly = true) @RequiredArgsConstructor @Service -public class CountryQueryService { +public class CountryQueryService implements CountryAdminPort { private final CountryRepository countryRepository; + @Override public List getCountries(String keyword) { if (hasNoKeyword(keyword)) { return toResults(countryRepository.findAllByOrderByNameKrAsc()); diff --git a/src/main/java/com/souzip/global/exception/ErrorCode.java b/src/main/java/com/souzip/global/exception/ErrorCode.java index 2f06c698..2f907bb1 100644 --- a/src/main/java/com/souzip/global/exception/ErrorCode.java +++ b/src/main/java/com/souzip/global/exception/ErrorCode.java @@ -52,12 +52,6 @@ public enum ErrorCode implements BaseErrorCode { INVALID_CATEGORY(HttpStatus.BAD_REQUEST, "유효하지 않은 카테고리입니다."), - SEARCH_INDEX_NOT_READY(HttpStatus.SERVICE_UNAVAILABLE, "검색 인덱스가 준비되지 않았습니다."), - SEARCH_INDEX_CREATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "검색 인덱스 생성에 실패했습니다."), - SEARCH_INDEX_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "검색 인덱스 삭제에 실패했습니다."), - SEARCH_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "검색 데이터 저장에 실패했습니다."), - SEARCH_SERVICE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "검색 서비스에 오류가 발생했습니다."), - AI_RECOMMENDATION_NOT_READY(HttpStatus.BAD_REQUEST, "추천 시스템을 위해 기념품 업로드 이력이 있어야 합니다."), APPLE_MIGRATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "Apple 마이그레이션 준비 중 오류가 발생했습니다."), diff --git a/src/test/java/com/souzip/adapter/webapi/admin/AdminLocationApiTest.java b/src/test/java/com/souzip/adapter/webapi/admin/AdminLocationApiTest.java index d2b6b990..b8b81951 100644 --- a/src/test/java/com/souzip/adapter/webapi/admin/AdminLocationApiTest.java +++ b/src/test/java/com/souzip/adapter/webapi/admin/AdminLocationApiTest.java @@ -1,13 +1,13 @@ package com.souzip.adapter.webapi.admin; -import com.souzip.application.admin.provided.AdminLocationFinder; -import com.souzip.application.admin.provided.AdminLocationModifier; import com.souzip.docs.RestDocsSupport; import com.souzip.domain.city.entity.City; import com.souzip.domain.city.entity.CityCreateRequest; import com.souzip.domain.city.entity.CityUpdateRequest; import com.souzip.domain.country.entity.Country; import com.souzip.domain.shared.Coordinate; +import java.math.BigDecimal; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.data.domain.Page; @@ -15,24 +15,30 @@ import org.springframework.data.domain.PageRequest; import org.springframework.http.MediaType; import org.springframework.restdocs.payload.JsonFieldType; - -import java.math.BigDecimal; -import java.util.List; +import com.souzip.application.admin.provided.AdminLocationFinder; +import com.souzip.application.admin.provided.AdminLocationModifier; import static com.souzip.docs.ApiDocumentUtils.getDocumentRequest; import static com.souzip.docs.ApiDocumentUtils.getDocumentResponse; import static com.souzip.docs.CommonDocumentation.apiResponseFields; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.mock; import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -273,4 +279,4 @@ private City createCity(Long id, String nameEn, String nameKr) { return city; } -} \ No newline at end of file +}