@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.
210 lines (189 loc) • 6.37 kB
JavaScript
import mongoose from 'mongoose';
const wishlistItemSchema = new mongoose.Schema({
// Product reference (minimart only)
productId: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
productType: {
type: String,
enum: ['minimart'], // Only minimart allowed
required: true
},
// Product details (matches cart structure)
productDetails: {
name: { type: String, required: true },
price: { type: Number, required: true },
description: String,
images: [{
url: { type: String, required: true },
alt: String,
isPrimary: { type: Boolean, default: false }
}],
category: String,
stock: Number,
sku: String,
barcode: String,
brand: String,
specifications: mongoose.Schema.Types.Mixed,
features: [String],
pricing: {
baseRate: { type: Number, required: true },
rateType: { type: String, enum: ['fixed'], default: 'fixed' },
currency: { type: String, default: 'NGN' }
},
isActive: { type: Boolean, default: true }
},
// User notes about this item
notes: String,
// Priority level (1-5, 5 being highest)
priority: { type: Number, min: 1, max: 5, default: 3 },
// Date added to wishlist
addedAt: { type: Date, default: Date.now }
}, { _id: true });
const wishlistSchema = new mongoose.Schema({
// User who owns this wishlist
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
unique: true
},
// Wishlist items
items: [wishlistItemSchema],
// Wishlist settings
settings: {
isPublic: { type: Boolean, default: false },
allowNotifications: { type: Boolean, default: true },
sortBy: {
type: String,
enum: ['addedAt', 'priority', 'price', 'name'],
default: 'addedAt'
},
sortOrder: {
type: String,
enum: ['asc', 'desc'],
default: 'desc'
}
},
// Statistics
stats: {
totalItems: { type: Number, default: 0 },
totalValue: { type: Number, default: 0 },
lastUpdated: { type: Date, default: Date.now }
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// Indexes for efficient queries
// Note: user field already has unique index from schema definition
wishlistSchema.index({ 'items.productId': 1, 'items.productType': 1 });
wishlistSchema.index({ 'items.addedAt': -1 });
// Virtual for item count
wishlistSchema.virtual('itemCount').get(function () {
return this.items.length;
});
// Virtual for total value
wishlistSchema.virtual('totalValue').get(function () {
return this.items.reduce((total, item) => total + (item.productDetails.price || 0), 0);
});
// Pre-save middleware to update stats
wishlistSchema.pre('save', function (next) {
this.stats.totalItems = this.items.length;
this.stats.totalValue = this.items.reduce((total, item) => total + (item.productDetails.price || 0), 0);
this.stats.lastUpdated = new Date();
next();
});
// Method to add item to wishlist
wishlistSchema.methods.addItem = function (productId, productType, productDetails, notes = '', priority = 3) {
// Check if item already exists
const existingItem = this.items.find(item =>
item.productId.toString() === productId.toString() &&
item.productType === productType
);
if (existingItem) {
// Update existing item
existingItem.notes = notes;
existingItem.priority = priority;
existingItem.addedAt = new Date();
} else {
// Add new item
this.items.push({
productId,
productType,
productDetails,
notes,
priority,
addedAt: new Date()
});
}
return this.save();
};
// Method to remove item from wishlist
wishlistSchema.methods.removeItem = function (productId, productType) {
this.items = this.items.filter(item =>
!(item.productId.toString() === productId.toString() && item.productType === productType)
);
return this.save();
};
// Method to clear wishlist
wishlistSchema.methods.clear = function () {
this.items = [];
return this.save();
};
// Method to get sorted items
wishlistSchema.methods.getSortedItems = function () {
const { sortBy, sortOrder } = this.settings;
const items = [...this.items];
items.sort((a, b) => {
let aValue, bValue;
switch (sortBy) {
case 'addedAt':
aValue = new Date(a.addedAt);
bValue = new Date(b.addedAt);
break;
case 'priority':
aValue = a.priority;
bValue = b.priority;
break;
case 'price':
aValue = a.productDetails.price || 0;
bValue = b.productDetails.price || 0;
break;
case 'name':
aValue = a.productDetails.name.toLowerCase();
bValue = b.productDetails.name.toLowerCase();
break;
default:
aValue = new Date(a.addedAt);
bValue = new Date(b.addedAt);
}
if (sortOrder === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
return items;
};
// Static method to get or create wishlist for user
wishlistSchema.statics.getOrCreateWishlist = async function (userId) {
let wishlist = await this.findOne({ user: userId });
if (!wishlist) {
wishlist = new this({
user: userId,
items: [],
settings: {
isPublic: false,
allowNotifications: true,
sortBy: 'addedAt',
sortOrder: 'desc'
}
});
await wishlist.save();
}
return wishlist;
};
export default mongoose.model('Wishlist', wishlistSchema);