Files
project-inter-server/src/entities/InterchangeSystem.ts

295 lines
11 KiB
TypeScript

import { ETANotificationScheduler } from "../notifications/schedulers/ETANotificationScheduler";
import { TimedApiBasedRepositoryLoader } from "../loaders/TimedApiBasedRepositoryLoader";
import { UnoptimizedInMemoryShuttleRepository } from "../repositories/shuttle/UnoptimizedInMemoryShuttleRepository";
import { RedisNotificationRepository } from "../repositories/notifications/RedisNotificationRepository";
import { NotificationRepository } from "../repositories/notifications/NotificationRepository";
import { ShuttleGetterSetterRepository } from "../repositories/shuttle/ShuttleGetterSetterRepository";
import { InMemoryNotificationRepository } from "../repositories/notifications/InMemoryNotificationRepository";
import { AppleNotificationSender } from "../notifications/senders/AppleNotificationSender";
import { ApiBasedShuttleRepositoryLoader } from "../loaders/shuttle/ApiBasedShuttleRepositoryLoader";
import { ParkingGetterSetterRepository } from "../repositories/parking/ParkingGetterSetterRepository";
import { InMemoryParkingRepository } from "../repositories/parking/InMemoryParkingRepository";
import {
buildParkingRepositoryLoaderIfExists,
ParkingRepositoryLoaderBuilderArguments
} from "../loaders/parking/buildParkingRepositoryLoaderIfExists";
import { RedisParkingRepository } from "../repositories/parking/RedisParkingRepository";
import { RedisShuttleRepository } from "../repositories/shuttle/RedisShuttleRepository";
import { ShuttleGetterRepository } from "../repositories/shuttle/ShuttleGetterRepository";
import { InMemoryExternalSourceETARepository } from "../repositories/shuttle/eta/InMemoryExternalSourceETARepository";
import { ETAGetterRepository } from "../repositories/shuttle/eta/ETAGetterRepository";
import { RedisSelfUpdatingETARepository } from "../repositories/shuttle/eta/RedisSelfUpdatingETARepository";
import { RedisExternalSourceETARepository } from "../repositories/shuttle/eta/RedisExternalSourceETARepository";
import { InMemorySelfUpdatingETARepository } from "../repositories/shuttle/eta/InMemorySelfUpdatingETARepository";
import { BaseRedisETARepository } from "../repositories/shuttle/eta/BaseRedisETARepository";
import { BaseInMemoryETARepository } from "../repositories/shuttle/eta/BaseInMemoryETARepository";
import createRedisClientForRepository from "../helpers/createRedisClientForRepository";
export interface InterchangeSystemBuilderArguments {
name: string;
/**
* ID to identify the system internally and in the API.
*/
id: string;
/**
* ID for fetching shuttle data from the Passio GO! system.
*/
passioSystemId: string;
/**
* ID for the parking repository ID in the codebase.
*/
parkingSystemId?: string;
/**
* Controls whether to self-calculate ETAs or use the external
* shuttle provider for them.
*/
useSelfUpdatingEtas: boolean;
/**
* The size of the threshold to detect when a shuttle has arrived
* at a stop, in latitude/longitude degrees.
*/
shuttleStopArrivalDegreeDelta: number;
}
export class InterchangeSystem {
private constructor(
public name: string,
public id: string,
public shuttleTimedDataLoader: TimedApiBasedRepositoryLoader,
public shuttleRepository: ShuttleGetterSetterRepository,
public etaRepository: ETAGetterRepository,
public notificationScheduler: ETANotificationScheduler,
public notificationRepository: NotificationRepository,
public parkingTimedDataLoader: TimedApiBasedRepositoryLoader | null,
public parkingRepository: ParkingGetterSetterRepository | null,
) {
}
/**
* Construct an instance of the class where all composited
* classes are correctly linked, meant for use in development and production.
* @param args
*/
static async build(
args: InterchangeSystemBuilderArguments,
) {
const { shuttleRepository, timedShuttleDataLoader, etaRepository } = await InterchangeSystem.buildRedisShuttleLoaderAndRepositories(args);
timedShuttleDataLoader.start();
const { notificationScheduler, notificationRepository } = await InterchangeSystem.buildNotificationSchedulerAndRepository(
etaRepository,
shuttleRepository,
args
);
notificationScheduler.startListeningForUpdates();
let { parkingRepository, timedParkingLoader } = await InterchangeSystem.buildRedisParkingLoaderAndRepository(args.parkingSystemId);
timedParkingLoader?.start();
return new InterchangeSystem(
args.name,
args.id,
timedShuttleDataLoader,
shuttleRepository,
etaRepository,
notificationScheduler,
notificationRepository,
timedParkingLoader,
parkingRepository,
);
}
private static async buildRedisShuttleLoaderAndRepositories(args: InterchangeSystemBuilderArguments) {
const shuttleRepository = new RedisShuttleRepository(
createRedisClientForRepository(),
args.shuttleStopArrivalDegreeDelta,
);
await shuttleRepository.connect();
let etaRepository: BaseRedisETARepository;
let shuttleDataLoader: ApiBasedShuttleRepositoryLoader;
if (args.useSelfUpdatingEtas) {
etaRepository = new RedisSelfUpdatingETARepository(shuttleRepository);
(etaRepository as RedisSelfUpdatingETARepository).startListeningForUpdates();
shuttleDataLoader = new ApiBasedShuttleRepositoryLoader(
args.passioSystemId,
args.id,
shuttleRepository,
);
} else {
etaRepository = new RedisExternalSourceETARepository();
shuttleDataLoader = new ApiBasedShuttleRepositoryLoader(
args.passioSystemId,
args.id,
shuttleRepository,
etaRepository as RedisExternalSourceETARepository,
);
}
await etaRepository.connect();
const timedShuttleDataLoader = new TimedApiBasedRepositoryLoader(
shuttleDataLoader,
);
return { shuttleRepository, etaRepository, timedShuttleDataLoader };
}
private static async buildNotificationSchedulerAndRepository(
etaRepository: ETAGetterRepository,
shuttleRepository: ShuttleGetterRepository,
args: InterchangeSystemBuilderArguments
) {
const notificationRepository = new RedisNotificationRepository();
await notificationRepository.connect();
const notificationScheduler = new ETANotificationScheduler(
etaRepository,
shuttleRepository,
notificationRepository,
new AppleNotificationSender(),
args.id
);
return { notificationScheduler, notificationRepository };
}
private static async buildRedisParkingLoaderAndRepository(id?: string) {
if (id === undefined) {
return { parkingRepository: null, timedParkingLoader: null };
}
let parkingRepository: RedisParkingRepository | null = new RedisParkingRepository();
await parkingRepository.connect();
const loaderBuilderArguments: ParkingRepositoryLoaderBuilderArguments = {
id,
repository: parkingRepository,
};
let parkingLoader = buildParkingRepositoryLoaderIfExists(
loaderBuilderArguments,
);
let timedParkingLoader = null;
if (parkingLoader == null) {
parkingRepository = null;
} else {
timedParkingLoader = new TimedApiBasedRepositoryLoader(parkingLoader);
}
return { parkingRepository, timedParkingLoader };
}
/**
* Construct an instance of the class where all composited
* classes are correctly linked, meant for unit tests, and server/app
* integration tests.
* @param args
*/
static buildForTesting(
args: InterchangeSystemBuilderArguments,
) {
const { shuttleRepository, timedShuttleLoader, etaRepository } = InterchangeSystem.buildInMemoryShuttleLoaderAndRepositories(args);
// Timed shuttle loader is not started here
const { notificationScheduler, notificationRepository } = InterchangeSystem.buildInMemoryNotificationSchedulerAndRepository(
etaRepository,
shuttleRepository,
args
);
notificationScheduler.startListeningForUpdates();
let { parkingRepository, timedParkingLoader } = this.buildInMemoryParkingLoaderAndRepository(args.parkingSystemId);
// Timed parking loader is not started here
return new InterchangeSystem(
args.name,
args.id,
timedShuttleLoader,
shuttleRepository,
etaRepository,
notificationScheduler,
notificationRepository,
timedParkingLoader,
parkingRepository,
);
}
private static buildInMemoryNotificationSchedulerAndRepository(
etaRepository: ETAGetterRepository,
shuttleRepository: UnoptimizedInMemoryShuttleRepository,
args: InterchangeSystemBuilderArguments
) {
const notificationRepository = new InMemoryNotificationRepository();
const notificationScheduler = new ETANotificationScheduler(
etaRepository,
shuttleRepository,
notificationRepository,
new AppleNotificationSender(false),
args.id
);
return { notificationScheduler, notificationRepository };
}
private static buildInMemoryParkingLoaderAndRepository(id?: string) {
if (id === undefined) {
return { parkingRepository: null, timedParkingLoader: null };
}
let parkingRepository: ParkingGetterSetterRepository | null = new InMemoryParkingRepository();
const loaderBuilderArguments: ParkingRepositoryLoaderBuilderArguments = {
id,
repository: parkingRepository,
};
let parkingLoader = buildParkingRepositoryLoaderIfExists(
loaderBuilderArguments,
);
let timedParkingLoader = null;
if (parkingLoader == null) {
parkingRepository = null;
} else {
timedParkingLoader = new TimedApiBasedRepositoryLoader(parkingLoader);
}
return { parkingRepository, timedParkingLoader };
}
private static buildInMemoryShuttleLoaderAndRepositories(args: InterchangeSystemBuilderArguments) {
const shuttleRepository = new UnoptimizedInMemoryShuttleRepository(
args.shuttleStopArrivalDegreeDelta,
);
let etaRepository: BaseInMemoryETARepository;
let shuttleDataLoader: ApiBasedShuttleRepositoryLoader;
if (args.useSelfUpdatingEtas) {
etaRepository = new InMemorySelfUpdatingETARepository(shuttleRepository);
(etaRepository as InMemorySelfUpdatingETARepository).startListeningForUpdates();
shuttleDataLoader = new ApiBasedShuttleRepositoryLoader(
args.passioSystemId,
args.id,
shuttleRepository,
);
} else {
etaRepository = new InMemoryExternalSourceETARepository();
shuttleDataLoader = new ApiBasedShuttleRepositoryLoader(
args.passioSystemId,
args.id,
shuttleRepository,
etaRepository as InMemoryExternalSourceETARepository,
);
}
// Note that this loader should not be started,
// so the test data doesn't get overwritten
const timedShuttleLoader = new TimedApiBasedRepositoryLoader(
shuttleDataLoader
);
return { shuttleRepository, etaRepository, timedShuttleLoader };
}
}