Initial commit
This commit is contained in:
16
server/models/Banner.js
Normal file
16
server/models/Banner.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const bannerSchema = new mongoose.Schema({
|
||||
title: { type: String, required: true },
|
||||
subtitle: { type: String, default: '' },
|
||||
desktopImage: { type: String, required: true },
|
||||
mobileImage: { type: String, default: '' },
|
||||
buttonText: { type: String, default: 'Shop Now' },
|
||||
buttonLink: { type: String, default: '/shop' },
|
||||
overlayOpacity: { type: Number, default: 0.4 },
|
||||
displayOrder: { type: Number, default: 0 },
|
||||
isActive: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Banner', bannerSchema);
|
||||
|
||||
15
server/models/Brand.js
Normal file
15
server/models/Brand.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const brandSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, trim: true, unique: true },
|
||||
slug: { type: String, required: true, unique: true, lowercase: true },
|
||||
logo: { type: String, default: '' },
|
||||
description: { type: String, default: '' },
|
||||
website: { type: String, default: '' },
|
||||
metaTitle: { type: String, default: '' },
|
||||
metaDescription: { type: String, default: '' },
|
||||
isActive: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Brand', brandSchema);
|
||||
|
||||
20
server/models/Cart.js
Normal file
20
server/models/Cart.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const cartItemSchema = new mongoose.Schema({
|
||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
|
||||
variantColor: { type: String, default: '' },
|
||||
variantFinish: { type: String, default: '' },
|
||||
quantity: { type: Number, required: true, default: 1, min: 1 },
|
||||
price: { type: Number, required: true }
|
||||
});
|
||||
|
||||
const cartSchema = new mongoose.Schema({
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null },
|
||||
sessionId: { type: String, default: null },
|
||||
items: [cartItemSchema],
|
||||
couponCode: { type: String, default: '' },
|
||||
discountAmount: { type: Number, default: 0 }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Cart', cartSchema);
|
||||
|
||||
16
server/models/Category.js
Normal file
16
server/models/Category.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const categorySchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, trim: true, unique: true },
|
||||
slug: { type: String, required: true, unique: true, lowercase: true },
|
||||
description: { type: String, default: '' },
|
||||
image: { type: String, default: 'https://images.unsplash.com/photo-1510915361894-db8b60106cb1?w=600' },
|
||||
homeImage: { type: String, default: '' },
|
||||
fallbackImage: { type: String, default: 'https://images.unsplash.com/photo-1510915361894-db8b60106cb1?w=600' },
|
||||
parentCategory: { type: mongoose.Schema.Types.ObjectId, ref: 'Category', default: null },
|
||||
displayOrder: { type: Number, default: 0 },
|
||||
isFeaturedHome: { type: Boolean, default: true },
|
||||
isActive: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Category', categorySchema);
|
||||
13
server/models/ContactMessage.js
Normal file
13
server/models/ContactMessage.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const contactMessageSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true },
|
||||
email: { type: String, required: true },
|
||||
phone: { type: String, default: '' },
|
||||
subject: { type: String, default: 'General Inquiry' },
|
||||
message: { type: String, required: true },
|
||||
status: { type: String, enum: ['Pending', 'Read', 'Resolved'], default: 'Pending' }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('ContactMessage', contactMessageSchema);
|
||||
|
||||
17
server/models/Coupon.js
Normal file
17
server/models/Coupon.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const couponSchema = new mongoose.Schema({
|
||||
code: { type: String, required: true, unique: true, uppercase: true, trim: true },
|
||||
discountType: { type: String, enum: ['percentage', 'fixed'], default: 'percentage' },
|
||||
discountValue: { type: Number, required: true },
|
||||
minPurchaseAmount: { type: Number, default: 0 },
|
||||
maxDiscountAmount: { type: Number, default: 0 },
|
||||
startDate: { type: Date, default: Date.now },
|
||||
endDate: { type: Date, required: true },
|
||||
usageLimit: { type: Number, default: 100 },
|
||||
usedCount: { type: Number, default: 0 },
|
||||
isActive: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Coupon', couponSchema);
|
||||
|
||||
11
server/models/EmailTemplate.js
Normal file
11
server/models/EmailTemplate.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const emailTemplateSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, unique: true }, // Welcome, Order Confirmation, Order Shipped, Password Reset, Low Stock, Contact Form
|
||||
subject: { type: String, required: true },
|
||||
bodyHtml: { type: String, required: true },
|
||||
placeholders: [{ type: String }] // e.g. ['{{customerName}}', '{{orderId}}', '{{orderTotal}}']
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('EmailTemplate', emailTemplateSchema);
|
||||
|
||||
19
server/models/InventoryHistory.js
Normal file
19
server/models/InventoryHistory.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const inventoryHistorySchema = new mongoose.Schema({
|
||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
|
||||
sku: { type: String, required: true },
|
||||
changeType: {
|
||||
type: String,
|
||||
enum: ['Order Placed', 'Order Cancelled', 'Manual Adjustment', 'Restock', 'Returned Product'],
|
||||
required: true
|
||||
},
|
||||
quantityChanged: { type: Number, required: true }, // positive for addition, negative for deduction
|
||||
previousStock: { type: Number, required: true },
|
||||
newStock: { type: Number, required: true },
|
||||
reason: { type: String, default: '' },
|
||||
performedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('InventoryHistory', inventoryHistorySchema);
|
||||
|
||||
85
server/models/Order.js
Normal file
85
server/models/Order.js
Normal file
@@ -0,0 +1,85 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const orderItemSchema = new mongoose.Schema({
|
||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
|
||||
name: { type: String, required: true },
|
||||
image: { type: String, required: true },
|
||||
price: { type: Number, required: true },
|
||||
quantity: { type: Number, required: true },
|
||||
variantColor: { type: String, default: '' },
|
||||
variantFinish: { type: String, default: '' },
|
||||
sku: { type: String, default: '' }
|
||||
});
|
||||
|
||||
const statusHistorySchema = new mongoose.Schema({
|
||||
status: { type: String, required: true },
|
||||
note: { type: String, default: '' },
|
||||
updatedAt: { type: Date, default: Date.now }
|
||||
});
|
||||
|
||||
const orderSchema = new mongoose.Schema({
|
||||
orderNumber: { type: String, required: true, unique: true },
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
|
||||
orderItems: [orderItemSchema],
|
||||
|
||||
// Indian Shipping Address Schema
|
||||
shippingAddress: {
|
||||
fullName: { type: String, default: '' },
|
||||
mobileNumber: { type: String, required: true },
|
||||
addressLine1: { type: String, required: true },
|
||||
addressLine2: { type: String, default: '' },
|
||||
landmark: { type: String, default: '' },
|
||||
city: { type: String, required: true },
|
||||
district: { type: String, default: '' },
|
||||
state: { type: String, required: true },
|
||||
postalCode: { type: String, required: true }, // 6-digit PIN code
|
||||
country: { type: String, required: true, default: 'India' }
|
||||
},
|
||||
|
||||
// Payment Configuration
|
||||
paymentMethod: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['Razorpay', 'UPI', 'Stripe', 'Credit/Debit Card', 'Net Banking', 'Cash on Delivery']
|
||||
},
|
||||
paymentResult: {
|
||||
id: { type: String },
|
||||
status: { type: String },
|
||||
update_time: { type: String },
|
||||
email_address: { type: String }
|
||||
},
|
||||
|
||||
// Pricing & Indian GST breakdown
|
||||
itemsPrice: { type: Number, required: true, default: 0 },
|
||||
gstPercentage: { type: Number, default: 18 },
|
||||
cgst: { type: Number, default: 0 },
|
||||
sgst: { type: Number, default: 0 },
|
||||
igst: { type: Number, default: 0 },
|
||||
taxPrice: { type: Number, required: true, default: 0 },
|
||||
shippingPrice: { type: Number, required: true, default: 0 },
|
||||
discountAmount: { type: Number, default: 0 },
|
||||
totalAmount: { type: Number, required: true, default: 0 },
|
||||
|
||||
// Order Status Lifecycle
|
||||
orderStatus: {
|
||||
type: String,
|
||||
enum: ['Pending', 'Payment Pending', 'Payment Confirmed', 'Processing', 'Packed', 'Shipped', 'Out for Delivery', 'Delivered', 'Cancelled', 'Returned', 'Refunded'],
|
||||
default: 'Pending'
|
||||
},
|
||||
statusHistory: [statusHistorySchema],
|
||||
|
||||
// Indian Courier Logistics
|
||||
courierProvider: { type: String, default: 'Delhivery' }, // Delhivery, BlueDart, Bluedart, FedEx, Xpressbees
|
||||
awbNumber: { type: String, default: '' },
|
||||
trackingNumber: { type: String, default: '' },
|
||||
trackingUrl: { type: String, default: '' },
|
||||
estimatedDeliveryDate: { type: Date },
|
||||
|
||||
isPaid: { type: Boolean, default: false },
|
||||
paidAt: { type: Date },
|
||||
isDelivered: { type: Boolean, default: false },
|
||||
deliveredAt: { type: Date },
|
||||
notes: { type: String, default: '' }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Order', orderSchema);
|
||||
105
server/models/Product.js
Normal file
105
server/models/Product.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const variantSchema = new mongoose.Schema({
|
||||
color: { type: String, required: true },
|
||||
colorHex: { type: String, default: '#000000' },
|
||||
finish: { type: String, default: 'Gloss' },
|
||||
sku: { type: String, default: '' },
|
||||
price: { type: Number, required: true },
|
||||
salePrice: { type: Number, default: 0 },
|
||||
stock: { type: Number, required: true, default: 0 },
|
||||
image: { type: String, default: '' }
|
||||
});
|
||||
|
||||
const customSpecSchema = new mongoose.Schema({
|
||||
key: { type: String, required: true },
|
||||
value: { type: String, required: true }
|
||||
});
|
||||
|
||||
const guitarSpecSchema = new mongoose.Schema({
|
||||
guitarType: { type: String, default: 'Electric' }, // Electric, Acoustic, Bass, Classical, Accessories
|
||||
bodyShape: { type: String, default: 'Stratocaster' },
|
||||
bodyMaterial: { type: String, default: 'Alder' },
|
||||
topMaterial: { type: String, default: '' },
|
||||
backMaterial: { type: String, default: '' },
|
||||
sideMaterial: { type: String, default: '' },
|
||||
neckMaterial: { type: String, default: 'Maple' },
|
||||
fingerboardMaterial: { type: String, default: 'Rosewood' },
|
||||
scaleLength: { type: String, default: '25.5"' },
|
||||
numberOfStrings: { type: Number, default: 6 },
|
||||
frets: { type: Number, default: 22 },
|
||||
pickupConfiguration: { type: String, default: 'SSS' }, // SSS, HSS, HH, Single, Acoustic Pickup
|
||||
pickupType: { type: String, default: 'Passive Single-Coil' },
|
||||
bridgeType: { type: String, default: 'Tremolo' },
|
||||
tuningMachines: { type: String, default: 'Standard Die-Cast' },
|
||||
handOrientation: { type: String, enum: ['Right-Handed', 'Left-Handed'], default: 'Right-Handed' },
|
||||
color: { type: String, default: '' },
|
||||
finish: { type: String, default: 'Gloss Polyurethane' },
|
||||
includesCase: { type: Boolean, default: false },
|
||||
customSpecs: [customSpecSchema]
|
||||
});
|
||||
|
||||
const productSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true, trim: true },
|
||||
slug: { type: String, required: true, unique: true, lowercase: true },
|
||||
sku: { type: String, required: true, unique: true },
|
||||
brand: { type: mongoose.Schema.Types.ObjectId, ref: 'Brand', required: true },
|
||||
category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category', required: true },
|
||||
description: { type: String, required: true },
|
||||
shortDescription: { type: String, default: '' },
|
||||
|
||||
// Pricing & Discounts
|
||||
price: { type: Number, required: true, min: 0 },
|
||||
salePrice: { type: Number, default: 0, min: 0 },
|
||||
discountType: { type: String, enum: ['percentage', 'fixed'], default: 'percentage' },
|
||||
discountValue: { type: Number, default: 0, min: 0 },
|
||||
costPrice: { type: Number, default: 0 },
|
||||
|
||||
// Inventory
|
||||
stock: { type: Number, required: true, default: 10, min: 0 },
|
||||
lowStockThreshold: { type: Number, default: 3 },
|
||||
|
||||
// Media
|
||||
images: [{ type: String }],
|
||||
primaryImage: { type: String, default: '' },
|
||||
videoUrl: { type: String, default: '' },
|
||||
audioDemoUrl: { type: String, default: '' },
|
||||
|
||||
// Specs & Variants
|
||||
specs: guitarSpecSchema,
|
||||
variants: [variantSchema],
|
||||
tags: [{ type: String }],
|
||||
weightKg: { type: Number, default: 4.5 },
|
||||
dimensionsCm: { type: String, default: '105 x 38 x 10' },
|
||||
|
||||
// Rating & Sales Metrics
|
||||
rating: { type: Number, default: 4.8 },
|
||||
numReviews: { type: Number, default: 0 },
|
||||
salesCount: { type: Number, default: 0 },
|
||||
|
||||
// Badges & Flags
|
||||
isFeatured: { type: Boolean, default: false },
|
||||
isNewArrival: { type: Boolean, default: false },
|
||||
isBestSeller: { type: Boolean, default: false },
|
||||
isActive: { type: Boolean, default: true },
|
||||
|
||||
// SEO
|
||||
metaTitle: { type: String, default: '' },
|
||||
metaDescription: { type: String, default: '' }
|
||||
}, { timestamps: true });
|
||||
|
||||
// Auto-generate SKU before validation if missing
|
||||
productSchema.pre('validate', function(next) {
|
||||
if (!this.sku) {
|
||||
const randomNum = Math.floor(10000 + Math.random() * 90000);
|
||||
this.sku = `GTR-2026-${randomNum}`;
|
||||
}
|
||||
if (this.images && this.images.length > 0 && !this.primaryImage) {
|
||||
this.primaryImage = this.images[0];
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
productSchema.index({ name: 'text', description: 'text', tags: 'text' });
|
||||
|
||||
module.exports = mongoose.model('Product', productSchema);
|
||||
17
server/models/Review.js
Normal file
17
server/models/Review.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const reviewSchema = new mongoose.Schema({
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
|
||||
product: { type: mongoose.Schema.Types.ObjectId, ref: 'Product', required: true },
|
||||
rating: { type: Number, required: true, min: 1, max: 5 },
|
||||
title: { type: String, required: true, trim: true },
|
||||
comment: { type: String, required: true },
|
||||
isVerifiedPurchase: { type: Boolean, default: true },
|
||||
isApproved: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
// Prevent multiple reviews per product by same user
|
||||
reviewSchema.index({ user: 1, product: 1 }, { unique: true });
|
||||
|
||||
module.exports = mongoose.model('Review', reviewSchema);
|
||||
|
||||
10
server/models/SocialMedia.js
Normal file
10
server/models/SocialMedia.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const socialMediaSchema = new mongoose.Schema({
|
||||
platform: { type: String, required: true, unique: true }, // instagram, facebook, youtube, tiktok, x, linkedin
|
||||
url: { type: String, required: true },
|
||||
isEnabled: { type: Boolean, default: true }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('SocialMedia', socialMediaSchema);
|
||||
|
||||
47
server/models/User.js
Normal file
47
server/models/User.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const mongoose = require('mongoose');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const addressSchema = new mongoose.Schema({
|
||||
fullName: { type: String, default: '' },
|
||||
mobileNumber: { type: String, default: '' },
|
||||
addressLine1: { type: String, default: '' },
|
||||
addressLine2: { type: String, default: '' },
|
||||
landmark: { type: String, default: '' },
|
||||
street: { type: String, default: '' },
|
||||
city: { type: String, required: true },
|
||||
district: { type: String, default: '' },
|
||||
state: { type: String, required: true },
|
||||
postalCode: { type: String, required: true },
|
||||
country: { type: String, required: true, default: 'India' },
|
||||
isDefault: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
name: { type: String, required: [true, 'Please provide your name'], trim: true },
|
||||
email: { type: String, required: [true, 'Please provide your email'], unique: true, lowercase: true, trim: true },
|
||||
password: { type: String, required: [true, 'Please provide a password'], minlength: 6, select: false },
|
||||
role: {
|
||||
type: String,
|
||||
enum: ['Customer', 'Admin', 'Staff', 'Warehouse Manager', 'Order Manager'],
|
||||
default: 'Customer'
|
||||
},
|
||||
phone: { type: String, default: '' },
|
||||
avatar: { type: String, default: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=300' },
|
||||
addresses: [addressSchema],
|
||||
isVerified: { type: Boolean, default: true },
|
||||
resetPasswordToken: String,
|
||||
resetPasswordExpire: Date
|
||||
}, { timestamps: true });
|
||||
|
||||
userSchema.pre('save', async function(next) {
|
||||
if (!this.isModified('password')) return next();
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
this.password = await bcrypt.hash(this.password, salt);
|
||||
next();
|
||||
});
|
||||
|
||||
userSchema.methods.matchPassword = async function(enteredPassword) {
|
||||
return await bcrypt.compare(enteredPassword, this.password);
|
||||
};
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
34
server/models/WebsiteSettings.js
Normal file
34
server/models/WebsiteSettings.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const websiteSettingsSchema = new mongoose.Schema({
|
||||
siteName: { type: String, default: 'Antigravity Guitars India' },
|
||||
tagline: { type: String, default: 'Crafted Sound. Pure Perfection.' },
|
||||
contactEmail: { type: String, default: 'support@antigravityguitars.in' },
|
||||
phone: { type: String, default: '+91 98765 43210' },
|
||||
businessName: { type: String, default: 'Antigravity Music Private Limited' },
|
||||
gstin: { type: String, default: '29ABCDE1234F1Z5' },
|
||||
addressLine1: { type: String, default: '100 Brigade Road, Ashok Nagar' },
|
||||
addressLine2: { type: String, default: 'Suite 400, Music Square' },
|
||||
city: { type: String, default: 'Bengaluru' },
|
||||
district: { type: String, default: 'Bengaluru Urban' },
|
||||
state: { type: String, default: 'Karnataka' },
|
||||
pincode: { type: String, default: '560025' },
|
||||
country: { type: String, default: 'India' },
|
||||
currencySymbol: { type: String, default: '₹' },
|
||||
currencyCode: { type: String, default: 'INR' },
|
||||
timezone: { type: String, default: 'Asia/Kolkata' },
|
||||
logoPrimary: { type: String, default: '/assets/logo-primary.svg' },
|
||||
logoDark: { type: String, default: '/assets/logo-dark.svg' },
|
||||
logoLight: { type: String, default: '/assets/logo-light.svg' },
|
||||
logoMobile: { type: String, default: '/assets/logo-mobile.svg' },
|
||||
favicon: { type: String, default: '/favicon.ico' },
|
||||
freeShippingThreshold: { type: Number, default: 5000 }, // ₹5,000 threshold
|
||||
codEnabled: { type: Boolean, default: true },
|
||||
maxCodAmount: { type: Number, default: 50000 }, // ₹50,000 max COD
|
||||
aboutUsText: { type: String, default: 'We are India’s premier music destination providing hand-crafted acoustic, electric, and custom shop guitars.' },
|
||||
privacyPolicy: { type: String, default: 'Your privacy is paramount to us under Indian IT Act regulations.' },
|
||||
termsConditions: { type: String, default: 'All sales governed by Antigravity Guitars warranty policies.' },
|
||||
returnPolicy: { type: String, default: '10-day hassle-free returns on all standard model guitars in original condition.' }
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('WebsiteSettings', websiteSettingsSchema);
|
||||
9
server/models/Wishlist.js
Normal file
9
server/models/Wishlist.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const wishlistSchema = new mongoose.Schema({
|
||||
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, unique: true },
|
||||
products: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Product' }]
|
||||
}, { timestamps: true });
|
||||
|
||||
module.exports = mongoose.model('Wishlist', wishlistSchema);
|
||||
|
||||
Reference in New Issue
Block a user