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.

421 lines (420 loc) • 19.4 kB
/* * */ import { HTTP_STATUS, HttpException } from '@tmlmobilidade/consts'; import { Dates } from '@tmlmobilidade/dates'; import { MongoConnector } from '@tmlmobilidade/mongo'; import { generateRandomString } from '@tmlmobilidade/strings'; /* * */ export class MongoCollectionClass { // createSchema = null; mongoCollection; mongoConnector; updateSchema = null; async aggregate(pipeline, options) { // Perform the aggregation pipeline const aggregationResult = this.mongoCollection.aggregate(pipeline, options); // If returnResult is false, return the cursor directly if (options?.returnResult === false) return aggregationResult; // Otherwise, return the aggregated documents as an array return aggregationResult.toArray(); } /** * Gets all documents in the collection. * @returns A promise that resolves to an array of all documents */ async all() { return await this.mongoCollection.find().toArray(); } /** * Establishes a connection to the Mongo database and initializes the collection. * @param options Optional Mongo client connection options. * @throws Error if the environment variable for the database URI is missing or if the connection fails. */ async connect(options) { // Extract the database URI from environment variables const dbUri = process.env[this.getEnvName()]; if (!dbUri) throw new Error(`Missing ${this.getEnvName()} environment variable`); // Attempt to connect to the MongoDB database this.mongoConnector = new MongoConnector(dbUri, options); await this.mongoConnector.connect(); // Initialize the MongoDB collection instance this.mongoCollection = this.mongoConnector.client.db('production').collection(this.getCollectionName()); } /** * Counts documents matching the filter criteria. * @param filter The filter criteria to match documents. * @returns A promise that resolves to the count of matching documents. */ async count(filter) { return await this.mongoCollection.countDocuments(filter); } /** * Deletes a single document by its ID. * @param id The ID of the document to delete. * @returns A promise that resolves to the result of the delete operation. */ async deleteById(id, options) { // If forceIfLocked is not set then check if the document is locked. // If it is locked, then throw an error to prevent the operation. if (!options?.forceIfLocked) { const isLocked = await this.isLockedById(id); if (isLocked) throw new HttpException(HTTP_STATUS.FORBIDDEN, 'Document is locked and cannot be deleted'); } // Perform the delete operation const result = await this.deleteOne({ _id: { $eq: id } }, options); // Check if the delete operation was acknowledged if (!result.acknowledged) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to delete documents', result); // Return the result of the delete operation return result; } /** * Deletes multiple documents matching the filter criteria. * @param filter The filter criteria to match documents to delete. * @returns A promise that resolves to the result of the delete many operation. */ async deleteMany(filter) { const result = await this.mongoCollection.deleteMany(filter); if (!result.acknowledged) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to delete documents', result); return result; } /** * Deletes a single document matching the filter criteria. * @param filter The filter criteria to match the document to delete. * @returns A promise that resolves to the result of the delete operation. */ async deleteOne(filter, options) { // If forceIfLocked is not set then check if the document is locked. // If it is locked, then throw an error to prevent the operation. if (!options?.forceIfLocked) { const isLocked = await this.isLocked(filter); if (isLocked) throw new HttpException(HTTP_STATUS.FORBIDDEN, 'Document is locked and cannot be deleted'); } // Perform the delete operation const result = await this.mongoCollection.deleteOne(filter, options); if (!result.acknowledged) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to delete document', result); return result; } /** * Disconnects from the MongoDB database. */ async disconnect() { await this.mongoConnector.disconnect(); } /** * Finds all distinct values for a key in the collection. * @param key The key to find distinct values for. * @returns A promise that resolves to an array of distinct values for the given key. */ async distinct(key, filter) { return this.mongoCollection.distinct(key, filter); } /** * Checks if a document with the given key and value exists in the collection. * @param key The key to check for existence. * @param value The value of the key to check for existence. * @returns A promise that resolves to true if the document exists, false otherwise. */ async exists(key, value) { const filter = { [key]: value }; const doc = await this.mongoCollection.findOne(filter, { projection: { [key]: 1 } }); return !!doc; } /** * Checks if a document with the given ID exists in the collection. * @param id The ID of the document to check for existence. * @returns A promise that resolves to true if the document exists, false otherwise. */ async existsById(id) { const foundDoc = await this.mongoCollection.findOne({ _id: id }, { projection: { _id: 1 } }); return !!foundDoc; } /** * Finds a document by its ID. * @param id The ID of the document to find. * @param options Optional find options. * @returns A promise that resolves to the matching document or null if not found. */ async findById(id, options) { return this.mongoCollection.findOne({ _id: { $eq: id } }, options); } /** * Finds multiple documents matching the filter criteria with optional pagination and sorting. * @param filter (Optional) filter criteria to match documents. * @param options (Optional) find options. * @returns A promise that resolves to an array of matching documents. */ async findMany(filter, options) { return await this.mongoCollection.find(filter ?? {}, options).toArray(); } /** * Finds a single document matching the filter criteria. * @param filter The filter criteria to match the document. * @param options (Optional) find options. * @returns A promise that resolves to the matching document or null if not found. */ async findOne(filter, options) { return await this.mongoCollection.findOne(filter, options); } /** * Gets the MongoDB collection instance. * @returns The MongoDB collection instance */ async getCollection() { return this.mongoCollection; } /** * Gets the MongoDB connector instance. * @returns The MongoDB connector instance */ getMongoConnector() { return this.mongoConnector; } /** * Inserts multiple documents into the collection. * @param docs - The documents to insert * @param options - The options for the insert operation * @returns A promise that resolves to the result of the insert operation */ async insertMany(docs, { options, unsafe = false } = {}) { const newDocuments = docs.map((doc) => { return { ...doc, _id: doc._id || generateRandomString({ length: 5 }), created_at: doc.created_at || Dates.now('utc').unix_timestamp, created_by: doc.created_by || 'system', updated_at: doc.updated_at || Dates.now('utc').unix_timestamp, updated_by: doc.updated_by || 'system', }; }); // Ensure all documents have a unique ID const foundIds = await this.mongoCollection.find({ _id: { $in: newDocuments.map(doc => doc._id) } }, { projection: { _id: 1 } }).toArray(); for (const newDocument of newDocuments) { if (foundIds.find(id => id._id === newDocument._id)) { newDocument._id = generateRandomString({ length: 5 }); } } const parsedDocuments = []; for (const newDocument of newDocuments) { let parsedDocument = newDocument; if (!unsafe) { try { if (!this.createSchema) { throw new Error('No schema defined for insert operation. This is either an internal interface error or you should pass unsafe=true to the insert operation.'); } parsedDocument = this.createSchema.parse(newDocument); } catch (error) { throw new HttpException(HTTP_STATUS.BAD_REQUEST, error.message, { cause: error }); } } parsedDocuments.push(parsedDocument); } return await this.mongoCollection.insertMany(parsedDocuments, options); } /** * Inserts a single document into the collection. * @param doc The document to insert. * @param options The options for the insert operation. * @returns A promise that resolves to the result of the insert operation. */ async insertOne(doc, { options, unsafe = false } = {}) { // Setup a copy of the document to be inserted let parsedDocument = { ...doc }; // Validate the document against the create schema if unsafe is false if (!unsafe) { try { // If no create schema is defined, throw an error. // In safe mode, a schema is required to validate the document. if (!this.createSchema) throw new Error('No schema defined for insert operation. This is either an internal interface error or you should pass unsafe=true to the insert operation.'); // Validate the document against the create schema parsedDocument = this.createSchema.parse(parsedDocument); // Add the missing default fields, if present in the original document. // The schema might have omitted these fields, so we need to add them back. if (doc._id) parsedDocument._id = doc._id; if (doc.created_at) parsedDocument.created_at = doc.created_at; if (doc.created_by) parsedDocument.created_by = doc.created_by; if (doc.updated_at) parsedDocument.updated_at = doc.updated_at; if (doc.updated_by) parsedDocument.updated_by = doc.updated_by; } catch (error) { throw new HttpException(HTTP_STATUS.BAD_REQUEST, error.message, { cause: error }); } } // Add default fields if they are missing from the original document if (!doc.created_at) parsedDocument.created_at = Dates.now('utc').unix_timestamp; if (!doc.created_by) parsedDocument.created_by = 'system'; if (!doc.updated_at) parsedDocument.updated_at = Dates.now('utc').unix_timestamp; if (!doc.updated_by) parsedDocument.updated_by = 'system'; // Add the ID if it is missing from the original document // If the document is missing any default fields, add them if (!doc._id) { parsedDocument._id = generateRandomString({ length: 5 }); while (await this.findById(parsedDocument._id)) { parsedDocument._id = generateRandomString({ length: 5 }); } } // Attempt to insert the document into the collection const result = await this.mongoCollection.insertOne(parsedDocument, options); // Check if the insert operation was acknowledged if (!result.acknowledged) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to insert document', result); // If returnResult is false, return the insert result directly if (options?.returnResult === false) return result; // Otherwise, fetch and return the inserted document const insertedDoc = await this.findOne({ _id: { $eq: result.insertedId } }, options); if (!insertedDoc) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to find inserted document', result); return insertedDoc; } /** * Checks if a document with the given ID is locked or not. * @param id The ID of the document to check. * @returns A promise that resolves to the result of the check operation. */ async isLocked(filter) { // Fetch the document by its ID from the database const foundDoc = await this.findOne(filter); // If the document has a is_locked field and it resolves to a truthy value, // then the document is considered locked. if (foundDoc?.is_locked) return true; // Otherwise, the document is not locked. return false; } /** * Checks if a document with the given ID is locked or not. * @param id The ID of the document to check. * @returns A promise that resolves to the result of the check operation. */ async isLockedById(id) { // Fetch the document by its ID from the database const foundDoc = await this.findById(id); // If the document has an is_locked field and it resolves // to a truthy value, then the document is considered locked. if (foundDoc?.is_locked) return true; // Otherwise, the document is not locked. return false; } /** * Toggle the lock status of a document by its ID. * @param id The ID of the document to toggle lock status. * @param forceValue Optional boolean to explicitly set the lock status. * @returns A promise that resolves to the result of the update operation. */ async toggleLockById(id, forceValue) { // Get the current document from the database const foundDoc = await this.findById(id); if (!foundDoc) throw new Error('Document not found'); // Determine the new lock status const newLockStatus = forceValue !== undefined ? forceValue : !foundDoc.is_locked; // Update the document with the new lock status await this.mongoCollection.updateOne({ _id: { $eq: id } }, { $set: { is_locked: newLockStatus } }); } /** * Updates a document by its ID. * @param id The ID of the document to update. * @param updateFields The fields to update in the document. * @param options Optional options for the update operation. * @returns A promise that resolves to the result of the update operation. */ async updateById(id, updateFields, options) { // If forceIfLocked is not set then check if the document is locked. // If it is locked, then throw an error to prevent the operation. if (!options?.forceIfLocked) { const isLocked = await this.isLockedById(id); if (isLocked) throw new HttpException(HTTP_STATUS.FORBIDDEN, 'Document is locked and cannot be updated'); } // Perform the update operation return this.updateOne({ _id: { $eq: id } }, updateFields, options); } /** * Updates multiple documents matching the filter criteria. * @param filter - The filter criteria to match documents to update * @param updateFields - The fields to update in the documents * @param options - The options for the update operation * @returns A promise that resolves to the result of the update operation */ async updateMany(filter, updateFields, options) { let parsedUpdateFields = { ...updateFields, updated_at: updateFields.updated_at || Dates.now('utc').unix_timestamp, updated_by: updateFields.updated_by || 'system', }; if (this.updateSchema) { try { parsedUpdateFields = this.updateSchema.parse(updateFields); } catch (error) { throw new HttpException(HTTP_STATUS.BAD_REQUEST, error.message, { cause: error }); } } const result = await this.mongoCollection.updateMany(filter, { $set: { ...parsedUpdateFields, updated_at: Dates.now('utc').unix_timestamp } }, options); if (options?.returnResults === false) return result; if (!result.acknowledged) { throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to update documents', result); } const updatedDocuments = await this.findMany(filter, options); if (!updatedDocuments) { throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to find updated documents', result); } return updatedDocuments; } /** * Updates a single document matching the filter criteria. * @param filter The filter criteria to match the document to update. * @param updateFields The fields to update in the document. * @param options The options for the update operation. * @returns A promise that resolves to the result of the update operation. */ async updateOne(filter, updateFields, options) { // If forceIfLocked is not set then check if the document is locked. // If it is locked, then throw an error to prevent the operation. if (!options?.forceIfLocked) { const isLocked = await this.isLocked(filter); if (isLocked) throw new HttpException(HTTP_STATUS.FORBIDDEN, 'Document is locked and cannot be updated'); } // Perform the update operation let parsedUpdateFields = updateFields; if (this.updateSchema) { try { parsedUpdateFields = this.updateSchema.parse(updateFields); } catch (error) { throw new HttpException(HTTP_STATUS.BAD_REQUEST, error.message, { cause: error }); } } const result = await this.mongoCollection.updateOne(filter, { $set: { ...parsedUpdateFields, updated_at: Dates.now('utc').unix_timestamp } }, options); if (options?.returnResult === false) return result; if (!result.acknowledged) { throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to update documents', result); } const updatedDocument = await this.findOne(filter, options); if (!updatedDocument) throw new HttpException(HTTP_STATUS.INTERNAL_SERVER_ERROR, 'Failed to find updated document', result); return updatedDocument; } }