Медиа и S3
Картинки и файлы, которые вы вставляете в блоки, PageCraft у себя не хранит. У каждого сайта своё S3-совместимое хранилище: туда кладётся файл, оттуда он отдаётся посетителям опубликованной страницы. Пока хранилище не подключено, поля типа «изображение» можно заполнять только ссылками на чужие ресурсы.
Подключение хранилища
Заголовок раздела «Подключение хранилища»Настройки сайта → раздел S3. Поля:
| Поле | Что это |
|---|---|
endpoint |
адрес S3-совместимого сервиса, например https://s3.storage.example |
bucket |
имя бакета |
region |
регион; если не указан — us-east-1 |
access_key |
ключ доступа |
secret_key |
секрет |
public_url |
база публичных ссылок; пусто — используется endpoint/bucket |
path_prefix |
папка внутри бакета; пусто — файлы лягут в корень, по умолчанию uploads |
public_url отделён от endpoint намеренно: файлы часто раздаются через CDN
или собственный домен, а не напрямую из бакета. Ссылка, которая попадёт в блок
и на опубликованную страницу, собирается именно из public_url.
Хранилище должно быть публично читаемым — PageCraft не подписывает ссылки на скачивание и не проксирует отдачу. Запись нужна только PageCraft, чтение — всем посетителям витрины.
Проверка доступа
Заголовок раздела «Проверка доступа»Кнопка «Проверить» (POST /api/sites/{id}/s3/test) кладёт в корень бакета
однобайтовый объект .pagecraft-healthcheck-<hex>.bin и пытается его удалить.
Проверка подтверждает право записи, и только его. Неудачное удаление
не считается ошибкой: политика бакета вполне может запрещать DELETE,
а загружать при этом позволять. Обратная сторона — зелёная проверка
не гарантирует, что удаление медиа из интерфейса сработает, и тестовые объекты
могут копиться в корне бакета. Их видно по префиксу .pagecraft-healthcheck-,
чистятся они вручную.
Ответ всегда 200, результат — в теле: { "ok": true, "error": null }.
Тест можно прогнать и до сохранения настроек, передав их в теле запроса.
Секреты в форме
Заголовок раздела «Секреты в форме»В ответах API secret_key и токены провайдера маскируются восьмёркой точек
••••••••. Форма показывает маску, и при сохранении значение, равное маске,
игнорируется — в базе остаётся прежний секрет. Не тронули поле — ничего
не изменилось. Побочный эффект: записать в секрет буквально •••••••• нельзя.
Загрузка из интерфейса
Заголовок раздела «Загрузка из интерфейса»Медиабиблиотека открывается из шапки или из поля блока с типом «изображение» (в последнем случае выбранный файл сразу подставляется в поле).
Что она умеет: показать загруженное, залить файл с диска, удалить файл.
Два ограничения интерфейса стоит знать заранее:
- выбор файла ограничен
image/*, и список показывает только изображения — расширенияjpg,jpeg,png,gif,webp,svg,avif. Файл другого типа, залитый через API, в бакете есть и по ссылке работает, но в библиотеке не появится; - удалять можно только объекты внутри
path_prefix— ключ за пределами префикса отклоняется с ошибкой доступа.
Имя файла не сохраняется. Ключ генерируется:
{path_prefix}/{ГГГГММДД}_{16 hex-символов}.{расширение} — исходные имена
конфликтуют, содержат пробелы и кириллицу, а иногда лишнее в названии.
Загрузка через API
Заголовок раздела «Загрузка через API»Внутренний контур (сессия в браузере):
| Метод и путь | Что делает |
|---|---|
GET /api/sites/{id}/media |
список изображений в префиксе |
POST /api/sites/{id}/s3/upload |
загрузить файлом (multipart/form-data, поле file) |
POST /api/sites/{id}/s3/upload-from-url |
скачать по ссылке и положить в бакет |
DELETE /api/sites/{id}/media?key=... |
удалить объект |
POST /api/sites/{id}/s3/test |
проверить доступ |
Публичный контур V1 (API-ключ в Authorization: Bearer pb_...):
| Метод и путь | Что делает |
|---|---|
GET /api/v1/sites/{id}/s3/status |
настроено ли хранилище и проходит ли запись |
POST /api/v1/sites/{id}/s3/upload |
загрузить файл, в ответе { "url": "..." } |
GET /api/v1/sites/{id}/s3/status отвечает 200 всегда, статус — в теле:
{ "configured": true, "writable": true, "error": null }configured — заполнены ли настройки, writable — прошёл ли реальный тест
записи, error — сообщение от хранилища при провале. Разделение даёт простое
правило: 200 — разбирайте тело, 5xx — повторяйте запрос.
Типы файлов
Заголовок раздела «Типы файлов»Загрузка файлом принимает любой тип: MIME определяется на сервере по содержимому, расширение берётся из имени файла. Ограничения нет намеренно — файл попадает в чужой бакет, на чужой домен, и политика допустимых типов — зона ответственности его владельца. Агентские сценарии кладут туда видео, PDF и архивы, а не только картинки.
Загрузка по ссылке ведёт себя иначе: расширение определяется по Content-Type
ответа, а если тип незнакомый — по расширению в URL. Если и оно не из списка
jpg, jpeg, png, gif, webp, svg, avif, файл всё равно будет
загружен, но как .jpg с MIME image/jpeg. Этот путь рассчитан на
изображения; всё остальное грузите файлом.
Размеры и таймауты
Заголовок раздела «Размеры и таймауты»Потолок — 100 MB (client_max_body_size в nginx и лимиты PHP). Больший
файл получит 413. Таймаут запроса — 300 секунд, а сам объект уходит в бакет
целиком из памяти: потоковой многочастной загрузки нет.
Коды ошибок
Заголовок раздела «Коды ошибок»| Код | Когда |
|---|---|
400 |
S3 не настроен для сайта, файл не передан, невалидный URL |
403 |
ключ при удалении лежит вне path_prefix |
404 |
сайт не найден или принадлежит другому пользователю |
413 |
файл больше 100 MB |
502 |
хранилище недоступно или отказало — ошибка внешней системы, не наша |
Как это делает агент
Заголовок раздела «Как это делает агент»Для ИИ-агентов есть скилл pagecraft-s3 — два скрипта поверх V1 API.
Порядок в нём жёсткий:
node scripts/s3-status.mjs <site_id>— обязательный первый шаг.configured: false→ хранилище не подключено, дальше идти нельзя;writable: false→ показатьerrorпользователю и остановиться.node scripts/s3-upload.mjs <site_id> <file>— на каждый файл, в ответе публичный URL.- Полученный URL подставляется в
placeholder_valuesблока при сборке страницы (скиллpagecraft-pages).
Скиллу нужен PAGECRAFT_API_KEY — ключ вида pb_..., создаётся в интерфейсе
PageCraft. Необязательная PAGECRAFT_API_URL меняет хост, префикс /api/v1
скрипты добавляют сами.
Чего система не делает
Заголовок раздела «Чего система не делает»- Не обрабатывает изображения: ни сжатия, ни превью, ни вариантов под экраны.
- Не убирает мусор: файл, на который никто не ссылается, остаётся в бакете. Удаление сайта тоже не чистит хранилище.
- Не следит за доступностью: сменили ключи или удалили бакет — картинки на опубликованных страницах отвалятся, и PageCraft об этом не узнает.
Источники
Заголовок раздела «Источники»backend/src/Service/S3Service.php— подпись AWS Signature V4, put/list/delete,testConnectionbackend/src/Service/Site/UploadSiteMediaService.php,UploadSiteMediaFromUrlService.php,DeleteSiteMediaService.phpbackend/src/Service/Query/Site/SiteMediaListQueryService.phpbackend/src/Controller/Site/*—/api/sites/{id}/s3/upload,/media,/s3/testbackend/src/Controller/V1/Site/UploadSiteMediaPublicAction.php,SiteS3StatusPublicAction.phpbackend/src/Service/Site/UpdateSiteService.php,backend/src/Assembler/Site/SiteAssembler.php— маскирование секретовskills/pagecraft-s3/SKILL.mdfront/src/components/MediaFinderModal.tsx,SiteSettingsDrawer.tsx