package pocketbase import ( "time" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/tools/types" "git.vakhrushev.me/av/transcriber/internal/entity" ) // Отображение аудиозаписи в запись коллекции и обратно живёт одним местом. // // Мест стало **два** вместо прежних четырёх: захват больше не перечисляет // колонки поимённо, а возвращает идентификатор и признак своего захвата. // Инвариант проекта о колонках очереди этим съёживается и перестаёт расти с // моделью — иначе каждая новая колонка записи попадала бы под него. // applyOwnedByPipeline кладёт в запись только те поля, которыми распоряжается // конвейер. Поля, которые он не меняет никогда — владелец, вход, заголовок, // краткое описание, темы и адресат ответа, — не трогаются вовсе. // // Разрез нужен потому, что шаг держит запись снимком с момента захвата и до // своего сохранения, а это часы. Всё, что владелец правил в панели за это время, // безусловная запись снимка стёрла бы молча: ни строки в журнале, ни отказа в // панели — владелец видел бы успешное сохранение и был бы уверен, что правка на // месте. func applyOwnedByPipeline(record *core.Record, r *entity.AudioRecord) { record.Set("state", r.State) record.Set("state_entered_at", dateOrEmpty(&r.StateEnteredAt)) record.Set("halted_at", dateOrEmpty(r.HaltedAt)) record.Set("halt_reason", derefString(r.HaltReason)) record.Set("error_text", derefString(r.ErrorText)) record.Set("acquisition_id", derefString(r.AcquisitionID)) record.Set("acquire_expires_at", dateOrEmpty(r.AcquireExpiresAt)) record.Set("delay_time", dateOrEmpty(r.DelayTime)) record.Set("attempts", r.Attempts) record.Set("original_file", derefString(r.OriginalFileID)) record.Set("normalized_file", derefString(r.NormalizedFileID)) record.Set("transcript_text", derefString(r.TranscriptTextID)) record.Set("literary_text", derefString(r.LiteraryTextID)) record.Set("structure", derefString(r.StructureID)) record.Set("recognition", derefString(r.RecognitionID)) } // applyToRecord кладёт запись целиком — это заведение, и спорить за поля здесь // не с кем. func applyToRecord(record *core.Record, r *entity.AudioRecord) { applyOwnedByPipeline(record, r) // Владелец кладётся только здесь, при заведении. В applyOwnedByPipeline его // нет намеренно: конвейер владельца не назначает и не меняет, а снимок шага, // записанный поверх, стёр бы его молча. record.Set("owner", derefString(r.OwnerID)) record.Set("source", r.Source) record.Set("title", derefString(r.Title)) record.Set("brief", derefString(r.Brief)) record.Set("tg_chat_id", derefInt64(r.TgChatId)) record.Set("tg_reply_message_id", derefInt(r.TgReplyMessageId)) } func recordToAudioRecord(record *core.Record) *entity.AudioRecord { return &entity.AudioRecord{ Id: record.Id, OwnerID: nilIfEmpty(record.GetString("owner")), Source: record.GetString("source"), Title: nilIfEmpty(record.GetString("title")), Brief: nilIfEmpty(record.GetString("brief")), State: record.GetString("state"), StateEnteredAt: record.GetDateTime("state_entered_at").Time(), HaltedAt: timeOrNil(record.GetDateTime("halted_at")), HaltReason: nilIfEmpty(record.GetString("halt_reason")), ErrorText: nilIfEmpty(record.GetString("error_text")), AcquisitionID: nilIfEmpty(record.GetString("acquisition_id")), AcquireExpiresAt: timeOrNil(record.GetDateTime("acquire_expires_at")), DelayTime: timeOrNil(record.GetDateTime("delay_time")), Attempts: record.GetInt("attempts"), OriginalFileID: nilIfEmpty(record.GetString("original_file")), NormalizedFileID: nilIfEmpty(record.GetString("normalized_file")), TranscriptTextID: nilIfEmpty(record.GetString("transcript_text")), LiteraryTextID: nilIfEmpty(record.GetString("literary_text")), StructureID: nilIfEmpty(record.GetString("structure")), RecognitionID: nilIfEmpty(record.GetString("recognition")), TgChatId: nilIfZero64(int64(record.GetInt("tg_chat_id"))), TgReplyMessageId: nilIfZeroInt(record.GetInt("tg_reply_message_id")), CreatedAt: record.GetDateTime("created").Time(), UpdatedAt: record.GetDateTime("updated").Time(), } } func derefString(v *string) string { if v == nil { return "" } return *v } func derefInt64(v *int64) int64 { if v == nil { return 0 } return *v } func derefInt(v *int) int { if v == nil { return 0 } return *v } // dateOrEmpty отдаёт пустое значение вместо нулевой даты: пустая колонка даты в // хранилище это пустая строка, и она же значит «времени нет». func dateOrEmpty(v *time.Time) any { if v == nil || v.IsZero() { return "" } date, err := types.ParseDateTime(*v) if err != nil { return "" } return date } func nilIfEmpty(v string) *string { if v == "" { return nil } return &v } func timeOrNil(v types.DateTime) *time.Time { if v.IsZero() { return nil } t := v.Time() return &t } func nilIfZero64(v int64) *int64 { if v == 0 { return nil } return &v } func nilIfZeroInt(v int) *int { if v == 0 { return nil } return &v }