server / models /TemplateStore.js
Mark-Lasfar
Fix: Optimize loadStore function, fix preview mode and follow-status
d02ef17
Raw
History Blame Contribute Delete
22.7 kB
const mongoose = require('mongoose');
/**
* نموذج معرض القوالب - يدعم القوالب الجاهزة للتحميل والتثبيت بنقرة واحدة
* @version 2.0.0 - متوافق مع نظام التطبيق الكامل للقوالب
*/
const templateReviewSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
rating: { type: Number, required: true, min: 1, max: 5 },
comment: { type: String, maxlength: 500 },
createdAt: { type: Date, default: Date.now }
});
const templateVersionSchema = new mongoose.Schema({
version: { type: String, required: true, match: /^\d+\.\d+\.\d+$/ },
changelog: { type: String },
zipUrl: { type: String },
releasedAt: { type: Date, default: Date.now }
});
const templateSchema = new mongoose.Schema({
// ============================================
// المعلومات الأساسية
// ============================================
name: {
type: String,
required: true,
trim: true,
maxlength: 100,
index: true
},
slug: {
type: String,
required: true,
unique: true,
lowercase: true,
trim: true,
match: /^[a-z0-9]+(?:-[a-z0-9]+)*$/
},
description: {
type: String,
required: true,
maxlength: 500
},
// ============================================
// التصنيفات
// ============================================
category: {
type: String,
enum: ['fashion', 'electronics', 'food', 'art', 'beauty', 'sports', 'education', 'business', 'portfolio', 'blog', 'minimal', 'modern', 'dark', 'light'],
default: 'general',
index: true
},
tags: [{
type: String,
trim: true,
lowercase: true,
index: true
}],
// ============================================
// الصور والمعاينة
// ============================================
previewImage: {
type: String,
required: true
},
previewImages: [{
type: String,
description: { type: String }
}],
previewVideo: { type: String },
demoUrl: { type: String },
thumbnail: { type: String },
// ============================================
// ملفات القالب
// ============================================
themeZipUrl: {
type: String,
required: true
},
themeZipSize: { type: Number },
themeZipHash: { type: String },
// ============================================
// 🎨 بيانات القالب الكاملة (محدثة)
// ============================================
themeData: {
// 🎨 الألوان
colors: {
primary: { type: String, default: '#3b82f6' },
secondary: { type: String, default: '#8b5cf6' },
accent: { type: String, default: '#f59e0b' },
background: { type: String, default: '#ffffff' },
text: { type: String, default: '#1f2937' },
textLight: { type: String, default: '#6b7280' },
border: { type: String, default: '#e5e7eb' },
success: { type: String, default: '#10b981' },
error: { type: String, default: '#ef4444' },
warning: { type: String, default: '#f59e0b' }
},
// 🎨 الخطوط
typography: {
fontFamily: { type: String, default: 'Inter, sans-serif' },
headingFont: { type: String, default: 'Inter, sans-serif' },
baseFontSize: { type: String, default: '16px' },
enableGoogleFonts: { type: Boolean, default: true }
},
// 🎨 التخطيط
layout: {
type: { type: String, enum: ['full-width', 'boxed', 'fluid'], default: 'full-width' },
headerLayout: { type: String, enum: ['default', 'centered', 'minimal', 'transparent'], default: 'default' },
footerLayout: { type: String, enum: ['default', 'minimal', 'columns'], default: 'default' },
sidebarPosition: { type: String, enum: ['left', 'right', 'none'], default: 'none' },
columns: { type: Number, default: 4, min: 2, max: 6 },
stickyHeader: { type: Boolean, default: false },
containerWidth: { type: String, default: '1280px' },
containerPadding: { type: String, default: '20px' },
showBreadcrumb: { type: Boolean, default: true },
backToTop: { type: Boolean, default: true },
showSearchBar: { type: Boolean, default: true },
showCategories: { type: Boolean, default: true },
productsPerRowMobile: { type: Number, default: 2 },
productsPerRowTablet: { type: Number, default: 3 },
enableStickyAddToCart: { type: Boolean, default: true },
enableCompareProducts: { type: Boolean, default: false },
enableWishlist: { type: Boolean, default: false },
enableRecentlyViewed: { type: Boolean, default: true }
},
// 🎨 تأثيرات إضافية
borderRadius: { type: Number, default: 16, min: 0, max: 48 },
shadowType: { type: String, enum: ['none', 'sm', 'md', 'lg', 'xl'], default: 'md' },
animationType: { type: String, enum: ['none', 'lift', 'scale', 'glow'], default: 'lift' },
glassEffect: { type: Boolean, default: false },
// 🎨 الخلفية
backgroundType: {
type: String,
enum: ['default', 'dots', 'grid', 'cross', 'gradient', 'image'],
default: 'default'
},
backgroundValue: { type: String, default: '' },
// 🎨 CSS/JS مخصص
customCss: { type: String, default: '' },
customJs: { type: String, default: '' },
// 📄 الأقسام
sections: { type: mongoose.Schema.Types.Mixed, default: [] },
// 🏪 معلومات المتجر الأساسية
storeInfo: {
storeName: { type: String, default: '' },
storeLogo: { type: String, default: '' },
storeBanner: { type: String, default: '' },
storeDescription: { type: String, default: '' },
contactEmail: { type: String, default: '' }
},
// 📄 الصفحات
pages: [{
slug: { type: String, required: true },
title: { type: String, required: true },
content: { type: String, default: '' },
css: { type: String, default: '' },
js: { type: String, default: '' },
seo: {
title: { type: String },
description: { type: String },
keywords: { type: String },
ogImage: { type: String }
},
isEnabled: { type: Boolean, default: true },
order: { type: Number, default: 0 },
type: { type: String, default: 'custom' }
}],
// 📄 الصفحات الافتراضية
defaultPages: {
about: {
title: { type: String, default: 'About Us' },
content: { type: String, default: '' },
seo: { type: mongoose.Schema.Types.Mixed, default: {} }
},
contact: {
title: { type: String, default: 'Contact Us' },
content: { type: String, default: '' },
seo: { type: mongoose.Schema.Types.Mixed, default: {} }
}
},
// 🔗 روابط التواصل الاجتماعي
socialLinks: {
instagram: { type: String, default: '' },
facebook: { type: String, default: '' },
twitter: { type: String, default: '' },
tiktok: { type: String, default: '' },
linkedin: { type: String, default: '' },
youtube: { type: String, default: '' },
whatsapp: { type: String, default: '' },
telegram: { type: String, default: '' }
},
// 💰 إعدادات الدفع
paymentSettings: {
enableCOD: { type: Boolean, default: false },
enableInstallments: { type: Boolean, default: false },
minOrderAmount: { type: Number, default: 0 },
freeShippingThreshold: { type: Number, default: 0 },
taxRate: { type: Number, default: 0, min: 0, max: 100 },
shippingCost: { type: Number, default: 0 }
},
// 🎯 إعدادات المنتجات
productsSettings: {
productViewType: { type: String, enum: ['modal', 'page'], default: 'modal' },
productsPerRow: { type: Number, default: 4, min: 2, max: 6 },
enableQuickView: { type: Boolean, default: true },
enableProductReviews: { type: Boolean, default: true },
autoApproveReviews: { type: Boolean, default: false }
},
// 🎨 الهيدر والفوتر المخصص
customHeaderFooter: {
header: { type: String, default: '' },
footer: { type: String, default: '' },
sidebar: { type: String, default: '' },
productCard: { type: String, default: '' },
categoryCard: { type: String, default: '' },
css: { type: String, default: '' },
js: { type: String, default: '' }
},
templateUrl: { type: String, default: null },
uploadedFiles: {
type: mongoose.Schema.Types.Mixed,
default: null
},
hasIndexHtml: { type: Boolean, default: false },
// 💱 العملة
currency: { type: String, default: 'USD', uppercase: true },
// 📑 ترتيب الصفحات
pagesOrder: {
type: mongoose.Schema.Types.Mixed,
default: { products: 1, about: 2, contact: 3 }
},
// 🏷️ أكواد الخصم
storeCoupons: [{
code: { type: String, uppercase: true },
discount: { type: Number, min: 0, max: 100 },
discountType: { type: String, enum: ['percentage', 'fixed'], default: 'percentage' },
minPurchase: { type: Number, default: 0 },
maxDiscount: { type: Number },
validUntil: { type: Date },
isActive: { type: Boolean, default: true }
}],
// 📦 مناطق الشحن
shippingZones: [{
name: { type: String },
countries: [{ type: String }],
cost: { type: Number, default: 0 },
freeShippingAbove: { type: Number, default: 0 },
estimatedDays: { type: String, default: '3-7 business days' }
}],
// 🔍 إعدادات SEO
seoSettings: {
metaTitleTemplate: { type: String, default: '{{title}} | {{storeName}}' },
metaDescriptionTemplate: { type: String, default: '' },
enableBreadcrumbs: { type: Boolean, default: true },
sitemapEnabled: { type: Boolean, default: true }
},
// ⚡ إعدادات الأداء
performanceSettings: {
enableLazyLoad: { type: Boolean, default: true },
enableImageOptimization: { type: Boolean, default: true },
enablePreload: { type: Boolean, default: true },
cacheTTL: { type: Number, default: 3600 }
},
// 🎨 معلومات إضافية عن القالب
templateMeta: {
minVersion: { type: String, default: '1.0.0' },
maxVersion: { type: String, default: '3.0.0' },
requires: [{ type: String }],
screenshots: [{ type: String }],
demoUrl: { type: String },
documentationUrl: { type: String },
supportUrl: { type: String }
}
},
// ============================================
// متطلبات التثبيت
// ============================================
requiredSubscription: {
type: String,
enum: ['free', 'basic', 'pro', 'business', 'enterprise'],
default: 'free',
index: true
},
requiredPermissions: [{
type: String,
enum: ['store_enabled', 'custom_domain', 'analytics']
}],
// ============================================
// إحصائيات
// ============================================
downloadsCount: {
type: Number,
default: 0,
index: true
},
viewsCount: {
type: Number,
default: 0
},
rating: {
type: Number,
default: 0,
min: 0,
max: 5
},
ratingCount: {
type: Number,
default: 0
},
reviews: [templateReviewSchema],
// ============================================
// الإصدارات
// ============================================
version: {
type: String,
default: '1.0.0',
match: /^\d+\.\d+\.\d+$/
},
versions: [templateVersionSchema],
// ============================================
// حالة القالب
// ============================================
isActive: {
type: Boolean,
default: true,
index: true
},
isFeatured: {
type: Boolean,
default: false,
index: true
},
isVerified: {
type: Boolean,
default: false,
index: true
},
// ============================================
// معلومات الناشر
// ============================================
authorId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true
},
authorName: { type: String },
authorEmail: { type: String },
authorWebsite: { type: String },
// ============================================
// إعدادات إضافية
// ============================================
settings: {
requiresSetup: { type: Boolean, default: false },
setupInstructions: { type: String },
compatibleWith: [{
platformVersion: { type: String },
minVersion: { type: String }
}],
dependencies: [{ type: String }]
},
// ============================================
// سجل التثبيت
// ============================================
installedBy: [{
userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
installedAt: { type: Date, default: Date.now },
storeId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
appliedSettings: { type: mongoose.Schema.Types.Mixed } // تخزين الإعدادات التي تم تطبيقها
}],
createdAt: { type: Date, default: Date.now, immutable: true },
updatedAt: { type: Date, default: Date.now }
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// ============================================
// ✅ Indexes محسنة للبحث
// ============================================
templateSchema.index({ category: 1, isActive: 1, isFeatured: -1 });
templateSchema.index({ downloadsCount: -1, rating: -1 });
templateSchema.index({ tags: 1 });
templateSchema.index({ name: 'text', description: 'text', tags: 'text' });
templateSchema.index({ requiredSubscription: 1 });
templateSchema.index({ 'installedBy.userId': 1 });
templateSchema.index({ 'themeData.currency': 1 });
templateSchema.index({ 'themeData.layout.type': 1 });
templateSchema.index({ version: -1 });
// ============================================
// ✅ Virtuals
// ============================================
templateSchema.virtual('downloadUrl').get(function() {
return `/api/templates/${this.slug}/download`;
});
templateSchema.virtual('previewUrl').get(function() {
return `/api/templates/${this.slug}/preview`;
});
templateSchema.virtual('isFree').get(function() {
return this.requiredSubscription === 'free';
});
templateSchema.virtual('totalInstalls').get(function() {
return this.installedBy?.length || 0;
});
templateSchema.virtual('lastInstalledAt').get(function() {
if (!this.installedBy || this.installedBy.length === 0) return null;
return this.installedBy[this.installedBy.length - 1]?.installedAt;
});
// ============================================
// ✅ Pre-save Middleware
// ============================================
templateSchema.pre('save', function(next) {
this.updatedAt = Date.now();
// إنشاء slug من الاسم إذا لم يكن موجوداً
if (!this.slug && this.name) {
this.slug = this.name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
// تنظيف روابط الصور
if (this.previewImages && this.previewImages.length > 0) {
this.previewImages = this.previewImages.filter(img => img && img.trim() !== '');
}
// تحديث متوسط التقييم عند إضافة مراجعة جديدة
if (this.reviews && this.reviews.length > 0) {
const totalRating = this.reviews.reduce((sum, r) => sum + r.rating, 0);
this.rating = totalRating / this.reviews.length;
this.ratingCount = this.reviews.length;
}
next();
});
// ============================================
// ✅ Static Methods
// ============================================
/**
* جلب القوالب المميزة
*/
templateSchema.statics.getFeatured = async function(limit = 6) {
return this.find({ isActive: true, isFeatured: true })
.sort({ downloadsCount: -1, rating: -1 })
.limit(limit)
.select('name slug category previewImage downloadsCount rating requiredSubscription themeData.storeInfo');
};
/**
* جلب القوالب الأكثر تحميلاً
*/
templateSchema.statics.getMostDownloaded = async function(limit = 12) {
return this.find({ isActive: true })
.sort({ downloadsCount: -1 })
.limit(limit)
.select('name slug category previewImage downloadsCount rating requiredSubscription');
};
/**
* جلب قوالب حسب التصنيف
*/
templateSchema.statics.getByCategory = async function(category, limit = 20) {
return this.find({ isActive: true, category })
.sort({ downloadsCount: -1 })
.limit(limit);
};
/**
* البحث في القوالب
*/
templateSchema.statics.searchTemplates = async function(query, filters = {}) {
const searchQuery = { isActive: true };
if (query) {
searchQuery.$text = { $search: query };
}
if (filters.category && filters.category !== 'all') {
searchQuery.category = filters.category;
}
if (filters.requiredSubscription && filters.requiredSubscription !== 'all') {
searchQuery.requiredSubscription = filters.requiredSubscription;
}
if (filters.minRating) {
searchQuery.rating = { $gte: parseFloat(filters.minRating) };
}
let sort = {};
switch (filters.sort) {
case 'popular': sort = { downloadsCount: -1 }; break;
case 'rating': sort = { rating: -1 }; break;
case 'newest': sort = { createdAt: -1 }; break;
default: sort = { downloadsCount: -1 };
}
return this.find(searchQuery)
.sort(sort)
.limit(filters.limit || 20)
.skip(filters.offset || 0);
};
/**
* زيادة عدد المشاهدات
*/
templateSchema.statics.incrementViews = async function(templateId) {
return this.findByIdAndUpdate(templateId, { $inc: { viewsCount: 1 } });
};
/**
* زيادة عدد التحميلات وتسجيل التثبيت
*/
templateSchema.statics.recordDownload = async function(templateId, userId, storeId, appliedSettings = {}) {
const template = await this.findById(templateId);
if (!template) throw new Error('Template not found');
template.downloadsCount += 1;
// تسجيل التثبيت مع الإعدادات المطبقة
template.installedBy.push({
userId,
storeId,
installedAt: new Date(),
appliedSettings
});
// الاحتفاظ بآخر 100 تثبيت فقط
if (template.installedBy.length > 100) {
template.installedBy = template.installedBy.slice(-100);
}
await template.save();
return template;
};
/**
* إضافة تقييم للقالب
*/
templateSchema.statics.addReview = async function(templateId, userId, rating, comment) {
const template = await this.findById(templateId);
if (!template) throw new Error('Template not found');
const existingReview = template.reviews.find(r => r.userId.toString() === userId);
if (existingReview) {
existingReview.rating = rating;
existingReview.comment = comment;
existingReview.createdAt = new Date();
} else {
template.reviews.push({ userId, rating, comment });
}
await template.save();
return template;
};
/**
* جلب قوالب مشابهة حسب التصنيف والعلامات
*/
templateSchema.statics.getSimilarTemplates = async function(templateId, limit = 4) {
const template = await this.findById(templateId);
if (!template) return [];
return this.find({
isActive: true,
_id: { $ne: templateId },
$or: [
{ category: template.category },
{ tags: { $in: template.tags } }
]
})
.sort({ downloadsCount: -1, rating: -1 })
.limit(limit);
};
/**
* تحديث إحصائيات القالب بعد التطبيق
*/
templateSchema.statics.updateAfterApply = async function(templateId, userId, success = true) {
if (!success) return;
const template = await this.findById(templateId);
if (template) {
template.downloadsCount += 1;
// تحديث آخر تثبيت ناجح
const existingInstall = template.installedBy.find(i => i.userId.toString() === userId);
if (existingInstall) {
existingInstall.installedAt = new Date();
} else {
template.installedBy.push({ userId, storeId: userId, installedAt: new Date() });
}
await template.save();
}
};
const TemplateStore = mongoose.model('TemplateStore', templateSchema);
module.exports = TemplateStore;