Merge branch 'main' into chore/improve-apns-performance

This commit is contained in:
2025-04-30 17:54:00 -07:00
19 changed files with 153 additions and 46 deletions

View File

@@ -1,4 +1,4 @@
import { Coordinates, Eta, OrderedStop, Resolvers } from "./generated/graphql";
import { Resolvers } from "./generated/graphql";
import { ServerContext } from "./ServerContext";
import { QueryResolvers } from "./resolvers/QueryResolvers";
import { SystemResolvers } from "./resolvers/SystemResolvers";
@@ -9,6 +9,7 @@ import { ShuttleResolvers } from "./resolvers/ShuttleResolvers";
import { RouteResolvers } from "./resolvers/RouteResolvers";
import { MutationResolvers } from "./resolvers/MutationResolvers";
import { ParkingSystemResolvers } from "./resolvers/ParkingSystemResolvers";
import { DateTime } from "./scalars/DateTime";
export const MergedResolvers: Resolvers<ServerContext> = {
...QueryResolvers,
@@ -20,4 +21,5 @@ export const MergedResolvers: Resolvers<ServerContext> = {
...OrderedStopResolvers,
...EtaResolvers,
...MutationResolvers,
DateTime: DateTime,
};

View File

@@ -1,6 +1,6 @@
import { ICoordinates, IEntityWithId, IEntityWithOptionalTimestamp } from "./SharedEntities";
import { ICoordinates, IEntityWithId, IEntityWithTimestamp } from "./SharedEntities";
export interface IParkingStructure extends IEntityWithOptionalTimestamp, IEntityWithId {
export interface IParkingStructure extends IEntityWithTimestamp, IEntityWithId {
address: string;
capacity: number;
spotsAvailable: number;

View File

@@ -1,5 +1,5 @@
export interface IEntityWithOptionalTimestamp {
millisecondsSinceEpoch?: number;
export interface IEntityWithTimestamp {
updatedTime: Date;
}
export interface IEntityWithId {

View File

@@ -1,19 +1,19 @@
import { ICoordinates, IEntityWithId, IEntityWithOptionalTimestamp } from "./SharedEntities";
import { ICoordinates, IEntityWithId, IEntityWithTimestamp } from "./SharedEntities";
export interface IRoute extends IEntityWithId, IEntityWithOptionalTimestamp {
export interface IRoute extends IEntityWithId, IEntityWithTimestamp {
name: string;
color: string;
polylineCoordinates: ICoordinates[];
systemId: string;
}
export interface IStop extends IEntityWithId, IEntityWithOptionalTimestamp {
export interface IStop extends IEntityWithId, IEntityWithTimestamp {
name: string;
systemId: string;
coordinates: ICoordinates;
}
export interface IShuttle extends IEntityWithId, IEntityWithOptionalTimestamp {
export interface IShuttle extends IEntityWithId, IEntityWithTimestamp {
coordinates: ICoordinates;
name: string;
routeId: string;
@@ -21,14 +21,14 @@ export interface IShuttle extends IEntityWithId, IEntityWithOptionalTimestamp {
orientationInDegrees: number;
}
export interface IEta extends IEntityWithOptionalTimestamp {
export interface IEta extends IEntityWithTimestamp {
secondsRemaining: number;
shuttleId: string;
stopId: string;
systemId: string;
}
export interface IOrderedStop extends IEntityWithOptionalTimestamp {
export interface IOrderedStop extends IEntityWithTimestamp {
nextStop?: IOrderedStop;
previousStop?: IOrderedStop;
routeId: string;

View File

@@ -58,7 +58,8 @@ export class ChapmanApiBasedParkingRepositoryLoader implements ParkingRepository
id: ChapmanApiBasedParkingRepositoryLoader.generateId(jsonStructure.Address),
name: jsonStructure.Name,
spotsAvailable: jsonStructure.CurrentCount > jsonStructure.Capacity ? jsonStructure.Capacity : jsonStructure.CurrentCount,
address: jsonStructure.Address
address: jsonStructure.Address,
updatedTime: new Date(),
}
return structureToReturn;

View File

@@ -12,6 +12,7 @@ const parkingStructures: IParkingStructure[] = [
},
name: "Anderson Structure",
id: "1",
updatedTime: new Date(),
},
{
address: "200 W Sycamore Ave, Orange, CA 92866-1053",
@@ -23,6 +24,7 @@ const parkingStructures: IParkingStructure[] = [
},
name: "Barrera",
id: "2",
updatedTime: new Date(),
}
];

View File

@@ -73,6 +73,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
id: jsonRoute.myid,
polylineCoordinates: [],
systemId: this.systemIdForConstructedData,
updatedTime: new Date(),
};
await this.repository.addOrUpdateRoute(constructedRoute);
@@ -172,7 +173,8 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
routeId: jsonBus.routeId,
systemId: this.systemIdForConstructedData,
id: `${jsonBus.busId}`,
orientationInDegrees: parseFloat(jsonBus.calculatedCourse)
orientationInDegrees: parseFloat(jsonBus.calculatedCourse),
updatedTime: new Date(),
}
await this.repository.addOrUpdateShuttle(constructedShuttle);
@@ -221,7 +223,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
secondsRemaining: jsonEta.secondsSpent,
shuttleId: `${shuttleId}`,
stopId: stopId,
millisecondsSinceEpoch: Date.now(),
updatedTime: new Date(),
systemId: this.systemIdForConstructedData,
};
@@ -249,6 +251,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
latitude: parseFloat(stop.latitude),
longitude: parseFloat(stop.longitude),
},
updatedTime: new Date(),
};
await this.repository.addOrUpdateStop(constructedStop);
@@ -280,6 +283,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
stopId,
position: index + 1,
systemId: this.systemIdForConstructedData,
updatedTime: new Date(),
};
}
@@ -289,6 +293,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
stopId: jsonOrderedStopData[index - 1][1],
position: index,
systemId: this.systemIdForConstructedData,
updatedTime: new Date(),
};
}
if (index < jsonOrderedStopData.length - 1) {
@@ -297,6 +302,7 @@ export class ApiBasedShuttleRepositoryLoader implements ShuttleRepositoryLoader
stopId: jsonOrderedStopData[index + 1][1],
position: index + 2,
systemId: this.systemIdForConstructedData,
updatedTime: new Date(),
};
}

