diff --git a/src/loaders/ApiBasedRepositoryLoader.ts b/src/loaders/ApiBasedRepositoryLoader.ts new file mode 100644 index 0000000..964dac0 --- /dev/null +++ b/src/loaders/ApiBasedRepositoryLoader.ts @@ -0,0 +1,268 @@ +import { GetterSetterRepository } from "../repositories/GetterSetterRepository"; +import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities"; + +const systemIdsToSupport = ["263"]; +const baseUrl = "https://passiogo.com/mapGetData.php"; + +export class ApiBasedRepositoryLoader { + constructor( + protected repository: GetterSetterRepository, + ) { + } + + protected async fetchAndUpdateSystemData() { + const params = { + getSystems: "2", + }; + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${baseUrl}?${query}`); + const json = await response.json() + + if (typeof json.all === "object") { + // filter down to supported systems + const filteredSystems = json.all.filter((jsonSystem: any) => systemIdsToSupport.includes(jsonSystem.id)); + await Promise.all(filteredSystems.map(async (system: any) => { + const constructedSystem: ISystem = { + id: system.id, + name: system.fullname, + }; + + await this.repository.addOrUpdateSystem(constructedSystem); + })); + } + } + + protected async fetchAndUpdateRouteDataForExistingSystems() { + const systems = await this.repository.getSystems(); + await Promise.all(systems.map(async (system) => { + const params = { + getRoutes: "2", + }; + + const formDataJsonObject = { + "systemSelected0": system.id, + "amount": "1", + } + const formData = new FormData(); + formData.set("json", JSON.stringify(formDataJsonObject)); + + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${baseUrl}?${query}`, { + method: "POST", + body: formData, + }); + const json = await response.json(); + + if (typeof json.all === "object") { + await Promise.all(json.all.map(async (jsonRoute: any) => { + const constructedRoute: IRoute = { + name: jsonRoute.name, + color: jsonRoute.color, + id: jsonRoute.myid, + polylineCoordinates: [], + systemId: system.id, + }; + + await this.repository.addOrUpdateRoute(constructedRoute); + })) + } + })); + } + + protected async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems() { + // Fetch from the API + // Pass JSON output into two different methods to update repository + const systems = await this.repository.getSystems(); + await Promise.all(systems.map(async (system: ISystem) => { + const params = { + getStops: "2", + }; + + const formDataJsonObject = { + "s0": system.id, + "sA": 1 + }; + const formData = new FormData(); + formData.set("json", JSON.stringify(formDataJsonObject)); + + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${baseUrl}?${query}`, { + method: "POST", + body: formData, + }); + const json = await response.json(); + + await this.updateStopDataForSystemAndApiResponse(system, json); + await this.updateOrderedStopDataForExistingStops(json); + await this.updatePolylineDataForExistingRoutesAndApiResponse(json); + })); + } + + protected async fetchAndUpdateShuttleDataForExistingSystems() { + const systems = await this.repository.getSystems(); + await Promise.all(systems.map(async (system: ISystem) => { + const params = { + getBuses: "2" + }; + + const formDataJsonObject = { + "s0": system.id, + "sA": "1" + }; + + const formData = new FormData(); + formData.set("json", JSON.stringify(formDataJsonObject)); + + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${baseUrl}?${query}`, { + method: "POST", + body: formData, + }); + const json = await response.json(); + + if (json.buses && json.buses["-1"] === undefined) { + const jsonBuses = Object.values(json.buses).map((busesArr: any) => { + return busesArr[0]; + }); + + await Promise.all(jsonBuses.map(async (jsonBus: any) => { + const constructedShuttle: IShuttle = { + name: jsonBus.bus, + coordinates: { + latitude: parseFloat(jsonBus.latitude), + longitude: parseFloat(jsonBus.longitude), + }, + routeId: jsonBus.routeId, + systemId: system.id, + id: `${jsonBus.busId}` + } + + await this.repository.addOrUpdateShuttle(constructedShuttle); + })) + } + })); + } + + protected async fetchAndUpdateEtaDataForExistingOrderedStops() { + // TODO implement once I figure out how to associate ETA data with shuttles + + const systems = await this.repository.getSystems() + await Promise.all(systems.map(async (system: ISystem) => { + const stops = await this.repository.getStopsBySystemId(system.id); + await Promise.all(stops.map(async (stop) => { + const params = { + eta: "3", + stopIds: stop.id, + }; + + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${baseUrl}?${query}`, { + method: "GET", + }); + const json = await response.json(); + + if (json.ETAs && json.ETAs[stop.id]) { + // Continue with the parsing + json.ETAs[stop.id].forEach((jsonEta: any) => { + // Update cache + const shuttleId: string = jsonEta.busId; + + const eta: IEta = { + secondsRemaining: jsonEta.secondsSpent, + shuttleId: `${shuttleId}`, + stopId: stop.id, + millisecondsSinceEpoch: Date.now(), + }; + + this.repository.addOrUpdateEta(eta); + }); + } + })); + })) + } + + protected async updateStopDataForSystemAndApiResponse(system: ISystem, json: any) { + if (json.stops) { + const jsonStops = Object.values(json.stops); + + await Promise.all(jsonStops.map(async (stop: any) => { + const constructedStop: IStop = { + name: stop.name, + id: stop.id, + systemId: system.id, + coordinates: { + latitude: parseFloat(stop.latitude), + longitude: parseFloat(stop.longitude), + }, + }; + + await this.repository.addOrUpdateStop(constructedStop); + })); + } + } + + protected async updateOrderedStopDataForExistingStops(json: any) { + if (json.routes) { + await Promise.all(Object.keys(json.routes).map(async (routeId) => { + const jsonOrderedStopData: any[][] = json.routes[routeId].slice(2); + + // The API may return the same stop twice to indicate that + // the route runs in a loop + // If the API does not do this, assume the route is one-way + // To account for this, check if ordered stop already exists in repo + // If it does, write to that stop instead + + for (let index = 0; index < jsonOrderedStopData.length; index++) { + const orderedStopDataArray = jsonOrderedStopData[index]; + + const stopId = orderedStopDataArray[1]; + let constructedOrderedStop = await this.repository.getOrderedStopByRouteAndStopId(routeId, stopId) + if (constructedOrderedStop === null) { + constructedOrderedStop = { + routeId, + stopId, + position: index + 1, + }; + } + + if (index >= 1) { + constructedOrderedStop.previousStop = { + routeId, + stopId: jsonOrderedStopData[index - 1][1], + position: index, + }; + } + if (index < jsonOrderedStopData.length - 1) { + constructedOrderedStop.nextStop = { + routeId, + stopId: jsonOrderedStopData[index + 1][1], + position: index + 2, + }; + } + + await this.repository.addOrUpdateOrderedStop(constructedOrderedStop); + } + })); + } + } + + protected async updatePolylineDataForExistingRoutesAndApiResponse(json: any) { + if (json.routePoints) { + await Promise.all(Object.keys(json.routePoints).map(async (routeId) => { + const routePoints = json.routePoints[routeId][0]; + + const existingRoute = await this.repository.getRouteById(routeId); + if (!existingRoute) return; + + existingRoute.polylineCoordinates = routePoints.map((point: any) => { + return { + latitude: parseFloat(point.lat), + longitude: parseFloat(point.lng), + }; + }); + + await this.repository.addOrUpdateRoute(existingRoute); + })) + } + } +} \ No newline at end of file diff --git a/src/loaders/TimedApiBasedRepositoryLoader.ts b/src/loaders/TimedApiBasedRepositoryLoader.ts index 4b92ce3..6fd40f4 100644 --- a/src/loaders/TimedApiBasedRepositoryLoader.ts +++ b/src/loaders/TimedApiBasedRepositoryLoader.ts @@ -1,9 +1,8 @@ import { GetterSetterRepository } from "../repositories/GetterSetterRepository"; import { IEta, IRoute, IShuttle, IStop, ISystem } from "../entities/entities"; +import { ApiBasedRepositoryLoader } from "./ApiBasedRepositoryLoader"; const timeout = 10000; -const systemIdsToSupport = ["263"]; -const baseUrl = "https://passiogo.com/mapGetData.php"; // Ideas to break this into smaller pieces in the future: // Have one repository data loader running for each supported system @@ -19,13 +18,14 @@ const baseUrl = "https://passiogo.com/mapGetData.php"; // - OrderedStops: reload every few minutes // - Systems: reload once a day -export class TimedApiBasedRepositoryLoader { +export class TimedApiBasedRepositoryLoader extends ApiBasedRepositoryLoader { private shouldBeRunning: boolean = false; private timer: any; constructor( - private repository: GetterSetterRepository, + repository: GetterSetterRepository, ) { + super(repository); this.startFetchDataAndUpdate = this.startFetchDataAndUpdate.bind(this); } @@ -64,259 +64,4 @@ export class TimedApiBasedRepositoryLoader { this.timer = setTimeout(this.startFetchDataAndUpdate, timeout); } - private async fetchAndUpdateSystemData() { - const params = { - getSystems: "2", - }; - const query = new URLSearchParams(params).toString(); - const response = await fetch(`${baseUrl}?${query}`); - const json = await response.json() - - if (typeof json.all === "object") { - // filter down to supported systems - const filteredSystems = json.all.filter((jsonSystem: any) => systemIdsToSupport.includes(jsonSystem.id)); - await Promise.all(filteredSystems.map(async (system: any) => { - const constructedSystem: ISystem = { - id: system.id, - name: system.fullname, - }; - - await this.repository.addOrUpdateSystem(constructedSystem); - })); - } - } - - private async fetchAndUpdateRouteDataForExistingSystems() { - const systems = await this.repository.getSystems(); - await Promise.all(systems.map(async (system) => { - const params = { - getRoutes: "2", - }; - - const formDataJsonObject = { - "systemSelected0": system.id, - "amount": "1", - } - const formData = new FormData(); - formData.set("json", JSON.stringify(formDataJsonObject)); - - const query = new URLSearchParams(params).toString(); - const response = await fetch(`${baseUrl}?${query}`, { - method: "POST", - body: formData, - }); - const json = await response.json(); - - if (typeof json.all === "object") { - await Promise.all(json.all.map(async (jsonRoute: any) => { - const constructedRoute: IRoute = { - name: jsonRoute.name, - color: jsonRoute.color, - id: jsonRoute.myid, - polylineCoordinates: [], - systemId: system.id, - }; - - await this.repository.addOrUpdateRoute(constructedRoute); - })) - } - })); - } - - private async fetchAndUpdateStopAndPolylineDataForRoutesInExistingSystems() { - // Fetch from the API - // Pass JSON output into two different methods to update repository - const systems = await this.repository.getSystems(); - await Promise.all(systems.map(async (system: ISystem) => { - const params = { - getStops: "2", - }; - - const formDataJsonObject = { - "s0": system.id, - "sA": 1 - }; - const formData = new FormData(); - formData.set("json", JSON.stringify(formDataJsonObject)); - - const query = new URLSearchParams(params).toString(); - const response = await fetch(`${baseUrl}?${query}`, { - method: "POST", - body: formData, - }); - const json = await response.json(); - - await this.updateStopDataForSystemAndApiResponse(system, json); - await this.updateOrderedStopDataForExistingStops(json); - await this.updatePolylineDataForExistingRoutesAndApiResponse(json); - })); - } - - private async fetchAndUpdateShuttleDataForExistingSystems() { - const systems = await this.repository.getSystems(); - await Promise.all(systems.map(async (system: ISystem) => { - const params = { - getBuses: "2" - }; - - const formDataJsonObject = { - "s0": system.id, - "sA": "1" - }; - - const formData = new FormData(); - formData.set("json", JSON.stringify(formDataJsonObject)); - - const query = new URLSearchParams(params).toString(); - const response = await fetch(`${baseUrl}?${query}`, { - method: "POST", - body: formData, - }); - const json = await response.json(); - - if (json.buses && json.buses["-1"] === undefined) { - const jsonBuses = Object.values(json.buses).map((busesArr: any) => { - return busesArr[0]; - }); - - await Promise.all(jsonBuses.map(async (jsonBus: any) => { - const constructedShuttle: IShuttle = { - name: jsonBus.bus, - coordinates: { - latitude: parseFloat(jsonBus.latitude), - longitude: parseFloat(jsonBus.longitude), - }, - routeId: jsonBus.routeId, - systemId: system.id, - id: `${jsonBus.busId}` - } - - await this.repository.addOrUpdateShuttle(constructedShuttle); - })) - } - })); - } - - private async fetchAndUpdateEtaDataForExistingOrderedStops() { - // TODO implement once I figure out how to associate ETA data with shuttles - - const systems = await this.repository.getSystems() - await Promise.all(systems.map(async (system: ISystem) => { - const stops = await this.repository.getStopsBySystemId(system.id); - await Promise.all(stops.map(async (stop) => { - const params = { - eta: "3", - stopIds: stop.id, - }; - - const query = new URLSearchParams(params).toString(); - const response = await fetch(`${baseUrl}?${query}`, { - method: "GET", - }); - const json = await response.json(); - - if (json.ETAs && json.ETAs[stop.id]) { - // Continue with the parsing - json.ETAs[stop.id].forEach((jsonEta: any) => { - // Update cache - const shuttleId: string = jsonEta.busId; - - const eta: IEta = { - secondsRemaining: jsonEta.secondsSpent, - shuttleId: `${shuttleId}`, - stopId: stop.id, - millisecondsSinceEpoch: Date.now(), - }; - - this.repository.addOrUpdateEta(eta); - }); - } - })); - })) - } - - private async updateStopDataForSystemAndApiResponse(system: ISystem, json: any) { - if (json.stops) { - const jsonStops = Object.values(json.stops); - - await Promise.all(jsonStops.map(async (stop: any) => { - const constructedStop: IStop = { - name: stop.name, - id: stop.id, - systemId: system.id, - coordinates: { - latitude: parseFloat(stop.latitude), - longitude: parseFloat(stop.longitude), - }, - }; - - await this.repository.addOrUpdateStop(constructedStop); - })); - } - } - - private async updateOrderedStopDataForExistingStops(json: any) { - if (json.routes) { - await Promise.all(Object.keys(json.routes).map(async (routeId) => { - const jsonOrderedStopData: any[][] = json.routes[routeId].slice(2); - - // The API may return the same stop twice to indicate that - // the route runs in a loop - // If the API does not do this, assume the route is one-way - // To account for this, check if ordered stop already exists in repo - // If it does, write to that stop instead - - for (let index = 0; index < jsonOrderedStopData.length; index++) { - const orderedStopDataArray = jsonOrderedStopData[index]; - - const stopId = orderedStopDataArray[1]; - let constructedOrderedStop = await this.repository.getOrderedStopByRouteAndStopId(routeId, stopId) - if (constructedOrderedStop === null) { - constructedOrderedStop = { - routeId, - stopId, - position: index + 1, - }; - } - - if (index >= 1) { - constructedOrderedStop.previousStop = { - routeId, - stopId: jsonOrderedStopData[index - 1][1], - position: index, - }; - } - if (index < jsonOrderedStopData.length - 1) { - constructedOrderedStop.nextStop = { - routeId, - stopId: jsonOrderedStopData[index + 1][1], - position: index + 2, - }; - } - - await this.repository.addOrUpdateOrderedStop(constructedOrderedStop); - } - })); - } - } - - private async updatePolylineDataForExistingRoutesAndApiResponse(json: any) { - if (json.routePoints) { - await Promise.all(Object.keys(json.routePoints).map(async (routeId) => { - const routePoints = json.routePoints[routeId][0]; - - const existingRoute = await this.repository.getRouteById(routeId); - if (!existingRoute) return; - - existingRoute.polylineCoordinates = routePoints.map((point: any) => { - return { - latitude: parseFloat(point.lat), - longitude: parseFloat(point.lng), - }; - }); - - await this.repository.addOrUpdateRoute(existingRoute); - })) - } - } } \ No newline at end of file