feat: Реализован полный CRUD для админ-панели и улучшена функциональность

- Portfolio CRUD: добавление, редактирование, удаление, переключение публикации
- Services CRUD: полное управление услугами с возможностью активации/деактивации
- Banner system: новая модель Banner с CRUD операциями и аналитикой кликов
- Telegram integration: расширенные настройки бота, обнаружение чатов, отправка сообщений
- Media management: улучшенная загрузка файлов с оптимизацией изображений и превью
- UI improvements: обновлённые админ-панели с rich-text редактором и drag&drop загрузкой
- Database: добавлена таблица banners с полями для баннеров и аналитики
This commit is contained in:
2025-10-22 20:32:16 +09:00
parent 150891b29d
commit 9477ff6de0
69 changed files with 11451 additions and 2321 deletions

View File

@@ -1,80 +1,108 @@
const mongoose = require('mongoose');
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const contactSchema = new mongoose.Schema({
const Contact = sequelize.define('Contact', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
name: {
type: String,
required: true,
trim: true
type: DataTypes.STRING,
allowNull: false,
set(value) {
this.setDataValue('name', value.trim());
}
},
email: {
type: String,
required: true,
lowercase: true,
trim: true
type: DataTypes.STRING,
allowNull: false,
validate: {
isEmail: true
},
set(value) {
this.setDataValue('email', value.toLowerCase().trim());
}
},
phone: {
type: String,
trim: true
type: DataTypes.STRING,
allowNull: true,
set(value) {
this.setDataValue('phone', value ? value.trim() : null);
}
},
company: {
type: String,
trim: true
type: DataTypes.STRING,
allowNull: true,
set(value) {
this.setDataValue('company', value ? value.trim() : null);
}
},
subject: {
type: String,
required: true,
trim: true
type: DataTypes.STRING,
allowNull: false,
set(value) {
this.setDataValue('subject', value.trim());
}
},
message: {
type: String,
required: true
type: DataTypes.TEXT,
allowNull: false
},
serviceInterest: {
type: String,
enum: ['web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other']
type: DataTypes.ENUM('web-development', 'mobile-app', 'ui-ux-design', 'branding', 'consulting', 'other'),
allowNull: true
},
budget: {
type: String,
enum: ['under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m']
type: DataTypes.ENUM('under-1m', '1m-5m', '5m-10m', '10m-20m', '20m-50m', 'over-50m'),
allowNull: true
},
timeline: {
type: String,
enum: ['asap', '1-month', '1-3-months', '3-6-months', 'flexible']
type: DataTypes.ENUM('asap', '1-month', '1-3-months', '3-6-months', 'flexible'),
allowNull: true
},
status: {
type: String,
enum: ['new', 'in-progress', 'replied', 'closed'],
default: 'new'
type: DataTypes.ENUM('new', 'in-progress', 'replied', 'closed'),
defaultValue: 'new'
},
priority: {
type: String,
enum: ['low', 'medium', 'high', 'urgent'],
default: 'medium'
type: DataTypes.ENUM('low', 'medium', 'high', 'urgent'),
defaultValue: 'medium'
},
source: {
type: String,
enum: ['website', 'telegram', 'email', 'phone', 'referral'],
default: 'website'
type: DataTypes.ENUM('website', 'telegram', 'email', 'phone', 'referral'),
defaultValue: 'website'
},
isRead: {
type: Boolean,
default: false
type: DataTypes.BOOLEAN,
defaultValue: false
},
adminNotes: {
type: String
type: DataTypes.TEXT,
allowNull: true
},
ipAddress: {
type: String
type: DataTypes.STRING,
allowNull: true
},
userAgent: {
type: String
type: DataTypes.TEXT,
allowNull: true
}
}, {
timestamps: true
tableName: 'contacts',
timestamps: true,
indexes: [
{
fields: ['status', 'createdAt']
},
{
fields: ['isRead', 'createdAt']
},
{
fields: ['email']
}
]
});
contactSchema.index({ status: 1, createdAt: -1 });
contactSchema.index({ isRead: 1, createdAt: -1 });
contactSchema.index({ email: 1 });
module.exports = mongoose.model('Contact', contactSchema);
module.exports = Contact;