# Transcriber Service

Сервис расшифровки аудиозаписей. Вход один — HTTP API.

## Возможности

- Приём аудиофайлов через HTTP API
- Конвертация в ogg через ffmpeg
- Распознавание речи через Yandex SpeechKit
- Отслеживание статуса задач расшифровки
- Своё хранилище: SQLite для метаданных и каталог файлов записей рядом с ним; метрики Prometheus

## Технологии

- **Язык**: Go 1.26, CGO не нужен
- **HTTP**: стандартная библиотека, `net/http`
- **Распознавание**: Yandex SpeechKit + Yandex Object Storage (S3)
- **Конвертация**: ffmpeg
- **База данных**: SQLite через modernc.org/sqlite, CGO не нужен
- **Шаги схемы**: pressly/goose/v3, библиотекой — накат при старте
- **Файлы записей**: свой каталог, подкаталог на запись
- **Метрики**: prometheus/client_golang

## Установка и запуск

1. Клонируйте репозиторий
2. Установите зависимости:
   ```bash
   go mod tidy
   ```
3. Скопируйте образец конфига и заполните его:
   ```bash
   cp config.example.toml config.toml
   ```
4. Запустите приложение:
   ```bash
   go run ./cmd/transcriber -c config.toml
   ```

Сервер запустится на порту из `[server] port`, по умолчанию 8080. Нужен
установленный `ffmpeg`.

### Кого пускают

Кто пришёл, сервис узнаёт из заголовка `Remote-User`, который ставит обратный
прокси, сходив к Authelia; своего входа у сервиса нет. Кому верить, задаёт
перечень доверенных адресов в секции `[auth]`. Подробности —
[docs/security.md](docs/security.md), «Что разграничивает доступ»; известные
прорехи образца конфига — [docs/conventions/config.md](docs/conventions/config.md).

Локально прокси нет, а браузер заголовков не ставит — заголовок подставляет сам
сервис по своим настройкам. Второго процесса для этого не нужно: приложение
открывают по адресу сервиса.

Рецепт целиком — связным блоком в `config.example.toml`, под перечнем доверенных
адресов: там названы три правки, принимаемые имена заголовков и цена включения.

Заполненная секция имитации при выключенном предохранителе роняет
старт с именем ключа: сервис с включённым предохранителем называет пришедшего
сам, никого не спросив, и в бою этот ключ стоит `false`.

## Деплой

Деплой запускается из `pet-project-server`:

```bash
inv pl -- transcriber
```

Плейбук сам зовёт `task image` (контракт роли `app_image`): образ собирается
локально и едет на сервер через `docker save`/`load`, реестр не участвует.
Локально образ можно собрать и руками — `task image` даст `transcriber:dev`.

## HTTP API

Адреса приложения живут под корнем `/app`: `POST /app/audiorecords` — приём
записи, `GET /app/audiorecords` — страница своих записей,
`GET /app/audiorecords/{id}` — карточка, `GET /app/audiorecords/{id}/text` —
текст названного вида, `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): поля запроса и
ответа, коды и условия. Менять его — необратимое действие
([CLAUDE.md](CLAUDE.md), «Работа»), и второго описания у него быть не должно.

## Состояния задач

Перечень состояний, переходы между ними и число воркеров —
[docs/database.md](docs/database.md), разделы «Таблицы» и «Представление
данных»; как сложен конвейер целиком — [docs/architecture.md](docs/architecture.md).

## Структура проекта

```
transcriber/
├── cmd/
│   ├── transcriber/        # Точка входа сервиса: конфиг, миграции, сборка зависимостей, запуск
│   └── devtools/           # Оснастка разработчика: возврат остановленной записи в работу
├── internal/
│   ├── entity/             # Модели: запись, файл, результат распознавания
│   ├── ident/              # Выдача и разбор идентификаторов строк (ULID)
│   ├── contract/           # Интерфейсы адаптеров и репозиториев, типы ошибок
│   ├── config/             # Разбор config.toml
│   ├── metrics/            # Метрики Prometheus
│   ├── service/            # Конвейер расшифровки
│   ├── controller/
│   │   ├── http/           # HTTP-обработчики
│   │   └── worker/         # Фоновые воркеры
│   └── adapter/
│       ├── converter/ffmpeg/    # Конвертация аудио
│       ├── metaviewer/ffmpeg/   # Длительность аудио
│       ├── recognizer/yandex/   # SpeechKit + Object Storage
│       └── repo/sqlite/         # Репозитории, подключение к базе, шаги схемы, каталог файлов
└── data/                   # Каталог данных: база и файлы записей вместе
    ├── transcriber.db      # База (создаётся автоматически)
    ├── migrate.lock        # Замок наката схемы
    └── records/            # Файлы записей: подкаталог на запись
```

## Хранилище

Таблицы базы — аудиозапись и её приложения. Поля, ключи, правило времени и
идентификаторов, раскладка файлов записи и механика захвата задачи воркером —
[docs/database.md](docs/database.md). Панели владельца у сервиса нет: единственное
его действие вне экранов — возврат остановленной записи в работу подкомандой
оснастки.

```bash
go run ./cmd/devtools resume -c config.toml <идентификатор записи>
```

## Разработка

Схему двигают шаги `pressly/goose/v3` —
`internal/adapter/repo/sqlite/migrations`, файл на шаг, версия шага — число в
начале имени файла. Непринятые шаги накатываются при старте, прежде чем поднимутся
входы и стартуют воркеры; отказ шага роняет старт. Применённый шаг не
переписывается: изменение — только новым файлом шага.

Проверки перед коммитом — одной командой:

```bash
task gate
```

Что она гоняет, чем краснеет и какой отказ считается объявленным долгом —
[CLAUDE.md](CLAUDE.md), раздел «Гейт».