View File

@@ -4324,6 +4324,7 @@ const routes: IRoute[] = [
systemId: supportedIntegrationTestSystems[0].id,
polylineCoordinates: redRoutePolylineCoordinates,
color: "#db2316",
updatedTime: new Date(),
},
{
name: "Teal Route",
@@ -4331,6 +4332,7 @@ const routes: IRoute[] = [
systemId: supportedIntegrationTestSystems[0].id,
polylineCoordinates: tealRoutePolylineCoordinates,
color: "#21bdd1",
updatedTime: new Date(),
},
];
@@ -4343,6 +4345,7 @@ const stops: IStop[] = [
longitude: -117.8892805,
},
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
id: "2",
@@ -4352,6 +4355,7 @@ const stops: IStop[] = [
longitude: -117.895966,
},
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
id: "3",
@@ -4361,6 +4365,7 @@ const stops: IStop[] = [
"longitude": -117.85281
},
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
}
];
@@ -4370,12 +4375,14 @@ const orderedStopsForRedRoute: IOrderedStop[] = [
stopId: stops[0].id,
position: 1,
systemId: "1",
updatedTime: new Date(),
},
{
routeId: routes[0].id,
stopId: stops[2].id,
position: 2,
systemId: "1",
updatedTime: new Date(),
},
];
@@ -4385,18 +4392,21 @@ const orderedStopsForTealRoute: IOrderedStop[] = [
stopId: stops[0].id,
position: 1,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
routeId: routes[1].id,
stopId: stops[1].id,
position: 2,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
routeId: routes[1].id,
stopId: stops[2].id,
position: 2,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
]
@@ -4416,6 +4426,7 @@ const shuttles: IShuttle[] = [
routeId: routes[0].id,
systemId: supportedIntegrationTestSystems[0].id,
orientationInDegrees: 45.91,
updatedTime: new Date(),
},
{
name: "24",
@@ -4427,6 +4438,7 @@ const shuttles: IShuttle[] = [
routeId: routes[0].id,
systemId: supportedIntegrationTestSystems[0].id,
orientationInDegrees: 90.24,
updatedTime: new Date(),
}
];
@@ -4436,24 +4448,28 @@ const etas: IEta[] = [
shuttleId: shuttles[0].id,
secondsRemaining: 12.023,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
stopId: stops[2].id,
shuttleId: shuttles[0].id,
secondsRemaining: 600.123,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
stopId: stops[2].id,
shuttleId: shuttles[1].id,
secondsRemaining: 172.015,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
},
{
stopId: stops[0].id,
shuttleId: shuttles[1].id,
secondsRemaining: 710.152,
systemId: supportedIntegrationTestSystems[0].id,
updatedTime: new Date(),
}
];

View File

