Merge pull request #4 from brendan-ch/feat/integrate-apibasedrepository

feat/integrate-apibasedrepository
This commit is contained in:
2025-01-09 14:21:28 -08:00
committed by GitHub
7 changed files with 268 additions and 235 deletions

View File

@@ -1,5 +1,6 @@
import { GetterRepository } from "./repositories/GetterRepository"; import { GetterRepository } from "./repositories/GetterRepository";
export interface ServerContext { export interface ServerContext {
repository: GetterRepository repository: GetterRepository;
apiBasedRepository: GetterRepository;
} }

View File

@@ -6,6 +6,7 @@ import { loadTestData } from "./loaders/loadTestData";
import { ServerContext } from "./ServerContext"; import { ServerContext } from "./ServerContext";
import { UnoptimizedInMemoryRepository } from "./repositories/UnoptimizedInMemoryRepository"; import { UnoptimizedInMemoryRepository } from "./repositories/UnoptimizedInMemoryRepository";
import { RepositoryDataLoader } from "./loaders/RepositoryDataLoader"; import { RepositoryDataLoader } from "./loaders/RepositoryDataLoader";
import { ApiBasedRepository } from "./repositories/ApiBasedRepository";
const typeDefs = readFileSync("./schema.graphqls", "utf8"); const typeDefs = readFileSync("./schema.graphqls", "utf8");
@@ -24,6 +25,14 @@ async function main() {
); );
await repositoryDataUpdater.start(); await repositoryDataUpdater.start();
// TODO: Migrate all logic over to this repository
const apiBasedRepository = new ApiBasedRepository();
const systemIds = ["263"];
await Promise.all(systemIds.map(async (systemId) => {
await apiBasedRepository.seedCacheForSystemId(systemId);
}));
const { url } = await startStandaloneServer(server, { const { url } = await startStandaloneServer(server, {
listen: { listen: {
port: process.env.PORT ? parseInt(process.env.PORT) : 4000, port: process.env.PORT ? parseInt(process.env.PORT) : 4000,
@@ -31,6 +40,7 @@ async function main() {
context: async ({ req, res }) => { context: async ({ req, res }) => {
return { return {
repository, repository,
apiBasedRepository,
} }
}, },
}); });

View File

@@ -68,10 +68,9 @@ export class ApiBasedRepository implements GetterRepository {
* @param systemId * @param systemId
*/ */
public async seedCacheForSystemId(systemId: string): Promise<void> { public async seedCacheForSystemId(systemId: string): Promise<void> {
await this.getShuttlesBySystemId(systemId); await this.updateShuttlesForSystemIfTTL(systemId);
await this.getShuttlesByRouteId(systemId); await this.updateEtasForSystemIfTTL(systemId);
await this.getStopsBySystemId(systemId); await this.updateStopsForSystemIdIfTTL(systemId);
await this.getSystemById(systemId);
} }
public async getEtaForShuttleAndStopId(shuttleId: string, stopId: string): Promise<IEta | null> { public async getEtaForShuttleAndStopId(shuttleId: string, stopId: string): Promise<IEta | null> {
@@ -84,7 +83,7 @@ export class ApiBasedRepository implements GetterRepository {
if (this.cache?.etasForStopId && this.cache.etasForStopId[stopId]) { if (this.cache?.etasForStopId && this.cache.etasForStopId[stopId]) {
const etas = this.cache.etasForStopId[stopId]; const etas = this.cache.etasForStopId[stopId];
const foundEta = etas.find((eta) => eta.stopId === stopId); const foundEta = etas.find((eta) => eta.shuttleId === shuttleId);
if (foundEta) { if (foundEta) {
return foundEta; return foundEta;
} }
@@ -306,7 +305,7 @@ ${json}`);
public async getStopsBySystemId(systemId: string): Promise<IStop[]> { public async getStopsBySystemId(systemId: string): Promise<IStop[]> {
await this.updateStopsForSystemIdIfTTL(systemId); await this.updateStopsForSystemIdIfTTL(systemId);
if (!this.cache.stopsBySystemId || this.cache.stopsBySystemId[systemId]) { if (!this.cache.stopsBySystemId || !this.cache.stopsBySystemId[systemId]) {
return []; return [];
} }
return this.cache.stopsBySystemId[systemId]; return this.cache.stopsBySystemId[systemId];
@@ -340,6 +339,10 @@ ${json}`);
if (json.stops) { if (json.stops) {
const jsonStops = Object.values(json.stops); const jsonStops = Object.values(json.stops);
// TODO: restore normal cache behavior
this.cache.stopsBySystemId = {};
this.cache.stopByStopId = {};
await Promise.all(jsonStops.map(async (stop: any) => { await Promise.all(jsonStops.map(async (stop: any) => {
const constructedStop: IStop = { const constructedStop: IStop = {
name: stop.name, name: stop.name,
@@ -351,13 +354,21 @@ ${json}`);
}, },
}; };
if (this.cache.stopsBySystemId) { if (!this.cache.stopsBySystemId) {
this.cache.stopsBySystemId[systemId]?.push(constructedStop); this.cache.stopsBySystemId = {};
} }
if (this.cache.stopByStopId) { if (!this.cache.stopsBySystemId[systemId]) {
this.cache.stopByStopId[constructedStop.id] = constructedStop; this.cache.stopsBySystemId[systemId] = [];
} }
this.cache.stopsBySystemId[systemId].push(constructedStop);
if (!this.cache.stopByStopId) {
this.cache.stopByStopId = {};
}
this.cache.stopByStopId[constructedStop.id] = constructedStop;
})); }));
} }
} catch (e) { } catch (e) {

View File

@@ -132,10 +132,10 @@ export const resolvers: Resolvers<ServerContext> = {
Shuttle: { Shuttle: {
eta: async (parent, args, contextValue, info) => { eta: async (parent, args, contextValue, info) => {
if (!args.forStopId) return null; if (!args.forStopId) return null;
const etaForStopId = await contextValue.repository.getEtaForShuttleAndStopId(parent.id, args.forStopId); const etaForStopId = await contextValue.apiBasedRepository.getEtaForShuttleAndStopId(parent.id, args.forStopId);
if (etaForStopId === null) return null; if (etaForStopId === null) return null;
const stop = await contextValue.repository.getStopById(etaForStopId.stopId); const stop = await contextValue.apiBasedRepository.getStopById(etaForStopId.stopId);
if (stop === null) return null; if (stop === null) return null;
return { return {
@@ -149,14 +149,14 @@ export const resolvers: Resolvers<ServerContext> = {
}; };
}, },
etas: async (parent, args, contextValue, info) => { etas: async (parent, args, contextValue, info) => {
const etasForShuttle = await contextValue.repository.getEtasForShuttleId(parent.id); const etasForShuttle = await contextValue.apiBasedRepository.getEtasForShuttleId(parent.id);
if (!etasForShuttle) return null; if (!etasForShuttle) return null;
const computedEtas = await Promise.all(etasForShuttle.map(async ({ const computedEtas = await Promise.all(etasForShuttle.map(async ({
secondsRemaining, secondsRemaining,
stopId, stopId,
}): Promise<Eta | null> => { }): Promise<Eta | null> => {
const stop = await contextValue.repository.getStopById(stopId); const stop = await contextValue.apiBasedRepository.getStopById(stopId);
if (stop === null) return null; if (stop === null) return null;
return { return {

View File

@@ -0,0 +1,115 @@
// Snapshot taken from the Passio GO! API
export const genericEtaDataByStopId = {
"ETAs": {
"177666": [
{
"OOS": 0,
"busName": "08",
"distance": 1,
"speed": 10.028535400123669,
"routeBlockId": "142270",
"actualRouteBlockId": "142270",
"arrived": null,
"eta": "10 min ",
"color": "#000000",
"bg": "#ffea3f",
"order": 0,
"dwell": null,
"stopsAmount": 2,
"secondsSpent": 587,
"etaR": "10",
"error": null,
"outdated": 0,
"routeId": "53966",
"serviceTime": "",
"scheduleTimes": [],
"goShowSchedule": 0,
"looping": "1",
"routeGroupId": "6703",
"busId": 5577,
"tripId": 751430,
"deviceId": 402840,
"created": "2025-01-07 15:00:09",
"routePointPosition": 6,
"routeStopPosition": 1,
"stopRoutePointPosition": 217,
"timezoneOffset": -10800,
"busLatLng": [
33.7933406,
-117.8539321
],
"busProjectionLatlng": {
"lat": 33.79331052666975,
"lng": -117.85392945849208
},
"busProjectionError": 3,
"stopId": "177666",
"theStop": {
"name": "Chapman Court",
"position": 3,
"userId": "263",
"routeStopId": "1348785",
"busId": 5577,
"routeName": "Red Route",
"shortName": null,
"routeId": "53966",
"stopId": "177666"
}
},
{
"OOS": 0,
"busName": "07",
"distance": 1,
"speed": 12.160256921380398,
"routeBlockId": "142270",
"actualRouteBlockId": "142270",
"arrived": null,
"eta": "11 min ",
"color": "#000000",
"bg": "#ffea3f",
"order": 0,
"dwell": null,
"stopsAmount": 2,
"secondsSpent": 635,
"etaR": "11",
"error": null,
"outdated": 0,
"routeId": "53966",
"serviceTime": "",
"scheduleTimes": [],
"goShowSchedule": 0,
"looping": "1",
"routeGroupId": "6703",
"busId": 5576,
"tripId": 751430,
"deviceId": 441065,
"created": "2025-01-07 15:00:10",
"routePointPosition": 448,
"routeStopPosition": 4,
"stopRoutePointPosition": 217,
"timezoneOffset": -10800,
"busLatLng": [
33.7933284,
-117.855132
],
"busProjectionLatlng": {
"lat": 33.79332033922653,
"lng": -117.85513217762522
},
"busProjectionError": 1,
"stopId": "177666",
"theStop": {
"name": "Chapman Court",
"position": 3,
"userId": "263",
"routeStopId": "1348785",
"busId": 5576,
"routeName": "Red Route",
"shortName": null,
"routeId": "53966",
"stopId": "177666"
}
},
]
},
}

View File

@@ -0,0 +1,106 @@
// Snapshot taken from the Passio GO! API
export const genericShuttleDataBySystemId = {
"alertCRC": "23c1b91c",
"buses": {
"402840": [
{
"deviceId": 402840,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "08",
"busId": 5577,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.791781800",
"longitude": "-117.858964600",
"calculatedCourse": "351.796001302109",
"outOfService": 0,
"more": "102",
"totalCap": 20,
"color": "#d62728",
"busName": "08",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"404873": [
{
"deviceId": 404873,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "10",
"busId": 7105,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.789331300",
"longitude": "-117.888790600",
"calculatedCourse": "76.005762226701",
"outOfService": 0,
"more": "101",
"totalCap": 20,
"color": "#d62728",
"busName": "10",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"421421": [
{
"deviceId": 421421,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "17",
"busId": 12502,
"userId": "263",
"routeBlockId": "142660",
"latitude": "33.790699500",
"longitude": "-117.890385500",
"calculatedCourse": "10.65684824528148",
"outOfService": 0,
"more": "101",
"totalCap": 32,
"color": "#bd9e39",
"busName": "17",
"busType": "Shuttle Bus",
"routeId": "54256",
"route": "Gold Route",
"outdated": 0
}
],
"441065": [
{
"deviceId": 441065,
"created": "08:19 PM",
"createdTime": "08:19 PM",
"paxLoad": 0,
"bus": "07",
"busId": 5576,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.793278900",
"longitude": "-117.852629400",
"calculatedCourse": "299.74488110904485",
"outOfService": 0,
"more": "22",
"totalCap": 20,
"color": "#d62728",
"busName": "07",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
]
},
"microtime": 0.023222923278808594,
"time": {
"263": "08:24 PM"
}
};

View File

@@ -5,6 +5,8 @@ import {
ApiBasedRepositoryMillisecondTTLs ApiBasedRepositoryMillisecondTTLs
} from "../../src/repositories/ApiBasedRepository"; } from "../../src/repositories/ApiBasedRepository";
import { IEta, IShuttle, IStop } from "../../src/entities/entities"; import { IEta, IShuttle, IStop } from "../../src/entities/entities";
import { genericEtaDataByStopId } from "../jsonSnapshots/genericEtaDataBySystemId";
import { genericShuttleDataBySystemId } from "../jsonSnapshots/genericShuttleDataBySystemId";
/** /**
* Update the global fetch function to return a specific object. * Update the global fetch function to return a specific object.
@@ -31,226 +33,6 @@ beforeEach(() => {
resetGlobalFetchMockJson(); resetGlobalFetchMockJson();
}) })
// Snapshots taken from the Passio GO! API
const genericEtaDataByStopId = {
"ETAs": {
"177666": [
{
"OOS": 0,
"busName": "08",
"distance": 1,
"speed": 10.028535400123669,
"routeBlockId": "142270",
"actualRouteBlockId": "142270",
"arrived": null,
"eta": "10 min ",
"color": "#000000",
"bg": "#ffea3f",
"order": 0,
"dwell": null,
"stopsAmount": 2,
"secondsSpent": 587,
"etaR": "10",
"error": null,
"outdated": 0,
"routeId": "53966",
"serviceTime": "",
"scheduleTimes": [],
"goShowSchedule": 0,
"looping": "1",
"routeGroupId": "6703",
"busId": 5577,
"tripId": 751430,
"deviceId": 402840,
"created": "2025-01-07 15:00:09",
"routePointPosition": 6,
"routeStopPosition": 1,
"stopRoutePointPosition": 217,
"timezoneOffset": -10800,
"busLatLng": [
33.7933406,
-117.8539321
],
"busProjectionLatlng": {
"lat": 33.79331052666975,
"lng": -117.85392945849208
},
"busProjectionError": 3,
"stopId": "177666",
"theStop": {
"name": "Chapman Court",
"position": 3,
"userId": "263",
"routeStopId": "1348785",
"busId": 5577,
"routeName": "Red Route",
"shortName": null,
"routeId": "53966",
"stopId": "177666"
}
},
{
"OOS": 0,
"busName": "07",
"distance": 1,
"speed": 12.160256921380398,
"routeBlockId": "142270",
"actualRouteBlockId": "142270",
"arrived": null,
"eta": "11 min ",
"color": "#000000",
"bg": "#ffea3f",
"order": 0,
"dwell": null,
"stopsAmount": 2,
"secondsSpent": 635,
"etaR": "11",
"error": null,
"outdated": 0,
"routeId": "53966",
"serviceTime": "",
"scheduleTimes": [],
"goShowSchedule": 0,
"looping": "1",
"routeGroupId": "6703",
"busId": 5576,
"tripId": 751430,
"deviceId": 441065,
"created": "2025-01-07 15:00:10",
"routePointPosition": 448,
"routeStopPosition": 4,
"stopRoutePointPosition": 217,
"timezoneOffset": -10800,
"busLatLng": [
33.7933284,
-117.855132
],
"busProjectionLatlng": {
"lat": 33.79332033922653,
"lng": -117.85513217762522
},
"busProjectionError": 1,
"stopId": "177666",
"theStop": {
"name": "Chapman Court",
"position": 3,
"userId": "263",
"routeStopId": "1348785",
"busId": 5576,
"routeName": "Red Route",
"shortName": null,
"routeId": "53966",
"stopId": "177666"
}
},
]
},
}
const genericShuttleDataBySystemId = {
"alertCRC": "23c1b91c",
"buses": {
"402840": [
{
"deviceId": 402840,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "08",
"busId": 5577,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.791781800",
"longitude": "-117.858964600",
"calculatedCourse": "351.796001302109",
"outOfService": 0,
"more": "102",
"totalCap": 20,
"color": "#d62728",
"busName": "08",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"404873": [
{
"deviceId": 404873,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "10",
"busId": 7105,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.789331300",
"longitude": "-117.888790600",
"calculatedCourse": "76.005762226701",
"outOfService": 0,
"more": "101",
"totalCap": 20,
"color": "#d62728",
"busName": "10",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
],
"421421": [
{
"deviceId": 421421,
"created": "08:24 PM",
"createdTime": "08:24 PM",
"paxLoad": 0,
"bus": "17",
"busId": 12502,
"userId": "263",
"routeBlockId": "142660",
"latitude": "33.790699500",
"longitude": "-117.890385500",
"calculatedCourse": "10.65684824528148",
"outOfService": 0,
"more": "101",
"totalCap": 32,
"color": "#bd9e39",
"busName": "17",
"busType": "Shuttle Bus",
"routeId": "54256",
"route": "Gold Route",
"outdated": 0
}
],
"441065": [
{
"deviceId": 441065,
"created": "08:19 PM",
"createdTime": "08:19 PM",
"paxLoad": 0,
"bus": "07",
"busId": 5576,
"userId": "263",
"routeBlockId": "142270",
"latitude": "33.793278900",
"longitude": "-117.852629400",
"calculatedCourse": "299.74488110904485",
"outOfService": 0,
"more": "22",
"totalCap": 20,
"color": "#d62728",
"busName": "07",
"busType": "",
"routeId": "53966",
"route": "Red Route",
"outdated": 0
}
]
},
"microtime": 0.023222923278808594,
"time": {
"263": "08:24 PM"
}
}
describe("getEtaForShuttleAndStopId", () => { describe("getEtaForShuttleAndStopId", () => {
test("getEtaForShuttleAndStopId returns correct ETA data", async () => { test("getEtaForShuttleAndStopId returns correct ETA data", async () => {
@@ -264,6 +46,12 @@ describe("getEtaForShuttleAndStopId", () => {
shuttleId: "5577", shuttleId: "5577",
stopId: "177666", stopId: "177666",
millisecondsSinceEpoch: Date.now(), millisecondsSinceEpoch: Date.now(),
},
{
secondsRemaining: 226,
shuttleId: "9909",
stopId: "177666",
millisecondsSinceEpoch: Date.now(),
} }
], ],
}, },
@@ -292,6 +80,8 @@ describe("getEtaForShuttleAndStopId", () => {
expect(result?.secondsRemaining).toEqual(587); expect(result?.secondsRemaining).toEqual(587);
expect(result?.millisecondsSinceEpoch).toBeDefined(); expect(result?.millisecondsSinceEpoch).toBeDefined();
expect(result?.shuttleId).toEqual("5577");
expect(result?.stopId).toEqual("177666");
}); });
test("getEtaForShuttleAndStopId returns null if API call is invalid and cache is empty", async () => { test("getEtaForShuttleAndStopId returns null if API call is invalid and cache is empty", async () => {