feat: announce material revisions on materials:{id}

- every revision bump (API writes and /bmadmin saves) enqueues
  updated.jz.bm.material with {material_id, revision} in a savepoint
- the materials namespace authorizer allows a subscribe only to a
  visible material the user may manage
- api.ManagesMaterials and api.CanWatchMaterial
This commit is contained in:
Jakub Zych
2026-11-09 01:12:49 +01:00
parent 40e4a2438b
commit 2a47186a7a
13 changed files with 571 additions and 10 deletions

View File

@@ -170,6 +170,12 @@ func replaceMaterialShares(tx *gorm.DB, materialID uint, userIDs []uint) error {
// managesMaterials tells whether the user holds jz.bm.access_materials.
func (h *Handlers) managesMaterials(ctx context.Context, db *gorm.DB, userID uint) (bool, error) {
return ManagesMaterials(ctx, db, userID)
}
// ManagesMaterials tells whether the user holds jz.bm.access_materials. The
// realtime connection-token guard admits such users (D-20).
func ManagesMaterials(ctx context.Context, db *gorm.DB, userID uint) (bool, error) {
principal, err := userclasses.PrincipalByID(ctx, db, userID)
if err != nil {
return false, err
@@ -177,6 +183,26 @@ func (h *Handlers) managesMaterials(ctx context.Context, db *gorm.DB, userID uin
return principal != nil && cabana.Allows(principal, []string{controllers.PermMaterials}), nil
}
// CanWatchMaterial tells whether the user may follow material id's live
// revision channel (D-20): the material is visible to them at now and they
// may manage it (canManageMaterial). It reads current state on every call;
// a material they cannot see is false without an error.
func CanWatchMaterial(ctx context.Context, db *gorm.DB, now time.Time, userID, id uint) (bool, error) {
var m models.Material
res := visibleMaterials(db.WithContext(ctx), now, userID).Where("jz_bm_materials.id = ?", id).Limit(1).Find(&m)
if res.Error != nil {
return false, res.Error
}
if res.RowsAffected == 0 {
return false, nil
}
manager, err := ManagesMaterials(ctx, db, userID)
if err != nil {
return false, err
}
return canManageMaterial(m, userID, manager), nil
}
// canManageMaterial tells whether a material manager may change the
// material's access: it is theirs, or it has no author yet (the first change
// makes them its author).