Files
project-inter-server/src/repositories/shuttle/UnoptimizedInMemoryShuttleRepository.ts

302 lines
9.6 KiB
TypeScript

import EventEmitter from "node:events";
import { ShuttleGetterSetterRepository } from "./ShuttleGetterSetterRepository";
import { IOrderedStop, IRoute, IShuttle, IStop, shuttleHasArrivedAtStop } from "../../entities/ShuttleRepositoryEntities";
import { IEntityWithId } from "../../entities/SharedEntities";
import {
ShuttleRepositoryEvent,
ShuttleRepositoryEventListener,
ShuttleRepositoryEventName,
ShuttleRepositoryEventPayloads,
ShuttleStopArrival,
ShuttleTravelTimeDataIdentifier,
ShuttleTravelTimeDateFilterArguments,
} from "./ShuttleGetterRepository";
/**
* An unoptimized in memory repository.
* (I would optimize it with actual data structures, but I'm
* switching to another data store later anyways)
*/
export class UnoptimizedInMemoryShuttleRepository
extends EventEmitter
implements ShuttleGetterSetterRepository {
public override on<T extends ShuttleRepositoryEventName>(
event: T,
listener: ShuttleRepositoryEventListener<T>,
): this;
public override on(event: string | symbol, listener: (...args: any[]) => void): this {
return super.on(event, listener);
}
public override once<T extends ShuttleRepositoryEventName>(
event: T,
listener: ShuttleRepositoryEventListener<T>,
): this;
public override once(event: string | symbol, listener: (...args: any[]) => void): this {
return super.once(event, listener);
}
public override off<T extends ShuttleRepositoryEventName>(
event: T,
listener: ShuttleRepositoryEventListener<T>,
): this;
public override off(event: string | symbol, listener: (...args: any[]) => void): this {
return super.off(event, listener);
}
public override addListener<T extends ShuttleRepositoryEventName>(
event: T,
listener: ShuttleRepositoryEventListener<T>,
): this;
public override addListener(event: string | symbol, listener: (...args: any[]) => void): this {
return super.addListener(event, listener);
}
public override removeListener<T extends ShuttleRepositoryEventName>(
event: T,
listener: ShuttleRepositoryEventListener<T>,
): this;
public override removeListener(event: string | symbol, listener: (...args: any[]) => void): this {
return super.removeListener(event, listener);
}
public override emit<T extends ShuttleRepositoryEventName>(
event: T,
payload: ShuttleRepositoryEventPayloads[T],
): boolean;
public override emit(event: string | symbol, ...args: any[]): boolean {
return super.emit(event, ...args);
}
private stops: IStop[] = [];
private routes: IRoute[] = [];
private shuttles: IShuttle[] = [];
private orderedStops: IOrderedStop[] = [];
private shuttleLastStopArrivals: Map<string, ShuttleStopArrival> = new Map();
private travelTimeData: Map<string, Array<{ timestamp: number; seconds: number }>> = new Map();
public async getStops(): Promise<IStop[]> {
return this.stops;
}
public async getStopById(stopId: string) {
return this.findEntityById(stopId, this.stops);
}
public async getRoutes(): Promise<IRoute[]> {
return this.routes;
}
public async getRouteById(routeId: string) {
return this.findEntityById(routeId, this.routes);
}
public async getShuttles(): Promise<IShuttle[]> {
return this.shuttles;
}
public async getShuttlesByRouteId(routeId: string) {
return this.shuttles.filter(shuttle => shuttle.routeId === routeId);
}
public async getShuttleById(shuttleId: string) {
return this.findEntityById(shuttleId, this.shuttles);
}
public async getOrderedStopByRouteAndStopId(routeId: string, stopId: string) {
return this.findEntityByMatcher<IOrderedStop>((value) => value.routeId === routeId && value.stopId === stopId, this.orderedStops)
}
public async getOrderedStopsByStopId(stopId: string) {
return this.orderedStops.filter((value) => value.stopId === stopId);
}
public async getOrderedStopsByRouteId(routeId: string) {
return this.orderedStops.filter((value) => value.routeId === routeId);
}
private findEntityById<T extends IEntityWithId>(entityId: string, arrayToSearchIn: T[]) {
return this.findEntityByMatcher((value) => value.id === entityId, arrayToSearchIn);
}
private findEntityByMatcher<T>(callback: (value: T) => boolean, arrayToSearchIn: T[]): T | null {
const entity = arrayToSearchIn.find(callback);
if (!entity) {
return null;
}
return entity;
}
public async addOrUpdateRoute(route: IRoute): Promise<void> {
const index = this.routes.findIndex((r) => r.id === route.id);
if (index !== -1) {
this.routes[index] = route;
} else {
this.routes.push(route);
}
}
public async addOrUpdateShuttle(
shuttle: IShuttle,
travelTimeTimestamp = Date.now(),
): Promise<void> {
const index = this.shuttles.findIndex((s) => s.id === shuttle.id);
if (index !== -1) {
this.shuttles[index] = shuttle;
} else {
this.shuttles.push(shuttle);
}
this.emit(ShuttleRepositoryEvent.SHUTTLE_UPDATED, shuttle);
await this.updateLastStopArrival(shuttle, travelTimeTimestamp);
}
public async addOrUpdateStop(stop: IStop): Promise<void> {
const index = this.stops.findIndex((s) => s.id === stop.id);
if (index !== -1) {
this.stops[index] = stop;
} else {
this.stops.push(stop);
}
}
public async addOrUpdateOrderedStop(orderedStop: IOrderedStop): Promise<void> {
const index = this.orderedStops.findIndex((value) => value.stopId === orderedStop.stopId && value.routeId === orderedStop.routeId);
if (index !== -1) {
this.orderedStops[index] = orderedStop;
} else {
this.orderedStops.push(orderedStop);
}
}
private async updateLastStopArrival(
shuttle: IShuttle,
travelTimeTimestamp = Date.now(),
) {
const arrivedStop = await this.getArrivedStopIfExists(shuttle);
if (arrivedStop != undefined) {
// stop if same stop
const lastStop = await this.getShuttleLastStopArrival(shuttle.id);
if (lastStop?.stopId === arrivedStop.id) return;
const shuttleArrival = {
stopId: arrivedStop.id,
timestamp: new Date(travelTimeTimestamp),
shuttleId: shuttle.id,
};
this.emit(ShuttleRepositoryEvent.SHUTTLE_WILL_ARRIVE_AT_STOP, {
lastArrival: lastStop,
currentArrival: shuttleArrival,
});
await this.updateShuttleLastStopArrival(shuttleArrival);
}
}
private async updateShuttleLastStopArrival(lastStopArrival: ShuttleStopArrival) {
this.shuttleLastStopArrivals.set(lastStopArrival.shuttleId, lastStopArrival);
}
public async getAverageTravelTimeSeconds(
{ routeId, fromStopId, toStopId }: ShuttleTravelTimeDataIdentifier,
{ from, to }: ShuttleTravelTimeDateFilterArguments,
): Promise<number | undefined> {
const key = `${routeId}:${fromStopId}:${toStopId}`;
const dataPoints = this.travelTimeData.get(key);
if (!dataPoints || dataPoints.length === 0) {
return undefined;
}
const fromTimestamp = from.getTime();
const toTimestamp = to.getTime();
const filteredPoints = dataPoints.filter(
(point) => point.timestamp >= fromTimestamp && point.timestamp <= toTimestamp
);
if (filteredPoints.length === 0) {
return undefined;
}
const sum = filteredPoints.reduce((acc, point) => acc + point.seconds, 0);
return sum / filteredPoints.length;
}
public async getArrivedStopIfExists(
shuttle: IShuttle,
delta = 0.001,
): Promise<IStop | undefined> {
const orderedStops = await this.getOrderedStopsByRouteId(shuttle.routeId);
for (const orderedStop of orderedStops) {
const stop = await this.getStopById(orderedStop.stopId);
if (stop != null && shuttleHasArrivedAtStop(shuttle, stop, delta)) {
return stop;
}
}
return undefined;
}
public async getShuttleLastStopArrival(shuttleId: string): Promise<ShuttleStopArrival | undefined> {
return this.shuttleLastStopArrivals.get(shuttleId);
}
private async removeEntityByMatcherIfExists<T>(callback: (value: T) => boolean, arrayToSearchIn: T[]) {
const index = arrayToSearchIn.findIndex(callback);
if (index > -1) {
const entityToReturn = arrayToSearchIn[index];
arrayToSearchIn.splice(index, 1);
return entityToReturn;
}
return null;
}
private async removeEntityByIdIfExists<T extends IEntityWithId>(entityId: string, arrayToSearchIn: T[]) {
return await this.removeEntityByMatcherIfExists((value) => value.id === entityId, arrayToSearchIn);
}
public async removeRouteIfExists(routeId: string): Promise<IRoute | null> {
return await this.removeEntityByIdIfExists(routeId, this.routes);
}
public async removeShuttleIfExists(shuttleId: string): Promise<IShuttle | null> {
const shuttle = await this.removeEntityByIdIfExists(shuttleId, this.shuttles);
if (shuttle != null) {
this.emit(ShuttleRepositoryEvent.SHUTTLE_REMOVED, shuttle);
}
return shuttle;
}
public async removeStopIfExists(stopId: string): Promise<IStop | null> {
return await this.removeEntityByIdIfExists(stopId, this.stops);
}
public async removeOrderedStopIfExists(stopId: string, routeId: string): Promise<IOrderedStop | null> {
return await this.removeEntityByMatcherIfExists((orderedStop) => {
return orderedStop.stopId === stopId
&& orderedStop.routeId === routeId
}, this.orderedStops);
}
public async clearShuttleData(): Promise<void> {
this.shuttles = [];
}
public async clearOrderedStopData(): Promise<void> {
this.orderedStops = [];
}
public async clearRouteData(): Promise<void> {
this.routes = [];
}
public async clearStopData(): Promise<void> {
this.stops = [];
}
}