Initial commit: Korea Tourism Agency website with AdminJS

- Full-stack Node.js/Express application with PostgreSQL
- Modern ES modules architecture
- AdminJS admin panel with Sequelize ORM
- Tourism routes, guides, articles, bookings management
- Responsive Bootstrap 5 frontend
- Docker containerization with docker-compose
- Complete database schema with migrations
- Authentication system for admin panel
- Dynamic placeholder images for tour categories
This commit is contained in:
2025-11-29 18:13:17 +09:00
commit 409e6c146b
53 changed files with 16195 additions and 0 deletions

195
views/articles/detail.ejs Normal file
View File

@@ -0,0 +1,195 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white py-5">
<div class="container">
<div class="row">
<div class="col-lg-8">
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/" class="text-light">Главная</a></li>
<li class="breadcrumb-item"><a href="/articles" class="text-light">Статьи</a></li>
<li class="breadcrumb-item active" aria-current="page"><%= article.title %></li>
</ol>
</nav>
<% if (article.category) { %>
<span class="badge bg-light text-dark mb-3">
<%= getCategoryLabel(article.category) %>
</span>
<% } %>
<h1 class="display-5 fw-bold mb-3"><%= article.title %></h1>
<% if (article.excerpt) { %>
<p class="lead mb-4"><%= article.excerpt %></p>
<% } %>
<div class="d-flex align-items-center text-light-50">
<i class="fas fa-calendar-alt me-2"></i>
<span class="me-4"><%= formatDate(article.created_at) %></span>
<i class="fas fa-eye me-2"></i>
<span><%= article.views %> просмотров</span>
</div>
</div>
</div>
</div>
</section>
<!-- Article Content -->
<section class="py-5">
<div class="container">
<div class="row">
<div class="col-lg-8">
<article class="mb-5">
<div class="mb-4">
<img src="<%= (article.image_url && article.image_url.trim()) ? article.image_url : '/images/placeholder.jpg' %>" alt="<%= article.title %>" class="img-fluid rounded shadow">
</div>
<div class="article-content">
<%- article.content %>
</div>
</article>
<!-- Social Share -->
<div class="border-top pt-4 mb-5">
<h5 class="mb-3">Поделиться статьей:</h5>
<div class="d-flex gap-2">
<a href="#" class="btn btn-outline-primary btn-sm" onclick="shareToFacebook()">
<i class="fab fa-facebook-f me-1"></i>Facebook
</a>
<a href="#" class="btn btn-outline-info btn-sm" onclick="shareToTwitter()">
<i class="fab fa-twitter me-1"></i>Twitter
</a>
<a href="#" class="btn btn-outline-success btn-sm" onclick="copyLink()">
<i class="fas fa-link me-1"></i>Копировать ссылку
</a>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">
<i class="fas fa-newspaper me-2"></i>Похожие статьи
</h5>
</div>
<div class="card-body">
<% if (relatedArticles && relatedArticles.length > 0) { %>
<% relatedArticles.forEach(related => { %>
<div class="mb-3 pb-3 border-bottom">
<div class="mb-2">
<img src="<%= (related.image_url && related.image_url.trim()) ? related.image_url : '/images/placeholder.jpg' %>" alt="<%= related.title %>"
class="img-fluid rounded" style="height: 80px; width: 100%; object-fit: cover;">
</div>
<h6 class="mb-1">
<a href="/articles/<%= related.id %>" class="text-decoration-none">
<%= related.title %>
</a>
</h6>
<% if (related.excerpt) { %>
<p class="text-muted small mb-1"><%= truncateText(related.excerpt, 80) %></p>
<% } %>
<small class="text-muted">
<i class="fas fa-calendar-alt me-1"></i>
<%= formatDateShort(related.created_at) %>
</small>
</div>
<% }); %>
<% } else { %>
<p class="text-muted mb-0">Нет похожих статей</p>
<% } %>
</div>
</div>
<!-- Back to Articles -->
<div class="mt-4">
<a href="/articles" class="btn btn-outline-primary w-100">
<i class="fas fa-arrow-left me-1"></i>Вернуться к статьям
</a>
</div>
</div>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Готовы посетить Корею?</h2>
<p class="lead text-muted mb-4">Ознакомьтесь с нашими турами и найдите идеальное путешествие для себя!</p>
<a href="/routes" class="btn btn-primary btn-lg me-3">
<i class="fas fa-route me-1"></i>Посмотреть туры
</a>
<a href="/contact" class="btn btn-outline-primary btn-lg">
<i class="fas fa-envelope me-1"></i>Свяжитесь с нами
</a>
</div>
</section>
<script>
function shareToFacebook() {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent('<%= article.title %>');
window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank', 'width=600,height=400');
}
function shareToTwitter() {
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent('<%= article.title %>');
window.open(`https://twitter.com/intent/tweet?url=${url}&text=${title}`, '_blank', 'width=600,height=400');
}
function copyLink() {
navigator.clipboard.writeText(window.location.href).then(() => {
alert('Ссылка скопирована в буфер обмена!');
});
}
</script>
<style>
.article-content {
font-size: 1.1rem;
line-height: 1.7;
}
.article-content h1,
.article-content h2,
.article-content h3,
.article-content h4,
.article-content h5,
.article-content h6 {
margin-top: 2rem;
margin-bottom: 1rem;
color: #2c3e50;
}
.article-content p {
margin-bottom: 1.5rem;
}
.article-content img {
max-width: 100%;
height: auto;
margin: 1.5rem 0;
border-radius: 8px;
}
.article-content blockquote {
background-color: #f8f9fa;
border-left: 4px solid #007bff;
padding: 1rem 1.5rem;
margin: 2rem 0;
font-style: italic;
}
.article-content ul,
.article-content ol {
padding-left: 2rem;
margin-bottom: 1.5rem;
}
.article-content li {
margin-bottom: 0.5rem;
}
</style>

