UNPKG

@tmlmobilidade/interfaces

Version:

This package provides SDK-style connectors for interacting with databases (e.g., stops, plans, rides, alerts) and external providers (e.g., authentication, storage). It simplifies data access and integration across projects.

212 lines (211 loc) • 9.33 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable perfectionist/sort-classes */ import { HTTP_STATUS, HttpException } from '@tmlmobilidade/consts'; import { MongoConnector } from '@tmlmobilidade/mongo'; import { asyncSingletonProxy } from '@tmlmobilidade/utils'; /* * */ class LocationsClass { static _instance; collections; mongoConnector; constructor() { this.collections = {}; } static async getInstance() { if (!this._instance) { const instance = new LocationsClass(); await instance.connect(); this._instance = instance; } return this._instance; } /* Public Methods */ /* Count */ countCensus = async (filter) => await this.count(this.collections.census, filter); countDistricts = async (filter) => { const _filter = this.convertFilter(filter); return await this.count(this.collections.districts, _filter); }; countLocalities = async (filter) => { const _filter = this.convertFilter(filter); return await this.count(this.collections.localities, _filter); }; countMunicipalities = async (filter) => { const _filter = this.convertFilter(filter); return await this.count(this.collections.municipalities, _filter); }; countParishes = async (filter) => { const _filter = this.convertFilter(filter); return await this.count(this.collections.parishes, _filter); }; /* Find All */ findCensus = async (filter, options) => await this.findMany(this.collections.census, filter, options); findDistricts = async (filter, options) => { const _filter = this.convertFilter(filter); const documents = await this.findMany(this.collections.districts, _filter, options); return documents.map(doc => this.transformDocument(doc)); }; findLocalities = async (filter, options) => { const _filter = this.convertFilter(filter); const documents = await this.findMany(this.collections.localities, _filter, options); return documents.map(doc => this.transformDocument(doc)); }; findMunicipalities = async (filter, options) => { const _filter = this.convertFilter(filter); const documents = await this.findMany(this.collections.municipalities, _filter, options); return documents.map(doc => this.transformDocument(doc)); }; findParishes = async (filter, options) => { const _filter = this.convertFilter(filter); const documents = await this.findMany(this.collections.parishes, _filter, options); return documents.map(doc => this.transformDocument(doc)); }; /* Find By Id */ findCensusById = async (id, options) => await this.findById(this.collections.census, id, options); findDistrictById = async (id, options) => { const document = await this.findById(this.collections.districts, id, options); return document ? this.transformDocument(document) : null; }; findLocalityById = async (id, options) => { const document = await this.findById(this.collections.localities, id, options); return document ? this.transformDocument(document) : null; }; findMunicipalityById = async (id, options) => { const document = await this.findById(this.collections.municipalities, id, options); return document ? this.transformDocument(document) : null; }; findParishById = async (id, options) => { const document = await this.findById(this.collections.parishes, id, options); return document ? this.transformDocument(document) : null; }; /* Find By Geo */ findMunicipalitiesByGeo = async (lat, lon, options) => { const document = await this.findOne(this.collections.municipalities, this.geoFilter(lat, lon), options); return document ? this.transformDocument(document) : null; }; findParishesByGeo = async (lat, lon, options) => { const document = await this.findOne(this.collections.parishes, this.geoFilter(lat, lon), options); return document ? this.transformDocument(document) : null; }; findDistrictsByGeo = async (lat, lon, options) => { const document = await this.findOne(this.collections.districts, this.geoFilter(lat, lon), options); return document ? this.transformDocument(document) : null; }; findLocalitiesByGeo = async (lat, lon, options) => { const document = await this.findOne(this.collections.localities, this.geoFilter(lat, lon), options); return document ? this.transformDocument(document) : null; }; findCensusByGeo = async (lat, lon, options) => await this.findOne(this.collections.census, this.geoFilter(lat, lon), options); async findLocationByGeo(lat, lon, { census = false } = {}) { if (!lat || !lon) throw new HttpException(HTTP_STATUS.BAD_REQUEST, 'Missing latitude or longitude'); const municipality = await this.findMunicipalitiesByGeo(lat, lon, { projection: { _id: 1, properties: 1 } }); const parish = await this.findParishesByGeo(lat, lon, { projection: { _id: 1, properties: 1 } }); const district = await this.findDistrictsByGeo(lat, lon, { projection: { _id: 1, properties: 1 } }); const locality = await this.findLocalitiesByGeo(lat, lon, { projection: { _id: 1, properties: 1 } }); const _census = census ? await this.findCensusByGeo(lat, lon, { projection: { _id: 1, properties: 1 } }) : undefined; return { census: { ..._census?.properties, _id: _census?._id }, district: district, latitude: lat, locality: locality, longitude: lon, municipality: municipality, parish: parish, }; } ; /* Private Methods - Database Connection */ async connect() { const dbUri = process.env.DATABASE_URI; if (!dbUri) throw new Error(`Missing DATABASE_URI environment variable`); try { this.mongoConnector = new MongoConnector(dbUri); await this.mongoConnector.connect(); const db = this.mongoConnector.client.db('production'); this.collections = { census: db.collection('census'), districts: db.collection('districts'), localities: db.collection('localities'), municipalities: db.collection('municipalities'), parishes: db.collection('parishes'), }; } catch (error) { throw new Error(`Error connecting to MongoDB`, { cause: error }); } } /* Private Methods - Database Operations */ async findById(collection, id, options) { return collection.findOne({ _id: { $eq: id } }, options); } async findMany(collection, filter = {}, options) { const query = collection.find(filter, options); return query.toArray(); } async findOne(collection, filter, options) { return collection.findOne(filter, options); } async count(collection, filter = {}) { return collection.countDocuments(filter); } geoFilter = (lat, lon) => ({ geometry: { $geoIntersects: { $geometry: { coordinates: [lon, lat], type: 'Point' } } } }); transformDocument(doc) { const geojson = doc.geometry ? { geometry: doc.geometry, properties: {}, type: doc.type, } : undefined; return { _id: doc._id, ...doc?.properties, geojson, }; } convertFilterField = (key) => { if (key.startsWith('geojson.geometry')) return key.replace('geojson.geometry', 'geometry'); if (key.startsWith('geojson.type')) return key.replace('geojson.type', 'type'); if (key.startsWith('geojson')) return key.replace('geojson', ''); // fallback if (!key.startsWith('_id')) return `properties.${key}`; return key; }; convertFilter = (filter) => { if (!filter || typeof filter !== 'object') return filter; if (Array.isArray(filter)) { return filter.map(this.convertFilter); } const output = {}; for (const [key, value] of Object.entries(filter)) { if (key.startsWith('$')) { // Recursive operator like $and, $or, $nor if (Array.isArray(value)) { output[key] = value.map(this.convertFilter); } else if (typeof value === 'object') { output[key] = this.convertFilter(value); } else { output[key] = value; } } else { const newKey = this.convertFilterField(key); if (typeof value === 'object' && value !== null && !Array.isArray(value)) { output[newKey] = this.convertFilter(value); // nested operator (e.g., $gt) } else { output[newKey] = value; } } } return output; }; } /* * */ export const locations = asyncSingletonProxy(LocationsClass);