- audiorecords вместо transcribe_jobs: приложения (texts, structures, recognitions, record_events, topics) живут своими коллекциями, ссылки на исходник и на приведённую копию перестали переставляться - рубеж называет достигнутое, отказ стал признаком остановки с причиной, а сторожей стало двое: число отказов и время в рубеже - воркеры потеряли специализацию, их число задаётся [pipeline] workers, шаг выбирается по рубежу, а захват отдаёт идентификатор и признак захвата
152 lines
5.0 KiB
Go
152 lines
5.0 KiB
Go
package pocketbase
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/pocketbase/dbx"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
|
|
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations"
|
|
"git.vakhrushev.me/av/transcriber/internal/entity"
|
|
)
|
|
|
|
type TextRepository struct {
|
|
app core.App
|
|
}
|
|
|
|
func NewTextRepository(app core.App) *TextRepository {
|
|
return &TextRepository{app: app}
|
|
}
|
|
|
|
// Put кладёт текст записи, заменяя прежний того же вида.
|
|
//
|
|
// Замена, а не вставка: пара «запись и вид» уникальна, и повтор прерванного шага
|
|
// иначе завёл бы второй комплект строк — тогда вопрос «какой текст отдавать
|
|
// человеку» стал бы вопросом порядка записи, а не состояния.
|
|
func (repo *TextRepository) Put(recordID, kind, contents string) (*entity.Text, error) {
|
|
collection, err := findCollection(repo.app, migrations.TextsCollection)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
record, err := repo.app.FindFirstRecordByFilter(
|
|
migrations.TextsCollection,
|
|
"record = {:record} && kind = {:kind}",
|
|
dbx.Params{"record": recordID, "kind": kind},
|
|
)
|
|
switch {
|
|
case err == nil:
|
|
// Строка есть — заменяем содержимое.
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
record = core.NewRecord(collection)
|
|
record.Set("record", recordID)
|
|
record.Set("kind", kind)
|
|
default:
|
|
// Отказ хранилища «строкой нет» не является, и подменять его вставкой
|
|
// нельзя: она упрётся в уникальный индекс, и наверх уедет жалоба на
|
|
// запись вместо правды о недоступной базе.
|
|
return nil, fmt.Errorf("failed to look up text of kind %s for record %s: %w", kind, recordID, err)
|
|
}
|
|
record.Set("contents", contents)
|
|
|
|
if err := repo.app.Save(record); err != nil {
|
|
// Текст расшифровки наружу не выходит даже отказом: цепочка `%w` от
|
|
// хранилища несёт значение поля.
|
|
return nil, fmt.Errorf("failed to store text of kind %s for record %s", kind, recordID)
|
|
}
|
|
|
|
return textFromRecord(record), nil
|
|
}
|
|
|
|
func (repo *TextRepository) GetByID(id string) (*entity.Text, error) {
|
|
record, err := repo.app.FindRecordById(migrations.TextsCollection, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get text %s: %w", id, err)
|
|
}
|
|
return textFromRecord(record), nil
|
|
}
|
|
|
|
func textFromRecord(record *core.Record) *entity.Text {
|
|
return &entity.Text{
|
|
Id: record.Id,
|
|
RecordID: record.GetString("record"),
|
|
Kind: record.GetString("kind"),
|
|
Contents: record.GetString("contents"),
|
|
}
|
|
}
|
|
|
|
type StructureRepository struct {
|
|
app core.App
|
|
}
|
|
|
|
func NewStructureRepository(app core.App) *StructureRepository {
|
|
return &StructureRepository{app: app}
|
|
}
|
|
|
|
// Put кладёт структуру реплик, заменяя прежнюю той же версии разбора. Довод тот
|
|
// же, что и у текста: повтор шага не должен заводить второй строки.
|
|
func (repo *StructureRepository) Put(recordID string, version int, replicas []entity.Replica) (*entity.Structure, error) {
|
|
collection, err := findCollection(repo.app, migrations.StructuresCollection)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contents, err := json.Marshal(replicas)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to encode structure of record %s", recordID)
|
|
}
|
|
|
|
record, err := repo.app.FindFirstRecordByFilter(
|
|
migrations.StructuresCollection,
|
|
"record = {:record} && version = {:version}",
|
|
dbx.Params{"record": recordID, "version": version},
|
|
)
|
|
switch {
|
|
case err == nil:
|
|
// Строка есть — заменяем содержимое.
|
|
case errors.Is(err, sql.ErrNoRows):
|
|
record = core.NewRecord(collection)
|
|
record.Set("record", recordID)
|
|
record.Set("version", version)
|
|
default:
|
|
return nil, fmt.Errorf("failed to look up structure of record %s: %w", recordID, err)
|
|
}
|
|
record.Set("contents", string(contents))
|
|
|
|
if err := repo.app.Save(record); err != nil {
|
|
return nil, fmt.Errorf("failed to store structure of record %s", recordID)
|
|
}
|
|
|
|
return &entity.Structure{
|
|
Id: record.Id,
|
|
RecordID: recordID,
|
|
Version: version,
|
|
Replicas: replicas,
|
|
}, nil
|
|
}
|
|
|
|
func (repo *StructureRepository) GetByID(id string) (*entity.Structure, error) {
|
|
record, err := repo.app.FindRecordById(migrations.StructuresCollection, id)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get structure %s: %w", id, err)
|
|
}
|
|
|
|
var replicas []entity.Replica
|
|
raw := record.GetString("contents")
|
|
if raw != "" {
|
|
if err := json.Unmarshal([]byte(raw), &replicas); err != nil {
|
|
return nil, fmt.Errorf("failed to decode structure %s", id)
|
|
}
|
|
}
|
|
|
|
return &entity.Structure{
|
|
Id: record.Id,
|
|
RecordID: record.GetString("record"),
|
|
Version: record.GetInt("version"),
|
|
Replicas: replicas,
|
|
}, nil
|
|
}
|