80
views/articles/index.ejs Normal file
View File

@@ -0,0 +1,80 @@
<!-- Hero Section -->
<section class="hero-section bg-primary text-white text-center py-5">
<div class="container">
<div class="row">
<div class="col-lg-8 mx-auto">
<h1 class="display-4 fw-bold mb-4">Полезные статьи</h1>
<p class="lead">Советы путешественникам и интересная информация о Корее</p>
</div>
</div>
</div>
</section>
<!-- Articles Grid -->
<section class="py-5">
<div class="container">
<% if (articles.length === 0) { %>
<div class="text-center py-5">
<i class="fas fa-newspaper text-muted" style="font-size: 4rem;"></i>
<h3 class="mt-3 text-muted">Статьи не найдены</h3>
<p class="text-muted">В данный момент нет опубликованных статей.</p>
</div>
<% } else { %>
<div class="row">
<% articles.forEach(article => { %>
<div class="col-lg-4 col-md-6 mb-4">
<div class="card h-100 shadow-sm">
<% if (article.image_url && article.image_url.trim()) { %>
<img src="<%= article.image_url %>" class="card-img-top" alt="<%= article.title %>" style="height: 200px; object-fit: cover;">
<% } else { %>
<img src="/images/placeholder.jpg" class="card-img-top" alt="<%= article.title %>" style="height: 200px; object-fit: cover;">
<% } %>
<div class="card-body d-flex flex-column">
<div class="mb-2">
<span class="badge <%= article.category === 'travel-tips' ? 'bg-success' : article.category === 'food' ? 'bg-warning' : article.category === 'culture' ? 'bg-info' : article.category === 'nature' ? 'bg-success' : article.category === 'history' ? 'bg-secondary' : 'bg-primary' %>">
<%= getCategoryLabel(article.category) %>
</span>
</div>
<h5 class="card-title"><%= article.title %></h5>
<p class="card-text text-muted flex-grow-1"><%= article.excerpt || truncateText(article.content, 120) %></p>
<div class="mt-auto">
<div class="d-flex justify-content-between align-items-center mb-3">
<small class="text-muted">
<i class="fas fa-eye me-1"></i><%= article.views || 0 %> просмотров
</small>
<small class="text-muted">
<i class="fas fa-calendar me-1"></i><%= formatDate(article.created_at) %>
</small>
</div>
<a href="/articles/<%= article.id %>" class="btn btn-primary w-100">
<i class="fas fa-book-open me-1"></i>Читать
</a>
</div>
</div>
</div>
</div>
<% }); %>
</div>
<% } %>
</div>
</section>
<!-- Newsletter Signup -->
<section class="bg-light py-5">
<div class="container text-center">
<h2 class="mb-4">Не пропустите новые статьи!</h2>
<p class="lead text-muted mb-4">Подпишитесь на нашу рассылку и получайте самые интересные материалы о Корее</p>
<div class="row justify-content-center">
<div class="col-md-6">
<form class="d-flex">
<input type="email" class="form-control me-2" placeholder="Ваш email адрес">
<button class="btn btn-primary" type="submit">Подписаться</button>
</form>
</div>
</div>
</div>
</section>