import { Injectable, Inject } from '@nestjs/common'; import { Db, Document, Collection, Filter, ObjectId, UpdateResult } from 'mongodb'; @Injectable() export class MongoService { private collection: Collection; 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): 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); 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 { 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): Promise { 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; * 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); */ async findMany(filter: Filter, limit?: number, skip?: number): Promise { 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 { 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 { 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).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): Promise { 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; * const updates = { status: 'archived', archivedAt: new Date() }; * const result = await mongoService.updateMany(filter, updates); * console.log(`${result.modifiedCount} users archived`); */ async updateMany(filter: Filter, data: Record): Promise { 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 { 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; * const count = await mongoService.deleteMany(filter); * console.log(`${count} expired sessions deleted`); */ async deleteMany(filter: Filter): Promise { 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 { if (!field || !value) { throw new Error('Field name and value are required for regex search') } const pattern = `${prefix}${value}${suffix}`; const query: Record = {}; query[field] = { $regex: pattern }; if (options) { query[field].$options = options; } return await this.collection.find(query as unknown as Filter).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 { 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) { 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).toArray(); } }