Files
transcriber/tasks/items/pocketbase-storage.md
T
av df65eb5e32 docs: решено оставить очередь своей таблицей коллекцией PocketBase
- заведены записка разведки job-queue-choice и ADR: готовые библиотеки River и
  goqite отвергнуты, захват сворачивается в один запрос с RETURNING
- в architecture.md уточнён принцип «очередь таблицей» и закрыт открытый вопрос
  «Очередь», кроме холостого опроса
- задача pocketbase-storage забрала очередь себе: границы, счётчик попыток,
  состояние «мертва» и оракулы
2026-08-11 14:13:27 +03:00

82 lines
7.4 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.
# 🧹 Перевести хранилище и файлы записей на встроенный PocketBase
- **Тип:** chore
- **Категория:** Очередь
- **Зачем:** Записи, метаданные и файлы лежат порознь, и владелец не видит их ничем, кроме консоли на сервере: панель PocketBase покажет их, только если они переедут к ней. Заодно переписывается захват задачи: сегодня это два запроса без транзакции, а падающая всегда задача падает вечно.
PocketBase встраивается библиотекой в тот же бинарник и приносит хранилище,
учётные записи и панель администратора разом. Конвейер и оба входа работают
по-прежнему, а снаружи прибавляется панель по адресу `/_/`.
Разведка `pocketbase-admin-fit` довод проверила, и 2026-08-11 принято решение
[перевести хранилище вместе с файлами](../../docs/adr/ADR-2026-08-11-pocketbase-storage-with-admin-panel.md):
панель показывает файлы и пользователей только тех, что лежат у неё, а половина
перевода довода не окупает. Замер — [docs/research/pocketbase.md](../../docs/research/pocketbase.md).
**Файлы переезжают в раскладку PocketBase**, а плоского каталога `data/files` с
именами-UUID не остаётся. Это необратимо, и момент перехода назначает человек.
Вход в этой задаче не трогаем: его переводит `oidc-login`.
Данные не переносим — база заводится с чистого листа, и это решение принято
сознательно.
**Очередь переписывается тем же заходом.** Разведка `job-queue-choice` 2026-08-11
[отвергла готовые библиотеки](../../docs/adr/ADR-2026-08-11-queue-as-pocketbase-collection.md):
очередь остаётся своей таблицей, но становится коллекцией PocketBase. Захват
сворачивается в один запрос с `RETURNING`, число попыток ложится колонкой,
нарастающая пауза выражается существующим `delay_time`, а исчерпавшая попытки
задача переходит в состояние «мертва» вместо `is_error = 1`. Сравнение
кандидатов — [docs/research/job-queue.md](../../docs/research/job-queue.md).
## Затрагивает
- таблицы `files` и `transcribe_jobs`, каталог `migrations/` и весь механизм
goose;
- `internal/adapter/repo/sqlite` целиком, включая захват задачи через
`FindAndAcquire`;
- состав колонок очереди: прибавляется число попыток, а `is_error` уступает
место состоянию «мертва» в перечне состояний задачи;
- `internal/contract`, интерфейсы `FileRepository` и `TranscriptJobRepository`;
- ключ конфигурации `[database] path`, ключ `[storage] path` и раскладка
каталога `data/`;
- **формат файла на диске:** запись переезжает в поле коллекции, путь становится
`pb_data/storage/<коллекция>/<запись>/<имя>_<10 случайных символов>` рядом с
файлом атрибутов. Имя, данное отправителем, впервые попадает в путь целиком;
- отдача файла: вместо чтения с диска — ссылка `/api/files/...`, и решение,
помечать ли поле защищённым;
- сборка образа: PocketBase тянет свой набор зависимостей, а `mattn/go-sqlite3`
с его требованием CGO уходит — библиотека ходит в SQLite через
`modernc.org/sqlite`;
- пароль суперпользователя панели: где заводится и как попадает на сервер;
- `docs/database.md` — схема, представление данных и таблица настроек;
- `CLAUDE.md` — строка стека про CGO и раздел про запреты с путями;
- `docs/security.md` — раздел «Из чего строятся пути и ключи».
## Критерии приёмки
- Сервис поднимается на чистом каталоге данных, накатывает свою схему сам и
принимает запись обоими входами. Оракул — запуск на пустом `data/` и прогон
записи из Telegram и через `POST /api/audio` до состояния `done`.
- Захват задачи воркером идёт одним запросом и не выдаёт одну запись двум
вызывающим. Оракул — тест на трёх параллельных вызовах захвата по одному
состоянию: ровно один получает запись.
- Задача, брошенная на середине, достаётся снова по истечении срока захвата, а
падающая всегда — уходит в «мертва» и из выборки исчезает. Оракулы — тест с
проставленным задним числом `acquire_time` и тест с шагом, падающим на каждой
попытке: после заданного их числа задача не выдаётся, а её состояние видно
отбором.
- Принятая запись видна в панели строкой и скачивается по ссылке
`/api/files/...` тем же файлом. Оракулы — прогон записи через
`POST /api/audio` на пустом каталоге, затем поиск её строки в коллекции задач
на `/_/` по идентификатору и запрос `/api/files/...` за тем же файлом: длина
совпадает с загруженной.
- `docs/database.md` описывает новую схему, а старые упоминания goose и goqu из
документов канона убраны. Оракул — `task gate`, шаг `docs.py check`.
## Рамки
Данные прежней базы не переносим и не пытаемся сохранить; выкладку не запускаем;
смена формата хранения на сервере необратима, и момент перехода назначает
человек. Вход не трогаем — он на `oidc-login`. Панель наружу закрывает Authelia
на обратном прокси: это работа выкладки, а не приложения.