mirror of
https://github.com/brendan-ch/project-inter-server.git
synced 2026-04-17 07:50:31 +00:00
restructure parking and shuttle repository loaders
This commit is contained in:
328
src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts
Normal file
328
src/loaders/shuttle/ApiBasedShuttleRepositoryLoader.ts
Normal file
@@ -0,0 +1,328 @@
|
||||
import { ShuttleGetterSetterRepository } from "../../repositories/ShuttleGetterSetterRepository";
|
||||
import { IEta, IRoute, IShuttle, IStop } from "../../entities/ShuttleRepositoryEntities";
|
||||
import { ShuttleRepositoryLoader } from "./ShuttleRepositoryLoader";
|
||||
import { IEntityWithId } from "../../entities/SharedEntities";
|
||||
import { ApiResponseError } from "../ApiResponseError";
|
||||
|
||||
/**
|
||||
* Class which can load data into a repository from the
|
||||
* Passio Go API. Supports automatic pruning of all data types
|
||||
* which inherit from `IEntityWithId`.
|
||||
*/
|
||||
export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader {
|
||||
baseUrl = "https://passiogo.com/mapGetData.php";
|
||||
|
||||
constructor(
|
||||
public passioSystemId: string,
|
||||
public systemIdForConstructedData: string,
|
||||
public repository: ShuttleGetterSetterRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
private async constructExistingEntityIdSet<T extends IEntityWithId>(entitySearchCallback: () => Promise<T[]>) {
|
||||
const existingEntities = await entitySearchCallback();
|
||||
const ids = new Set<string>();
|
||||
existingEntities.forEach((entity) => {
|
||||
ids.add(entity.id);
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
|
||||
public async fetchAndUpdateAll() {
|
||||
await this.fetchAndUpdateRouteDataForSystem();
|
||||
await this.fetchAndUpdateStopAndPolylineDataForRoutesInSystem();
|
||||
await this.fetchAndUpdateShuttleDataForSystem();
|
||||
|
||||
// Because ETA method doesn't support pruning yet,
|
||||
// add a call to the clear method here
|
||||
await this.repository.clearEtaData();
|
||||
await this.fetchAndUpdateEtaDataForExistingStopsForSystem();
|
||||
}
|
||||
|
||||
public async fetchAndUpdateRouteDataForSystem() {
|
||||
const systemId = this.passioSystemId;
|
||||
const routeIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||
return await this.repository.getRoutes();
|
||||
});
|
||||
|
||||
const params = {
|
||||
getRoutes: "2",
|
||||
};
|
||||
|
||||
const formDataJsonObject = {
|
||||
"systemSelected0": systemId,
|
||||
"amount": "1",
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.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: this.systemIdForConstructedData,
|
||||
};
|
||||
|
||||
await this.repository.addOrUpdateRoute(constructedRoute);
|
||||
|
||||
routeIdsToPrune.delete(constructedRoute.id);
|
||||
}))
|
||||
}
|
||||
|
||||
await Promise.all(Array.from(routeIdsToPrune).map(async (routeId) => {
|
||||
await this.repository.removeRouteIfExists(routeId);
|
||||
}));
|
||||
} catch(e: any) {
|
||||
throw new ApiResponseError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchAndUpdateStopAndPolylineDataForRoutesInSystem() {
|
||||
const passioSystemId = this.passioSystemId;
|
||||
|
||||
// Fetch from the API
|
||||
// Pass JSON output into two different methods to update repository
|
||||
const stopIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||
return await this.repository.getStops();
|
||||
});
|
||||
|
||||
const params = {
|
||||
getStops: "2",
|
||||
};
|
||||
|
||||
const formDataJsonObject = {
|
||||
"s0": passioSystemId,
|
||||
"sA": 1
|
||||
};
|
||||
const formData = new FormData();
|
||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const json = await response.json();
|
||||
|
||||
await this.updateStopDataForSystemAndApiResponse(json, stopIdsToPrune);
|
||||
await this.updateOrderedStopDataForExistingStops(json);
|
||||
await this.updatePolylineDataForExistingRoutesAndApiResponse(json);
|
||||
|
||||
await Promise.all(Array.from(stopIdsToPrune).map(async (stopId) => {
|
||||
await this.repository.removeStopIfExists(stopId);
|
||||
}));
|
||||
} catch(e: any) {
|
||||
throw new ApiResponseError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchAndUpdateShuttleDataForSystem() {
|
||||
const systemId = this.passioSystemId;
|
||||
const shuttleIdsToPrune = await this.constructExistingEntityIdSet(async () => {
|
||||
return await this.repository.getShuttles();
|
||||
});
|
||||
|
||||
const params = {
|
||||
getBuses: "2"
|
||||
};
|
||||
|
||||
const formDataJsonObject = {
|
||||
"s0": systemId,
|
||||
"sA": "1"
|
||||
};
|
||||
|
||||
const formData = new FormData();
|
||||
formData.set("json", JSON.stringify(formDataJsonObject));
|
||||
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.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: this.systemIdForConstructedData,
|
||||
id: `${jsonBus.busId}`,
|
||||
orientationInDegrees: parseFloat(jsonBus.calculatedCourse)
|
||||
}
|
||||
|
||||
await this.repository.addOrUpdateShuttle(constructedShuttle);
|
||||
|
||||
shuttleIdsToPrune.delete(constructedShuttle.id);
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(Array.from(shuttleIdsToPrune).map(async (shuttleId) => {
|
||||
await this.repository.removeShuttleIfExists(shuttleId);
|
||||
}));
|
||||
} catch(e: any) {
|
||||
throw new ApiResponseError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async fetchAndUpdateEtaDataForExistingStopsForSystem() {
|
||||
const stops = await this.repository.getStops();
|
||||
await Promise.all(stops.map(async (stop) => {
|
||||
let stopId = stop.id;
|
||||
await this.fetchAndUpdateEtaDataForStopId(stopId);
|
||||
}));
|
||||
}
|
||||
|
||||
public async fetchAndUpdateEtaDataForStopId(stopId: string) {
|
||||
const params = {
|
||||
eta: "3",
|
||||
stopIds: stopId,
|
||||
};
|
||||
|
||||
const query = new URLSearchParams(params).toString();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}?${query}`, {
|
||||
method: "GET",
|
||||
});
|
||||
const json = await response.json();
|
||||
|
||||
if (json.ETAs && json.ETAs[stopId]) {
|
||||
// Continue with the parsing
|
||||
json.ETAs[stopId].forEach((jsonEta: any) => {
|
||||
// Update cache
|
||||
const shuttleId: string = jsonEta.busId;
|
||||
|
||||
const eta: IEta = {
|
||||
secondsRemaining: jsonEta.secondsSpent,
|
||||
shuttleId: `${shuttleId}`,
|
||||
stopId: stopId,
|
||||
millisecondsSinceEpoch: Date.now(),
|
||||
systemId: this.systemIdForConstructedData,
|
||||
};
|
||||
|
||||
this.repository.addOrUpdateEta(eta);
|
||||
});
|
||||
}
|
||||
} catch(e: any) {
|
||||
throw new ApiResponseError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
protected async updateStopDataForSystemAndApiResponse(
|
||||
json: any,
|
||||
setOfIdsToPrune: Set<string> = new Set(),
|
||||
) {
|
||||
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: this.systemIdForConstructedData,
|
||||
coordinates: {
|
||||
latitude: parseFloat(stop.latitude),
|
||||
longitude: parseFloat(stop.longitude),
|
||||
},
|
||||
};
|
||||
|
||||
await this.repository.addOrUpdateStop(constructedStop);
|
||||
|
||||
setOfIdsToPrune.delete(constructedStop.id);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
systemId: this.systemIdForConstructedData,
|
||||
};
|
||||
}
|
||||
|
||||
if (index >= 1) {
|
||||
constructedOrderedStop.previousStop = {
|
||||
routeId,
|
||||
stopId: jsonOrderedStopData[index - 1][1],
|
||||
position: index,
|
||||
systemId: this.systemIdForConstructedData,
|
||||
};
|
||||
}
|
||||
if (index < jsonOrderedStopData.length - 1) {
|
||||
constructedOrderedStop.nextStop = {
|
||||
routeId,
|
||||
stopId: jsonOrderedStopData[index + 1][1],
|
||||
position: index + 2,
|
||||
systemId: this.systemIdForConstructedData,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user