- колонка `owner` связью с `users` в обеих коллекциях новым шагом схемы `202608140001`; чтение задачи сужено владельцем, и чужая, ничья и несуществующая дают один ответ; правило просмотра файлов сужено им же - приём по HTTP берёт владельца из сессии, а предъявителя без учётной записи пользователя отвергает до чтения тела: позже пришлось бы убирать уложенный файл, а уборки файлов сервис не умеет. Выборка воркера владельцем не сужается - удаление учётной записи с записями отвергается стражем, и вешает его сама сборка хранилища: сборка, забывшая его позвать, теряла защиту молча
66 lines
3.8 KiB
Go
66 lines
3.8 KiB
Go
// Package pocketbase — хранилище задач и файлов поверх встроенной PocketBase.
|
|
//
|
|
// Приложение поднимается библиотекой, а не её набором команд: разбор флагов и
|
|
// мягкая остановка остаются нашими, а ключ `-c config.toml` — объявленный
|
|
// контракт запуска.
|
|
package pocketbase
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
pb "github.com/pocketbase/pocketbase"
|
|
"github.com/pocketbase/pocketbase/core"
|
|
|
|
// Шаги схемы регистрируются загрузкой своего пакета, а накатывает их
|
|
// `RunAllMigrations` ниже. Импорт здесь пустой и явный, хотя соседние файлы
|
|
// пакета и так берут оттуда имена коллекций: день, когда имена перестанут
|
|
// читаться отсюда, унёс бы вместе с последней ссылкой и регистрацию — список
|
|
// шагов остался бы пустым, `RunAllMigrations` вернул бы `nil`, и приложение
|
|
// поднялось бы здоровым, но без коллекций. Отказ вылез бы не на старте, а на
|
|
// первом приёме записи.
|
|
_ "git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations"
|
|
)
|
|
|
|
// New создаёт приложение хранилища на заданном каталоге данных и приводит его в
|
|
// рабочее состояние: открывает базу, читает настройки и накатывает непринятые
|
|
// шаги схемы.
|
|
//
|
|
// Схема накатывается **здесь**, а не оставляется серверу, хотя тот и гоняет
|
|
// непринятые шаги сам. Причина в порядке: воркеры стартуют раньше сервера, и на
|
|
// чистом каталоге их первые опросы приходились бы на несуществующую таблицу —
|
|
// отказ в журнале и в счётчике на каждую секунду до конца накатки.
|
|
func New(dataDir string) (*pb.PocketBase, error) {
|
|
app := pb.NewWithConfig(pb.Config{
|
|
DefaultDataDir: dataDir,
|
|
HideStartBanner: true,
|
|
})
|
|
|
|
if err := app.Bootstrap(); err != nil {
|
|
return nil, fmt.Errorf("failed to bootstrap storage: %w", err)
|
|
}
|
|
|
|
if err := app.RunAllMigrations(); err != nil {
|
|
return nil, fmt.Errorf("failed to apply storage schema: %w", err)
|
|
}
|
|
|
|
// Страж владельца вешается здесь, а не вызывающим: он защищает архив от
|
|
// удаления учётной записи, и сборка, забывшая его позвать, теряет защиту
|
|
// молча. Так это уже и было — окружение проверок его не ставило, и всё
|
|
// разграничение проверялось на приложении, где архив сносится одним
|
|
// запросом.
|
|
GuardOwnerDeletion(app)
|
|
|
|
return app, nil
|
|
}
|
|
|
|
// MustFindCollection достаёт коллекцию по имени. Отсутствие коллекции здесь —
|
|
// не отказ окружения, а несделанный шаг схемы: сервис до этой точки не доходит,
|
|
// потому что Serve накатывает схему прежде, чем поднять сервер.
|
|
func findCollection(app core.App, name string) (*core.Collection, error) {
|
|
collection, err := app.FindCollectionByNameOrId(name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to find collection %s: %w", name, err)
|
|
}
|
|
return collection, nil
|
|
}
|