Наборы данных и системные источники
Всё повторяющееся на странице — пункты меню, вопросы FAQ, карточки тарифов, дочерние страницы, хлебные крошки — выводит повторитель onpress/repeat из списка по имени. Список берётся из одного из трёх мест, всегда в одном порядке:
- Системный источник — состояние сайта, которое платформа знает сама: меню, настройки, языки, главная, страницы, хлебные крошки. Имена зарезервированы.
- Набор сайта — ваш JSON-список с произвольным именем (
faq,tariffs,team). - Список в атрибуте —
{"items":[…]}прямо во вставке, когда список нужен в одном месте.
Как выводить списки во фрейме — на странице повторитель onpress/repeat. Здесь — сами данные.
Набор сайта
Набор — всегда список (массив) объектов с любыми полями. Схемы нет: какие поля положили, такие и доступны в шаблоне как item.<поле>.
[
{ "id": 1, "q": "Сколько стоит?", "a": "Бесплатно на старте", "page": "tarify" },
{ "id": 2, "q": "Нужна ли карта?", "a": "Нет", "page": "tarify" }
]
- Имя: латиница в нижнем регистре, цифры,
-и_, до 64 символов, первый символ — буква или цифра ([a-z0-9][a-z0-9_-]{0,63}). Заглавные приводятся к строчным. - Заняты:
menu.*,site.*,pages.*,breadcrumbsи словаsources,resolve—409 reserved_dataset_name. - Предел — 512 КБ на набор (
413 dataset_too_large). Это список для повторителя, а не хранилище контента. - Одиночный объект вместо массива оборачивается в список из одного элемента, ответ предупреждает.
Положить набор
curl -s -X PUT "$API/site/$SITE/data/faq" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"items": [
{"id": 1, "q": "Сколько стоит?", "a": "Бесплатно на старте", "page": "tarify"},
{"id": 2, "q": "Нужна ли карта?", "a": "Нет", "page": "tarify"}
]}' | jq '{applied, updated, dataset: .dataset | {name, lang, items, fields}, rebaked: .rebaked.status}'
{
"applied": true,
"updated": false,
"dataset": {
"name": "faq",
"lang": null,
"items": 2,
"fields": [
{ "field": "id", "types": ["number"], "in_items": 2 },
{ "field": "q", "types": ["string"], "in_items": 2 },
{ "field": "a", "types": ["string"], "in_items": 2 },
{ "field": "page", "types": ["string"], "in_items": 2 }
]
},
"rebaked": "nothing"
}
PUT заменяет набор целиком. dry_run: true — проверить и показать итог без записи. fields — какие поля реально есть в элементах и сколько элементов их содержат: удобно, когда пишете разметку под чужой набор. После записи пересобираются фреймы, которые читают набор, и сбрасывается кэш их страниц — ответ в rebaked.
Вариант на язык
curl -s -X PUT "$API/site/$SITE/data/faq" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"lang": "en", "items": [{"id": 1, "q": "How much is it?", "a": "Free to start", "page": "tarify"}]}' \
| jq '.dataset | {name, lang, option}'
{ "name": "faq", "lang": "en", "option": "onpress_dataset_faq__en" }
Вариант лежит рядом с общим набором, а не вместо него: в реестре это faq@en. Какой вариант отдаётся при чтении и при сборке страницы:
- вариант языка из
langзапроса или атрибута повторителя, иначе языка страницы, иначе языка сборки; - нет варианта — общий набор (
lang_fallbackв ответе поясняет); - нет общего — вариант языка сайта по умолчанию;
- ничего нет —
404 source_not_found.
Так сайт с одним общим списком работает без изменений, а переведённый список подхватывается там, где он есть. PATCH и DELETE с lang адресуют ровно один вариант; без lang — общий набор. Запись любого варианта пересобирает всех, кто читает набор по базовому имени.
Прочитать
# все наборы сайта (без системных источников)
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data" | jq '.datasets[] | {name, lang, items, bytes}'
# один набор
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/faq?lang=en" | jq '{name, lang, lang_fallback, total, items}'
GET /data/{name} принимает те же параметры, что повторитель: lang, page (для page.* в match и для языка), match (JSON), limit, offset, paged, statuses. Поэтому ответ показывает ровно то, что получит страница.
Если набор меняли мимо API и реестр отстал от содержимого, ответ несёт registry_stale с цифрами реестра и настоящими; отдаются настоящие элементы, а любой PUT или PATCH приводит реестр в порядок.
Поправить точечно
curl -s -X PATCH "$API/site/$SITE/data/faq" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"ops": [
{"op": "merge", "match": {"id": 2}, "fields": {"a": "Нет, карта не нужна"}},
{"op": "append", "item": {"id": 3, "q": "Есть API?", "a": "Да", "page": "tarify"}},
{"op": "move", "match": {"id": 3}, "to": 0},
{"op": "remove", "index": 2}
]}' | jq '{applied, total}'
{
"applied": [
{ "op": "merge", "index": 1, "fields": ["a"] },
{ "op": "append", "added": 1, "total": 3 },
{ "op": "move", "from": 2, "to": 0 },
{ "op": "remove", "index": 2, "total": 2 }
],
"total": 2
}
op | Адрес | Данные | Что делает |
|---|---|---|---|
set | index или match | item | заменяет элемент |
merge | index или match | fields | дописывает и перезаписывает поля элемента |
append, prepend | — | item или items | добавляет в конец или в начало |
remove | index или match | — | удаляет элемент |
move | index или match | to | переносит на позицию to |
match — объект, сравниваемый с полями элемента, берётся первое совпадение. Операции применяются по порядку в одной транзакции: если одна не удалась (404 item_not_found, 400 bad_patch_op), набор остаётся нетронутым. Одну операцию можно прислать без обёртки ops: {"op": "append", "item": {…}}.
Удалить
curl -s -X DELETE "$API/site/$SITE/data/faq?lang=en" -H "Authorization: Bearer $OP_TOKEN" | jq .deleted
curl -s -X DELETE "$API/site/$SITE/data/faq" -H "Authorization: Bearer $OP_TOKEN" | jq .deleted
Первый вызов удаляет только вариант en, второй — общий набор. Несуществующий — 404 dataset_not_found. Повторители, которые читали набор, после удаления выводят пустой список.
Системные источники
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/sources" | jq '.sources[] | {name, contextual, writable}'
| Источник | Контекстный | Запись | Что отдаёт |
|---|---|---|---|
menu.header | нет | PUT → пункты меню в области header | дерево пунктов меню, стоящего в области: id, title, url, type, object, object_id, target, classes, description, parent, order, children. С lang — меню этого языка |
menu.footer_1, menu.footer_2, menu.footer_3 | нет | PUT → пункты меню области | то же для областей подвала |
site.settings | нет | PATCH → настройки сайта | один элемент: name, description, home_url, site_url, admin_email, timezone, date_format, time_format, locale, charset, posts_per_page, show_on_front, front_page_id, posts_page_id, theme |
site.languages | нет | PATCH → один язык или язык по умолчанию | языки сайта: слаг, имя, локаль, флаг, направление, основной ли, адрес главной языка |
site.front_page | нет | PUT → главная сайта | один элемент: id, заголовок, слаг, статус и адрес главной |
pages.all | нет | — | все опубликованные страницы плоским списком: id, title, slug, url (с префиксом языка), lang, parent, menu_order, status, frame ({id, slug} или null). По языку запроса не фильтруется — для этого match |
pages.tree | нет | — | те же страницы деревом по родителям, children у каждой |
pages.top | нет | — | страницы верхнего уровня |
pages.children | да | — | дочерние страницы текущей |
pages.siblings | да | — | соседи текущей страницы, включая её саму, только её языка |
pages.adjacent | да | — | один элемент с prev и next — предыдущая и следующая страница уровня |
breadcrumbs | да | — | цепочка от главной до текущей: id, title, slug, url, current |
Контекстный источник зависит от того, какая страница собирается. Без страницы он отдаёт пустой список и needs_page: true — при чтении через API передайте page (id, слаг или путь):
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/breadcrumbs?page=/docs/vvedenie/" | jq '.items[] | {title, url, current}'
curl -s -H "Authorization: Bearer $OP_TOKEN" "$API/site/$SITE/data/pages.children?page=docs" | jq '[.items[] | {title, url}]'
Параметр page здесь — id, слаг или путь со слешами (/docs/vvedenie/); форма с тильдой, как в адресах ручек /pages/{ref}, в параметре не понимается. Повторитель по контекстному источнику делает фрейм зависимым от страницы.
Адреса в menu.*, pages.* и breadcrumbs — настоящие, с префиксом языка: русская страница — /ru/o-nas/. Первое звено breadcrumbs — главная языка страницы. Источник меню несёт meta с id меню и числом отброшенных пунктов: пункт, ведущий на удалённую или неопубликованную страницу, посетитель не видит, и "invalid_items": 4 при пустом меню значит, что сломано меню, а не источник.
Запись в системный источник
Меню, настройки, языки и главная — состояние, которое меняется, и меняется оно через то же имя. Запись уходит туда, где эти данные живут на самом деле, второй копии не появляется, и после неё пересобираются зависимые фреймы:
# пункты меню, стоящего в области header (язык — ru)
curl -s -X PUT "$API/site/$SITE/data/menu.header" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"lang": "ru", "items": [{"page": "/docs/"}, {"url": "https://t.me/onpress", "title": "Telegram", "target": "_blank"}]}'
# название и описание сайта
curl -s -X PATCH "$API/site/$SITE/data/site.settings" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "Мой сайт", "description": "Описание сайта"}'
# имя языка или язык по умолчанию
curl -s -X PATCH "$API/site/$SITE/data/site.languages" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"lang": "ru", "name": "Русский"}'
# главная сайта
curl -s -X PUT "$API/site/$SITE/data/site.front_page" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"page": "/home/"}'
| Источник | Метод | Куда уходит | Что принимает |
|---|---|---|---|
menu.{область} | PUT | PUT /menus/@{область}/items | items — дерево пунктов ({page} или {url}, title, target, classes, children), lang, allow_invalid |
site.settings | PATCH | PATCH /site/settings | name, description (или blogname, blogdescription), blog_public, site_icon, analytics — только публичные ключи, секрет даёт 400 secret_field и в ответ не попадает |
site.languages | PATCH | PATCH /site/languages/{slug} или PUT /site/languages/default | lang и поля языка (slug, locale, name, rtl, flag, order); {"default": "ru"} — сменить основной |
site.front_page | PUT | PUT /site/front-page | page — id или путь; lang |
Ответ — ответ той ручки, куда ушла запись, плюс source, wrote и rebaked. Чего нельзя и почему:
| Запрос | Отказ | Вместо этого |
|---|---|---|
PUT /data/menu.header, а в области нет меню | 404 menu_not_assigned | создать меню POST /menus и поставить в область PUT /menus/{ref}/location |
PATCH /data/site.settings с locale, front_page_id, home_url, theme | 400 field_belongs_elsewhere | язык — site.languages, главная — site.front_page, адрес — смена домена, тему API не меняет |
PATCH /data/site.languages со списком items | 400 whole_list_not_accepted | по одному языку; добавить — POST /site/languages, удалить — DELETE /site/languages/{slug} |
не тот метод (PUT вместо PATCH) | 405 wrong_method_for_source с правильным write | метод из write.method |
запись в pages.* или breadcrumbs | 409 computed_source с readonly_because | менять сами страницы — список считается из них |
DELETE системного источника | 409 system_source_not_deletable | опустошить меню — PUT с "items": [] |
Каждый источник в GET /data/sources и в GET /data/{name} сам говорит, пишется ли он: у записываемого есть write (метод, адрес, куда уходит, что принимает), у вычисляемого — readonly_because.
Одно короткое имя — две разные вещи. Если шапка сайта собрана из набора header, источник menu.header пуст просто потому, что в области нет меню ядра. Такой ответ несёт same_name_dataset с подсказкой, что шапку этого сайта меняет PUT /data/header.
Проверить, что получит страница
POST /data/resolve разрешает источник ровно так, как это сделает повторитель при сборке, — проверка до загрузки фрейма:
curl -s -X POST "$API/site/$SITE/data/resolve" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"source": "faq", "page": "tarify", "match": {"page": "page.slug"}}' \
| jq '{origin, contextual, lang, lang_fallback, match, match_total, total, items}'
{
"origin": "site",
"contextual": true,
"lang": null,
"lang_fallback": "there is no dataset \"faq\" for language \"en\", the shared one was returned",
"match": { "page": "tarify" },
"match_total": 3,
"total": 2,
"items": [ { "id": 1, "q": "Сколько стоит?", "…": "…" }, { "id": 2, "…": "…" } ]
}
| Поле тела | Смысл |
|---|---|
source | имя источника или набора; без него — проверяется items |
items | список, как в атрибуте повторителя |
page | страница: даёт значения page.* и язык набора |
lang | язык варианта набора |
match | фильтр — объект или его JSON-строка |
limit, offset, paged | окно, как у повторителя; paged — значение page.paged |
statuses | статусы для pages.* |
origin — откуда взят список: system, site или inline. С match ответ показывает, во что разрешился каждый токен (match), сколько было до фильтра (match_total) и после (total). Токен page.* без page — пустой список, needs_page: true и подсказка match_note. Сервис вычисляет в фильтре page.id, page.slug, page.title, page.status, page.type, page.parent_id, page.paged, site.lang, site.blog_id и raw:; другой токен — 400 match_token_unsupported (при сборке страницы он всё равно сработает — проверяйте такой фильтр сборкой).
Где лежат данные
Набор — в опциях своего сайта: реестр onpress_datasets и сам список в onpress_dataset_<имя> (вариант — onpress_dataset_<имя>__<язык>). Наборы до 32 КБ загружаются вместе с остальными опциями, большие — отдельным запросом. Сетевых наборов нет: данные одного сайта другим не видны.