diff --git a/src/models/DriverProfile.ts b/src/models/DriverProfile.ts index 91ad4c1..f5fc265 100644 --- a/src/models/DriverProfile.ts +++ b/src/models/DriverProfile.ts @@ -1,5 +1,6 @@ import mongoose, { Schema } from 'mongoose'; import { IDriverProfile, ReputationTier } from '../interfaces/IDriverProfile'; +import { nowUTC } from '../utils/dateUtils'; const vehicleDetailsSchema = new Schema( { @@ -16,7 +17,7 @@ const vehicleDetailsSchema = new Schema( year: { type: Number, min: [1980, 'Vehicle year must be 1980 or later'], - max: [new Date().getFullYear() + 1, 'Vehicle year cannot be in the future'], + max: [nowUTC().getUTCFullYear() + 1, 'Vehicle year cannot be in the future'], }, plateNumber: { type: String, diff --git a/src/services/escrowService.ts b/src/services/escrowService.ts index 1636c71..32162fb 100644 --- a/src/services/escrowService.ts +++ b/src/services/escrowService.ts @@ -4,6 +4,7 @@ import Escrow, { EscrowLockStatus, IEscrow } from '../models/Escrow'; import { sorobanService } from '../blockchain/soroban.service'; import AppError from '../utils/AppError'; import logger from '../config/logger'; +import { nowUTC } from '../utils/dateUtils'; // ─── DTOs ────────────────────────────────────────────────────────────────────── @@ -49,7 +50,7 @@ export interface ResolveEscrowInput { * or expiresAt field in the current Escrow model. */ export const scanForExpiredEscrows = async (): Promise => { - const now = new Date(); + const now = nowUTC(); // Note: Commented out until EscrowStatus.EXPIRED and expiresAt field are added to model // const expiredCandidates = await Escrow.find({ diff --git a/src/services/idempotency.service.ts b/src/services/idempotency.service.ts index e63f8b0..90a0cdf 100644 --- a/src/services/idempotency.service.ts +++ b/src/services/idempotency.service.ts @@ -4,6 +4,7 @@ import IdempotencyRecord, { IdempotencyStatus } from '../models/IdempotencyRecor import env from '../config/env'; import logger from '../config/logger'; import { AppError } from '../utils/AppError'; +import { toUTC } from '../utils/dateUtils'; /** Payload stored against an idempotency key once a request completes. */ export interface IdempotencyPayload { @@ -80,7 +81,7 @@ export class IdempotencyService { // ─── MongoDB helpers ──────────────────────────────────────────────────────── private expiresAt(): Date { - return new Date(Date.now() + this.ttlSeconds * 1000); + return toUTC(Date.now() + this.ttlSeconds * 1000); } private async getFromMongo(key: string, endpoint: string): Promise { diff --git a/src/sockets/location.service.ts b/src/sockets/location.service.ts index de00385..381659c 100644 --- a/src/sockets/location.service.ts +++ b/src/sockets/location.service.ts @@ -3,6 +3,7 @@ import { Server as SocketIOServer } from 'socket.io'; import logger from '../config/logger'; import { LocationUpdate } from '../models/LocationUpdate'; import { redisClient } from '../config/redis'; +import { toUTC, nowUTC } from '../utils/dateUtils'; import { DriverLocationUpdatePayload, LocationBroadcastPayload, @@ -225,7 +226,7 @@ export class LocationService { } const capturedAt = payload.capturedAt ?? Date.now(); - const receivedAt = new Date().toISOString(); + const receivedAt = nowUTC().toISOString(); // ── 2. Validate timestamp ──────────────────────────────────────────────── const timestampError = this.validateTimestamp(capturedAt); @@ -281,7 +282,7 @@ export class LocationService { driverId: new Types.ObjectId(driverId), deliveryId: new Types.ObjectId(payload.deliveryId), coordinates: { lat: payload.lat, lng: payload.lng }, - capturedAt: new Date(capturedAt), + capturedAt: toUTC(capturedAt), isOfflineSync: false, status: 'pending', }); diff --git a/src/sockets/sync.service.ts b/src/sockets/sync.service.ts index 185a189..195e8e7 100644 --- a/src/sockets/sync.service.ts +++ b/src/sockets/sync.service.ts @@ -1,6 +1,7 @@ import { Types } from 'mongoose'; import logger from '../config/logger'; import { LocationUpdate, ILocationUpdate } from '../models/LocationUpdate'; +import { toUTC, nowUTC } from '../utils/dateUtils'; import { LocationSyncPayload, OfflineLocationPoint, @@ -41,7 +42,7 @@ export class SyncService { driverId: string, payload: LocationSyncPayload, ): Promise { - const processedAt = new Date().toISOString(); + const processedAt = nowUTC().toISOString(); // ── 1. Validate driverId ───────────────────────────────────────────────── if (!Types.ObjectId.isValid(driverId)) { @@ -86,7 +87,7 @@ export class SyncService { } // ── 4. Fetch existing capturedAt values for this driver to detect dupes ── - const capturedAtDates = validPoints.map((p) => new Date(p.capturedAt)); + const capturedAtDates = validPoints.map((p) => toUTC(p.capturedAt)); const existingDocs = await LocationUpdate.find( { @@ -96,7 +97,7 @@ export class SyncService { { capturedAt: 1 }, ).lean[]>(); - const existingSet = new Set(existingDocs.map((d) => new Date(d.capturedAt).getTime())); + const existingSet = new Set(existingDocs.map((d) => toUTC(d.capturedAt).getTime())); // ── 5. Build insertable documents, deduplicating within batch ───────────── const seenInBatch = new Set(); @@ -116,7 +117,7 @@ export class SyncService { driverId: driverObjectId, deliveryId: point.deliveryId ? new Types.ObjectId(point.deliveryId) : undefined, coordinates: { lat: point.lat, lng: point.lng }, - capturedAt: new Date(ts), + capturedAt: toUTC(ts), isOfflineSync: true, status: 'pending', }); diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts new file mode 100644 index 0000000..b6738eb --- /dev/null +++ b/src/utils/dateUtils.ts @@ -0,0 +1,20 @@ +/** + * Returns the current date/time as a UTC Date object. + */ +export const nowUTC = (): Date => { + return new Date(); +}; + +/** + * Parses a date string, number, or Date into a UTC Date object. + */ +export const toUTC = (date: Date | string | number): Date => { + return new Date(date); +}; + +/** + * Formats a date to an ISO 8601 string in UTC. + */ +export const toISOUTC = (date: Date): string => { + return date.toISOString(); +};