feat(12.2-01): add deferred:purge, its daily framework schedule and relation child hooks

- deferred:purge [--days] in lagoon.RuntimeCommands (purge_days, default 5)
- lagoon.FrameworkSchedule entry at purge_at (default 03:00, empty disables)
- conga prepends framework entries as summercms.lagoon[i]:<command>
- pact.Relation{Before,After}{Create,Update,Delete} optional hooks
- lagoon, conga and pact READMEs, scheduling and setup docs
This commit is contained in:
Jakub Zych
2026-10-02 17:45:41 +02:00
parent f48a886f94
commit 8818d7b023
11 changed files with 378 additions and 8 deletions

View File

@@ -15,7 +15,7 @@ Capability interfaces that compiled plugins implement to contribute routes, conf
- Backend registration data: `pact.Permission`, `pact.NavigationItem` and `pact.SettingsItem`, exposed through `pact.HasPermissions`, `pact.HasNavigation` and `pact.HasSettings`.
- Admin controller contracts: `pact.AdminController`, `pact.HasAdminControllers`, `pact.AdminAssets` (embedded Winter-shaped admin YAML), `pact.AdminPermissioned` and `pact.AdminRecordSource`.
- Admin extension contracts, so a plugin extends the compiled admin SPA without a Node build: `pact.AdminClientAssets` (per-controller JS and CSS from the plugin's embedded `assets/` tree, Winter's `addJs`/`addCss`), `pact.HasAdminActions` with `pact.AdminAction`, `pact.AdminActionInput` and `pact.AdminActionResult` (named toolbar and widget actions whose routes, CSRF check, permissions and record scoping the framework owns), and `pact.AdminPartialData` (the curated view model a partial template renders).
- Optional admin hooks a controller or model can implement: list and form query scoping (`pact.ListExtendQuery`, `pact.FormExtendQuery`), create, update and delete hooks (`pact.FormBeforeCreate`, `pact.FormAfterUpdate`, `pact.FormBeforeDelete` and their siblings), relation hooks (`pact.RelationExtendManageQuery`, `pact.RelationExtendOptionsQuery`, `pact.RelationBeforeLink`), filter scopes (`pact.FilterScope`, `pact.FilterOptions`) and dropdown options (`pact.DropdownOptionsProvider`).
- Optional admin hooks a controller or model can implement: list and form query scoping (`pact.ListExtendQuery`, `pact.FormExtendQuery`), create, update and delete hooks (`pact.FormBeforeCreate`, `pact.FormAfterUpdate`, `pact.FormBeforeDelete` and their siblings), relation hooks (`pact.RelationExtendManageQuery`, `pact.RelationExtendOptionsQuery`, `pact.RelationBeforeLink`), relation child hooks around creating, updating and deleting a related record (`pact.RelationBeforeCreate`, `pact.RelationAfterCreate`, `pact.RelationBeforeUpdate`, `pact.RelationAfterUpdate`, `pact.RelationBeforeDelete`, `pact.RelationAfterDelete`), filter scopes (`pact.FilterScope`, `pact.FilterOptions`) and dropdown options (`pact.DropdownOptionsProvider`).
- A background job contract (`pact.Job`, `pact.JobArgs`) that does not depend on any queue library.
- A schedule contract: `pact.HasSchedule` returns `pact.ScheduledCommand` entries (a registered command name, its arguments and a `pact.Cadence` built with `pact.Daily`, `pact.DailyAt` or `pact.Every`), the Go form of WinterCMS `registerSchedule`. It does not depend on any queue library either.
- `pact.OptionalMessage`, a service an optional plugin can publish so others integrate with it without importing its package.
@@ -111,6 +111,13 @@ func (p *Plugin) Schedule() []pact.ScheduledCommand {
| `pact.HasAdminActions` | Registers a controller's actions for `toolbar.buttons` and `type: widget` fields. |
| `pact.AdminPartialData` | Supplies the view model a controller partial template renders; never the GORM model. |
| `pact.FilterScope` | Model scopes a list filter may call, limited to an exact allow list. |
| `pact.RelationBeforeLink` | Optional controller hook that checks or fills pivot columns before a relation link is written. |
| `pact.RelationBeforeCreate` | Optional controller hook run in the write transaction before a relation manager creates a related record. |
| `pact.RelationAfterCreate` | Optional controller hook run after a relation manager creates a related record, before the commit. |
| `pact.RelationBeforeUpdate` | Optional controller hook run in the write transaction before a relation manager updates a related record. |
| `pact.RelationAfterUpdate` | Optional controller hook run after a relation manager updates a related record, before the commit. |
| `pact.RelationBeforeDelete` | Optional controller hook run in the write transaction before a relation manager deletes a related record. |
| `pact.RelationAfterDelete` | Optional controller hook run after a relation manager deletes a related record, before the commit. |
| `pact.Option` | One dropdown choice (value and label). |
## Dependencies

View File

@@ -391,6 +391,51 @@ type RelationBeforeLink interface {
RelationBeforeLink(ctx context.Context, relation string, parent, related any, pivot map[string]any) error
}
// The six relation child hooks below are optional admin-controller
// capabilities for a relation manager that creates, updates or deletes
// related records. Before hooks run inside the child write's transaction
// before the row is written; After hooks run after the write and before the
// commit. parent is the loaded parent record, or a fresh record with a zero
// key while the parent is not saved yet (deferral). child is the related
// record being written. An error rolls the write back and the admin API
// answers its opaque lifecycle error.
// RelationBeforeCreate optionally runs before a related record is created
// through a relation manager.
type RelationBeforeCreate interface {
RelationBeforeCreate(ctx context.Context, relation string, parent, child any) error
}
// RelationAfterCreate optionally runs after a related record is created
// through a relation manager, before the commit.
type RelationAfterCreate interface {
RelationAfterCreate(ctx context.Context, relation string, parent, child any) error
}
// RelationBeforeUpdate optionally runs before a related record is updated
// through a relation manager.
type RelationBeforeUpdate interface {
RelationBeforeUpdate(ctx context.Context, relation string, parent, child any) error
}
// RelationAfterUpdate optionally runs after a related record is updated
// through a relation manager, before the commit.
type RelationAfterUpdate interface {
RelationAfterUpdate(ctx context.Context, relation string, parent, child any) error
}
// RelationBeforeDelete optionally runs before a related record is deleted
// through a relation manager.
type RelationBeforeDelete interface {
RelationBeforeDelete(ctx context.Context, relation string, parent, child any) error
}
// RelationAfterDelete optionally runs after a related record is deleted
// through a relation manager, before the commit.
type RelationAfterDelete interface {
RelationAfterDelete(ctx context.Context, relation string, parent, child any) error
}
// FilterScope is the model capability a config_filter scope name may call.
// FilterScopes is the exact, case-sensitive set of names; request text never
// selects a method outside that set.