@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
JavaScript
/* 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);