An open API service indexing awesome lists of open source software.

https://github.com/07rinat07/myblog

Website development using the Yii framework (Yii2 --> Yes It Is -2)
https://github.com/07rinat07/myblog

gii-crud mysql php8 phpmyadmin-database yii2-framework

Last synced: 3 months ago
JSON representation

Website development using the Yii framework (Yii2 --> Yes It Is -2)

Awesome Lists containing this project

README

          

# MyBlog (Yii2)
Проект после рефакторинга и обновления зависимостей. Находится в активной разработке.
## Участие в разработке
Приветствуются пулл-реквесты (PR) и сообщения об ошибках. Пожалуйста, добавляйте тесты для нового функционала.

Блог-приложение на Yii2 с публичной частью и административной панелью.

## Что уже реализовано

- Публичные страницы: список статей, просмотр статьи, категории, контактная форма.
- Переключение языка на всех страницах: English / Русский (публичная часть + админка).
- Регистрация/авторизация пользователей (`/auth/signup`, `/auth/login`).
- Регистрация с подтверждением email:
- после регистрации отправляется письмо с ссылкой подтверждения
- после перехода по ссылке аккаунт активируется, пользователь автоматически логинится и попадает на главную
- Личный кабинет пользователя (`/cabinet/profile`) с редактированием профиля (имя, email, пароль, аватар).
- Комментарии к статьям и лайки/снятие лайка (для авторизованных пользователей).
- Загрузка изображений в статьи (админ и пользовательский кабинет).
- Встроенный редактор текста в форме статьи (форматирование, шрифты, цвета, стили).
- Админ-панель (`/admin`) с разделами:
- Dashboard
- Articles
- Categories
- Tags
- Comments
- Users
- Feedback (сообщения из формы `/contact`)
- Усиленная безопасность:
- проверка роли администратора для админ-модуля
- ограничение доступа к чужим записям для обычных пользователей
- хеширование паролей
- CSRF/cookie-настройки
- экранирование вывода и очистка HTML-контента
- Обновлённый UI (публичная часть, админка и кабинет) и адаптивная вёрстка для mobile/tablet/desktop.
- Обновлённый стек зависимостей (Yii 2.0.54 и актуальные пакеты ветки 2.x).

## Требования

- PHP `>= 8.1`
- Composer 2
- MySQL/MariaDB
- Docker Engine + Docker Compose v2 (или Docker Desktop)
- Рекомендуемые расширения PHP:
- `pdo_mysql`
- `gd` (или `imagick`) для captcha

### Проверка `pdo_mysql` (локально и в Docker)

Проверить, что расширение загружено:

```bash
php -m | grep -E "PDO|pdo_mysql|mysqli"
```

Если локально (Linux/WSL) `pdo_mysql` нет:

```bash
sudo apt-get update
sudo apt-get install -y php8.1-mysql
```

Для OSPanel (Windows): в `C:\OSPanel\modules\PHP-8.1\php.ini` должны быть активны строки:

- `extension=mysqli`
- `extension=pdo_mysql`

После изменений перезапустите OSPanel (или PHP-модуль).

В Docker `pdo_mysql` уже устанавливается в `docker/php/Dockerfile` через:

- `docker-php-ext-install ... pdo_mysql ...`

Проверка в контейнере:

```bash
docker compose run --rm app php -m | grep -E "PDO|pdo_mysql"
```

## Установка

1. Установить зависимости:

```bash
composer install
```

2. Настроить подключение к БД (через переменные окружения или `config/db.php`):

- `DB_DSN` (если не задан, собирается из `DB_HOST`/`DB_PORT`/`DB_NAME`, по умолчанию `127.0.0.1:3306/myblog`)
- `DB_USERNAME` (по умолчанию: `root`)
- `DB_PASSWORD` (по умолчанию: `newpassword`)
- `COOKIE_VALIDATION_KEY` (рекомендуется задать свой)

Если БД запущена в Docker (`docker-compose`), для локального запуска приложения удобно использовать:

