Move repositories into folders.

This commit is contained in:
2025-07-19 11:58:45 -04:00
parent 3302822bf8
commit ed037cf2d2
28 changed files with 47 additions and 47 deletions

View File

@@ -0,0 +1,203 @@
import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository";
import { IParkingStructure } from "../../entities/ParkingRepositoryEntities";
import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository";
import { BaseRedisRepository } from "../BaseRedisRepository";
import { PARKING_LOGGING_INTERVAL_MS } from "./ParkingRepositoryConstants";
export type ParkingStructureID = string;
export class RedisParkingRepository extends BaseRedisRepository implements ParkingGetterSetterRepository {
private dataLastAdded: Map<ParkingStructureID, Date> = new Map();
private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS;
addOrUpdateParkingStructure = async (structure: IParkingStructure): Promise<void> => {
const keys = this.createRedisKeys(structure.id);
await this.redisClient.hSet(keys.structure, this.createRedisHashFromStructure(structure));
await this.addHistoricalDataForStructure(structure);
};
private addHistoricalDataForStructure = async (structure: IParkingStructure): Promise<void> => {
const now = Date.now();
const lastAdded = this.dataLastAdded.get(structure.id);
if (this.shouldLogHistoricalData(lastAdded, now)) {
const keys = this.createRedisKeys(structure.id);
await this.addTimeSeriesDataPoint(keys.timeSeries, now, structure.spotsAvailable, structure.id);
this.dataLastAdded.set(structure.id, new Date(now));
}
};
clearParkingStructureData = async (): Promise<void> => {
const structureKeys = await this.redisClient.keys('parking:structure:*');
const timeSeriesKeys = await this.redisClient.keys('parking:timeseries:*');
const allKeys = [...structureKeys, ...timeSeriesKeys];
if (allKeys.length > 0) {
await this.redisClient.del(allKeys);
}
this.dataLastAdded.clear();
};
getParkingStructureById = async (id: string): Promise<IParkingStructure | null> => {
const keys = this.createRedisKeys(id);
const data = await this.redisClient.hGetAll(keys.structure);
if (Object.keys(data).length === 0) {
return null;
}
return this.createStructureFromRedisData(data);
};
getParkingStructures = async (): Promise<IParkingStructure[]> => {
const keys = await this.redisClient.keys('parking:structure:*');
const structures: IParkingStructure[] = [];
for (const key of keys) {
const data = await this.redisClient.hGetAll(key);
if (Object.keys(data).length > 0) {
structures.push(this.createStructureFromRedisData(data));
}
}
return structures;
};
removeParkingStructureIfExists = async (id: string): Promise<IParkingStructure | null> => {
const structure = await this.getParkingStructureById(id);
if (structure) {
const keys = this.createRedisKeys(id);
await this.redisClient.del([keys.structure, keys.timeSeries]);
this.dataLastAdded.delete(id);
return structure;
}
return null;
};
getHistoricalAveragesOfParkingStructureCounts = async (id: string, options: ParkingStructureCountOptions): Promise<HistoricalParkingAverageQueryResult[]> => {
return this.calculateAveragesFromRecords(id, options);
};
private createRedisKeys = (structureId: string) => ({
structure: `parking:structure:${structureId}`,
timeSeries: `parking:timeseries:${structureId}`
});
private createRedisHashFromStructure = (structure: IParkingStructure): Record<string, string> => ({
id: structure.id,
name: structure.name,
address: structure.address,
capacity: structure.capacity.toString(),
spotsAvailable: structure.spotsAvailable.toString(),
latitude: structure.coordinates.latitude.toString(),
longitude: structure.coordinates.longitude.toString(),
updatedTime: structure.updatedTime.toISOString()
});
private createStructureFromRedisData = (data: Record<string, string>): IParkingStructure => ({
id: data.id,
name: data.name,
address: data.address,
capacity: parseInt(data.capacity),
spotsAvailable: parseInt(data.spotsAvailable),
coordinates: {
latitude: parseFloat(data.latitude),
longitude: parseFloat(data.longitude)
},
updatedTime: new Date(data.updatedTime)
});
private shouldLogHistoricalData = (lastAdded: Date | undefined, currentTime: number): boolean => {
return !lastAdded || (currentTime - lastAdded.getTime()) >= this.loggingIntervalMs;
};
private addTimeSeriesDataPoint = async (timeSeriesKey: string, timestamp: number, value: number, structureId: string): Promise<void> => {
try {
await this.redisClient.sendCommand([
'TS.ADD',
timeSeriesKey,
timestamp.toString(),
value.toString(),
'LABELS',
'structureId',
structureId
]);
} catch (error) {
await this.createTimeSeriesAndAddDataPoint(timeSeriesKey, timestamp, value, structureId);
}
};
private createTimeSeriesAndAddDataPoint = async (timeSeriesKey: string, timestamp: number, value: number, structureId: string): Promise<void> => {
try {
await this.redisClient.sendCommand([
'TS.CREATE',
timeSeriesKey,
'RETENTION',
'2678400000', // one month
'LABELS',
'structureId',
structureId
]);
await this.redisClient.sendCommand([
'TS.ADD',
timeSeriesKey,
timestamp.toString(),
value.toString()
]);
} catch (createError) {
await this.redisClient.sendCommand([
'TS.ADD',
timeSeriesKey,
timestamp.toString(),
value.toString()
]);
}
};
private calculateAveragesFromRecords = async (
id: string,
options: ParkingStructureCountOptions
): Promise<HistoricalParkingAverageQueryResult[]> => {
const keys = this.createRedisKeys(id);
const { startUnixEpochMs, endUnixEpochMs, intervalMs } = options;
const results: HistoricalParkingAverageQueryResult[] = [];
let currentIntervalStart = startUnixEpochMs;
while (currentIntervalStart < endUnixEpochMs) {
const currentIntervalEnd = Math.min(currentIntervalStart + intervalMs, endUnixEpochMs);
try {
const aggregationResult = await this.redisClient.sendCommand([
'TS.RANGE',
keys.timeSeries,
currentIntervalStart.toString(),
currentIntervalEnd.toString(),
'AGGREGATION',
'AVG',
intervalMs.toString()
]) as [string, string][];
if (aggregationResult && aggregationResult.length > 0) {
const [, averageValue] = aggregationResult[0];
results.push({
fromUnixEpochMs: currentIntervalStart,
toUnixEpochMs: currentIntervalEnd,
averageSpotsAvailable: parseFloat(averageValue)
});
}
} catch (error) {
// If Redis aggregation fails, skip this interval
}
currentIntervalStart = currentIntervalEnd;
}
return results;
};
setLoggingInterval = (intervalMs: number): void => {
this.loggingIntervalMs = intervalMs;
};
}