package migrations import ( "github.com/pocketbase/pocketbase/core" "git.vakhrushev.me/av/transcriber/internal/entity" ) func up202608110001(app core.App) error { files := core.NewBaseCollection(FilesCollection) files.Fields.Add( // Сам файл. Защищённым поле не помечено намеренно: право прочитать // запись даёт знание её идентификатора, и файл встаёт вровень с опросом // готовности задачи, а не ниже. // // Потолок задан **числом**: нулём библиотека читает не «без предела», а // своё умолчание в 5 МиБ, и на нём отваливалось бы всё длиннее пяти // минут. Число выведено из расчётного потолка записи в шесть часов с // запасом на видео; оно же стоит строкой в docs/database.md. &core.FileField{Name: "file", MaxSelect: 1, MaxSize: entity.MaxRecordSize}, // Где лежит копия. Поле названо `location`, а не `storage`: последним // словом зовут само хранилище, и третий смысл развёл бы одно слово по // разным вещам. &core.SelectField{ Name: "location", Values: []string{entity.LocationLocal, entity.LocationS3}, MaxSelect: 1, Required: true, }, // Ключ объекта во внешнем хранилище; у местной копии пуст. &core.TextField{Name: "object_key"}, &core.NumberField{Name: "size", OnlyInt: true}, &core.AutodateField{Name: "created", OnCreate: true}, &core.AutodateField{Name: "updated", OnCreate: true, OnUpdate: true}, ) if err := app.Save(files); err != nil { return err } jobs := core.NewBaseCollection(JobsCollection) jobs.Fields.Add( // Перечень состояний закрыт схемой: задача, заведённая в панели руками, // не должна попасть в выборку с состоянием, которого конвейер не знает. &core.SelectField{ Name: "state", Values: []string{ entity.StateCreated, entity.StateConverted, entity.StateTranscribe, entity.StateDone, entity.StateFailed, entity.StateDead, }, MaxSelect: 1, Required: true, }, &core.SelectField{ Name: "source", Values: []string{entity.SourceUnknown, entity.SourceApi, entity.SourceTelegram}, MaxSelect: 1, Required: true, }, // Текущий файл задачи: шаг конвейера переставляет ссылку на свой // результат. // Обязательна: задача без записи не может пройти ни одного шага, и // заведённая в панели руками она дошла бы до шага только затем, чтобы // отказать. Компилятор этого не держит — держит схема. &core.RelationField{ Name: "file", CollectionId: files.Id, MaxSelect: 1, Required: true, }, &core.TextField{Name: "error_text"}, &core.TextField{Name: "acquisition_id"}, &core.DateField{Name: "acquire_time"}, &core.DateField{Name: "delay_time"}, // Число попыток: растёт при каждом захвате, обнуляется на шаге, // завершившемся без отказа. &core.NumberField{Name: "attempts", OnlyInt: true, Min: ptr(0.0)}, &core.TextField{Name: "recognition_op_id"}, &core.EditorField{Name: "transcription_text"}, &core.NumberField{Name: "tg_chat_id", OnlyInt: true}, &core.NumberField{Name: "tg_reply_message_id", OnlyInt: true}, &core.AutodateField{Name: "created", OnCreate: true}, &core.AutodateField{Name: "updated", OnCreate: true, OnUpdate: true}, ) // Выборка воркера идёт по состоянию, паузе и сроку захвата — индекс по // состоянию снимает полный перебор, который был у прежней таблицы. jobs.AddIndex("idx_transcribe_jobs_state", false, "state", "") return app.Save(jobs) } func down202608110001(app core.App) error { // Порядок обратный порядку заведения: задачи ссылаются на файлы. for _, name := range []string{JobsCollection, FilesCollection} { collection, err := app.FindCollectionByNameOrId(name) if err != nil { continue } if err := app.Delete(collection); err != nil { return err } } return nil }