```bash
export DB_HOST=127.0.0.1
export DB_PORT=3307
export DB_USERNAME=myblog
export DB_PASSWORD=myblog_password
```

3. Применить миграции:

```bash
php yii migrate
```

4. Запустить приложение (вариант для локальной разработки):

```bash
php yii serve --port=8080
```

Или настроить веб-сервер так, чтобы document root указывал на папку `web/`.

## Запуск в Docker (Windows/Linux/macOS)

1. Подготовить переменные окружения:

```bash
# Linux/macOS
cp .env.example .env

# Windows PowerShell
Copy-Item .env.example .env
```

Рекомендуется сразу заменить в `.env` секреты:

- `COOKIE_VALIDATION_KEY`
- `MYSQL_ROOT_PASSWORD`
- `WEBHOOK_INCOMING_GENERIC_SECRET`

2. Запустить контейнеры:

```bash
docker compose up -d --build
```

3. Открыть сайт:

- `http://localhost:8080` (или ваш `APP_PORT` из `.env`)

4. Проверить логи:

```bash
docker compose logs -f app
```

Что настроено для стабильного запуска:

- `restart: unless-stopped` для `app`, `db`, `selenium` (контейнеры автоматически поднимаются после перезагрузки сервера/демона Docker)
- ожидание готовности БД перед стартом приложения
- автоматическая установка зависимостей (если нет `vendor/autoload.php`)
- автоматический запуск миграций при старте (`AUTO_MIGRATE=1`, можно выключить в `.env`)
- автоматический запуск миграций тестовой БД (`AUTO_MIGRATE_TEST_DB=1`)
- автоматическая загрузка фикстур в основной и тестовой БД при пустой таблице `user` (`AUTO_LOAD_FIXTURES=1`, список через `FIXTURES_LIST`)
- постоянное хранилище БД в docker volume `db-data`
- Selenium WebDriver для `acceptance` тестов (порт `4444`)

Команды для фикстур в Docker:

```bash
# загрузить фикстуры вручную
docker compose exec app php yii fixture/load "Category,Tag,User,Article,ArticleTag" --interactive=0

# полностью пересобрать проект с "чистой" БД (и авто-фикстурами)
docker compose down -v
docker compose up -d --build
```

Важно для автозапуска после reboot:

- Linux: включите автозапуск Docker демона (`sudo systemctl enable --now docker`)
- Windows/macOS: включите опцию автозапуска Docker Desktop при входе в систему

## Админ-доступ

Админка доступна по адресу:

- `/admin`

Пользователь должен иметь `isAdmin = 1` в таблице `user`.

Личный кабинет пользователя:

- `/cabinet/profile`

## MVP API (`/api/v1`)

В проект добавлен базовый JSON API для интеграций.

OpenAPI спецификация:

- `web/openapi/swagger.json`
- после запуска проекта доступна по URL: `/openapi/swagger.json`
- Swagger UI доступен по URL: `/docs/`

Аутентификация:

- Bearer token
- токен выдаётся через `POST /api/v1/auth/login`
- токены имеют срок жизни (`expiresAt`)
- для login включён rate limit (защита от brute-force)
- поддержаны отзыв и ротация токена

Эндпоинты:

- `GET /api/v1/health` — статус API
- `POST /api/v1/auth/login` — логин по `email`/`password`, выдаёт `accessToken`
- `POST /api/v1/auth/rotate` — ротация текущего токена (нужен Bearer token)
- `POST /api/v1/auth/logout` — отзыв текущего токена (нужен Bearer token)
- `POST /api/v1/auth/logout-all` — отзыв всех активных токенов пользователя
- `GET /api/v1/me` — профиль текущего пользователя (нужен Bearer token)
- `GET /api/v1/categories` — список категорий + количество опубликованных статей
- `GET /api/v1/articles` — список опубликованных статей (параметры: `page`, `per_page`, `category_id`, `q`)
- `GET /api/v1/articles/{id}` — одна опубликованная статья
- `GET /api/v1/articles/{id}/comments` — комментарии к статье
- `POST /api/v1/articles/{id}/comments` — добавить комментарий (нужен Bearer token, параметр `text`)
- `POST /api/v1/articles/{id}/like` — лайк/анлайк (нужен Bearer token)
- `POST /api/v1/webhooks/incoming/{source}` — входящий webhook с HMAC-подписью и idempotency key

