хранилище переехало с PocketBase на SQLite со своим каталогом файлов

- база своя: два пула, захват одним UPDATE ... RETURNING, шаги схемы на goose
  под файловым замком, одна миграция начальной схемы вместо семи прежних
- транспорт переписан на net/http: свои слои, свой ограничитель частоты,
  отдача файла с проверкой владельца; панель /_/ и пространство /api/ исчезли
- по находкам ревью: журнал не пишет путь под корнем приложения, ключ бюджета
  читается справа налево, узнавание известного идёт читающим пулом
This commit is contained in:
av
2026-08-23 08:06:04 +03:00
parent 1edf8cb225
commit c9b7765646
118 changed files with 11668 additions and 6679 deletions
+34 -26
View File
@@ -8,16 +8,17 @@
- Конвертация в ogg через ffmpeg
- Распознавание речи через Yandex SpeechKit
- Отслеживание статуса задач расшифровки
- Встроенная PocketBase для метаданных, файлов и панели владельца; метрики Prometheus
- Своё хранилище: SQLite для метаданных и каталог файлов записей рядом с ним; метрики Prometheus
## Технологии
- **Язык**: Go 1.26, CGO не нужен
- **Веб-фреймворк**: gin-gonic/gin
- **HTTP**: стандартная библиотека, `net/http`
- **Распознавание**: Yandex SpeechKit + Yandex Object Storage (S3)
- **Конвертация**: ffmpeg
- **Хранилище, файлы и панель**: встроенная PocketBase
- **База данных**: SQLite внутри PocketBase (через modernc.org/sqlite, CGO не нужен)
- **База данных**: SQLite через modernc.org/sqlite, CGO не нужен
- **Шаги схемы**: pressly/goose/v3, библиотекой — накат при старте
- **Файлы записей**: свой каталог, подкаталог на запись
- **Метрики**: prometheus/client_golang
## Установка и запуск
@@ -75,13 +76,13 @@ inv pl -- transcriber
Адреса приложения живут под корнем `/app`: `POST /app/audiorecords` — приём
записи, `GET /app/audiorecords` — страница своих записей,
`GET /app/audiorecords/{id}` — карточка, `GET /app/audiorecords/{id}/text` —
текст названного вида, `GET /app/me` — кто пришёл, `GET /app/config` — пределы,
которые сервис объявляет приложению. Отдельными адресами стоят `GET /metrics` —
метрики Prometheus с префиксом `transcriber_` — и `GET /health` — проверка
живости. Своего входа у сервиса нет: кто пришёл, называет заголовок обратного
прокси ([access](openspec/specs/access/spec.md)). Сверх этого тем же портом
отдаётся собственная поверхность встроенного хранилища и панель `/_/` —
[docs/security.md](docs/security.md), «Из чего строятся пути и ключи».
текст названного вида, `GET /app/audiorecords/{id}/file` — файл записи названной
копии, `GET /app/me` — кто пришёл, `GET /app/config` — пределы, которые сервис
объявляет приложению. Отдельными адресами стоят `GET /metrics` — метрики
Prometheus с префиксом `transcriber_` — и `GET /health` — проверка живости.
Своего входа у сервиса нет: кто пришёл, называет заголовок обратного прокси
([access](openspec/specs/access/spec.md)). Больше на этом порту не отвечает
ничего: всякий прочий путь получает разметку приложения.
Контракт приёма и опроса нормативен и живёт в
[openspec/specs/intake/spec.md](openspec/specs/intake/spec.md): поля запроса и
@@ -91,7 +92,7 @@ inv pl -- transcriber
## Состояния задач
Перечень состояний, переходы между ними и число воркеров —
[docs/database.md](docs/database.md), разделы «Коллекции» и «Представление
[docs/database.md](docs/database.md), разделы «Таблицы» и «Представление
данных»; как сложен конвейер целиком — [docs/architecture.md](docs/architecture.md).
## Структура проекта
@@ -100,9 +101,10 @@ inv pl -- transcriber
transcriber/
├── cmd/
│ ├── transcriber/ # Точка входа сервиса: конфиг, миграции, сборка зависимостей, запуск
│ └── devtools/ # Оснастка разработчика: подставной прокси для локального входа
│ └── devtools/ # Оснастка разработчика: подставной прокси и возврат записи в работу
├── internal/
│ ├── entity/ # Модели: задача, файл, результат распознавания
│ ├── entity/ # Модели: запись, файл, результат распознавания
│ ├── ident/ # Выдача и разбор идентификаторов строк (ULID)
│ ├── contract/ # Интерфейсы адаптеров и репозиториев, типы ошибок
│ ├── config/ # Разбор config.toml
│ ├── metrics/ # Метрики Prometheus
@@ -114,26 +116,32 @@ transcriber/
│ ├── converter/ffmpeg/ # Конвертация аудио
│ ├── metaviewer/ffmpeg/ # Длительность аудио
│ ├── recognizer/yandex/ # SpeechKit + Object Storage
│ └── repo/pocketbase/ # Репозитории, схема коллекций, правила панели
│ └── repo/sqlite/ # Репозитории, подключение к базе, шаги схемы, каталог файлов
└── data/ # Каталог данных: база и файлы записей вместе
├── data.db # База хранилища (создаётся автоматически)
── storage/ # Файлы записей в раскладке хранилища
├── transcriber.db # База (создаётся автоматически)
── migrate.lock # Замок наката схемы
└── records/ # Файлы записей: подкаталог на запись
```
## Хранилище
Коллекции хранилища — аудиозапись и её приложения. Поля, ключи, правило времени и
идентификаторов, а также механика захвата задачи воркером —
[docs/database.md](docs/database.md). Панель владельца — по адресу `/_/` того же
порта; пароль от неё задаёт сам владелец по приглашению, которое сервис печатает
в журнал при первом запуске.
Таблицы базы — аудиозапись и её приложения. Поля, ключи, правило времени и
идентификаторов, раскладка файлов записи и механика захвата задачи воркером —
[docs/database.md](docs/database.md). Панели владельца у сервиса нет: единственное
его действие вне экранов — возврат остановленной записи в работу подкомандой
оснастки.
```bash
go run ./cmd/devtools resume -c config.toml <идентификатор записи>
```
## Разработка
Схему двигают шаги миграций PocketBase на Go
`internal/adapter/repo/pocketbase/migrations`, файл на шаг. Непринятые шаги
накатываются при подъёме хранилища, прежде чем стартуют воркеры и сервер.
Применённый шаг не переписывается: изменение — только новым файлом шага.
Схему двигают шаги `pressly/goose/v3`
`internal/adapter/repo/sqlite/migrations`, файл на шаг, версия шага — число в
начале имени файла. Непринятые шаги накатываются при старте, прежде чем поднимутся
входы и стартуют воркеры; отказ шага роняет старт. Применённый шаг не
переписывается: изменение — только новым файлом шага.
Проверки перед коммитом — одной командой: