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:
@@ -78,6 +78,9 @@ type Handlers struct {
|
||||
// means a search (q) answers 503 and the syncs after a write are
|
||||
// skipped.
|
||||
MaterialIndex *classes.MaterialIndexer
|
||||
// Revisions publishes the live heads-up after a write that bumped a
|
||||
// material's revision (D-20). Nil means no live event.
|
||||
Revisions *classes.MaterialRevisionNotifier
|
||||
}
|
||||
|
||||
func (h *Handlers) now() time.Time {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -27,10 +27,18 @@ func staleRevision(sent *uint, stored uint) bool {
|
||||
|
||||
// revise bumps the revision of material id inside tx when its content
|
||||
// differs from before (models.ReviseMaterial). Every content writer of the
|
||||
// API calls it after its write.
|
||||
// API calls it after its write. A bump also enqueues the live heads-up on
|
||||
// tx (D-20), published only when the write commits; its failure never fails
|
||||
// the write.
|
||||
func (h *Handlers) revise(ctx context.Context, tx *gorm.DB, id uint, before string) error {
|
||||
_, _, err := models.ReviseMaterial(tx.WithContext(ctx), id, before)
|
||||
return err
|
||||
rev, bumped, err := models.ReviseMaterial(tx.WithContext(ctx), id, before)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if bumped {
|
||||
h.Revisions.Revised(ctx, tx, id, rev)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeConflict answers 409 to a write that sent a stale revision, with the
|
||||
|
||||
Reference in New Issue
Block a user