feat: extract file text on every attach path and backfill on reindex

- inline and signed uploads queue the text job in their transaction
- the admin form queues it in the save transaction
- detach resyncs the material (its text row goes by cascade)
- bm:materials-reindex backfills missing PDF texts before each sync
This commit is contained in:
Jakub Zych
2026-11-08 20:16:14 +01:00
parent 412821ec56
commit f841e8ed71
11 changed files with 480 additions and 22 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"embed"
"errors"
"fmt"
"io/fs"
"time"
@@ -11,6 +12,7 @@ import (
"git.golem15.com/golem15/summercms/modules/bonfire"
"git.golem15.com/golem15/summercms/modules/conga"
"git.golem15.com/golem15/summercms/modules/lagoon"
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
"git.golem15.com/golem15/summercms/modules/lighthouse"
"git.golem15.com/golem15/summercms/modules/lighthouse/centrifugo"
"git.golem15.com/golem15/summercms/modules/pact"
@@ -19,6 +21,7 @@ import (
"git.golem15.com/jakub/sm-bm-plugin/console"
"git.golem15.com/jakub/sm-bm-plugin/controllers"
"github.com/go-gormigrate/gormigrate/v2"
"gocloud.dev/blob"
"gorm.io/gorm"
)
@@ -131,7 +134,7 @@ func (p *Plugin) Commands() []bonfire.Command {
cmds = append(cmds, console.ChatSyncCommand(p.withDB, func() (string, string) {
return p.chatGroup(), p.chatModeratorGroup()
}))
cmds = append(cmds, console.MaterialsReindexCommand(console.MaterialsReindexDeps{DB: p.publishedDB, Index: p.materialIndex}))
cmds = append(cmds, console.MaterialsReindexCommand(console.MaterialsReindexDeps{DB: p.publishedDB, Index: p.materialIndex, Backfill: p.backfillFileTexts}))
return append(cmds, centrifugo.Commands(p.app)...)
}
@@ -222,6 +225,42 @@ func (p *Plugin) publishedDB(ctx context.Context) (*gorm.DB, func(), error) {
return gdb, func() { _ = sqlDB.Close() }, nil
}
// backfillFileTexts extracts the text of material id's PDFs that have none
// yet, for bm:materials-reindex (D-04: PDFs attached before file search).
func (p *Plugin) backfillFileTexts(ctx context.Context, db *gorm.DB, id uint) error {
bucket, err := p.uploadsBucket(ctx)
if err != nil {
return err
}
_, err = classes.EnsureFileTexts(ctx, db, bucket, classes.DefaultPDFText(), id)
return err
}
// uploadsBucket returns the uploads bucket published on the app: the one
// serve published or, when a console command runs, one opened from
// storage.uploads.bucket_url and published. It is an error when no bucket
// is configured.
func (p *Plugin) uploadsBucket(ctx context.Context) (*blob.Bucket, error) {
if p.app == nil {
return nil, errors.New("jz.bm: app is nil")
}
if b, ok := p.app.Lookup[*blob.Bucket](); ok && b != nil {
return b, nil
}
b, err := attach.OpenBucket(ctx, p.app.Config)
if err != nil {
return nil, fmt.Errorf("jz.bm: the uploads bucket is not configured: %w", err)
}
if err := attach.Publish(p.app, b); err != nil {
_ = b.Close()
if pub, ok := p.app.Lookup[*blob.Bucket](); ok && pub != nil {
return pub, nil
}
return nil, err
}
return b, nil
}
func init() {
party.Register(&Plugin{})
}