Files
preland/README.md
T
2026-10-19 14:57:39 +03:00

176 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Преленд zoomzilla
Одностраничный лендинг перед основным сайтом (`zoomzilla.ru`): посетитель с рекламы выбирает фото прямо на
преленде, файл сразу уходит в API сайта, а сам посетитель переходит на сайт — на страницу обработки этого фото.
Варианты страницы и сайта (A/B) задаются метками в адресе. Код — обычный PHP без фреймворка и сборки; переводы —
маленькая заглушка `Yii::t` в `i18n.php`.
Активные production: `https://zoompixel.ru`, `https://zoompixel.online`; текущая цель выката — только `https://zoomstage.online`. Пример ссылки входа:
`https://zoomstage.online/?indpic=1&theme=dark&degr=2&registration=after&bchoice=a&tp=1&utm_source=test&subid=CLICK1`.
## Как работает
### Метки
`index.php` разбирает параметры адреса по разделу `utm` из `config.json`. Для каждой настроенной метки:
- значение из адреса принимается, если оно есть в `weights`;
- `rnd` — случайное значение по весам (`weights`), так делится трафик между вариантами;
- неизвестное значение отбрасывается, отсутствующее — заменяется на `default`.
Ненастроенные параметры (`utm_source`, `keyword`, `subid` и любые другие) проходят как есть. Перед переходом
Preland всегда формирует полный resolved-набор своих восьми меток: явное допустимое значение остаётся, `rnd`
разрешается по весам, отсутствующее получает `default`. Вместе с внешними рекламными параметрами этот набор
уходит в `claim`, а затем сайт переносит его в `upscale`; ни один из восьми параметров не должен пропадать только
потому, что был назначен default.
| Метка | Что задаёт | Где |
|---|---|---|
| `indpic` | картинка-пример на преленде | корневой каталог сета `/1/`, `/2/` и т. д. |
| `theme` | `light` / `dark` | преленд и сайт |
| `degr` | пресет ухудшения превью «До» | сайт (#42) |
| `registration` | `before` — PIN до оплаты, `after` — после | сайт, страница результата |
| `bchoice` | попап выбора банка (`a` — показать) | сайт, оплата (#44) |
| `chb` | галочки согласия на странице оплаты (`no` — не обязательны) | сайт, оплата |
| `tp` | группа тарифов | сайт, cpanel «Группы тарифов (tp)» |
| `focus` | первоначальная область лупы Join: `center`/`face` | сайт, cpanel после `tp` |
### Загрузка и переход на сайт
1. Браузер проверяет файл: изображение, не больше 6000 px по большей стороне.
2. XHR `POST https://<host>/api/images/public/create` (`multipart/form-data`: `file` и `client_key` — случайный UUID
загрузки, `crypto.randomUUID()`), `Accept: application/json`. Ответ — JSON с id обработки.
3. Браузер переходит на `https://<host>/api/images/public/claim?key=<client_key>&<метки>`. Сайт кладёт загрузку
в сессию посетителя и отвечает 302 на `/public/upscale?id=<обработка>&<метки>` (уже без ключа); страница сама
запускает обработку.
Почему не сразу `/public/upscale`: XHR с преленда кросс-доменный, и сессия, в которой сайт запомнил загрузку,
посетителю на сайте недоступна — браузеры изолируют или не принимают куки третьей стороны. Страница обработки
пускает только владельца загрузки, поэтому без передачи по ключу посетитель видел 404. Ключ одноразовый и годен
30 минут после загрузки.
`<host>` — домен сайта из `config.json`. API сайта принимает загрузку с любого домена (CORS отражает `Origin`), так
что для нового домена преленда на сайте ничего настраивать не нужно.
### Файлы
| Путь | Назначение |
|---|---|
| `index.php` | вход: конфиг, разбор меток, адреса API и перехода, общие переменные, `renderComponent()` |
| `template.php` | разметка страницы: шапка, блок загрузки, примеры, подвал с реквизитами |
| `components/form.php` | форма выбора файла и загрузка (XHR, `client_key`, переход на `claim`) |
| `components/preview.php`, `process.php`, `examples.php` | превью файла, индикатор загрузки, примеры «до/после» |
| `i18n.php`, `messages/{ru,en}/` | заглушка `Yii::t` и переводы; язык сейчас всегда `ru` (определение языка закомментировано) |
| `docs/` | `logo.svg`, `logo-alt.svg` и документы в PDF: тарифы, оферта, соглашение, политика |
| `requisites.html` | реквизиты для подвала (HTML), если в `config.json` нет `requisites`. Без обоих — **заглушка «ИП Иванов Иван Иванович»** из `template.php` |
| `images/`, `fonts/`, `favicon/`, `*.css` | статика; `style.min.css` встраивается в страницу |
| `.htaccess` | только сроки кэширования статики (Apache) |
| `config.json` | снимок актуального stage-конфига и схема. Рабочий конфиг на каждом хостинге настраивает владелец; deploy не заменяет его и меняет только явно согласованные в задаче ключи. После такого изменения актуальный stage-снимок забирают обратно в Git |
### `config.json`
| Ключ | Обязательный | Что задаёт |
|---|:---:|---|
| `host` | **да** | домен сайта, куда идут загрузка и переход (`zoomzilla.ru`). Без него берётся домен самого преленда — запросы уходят на преленд и получают 404 |
| `utm` | да | метки: `weights` (значение → доля трафика для `rnd`) и `default` |
| `requisites` | нет | реквизиты в подвале — список строк, каждая отдельным абзацем (текст экранируется). Важнее `requisites.html`; без обоих — заглушка «ИП Иванов» |
| `assetsUriPrefix` | нет | откуда брать статику. Пусто — из своего каталога; если на хостинге нет каталога `images`, статика берётся с сайта (`https://<host>`) |
| `fontsUrl`, `faviconUrl`, `spritePath` | нет | адреса шрифтов, иконок и SVG-спрайта, если они не в `assetsUriPrefix` |
| `logoAltExt` | нет | расширение второго логотипа (`svg` по умолчанию) |
| `title` | нет | заголовок страницы («Улучшение изображения») |
| `default_language` | нет | сейчас не действует: язык всегда `ru` |
### Демо-наборы
Preland хранит наборы ровно как Index, поэтому их можно переносить между проектами без переименования и
перекладывания файлов:
```text
1/01_desk_1-source.{jpg,webp,avif}
1/01_desk_2-result.{jpg,webp,avif}
1/01_desk_3-thumb.{jpg,webp,avif}
1/01_mob_1-source.{jpg,webp,avif}
1/01_mob_2-result.{jpg,webp,avif}
1/01_mob_3-thumb.{jpg,webp,avif}
...
1/05_...
2/01_...
...
N/05_...
```
`indpic=N` выбирает корневой каталог `/N/`. Набор копируется с Index в корень
домена целиком: без переименования, вложения в `images/` и промежуточных
каталогов. Устаревшие `images/demo*` не являются частью поставки.
Для `zoomstage.online` обычный deploy ветки `staging` намеренно **не загружает**
корневые числовые каталоги `/N/`: это редактируемый контент stage, а не артефакт
кодовой доставки. Workflow продолжает проверять их доступность HTTP-smoke. Чтобы
зафиксировать обновлённый набор, сначала синхронизируйте его со stage в Git по
отдельной задаче; следующая заливка не перетрёт его старой ревизией.
## Деплой на произвольный домен
**Требования.** PHP 8.0+ (боевой хостинг — 8.2), веб-сервер с `index.php` в роли индексного файла и **HTTPS**:
`crypto.randomUUID()` работает только в защищённом контексте, а сайт отвечает по https.
1. **Домен.** Запись A (или CNAME) на хостинг, сертификат (Let's Encrypt или от хостинга); открыть `https://<домен>/`.
2. **Файлы.** Содержимое репозитория — в корень сайта, без `.git` и служебного (`README.md`, `.gitea/`):
```bash
git clone http://git.dira89kv.beget.tech:3000/anton/preland.git && cd preland
# дальше — загрузка на хостинг: rsync/scp по SSH или FTP-клиент
```
3. **Конфиг и реквизиты.** Рабочий `config.json` на хостинге — источник истины. При первичном создании домена его настраивают отдельно; обычный deploy не заменяет его репозиторным снимком и не перезаписывает `requisites.html`. Менять можно только явно согласованные ключи после чтения текущего файла.
4. **nginx без Apache** (если хостинг не обрабатывает `.htaccess`):
```nginx
server {
server_name <домен>;
root /var/www/<домен>;
index index.php;
location ~ /(\.git|config\.json|README\.md) { deny all; }
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~* \.(avif|webp|jpe?g|png|svg|ico|woff2?|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }
}
```
5. **Сайт (по желанию).** Домен преленда сайт определяет сам — по `Referer` первого захода. Свои умолчания меток
для этого домена: cpanel → «Метки (UTM)» → «Умолчания по домену прелендинга».
6. **Проверка.**
```bash
curl -s 'https://<домен>/?nc=1' | grep -o 'const createApiUrl = [^;]*;\|const redirectUrlBase = [^;]*;'
# оба адреса — https://<host>/api/images/public/…, а не домен преленда
curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' 'https://<host>/api/images/public/claim?key=x'
# 302 на https://<host>/ — точка передачи на сайте есть
```
В браузере: открыть `https://<домен>/?indpic=1&theme=dark&subid=TEST`, выбрать фото — адрес сменится на
`https://<host>/public/upscale?id=…&indpic=1&theme=dark&subid=TEST…` (без `key=`), пойдёт обработка.
Это настоящая загрузка и обработка на сайте.
## Автодеплой (Gitea Actions)
Решение владельца от 19.10.2026, задача preland#4: сейчас выкатываем **только stage**.
| Экземпляр | Назначение | Выкат |
|---|---|---|
| https://zoomstage.online | Тестовый стенд | Push в staging, SFTP |
| https://zoompixel.ru | Production | Отключён до отдельного поручения |
| https://zoompixel.online | Production | Отключён до отдельного поручения |
Прежний preland.anton.barbariskatest.ru выведен из использования и исключён из workflow.
Публикация изменений самого workflow в main не запускает доставку: триггер оставлен только для staging.
Workflow `.gitea/workflows/deploy.yml` содержит единственную job deploy-zoomstage на
выделенном runner zoomzilla-production. Production возвращается только отдельной задачей
и прямым поручением владельца. Ошибки старого FTP больше не являются блокером выпуска.
Рабочие config.json/requisites.html на хостинге не перезаписываются. Корневые наборы
/N/ принадлежат stage и исключены из кодовой доставки. Родительские каталоги создаются
штатным SFTP-генератором; smoke проверяет страницу и assets наборов1/2.
Откат — отдельный согласованный revert в staging, который обновляет только stage.
## Android: фото и файлы (2026-10-19)
Основная кнопка выбирает image/*; отдельная ссылка «Выбрать файлы (PDF и др.)» открывает общий picker без ограничения image/*. PDF в подписи скрывается по runtime API-policy. iOS/iPad/Desktop сохраняют прежний общий выбор. +много сохраняется в одной компактной mobile-строке. components/browser-upload.php и CSS upload-picker-links.php — лёгкие inline-копии соответствующих компонентов API: отдельный PHP-хост не использует Yii AssetManager, новый внешний запрос не добавляется. При изменении общего контракта синхронизировать обе копии; API mobile-picker.cjs покрывает обе реальные страницы. Android byte-copy FileProvider сохраняет исходные байты/имя/MIME и ограничен max_file_size_bytes; до ответа policy используется консервативный15MB предел копирования. UTM/client_key/claim/config.json не меняются.