From 722189dcf95c0cbc1b3f37090e149d9ff9a9f152 Mon Sep 17 00:00:00 2001 From: GPlay97 Date: Fri, 30 Jan 2026 22:22:11 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9C=A8=20implement=20StationAssociationH?= =?UTF-8?q?andler=20for=20handling=20log=20events=20and=20associating=20st?= =?UTF-8?q?ations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/station-association.handler.ts | 72 +++++++++++++++++++ src/logs/logs.module.ts | 7 +- src/logs/schemas/log.schema.ts | 3 + .../schemas/missing-station.schema.ts | 26 +++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/logs/handler/station-association.handler.ts create mode 100644 src/stations/schemas/missing-station.schema.ts diff --git a/src/logs/handler/station-association.handler.ts b/src/logs/handler/station-association.handler.ts new file mode 100644 index 0000000..4a81392 --- /dev/null +++ b/src/logs/handler/station-association.handler.ts @@ -0,0 +1,72 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { LOG_FINISHED_EVENT } from 'src/logs/entities/log.entity'; +import { Log } from 'src/logs/schemas/log.schema'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model, Types } from 'mongoose'; +import { LogsService } from 'src/logs/logs.service'; +import { TYPE } from 'src/logs/entities/type.entity'; +import { HISTORY_TYPE } from 'src/logs/entities/history-type.entity'; +import { Station } from 'src/stations/schemas/station.schema'; +import { MissingStation } from 'src/stations/schemas/missing-station.schema'; + +@Injectable() +export class StationAssociationHandler { + constructor( + private readonly logsService: LogsService, + @InjectModel(Station.name) private readonly stationModel: Model, + @InjectModel(MissingStation.name) private readonly missingStationModel: Model, + @InjectModel(Log.name) private readonly logModel: Model, + ) {} + + @OnEvent(LOG_FINISHED_EVENT) + async handleFinished(log: Log) { + try { + if (log.type !== TYPE.CHARGE) { + return; + } + + const history = await this.logsService.findOneWithHistory(log.akey, log._id.toString(), HISTORY_TYPE.LOCATION_DATA); + + const firstWithLocation = history.find((entry) => entry.latitude != null && entry.longitude != null); + + if (!firstWithLocation) { + return; + } + + const latitude = firstWithLocation.latitude; + const longitude = firstWithLocation.longitude; + + await this.stationModel.ensureIndexes(); + + const results = await this.stationModel.aggregate([ + { + $geoNear: { + near: { type: 'Point', coordinates: [longitude, latitude] }, + distanceField: 'distance', + spherical: true, + maxDistance: 200, + key: 'location', + }, + }, + { $sort: { distance: 1 } }, + { $limit: 1 }, + ]); + + if (results.length) { + const stationDoc = results[0]; + + await this.logModel.updateOne({ _id: log._id, akey: log.akey }, { $set: { station: stationDoc._id } }); + } else { + await this.missingStationModel.create({ + akey: log.akey, + logRef: log._id, + latitude, + longitude, + }); + } + } catch (error) { + Logger.error('Error in StationAssociationHandler', error); + } + } +} diff --git a/src/logs/logs.module.ts b/src/logs/logs.module.ts index 54476b2..76a039a 100644 --- a/src/logs/logs.module.ts +++ b/src/logs/logs.module.ts @@ -9,18 +9,23 @@ import { LastSyncHandler } from './handler/last-sync'; import { LastSync, LastSyncSchema } from './schemas/last-sync.schema'; import { CronHandler } from './handler/cron'; import { PremiumModule } from '../premium/premium.module'; +import { Station, StationSchema } from 'src/stations/schemas/station.schema'; +import { MissingStation, MissingStationSchema } from 'src/stations/schemas/missing-station.schema'; +import { StationAssociationHandler } from './handler/station-association.handler'; @Module({ controllers: [LogsController], - providers: [LogsService, MetadataHandler, LastSyncHandler, CronHandler], imports: [ AccountModule, MongooseModule.forFeature([ { name: Log.name, schema: LogSchema }, { name: LastSync.name, schema: LastSyncSchema }, + { name: Station.name, schema: StationSchema }, + { name: MissingStation.name, schema: MissingStationSchema }, ]), PremiumModule, ], + providers: [LogsService, MetadataHandler, LastSyncHandler, CronHandler, StationAssociationHandler], exports: [LogsService], }) export class LogsModule {} diff --git a/src/logs/schemas/log.schema.ts b/src/logs/schemas/log.schema.ts index 82bd5dc..043bd6f 100644 --- a/src/logs/schemas/log.schema.ts +++ b/src/logs/schemas/log.schema.ts @@ -124,6 +124,9 @@ export class Log { @Prop() thresholdReached: Date; + + @Prop({ type: Types.ObjectId, ref: 'Station' }) + station: Types.ObjectId; } export const LogSchema = SchemaFactory.createForClass(Log); diff --git a/src/stations/schemas/missing-station.schema.ts b/src/stations/schemas/missing-station.schema.ts new file mode 100644 index 0000000..4b6071b --- /dev/null +++ b/src/stations/schemas/missing-station.schema.ts @@ -0,0 +1,26 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document, Types } from 'mongoose'; + +export type MissingStationDocument = MissingStation & Document; + +@Schema({ timestamps: true }) +export class MissingStation { + _id: Types.ObjectId; + + @Prop({ required: true }) + akey: string; + + @Prop({ type: Types.ObjectId, ref: 'Log', required: true }) + logRef: Types.ObjectId; + + @Prop({ required: true }) + latitude: number; + + @Prop({ required: true }) + longitude: number; + + @Prop() + note: string; +} + +export const MissingStationSchema = SchemaFactory.createForClass(MissingStation); From aa0ec6e9d069f75f498c951a955753da70616b0c Mon Sep 17 00:00:00 2001 From: GPlay97 Date: Fri, 30 Jan 2026 22:37:50 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9C=A8=20enhance=20missing=20station=20h?= =?UTF-8?q?andling=20with=20geospatial=20indexing=20and=20aggregation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../handler/station-association.handler.ts | 29 +++++++++++++++---- .../schemas/missing-station.schema.ts | 22 ++++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/logs/handler/station-association.handler.ts b/src/logs/handler/station-association.handler.ts index 4a81392..cb311cc 100644 --- a/src/logs/handler/station-association.handler.ts +++ b/src/logs/handler/station-association.handler.ts @@ -58,12 +58,29 @@ export class StationAssociationHandler { await this.logModel.updateOne({ _id: log._id, akey: log.akey }, { $set: { station: stationDoc._id } }); } else { - await this.missingStationModel.create({ - akey: log.akey, - logRef: log._id, - latitude, - longitude, - }); + await this.missingStationModel.ensureIndexes(); + + const msResults = await this.missingStationModel.aggregate([ + { + $geoNear: { + near: { type: 'Point', coordinates: [longitude, latitude] }, + distanceField: 'distance', + spherical: true, + maxDistance: 200, + key: 'location', + }, + }, + { $sort: { distance: 1 } }, + { $limit: 1 }, + ]); + + if (!msResults.length) { + await this.missingStationModel.create({ + akey: log.akey, + logRef: log._id, + location: { type: 'Point', coordinates: [longitude, latitude] }, + }); + } } } catch (error) { Logger.error('Error in StationAssociationHandler', error); diff --git a/src/stations/schemas/missing-station.schema.ts b/src/stations/schemas/missing-station.schema.ts index 4b6071b..83a10c3 100644 --- a/src/stations/schemas/missing-station.schema.ts +++ b/src/stations/schemas/missing-station.schema.ts @@ -13,14 +13,26 @@ export class MissingStation { @Prop({ type: Types.ObjectId, ref: 'Log', required: true }) logRef: Types.ObjectId; - @Prop({ required: true }) - latitude: number; - - @Prop({ required: true }) - longitude: number; + @Prop({ + type: { + type: String, + enum: ['Point'], + default: 'Point', + }, + coordinates: { + type: [Number], + required: true, + }, + }) + location: { + type: 'Point'; + coordinates: [number, number]; + }; @Prop() note: string; } export const MissingStationSchema = SchemaFactory.createForClass(MissingStation); + +MissingStationSchema.index({ location: '2dsphere' }); From fc2388d7766100d0633f0baea7ae27ded8403692 Mon Sep 17 00:00:00 2001 From: GPlay97 Date: Tue, 3 Feb 2026 21:38:55 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20import=20paths=20for=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/logs/handler/station-association.handler.ts | 14 +++++++------- src/logs/logs.module.ts | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/logs/handler/station-association.handler.ts b/src/logs/handler/station-association.handler.ts index cb311cc..70a467c 100644 --- a/src/logs/handler/station-association.handler.ts +++ b/src/logs/handler/station-association.handler.ts @@ -1,14 +1,14 @@ import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; -import { LOG_FINISHED_EVENT } from 'src/logs/entities/log.entity'; -import { Log } from 'src/logs/schemas/log.schema'; +import { LOG_FINISHED_EVENT } from '../entities/log.entity'; +import { Log } from '../schemas/log.schema'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; -import { LogsService } from 'src/logs/logs.service'; -import { TYPE } from 'src/logs/entities/type.entity'; -import { HISTORY_TYPE } from 'src/logs/entities/history-type.entity'; -import { Station } from 'src/stations/schemas/station.schema'; -import { MissingStation } from 'src/stations/schemas/missing-station.schema'; +import { LogsService } from '../logs.service'; +import { TYPE } from '../entities/type.entity'; +import { HISTORY_TYPE } from '../entities/history-type.entity'; +import { Station } from '../../stations/schemas/station.schema'; +import { MissingStation } from '../../stations/schemas/missing-station.schema'; @Injectable() export class StationAssociationHandler { diff --git a/src/logs/logs.module.ts b/src/logs/logs.module.ts index 76a039a..3728fca 100644 --- a/src/logs/logs.module.ts +++ b/src/logs/logs.module.ts @@ -9,8 +9,8 @@ import { LastSyncHandler } from './handler/last-sync'; import { LastSync, LastSyncSchema } from './schemas/last-sync.schema'; import { CronHandler } from './handler/cron'; import { PremiumModule } from '../premium/premium.module'; -import { Station, StationSchema } from 'src/stations/schemas/station.schema'; -import { MissingStation, MissingStationSchema } from 'src/stations/schemas/missing-station.schema'; +import { Station, StationSchema } from '../stations/schemas/station.schema'; +import { MissingStation, MissingStationSchema } from '../stations/schemas/missing-station.schema'; import { StationAssociationHandler } from './handler/station-association.handler'; @Module({