приём и чтение записей сведены к одному контракту приложения

- адреса приложения переехали в своё пространство `/app/`, опрос готовности
  убран целиком: рубеж и причину остановки владелец узнаёт карточкой записи,
  текст — отдельным адресом названного вида
- заведена единая точка отображения доменной ошибки и слой, приводящий к той же
  форме отказы библиотеки: тело несёт машиночитаемый код рядом с сообщением
- у записи появились имя файла отправителя, длительность и размер своими
  колонками, а у ленты владельца — свой индекс: без него страница сканировала
  весь архив сервиса
This commit is contained in:
av
2026-08-15 13:51:23 +03:00
parent 79ff12548f
commit 3a2da3004b
55 changed files with 5506 additions and 466 deletions
+33
View File
@@ -71,8 +71,41 @@ type AcquiredRecord struct {
Holder string
}
// RecordCursor — положение в ленте записей, заданное **полным** ключом
// сортировки. Одного времени мало: у записей, принятых одним запросом, оно
// совпадает, и порядок между ними иначе не определён.
type RecordCursor struct {
CreatedAt string
ID string
}
// RecordQuery — что спрашивают у ленты записей.
type RecordQuery struct {
// OwnerID обязателен: пустой не совпадает ни с одной записью.
OwnerID string
// Filter — состояние записи. Пустой значит «все».
Filter *entity.ListFilter
// Cursor — положение, с которого продолжать. Пустой значит «сначала».
Cursor *RecordCursor
Limit int
}
// RecordPage — страница ленты. Ключ следующей страницы пуст, когда страница
// последняя.
type RecordPage struct {
Items []*entity.AudioRecord
NextCursor *RecordCursor
TotalItems int
}
type AudioRecordRepository interface {
Create(record *entity.AudioRecord) error
// List отдаёт страницу записей владельца, новыми сверху, не читая ни
// расшифровки, ни структуры реплик.
List(q RecordQuery) (*RecordPage, error)
// ResolveTopicNames разрешает темы названиями одним запросом на страницу и
// сужает их владельцем: словарь тем свой у каждого человека.
ResolveTopicNames(ownerID string, ids []string) (map[string]string, error)
// Save сохраняет запись, захват которой держит holder. Захват, доставшийся
// за время работы другому, даёт LostAcquisitionError и запись не проводит.
// Пустой holder снимает эту условность и в конвейере не употребляется: все