Skip to content

Commit

Permalink
Merge pull request #27 from aalises/aalises/create-bike-queries
Browse files Browse the repository at this point in the history
feat(queries): create BikeStations query
  • Loading branch information
aalises authored Nov 14, 2020
2 parents 75b6f44 + 5ed07a6 commit 1ea8283
Show file tree
Hide file tree
Showing 17 changed files with 876 additions and 14 deletions.
97 changes: 94 additions & 3 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ schema {

"""Root Query"""
type RootQuery {
metroStations(after: String, first: Int, before: String, last: Int, filterBy: FilterByInput): MetroStations
metroStations(after: String, first: Int, before: String, last: Int, filterBy: FilterByInputMetro): MetroStations
metroStation(findBy: FindByInput!): MetroStation
metroLine(findBy: FindByInput!): MetroLine
metroLines(after: String, first: Int, before: String, last: Int): MetroLines
bikeStations(after: String, first: Int, before: String, last: Int, filterBy: FilterByInputBike): BikeStations
}

"""Information about the metro stations of the city of Barcelona"""
Expand Down Expand Up @@ -75,9 +76,9 @@ type Coordinates {
}

"""
Input for the filterBy argument of the queries, which allows filtering a connection by some parameters (e.g. lineName or lineId)
Input for the filterBy argument of the metro queries, which allows filtering a connection by some parameters (e.g. lineName or lineId)
"""
input FilterByInput {
input FilterByInputMetro {
lineId: Int
lineName: String
}
Expand Down Expand Up @@ -137,3 +138,93 @@ type MetroLineEdge {
"""A cursor for use in pagination"""
cursor: String!
}

"""
Information about the public bike stations (SMOU) of the city of Barcelona
"""
type BikeStations {
"""Connection with the data about bike stations"""
stations: BikeStationConnection
}

"""A connection to a list of items."""
type BikeStationConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!

"""A list of edges."""
edges: [BikeStationEdge]
}

"""An edge in a connection."""
type BikeStationEdge {
"""The item at the end of the edge"""
node: BikeStation

"""A cursor for use in pagination"""
cursor: String!
}

"""Bike station information"""
type BikeStation {
"""Unique ID of the station"""
id: ID

"""Status of the station e.g. IN_SERVICE"""
status: BikeStationStatus

"""Last updated information timestamp (in ms since epoch)"""
lastUpdated: Int

"""Name of the station"""
name: String

"""Total number of bikes the station has"""
capacity: Int

"""Location coordinates of the station"""
location: Coordinates

"""Information about the available bikes and docks of the station"""
available: BikeStationAvailabilityInfo
}

enum BikeStationStatus {
IN_SERVICE
MAINTENANCE
CLOSED
}

"""Information about the available bikes and docks of the station"""
type BikeStationAvailabilityInfo {
"""Number of available bikes in the station by type"""
bikes: BikeAvailabilityInfo

"""Number of available docks in the station"""
docks: Int
}

"""Information of the bike availability of a station by type"""
type BikeAvailabilityInfo {
"""Number of available electrical bikes in the station"""
electrical: Int

"""Number of available mechanical bikes in the station"""
mechanical: Int

"""Total number of available bikes in the station"""
total: Int
}

"""
Input for the filterBy argument of the bikes queries, which allows filtering a connection by some parameters (e.g. only with available bikes)
"""
input FilterByInputBike {
only: OnlyFilterByInputBike
}

input OnlyFilterByInputBike {
hasAvailableBikes: Boolean
hasAvailableElectricalBikes: Boolean
isInService: Boolean
}
3 changes: 3 additions & 0 deletions src/RootQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import metroStations from "./queries/MetroStationsQuery";
import metroLine from "./queries/MetroLineQuery";
import metroLines from "./queries/MetroLinesQuery";

import bikeStations from "./queries/BikeStationsQuery";

export default new GraphQLObjectType({
name: "RootQuery",
description: "Root Query",
Expand All @@ -14,5 +16,6 @@ export default new GraphQLObjectType({
metroStation,
metroLine,
metroLines,
bikeStations,
},
});
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const TMB_API_BASE_URL = "https://api.tmb.cat/v1/transit";
export const SMOU_API_BASE_URL = "https://api.bsmsa.eu/ext/api/bsm/gbfs/v2/en";
124 changes: 124 additions & 0 deletions src/datasources/BikeDataSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { RESTDataSource } from "apollo-datasource-rest";
import { SMOU_API_BASE_URL } from "../config";
import { ApolloError } from "apollo-server-lambda";
import type {
FilterByInputBike,
BikeStation as BikeStationType,
BikeStationStatus as BikeStationStatusType,
} from "../../types";

interface StationInfo {
address: string;
altitude: number;
capacity: number;
lat: number;
lon: number;
name: string;
nearby_distance: number;
physical_configuration: "MECHANICBIKESTATION" | "ELECTRICBIKESTATION";
post_code: string;
station_id: number;
}

interface StationStatus {
station_id: number;
is_charging_station: boolean;
is_installed: 1 | 0;
is_renting: 1 | 0;
is_returning: 1 | 0;
last_reported: number;
num_bikes_available: number;
num_bikes_available_types: {
ebike: number;
mechanical: number;
};
num_docks_available: number;
status: "IN_SERVICE" | "MAINTENANCE" | "CLOSED";
}

interface StationInfoAPIResponse {
last_updated: number;
ttl: number;
data: {
stations: ReadonlyArray<StationInfo>;
};
}

interface StationStatusAPIResponse {
last_updated: number;
ttl: number;
data: {
stations: ReadonlyArray<StationStatus>;
};
}

export default class BikeDataSource extends RESTDataSource {
constructor() {
super();
this.baseURL = SMOU_API_BASE_URL;
}

bikeStationReducer(
stationInfoData: StationInfo | null,
stationStatusData: StationStatus | null
): BikeStationType {
const reducedStationInfo = {
id: String(stationInfoData?.station_id) ?? null,
name: stationInfoData?.name ?? null,
capacity: stationInfoData?.capacity ?? null,
location: {
latitude: stationInfoData?.lat ?? null,
longitude: stationInfoData?.lon ?? null,
altitude: stationInfoData?.altitude ?? null,
},
};

const reducedStationStatus = {
status: (stationStatusData?.status as BikeStationStatusType) ?? null,
lastUpdated: stationStatusData?.last_reported ?? null,
available: {
bikes: {
electrical:
stationStatusData?.num_bikes_available_types?.ebike ?? null,
mechanical:
stationStatusData?.num_bikes_available_types?.mechanical ?? null,
total: stationStatusData?.num_bikes_available ?? null,
},
docks: stationStatusData?.num_docks_available ?? null,
},
};

return {
...reducedStationInfo,
...reducedStationStatus,
};
}

async getAllStations(): Promise<BikeStationType[] | Error> {
const stationInfoResponse: StationInfoAPIResponse | null = await this.get(
"station_information"
);
const stationStatusResponse: StationStatusAPIResponse | null = await this.get(
"station_status"
);

if (!stationInfoResponse && !stationStatusResponse) {
return new ApolloError(
"There was an error fetching the bike stations information"
);
}

return (
stationInfoResponse?.data?.stations?.map(
(stationInfoData: StationInfo | null) => {
const stationStatusData: StationStatus | null =
stationStatusResponse?.data?.stations?.find(
({ station_id }) => station_id === stationInfoData?.station_id
) ?? null;

return this.bikeStationReducer(stationInfoData, stationStatusData);
}
) ?? []
);
}
}
1 change: 0 additions & 1 deletion src/datasources/MetroDataSource.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import TmbApiDataSource from "./TmbApiDataSource";
import type {
FindByInput,
FilterByInput,
MetroStation as MetroStationType,
MetroLine as MetroLineType,
} from "../../types";
Expand Down
Loading

0 comments on commit 1ea8283

Please sign in to comment.