# Деплой Libro.az на обычный хостинг (shared hosting)

Проект работает на обычном shared-хостинге (PHP 8.2+ / MySQL 8.0).
VPS не обязателен — Docker нужен только для локальной разработки.

---

## 1. Требования хостинга

- PHP **8.2+** (рекомендуется 8.3) с расширениями: `mbstring`, `openssl`, `pdo_mysql`,
  `tokenizer`, `xml`, `ctype`, `json`, `bcmath`, `fileinfo`, `gd` (для обработки изображений).
- MySQL **8.0+** (или MariaDB 10.6+).
- Возможность указать **DocumentRoot → `public/`** (или домен должен смотреть в папку `public`).
- SSH не обязателен: достаточно загрузить файлы и настроить БД через панель.

---

## 2. Сборка готового пакета (локально, один раз)

```bash
# 1) PHP-зависимости БЕЗ dev-пакетов
composer install --no-dev --optimize-autoloader --no-interaction

# 2) Собрать CSS (Tailwind)
npm install
npm run build
```

Результат сборки уже лежит в `public/css/app.css` и `public/css/home.css` —
на хостинге ничего собирать не нужно.

---

## 3. Что загружать на хостинг

Загрузите ВЕСЬ проект (кроме служебного):

```
app/  bootstrap/  config/  database/  lang/  public/  resources/  routes/
artisan  composer.json  package.json  ...
vendor/          # ← обязательно, т.к. на shared-хостинге часто нет composer
```

**Не загружать / исключить** (уже в `.gitignore`):
- `.env` (создаётся на хостинге)
- `node_modules/`, `storage/logs/*`, `.DS_Store`, `docker-compose.yml`, `Dockerfile`

---

## 4. Настройка на хостинге

### 4.1. Создать `.env`

```bash
cp .env.production.example .env
php artisan key:generate
```

Заполнить:
- `APP_URL=https://ваш-домен`
- `DB_*` — доступы к созданной БД
- `MAIL_*` — SMTP (по желанию)

### 4.2. БД и права

```bash
php artisan migrate --force
```

Убедиться, что папки доступны для записи:
```bash
chmod -R 775 storage bootstrap/cache
chmod -R 775 public/photo
```

### 4.3. Кэшировать конфигурацию

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

### 4.4. Первый админ

Сиды с dev-доступами на проде **заблокированы** (`DatabaseSeeder` завершается
в production). Создайте админа вручную через `tinker`:

```bash
php artisan tinker
```

```php
\App\Models\User::create([
  'name'     => 'Admin',
  'email'    => 'ВАШ@email.com',
  'phone'    => '+994000000000',
  'password' => \Illuminate\Support\Facades\Hash::make('СЛОЖНЫЙ-ПАРОЛЬ'),
  'role'     => 'admin',
  'plan_id'  => null,
  'status'   => 'active',
]);
```

---

## 5. Чек-лист безопасности перед запуском

