init commit
This commit is contained in:
80
models/Contact.js
Normal file
80
models/Contact.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const contactSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
lowercase: true,
|
||||
trim: true
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
company: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
subject: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
serviceInterest: {
|
||||
type: String,
|
||||
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other']
|
||||
},
|
||||
budget: {
|
||||
type: String,
|
||||
enum: ['under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m']
|
||||
},
|
||||
timeline: {
|
||||
type: String,
|
||||
enum: ['asap', '1-month', '1-3-months', '3-6-months', 'flexible']
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['new', 'in-progress', 'replied', 'closed'],
|
||||
default: 'new'
|
||||
},
|
||||
priority: {
|
||||
type: String,
|
||||
enum: ['low', 'medium', 'high', 'urgent'],
|
||||
default: 'medium'
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
enum: ['website', 'telegram', 'email', 'phone', 'referral'],
|
||||
default: 'website'
|
||||
},
|
||||
isRead: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
adminNotes: {
|
||||
type: String
|
||||
},
|
||||
ipAddress: {
|
||||
type: String
|
||||
},
|
||||
userAgent: {
|
||||
type: String
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
contactSchema.index({ status: 1, createdAt: -1 });
|
||||
contactSchema.index({ isRead: 1, createdAt: -1 });
|
||||
contactSchema.index({ email: 1 });
|
||||
|
||||
module.exports = mongoose.model('Contact', contactSchema);
|
||||
107
models/Portfolio.js
Normal file
107
models/Portfolio.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const portfolioSchema = new mongoose.Schema({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
shortDescription: {
|
||||
type: String,
|
||||
required: true,
|
||||
maxlength: 200
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'e-commerce', 'other']
|
||||
},
|
||||
technologies: [{
|
||||
type: String,
|
||||
trim: true
|
||||
}],
|
||||
images: [{
|
||||
url: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isPrimary: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
}],
|
||||
clientName: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
projectUrl: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
githubUrl: {
|
||||
type: String,
|
||||
trim: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
enum: ['completed', 'in-progress', 'planning'],
|
||||
default: 'completed'
|
||||
},
|
||||
featured: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
publishedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
completedAt: {
|
||||
type: Date
|
||||
},
|
||||
isPublished: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
viewCount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
likes: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
seo: {
|
||||
metaTitle: String,
|
||||
metaDescription: String,
|
||||
keywords: [String]
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
// Index for search and sorting
|
||||
portfolioSchema.index({ title: 'text', description: 'text', technologies: 'text' });
|
||||
portfolioSchema.index({ category: 1, publishedAt: -1 });
|
||||
portfolioSchema.index({ featured: -1, publishedAt: -1 });
|
||||
|
||||
// Virtual for primary image
|
||||
portfolioSchema.virtual('primaryImage').get(function() {
|
||||
const primary = this.images.find(img => img.isPrimary);
|
||||
return primary || (this.images.length > 0 ? this.images[0] : null);
|
||||
});
|
||||
|
||||
portfolioSchema.set('toJSON', { virtuals: true });
|
||||
|
||||
module.exports = mongoose.model('Portfolio', portfolioSchema);
|
||||
102
models/Service.js
Normal file
102
models/Service.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const serviceSchema = new mongoose.Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
shortDescription: {
|
||||
type: String,
|
||||
required: true,
|
||||
maxlength: 150
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['development', 'design', 'consulting', 'marketing', 'maintenance']
|
||||
},
|
||||
features: [{
|
||||
name: String,
|
||||
description: String,
|
||||
included: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}],
|
||||
pricing: {
|
||||
basePrice: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 0
|
||||
},
|
||||
currency: {
|
||||
type: String,
|
||||
default: 'KRW'
|
||||
},
|
||||
priceType: {
|
||||
type: String,
|
||||
enum: ['fixed', 'hourly', 'project'],
|
||||
default: 'project'
|
||||
},
|
||||
priceRange: {
|
||||
min: Number,
|
||||
max: Number
|
||||
}
|
||||
},
|
||||
estimatedTime: {
|
||||
min: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
unit: {
|
||||
type: String,
|
||||
enum: ['hours', 'days', 'weeks', 'months'],
|
||||
default: 'days'
|
||||
}
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
featured: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
order: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
portfolio: [{
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: 'Portfolio'
|
||||
}],
|
||||
tags: [{
|
||||
type: String,
|
||||
trim: true
|
||||
}],
|
||||
seo: {
|
||||
metaTitle: String,
|
||||
metaDescription: String,
|
||||
keywords: [String]
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
serviceSchema.index({ name: 'text', description: 'text', tags: 'text' });
|
||||
serviceSchema.index({ category: 1, featured: -1, order: 1 });
|
||||
|
||||
module.exports = mongoose.model('Service', serviceSchema);
|
||||
116
models/SiteSettings.js
Normal file
116
models/SiteSettings.js
Normal file
@@ -0,0 +1,116 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const siteSettingsSchema = new mongoose.Schema({
|
||||
siteName: {
|
||||
type: String,
|
||||
default: 'SmartSolTech'
|
||||
},
|
||||
siteDescription: {
|
||||
type: String,
|
||||
default: 'Innovative technology solutions for modern businesses'
|
||||
},
|
||||
logo: {
|
||||
type: String,
|
||||
default: '/images/logo.png'
|
||||
},
|
||||
favicon: {
|
||||
type: String,
|
||||
default: '/images/favicon.ico'
|
||||
},
|
||||
contact: {
|
||||
email: {
|
||||
type: String,
|
||||
default: 'info@smartsoltech.kr'
|
||||
},
|
||||
phone: {
|
||||
type: String,
|
||||
default: '+82-10-0000-0000'
|
||||
},
|
||||
address: {
|
||||
type: String,
|
||||
default: 'Seoul, South Korea'
|
||||
}
|
||||
},
|
||||
social: {
|
||||
facebook: String,
|
||||
twitter: String,
|
||||
linkedin: String,
|
||||
instagram: String,
|
||||
github: String,
|
||||
telegram: String
|
||||
},
|
||||
telegram: {
|
||||
botToken: String,
|
||||
chatId: String,
|
||||
isEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
seo: {
|
||||
metaTitle: {
|
||||
type: String,
|
||||
default: 'SmartSolTech - Technology Solutions'
|
||||
},
|
||||
metaDescription: {
|
||||
type: String,
|
||||
default: 'Professional web development, mobile apps, and digital solutions in Korea'
|
||||
},
|
||||
keywords: {
|
||||
type: String,
|
||||
default: 'web development, mobile apps, UI/UX design, Korea, technology'
|
||||
},
|
||||
googleAnalytics: String,
|
||||
googleTagManager: String
|
||||
},
|
||||
hero: {
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Smart Technology Solutions'
|
||||
},
|
||||
subtitle: {
|
||||
type: String,
|
||||
default: 'We create innovative digital experiences that drive business growth'
|
||||
},
|
||||
backgroundImage: {
|
||||
type: String,
|
||||
default: '/images/hero-bg.jpg'
|
||||
},
|
||||
ctaText: {
|
||||
type: String,
|
||||
default: 'Get Started'
|
||||
},
|
||||
ctaLink: {
|
||||
type: String,
|
||||
default: '#contact'
|
||||
}
|
||||
},
|
||||
about: {
|
||||
title: {
|
||||
type: String,
|
||||
default: 'About SmartSolTech'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: 'We are a team of passionate developers and designers creating cutting-edge technology solutions.'
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
default: '/images/about.jpg'
|
||||
}
|
||||
},
|
||||
maintenance: {
|
||||
isEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
default: 'We are currently performing maintenance. Please check back soon.'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('SiteSettings', siteSettingsSchema);
|
||||
75
models/User.js
Normal file
75
models/User.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const mongoose = require('mongoose');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
lowercase: true,
|
||||
trim: true
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
minlength: 6
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
enum: ['admin', 'moderator'],
|
||||
default: 'admin'
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
lastLogin: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
},
|
||||
updatedAt: {
|
||||
type: Date,
|
||||
default: Date.now
|
||||
}
|
||||
}, {
|
||||
timestamps: true
|
||||
});
|
||||
|
||||
// Hash password before saving
|
||||
userSchema.pre('save', async function(next) {
|
||||
if (!this.isModified('password')) return next();
|
||||
|
||||
try {
|
||||
const salt = await bcrypt.genSalt(12);
|
||||
this.password = await bcrypt.hash(this.password, salt);
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
// Compare password method
|
||||
userSchema.methods.comparePassword = async function(candidatePassword) {
|
||||
return bcrypt.compare(candidatePassword, this.password);
|
||||
};
|
||||
|
||||
// Update last login
|
||||
userSchema.methods.updateLastLogin = function() {
|
||||
this.lastLogin = new Date();
|
||||
return this.save();
|
||||
};
|
||||
|
||||
module.exports = mongoose.model('User', userSchema);
|
||||
Reference in New Issue
Block a user