Skip to content

Commit b8711b2

Browse files
Choon0414Choi-JJunho
authored andcommitted
feat : 버스 버저닝 V2 updated_at 추가 (#446)
* feat : 버스 timetable v2 추가 * feat : 버스 버저닝 timetable/v2 서비스 구현 * test : 버스 시간표 조회 v2 테스트 추가 * chore : 버스 v2 테스트 수정 * chore : 사용하지 않는 import 제거 * chore : log() 제거 * chore : 스웨거 설명 추가 (cherry picked from commit f778f5e)
1 parent 7da8d99 commit b8711b2

File tree

5 files changed

+133
-9
lines changed

5 files changed

+133
-9
lines changed

src/main/java/in/koreatech/koin/domain/bus/controller/BusApi.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
1212
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
13+
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
1314
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
1415
import in.koreatech.koin.domain.bus.model.BusTimetable;
1516
import in.koreatech.koin.domain.bus.model.enums.BusStation;
@@ -48,6 +49,14 @@ ResponseEntity<List<? extends BusTimetable>> getBusTimetable(
4849
@RequestParam(value = "region") String region
4950
);
5051

52+
@Operation(summary = "버스 시간표 조회")
53+
@GetMapping("/timetable/v2")
54+
ResponseEntity<BusTimetableResponse> getBusTimetableV2(
55+
@RequestParam(value = "bus_type") BusType busType,
56+
@RequestParam(value = "direction") String direction,
57+
@RequestParam(value = "region") String region
58+
);
59+
5160
@ApiResponses(
5261
value = {
5362
@ApiResponse(responseCode = "200"),

src/main/java/in/koreatech/koin/domain/bus/controller/BusController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
1515
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
16+
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
1617
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
1718
import in.koreatech.koin.domain.bus.model.BusTimetable;
1819
import in.koreatech.koin.domain.bus.model.enums.BusStation;
@@ -46,6 +47,15 @@ public ResponseEntity<List<? extends BusTimetable>> getBusTimetable(
4647
return ResponseEntity.ok().body(busService.getBusTimetable(busType, direction, region));
4748
}
4849

50+
@GetMapping("/timetable/v2")
51+
public ResponseEntity<BusTimetableResponse> getBusTimetableV2(
52+
@RequestParam(value = "bus_type") BusType busType,
53+
@RequestParam(value = "direction") String direction,
54+
@RequestParam(value = "region") String region
55+
){
56+
return ResponseEntity.ok().body(busService.getBusTimetableWithUpdatedAt(busType, direction, region));
57+
}
58+
4959
@GetMapping("/courses")
5060
public ResponseEntity<List<BusCourseResponse>> getBusCourses() {
5161
return ResponseEntity.ok().body(busService.getBusCourses());
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package in.koreatech.koin.domain.bus.dto;
2+
3+
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
4+
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
5+
6+
import java.time.LocalDateTime;
7+
import java.util.List;
8+
9+
import com.fasterxml.jackson.databind.annotation.JsonNaming;
10+
11+
import in.koreatech.koin.domain.bus.model.BusTimetable;
12+
import io.swagger.v3.oas.annotations.media.Schema;
13+
14+
@JsonNaming(SnakeCaseStrategy.class)
15+
public record BusTimetableResponse(
16+
@Schema(description = "버스 시간표", example = """
17+
{
18+
"route_name": "주말(14시 35분)",
19+
"arrival_info": {
20+
"nodeName": "터미널(신세계 앞 횡단보도)",
21+
"arrivalTime": "14:35"
22+
}
23+
}
24+
""", requiredMode = NOT_REQUIRED)
25+
List<? extends BusTimetable> busTimetable,
26+
27+
@Schema(description = "업데이트 시각", example = "2024-04-20 18:00:00", requiredMode = NOT_REQUIRED)
28+
LocalDateTime updatedAt
29+
) {
30+
}

src/main/java/in/koreatech/koin/domain/bus/service/BusService.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
1818
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
19+
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
1920
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
2021
import in.koreatech.koin.domain.bus.exception.BusIllegalStationException;
2122
import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException;
@@ -31,6 +32,8 @@
3132
import in.koreatech.koin.domain.bus.repository.BusRepository;
3233
import in.koreatech.koin.domain.bus.util.CityBusOpenApiClient;
3334
import in.koreatech.koin.domain.bus.util.ExpressBusOpenApiClient;
35+
import in.koreatech.koin.domain.version.dto.VersionResponse;
36+
import in.koreatech.koin.domain.version.service.VersionService;
3437
import lombok.RequiredArgsConstructor;
3538

3639
@Service
@@ -42,6 +45,7 @@ public class BusService {
4245
private final BusRepository busRepository;
4346
private final CityBusOpenApiClient cityBusOpenApiClient;
4447
private final ExpressBusOpenApiClient expressBusOpenApiClient;
48+
private final VersionService versionService;
4549

4650
@Transactional
4751
public BusRemainTimeResponse getBusRemainTime(BusType busType, BusStation depart, BusStation arrival) {
@@ -178,6 +182,17 @@ public List<? extends BusTimetable> getBusTimetable(BusType busType, String dire
178182
throw new BusTypeNotFoundException(busType.name());
179183
}
180184

185+
public BusTimetableResponse getBusTimetableWithUpdatedAt(BusType busType, String direction, String region){
186+
List<? extends BusTimetable> BusTimetables = getBusTimetable(busType, direction, region);
187+
188+
if (busType.equals(BusType.COMMUTING)){
189+
busType = BusType.SHUTTLE;
190+
}
191+
192+
VersionResponse version = versionService.getVersion(busType.name().toLowerCase() + "_bus_timetable");
193+
return new BusTimetableResponse(BusTimetables, version.updatedAt());
194+
}
195+
181196
public List<BusCourseResponse> getBusCourses() {
182197
return busRepository.findAll().stream()
183198
.map(BusCourseResponse::from)

src/test/java/in/koreatech/koin/acceptance/BusApiTest.java

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import in.koreatech.koin.domain.bus.repository.ExpressBusCacheRepository;
4141
import in.koreatech.koin.domain.version.model.Version;
4242
import in.koreatech.koin.domain.version.repository.VersionRepository;
43+
import in.koreatech.koin.domain.version.service.VersionService;
4344
import in.koreatech.koin.support.JsonAssertions;
4445
import io.restassured.RestAssured;
4546
import io.restassured.response.ExtractableResponse;
@@ -59,6 +60,9 @@ class BusApiTest extends AcceptanceTest {
5960
@Autowired
6061
private ExpressBusCacheRepository expressBusCacheRepository;
6162

63+
@Autowired
64+
private VersionService versionService;
65+
6266
private final Instant UPDATED_AT = ZonedDateTime.parse(
6367
"2024-02-21 18:00:00 KST",
6468
ofPattern("yyyy-MM-dd " + "HH:mm:ss z")
@@ -137,11 +141,11 @@ void getNextShuttleBusRemainTime() {
137141
softly -> {
138142
softly.assertThat(response.body().jsonPath().getString("bus_type"))
139143
.isEqualTo(busType.name().toLowerCase());
140-
softly.assertThat((Long) response.body().jsonPath().get("now_bus.bus_number")).isNull();
144+
softly.assertThat((Long)response.body().jsonPath().get("now_bus.bus_number")).isNull();
141145
softly.assertThat(response.body().jsonPath().getLong("now_bus.remain_time")).isEqualTo(
142146
BusRemainTime.from(arrivalTime).getRemainSeconds(clock));
143-
softly.assertThat((Long) response.body().jsonPath().get("next_bus.bus_number")).isNull();
144-
softly.assertThat((Long) response.body().jsonPath().get("next_bus.remain_time")).isNull();
147+
softly.assertThat((Long)response.body().jsonPath().get("next_bus.bus_number")).isNull();
148+
softly.assertThat((Long)response.body().jsonPath().get("next_bus.remain_time")).isNull();
145149
}
146150
);
147151
}
@@ -200,8 +204,8 @@ void getNextCityBusRemainTimeRedis() {
200204
softly -> {
201205
softly.assertThat(response.body().jsonPath().getString("bus_type"))
202206
.isEqualTo(busType.name().toLowerCase());
203-
softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber);
204-
softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time"))
207+
softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(busNumber);
208+
softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time"))
205209
.isEqualTo(
206210
BusRemainTime.of(remainTime, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock));
207211
softly.assertThat(response.body().jsonPath().getObject("next_bus.bus_number", Long.class)).isNull();
@@ -303,12 +307,12 @@ void getNextCityBusRemainTimeOpenApi() {
303307
softly -> {
304308
softly.assertThat(response.body().jsonPath().getString("bus_type"))
305309
.isEqualTo(busType.name().toLowerCase());
306-
softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400);
307-
softly.assertThat((Long) response.body().jsonPath().getLong("now_bus.remain_time"))
310+
softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.bus_number")).isEqualTo(400);
311+
softly.assertThat((Long)response.body().jsonPath().getLong("now_bus.remain_time"))
308312
.isEqualTo(
309313
BusRemainTime.of(600L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock));
310-
softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405);
311-
softly.assertThat((Long) response.body().jsonPath().getLong("next_bus.remain_time"))
314+
softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.bus_number")).isEqualTo(405);
315+
softly.assertThat((Long)response.body().jsonPath().getLong("next_bus.remain_time"))
312316
.isEqualTo(
313317
BusRemainTime.of(800L, version.getUpdatedAt().toLocalTime()).getRemainSeconds(clock));
314318
}
@@ -470,4 +474,60 @@ void getShuttleBusTimetable() {
470474
]
471475
""");
472476
}
477+
478+
@Test
479+
@DisplayName("셔틀버스 시간표를 조회한다(업데이트 시각 포함).")
480+
void getShuttleBusTimetableWithUpdatedAt() {
481+
when(dateTimeProvider.getNow()).thenReturn(Optional.of(UPDATED_AT));
482+
483+
Version version = Version.builder()
484+
.version("20240_1712920946")
485+
.type("shuttle_bus_timetable")
486+
.build();
487+
versionRepository.save(version);
488+
489+
BusType busType = BusType.from("shuttle");
490+
String direction = "from";
491+
String region = "천안";
492+
493+
ExtractableResponse<Response> response = RestAssured
494+
.given()
495+
.when()
496+
.param("bus_type", busType.name().toLowerCase())
497+
.param("direction", direction)
498+
.param("region", region)
499+
.get("/bus/timetable/v2")
500+
.then()
501+
.statusCode(HttpStatus.OK.value())
502+
.extract();
503+
504+
JsonAssertions.assertThat(response.asPrettyString())
505+
.isEqualTo(String.format("""
506+
{
507+
"bus_timetable": [
508+
{
509+
"route_name": "주중",
510+
"arrival_info": [
511+
{
512+
"nodeName": "한기대",
513+
"arrivalTime": "18:10"
514+
},
515+
{
516+
"nodeName": "신계초,운전리,연춘리",
517+
"arrivalTime": "정차"
518+
},
519+
{
520+
"nodeName": "천안역(학화호두과자)",
521+
"arrivalTime": "18:50"
522+
},{
523+
"nodeName": "터미널(신세계 앞 횡단보도)",
524+
"arrivalTime": "18:55"
525+
}
526+
]
527+
}
528+
],
529+
"updated_at": %s
530+
}
531+
""", version.getUpdatedAt()));
532+
}
473533
}

0 commit comments

Comments
 (0)