feat(11.1-05): add the walkthrough's admin controller, publish command and published_at migration

- make:admin-controller, make:command and make:migration output, finished:
  the Posts controller serves models.Post behind acme.blog.access_posts,
  WinterCMS-style form and list YAML embedded through pact.AdminAssets,
  blog:publish sets published_at by slug with a bound parameter
- the posts route lists published posts only, newest first
- Docker tests migrate an ICU pl-PL database, roll back published_at, serve
  the route and run blog:publish with a published and an opened database
- the page gains the admin controller, console command and added-column
  sections
This commit is contained in:
Jakub Zych
2026-09-30 23:35:44 +02:00
parent 41a3190956
commit dd82b8a2ad
17 changed files with 1223 additions and 59 deletions

View File

@@ -9,6 +9,7 @@ import (
"git.golem15.com/golem15/summercms/docs/examples/blog"
"git.golem15.com/golem15/summercms/docs/examples/blog/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bonfire"
"git.golem15.com/golem15/summercms/modules/compass"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/party"
@@ -16,8 +17,8 @@ import (
)
// activate boots acme.blog the way the generated main does: an application
// config directory with the required HTTP limits, then party.Activate with
// the plugin ID.
// config directory with the required HTTP limits and admin secret, then
// party.Activate with the plugin ID.
func activate(t *testing.T) (*backpack.App, party.Plugin) {
t.Helper()
dir := t.TempDir()
@@ -29,6 +30,10 @@ func activate(t *testing.T) (*backpack.App, party.Plugin) {
if err != nil {
t.Fatal(err)
}
// The admin controller makes the admin API mount, which needs a secret.
if err := cfg.Set("admin.jwt.secret", "test-only-secret-with-at-least-32-bytes"); err != nil {
t.Fatal(err)
}
app := backpack.New(cfg)
plugins, err := party.Activate(app, []string{"acme.blog"})
if err != nil {
@@ -76,6 +81,23 @@ func TestPluginActivates(t *testing.T) {
if len(hasMigrations.Migrations()) == 0 {
t.Error("Migrations is empty")
}
hasCommands, ok := p.(pact.HasCommands)
if !ok {
t.Fatal("plugin does not implement pact.HasCommands")
}
if !slices.ContainsFunc(hasCommands.Commands(), func(c bonfire.Command) bool { return c.Name == "blog:publish" }) {
t.Error("Commands does not include blog:publish")
}
hasAdmin, ok := p.(pact.HasAdminControllers)
if !ok {
t.Fatal("plugin does not implement pact.HasAdminControllers")
}
if ctls := hasAdmin.AdminControllers(); len(ctls) != 1 || ctls[0].ID() != "acme.blog.posts" {
t.Errorf("AdminControllers = %v, want acme.blog.posts", ctls)
}
if _, ok := p.(pact.AdminAssets); !ok {
t.Error("plugin does not implement pact.AdminAssets")
}
if got := app.Config.Int("acme.blog.per_page"); got != 15 {
t.Errorf("acme.blog.per_page = %d, want the plugin default 15", got)
}