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.

224 lines (199 loc) 5.79 kB
import mongoose from 'mongoose'; const makeoverOfferSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true, maxlength: 100 }, description: { type: String, required: true, trim: true, maxlength: 500 }, offerType: { type: String, required: true, trim: true, maxlength: 50 }, basePrice: { type: Number, required: true, min: 0 }, currency: { type: String, default: 'NGN', enum: ['NGN', 'USD', 'EUR'] }, // Duration and timing defaultDuration: { type: Number, required: true, min: 30, // minimum 30 minutes default: 120 // 2 hours default }, minDuration: { type: Number, min: 30, validate: { validator: function (value) { return !value || value <= this.defaultDuration; }, message: 'Minimum duration cannot be greater than default duration' } }, maxDuration: { type: Number, min: 30, validate: { validator: function (value) { return !value || value >= this.defaultDuration; }, message: 'Maximum duration cannot be less than default duration' } }, // Availability and rules status: { type: String, enum: ['active', 'inactive', 'expired'], default: 'active' }, isFeatured: { type: Boolean, default: false }, isPopular: { type: Boolean, default: false }, // Business rules advanceBookingHours: { type: Number, default: 24, min: 1 }, // Media - Array of image URLs images: [String], // Array of Cloudinary image URLs // Metadata createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'Admin', required: true }, lastModifiedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'Admin' }, // Analytics (computed fields) bookingCount: { type: Number, default: 0, min: 0 }, totalRevenue: { type: Number, default: 0, min: 0 }, averageRating: { type: Number, min: 0, max: 5 }, popularityScore: { type: Number, default: 0, min: 0 } }, { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }); // Indexes for performance makeoverOfferSchema.index({ offerType: 1, status: 1 }); makeoverOfferSchema.index({ status: 1, createdAt: -1 }); makeoverOfferSchema.index({ basePrice: 1 }); makeoverOfferSchema.index({ bookingCount: -1 }); // Text search index makeoverOfferSchema.index({ name: 'text', description: 'text' }); // Virtual for formatted price makeoverOfferSchema.virtual('formattedPrice').get(function () { return new Intl.NumberFormat('en-NG', { style: 'currency', currency: this.currency, minimumFractionDigits: 0 }).format(this.basePrice); }); // Virtual for duration in hours makeoverOfferSchema.virtual('durationInHours').get(function () { return Math.round(this.defaultDuration / 60 * 10) / 10; // Round to 1 decimal place }); // Static methods makeoverOfferSchema.statics.getActiveOffers = function () { return this.find({ status: 'active' }) .sort({ bookingCount: -1, createdAt: -1 }); }; makeoverOfferSchema.statics.getActiveOffersByType = function (offerType) { return this.find({ status: 'active', offerType: offerType }).sort({ bookingCount: -1, createdAt: -1 }); }; makeoverOfferSchema.statics.getPopularOffers = function (limit = 5) { return this.find({ status: 'active', isPopular: true }) .sort({ bookingCount: -1, averageRating: -1 }) .limit(limit); }; makeoverOfferSchema.statics.getFeaturedOffers = function (limit = 3) { return this.find({ status: 'active', isFeatured: true }) .sort({ createdAt: -1 }) .limit(limit); }; // Instance methods makeoverOfferSchema.methods.incrementBookingCount = function () { this.bookingCount += 1; return this.save(); }; makeoverOfferSchema.methods.addRevenue = function (amount) { this.totalRevenue += amount; return this.save(); }; makeoverOfferSchema.methods.updateRating = function (newRating) { if (this.averageRating === undefined || this.averageRating === null) { this.averageRating = newRating; } else { // Simple average - in production, you might want to store individual ratings this.averageRating = (this.averageRating + newRating) / 2; } return this.save(); }; // Pre-save middleware to calculate popularity makeoverOfferSchema.pre('save', function (next) { // Mark as popular if booking count is above threshold this.isPopular = this.bookingCount >= 10; next(); }); // Post-save middleware to update popularity score makeoverOfferSchema.post('save', function (doc) { // Calculate popularity score based on booking count and rating const popularityScore = (doc.bookingCount * 0.7) + ((doc.averageRating || 0) * 10 * 0.3); if (Math.abs(doc.popularityScore - popularityScore) > 0.1) { doc.popularityScore = popularityScore; doc.save(); } }); const MakeoverOffer = mongoose.model('MakeoverOffer', makeoverOfferSchema); export default MakeoverOffer;