first commit

This commit is contained in:
tusuii
2026-02-19 17:25:38 +05:30
commit 09ea6d4efb
72 changed files with 24296 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
const mongoose = require('mongoose');
const variantSchema = new mongoose.Schema({
size: {
type: String,
required: true,
},
color: {
type: String,
required: true,
},
sku: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
min: 0,
},
compareAtPrice: {
type: Number,
min: 0,
},
inventory: {
quantity: {
type: Number,
default: 0,
min: 0,
},
trackInventory: {
type: Boolean,
default: true,
},
},
images: [String],
isActive: {
type: Boolean,
default: true,
},
});
const productSchema = new mongoose.Schema({
// Basic Information
name: {
type: String,
required: true,
trim: true,
},
slug: {
type: String,
required: true,
unique: true,
lowercase: true,
},
description: {
type: String,
required: true,
},
shortDescription: {
type: String,
maxLength: 500,
},
// Categorization
category: {
type: String,
required: true,
},
subcategory: String,
tags: [String],
brand: String,
// Pricing & Inventory
basePrice: {
type: Number,
required: true,
min: 0,
},
compareAtPrice: {
type: Number,
min: 0,
},
costPrice: {
type: Number,
min: 0,
},
// Variants
variants: [variantSchema],
hasVariants: {
type: Boolean,
default: false,
},
// Media
images: {
primary: String,
gallery: [String],
videos: [String],
},
// SEO
metaTitle: String,
metaDescription: String,
metaKeywords: [String],
// Status & Visibility
status: {
type: String,
enum: ['draft', 'active', 'inactive', 'archived'],
default: 'draft',
},
isFeatured: {
type: Boolean,
default: false,
},
isDigital: {
type: Boolean,
default: false,
},
// Physical Attributes
weight: {
value: Number,
unit: {
type: String,
enum: ['g', 'kg', 'lb', 'oz'],
default: 'g',
},
},
dimensions: {
length: Number,
width: Number,
height: Number,
unit: {
type: String,
enum: ['cm', 'in'],
default: 'cm',
},
},
// Analytics
viewCount: {
type: Number,
default: 0,
},
purchaseCount: {
type: Number,
default: 0,
},
// AI Generated Tags
aiTags: [String],
aiGeneratedDescription: String,
// Timestamps
createdAt: {
type: Date,
default: Date.now,
},
updatedAt: {
type: Date,
default: Date.now,
},
publishedAt: Date,
});
// Indexes for better performance
productSchema.index({ slug: 1 });
productSchema.index({ category: 1, status: 1 });
productSchema.index({ brand: 1 });
productSchema.index({ tags: 1 });
productSchema.index({ 'variants.sku': 1 });
productSchema.index({ status: 1, isFeatured: 1 });
productSchema.index({ createdAt: -1 });
// Text search index
productSchema.index({
name: 'text',
description: 'text',
tags: 'text',
brand: 'text',
});
// Virtual for average rating (if implementing ratings)
productSchema.virtual('averageRating').get(function() {
// This would be calculated from reviews in PostgreSQL
return 0;
});
// Pre-save middleware
productSchema.pre('save', function(next) {
this.updatedAt = new Date();
// Auto-generate slug if not provided
if (!this.slug && this.name) {
this.slug = this.name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
// Set published date when status changes to active
if (this.isModified('status') && this.status === 'active' && !this.publishedAt) {
this.publishedAt = new Date();
}
next();
});
// Instance methods
productSchema.methods.incrementViewCount = function() {
this.viewCount += 1;
return this.save();
};
productSchema.methods.incrementPurchaseCount = function(quantity = 1) {
this.purchaseCount += quantity;
return this.save();
};
productSchema.methods.getAvailableVariants = function() {
return this.variants.filter(variant =>
variant.isActive &&
(!variant.inventory.trackInventory || variant.inventory.quantity > 0)
);
};
// Static methods
productSchema.statics.findBySlug = function(slug) {
return this.findOne({ slug, status: 'active' });
};
productSchema.statics.findByCategory = function(category, limit = 20, skip = 0) {
return this.find({ category, status: 'active' })
.limit(limit)
.skip(skip)
.sort({ createdAt: -1 });
};
productSchema.statics.searchProducts = function(query, options = {}) {
const { category, brand, minPrice, maxPrice, limit = 20, skip = 0 } = options;
const searchQuery = {
$text: { $search: query },
status: 'active',
};
if (category) searchQuery.category = category;
if (brand) searchQuery.brand = brand;
if (minPrice || maxPrice) {
searchQuery.basePrice = {};
if (minPrice) searchQuery.basePrice.$gte = minPrice;
if (maxPrice) searchQuery.basePrice.$lte = maxPrice;
}
return this.find(searchQuery)
.limit(limit)
.skip(skip)
.sort({ score: { $meta: 'textScore' } });
};
module.exports = mongoose.model('Product', productSchema);

View File

@@ -0,0 +1,254 @@
const mongoose = require('mongoose');
const wardrobeItemSchema = new mongoose.Schema({
// Basic Information
name: {
type: String,
required: true,
},
description: String,
// Category & Type
category: {
type: String,
required: true,
enum: ['tops', 'bottoms', 'dresses', 'outerwear', 'shoes', 'accessories', 'other'],
},
subcategory: String,
brand: String,
color: String,
// Images
images: [{
url: {
type: String,
required: true,
},
isPrimary: {
type: Boolean,
default: false,
},
uploadedAt: {
type: Date,
default: Date.now,
},
}],
// AI Generated Tags
aiTags: [String],
aiColorPalette: [String],
aiStyleTags: [String],
// User Tags
userTags: [String],
// Physical Attributes
size: String,
material: String,
condition: {
type: String,
enum: ['new', 'like-new', 'good', 'fair', 'poor'],
default: 'good',
},
// Status
isActive: {
type: Boolean,
default: true,
},
// Timestamps
createdAt: {
type: Date,
default: Date.now,
},
updatedAt: {
type: Date,
default: Date.now,
},
});
const wardrobeSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'User', // Reference to PostgreSQL User
},
// Wardrobe Information
name: {
type: String,
default: 'My Wardrobe',
},
description: String,
// Items
items: [wardrobeItemSchema],
// Statistics
totalItems: {
type: Number,
default: 0,
},
categoryCounts: {
tops: { type: Number, default: 0 },
bottoms: { type: Number, default: 0 },
dresses: { type: Number, default: 0 },
outerwear: { type: Number, default: 0 },
shoes: { type: Number, default: 0 },
accessories: { type: Number, default: 0 },
other: { type: Number, default: 0 },
},
// AI Analysis
aiAnalysis: {
lastAnalyzed: Date,
dominantColors: [String],
styleProfile: {
casual: Number,
formal: Number,
trendy: Number,
classic: Number,
bohemian: Number,
minimalist: Number,
},
recommendations: [String],
},
// Privacy Settings
isPublic: {
type: Boolean,
default: false,
},
shareSettings: {
allowViewing: Boolean,
allowRecommendations: Boolean,
},
// Timestamps
createdAt: {
type: Date,
default: Date.now,
},
updatedAt: {
type: Date,
default: Date.now,
},
});
// Indexes
wardrobeSchema.index({ userId: 1 });
wardrobeSchema.index({ isPublic: 1 });
wardrobeSchema.index({ 'items.category': 1 });
wardrobeSchema.index({ 'items.aiTags': 1 });
// Text search index
wardrobeSchema.index({
name: 'text',
description: 'text',
'items.name': 'text',
'items.brand': 'text',
'items.userTags': 'text',
});
// Pre-save middleware
wardrobeSchema.pre('save', function(next) {
this.updatedAt = new Date();
// Update statistics
this.totalItems = this.items.length;
// Reset category counts
this.categoryCounts = {
tops: 0,
bottoms: 0,
dresses: 0,
outerwear: 0,
shoes: 0,
accessories: 0,
other: 0,
};
// Count items by category
this.items.forEach(item => {
if (this.categoryCounts[item.category] !== undefined) {
this.categoryCounts[item.category]++;
}
});
next();
});
// Instance methods
wardrobeSchema.methods.addItem = function(itemData) {
this.items.push(itemData);
return this.save();
};
wardrobeSchema.methods.removeItem = function(itemId) {
this.items = this.items.filter(item => item._id.toString() !== itemId);
return this.save();
};
wardrobeSchema.methods.updateItem = function(itemId, updateData) {
const item = this.items.id(itemId);
if (item) {
Object.assign(item, updateData);
item.updatedAt = new Date();
return this.save();
}
throw new Error('Item not found');
};
wardrobeSchema.methods.getItemsByCategory = function(category) {
return this.items.filter(item => item.category === category && item.isActive);
};
wardrobeSchema.methods.getItemsByTags = function(tags) {
return this.items.filter(item =>
item.isActive &&
tags.some(tag =>
item.aiTags.includes(tag) ||
item.userTags.includes(tag)
)
);
};
wardrobeSchema.methods.generateOutfitRecommendations = function() {
// This would integrate with the recommendation service
const tops = this.getItemsByCategory('tops');
const bottoms = this.getItemsByCategory('bottoms');
const shoes = this.getItemsByCategory('shoes');
const accessories = this.getItemsByCategory('accessories');
const recommendations = [];
// Simple outfit combination logic
for (let i = 0; i < Math.min(3, tops.length); i++) {
for (let j = 0; j < Math.min(3, bottoms.length); j++) {
for (let k = 0; k < Math.min(2, shoes.length); k++) {
recommendations.push({
id: `${tops[i]._id}-${bottoms[j]._id}-${shoes[k]._id}`,
items: [tops[i], bottoms[j], shoes[k]],
confidence: Math.random(), // This would come from AI analysis
});
}
}
}
return recommendations.slice(0, 10); // Limit to 10 recommendations
};
// Static methods
wardrobeSchema.statics.findByUserId = function(userId) {
return this.findOne({ userId });
};
wardrobeSchema.statics.findPublicWardrobes = function(limit = 20, skip = 0) {
return this.find({ isPublic: true })
.limit(limit)
.skip(skip)
.sort({ updatedAt: -1 });
};
module.exports = mongoose.model('Wardrobe', wardrobeSchema);