production-evyos-systems-an.../ServicesApi/src/database/mongo/mongo.service.ts

258 lines
11 KiB
TypeScript

import { Injectable, Inject } from '@nestjs/common';
import { Db, Document, Collection, Filter, ObjectId, UpdateResult } from 'mongodb';
@Injectable()
export class MongoService {
private collection: Collection<Document>;
constructor(@Inject('MONGO_DB') private readonly db: Db) { this.collection = this.db.collection('mongoCache') }
async set(collectionName: string) { this.collection = this.db.collection(collectionName) }
async getDb() { return this.collection }
/**
* Find a document by UUID or create it if it doesn't exist
* @param data Document data with UUID field
* @returns The found or created document
*
* @example
* Create a new user or retrieve existing one
* const userData = { uuid: 'TOKEN:12345:user', name: 'John Doe', email: 'john@example.com' };
* const user = await mongoService.findOrCreate(userData);
*/
async findOrCreate(data: Record<string, any>): Promise<{ data: Document, isCreated: boolean }> {
if (!data.uuid) { throw new Error('UUID is required for findOrCreate operation') }
// Use direct UUID lookup instead of regex for exact match
const existingDoc = await this.collection.findOne({ uuid: data.uuid } as Filter<Document>);
if (existingDoc) { return { data: existingDoc, isCreated: false } }
const insertResult = await this.collection.insertOne(data);
if (!insertResult.acknowledged) { throw new Error('Failed to insert document') }
return { data: await this.getOne(insertResult.insertedId), isCreated: true };
}
/**
* Get all documents from the collection
* @returns Array of all documents
*
* @example
* Get all users in the collection
* const allUsers = await mongoService.getAll();
*/
async getAll(): Promise<Document[]> { return await this.collection.find().toArray() }
/**
* Find a document by ID key using regex pattern
* @param idKey ID key to search for
* @returns The found document
* @throws Error if document is not found
*
* @example
* Find a user by ID key
* const user = await mongoService.findOne('12345');
* This will search for documents with uuid matching pattern ^TOKEN:12345:
*/
async findOne(filter: Filter<Document>): Promise<Document> {
const result = await this.collection.findOne(filter);
if (!result) { throw new Error(`Document with ID key ${filter} not found`) }
return result;
}
/**
* Find multiple documents using a filter
* @param filter MongoDB filter
* @param limit Optional limit of results (default: no limit)
* @param skip Optional number of documents to skip (default: 0)
* @returns Array of matching documents
*
* @example
* Find active users with pagination
* const filter = { active: true } as Filter<Document>;
* const activeUsers = await mongoService.findMany(filter, 10, 20); // limit 10, skip 20
*
* @example
* Find users by role
* const admins = await mongoService.findMany({ role: 'admin' } as Filter<Document>);
*/
async findMany(filter: Filter<Document>, limit?: number, skip?: number): Promise<Document[]> {
let query = this.collection.find(filter);
if (typeof skip === 'number') { query = query.skip(skip) }
if (typeof limit === 'number') { query = query.limit(limit) }
return await query.toArray();
}
/**
* Get a document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @returns The found document
* @throws Error if document is not found
*
* @example
* Get a user by ObjectId
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const user = await mongoService.getOne(userId);
*/
async getOne(id: ObjectId): Promise<Document> {
const result = await this.collection.findOne({ _id: id });
if (!result) { throw new Error(`Document with ID ${id.toString()} not found`) }
return result;
}
/**
* Find documents by regex pattern on UUID field
* @param idKey ID key to search for
* @returns Array of matching documents
*
* @example
* Find all users with a specific ID key pattern
* const users = await mongoService.findByRegex('12345');
* This will return all documents with uuid matching pattern ^TOKEN:12345:
*/
async findByRegex(idKey: string): Promise<Document[]> {
if (!idKey) { throw new Error('ID key is required for regex search') }
const pattern = `^${idKey}`;
return await this.collection.find({ uuid: { $regex: pattern } } as Filter<Document>).toArray();
}
/**
* Update a single document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @param data Data to update
* @returns The updated document
* @throws Error if document is not found or update fails
*
* @example
* Update a user's profile
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const updates = { name: 'Jane Doe', lastLogin: new Date() };
* const updatedUser = await mongoService.updateOne(userId, updates);
*/
async updateOne(id: ObjectId, data: Record<string, any>): Promise<Document> {
const updateResult = await this.collection.updateOne(
{ _id: id },
{ $set: data }
);
if (!updateResult.acknowledged) { throw new Error('Update operation failed') }
if (updateResult.matchedCount === 0) { throw new Error(`Document with ID ${id.toString()} not found`) }
return await this.getOne(id);
}
/**
* Update multiple documents matching a filter
* @param filter MongoDB filter
* @param data Data to update
* @returns Update result with count of modified documents
* @throws Error if update fails
*
* @example
* Mark all inactive users as archived
* const filter = { active: false } as Filter<Document>;
* const updates = { status: 'archived', archivedAt: new Date() };
* const result = await mongoService.updateMany(filter, updates);
* console.log(`${result.modifiedCount} users archived`);
*/
async updateMany(filter: Filter<Document>, data: Record<string, any>): Promise<UpdateResult> {
const updateResult = await this.collection.updateMany(filter, { $set: data });
if (!updateResult.acknowledged) { throw new Error('Update operation failed') }
return updateResult;
}
/**
* Delete a document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @returns True if document was deleted, false otherwise
*
* @example
* Delete a user account
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const deleted = await mongoService.deleteOne(userId);
* if (deleted) console.log('User successfully deleted');
*/
async deleteOne(id: ObjectId): Promise<boolean> {
const deleteResult = await this.collection.deleteOne({ _id: id });
return deleteResult.acknowledged && deleteResult.deletedCount > 0;
}
/**
* Delete multiple documents matching a filter
* @param filter MongoDB filter
* @returns Number of deleted documents
*
* @example
* Delete all expired sessions
* const filter = { expiresAt: { $lt: new Date() } } as Filter<Document>;
* const count = await mongoService.deleteMany(filter);
* console.log(`${count} expired sessions deleted`);
*/
async deleteMany(filter: Filter<Document>): Promise<number> {
const deleteResult = await this.collection.deleteMany(filter);
return deleteResult.acknowledged ? deleteResult.deletedCount : 0;
}
/**
* Find documents by regex pattern on any specified field
* @param field The field name to apply the regex filter on
* @param value The value to search for in the field
* @param options Optional regex options (e.g., 'i' for case-insensitive)
* @param prefix Optional prefix to add before the value (default: '')
* @param suffix Optional suffix to add after the value (default: '')
* @returns Array of matching documents
*
* @example
* Find users with email from a specific domain (case-insensitive)
* const gmailUsers = await mongoService.findByFieldRegex('email', 'gmail.com', 'i');
*
* @example
* Find users with names starting with 'J'
* const usersStartingWithJ = await mongoService.findByFieldRegex('name', 'J', 'i', '^');
*
* @example
* Find users with phone numbers ending in specific digits
* const specificPhoneUsers = await mongoService.findByFieldRegex('phone', '5555', '', '', '$');
*/
async findByFieldRegex(field: string, value: string, options?: string, prefix: string = '', suffix: string = ''): Promise<Document[]> {
if (!field || !value) { throw new Error('Field name and value are required for regex search') }
const pattern = `${prefix}${value}${suffix}`;
const query: Record<string, any> = {};
query[field] = { $regex: pattern };
if (options) { query[field].$options = options; }
return await this.collection.find(query as unknown as Filter<Document>).toArray();
}
/**
* Find documents by regex pattern across all fields (including nested)
* @param value The value to search for
* @param options Optional regex options (e.g., 'i' for case-insensitive)
* @returns Array of matching documents
*
* @example
* Find any document containing a specific value anywhere
* const docs = await mongoService.findByRegexAcrossFields('someValue', 'i');
*/
async findByRegexAcrossFields(value: string, options?: string, searchType: 'value' | 'key' | 'both' = 'value'): Promise<Document[]> {
if (!value) { throw new Error('Search value is required') }
const query: any = { $or: [] };
if (searchType === 'value' || searchType === 'both') {
query.$or.push(
{ '$expr': { $regexMatch: { input: { $toString: '$$ROOT' }, regex: value, options } } },
{ 'data': { $type: 'object', $regex: value, $options: options } }
);
}
if (searchType === 'key' || searchType === 'both') {
query.$where = function () {
const searchRegex = new RegExp(value, options);
function checkKeys(obj: Record<string, any>) {
for (const key in obj) {
if (searchRegex.test(key)) return true; if (obj[key] && typeof obj[key] === 'object') { if (checkKeys(obj[key])) return true }
}
return false;
} return checkKeys(this)
}.toString();
}
return await this.collection.find(query as unknown as Filter<Document>).toArray();
}
}