Initial commit

This commit is contained in:
2026-09-16 17:36:46 +05:30
commit c9f429d123
117 changed files with 14145 additions and 0 deletions

16
server/models/Banner.js Normal file
View 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
View 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
View 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
View 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);

View 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
View 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);

View 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);

View 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
View 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
View 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
View 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);

View 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
View 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);

View 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);

View 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);