feat(12.1-01): cabana.ForbiddenError answers a refused write with 403

- hooks and bulk, record, toolbar and widget actions may return it
- 403 forbidden with the localized message and field details; the write's
  transaction is rolled back; other errors stay the opaque 500
- form shows a refused save as a persistent banner and keeps the values;
  a refused delete is a toast
- smoke tests, OpenAPI notes, dist, README, docs
This commit is contained in:
Jakub Zych
2026-10-04 23:53:34 +02:00
parent 61d5fc72ad
commit 71073bc8a2
23 changed files with 486 additions and 48 deletions

View File

@@ -150,7 +150,7 @@ func (s RelationService) CreateChild(ctx context.Context, cc *CompiledController
return RecordResult{}, &CapabilityError{ControllerID: controllerID(cc)}
}
var result RecordResult
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {
@@ -269,7 +269,7 @@ func (s RelationService) ShowChild(ctx context.Context, cc *CompiledController,
return RecordResult{}, recordNotFound{}
}
var result RecordResult
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {
@@ -304,7 +304,7 @@ func (s RelationService) UpdateChild(ctx context.Context, cc *CompiledController
return RecordResult{}, &CapabilityError{ControllerID: controllerID(cc)}
}
var result RecordResult
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {
@@ -361,7 +361,7 @@ func (s RelationService) DeleteChildren(ctx context.Context, cc *CompiledControl
return BulkResult{}, err
}
var result BulkResult
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {
@@ -453,7 +453,7 @@ func (s RelationService) ShowPivot(ctx context.Context, cc *CompiledController,
return nil, recordNotFound{}
}
var data map[string]any
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {
@@ -500,7 +500,7 @@ func (s RelationService) UpdatePivot(ctx context.Context, cc *CompiledController
return nil, recordNotFound{}
}
var data map[string]any
err = lagoon.Transaction(ctx, s.DB, func(ctx context.Context, tx *gorm.DB) error {
err = s.transaction(ctx, func(ctx context.Context, tx *gorm.DB) error {
ctx = withTx(ctx, tx)
parent, err := s.loadParent(ctx, tx, cc, cr, ownerID)
if err != nil {