httpapi: точки метрики за период отдаются одним запросом

- `GET /api/v1/metrics/{name}?from&to&layer` — ряд точек за период; конверт
  объявляет слой, измеренный род, его применимость к отданному ряду и границу
  окна измерения, а сам ряд собирается из одного слоя, выбранного по охвату
  точек внутри периода
- use-case вынесен в `internal/points`, чтение — одним входом `store.ReadSeries`
  под одной транзакцией; правило выбора слоя остаётся в домене и приходит в
  хранилище колбэком
- `writeJSON` перестал экранировать HTML-символы и перестал глушить отказ
  записи: дословность содержимого точки иначе не удерживается, а оборванное
  тело уходило под видом успешного `200`
This commit is contained in:
av
2026-08-04 18:46:45 +03:00
parent b819b77f62
commit 29ca8d415c
36 changed files with 4721 additions and 58 deletions
+11 -3
View File
@@ -61,9 +61,17 @@ disabled`, `read auth disabled`), но стартовать не отказыв
**Ни один сегмент пути не берётся из тела или заголовков доставки** — это и
есть защита от выхода за пределы каталога, и она держится ровно на этом.
- **Координатный ключ точки** — `метрика + слой + начало + конец`. Имя метрики
приходит из тела и в путь на диске не попадает, но попадает в ключ, в лог и в
ответ каталога. Любое значение из чужого JSON, попадающее в ключ, в лог или в
отчёт, имеет названный предел длины.
приходит из тела и в путь на диске не попадает, но попадает в ключ, в лог, в
ответ каталога и — с появлением маршрута точек — **в адрес запроса и в
заголовок `ETag` ответа**. Любое значение из чужого JSON, попадающее в ключ, в
лог, в отчёт или в заголовок, имеет названный предел длины. У метки ответа
предел взят формой: в неё уезжает не имя, а хеш канонизированной формы запроса
(128 бит). Причина не только в длине — имя законно содержит кавычку, которая
по RFC 9110 кончает метку, и разбор обрезал бы её ровно там.
- **Имя метрики в адресе** декодируется из пути **ровно один раз**. Второе
декодирование превращает имя `a%41b` в имя `aAb` — то есть в имя **другой**
метрики витрины, и маршрут отвечает `200` её данными. Путь построен и прогнан
враждебным проходом ревью.
- **Ключ сущности** — `род секции + id` из HealthKit для `record`, `id` для
`workout`. `id` приходит из тела.
- **Ключ наблюдённого категориального значения** — `метрика + поле + значение`.