Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/주차장 데이터 추가 #82

Merged
merged 12 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
import com.parkingcomestrue.parking.support.exception.ClientExceptionInformation;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.stereotype.Component;

@Component
public class SearchConditionMapper {

public <E extends Enum<E> & SearchConditionAvailable> List<E> toEnums(Class<E> searchConditionAvailableClass,
List<String> descriptions) {
public <E extends Enum<E> & SearchConditionAvailable> Set<E> toEnums(Class<E> searchConditionAvailableClass,
List<String> descriptions) {
return descriptions.stream()
.map(description -> toEnum(searchConditionAvailableClass, description))
.toList();
.collect(Collectors.toSet());
}

public <E extends Enum<E> & SearchConditionAvailable> E toEnum(Class<E> searchConditionAvailableClass,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.parkingcomestrue.common.domain.parking.ParkingFeeCalculator;
import com.parkingcomestrue.common.domain.parking.ParkingType;
import com.parkingcomestrue.common.domain.parking.PayType;
import com.parkingcomestrue.common.domain.parking.SearchingCondition;
import com.parkingcomestrue.common.domain.parking.service.SearchingCondition;
import com.parkingcomestrue.common.domain.parking.repository.ParkingRepository;
import com.parkingcomestrue.common.domain.parking.service.ParkingFilteringService;
import com.parkingcomestrue.common.domain.searchcondition.FeeType;
Expand Down Expand Up @@ -89,10 +89,10 @@ private List<Parking> findParkingLotsByOrderCondition(String priority, ParkingQu
}

private SearchingCondition toSearchingCondition(ParkingSearchConditionRequest request) {
List<ParkingType> parkingTypes = searchConditionMapper.toEnums(ParkingType.class, request.getParkingTypes());
List<OperationType> operationTypes = searchConditionMapper.toEnums(OperationType.class,
Set<ParkingType> parkingTypes = searchConditionMapper.toEnums(ParkingType.class, request.getParkingTypes());
Set<OperationType> operationTypes = searchConditionMapper.toEnums(OperationType.class,
request.getOperationTypes());
List<PayType> payTypes = searchConditionMapper.toEnums(PayType.class, request.getPayTypes());
Set<PayType> payTypes = searchConditionMapper.toEnums(PayType.class, request.getPayTypes());
FeeType feeType = searchConditionMapper.toEnum(FeeType.class, request.getFeeType());

return new SearchingCondition(operationTypes, parkingTypes, payTypes, feeType, request.getHours());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import com.parkingcomestrue.common.domain.review.Content;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public record ReviewCreateRequest(List<String> contents) {

public List<Content> toContents() {
public Set<Content> toContents() {
return contents.stream()
.map(Content::find)
.toList();
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.parkingcomestrue.common.domain.searchcondition.repository.SearchConditionRepository;
import com.parkingcomestrue.common.support.Association;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -41,9 +42,10 @@ private SearchConditionDto toSearchConditionDto(SearchCondition searchCondition)
);
}

private <E extends SearchConditionAvailable> List<String> toDescriptions(List<E> enums) {
private <E extends SearchConditionAvailable> List<String> toDescriptions(Set<E> enums) {
return enums.stream()
.map(SearchConditionAvailable::getDescription)
.sorted()
.toList();
}

Expand Down
2 changes: 1 addition & 1 deletion app-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ authcode:
cors:
allowedOrigins: ${ORIGIN:http://localhost:3000}

api-prefix: ${API_PREFIX:/api/}
api-prefix: ${API_PREFIX:/}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.parkingcomestrue.common.domain.searchcondition.SearchConditionAvailable;

import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand Down Expand Up @@ -63,7 +64,7 @@ static Stream<Arguments> parametersProvider1() {
List<String> descriptions,
List<E> expected) {
//given, when
List<E> actual = searchConditionMapper.toEnums(clazz, descriptions);
Set<E> actual = searchConditionMapper.toEnums(clazz, descriptions);

//then
assertThat(actual).containsExactlyInAnyOrderElementsOf(expected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.parkingcomestrue.parking.application.review.dto.ReviewCreateRequest;
import java.time.LocalTime;
import java.util.List;
import java.util.Set;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -64,7 +65,7 @@ class ParkingServiceTest extends ContainerTest {
private Parking makeParking(String parkingName) {
return new Parking
(
new BaseInformation(parkingName, "010", "부산", List.of(PayType.NO_INFO), ParkingType.MECHANICAL,
new BaseInformation(parkingName, "010", "부산", Set.of(PayType.NO_INFO), ParkingType.MECHANICAL,
OperationType.PRIVATE),
Location.of(30d, 30d),
Space.of(100, 30),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.parkingcomestrue.common.support.Association;
import com.parkingcomestrue.common.support.exception.DomainException;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.Test;
import repository.BasicMemberRepository;
import repository.BasicParkingRepository;
Expand Down Expand Up @@ -65,9 +66,9 @@ class ReviewServiceTest {
Association<Parking> parkingId = Association.from(parking.getId());
List<Member> reviewers = memberRepository.saveAndGet(3);

List<Content> contents1 = List.of(Content.LOW_PRICE);
List<Content> contents2 = List.of(Content.LOW_PRICE, Content.EASY_TO_PAY);
List<Content> contents3 = List.of(Content.LOW_PRICE, Content.EASY_TO_PAY, Content.GOOD_ACCESSIBILITY);
Set<Content> contents1 = Set.of(Content.LOW_PRICE);
Set<Content> contents2 = Set.of(Content.LOW_PRICE, Content.EASY_TO_PAY);
Set<Content> contents3 = Set.of(Content.LOW_PRICE, Content.EASY_TO_PAY, Content.GOOD_ACCESSIBILITY);
reviewRepository.save(
new Review(parkingId, Association.from(reviewers.get(0).getId()), contents1)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.parkingcomestrue.external.config;

import com.parkingcomestrue.external.coordinate.CoordinateErrorHandler;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
Expand Down Expand Up @@ -29,8 +30,9 @@ public RestTemplate coordinateRestTemplate(RestTemplateBuilder restTemplateBuild
@Qualifier("parkingApiRestTemplate")
public RestTemplate parkingApiRestTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(30))
.errorHandler(new ParkingApiErrorHandler())
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_UTF8_VALUE)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package com.parkingcomestrue.external.parkingapi.korea;

import com.parkingcomestrue.common.domain.parking.BaseInformation;
import com.parkingcomestrue.common.domain.parking.Fee;
import com.parkingcomestrue.common.domain.parking.FeePolicy;
import com.parkingcomestrue.common.domain.parking.FreeOperatingTime;
import com.parkingcomestrue.common.domain.parking.Location;
import com.parkingcomestrue.common.domain.parking.OperatingTime;
import com.parkingcomestrue.common.domain.parking.OperationType;
import com.parkingcomestrue.common.domain.parking.Parking;
import com.parkingcomestrue.common.domain.parking.ParkingType;
import com.parkingcomestrue.common.domain.parking.PayType;
import com.parkingcomestrue.common.domain.parking.Space;
import com.parkingcomestrue.common.domain.parking.TimeInfo;
import com.parkingcomestrue.common.domain.parking.TimeUnit;
import java.time.DateTimeException;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;

@Component
public class KoreaParkingAdapter {

private static final String NO_PROVIDE = "-1";
private static final String FREE = "무료";
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final String HOURS_24 = "23:59";
private static final String HOURS_00 = "00:00";

public List<Parking> convert(KoreaParkingResponse response) {
return response.getResponse().getBody().getItems().stream()
.map(this::toParking)
.toList();
}

private Parking toParking(KoreaParkingResponse.Response.Body.Item item) {
return new Parking(
getBaseInformation(item),
getLocation(item),
getSpace(item),
getFreeOperatingTime(item),
getOperatingTime(item),
getFeePolicy(item)
);
}

private BaseInformation getBaseInformation(KoreaParkingResponse.Response.Body.Item item) {
return new BaseInformation(
item.getParkingName(),
item.getTel(),
filterAddress(item),
toPayTypes(item),
ParkingType.find(item.getParkingType()),
OperationType.find(item.getOperationType())
);
}

private String filterAddress(KoreaParkingResponse.Response.Body.Item item) {
if (Strings.isBlank(item.getOldAddress())) {
return item.getNewAddress();
}
return item.getOldAddress();
}

private Set<PayType> toPayTypes(KoreaParkingResponse.Response.Body.Item item) {
Set<PayType> payTypes = new HashSet<>();
for (PayType payType : PayType.values()) {
if (item.getPayType().contains(payType.getDescription())) {
payTypes.add(payType);
}
}
if (payTypes.isEmpty()) {
return Set.of(PayType.NO_INFO);
}
return payTypes;
}

private Location getLocation(KoreaParkingResponse.Response.Body.Item item) {
return Location.of(item.getLongitude(), item.getLatitude());
}

private Space getSpace(KoreaParkingResponse.Response.Body.Item item) {
return Space.of(item.getCapacity(), NO_PROVIDE);
}

private FreeOperatingTime getFreeOperatingTime(KoreaParkingResponse.Response.Body.Item item) {
if (item.getFeeInfo().equals(FREE)) {
return FreeOperatingTime.ALWAYS_FREE;
}
return FreeOperatingTime.ALWAYS_PAY;
}

private OperatingTime getOperatingTime(KoreaParkingResponse.Response.Body.Item item) {
return new OperatingTime(
toTimeInfo(item.getWeekDayBeginTime(), item.getWeekdayEndTime()),
toTimeInfo(item.getSaturdayBeginTime(), item.getSaturdayEndTime()),
toTimeInfo(item.getHolidayBeginTime(), item.getHolidayEndTime())
);
}

private TimeInfo toTimeInfo(String beginTime, String endTime) {
if (HOURS_00.equals(beginTime) && HOURS_24.equals(endTime)) {
return TimeInfo.ALL_DAY;
}
if (HOURS_00.equals(beginTime) && HOURS_00.equals(endTime)) {
return TimeInfo.CLOSED;
}
return new TimeInfo(parsingOperationTime(beginTime), parsingOperationTime(endTime));
}

private LocalTime parsingOperationTime(String time) {
if (time.equals(HOURS_24)) {
return LocalTime.MAX;
}
try {
return LocalTime.parse(time, TIME_FORMATTER);
} catch (DateTimeException e) {
return null;
}
}

private FeePolicy getFeePolicy(KoreaParkingResponse.Response.Body.Item item) {
return new FeePolicy(
Fee.from(item.getBaseFee()),
Fee.from(item.getExtraFee()),
TimeUnit.from(item.getBaseTimeUnit()),
TimeUnit.from(item.getExtraTimeUnit()),
Fee.from(item.getDayMaximumFee())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.parkingcomestrue.external.parkingapi.korea;

import com.parkingcomestrue.common.domain.parking.Parking;
import com.parkingcomestrue.external.parkingapi.ParkingApiService;
import java.net.URI;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.DefaultUriBuilderFactory;

@Component
public class KoreaParkingApiService implements ParkingApiService {

private static final String URL = "http://api.data.go.kr/openapi/tn_pubr_prkplce_info_api";
private static final String RESULT_TYPE = "json";
private static final int SIZE = 250;
private static final String NORMAL_RESULT_CODE = "00";

@Value("${korea-parking-key}")
private String API_KEY;

private final KoreaParkingAdapter adapter;
private final RestTemplate restTemplate;

public KoreaParkingApiService(KoreaParkingAdapter adapter,
@Qualifier("parkingApiRestTemplate") RestTemplate restTemplate) {
this.adapter = adapter;
this.restTemplate = restTemplate;
}

@Override
public List<Parking> read() throws Exception {
Set<KoreaParkingResponse> result = new HashSet<>();
for (int pageNumber = 1; ; pageNumber++) {
KoreaParkingResponse response = call(pageNumber, SIZE);
String resultCode = response.getResponse().getHeader().getResultCode();
if (NORMAL_RESULT_CODE.equals(resultCode)) {
result.add(response);
continue;
}
break;
}
return result.stream()
.flatMap(response -> adapter.convert(response).stream())
.toList();
}

private KoreaParkingResponse call(int startIndex, int size) {
URI uri = makeUri(startIndex, size);
ResponseEntity<KoreaParkingResponse> response = restTemplate.getForEntity(uri, KoreaParkingResponse.class);
return response.getBody();
}

private URI makeUri(int startIndex, int size) {
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(URL);
factory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY);
return factory.builder()
.queryParam("serviceKey", API_KEY)
.queryParam("pageNo", startIndex)
.queryParam("numOfRows", size)
.queryParam("type", RESULT_TYPE)
.build();
}
}
Loading
Loading