start deplou
This commit is contained in:
39
.env.example
39
.env.example
@@ -1,29 +1,28 @@
|
|||||||
DATABASE_URL=postgresql+asyncpg://user:password@host:5432/news
|
# ============= БАЗА ДАННЫХ =============
|
||||||
REDIS_URL=redis://host:6379/0
|
DATABASE_URL=postgresql+asyncpg://news_admin:news_secure_2026@192.168.1.11:5432/news
|
||||||
JWT_SECRET=your-very-secret-key-change-me
|
|
||||||
|
|
||||||
|
# ============= MINIO / ХРАНИЛИЩЕ =============
|
||||||
|
MINIO_ENDPOINT=minio:9000
|
||||||
MINIO_ACCESS_KEY=minioadmin
|
MINIO_ACCESS_KEY=minioadmin
|
||||||
MINIO_SECRET_KEY=minioadmin
|
MINIO_SECRET_KEY=minioadmin
|
||||||
MINIO_BUCKET=news-media
|
MINIO_BUCKET=news-media
|
||||||
MINIO_PUBLIC_URL=http://localhost:9000
|
MINIO_PUBLIC_URL=http://localhost:9000
|
||||||
|
|
||||||
|
# ============= JWT / БЕЗОПАСНОСТЬ =============
|
||||||
|
JWT_SECRET=your-super-secret-key-change-in-production
|
||||||
|
JWT_ALGORITHM=HS256
|
||||||
|
|
||||||
|
# ============= ВКОНТАКТЕ API =============
|
||||||
|
VK_API_VERSION=5.131
|
||||||
|
VK_SERVICE_TOKEN=your_vk_service_token_here
|
||||||
|
|
||||||
|
# ============= АДМИНИСТРАТОР =============
|
||||||
|
ADMIN_USERNAME=admin
|
||||||
|
ADMIN_PASSWORD=your-strong-password
|
||||||
|
|
||||||
|
# ============= ДОКУМЕНТАЦИЯ API =============
|
||||||
DOCS_USERNAME=admin
|
DOCS_USERNAME=admin
|
||||||
DOCS_PASSWORD=admin
|
DOCS_PASSWORD=admin
|
||||||
|
|
||||||
# CORS: разрешённые домены через запятую
|
# ============= ЛОГИРОВАНИЕ =============
|
||||||
ALLOWED_ORIGINS=http://localhost,http://localhost:80
|
LOG_LEVEL=INFO
|
||||||
|
|
||||||
ADMIN_USERNAME=admin
|
|
||||||
ADMIN_PASSWORD=changeme
|
|
||||||
# VK импорт (необязательно)
|
|
||||||
# Сервисный токен приложения: vk.com/apps → Настройки → Сервисный ключ доступа
|
|
||||||
VK_ACCESS_TOKEN=
|
|
||||||
# ID групп через запятую (без минуса): 12345,67890
|
|
||||||
VK_GROUP_IDS=
|
|
||||||
# Статус импортированных постов: published или draft
|
|
||||||
VK_IMPORT_STATUS=published
|
|
||||||
# Сколько последних постов проверять за раз
|
|
||||||
VK_POSTS_PER_RUN=20
|
|
||||||
# Время запуска импорта (UTC)
|
|
||||||
VK_IMPORT_HOUR=6
|
|
||||||
VK_IMPORT_MINUTE=0
|
|
||||||
|
|||||||
170
DEPLOY.md
Normal file
170
DEPLOY.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Развёртывание news_flet
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
- Docker & Docker Compose (20.10+)
|
||||||
|
- Git
|
||||||
|
- PostgreSQL 13+ (внешний сервер или локальный контейнер)
|
||||||
|
- MinIO S3 (локальный в Docker или облачный)
|
||||||
|
|
||||||
|
## Быстрый старт
|
||||||
|
|
||||||
|
### 1. Клонирование и подготовка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repo-url> news_flet
|
||||||
|
cd news_flet
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Редактирование .env
|
||||||
|
|
||||||
|
Заполнить критические переменные:
|
||||||
|
```bash
|
||||||
|
DATABASE_URL=postgresql+asyncpg://user:password@host:5432/news
|
||||||
|
JWT_SECRET=<генерируем на продакшене>
|
||||||
|
ADMIN_PASSWORD=<сильный пароль>
|
||||||
|
VK_SERVICE_TOKEN=<token из ВКонтакте>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Запуск контейнеров
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Сервис поднимается на:
|
||||||
|
- **Frontend (React)**: http://localhost:80
|
||||||
|
- **API**: http://localhost/api/ (за nginx)
|
||||||
|
- **MinIO**: http://localhost:9000 (если локально)
|
||||||
|
- **API Docs**: http://localhost/api/docs
|
||||||
|
|
||||||
|
### 4. Первый запуск
|
||||||
|
|
||||||
|
При старте API:
|
||||||
|
- Автоматически запускает Alembic миграции
|
||||||
|
- Создаёт таблицы в БД (если нет)
|
||||||
|
- Создаёт default админ юзера (если нет)
|
||||||
|
|
||||||
|
Логин админ-панели:
|
||||||
|
```
|
||||||
|
URL: http://localhost/admin/login
|
||||||
|
Username: admin
|
||||||
|
Password: <из .env ADMIN_PASSWORD>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура docker-compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
api: # FastAPI + Alembic + VK-парсер
|
||||||
|
web: # React SPA + nginx
|
||||||
|
minio: # S3-совместимое хранилище (опционально)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Переменные окружения
|
||||||
|
|
||||||
|
| Переменная | Обязательна | Описание |
|
||||||
|
|-----------|-----------|---------|
|
||||||
|
| `DATABASE_URL` | ✅ | PostgreSQL + asyncpg подключение |
|
||||||
|
| `JWT_SECRET` | ✅ | Ключ для подписи JWT токенов |
|
||||||
|
| `ADMIN_PASSWORD` | ✅ | Пароль дефолт админа |
|
||||||
|
| `VK_SERVICE_TOKEN` | ✅ | Токен сервиса ВКонтакте |
|
||||||
|
| `MINIO_ENDPOINT` | — | MinIO хост (default: `minio:9000`) |
|
||||||
|
| `MINIO_PUBLIC_URL` | — | Публичный URL для доступа к файлам |
|
||||||
|
| `LOG_LEVEL` | — | DEBUG/INFO/WARNING (default: INFO) |
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
### Frontend (`web-react/`)
|
||||||
|
- React 18 + React Router
|
||||||
|
- Admin панель: управление статьями, медиа, источниками ВК
|
||||||
|
- Публичный фид с поиском по категориям и тегам
|
||||||
|
- TipTap редактор с поддержкой видео, картинок, таблиц
|
||||||
|
|
||||||
|
### Backend (`api/`)
|
||||||
|
- FastAPI + SQLAlchemy async
|
||||||
|
- VK-парсер: загрузка статей из групп/публичных страниц
|
||||||
|
- yt-dlp: скачивание видео из ВК на MinIO
|
||||||
|
- Slug-based медиа: организованное хранилище на S3
|
||||||
|
- JWT auth + role-based access control
|
||||||
|
|
||||||
|
### Storage (`minio/`)
|
||||||
|
- S3-совместимое хранилище
|
||||||
|
- Структура: `articles/{slug}-{uuid}/images/`
|
||||||
|
- Публичный доступ к медиа через HTTP
|
||||||
|
|
||||||
|
## Миграции БД
|
||||||
|
|
||||||
|
При запуске API проверяет и применяет миграции через Alembic:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ручно (внутри контейнера api):
|
||||||
|
docker compose exec api alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
Новая миграция при изменении схемы:
|
||||||
|
```bash
|
||||||
|
docker compose exec api alembic revision --autogenerate -m "description"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Мониторинг
|
||||||
|
|
||||||
|
### Логи
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs -f api
|
||||||
|
docker compose logs -f web
|
||||||
|
```
|
||||||
|
|
||||||
|
### API документация
|
||||||
|
|
||||||
|
Доступна по адресу `/api/docs` (требует auth)
|
||||||
|
|
||||||
|
## Production рекомендации
|
||||||
|
|
||||||
|
- 🔒 Установить SSL сертификат (Let's Encrypt)
|
||||||
|
- 🔐 Сменить все default пароли и токены
|
||||||
|
- 💾 Настроить резервное копирование БД
|
||||||
|
- 📊 Включить мониторинг (Prometheus/Grafana)
|
||||||
|
- 🛡️ Настроить firewall и rate limiting
|
||||||
|
- 🌍 Использовать CDN для медиа-файлов
|
||||||
|
|
||||||
|
## Решение проблем
|
||||||
|
|
||||||
|
### БД не подключается
|
||||||
|
```bash
|
||||||
|
# Проверить подключение:
|
||||||
|
docker compose exec api python -c "import asyncio; from bd.database import engine; asyncio.run(engine.connect())"
|
||||||
|
```
|
||||||
|
|
||||||
|
### MinIO не доступен
|
||||||
|
```bash
|
||||||
|
# Проверить health:
|
||||||
|
curl http://localhost:9000/minio/health/live
|
||||||
|
```
|
||||||
|
|
||||||
|
### Миграции не применились
|
||||||
|
```bash
|
||||||
|
# Ручной запуск:
|
||||||
|
docker compose exec api alembic upgrade head
|
||||||
|
```
|
||||||
|
|
||||||
|
## Развёртывание на сервере
|
||||||
|
|
||||||
|
1. SSH на сервер
|
||||||
|
2. `git clone ...` → `cd news_flet`
|
||||||
|
3. Скопировать и отредактировать `.env`
|
||||||
|
4. `docker compose up -d`
|
||||||
|
5. Проверить: `curl http://localhost/`
|
||||||
|
|
||||||
|
Если используется reverse proxy (nginx):
|
||||||
|
```nginx
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:80;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -5,6 +5,7 @@ import { useTheme } from './hooks/useTheme'
|
|||||||
|
|
||||||
import NewsFeed from './pages/NewsFeed'
|
import NewsFeed from './pages/NewsFeed'
|
||||||
import NewsDetail from './pages/NewsDetail'
|
import NewsDetail from './pages/NewsDetail'
|
||||||
|
import NotFound from './pages/NotFound'
|
||||||
import Login from './pages/admin/Login'
|
import Login from './pages/admin/Login'
|
||||||
import Dashboard from './pages/admin/Dashboard'
|
import Dashboard from './pages/admin/Dashboard'
|
||||||
import ArticlesList from './pages/admin/ArticlesList'
|
import ArticlesList from './pages/admin/ArticlesList'
|
||||||
@@ -36,7 +37,7 @@ export default function App() {
|
|||||||
<Route path="/admin/media" element={<PrivateRoute><MediaLibrary /></PrivateRoute>} />
|
<Route path="/admin/media" element={<PrivateRoute><MediaLibrary /></PrivateRoute>} />
|
||||||
<Route path="/admin/vk" element={<PrivateRoute><VkSources /></PrivateRoute>} />
|
<Route path="/admin/vk" element={<PrivateRoute><VkSources /></PrivateRoute>} />
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
45
web-react/src/pages/NotFound.jsx
Normal file
45
web-react/src/pages/NotFound.jsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: '100vh',
|
||||||
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||||
|
color: 'white',
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '2rem',
|
||||||
|
}}>
|
||||||
|
<h1 style={{ fontSize: '5rem', margin: 0, fontWeight: 'bold' }}>404</h1>
|
||||||
|
<h2 style={{ fontSize: '1.5rem', marginTop: '0.5rem', fontWeight: '600' }}>Страница не найдена</h2>
|
||||||
|
<p style={{ fontSize: '1rem', margin: '1rem 0', opacity: 0.9 }}>
|
||||||
|
Кажется, такой страницы не существует
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
style={{
|
||||||
|
marginTop: '2rem',
|
||||||
|
padding: '0.75rem 2rem',
|
||||||
|
background: 'white',
|
||||||
|
color: '#667eea',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
fontSize: '1rem',
|
||||||
|
fontWeight: '600',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => e.target.style.transform = 'scale(1.05)'}
|
||||||
|
onMouseLeave={e => e.target.style.transform = 'scale(1)'}
|
||||||
|
>
|
||||||
|
← Вернуться на главную
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user