@@ -24,6 +24,7 @@ export const OrderedStopResolvers: Resolvers<ServerContext> = {
routeId: parent.routeId,
stopId: nextOrderedStopObject.id,
systemId: system.id,
updatedTime: nextOrderedStopObject.updatedTime
}
},
previousStop: async (parent, args, contextValue, _info): Promise<OrderedStop | null> => {
@@ -47,6 +48,7 @@ export const OrderedStopResolvers: Resolvers<ServerContext> = {
routeId: parent.routeId,
stopId: previousOrderedStopObject.id,
systemId: system.id,
updatedTime: previousOrderedStopObject.updatedTime,
}
},
stop: async (parent, args, contextValue, _info) => {

View File

@@ -3,7 +3,7 @@ import { ServerContext } from "../ServerContext";
export const RouteResolvers: Resolvers<ServerContext> = {
Route: {
shuttles: async (parent, args, contextValue, info) => {
shuttles: async (parent, args, contextValue, _info) => {
const system = contextValue.findSystemById(parent.systemId);
if (!system) return null;
@@ -13,7 +13,8 @@ export const RouteResolvers: Resolvers<ServerContext> = {
coordinates,
name,
id,
orientationInDegrees
orientationInDegrees,
updatedTime
}) => ({
coordinates: coordinates as Coordinates,
name,
@@ -22,9 +23,10 @@ export const RouteResolvers: Resolvers<ServerContext> = {
id,
orientationInDegrees,
systemId: parent.systemId,
updatedTime,
}));
},
orderedStop: async (parent, args, contextValue, info) => {
orderedStop: async (parent, args, contextValue, _info) => {
if (!args.forStopId) return null;
const system = contextValue.findSystemById(parent.systemId);
@@ -41,6 +43,7 @@ export const RouteResolvers: Resolvers<ServerContext> = {
routeId: parent.id,
route: parent,
systemId: system.id,
updatedTimeMs: orderedStop.updatedTime,
}
},
},

View File

@@ -18,6 +18,7 @@ export const ShuttleResolvers: Resolvers<ServerContext> = {
shuttleId: parent.id,
shuttle: parent,
systemId: system.id,
updatedTimeMs: etaForStopId.updatedTime,
};
},
etas: async (parent, args, contextValue, info) => {
@@ -27,17 +28,20 @@ export const ShuttleResolvers: Resolvers<ServerContext> = {
const etasForShuttle = await system.shuttleRepository.getEtasForShuttleId(parent.id);
if (!etasForShuttle) return null;
const computedEtas = await Promise.all(etasForShuttle.map(async ({
secondsRemaining,
stopId,
}): Promise<Eta | null> => {
return {
const computedEtas = await Promise.all(
etasForShuttle.map(async ({
secondsRemaining,
stopId,
shuttle: parent,
shuttleId: parent.id,
systemId: system.id,
}
updatedTime
}): Promise<Eta | null> => {
return {
secondsRemaining,
stopId,
shuttle: parent,
shuttleId: parent.id,
systemId: system.id,
updatedTime: updatedTime,
}
}));
if (computedEtas.every((eta) => eta !== null)) {
@@ -59,6 +63,7 @@ export const ShuttleResolvers: Resolvers<ServerContext> = {
name: route.name,
polylineCoordinates: route.polylineCoordinates,
systemId: system.id,
updatedTimeMs: route.updatedTime
}
}
},

View File

@@ -36,6 +36,7 @@ export const SystemResolvers: Resolvers<ServerContext> = {
name: stop.name,
coordinates: stop.coordinates as Coordinates,
systemId: parent.id,
updatedTimeMs: stop.updatedTime,
};
},
route: async (parent, args, contextValue, _info) => {
@@ -55,6 +56,7 @@ export const SystemResolvers: Resolvers<ServerContext> = {
name: route.name,
polylineCoordinates: route.polylineCoordinates as Coordinates[],
systemId: parent.id,
updatedTimeMs: route.updatedTime,
};
},
shuttle: async (parent, args, contextValue, _info) => {

28
src/scalars/DateTime.ts Normal file
View File

@@ -0,0 +1,28 @@
import { GraphQLScalarType } from "graphql/type";
import { Kind } from "graphql/language";
// See Apollo documentation: https://www.apollographql.com/docs/apollo-server/schema/custom-scalars#providing-custom-scalars-to-apollo-server
export const DateTime = new GraphQLScalarType({
name: 'DateTime',
description: 'DateTime custom scalar type',
serialize(value) {
if (value instanceof Date) {
return value.getTime();
} else if (value instanceof Number) {
return value;
}
throw Error('GraphQL Date Scalar serializer expected a `Date` object or number');
},
parseValue(value) {
if (typeof value === 'number') {
return new Date(value);
}
throw new Error('GraphQL Date Scalar parser expected a `number`');
},
parseLiteral(ast) {
if (ast.kind === Kind.INT) {
return new Date(parseInt(ast.value, 10));
}
return null;
},
})