Пример получения токена:

```bash
curl -X POST "http://localhost:8080/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"user12345"}'
```

Пример защищённого запроса:

```bash
curl "http://localhost:8080/api/v1/me" \
-H "Authorization: Bearer "
```

Пример ротации токена:

```bash
curl -X POST "http://localhost:8080/api/v1/auth/rotate" \
-H "Authorization: Bearer "
```

Пример logout (отзыва текущего токена):

```bash
curl -X POST "http://localhost:8080/api/v1/auth/logout" \
-H "Authorization: Bearer "
```

## Webhooks (incoming + outgoing)

### Входящие webhook

Эндпоинт:

- `POST /api/v1/webhooks/incoming/generic`

Обязательные заголовки:

- `Idempotency-Key: `
- `X-Webhook-Signature: sha256=`

Опционально:

- `X-Webhook-Event: `

Секрет для source `generic` задаётся через `.env`:

- `WEBHOOK_INCOMING_GENERIC_SECRET=...`

В `prod` без секрета endpoint вернёт `503` (безопасный fail-close).

Подпись считается как `HMAC-SHA256` по raw body запроса.
Все входящие доставки логируются в таблицу `webhook_incoming_delivery`.

Пример:

```bash
BODY='{"event":"ping"}'
SIG=$(php -r "echo hash_hmac('sha256', '$BODY', getenv('WEBHOOK_INCOMING_GENERIC_SECRET') ?: 'change-me-incoming-secret');")

curl -X POST "http://localhost:8080/api/v1/webhooks/incoming/generic" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: incoming-1" \
-H "X-Webhook-Event: ping" \
-H "X-Webhook-Signature: sha256=$SIG" \
-d "$BODY"
```

### Исходящие webhook (outbox + worker)

События, которые ставятся в очередь автоматически:

- `article.published` (когда статья становится published)
- `comment.created`
- `contact.created`

Таблицы:

- `webhook_endpoint` — получатели webhook
- `webhook_delivery` — очередь и история попыток доставки

Команды:

```bash
# добавить/обновить endpoint
php yii webhook/create-endpoint "crm" "https://example.com/webhooks" "super-secret" "article.published,comment.created,contact.created" 10

# показать endpoint'ы
php yii webhook/list-endpoints

# обработать очередь (воркер)
php yii webhook/dispatch 100
```

Для фоновой обработки добавьте cron, например каждую минуту:

```bash
* * * * * /usr/bin/php /path/to/project/yii webhook/dispatch 100 >> /var/log/myblog-webhooks.log 2>&1
```

## Загрузка больших фото

В проекте уже настроены лимиты для больших изображений:

- application limit: `128 MB` (`config/params.php -> upload.maxImageSize`)
- Apache (`web/.htaccess`) и PHP-FPM/CGI (`web/.user.ini`)
- Docker (`docker/php/uploads.ini`)
- Vagrant/Nginx: `client_max_body_size 128M` + правка `php.ini` в provision-скрипте

Если меняете лимит, обновляйте его одновременно в:

- `config/params.php`
- `web/.user.ini`
- `web/.htaccess` (для mod_php)
- `docker/php/uploads.ini` (если используете Docker)
- `vagrant/nginx/app.conf` и `vagrant/provision/once-as-root.sh` (если используете Vagrant)

## Важные миграции

- `m260203_000001_harden_blog_schema`:
- добавляет `auth_key` и `access_token` в `user`
- нормализует/усиливает данные и ограничения
- добавляет индексы и внешние ключи
- приводит старые пароли к безопасному хешу (если это не хеш)
- `m260204_000006_create_webhook_tables`:
- создаёт таблицы для входящих и исходящих webhook
- добавляет индексы и FK для outbox-очереди
- `m260204_000007_optimize_query_indexes` и `m260204_000008_add_article_category_status_index`:
- добавляют индексы под API/воркер запросы
- ускоряют выборки статей, комментариев и webhook-delivery