- [x] `.env`: `APP_ENV=production`, `APP_DEBUG=false`, `APP_KEY` сгенерирован.
- [x] Пароль админа заменён (не `admin123`), тестовый `test@example.com` удалён.
- [x] `.env` не загружен в git / не доступен из web (DocumentRoot = `public/`).
- [x] Выполнены `config:cache` и `view:cache` (`route:cache` пропущен — в `web.php` есть замыкания, команда падает).
- [x] `storage/` и `vendor/` вне `public/` (стандартная структура Laravel).
- [ ] Настроен HTTPS (Let's Encrypt / панель хостинга).
- [ ] Проверено: `https://ваш-домен/storage` и `/.env` недоступны (403/404).

---

## 6. Если хостинг не даёт DocumentRoot в `public/`

Вариант A (проще): загрузить в корень, а корень сайта указать на `public`.

Вариант B: положить `index.php` из `public/` в корень и поправить пути
`__DIR__.'/../'` на `__DIR__.'/'` — но тогда БД и `.env` окажутся в web-корне,
что **небезопасно**. Лучше использовать Вариант A.

---

## 7. Рабочий цикл: правки локально → загрузка на хостинг

### 7.1. Главное правило — база никогда не очищается

- `php artisan migrate` (на проде — `migrate --force`) применяет **только новые**
  миграции. Существующие таблицы и данные не трогаются.
- `php artisan migrate:fresh` / `make fresh` **ДРОПАЕТ всю базу** и пересоздаёт её —
  только для локальной разработки в Docker. **На проде НИКОГДА.**
- Новая фича, связанная с БД → новый файл миграции
  (`create_*`, `add_*_to_*`, `backfill_*`, `seed_*`). Справочные данные (тарифы
  и т.п.) — идемпотентной seed-миграцией, как
  `database/migrations/2026_09_18_000001_seed_default_plans.php`.

### 7.2. Что НЕ затирать при загрузке на хостинг

Это «состояние» живого сайта — при загрузке кода оставляй как есть:

| Файл / папка | Зачем |
|---|---|
| `.env` | доступы к БД, `APP_KEY`, SMTP |
| `storage/` | сессии, логи, кэш |
| `public/photo/` | загрузки пользователей (аватары, товары, шаблоны) |
| `public/.user.ini` | лимиты PHP (`post_max_size` и т.д.) |
| `vendor/` | можно не трогать, если не менялись PHP-зависимости |

### 7.3. Локально (перед загрузкой)

```bash
# правки кода / миграций / блейдов ...

# если добавил новые PHP-зависимости:
composer install

# собрать CSS/JS:
npm run build
```

### 7.4. Загрузка на хостинг

Загрузи только изменённые папки **кода** (не трогая файлы из 7.2):

```
app/  resources/  routes/  database/migrations/  config/  public/css/
public/js/ (если есть)  + новые ассеты
```

Затем на сервере:

```bash
cd ~/public_html/libro.az
/opt/cpanel/ea-php82/root/usr/bin/php artisan migrate --force
```

`migrate --force` применит только новые миграции (новые таблицы/колонки/seed),
данные пользователей останутся нетронутыми.

### 7.5. Про кэш (важно)

- **Не используй `route:cache`** — в `routes/web.php` есть замыкания (routes-замыкания
  не сериализуются), команда падает. Роуты и так читаются из файла, это нормально.
- `config:cache` / `view:cache` — опционально. Если менял `.env`, после этого
  выполни `config:clear`.

### 7.6. Удобнее — через rsync (если есть SSH)

Вместо ручной загрузки через файловый менеджер:

```bash
# локально, из корня проекта (замени USER/HOST на свои):
rsync -avz --delete \
  --exclude '.env' \
  --exclude 'storage/' \
  --exclude 'public/photo/' \
  --exclude 'public/.user.ini' \
  --exclude 'vendor/' \
  --exclude 'node_modules/' \
  --exclude '.git/' \
  ./ user@HOST:~/public_html/libro.az/
```

Потом один раз на сервере: `php artisan migrate --force`.

### 7.7. Cron: авто-возврат тарифов + очередь писем

`schedule:run` теперь запускает две вещи (см. `bootstrap/app.php`):

1. `plans:reset-expired` — раз в сутки сбрасывает пользователей с истёкшим
   `plan_expires_at` обратно на FREE (и очищает дату окончания).
2. `queue:work --stop-when-empty` — раз в минуту обрабатывает очередь, из которой
   асинхронно уходят письма заявок (форма не «висит» во время SMTP-отправки).

> ВАЖНО: тариф FREE подставляется «на лету» (`User::activePlan()` / `PlanService`)
> даже без cron — cron нужен только чтобы список в БД/админке был аккуратным.
> А вот **письма заявок** теперь уходят через очередь — поэтому cron здесь **обязателен**,
> иначе письма будут копиться в таблице `jobs` и не уходить.

В cPanel → Cron Jobs добавь запись (запуск планировщика Laravel раз в минуту):

```
* * * * * cd ~/public_html/libro.az && /opt/cpanel/ea-php82/root/usr/bin/php artisan schedule:run >> /dev/null 2>&1
```

(Если путь/версия PHP другие — подставь свой.)

---

## 8. Разделение доменов: `libro.az` + `app.libro.az`

Один проект Laravel обслуживает **два домена**:

| Домен | Что там |
|---|---|
| `libro.az` | лендинг `/`, блог `/blog`, `/blog/{slug}`, статичные `/custom-page`, публичные bio-страницы `/{slug}`, QR-меню `/menu/{slug}` |
| `app.libro.az` | вход/регистрация, дашборд `/profile/**`, редактор, админка `/idareetme`. **Гость на любом пути `app.*` → `/login`** |

### 8.1. Настройка в `.env`

```dotenv
APP_URL=https://libro.az
APP_PUBLIC_DOMAIN=libro.az
APP_APP_DOMAIN=app.libro.az
APP_APP_URL=https://app.libro.az

# Общая сессия на оба поддомена (иначе вход «потеряется» при переходе между доменами)
SESSION_DOMAIN=.libro.az
```

После изменения `.env` выполни `php artisan config:clear` (или `config:cache`).

> Локально (Docker) эти переменные **пустые** — маршруты регистрируются без
> ограничения по домену, всё работает на `localhost` как раньше.

### 8.2. cPanel: два домена → одна установка

У тебя уже созданы:
- `libro.az` → docroot `/public_html/libro.az/public`
- `app.libro.az` → docroot `/public_html/app.libro.az`

Нужно, чтобы **оба домена смотрели в один и тот же `public/`** установки. Проще
всего — symlink: удалить папку `/public_html/app.libro.az` и создать ссылку на
`public` основного проекта:

```bash
cd ~/public_html
rm -rf app.libro.az
ln -s libro.az/public app.libro.az
```

Либо в панели хостинга у субдомена `app.libro.az` указать DocumentRoot
`/public_html/libro.az/public` (тот же `public/`).

После этого оба домена обслуживаются одним `index.php`, а маршруты разводятся
по `APP_PUBLIC_DOMAIN` / `APP_APP_DOMAIN`.

### 8.3. Google OAuth

В Google Cloud Console для OAuth-клиента укажи **Authorized redirect URI**:

```
https://app.libro.az/auth/google/callback
```

В админке (`/idareetme/settings`) поле `google_redirect_uri` тоже поставь
`https://app.libro.az/auth/google/callback`. Если поле пустое, подставится
`APP_APP_URL . /auth/google/callback` (т.е. `https://app.libro.az/auth/google/callback`).

### 8.4. Проверка после деплоя

- [ ] `libro.az/` — лендинг.
- [ ] `libro.az/{slug}` — публичная bio-страница (не ломает старые ссылки/QR).
- [ ] `libro.az/menu/{slug}` — QR-меню.
- [ ] `app.libro.az/` (не авторизован) — редирект на `app.libro.az/login`.
- [ ] `app.libro.az/profile/{user}` (не авторизован) — редирект на `/login`.
- [ ] Вход работает; после входа `app.libro.az/` открывает дашборд.
- [ ] Google-вход работает через `app.libro.az`.
