UNPKG

@ideal-photography/shared

Version:

Shared MongoDB and utility logic for Ideal Photography PWAs: users, products, services, bookings, orders/cart, galleries, reviews, notifications, campaigns, settings, audit logs, minimart items/orders, and push notification subscriptions.

460 lines (399 loc) 15.2 kB
/** * Database query optimization utilities * Provides performance enhancements for MongoDB queries */ /** * Create optimized aggregation pipelines with proper indexing hints */ export const createOptimizedPipeline = (baseQuery = {}) => { return { // Add index hints for better performance addIndexHint: (indexName) => ({ $addFields: { __indexHint: indexName } }), // Optimize text search queries addTextSearch: (searchTerm, fields = []) => { if (!searchTerm) return null; return { $match: { $or: [ // Text index search (if available) { $text: { $search: searchTerm } }, // Fallback to regex search on specific fields ...fields.map(field => ({ [field]: { $regex: searchTerm, $options: 'i' } })) ] } }; }, // Add efficient pagination addPagination: (page = 1, limit = 20) => [ { $skip: (page - 1) * limit }, { $limit: limit } ], // Add sorting with index optimization addSort: (sortField, sortOrder = 1) => ({ $sort: { [sortField]: sortOrder, _id: 1 } // Always include _id for consistent pagination }), // Project only needed fields to reduce network overhead addProjection: (fields) => ({ $project: fields.reduce((acc, field) => { acc[field] = 1; return acc; }, {}) }), // Add lookup with proper pipeline optimization addLookup: (from, localField, foreignField, as, pipeline = []) => ({ $lookup: { from, localField, foreignField, as, pipeline: [ // Only project necessary fields in lookups { $project: { _id: 1, name: 1, email: 1, status: 1 } }, ...pipeline ] } }), // Count total documents efficiently addCountFacet: () => ({ $facet: { data: [{ $match: {} }], count: [{ $count: 'total' }] } }) }; }; /** * Query performance monitoring */ export class QueryPerformanceMonitor { constructor() { this.queries = new Map(); this.slowQueryThreshold = 1000; // 1 second } startQuery(queryId, operation, collection) { this.queries.set(queryId, { operation, collection, startTime: Date.now(), endTime: null, duration: null }); } endQuery(queryId) { const query = this.queries.get(queryId); if (query) { query.endTime = Date.now(); query.duration = query.endTime - query.startTime; if (query.duration > this.slowQueryThreshold) { console.warn(`Slow query detected:`, { operation: query.operation, collection: query.collection, duration: `${query.duration}ms` }); } } } getStats() { const queries = Array.from(this.queries.values()).filter(q => q.duration !== null); const totalQueries = queries.length; const slowQueries = queries.filter(q => q.duration > this.slowQueryThreshold); const avgDuration = totalQueries > 0 ? queries.reduce((sum, q) => sum + q.duration, 0) / totalQueries : 0; return { totalQueries, slowQueries: slowQueries.length, averageDuration: Math.round(avgDuration), slowQueryPercentage: totalQueries > 0 ? (slowQueries.length / totalQueries * 100).toFixed(1) : 0 }; } clear() { this.queries.clear(); } } // Global performance monitor instance export const queryMonitor = new QueryPerformanceMonitor(); /** * Optimized query builder for common operations */ export const QueryBuilder = { // Build user queries with proper indexing users: { list: (filters = {}, pagination = {}) => { const pipeline = []; const { page = 1, limit = 20, sortBy = 'createdAt', sortOrder = -1 } = pagination; const { search, isActive, role, isEmailVerified, verificationStatus } = filters; // Add search filter if (search) { pipeline.push({ $match: { $or: [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } }, { phone: { $regex: search, $options: 'i' } } ] } }); } // Add status filters const matchConditions = {}; if (isActive !== undefined) matchConditions.isActive = isActive; if (role) matchConditions.role = role; if (isEmailVerified !== undefined) matchConditions.isEmailVerified = isEmailVerified; if (verificationStatus) { // Handle verification status mapping if (verificationStatus === 'verified') { matchConditions.isEmailVerified = true; } else if (verificationStatus === 'pending') { matchConditions.isEmailVerified = false; } } if (Object.keys(matchConditions).length > 0) { pipeline.push({ $match: matchConditions }); } // Add sorting pipeline.push({ $sort: { [sortBy]: sortOrder, _id: 1 } }); // Add pagination pipeline.push({ $skip: (page - 1) * limit }); pipeline.push({ $limit: limit }); // Project only necessary fields pipeline.push({ $project: { _id: 1, uuid: 1, name: 1, email: 1, phone: 1, isActive: 1, role: 1, permissions: 1, isEmailVerified: 1, verification: 1, createdAt: 1, lastLogin: 1, avatar: 1 } }); return pipeline; }, count: (filters = {}) => { const pipeline = []; const { search, isActive, role, isEmailVerified, verificationStatus } = filters; // Add search filter if (search) { pipeline.push({ $match: { $or: [ { name: { $regex: search, $options: 'i' } }, { email: { $regex: search, $options: 'i' } }, { phone: { $regex: search, $options: 'i' } } ] } }); } // Add status filters const matchConditions = {}; if (isActive !== undefined) matchConditions.isActive = isActive; if (role) matchConditions.role = role; if (isEmailVerified !== undefined) matchConditions.isEmailVerified = isEmailVerified; if (verificationStatus) { // Handle verification status mapping if (verificationStatus === 'verified') { matchConditions.isEmailVerified = true; } else if (verificationStatus === 'pending') { matchConditions.isEmailVerified = false; } } if (Object.keys(matchConditions).length > 0) { pipeline.push({ $match: matchConditions }); } pipeline.push({ $count: 'total' }); return pipeline; } }, // Build booking queries with user population bookings: { list: (filters = {}, pagination = {}) => { const pipeline = []; const { page = 1, limit = 20, sortBy = 'scheduledDate', sortOrder = -1 } = pagination; const { search, status, serviceType, dateFrom, dateTo } = filters; // Add date range filter if (dateFrom || dateTo) { const dateFilter = {}; if (dateFrom) dateFilter.$gte = new Date(dateFrom); if (dateTo) dateFilter.$lte = new Date(dateTo); pipeline.push({ $match: { scheduledDate: dateFilter } }); } // Add status and service type filters const matchConditions = {}; if (status) matchConditions.status = status; if (serviceType) matchConditions.serviceType = serviceType; if (Object.keys(matchConditions).length > 0) { pipeline.push({ $match: matchConditions }); } // Add user lookup for search pipeline.push({ $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user', pipeline: [ { $project: { firstName: 1, lastName: 1, email: 1, phone: 1 } } ] } }); // Unwind user array pipeline.push({ $unwind: '$user' }); // Add search filter (after user lookup) if (search) { pipeline.push({ $match: { $or: [ { 'user.firstName': { $regex: search, $options: 'i' } }, { 'user.lastName': { $regex: search, $options: 'i' } }, { 'user.email': { $regex: search, $options: 'i' } }, { serviceType: { $regex: search, $options: 'i' } } ] } }); } // Add sorting pipeline.push({ $sort: { [sortBy]: sortOrder, _id: 1 } }); // Add pagination pipeline.push({ $skip: (page - 1) * limit }); pipeline.push({ $limit: limit }); return pipeline; } }, // Build service queries services: { list: (filters = {}, pagination = {}) => { const pipeline = []; const { page = 1, limit = 20, sortBy = 'name', sortOrder = 1 } = pagination; const { search, category, active, featured } = filters; // Add search filter if (search) { pipeline.push({ $match: { $or: [ { name: { $regex: search, $options: 'i' } }, { description: { $regex: search, $options: 'i' } }, { category: { $regex: search, $options: 'i' } } ] } }); } // Add filters const matchConditions = {}; if (category) matchConditions.category = category; if (active !== undefined) matchConditions.isActive = active; if (featured !== undefined) matchConditions.isFeatured = featured; if (Object.keys(matchConditions).length > 0) { pipeline.push({ $match: matchConditions }); } // Add sorting pipeline.push({ $sort: { [sortBy]: sortOrder, _id: 1 } }); // Add pagination pipeline.push({ $skip: (page - 1) * limit }); pipeline.push({ $limit: limit }); return pipeline; } } }; /** * Database index recommendations */ export const IndexRecommendations = { users: [ // Single field indexes { email: 1 }, { accountStatus: 1 }, { role: 1 }, { createdAt: -1 }, { lastLogin: -1 }, // Compound indexes for common query patterns { accountStatus: 1, role: 1 }, { accountStatus: 1, createdAt: -1 }, { role: 1, createdAt: -1 }, // Text search index { firstName: 'text', lastName: 'text', email: 'text' } ], bookings: [ // Single field indexes { userId: 1 }, { status: 1 }, { serviceType: 1 }, { scheduledDate: -1 }, { createdAt: -1 }, { paymentStatus: 1 }, // Compound indexes { status: 1, scheduledDate: -1 }, { userId: 1, status: 1 }, { serviceType: 1, scheduledDate: -1 }, { status: 1, createdAt: -1 } ], services: [ // Single field indexes { category: 1 }, { isActive: 1 }, { isFeatured: 1 }, { price: 1 }, { createdAt: -1 }, // Compound indexes { isActive: 1, category: 1 }, { isActive: 1, isFeatured: 1 }, { category: 1, price: 1 }, // Text search index { name: 'text', description: 'text', category: 'text' } ], orders: [ // Single field indexes { userId: 1 }, { status: 1 }, { paymentStatus: 1 }, { createdAt: -1 }, { totalAmount: 1 }, // Compound indexes { userId: 1, status: 1 }, { status: 1, createdAt: -1 }, { paymentStatus: 1, createdAt: -1 } ] }; /** * Create database indexes automatically */ export const createOptimizedIndexes = async (db) => { try { console.log('Creating optimized database indexes...'); for (const [collection, indexes] of Object.entries(IndexRecommendations)) { const coll = db.collection(collection); for (const index of indexes) { try { await coll.createIndex(index); console.log(`✓ Created index on ${collection}:`, index); } catch (error) { // Index might already exist, skip if (!error.message.includes('already exists')) { console.warn(`⚠ Failed to create index on ${collection}:`, error.message); } } } } console.log('✅ Database indexes optimization completed'); } catch (error) { console.error('❌ Failed to create database indexes:', error); } };