## Тесты

Основная команда для прогона всех уровней тестов (acceptance + functional + unit):

```bash
docker compose exec app vendor/bin/codecept run
```

Если контейнеры ещё не запущены:

```bash
docker compose up -d
```

Команды по отдельным уровням:

```bash
docker compose exec app vendor/bin/codecept run acceptance
docker compose exec app vendor/bin/codecept run functional
docker compose exec app vendor/bin/codecept run unit
```

Локальный запуск (без Docker):

```bash
vendor/bin/codecept run
```

Если в окружении нет `pdo_mysql` и/или `gd`/`imagick`, часть тестов будет пропущена (skipped).
В Docker-конфигурации используется отдельная тестовая БД `myblog_test`.
`acceptance` suite работает через `tests/acceptance.suite.yml` и контейнер `selenium`.

### Фикстуры для проверки (admin + user)

В проект добавлены фикстуры:

- `admin@example.com / admin12345` (админ)
- `user@example.com / user12345` (обычный пользователь)
- демо-категории, теги и статьи для корректного рендера главной страницы

Загрузка фикстур:

```bash
php yii fixture/load "Category,Tag,User,Article,ArticleTag" --interactive=0
```

## Полезные переменные окружения

- `APP_PORT` — внешний порт приложения в Docker (по умолчанию `8080`)
- `YII_ENV` (`dev`/`prod`, по умолчанию `prod`)
- `YII_DEBUG` (`true`/`false`, по умолчанию `false`)
- `DB_HOST`, `DB_PORT`, `DB_NAME` — параметры основной БД (если `DB_DSN` не задан)
- `DB_TEST_DSN` или `DB_TEST_HOST`/`DB_TEST_PORT`/`DB_TEST_NAME` — тестовая БД
- `MYSQL_DATABASE`, `MYSQL_TEST_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_ROOT_PASSWORD` — переменные MySQL в Docker
- `DB_PORT` в `.env` также используется для проброса порта MySQL контейнера наружу (`:3306`, по умолчанию `3307`)
- `SELENIUM_PORT` (по умолчанию `4444`) — порт Selenium WebDriver
- `AUTO_MIGRATE` (`1`/`0`) — авто-применение миграций в Docker
- `AUTO_MIGRATE_TEST_DB` (`1`/`0`) — авто-применение миграций в тестовой БД
- `AUTO_LOAD_FIXTURES` (`1`/`0`) — авто-загрузка фикстур при пустой таблице `user`
- `FIXTURES_LIST` (по умолчанию `Category,Tag,User,Article,ArticleTag`) — какие фикстуры грузить автоматически
- `WAIT_FOR_DB`, `DB_WAIT_TIMEOUT` — ожидание готовности БД в Docker entrypoint
- `WEBHOOK_INCOMING_GENERIC_SECRET`, `WEBHOOK_OUTGOING_MAX_ATTEMPTS` — настройки webhook
- `API_ALLOWED_ORIGINS` — CORS origins для API (список через запятую)
- `API_ACCESS_TOKEN_TTL` — TTL access token в секундах (по умолчанию `43200`)
- `API_REVOKE_EXISTING_TOKENS_ON_LOGIN` — при логине отзывать прошлые активные токены (`1`/`0`)
- `API_LEGACY_ACCESS_TOKEN_FALLBACK` — временный fallback на старый `user.access_token` (`1`/`0`, по умолчанию `0`)

## Структура проекта (основное)

- `controllers/` — контроллеры публичной части
- `models/` — модели и search-модели
- `modules/admin/` — админ-модуль
- `views/` — шаблоны
- `migrations/` — миграции БД
- `config/` — конфиги приложения
- `tests/` — unit/functional/acceptance тесты