-
Notifications
You must be signed in to change notification settings - Fork 1
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: 학교버스 시간표 관련 API #1119
feat: 학교버스 시간표 관련 API #1119
Changes from 8 commits
5dba1f3
9303f98
87ae230
fe59526
19589fe
91c34cb
c835f5f
98f3b67
2fac59b
5c8bdad
0e8e060
7cc9836
ff355d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,12 +15,17 @@ | |
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.ShuttleBusRouteResponse; | ||
import in.koreatech.koin.domain.bus.dto.ShuttleBusRoutesResponse; | ||
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.enums.CityBusDirection; | ||
import in.koreatech.koin.domain.bus.repository.ShuttleBusRepository; | ||
import in.koreatech.koin.domain.bus.service.BusService; | ||
import in.koreatech.koin.domain.version.dto.VersionMessageResponse; | ||
import in.koreatech.koin.domain.version.service.VersionService; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
|
@@ -29,6 +34,8 @@ | |
public class BusController implements BusApi { | ||
|
||
private final BusService busService; | ||
private final ShuttleBusRepository shuttleBusRepository; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. db로부터 받은 데이터를 단순히 response dto에 매핑시키는 간단한 로직이라 service 단 거치지 않는 구조로 만드신 것 같습니다. 이 과정에서 contoller에 repository 의존성이 추가되었는데 확장성이나 유지보수 고려하면 간단하더라도 service단 한번 거치는게 좋지 않을까요? |
||
private final VersionService versionService; | ||
|
||
@GetMapping | ||
public ResponseEntity<BusRemainTimeResponse> getBusRemainTime( | ||
|
@@ -82,4 +89,16 @@ public ResponseEntity<List<SingleBusTimeResponse>> getSearchTimetable( | |
depart, arrival); | ||
return ResponseEntity.ok().body(singleBusTimeResponses); | ||
} | ||
|
||
@GetMapping("/courses/shuttle") | ||
public ResponseEntity<ShuttleBusRoutesResponse> getShuttleBusRoutes() { | ||
VersionMessageResponse version = versionService.getVersionWithMessage("shuttle_bus_timetable"); | ||
return ResponseEntity.ok() | ||
.body(ShuttleBusRoutesResponse.of(shuttleBusRepository.findBySemesterType(version.title()), version)); | ||
} | ||
|
||
@GetMapping("/timetable/shuttle") | ||
public ResponseEntity<ShuttleBusRouteResponse> getShuttleBusTimetable(@RequestParam String id) { | ||
return ResponseEntity.ok().body(ShuttleBusRouteResponse.from(shuttleBusRepository.getById(id))); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acontroller에서 repository에 직접 접근하고 있군요..! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. controller 입장에서 계층적으로 service는 가까운 친구이지만, repository는 멀리있는 친구이기 때문에 service만 알고 있는 것도 좋은 방법 같습니다. (repository에 직접 참조한다면 당장은 편하겠지만..) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package in.koreatech.koin.domain.bus.dto; | ||
|
||
import java.util.List; | ||
|
||
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; | ||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
import in.koreatech.koin.domain.bus.model.mongo.ShuttleBusRoute; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "셔틀버스 경로 응답 DTO") | ||
public record ShuttleBusRouteResponse( | ||
@Schema(description = "경로 ID", example = "675013f9465776d6265ddfdb") | ||
String id, | ||
|
||
@Schema(description = "지역 이름", example = "천안") | ||
String region, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aenum을 적용하는 건 어떻게 생각하시나요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. enum 사용 시, 정렬할 때 우선순위 값으로 활용할 수 있고 확장성이 향상되는 등 객체지향 기법에 적절하다고 생각해서 말씀하신대로 수정해보겠습니다👍 |
||
|
||
@Schema(description = "경로 타입", example = "순환") | ||
String routeType, | ||
|
||
@Schema(description = "경로 이름", example = "천안 셔틀") | ||
String routeName, | ||
|
||
@Schema(description = "경로 부가 이름", example = "null") | ||
String subName, | ||
|
||
@Schema(description = "노드 정보 목록") | ||
List<NodeInfoResponse> nodeInfo, | ||
|
||
@Schema(description = "경로 정보 목록") | ||
List<RouteInfoResponse> routeInfo | ||
) { | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "노드 정보") | ||
private record NodeInfoResponse( | ||
@Schema(description = "노드 이름", example = "캠퍼스 정문") | ||
String name, | ||
|
||
@Schema(description = "노드 세부 정보", example = "정문 앞 정류장") | ||
String detail | ||
) { | ||
} | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "경로 정보") | ||
private record RouteInfoResponse( | ||
@Schema(description = "경로 이름", example = "1회") | ||
String name, | ||
|
||
@Schema(description = "도착 시간 목록", example = "[\"08:00\", \"09:00\"]") | ||
List<String> arrivalTime | ||
) { | ||
} | ||
|
||
public static ShuttleBusRouteResponse from(ShuttleBusRoute shuttleBusRoute) { | ||
List<NodeInfoResponse> nodeInfoResponses = shuttleBusRoute.getNodeInfo().stream() | ||
.map(node -> new NodeInfoResponse(node.getName(), node.getDetail())) | ||
.toList(); | ||
List<RouteInfoResponse> routeInfoResponses = shuttleBusRoute.getRouteInfo().stream() | ||
.map(route -> new RouteInfoResponse(route.getName(), route.getArrivalTime())) | ||
.toList(); | ||
return new ShuttleBusRouteResponse( | ||
shuttleBusRoute.getId(), | ||
shuttleBusRoute.getRegion(), | ||
shuttleBusRoute.getRouteType(), | ||
shuttleBusRoute.getRouteName(), | ||
shuttleBusRoute.getSubName(), | ||
nodeInfoResponses, | ||
routeInfoResponses | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package in.koreatech.koin.domain.bus.dto; | ||
|
||
import static com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
|
||
import com.fasterxml.jackson.databind.annotation.JsonNaming; | ||
|
||
import in.koreatech.koin.domain.bus.model.mongo.ShuttleBusRoute; | ||
import in.koreatech.koin.domain.version.dto.VersionMessageResponse; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "셔틀버스 경로 응답") | ||
public record ShuttleBusRoutesResponse( | ||
@Schema(description = "노선 지역 분류 목록") | ||
List<RouteRegion> routeRegions, | ||
|
||
@Schema(description = "학기 정보") | ||
RouteSemester semesterInfo | ||
) { | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "노선 지역 정보") | ||
private record RouteRegion( | ||
@Schema(description = "지역 이름", example = "천안") String region, | ||
@Schema(description = "해당 지역의 경로 목록") List<RouteName> routes | ||
) implements Comparable<RouteRegion> { | ||
|
||
@Override | ||
public int compareTo(RouteRegion routeName) { | ||
return Integer.compare(getPriority(this.region), getPriority(routeName.region)); | ||
} | ||
|
||
private int getPriority(String region) { | ||
return switch (region) { | ||
case "천안" -> 1; | ||
case "청주" -> 2; | ||
case "서울" -> 3; | ||
default -> 4; | ||
}; | ||
} | ||
|
||
public static List<RouteRegion> mapCategories(List<ShuttleBusRoute> shuttleBusRoutes) { | ||
return shuttleBusRoutes.stream() | ||
.collect(Collectors.groupingBy(ShuttleBusRoute::getRegion)) | ||
.entrySet().stream() | ||
.map(entry -> new RouteRegion(entry.getKey(), RouteName.mapRouteNames(entry.getValue()))) | ||
.sorted() | ||
.toList(); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 데이터 전송 객체가 너무 많은 책임을 가지고 있는 것 같아요. service 사용하지 않은 이유가 궁금해요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
그런데 다시 생각해보니 말씀하신대로 데이터 전송 객체에 무거운 책임을 두는 것은 좋지 않은 생각인 것 같아요. |
||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "노선 세부 정보") | ||
private record RouteName( | ||
@Schema(description = "노선 ID", example = "675013f9465776d6265ddfdb") String id, | ||
@Schema(description = "노선 종류", example = "주말") String type, | ||
@Schema(description = "노선 이름", example = "대학원") String routeName, | ||
@Schema(description = "노선 부가 이름", example = "토요일") String subName | ||
) implements Comparable<RouteName> { | ||
|
||
@Override | ||
public int compareTo(RouteName routeName) { | ||
return Integer.compare(getPriority(this.type), getPriority(routeName.type)); | ||
} | ||
|
||
private int getPriority(String type) { | ||
return switch (type) { | ||
case "순환" -> 1; | ||
case "주중" -> 2; | ||
case "주말" -> 3; | ||
default -> 4; | ||
}; | ||
} | ||
|
||
public static List<RouteName> mapRouteNames(List<ShuttleBusRoute> routes) { | ||
return routes.stream() | ||
.map(route -> new RouteName(route.getId(), route.getRouteType(), route.getRouteName(), route.getSubName())) | ||
.sorted() | ||
.toList(); | ||
} | ||
} | ||
|
||
@JsonNaming(SnakeCaseStrategy.class) | ||
@Schema(description = "학기 정보") | ||
private record RouteSemester( | ||
@Schema(description = "학기 이름", example = "정규학기") String name, | ||
@Schema(description = "학기 기간", example = "2024-09-02 ~ 2024-12-20") String term | ||
) { | ||
|
||
public static RouteSemester from(VersionMessageResponse versionMessageResponse) { | ||
return new RouteSemester(versionMessageResponse.title(), | ||
versionMessageResponse.content()); | ||
} | ||
} | ||
|
||
public static ShuttleBusRoutesResponse of(List<ShuttleBusRoute> shuttleBusRoutes, | ||
VersionMessageResponse versionMessageResponse) { | ||
List<RouteRegion> categories = RouteRegion.mapCategories(shuttleBusRoutes); | ||
RouteSemester routeSemester = RouteSemester.from(versionMessageResponse); | ||
return new ShuttleBusRoutesResponse(categories, routeSemester); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package in.koreatech.koin.domain.bus.model.mongo; | ||
|
||
import static lombok.AccessLevel.PROTECTED; | ||
|
||
import java.util.List; | ||
|
||
import org.springframework.data.annotation.Id; | ||
import org.springframework.data.mongodb.core.mapping.Document; | ||
import org.springframework.data.mongodb.core.mapping.Field; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
|
||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor(access = PROTECTED) | ||
@Document(collection = "shuttlebus_timetables") | ||
public class ShuttleBusRoute { | ||
|
||
@Id | ||
private String id; | ||
|
||
@Field("semester_type") | ||
private String semesterType; | ||
|
||
@Field("region") | ||
private String region; | ||
|
||
@Field("route_type") | ||
private String routeType; | ||
|
||
@Field("route_name") | ||
private String routeName; | ||
|
||
@Field("sub_name") | ||
private String subName; | ||
|
||
@Field("node_info") | ||
private List<NodeInfo> nodeInfo; | ||
|
||
@Field("route_info") | ||
private List<RouteInfo> routeInfo; | ||
|
||
@Getter | ||
@NoArgsConstructor(access = PROTECTED) | ||
public static class NodeInfo { | ||
|
||
@Field("name") | ||
private String name; | ||
|
||
@Field("detail") | ||
private String detail; | ||
} | ||
|
||
@Getter | ||
@NoArgsConstructor(access = PROTECTED) | ||
public static class RouteInfo { | ||
|
||
@Field("name") | ||
private String name; | ||
|
||
@Field("running_days") | ||
@JsonIgnore | ||
private List<String> runningDays; | ||
|
||
@Field("arrival_time") | ||
private List<String> arrivalTime; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package in.koreatech.koin.domain.bus.repository; | ||
|
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import org.bson.types.ObjectId; | ||
import org.springframework.data.repository.Repository; | ||
|
||
import in.koreatech.koin.domain.bus.exception.BusNotFoundException; | ||
import in.koreatech.koin.domain.bus.model.mongo.ShuttleBusRoute; | ||
|
||
public interface ShuttleBusRepository extends Repository<ShuttleBusRoute, ObjectId> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AObjectId는 무슨 자료형인가요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
List<ShuttleBusRoute> findBySemesterType(String semesterType); | ||
|
||
Optional<ShuttleBusRoute> findById(String id); | ||
|
||
default ShuttleBusRoute getById(String id) { | ||
return findById(id).orElseThrow( | ||
() -> BusNotFoundException.withDetail("id: " + id)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id에 무슨 내용이 들어와야 하는지
@Opreation(description = "")
로 적어주시면 api 문서 안봐도 되서 편할 것 같아요