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: 시내버스 시간표 #714

Merged
merged 6 commits into from
Jul 19, 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 @@ -11,11 +11,12 @@
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.CityBusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
import in.koreatech.koin.domain.bus.model.BusTimetable;
import in.koreatech.koin.domain.bus.model.enums.BusStation;
import in.koreatech.koin.domain.bus.model.enums.BusType;
import in.koreatech.koin.domain.bus.model.express.TmoneyOpenApiExpressBusArrival;
import in.koreatech.koin.domain.bus.model.enums.CityBusDirection;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand Down Expand Up @@ -58,6 +59,13 @@ ResponseEntity<BusTimetableResponse> getBusTimetableV2(
@RequestParam(value = "region") String region
);

@Operation(summary = "시내버스 시간표 조회")
@GetMapping("/timetable/city")
ResponseEntity<CityBusTimetableResponse> getCityBusTimetable(
@RequestParam(value = "bus_number") Long bus_number,
@RequestParam(value = "direction") CityBusDirection direction
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.CityBusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
import in.koreatech.koin.domain.bus.model.BusTimetable;
import in.koreatech.koin.domain.bus.model.enums.BusStation;
import in.koreatech.koin.domain.bus.model.enums.BusType;
import in.koreatech.koin.domain.bus.model.express.TmoneyOpenApiExpressBusArrival;
import in.koreatech.koin.domain.bus.model.enums.CityBusDirection;
import in.koreatech.koin.domain.bus.service.BusService;
import lombok.RequiredArgsConstructor;

Expand Down Expand Up @@ -57,6 +58,14 @@ public ResponseEntity<BusTimetableResponse> getBusTimetableV2(
return ResponseEntity.ok().body(busService.getBusTimetableWithUpdatedAt(busType, direction, region));
}

@GetMapping("/timetable/city")
public ResponseEntity<CityBusTimetableResponse> getCityBusTimetable(
@RequestParam(value = "bus_number") Long busNumber,
@RequestParam(value = "direction") CityBusDirection direction
) {
return ResponseEntity.ok().body(busService.getCityBusTimetable(busNumber, direction));
}

@GetMapping("/courses")
public ResponseEntity<List<BusCourseResponse>> getBusCourses() {
return ResponseEntity.ok().body(busService.getBusCourses());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package in.koreatech.koin.domain.bus.dto;

import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.NOT_REQUIRED;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import java.time.LocalDateTime;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

import in.koreatech.koin.domain.bus.model.mongo.CityBusTimetable;
import io.swagger.v3.oas.annotations.media.Schema;

@JsonNaming(SnakeCaseStrategy.class)
public record CityBusTimetableResponse(
@Schema(description = "업데이트 시각", example = "2024-07-18 18:00:00", requiredMode = NOT_REQUIRED)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
LocalDateTime updatedAt,

@Schema(description = "버스 정보", example = """
{
"number": 400,
"depart_node": "병천3리",
"arrival_node": "종합터미널"
}
""", requiredMode = REQUIRED)
BusInfo busInfo,

@Schema(description = "버스 시간표", example = """
[
{
"day_of_week": "평일",
"depart_info": ["06:00", "13:12", "22:30"]
},
{
"day_of_week": "주말",
"depart_info": ["06:00", "13:12", "22:30"]
},
{
"day_of_week": "공휴일",
"depart_info": ["06:00", "13:12", "22:30"]
},
{
"day_of_week": "임시",
"depart_info": ["06:00", "13:12", "22:30"]
}
]
""", requiredMode = NOT_REQUIRED)
List<BusTimetable> busTimetables
) {

public static CityBusTimetableResponse from(CityBusTimetable timetable) {
return new CityBusTimetableResponse(
timetable.getUpdatedAt(),
BusInfo.from(timetable.getBusInfo()),
timetable.getBusTimetables().stream()
.map(BusTimetable::from)
.toList()
);
}

@JsonNaming(SnakeCaseStrategy.class)
public record BusInfo(
Long number,
String departNode,
String arrivalNode
) {
public static BusInfo from(CityBusTimetable.BusInfo busInfo) {
return new BusInfo(
busInfo.getNumber(),
busInfo.getDepart(),
busInfo.getArrival()
);
}
}

@JsonNaming(SnakeCaseStrategy.class)
public record BusTimetable(
String dayOfWeek,
List<String> departInfo
) {
public static BusTimetable from(CityBusTimetable.BusTimetable timetable) {
return new BusTimetable(
timetable.getDayOfWeek(),
timetable.getDepartInfo()
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package in.koreatech.koin.domain.bus.model.enums;

import java.util.Arrays;

import com.fasterxml.jackson.annotation.JsonCreator;

import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException;

public enum CityBusDirection {
종합터미널,
병천3리,
황사동,
유관순열사사적지
;

@JsonCreator
public static CityBusDirection from(String direction) {
return Arrays.stream(values())
.filter(direct -> direct.name().equalsIgnoreCase(direction))
.findAny()
.orElseThrow(() -> BusTypeNotFoundException.withDetail("busDirection: " + direction));
}

public String getName() {
return this.name().toLowerCase();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package in.koreatech.koin.domain.bus.model.mongo;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;

import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Document(collection = "citybus_timetables")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CityBusTimetable {

@Id
@Field("_id")
private String routeId;

@Field("bus_info")
private BusInfo busInfo;

@Field("bus_timetables")
private List<BusTimetable> busTimetables = new ArrayList<>();

@Field("updated_at")
private LocalDateTime updatedAt;

@Builder
private CityBusTimetable(BusInfo busInfo, List<BusTimetable> busTimetables, LocalDateTime updatedAt) {
this.busInfo = busInfo;
this.busTimetables = busTimetables;
this.updatedAt = updatedAt;
}

@Getter
public static class BusInfo {

@Field("number")
private Long number;

@Field("depart_node")
private String depart;

@Field("arrival_node")
private String arrival;

@Builder
private BusInfo(Long number, String depart, String arrival) {
this.number = number;
this.depart = depart;
this.arrival = arrival;
}
}

@Getter
public static class BusTimetable {
@Field("day_of_week")
private String dayOfWeek;

@Field("depart_info")
private List<String> departInfo;

@Builder
private BusTimetable(String dayOfWeek, List<String> departInfo) {
this.dayOfWeek = dayOfWeek;
this.departInfo = departInfo;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package in.koreatech.koin.domain.bus.repository;

import java.util.Optional;

import org.springframework.data.repository.Repository;

import in.koreatech.koin.domain.bus.exception.BusCacheNotFoundException;
import in.koreatech.koin.domain.bus.model.mongo.CityBusTimetable;

public interface CityBusTimetableRepository extends Repository<CityBusTimetable, String> {

CityBusTimetable save(CityBusTimetable cityBusTimetable);

Optional<CityBusTimetable> findByBusInfoNumberAndBusInfoArrival(Long number, String arrivalNode);

default CityBusTimetable getByBusInfoNumberAndBusInfoArrival(Long number, String arrivalNode) {
return findByBusInfoNumberAndBusInfoArrival(number, arrivalNode)
.orElseThrow(() -> BusCacheNotFoundException.withDetail("number: " + number + ", direction: " + arrivalNode + " 기점 방향"));
}
}
12 changes: 12 additions & 0 deletions src/main/java/in/koreatech/koin/domain/bus/service/BusService.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import in.koreatech.koin.domain.bus.dto.BusCourseResponse;
import in.koreatech.koin.domain.bus.dto.BusRemainTimeResponse;
import in.koreatech.koin.domain.bus.dto.BusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.CityBusTimetableResponse;
import in.koreatech.koin.domain.bus.dto.SingleBusTimeResponse;
import in.koreatech.koin.domain.bus.exception.BusIllegalStationException;
import in.koreatech.koin.domain.bus.exception.BusTypeNotFoundException;
Expand All @@ -30,9 +31,12 @@
import in.koreatech.koin.domain.bus.model.enums.BusDirection;
import in.koreatech.koin.domain.bus.model.enums.BusStation;
import in.koreatech.koin.domain.bus.model.enums.BusType;
import in.koreatech.koin.domain.bus.model.enums.CityBusDirection;
import in.koreatech.koin.domain.bus.model.mongo.BusCourse;
import in.koreatech.koin.domain.bus.model.mongo.CityBusTimetable;
import in.koreatech.koin.domain.bus.model.mongo.Route;
import in.koreatech.koin.domain.bus.repository.BusRepository;
import in.koreatech.koin.domain.bus.repository.CityBusTimetableRepository;
import in.koreatech.koin.domain.bus.util.CityBusClient;
import in.koreatech.koin.domain.bus.util.CityBusRouteClient;
import in.koreatech.koin.domain.bus.util.TmoneyExpressBusClient;
Expand All @@ -48,6 +52,7 @@ public class BusService {

private final Clock clock;
private final BusRepository busRepository;
private final CityBusTimetableRepository cityBusTimetableRepository;
private final CityBusClient cityBusClient;
private final TmoneyExpressBusClient tmoneyExpressBusClient;
private final CityBusRouteClient cityBusRouteClient;
Expand Down Expand Up @@ -217,4 +222,11 @@ public List<BusCourseResponse> getBusCourses() {
.map(BusCourseResponse::from)
.toList();
}

public CityBusTimetableResponse getCityBusTimetable(Long busNumber, CityBusDirection direction) {
CityBusTimetable timetable = cityBusTimetableRepository
.getByBusInfoNumberAndBusInfoArrival(busNumber, direction.getName());

return CityBusTimetableResponse.from(timetable);
}
}
43 changes: 37 additions & 6 deletions src/test/java/in/koreatech/koin/acceptance/BusApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class BusApiTest extends AcceptanceTest {
@BeforeEach
void setup() {
busFixture.버스_시간표_등록();
busFixture.시내버스_시간표_등록();
when(cityBusClient.getOpenApiResponse(anyString())).thenReturn("""
{
"response": {
Expand Down Expand Up @@ -373,16 +374,46 @@ void getSearchTimetable() {
@Test
@DisplayName("시내버스 시간표를 조회한다 - 지원하지 않음")
void getCityBusTimetable() {
RestAssured
Version version = Version.builder()
.version("test_version")
.type(VersionType.CITY.getValue())
.build();
versionRepository.save(version);

Long busNumber = 400L;
String direction = "종합터미널";

var response = RestAssured
.given()
.when()
.param("bus_type", "city")
.param("direction", "to")
.param("region", "천안")
.get("/bus/timetable")
.param("bus_number", busNumber)
.param("direction", direction)
.get("/bus/timetable/city")
.then()
.statusCode(HttpStatus.BAD_REQUEST.value())
.statusCode(HttpStatus.OK.value())
.extract();

JsonAssertions.assertThat(response.asPrettyString())
.isEqualTo("""
{
"bus_info": {
"arrival_node": "종합터미널",
"depart_node": "병천3리",
"number": 400
},
"bus_timetables": [
{
"day_of_week": "평일",
"depart_info": ["06:00", "07:00"]
},
{
"day_of_week": "주말",
"depart_info": ["08:00", "09:00"]
}
],
"updated_at": "2024-07-19 19:00:00"
}
""");
}

@Test
Expand Down
Loading
Loading