server / models /StoreShortcode.js
Mark-Lasfar
feat(seo): add complete dynamic sitemap system with posts, jobs, templates, pages, tags, images, and comments
bee235c
Raw
History Blame Contribute Delete
29.4 kB
const mongoose = require('mongoose');
/**
* نموذج الأكواد المختصرة - يدعم الـ Shortcodes المخصصة والمدمجة
* @version 2.0.0
*/
const shortcodeParameterSchema = new mongoose.Schema({
name: { type: String, required: true },
type: { type: String, enum: ['string', 'number', 'boolean', 'array'], default: 'string' },
required: { type: Boolean, default: false },
defaultValue: { type: mongoose.Schema.Types.Mixed },
description: { type: String }
}, { _id: false });
const storeShortcodeSchema = new mongoose.Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true
},
// معلومات الـ Shortcode
name: {
type: String,
required: true,
trim: true,
lowercase: true,
match: /^[a-z][a-z0-9_-]*$/,
index: true
},
displayName: {
type: String,
required: true,
trim: true,
maxlength: 50
},
description: {
type: String,
maxlength: 500
},
// نوع الـ Shortcode
type: {
type: String,
enum: ['builtin', 'custom', 'dynamic'],
default: 'custom'
},
// القالب أو الدالة التي تنشئ المخرجات
template: {
type: String,
default: '',
maxlength: 50000
},
// المعاملات المقبولة
parameters: [shortcodeParameterSchema],
// إعدادات إضافية
settings: {
cacheEnabled: { type: Boolean, default: false },
cacheTTL: { type: Number, default: 3600 }, // seconds
requiresAuth: { type: Boolean, default: false },
showInEditor: { type: Boolean, default: true },
icon: { type: String, default: 'bx-code-alt' },
category: { type: String, default: 'general' }
},
// المخرجات (يمكن تخزينها مؤقتاً)
cachedOutput: {
html: { type: String },
expiresAt: { type: Date }
},
// إحصائيات الاستخدام
stats: {
usageCount: { type: Number, default: 0 },
lastUsedAt: { type: Date },
avgRenderTime: { type: Number, default: 0 }
},
isActive: {
type: Boolean,
default: true,
index: true
},
isDeleted: {
type: Boolean,
default: false,
index: true
},
createdAt: { type: Date, default: Date.now, immutable: true },
updatedAt: { type: Date, default: Date.now }
}, {
timestamps: true
});
// ============================================
// Indexes
// ============================================
storeShortcodeSchema.index({ userId: 1, name: 1 }, { unique: true });
storeShortcodeSchema.index({ userId: 1, isActive: 1 });
storeShortcodeSchema.index({ 'stats.usageCount': -1 });
storeShortcodeSchema.index({ 'settings.category': 1 });
// ============================================
// Static Methods - Shortcodes المدمجة
// ============================================
/**
* الحصول على قائمة الـ Shortcodes المدمجة في النظام
* @returns {Array} - قائمة الـ Shortcodes المدمجة
*/
storeShortcodeSchema.statics.getBuiltinShortcodes = function() {
return [
{
name: 'products',
displayName: 'Products Grid',
description: 'Display a grid of products with filtering options',
category: 'products',
icon: 'bx-package',
parameters: [
{ name: 'limit', type: 'number', defaultValue: 12, description: 'Number of products to show' },
{ name: 'type', type: 'string', defaultValue: 'all', description: 'Product type (digital, project, service, all)' },
{ name: 'category', type: 'string', description: 'Filter by category/tag' },
{ name: 'exclude', type: 'array', description: 'Product IDs to exclude (comma separated)' },
{ name: 'layout', type: 'string', defaultValue: 'grid', description: 'Layout type (grid, list, carousel)' },
{ name: 'columns', type: 'number', defaultValue: 4, description: 'Number of columns (2,3,4,6)' },
{ name: 'sort', type: 'string', defaultValue: 'newest', description: 'Sort by (newest, popular, price_asc, price_desc)' }
]
},
{
name: 'product',
displayName: 'Single Product',
description: 'Display a single product by ID',
category: 'products',
icon: 'bx-package',
parameters: [
{ name: 'id', type: 'string', required: true, description: 'Product ID' },
{ name: 'layout', type: 'string', defaultValue: 'default', description: 'Layout style (default, compact, detailed)' }
]
},
{
name: 'contact-form',
displayName: 'Contact Form',
description: 'Display a contact form for customers to message you',
category: 'forms',
icon: 'bx-envelope',
parameters: [
{ name: 'title', type: 'string', defaultValue: 'Contact Us', description: 'Form title' },
{ name: 'fields', type: 'array', defaultValue: 'name,email,message', description: 'Form fields to show' }
]
},
{
name: 'store-info',
displayName: 'Store Information',
description: 'Display store information (name, description, followers, etc.)',
category: 'info',
icon: 'bx-store',
parameters: [
{ name: 'field', type: 'string', required: true, description: 'Field to display (name, description, logo, followers, rating)' }
]
},
{
name: 'cart-count',
displayName: 'Cart Count',
description: 'Display the number of items in cart',
category: 'cart',
icon: 'bx-cart',
parameters: []
},
{
name: 'categories',
displayName: 'Categories List',
description: 'Display all product categories',
category: 'products',
icon: 'bx-category',
parameters: [
{ name: 'showCount', type: 'boolean', defaultValue: true, description: 'Show product count per category' },
{ name: 'layout', type: 'string', defaultValue: 'grid', description: 'Layout (grid, list, chips)' }
]
},
{
name: 'search-form',
displayName: 'Search Form',
description: 'Display a search form for products',
category: 'forms',
icon: 'bx-search',
parameters: [
{ name: 'placeholder', type: 'string', defaultValue: 'Search products...', description: 'Input placeholder' },
{ name: 'showFilters', type: 'boolean', defaultValue: false, description: 'Show advanced filters' }
]
},
{
name: 'testimonials',
displayName: 'Testimonials',
description: 'Display customer testimonials',
category: 'social',
icon: 'bx-chat',
parameters: [
{ name: 'limit', type: 'number', defaultValue: 6, description: 'Number of testimonials to show' },
{ name: 'autoplay', type: 'boolean', defaultValue: false, description: 'Enable carousel autoplay' }
]
},
{
name: 'social-links',
displayName: 'Social Links',
description: 'Display store social media links',
category: 'social',
icon: 'bx-share-alt',
parameters: [
{ name: 'layout', type: 'string', defaultValue: 'horizontal', description: 'Layout (horizontal, vertical)' }
]
}
];
};
/**
* تهيئة الـ Shortcodes المدمجة لمستخدم جديد
* @param {string} userId - معرف المستخدم
*/
storeShortcodeSchema.statics.initializeBuiltins = async function(userId) {
const builtins = this.getBuiltinShortcodes();
for (const shortcode of builtins) {
await this.findOneAndUpdate(
{ userId, name: shortcode.name, type: 'builtin' },
{
...shortcode,
userId,
type: 'builtin',
isActive: true
},
{ upsert: true }
);
}
};
// ============================================
// Instance Methods
// ============================================
/**
* تنفيذ الـ Shortcode وإرجاع HTML
* @param {Object} attrs - المعاملات المُمررة
* @param {Object} context - السياق (userId, storeId, etc.)
* @returns {Promise<string>} - HTML الناتج
*/
storeShortcodeSchema.methods.render = async function(attrs = {}, context = {}) {
const startTime = Date.now();
try {
// التحقق من التخزين المؤقت
if (this.settings.cacheEnabled && this.cachedOutput && this.cachedOutput.expiresAt > new Date()) {
return this.cachedOutput.html;
}
// دمج المعاملات مع القيم الافتراضية
const mergedAttrs = {};
for (const param of this.parameters) {
mergedAttrs[param.name] = attrs[param.name] !== undefined
? attrs[param.name]
: param.defaultValue;
}
// تنفيذ القالب
let output = '';
if (this.type === 'builtin') {
output = await this.renderBuiltin(mergedAttrs, context);
} else if (this.template) {
output = await this.renderCustomTemplate(mergedAttrs, context);
}
// تحديث الإحصائيات
this.stats.usageCount += 1;
this.stats.lastUsedAt = new Date();
this.stats.avgRenderTime = (this.stats.avgRenderTime + (Date.now() - startTime)) / 2;
// تحديث التخزين المؤقت
if (this.settings.cacheEnabled) {
this.cachedOutput = {
html: output,
expiresAt: new Date(Date.now() + this.settings.cacheTTL * 1000)
};
}
await this.save();
return output;
} catch (error) {
console.error(`Error rendering shortcode ${this.name}:`, error);
return `<div class="shortcode-error">Error rendering ${this.displayName}: ${error.message}</div>`;
}
};
/**
* تنفيذ Shortcode مدمج
* @private
*/
storeShortcodeSchema.methods.renderBuiltin = async function(attrs, context) {
const { storeId, userToken } = context;
switch (this.name) {
case 'products':
return await renderProductsShortcode(attrs, storeId);
case 'product':
return await renderSingleProductShortcode(attrs, storeId);
case 'contact-form':
return renderContactFormShortcode(attrs, storeId);
case 'store-info':
return await renderStoreInfoShortcode(attrs, storeId);
case 'cart-count':
return `<span class="cart-count" data-user-token="${userToken || ''}">0</span>`;
case 'categories':
return await renderCategoriesShortcode(attrs, storeId);
case 'search-form':
return renderSearchFormShortcode(attrs);
case 'testimonials':
return await renderTestimonialsShortcode(attrs, storeId);
case 'social-links':
return await renderSocialLinksShortcode(attrs, storeId);
default:
return `<div class="shortcode-unknown">Unknown shortcode: ${this.name}</div>`;
}
};
/**
* تنفيذ قالب مخصص
* @private
*/
storeShortcodeSchema.methods.renderCustomTemplate = async function(attrs, context) {
let html = this.template;
// استبدال المتغيرات في القالب
for (const [key, value] of Object.entries(attrs)) {
const regex = new RegExp(`\\{\\{\\s*${key}\\s*\\}\\}`, 'g');
html = html.replace(regex, String(value ?? ''));
}
return html;
};
// ============================================
// دوال مساعدة لتنفيذ الـ Shortcodes المدمجة
// ============================================
async function renderProductsShortcode(attrs, storeId) {
const Product = mongoose.model('Product');
const StoreSettings = mongoose.model('StoreSettings');
const limit = Math.min(parseInt(attrs.limit) || 12, 48);
const type = attrs.type !== 'all' ? attrs.type : null;
const sort = attrs.sort || 'newest';
const layout = attrs.layout || 'grid';
const columns = parseInt(attrs.columns) || 4;
let query = { userId: storeId, isActive: true };
if (type) query.type = type;
if (attrs.category) query.tags = attrs.category;
if (attrs.exclude) query._id = { $nin: attrs.exclude.split(',') };
let productsQuery = Product.find(query);
switch (sort) {
case 'popular':
productsQuery = productsQuery.sort({ salesCount: -1 });
break;
case 'price_asc':
productsQuery = productsQuery.sort({ price: 1 });
break;
case 'price_desc':
productsQuery = productsQuery.sort({ price: -1 });
break;
default:
productsQuery = productsQuery.sort({ createdAt: -1 });
}
const products = await productsQuery.limit(limit);
const storeSettings = await StoreSettings.findOne({ userId: storeId });
if (!products.length) {
return '<div class="text-center py-8 text-gray-500">No products found</div>';
}
const columnsClass = {
2: 'md:grid-cols-2',
3: 'md:grid-cols-3',
4: 'md:grid-cols-4',
6: 'md:grid-cols-6'
}[columns] || 'md:grid-cols-4';
return `
<div class="shortcode-products products-${layout} grid grid-cols-1 sm:grid-cols-2 ${columnsClass} gap-6">
${products.map(product => generateProductCardHtml(product, storeSettings)).join('')}
</div>
`;
}
async function renderSingleProductShortcode(attrs, storeId) {
const Product = mongoose.model('Product');
const StoreSettings = mongoose.model('StoreSettings');
const product = await Product.findOne({ _id: attrs.id, userId: storeId, isActive: true });
if (!product) {
return '<div class="text-center py-4 text-gray-500">Product not found</div>';
}
const storeSettings = await StoreSettings.findOne({ userId: storeId });
const layout = attrs.layout || 'default';
if (layout === 'compact') {
return generateCompactProductHtml(product, storeSettings);
}
if (layout === 'detailed') {
return generateDetailedProductHtml(product, storeSettings);
}
return generateProductCardHtml(product, storeSettings);
}
function generateProductCardHtml(product, storeSettings) {
const currencySymbol = storeSettings?.currencySymbol || '$';
const primaryColor = storeSettings?.primaryColor || '#3b82f6';
const imageUrl = product.images?.[0] || '/assets/img/default-product.png';
return `
<div class="product-card bg-white dark:bg-gray-800 rounded-xl overflow-hidden shadow-md hover:shadow-xl transition-all duration-300">
<img src="${imageUrl}" class="w-full h-48 object-cover" alt="${escapeHtml(product.title)}" loading="lazy">
<div class="p-4">
<h3 class="font-semibold text-lg line-clamp-1">${escapeHtml(product.title)}</h3>
<p class="text-sm text-gray-500 mt-1 line-clamp-2">${escapeHtml(product.description?.substring(0, 80))}</p>
<div class="flex justify-between items-center mt-3">
<span class="text-xl font-bold" style="color: ${primaryColor}">${product.price} ${currencySymbol}</span>
<button onclick="addToCart('${product._id}')"
class="px-4 py-2 text-white rounded-lg text-sm transition-all hover:opacity-90"
style="background-color: ${primaryColor}">
Add to Cart
</button>
</div>
</div>
</div>
`;
}
function generateCompactProductHtml(product, storeSettings) {
const currencySymbol = storeSettings?.currencySymbol || '$';
const imageUrl = product.images?.[0] || '/assets/img/default-product.png';
return `
<div class="compact-product flex items-center gap-4 p-3 bg-white dark:bg-gray-800 rounded-lg shadow-sm hover:shadow-md transition">
<img src="${imageUrl}" class="w-16 h-16 object-cover rounded" alt="${escapeHtml(product.title)}">
<div class="flex-1">
<h4 class="font-medium">${escapeHtml(product.title)}</h4>
<span class="text-lg font-bold text-blue-600">${product.price} ${currencySymbol}</span>
</div>
<button onclick="addToCart('${product._id}')" class="px-3 py-1 bg-blue-500 text-white rounded-lg text-sm">
Buy
</button>
</div>
`;
}
function generateDetailedProductHtml(product, storeSettings) {
const currencySymbol = storeSettings?.currencySymbol || '$';
const primaryColor = storeSettings?.primaryColor || '#3b82f6';
const imageUrl = product.images?.[0] || '/assets/img/default-product.png';
return `
<div class="detailed-product flex flex-col md:flex-row gap-6 p-6 bg-white dark:bg-gray-800 rounded-xl shadow-lg">
<div class="md:w-1/3">
<img src="${imageUrl}" class="w-full rounded-lg object-cover" alt="${escapeHtml(product.title)}">
${product.images && product.images.length > 1 ? `
<div class="flex gap-2 mt-3">
${product.images.slice(1, 4).map(img => `
<img src="${img}" class="w-16 h-16 rounded cursor-pointer hover:opacity-80" onclick="this.parentElement.parentElement.querySelector('img:first-child').src='${img}'">
`).join('')}
</div>
` : ''}
</div>
<div class="md:w-2/3">
<h2 class="text-2xl font-bold">${escapeHtml(product.title)}</h2>
<div class="flex items-center gap-2 mt-2">
<span class="text-3xl font-bold" style="color: ${primaryColor}">${product.price} ${currencySymbol}</span>
${product.type === 'digital' ? '<span class="px-2 py-1 text-xs bg-blue-100 text-blue-700 rounded-full">Digital</span>' : ''}
${product.type === 'service' ? '<span class="px-2 py-1 text-xs bg-purple-100 text-purple-700 rounded-full">Service</span>' : ''}
</div>
<p class="text-gray-600 dark:text-gray-400 mt-4 leading-relaxed">${escapeHtml(product.description)}</p>
${product.deliveryTime ? `
<p class="mt-3 text-sm text-gray-500"><i class="bx bx-time"></i> Delivery: ${escapeHtml(product.deliveryTime)}</p>
` : ''}
<button onclick="addToCart('${product._id}')"
class="mt-6 px-6 py-3 text-white rounded-lg font-semibold transition-all hover:opacity-90"
style="background-color: ${primaryColor}">
<i class="bx bx-cart-add"></i> Add to Cart
</button>
</div>
</div>
`;
}
async function renderStoreInfoShortcode(attrs, storeId) {
const StoreSettings = mongoose.model('StoreSettings');
const User = mongoose.model('User');
const StoreFollow = mongoose.model('StoreFollow');
const field = attrs.field;
const store = await User.findById(storeId);
const settings = await StoreSettings.findOne({ userId: storeId });
switch (field) {
case 'name':
return settings?.storeName || store?.username || 'Store';
case 'description':
return settings?.storeDescription || '';
case 'logo':
return settings?.storeLogo ? `<img src="${settings.storeLogo}" alt="Store Logo" class="store-logo">` : '';
case 'followers':
const followers = await StoreFollow.countDocuments({ storeId });
return followers.toString();
case 'rating':
const products = await mongoose.model('Product').find({ userId: storeId });
const avgRating = products.reduce((sum, p) => sum + (p.averageRating || 0), 0) / (products.length || 1);
return avgRating.toFixed(1);
default:
return '';
}
}
function renderContactFormShortcode(attrs, storeId) {
const title = attrs.title || 'Contact Us';
const fields = attrs.fields ? attrs.fields.split(',') : ['name', 'email', 'message'];
const fieldsHtml = fields.map(field => {
switch (field) {
case 'name':
return `<input type="text" name="name" placeholder="Your Name" class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500" required>`;
case 'email':
return `<input type="email" name="email" placeholder="Your Email" class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500" required>`;
case 'message':
return `<textarea name="message" rows="4" placeholder="Your Message" class="w-full p-3 border rounded-lg focus:ring-2 focus:ring-blue-500" required></textarea>`;
default:
return '';
}
}).join('');
return `
<div class="shortcode-contact-form bg-white dark:bg-gray-800 rounded-xl shadow-md p-6">
<h3 class="text-xl font-bold mb-4">${escapeHtml(title)}</h3>
<form onsubmit="event.preventDefault(); sendStoreContactMessage('${storeId}')" class="space-y-4">
${fieldsHtml}
<button type="submit" class="w-full py-3 bg-blue-500 text-white rounded-lg font-semibold hover:bg-blue-600 transition">
Send Message
</button>
</form>
<div id="contact-form-response" class="mt-3 text-center hidden"></div>
</div>
`;
}
async function renderCategoriesShortcode(attrs, storeId) {
const Product = mongoose.model('Product');
const categories = [
{ name: 'Digital', type: 'digital', icon: 'bx-download' },
{ name: 'Projects', type: 'project', icon: 'bx-code-alt' },
{ name: 'Services', type: 'service', icon: 'bx-briefcase' }
];
const counts = await Promise.all(
categories.map(async cat => ({
...cat,
count: await Product.countDocuments({ userId: storeId, type: cat.type, isActive: true })
}))
);
const layout = attrs.layout || 'grid';
const showCount = attrs.showCount !== 'false';
if (layout === 'list') {
return `
<div class="categories-list space-y-2">
${counts.filter(c => c.count > 0).map(cat => `
<a href="#" onclick="filterProductsByCategory('${cat.type}'); return false;"
class="flex justify-between items-center p-3 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition">
<span><i class='bx ${cat.icon} mr-2'></i> ${cat.name}</span>
${showCount ? `<span class="text-sm text-gray-500">(${cat.count})</span>` : ''}
</a>
`).join('')}
</div>
`;
}
return `
<div class="categories-grid grid grid-cols-2 md:grid-cols-3 gap-4">
${counts.filter(c => c.count > 0).map(cat => `
<a href="#" onclick="filterProductsByCategory('${cat.type}'); return false;"
class="category-card p-4 bg-white dark:bg-gray-800 rounded-lg text-center hover:shadow-md transition">
<i class='bx ${cat.icon} text-3xl text-blue-500'></i>
<h3 class="font-medium mt-2">${cat.name}</h3>
${showCount ? `<p class="text-xs text-gray-500">${cat.count} products</p>` : ''}
</a>
`).join('')}
</div>
`;
}
function renderSearchFormShortcode(attrs) {
const placeholder = attrs.placeholder || 'Search products...';
const showFilters = attrs.showFilters === 'true';
return `
<div class="shortcode-search-form">
<form onsubmit="event.preventDefault(); searchProducts(this.querySelector('input').value)" class="relative">
<input type="text" placeholder="${escapeHtml(placeholder)}"
class="w-full p-3 pl-12 border rounded-lg focus:ring-2 focus:ring-blue-500">
<i class='bx bx-search absolute left-4 top-1/2 transform -translate-y-1/2 text-gray-400 text-xl'></i>
</form>
${showFilters ? `
<div class="flex gap-2 mt-3 flex-wrap">
<button onclick="filterProductsByCategory('digital')" class="px-3 py-1 text-sm bg-gray-100 rounded-full">Digital</button>
<button onclick="filterProductsByCategory('project')" class="px-3 py-1 text-sm bg-gray-100 rounded-full">Projects</button>
<button onclick="filterProductsByCategory('service')" class="px-3 py-1 text-sm bg-gray-100 rounded-full">Services</button>
</div>
` : ''}
</div>
`;
}
async function renderTestimonialsShortcode(attrs, storeId) {
// يمكن جلب التقييمات الفعلية من قاعدة البيانات
const Review = mongoose.model('Review');
const reviews = await Review.find({ storeId, isApproved: true })
.sort({ createdAt: -1 })
.limit(Math.min(parseInt(attrs.limit) || 6, 12));
if (!reviews.length) {
return '<div class="text-center py-8 text-gray-500">No testimonials yet</div>';
}
const autoplay = attrs.autoplay === 'true';
const carouselClass = autoplay ? 'testimonials-carousel' : '';
return `
<div class="testimonials-grid grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 ${carouselClass}">
${reviews.map(review => `
<div class="testimonial-card p-6 bg-white dark:bg-gray-800 rounded-xl shadow-md">
<div class="flex gap-1 mb-3">
${Array(5).fill().map((_, i) => `
<i class='bx ${i < review.rating ? 'bxs-star' : 'bx-star'} text-yellow-400'></i>
`).join('')}
</div>
<p class="text-gray-600 dark:text-gray-400 mb-4">"${escapeHtml(review.comment?.substring(0, 200))}"</p>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 flex items-center justify-center">
<span class="text-white font-bold">${escapeHtml(review.author?.charAt(0) || 'U')}</span>
</div>
<div>
<p class="font-semibold">${escapeHtml(review.author || 'Anonymous')}</p>
<p class="text-xs text-gray-500">Verified Buyer</p>
</div>
</div>
</div>
`).join('')}
</div>
`;
}
async function renderSocialLinksShortcode(attrs, storeId) {
const StoreSettings = mongoose.model('StoreSettings');
const settings = await StoreSettings.findOne({ userId: storeId });
const socialLinks = settings?.socialLinks || {};
const socialIcons = {
instagram: 'bxl-instagram',
facebook: 'bxl-facebook',
twitter: 'bxl-twitter',
tiktok: 'bxl-tiktok',
linkedin: 'bxl-linkedin',
github: 'bxl-github',
youtube: 'bxl-youtube'
};
const activeLinks = Object.entries(socialLinks)
.filter(([key, value]) => value && socialIcons[key]);
if (!activeLinks.length) return '';
const layout = attrs.layout || 'horizontal';
const flexDirection = layout === 'vertical' ? 'flex-col' : 'flex-row';
return `
<div class="social-links flex ${flexDirection} gap-4 flex-wrap">
${activeLinks.map(([platform, url]) => `
<a href="${escapeHtml(url)}" target="_blank" rel="noopener noreferrer"
class="social-link text-gray-600 hover:text-blue-500 transition transform hover:scale-110">
<i class='bx ${socialIcons[platform]} text-2xl'></i>
</a>
`).join('')}
</div>
`;
}
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
});
}
const StoreShortcode = mongoose.model('StoreShortcode', storeShortcodeSchema);
module.exports = StoreShortcode;