# pact Capability interfaces that compiled plugins implement to contribute routes, config, migrations, middleware, commands, admin screens, translations, mail templates, jobs and scheduled commands. `import "git.golem15.com/golem15/summercms/modules/pact"` ## Overview `pact` is the contract layer between plugins and the framework. It holds interfaces and plain data types, with no behaviour of its own. A plugin opts into a capability by implementing one of the `Has*` interfaces, and the framework package that owns the capability discovers it with a type assertion ([party](../party/README.md) for config, [surf](../surf/README.md) for routes and middleware, [lagoon](../lagoon/README.md) for migrations, [cabana](../cabana/README.md) for admin controllers, [conga](../conga/README.md) for jobs and schedules). It replaces the `register*()` methods of a WinterCMS PluginBase (`registerPermissions`, `registerNavigation`, `registerSettings` and so on) with small, separately implementable interfaces. ## Features - Plugin capability interfaces: `pact.HasRoutes`, `pact.HasConfig`, `pact.HasMigrations`, `pact.HasCommands`, `pact.HasModels`, `pact.HasJobs`, `pact.HasSchedule`, `pact.HasLang`, `pact.HasLangOverrides` and `pact.HasMailTemplates`. - HTTP contracts: the `pact.Router` group builder (implemented by surf), the `pact.Middleware` type, and named, parameterized (`name:param`) and house-envelope middleware through `pact.HasMiddleware`, `pact.HasMiddlewareFactories` and `pact.HasHouseMiddleware`. - 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`), 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. - `pact.HasModels` and `pact.OptionalMessage` are declared for plugins to implement, but no framework package consumes them yet. ## Usage A plugin declares its capabilities by implementing the interfaces and asserting them at compile time: ```go package blog import ( "net/http" "git.golem15.com/golem15/summercms/modules/pact" ) var ( _ pact.HasRoutes = (*Plugin)(nil) _ pact.HasPermissions = (*Plugin)(nil) ) type Plugin struct{} func (p *Plugin) Routes(r pact.Router) error { r.Group("/api/blog", []string{"auth"}, func(r pact.Router) { r.Get("/posts", listPosts) r.Get("/posts/{id}", showPost) r.Where("id", "[0-9]+") }) return nil } func (p *Plugin) Permissions() []pact.Permission { return []pact.Permission{ {Code: "acme.blog.access_posts", Tab: "Blog", Label: "Manage posts"}, } } func listPosts(w http.ResponseWriter, r *http.Request) {} func showPost(w http.ResponseWriter, r *http.Request) {} ``` A plugin schedules one of its registered commands by implementing `pact.HasSchedule`: ```go var _ pact.HasSchedule = (*Plugin)(nil) func (p *Plugin) Schedule() []pact.ScheduledCommand { return []pact.ScheduledCommand{ {Command: "blog:prune-drafts", Cadence: pact.Daily()}, {Command: "blog:ping", Args: []string{"--quiet"}, Cadence: pact.Every(15 * time.Minute)}, } } ``` ## API reference | Identifier | Description | |------------|-------------| | `pact.Router` | Laravel-style route group builder (`pact.Router.Group`, `pact.Router.GroupRaw`, one method per HTTP verb, `pact.Router.Where`, `pact.Router.WhereIn`); implemented by surf. | | `pact.HasRoutes` | Declares HTTP routes on a `pact.Router`. | | `pact.Middleware` | A named `func(http.Handler) http.Handler` wrapper. | | `pact.HasMiddleware` | Registers named middleware. | | `pact.HasMiddlewareFactories` | Registers parameterized middleware resolved from `name:param` at wrap time. | | `pact.HasHouseMiddleware` | Registers middleware tagged as envelope and error handling, which raw groups refuse. | | `pact.HasConfig` | Ships default YAML config, merged under the plugin ID. | | `pact.HasMigrations` | Ships an ordered gormigrate set, run with a per-plugin history table. | | `pact.HasCommands` | Contributes bonfire console commands to the application binary. | | `pact.HasModels` | Exposes GORM models. | | `pact.Job` | Background unit of work that receives `pact.JobArgs`. | | `pact.HasJobs` | Registers background jobs. | | `pact.HasSchedule` | Declares recurring console commands (`Schedule() []pact.ScheduledCommand`); conga workers run them. | | `pact.ScheduledCommand` | One schedule entry: `Command`, `Args` and `Cadence`. | | `pact.Cadence` | Opaque run frequency with `IsZero`, `Interval` (24h for daily cadences) and `At` (hour and minute of a daily cadence). | | `pact.Daily` | Cadence at 00:00 every day in the app timezone (Laravel `->daily()`). | | `pact.DailyAt` | Cadence at a given hour and minute every day in the app timezone. | | `pact.Every` | Cadence at every multiple of an interval since local midnight; the interval must be at least 1s and divide 24h. | | `pact.HasLang` | Ships translation YAML under `lang//.yaml`. | | `pact.HasLangOverrides` | Replaces or adds translations of any loaded namespace, including the framework's own. | | `pact.HasMailTemplates` | Ships mail templates and layout aliases. | | `pact.Permission` | One backend permission entry (code, tab, label, roles). | | `pact.NavigationItem` | One backend navigation entry, with an optional side menu. | | `pact.SettingsItem` | One settings screen entry. | | `pact.AdminController` | Admin controller identity: ID, model name and YAML config directory. | | `pact.AdminAssets` | Embedded tree of the plugin's admin YAML. | | `pact.AdminRecordSource` | Supplies a new model record for the generic admin handlers. | | `pact.AdminClientAssets` | Declares a controller's admin JS (`AdminJS`) and CSS (`AdminCSS`) files, paths under the plugin's `assets/` directory. | | `pact.AdminAction` | One named controller action: name, label, extra permissions and the Go `Run` function. | | `pact.AdminActionInput` | What an action receives: widget field, optional record id and scoped record, and the fill snapshot. | | `pact.AdminActionResult` | What an action returns: a message for the toast and the fill write-back values. | | `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 - SummerCMS modules: [bonfire](../bonfire/README.md) (the command type in `pact.HasCommands`). - Third-party: `github.com/go-gormigrate/gormigrate/v2`, `gorm.io/gorm`. - Standard library: `context`, `io/fs`, `net/http`, `time`. ## Testing ```sh go test ./modules/pact/... ``` The tests are compile-time interface checks and need no external services.