🚀 Korea Tourism Agency - Complete implementation
✨ Features: - Modern tourism website with responsive design - AdminJS admin panel with image editor integration - PostgreSQL database with comprehensive schema - Docker containerization - Image upload and gallery management 🛠 Tech Stack: - Backend: Node.js + Express.js - Database: PostgreSQL 13+ - Frontend: HTML/CSS/JS with responsive design - Admin: AdminJS with custom components - Deployment: Docker + Docker Compose - Image Processing: Sharp with optimization 📱 Admin Features: - Routes/Tours management (city, mountain, fishing) - Guides profiles with specializations - Articles and blog system - Image editor with upload/gallery/URL options - User management and authentication - Responsive admin interface 🎨 Design: - Korean tourism focused branding - Mobile-first responsive design - Custom CSS with modern aesthetics - Image optimization and gallery - SEO-friendly structure 🔒 Security: - Helmet.js security headers - bcrypt password hashing - Input validation and sanitization - CORS protection - Environment variables
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
import AdminJS from 'adminjs';
|
||||
import AdminJSExpress from '@adminjs/express';
|
||||
import AdminJSSequelize from '@adminjs/sequelize';
|
||||
import uploadFeature from '@adminjs/upload';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import pkg from 'pg';
|
||||
import { Sequelize, DataTypes } from 'sequelize';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const { Pool } = pkg;
|
||||
|
||||
// Регистрируем адаптер Sequelize
|
||||
@@ -61,6 +68,7 @@ const Guides = sequelize.define('guides', {
|
||||
specialization: { type: DataTypes.ENUM('city', 'mountain', 'fishing', 'general') },
|
||||
bio: { type: DataTypes.TEXT },
|
||||
experience: { type: DataTypes.INTEGER },
|
||||
image_url: { type: DataTypes.STRING },
|
||||
hourly_rate: { type: DataTypes.DECIMAL(10, 2) },
|
||||
is_active: { type: DataTypes.BOOLEAN, defaultValue: true },
|
||||
created_at: { type: DataTypes.DATE, defaultValue: DataTypes.NOW }
|
||||
@@ -280,7 +288,7 @@ const adminJsOptions = {
|
||||
},
|
||||
image_url: {
|
||||
type: 'string',
|
||||
description: 'URL изображения тура (например: /images/tours/seoul-1.jpg)'
|
||||
description: 'Изображение тура. Кнопка "Выбрать" будет добавлена автоматически'
|
||||
},
|
||||
is_featured: { type: 'boolean' },
|
||||
is_active: { type: 'boolean' },
|
||||
@@ -298,8 +306,8 @@ const adminJsOptions = {
|
||||
options: {
|
||||
parent: { name: 'Персонал', icon: 'Users' },
|
||||
listProperties: ['id', 'name', 'email', 'specialization', 'experience', 'hourly_rate', 'is_active'],
|
||||
editProperties: ['name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'hourly_rate', 'is_active'],
|
||||
showProperties: ['id', 'name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'hourly_rate', 'is_active', 'created_at'],
|
||||
editProperties: ['name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'image_url', 'hourly_rate', 'is_active'],
|
||||
showProperties: ['id', 'name', 'email', 'phone', 'languages', 'specialization', 'bio', 'experience', 'image_url', 'hourly_rate', 'is_active', 'created_at'],
|
||||
filterProperties: ['name', 'specialization', 'is_active'],
|
||||
properties: {
|
||||
name: {
|
||||
@@ -328,6 +336,10 @@ const adminJsOptions = {
|
||||
type: 'number',
|
||||
description: 'Опыт работы в годах',
|
||||
},
|
||||
image_url: {
|
||||
type: 'string',
|
||||
description: 'Фотография гида. Кнопка "Выбрать" будет добавлена автоматически'
|
||||
},
|
||||
hourly_rate: {
|
||||
type: 'number',
|
||||
description: 'Ставка за час в вонах',
|
||||
@@ -344,7 +356,7 @@ const adminJsOptions = {
|
||||
options: {
|
||||
parent: { name: 'Контент', icon: 'DocumentText' },
|
||||
listProperties: ['id', 'title', 'category', 'is_published', 'views', 'created_at'],
|
||||
editProperties: ['title', 'excerpt', 'content', 'category', 'is_published'],
|
||||
editProperties: ['title', 'excerpt', 'content', 'category', 'image_url', 'is_published'],
|
||||
showProperties: ['id', 'title', 'excerpt', 'content', 'category', 'is_published', 'views', 'created_at', 'updated_at'],
|
||||
filterProperties: ['title', 'category', 'is_published'],
|
||||
properties: {
|
||||
@@ -369,6 +381,10 @@ const adminJsOptions = {
|
||||
{ value: 'history', label: 'История' }
|
||||
],
|
||||
},
|
||||
image_url: {
|
||||
type: 'string',
|
||||
description: 'Изображение статьи. Кнопка "Выбрать" будет добавлена автоматически'
|
||||
},
|
||||
is_published: { type: 'boolean' },
|
||||
views: {
|
||||
type: 'number',
|
||||
@@ -730,10 +746,15 @@ const adminJsOptions = {
|
||||
},
|
||||
dashboard: {
|
||||
component: false
|
||||
},
|
||||
assets: {
|
||||
styles: ['/css/admin-custom.css'],
|
||||
scripts: ['/js/admin-image-selector-fixed.js']
|
||||
}
|
||||
};
|
||||
|
||||
// Создаем экземпляр AdminJS
|
||||
// Создаем экземпляр AdminJS с componentLoader
|
||||
// Создание AdminJS с конфигурацией
|
||||
const adminJs = new AdminJS(adminJsOptions);
|
||||
|
||||
// Настраиваем аутентификацию
|
||||
|
||||
Reference in New Issue
Block a user