import {Timestamp} from "firebase-admin/firestore"; import {DriverAdapter, DriverEntity, DriverEntityData, DriverStatus} from "../entities/driver_entity"; import {distanceMeters, encodeGeohash, geohashNeighbors} from "../geohash"; // ~4.9km x 4.9km cells at the equator — coarse enough that the 3x3 neighbor // grid comfortably covers the default search radius (see drive_functions.ts). const GEOHASH_QUERY_PRECISION = 5; const GEOHASH_STORAGE_PRECISION = 9; export class DriverRepository { constructor(private db: FirebaseFirestore.Firestore) {} private collection() { return this.db.collection("drivers"); } async getFromId(driverId: string): Promise { const snapshot = await this.collection().doc(driverId).get(); if (!snapshot.exists) return null; return DriverEntity.fromDocument(snapshot as FirebaseFirestore.QueryDocumentSnapshot); } async upsertProfile(driverId: string, data: Partial): Promise { const now = Timestamp.now(); const existing = await this.getFromId(driverId); await this.collection() .doc(driverId) .set( DriverAdapter.toMap({ ...data, rating: existing?.rating ?? 0, ratingCount: existing?.ratingCount ?? 0, createdAt: existing?.createdAt ?? now, updatedAt: now, }), {merge: true} ); } async updateLocation(driverId: string, lat: number, lng: number, status: DriverStatus): Promise { await this.collection() .doc(driverId) .set( DriverAdapter.toMap({ currentLat: lat, currentLng: lng, geohash: encodeGeohash(lat, lng, GEOHASH_STORAGE_PRECISION), status, updatedAt: Timestamp.now(), }), {merge: true} ); } /** * Finds online drivers within `radiusMeters` of (lat, lng). * * Firestore has no native geo-radius query: query the geohash cell the * point falls in plus its 8 neighbors (see geohash.ts), then filter the * merged results down to the real circle with a haversine check — the * geohash query is only a cheap pre-filter, not the final radius. Needs a * composite index on (status ASC, geohash ASC) — see firestore.indexes.json. */ async findNearbyOnline(lat: number, lng: number, radiusMeters: number): Promise { const centerCell = encodeGeohash(lat, lng, GEOHASH_QUERY_PRECISION); const cells = geohashNeighbors(centerCell); const snapshots = await Promise.all( cells.map((cell) => this.collection() .where("status", "==", "online") .where("geohash", ">=", cell) .where("geohash", "<", cell + "~") .get() ) ); const byId = new Map(); for (const snapshot of snapshots) { for (const doc of snapshot.docs) { byId.set(doc.id, DriverEntity.fromDocument(doc)); } } return Array.from(byId.values()).filter((driver) => { if (driver.currentLat == null || driver.currentLng == null) return false; return distanceMeters(lat, lng, driver.currentLat, driver.currentLng) <= radiusMeters; }); } }