feat: SEO field types and the complete SEO tab
This commit is contained in:
@@ -26,11 +26,13 @@ import (
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/compass"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
"git.golem15.com/golem15/summercms/modules/signpost"
|
||||
"git.golem15.com/golem15/summercms/modules/surf"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gocloud.dev/blob/memblob"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -44,6 +46,9 @@ const (
|
||||
pagesTypeKey = "acme-page"
|
||||
pagesModel = "acme_pages_pages"
|
||||
tagsTypeKey = "acme-tag"
|
||||
notesControllerID = "acme.pages.notes"
|
||||
notesTypeKey = "acme-note"
|
||||
notesModel = "acme_pages_notes"
|
||||
adminJWTSecret = "seo-admin-test-only-hs256-secret-key"
|
||||
siteURL = "https://www.example.test"
|
||||
)
|
||||
@@ -70,6 +75,26 @@ func (pagesController) RequiredPermissions() []string {
|
||||
|
||||
type pagesController struct{}
|
||||
|
||||
// note is the model of the tab-less acme.pages.notes form.
|
||||
type note struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
Title string `gorm:"column:title"`
|
||||
Slug string `gorm:"column:slug"`
|
||||
}
|
||||
|
||||
func (note) TableName() string { return notesModel }
|
||||
func (*note) Fillable() []string { return []string{"title", "slug"} }
|
||||
func (*note) Rules() map[string]string { return map[string]string{} }
|
||||
func (notesController) ID() string { return notesControllerID }
|
||||
func (notesController) ModelName() string { return "Note" }
|
||||
func (notesController) ConfigDir() string { return "controllers/notes" }
|
||||
func (notesController) NewRecord() any { return ¬e{} }
|
||||
func (notesController) RequiredPermissions() []string {
|
||||
return []string{pagesPermission}
|
||||
}
|
||||
|
||||
type notesController struct{}
|
||||
|
||||
// pagesPlugin is acme.pages.
|
||||
type pagesPlugin struct {
|
||||
app *backpack.App
|
||||
@@ -108,10 +133,14 @@ func (*pagesPlugin) AdminFS() fs.FS {
|
||||
"controllers/pages/config_list.yaml": &fstest.MapFile{Data: []byte("list: ~/plugins/acme/pages/models/page/columns.yaml\nmodelClass: Page\nrecordsPerPage: 20\ntoolbar:\n buttons: [create]\n")},
|
||||
"models/page/columns.yaml": &fstest.MapFile{Data: []byte("columns:\n title:\n label: Title\n")},
|
||||
"models/page/fields.yaml": &fstest.MapFile{Data: []byte(pagesFields)},
|
||||
"controllers/notes/config_form.yaml": &fstest.MapFile{Data: []byte("name: Note\nform: ~/plugins/acme/pages/models/note/fields.yaml\nmodelClass: Note\n")},
|
||||
"controllers/notes/config_list.yaml": &fstest.MapFile{Data: []byte("list: ~/plugins/acme/pages/models/note/columns.yaml\nmodelClass: Note\nrecordsPerPage: 20\ntoolbar:\n buttons: [create]\n")},
|
||||
"models/note/columns.yaml": &fstest.MapFile{Data: []byte("columns:\n title:\n label: Title\n")},
|
||||
"models/note/fields.yaml": &fstest.MapFile{Data: []byte("fields:\n title:\n label: Title\n type: text\n slug:\n label: Slug\n type: text\n")},
|
||||
}
|
||||
}
|
||||
func (*pagesPlugin) AdminControllers() []pact.AdminController {
|
||||
return []pact.AdminController{pagesController{}}
|
||||
return []pact.AdminController{pagesController{}, notesController{}}
|
||||
}
|
||||
func (*pagesPlugin) Permissions() []pact.Permission {
|
||||
return []pact.Permission{{Code: pagesPermission, Tab: "Pages", Label: "Manage pages"}}
|
||||
@@ -128,9 +157,14 @@ func (*pagesPlugin) Migrations() []*gormigrate.Migration {
|
||||
published BOOLEAN NOT NULL DEFAULT FALSE
|
||||
)`).Error
|
||||
},
|
||||
}, {
|
||||
ID: "acme_notes_create",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.Exec(`CREATE TABLE acme_pages_notes (id BIGSERIAL PRIMARY KEY, title TEXT NOT NULL DEFAULT '', slug TEXT NOT NULL DEFAULT '')`).Error
|
||||
},
|
||||
}}
|
||||
}
|
||||
func (*pagesPlugin) Models() []any { return []any{page{}} }
|
||||
func (*pagesPlugin) Models() []any { return []any{page{}, note{}} }
|
||||
|
||||
// db is the write transaction in ctx, else the application database.
|
||||
func (p *pagesPlugin) db(ctx context.Context) *gorm.DB {
|
||||
@@ -154,7 +188,31 @@ func (p *pagesPlugin) describe(row page) pact.ContentRecord {
|
||||
}
|
||||
|
||||
func (p *pagesPlugin) MenuItemTypes() []pact.MenuItemType {
|
||||
return []pact.MenuItemType{p.pageType(), p.tagType()}
|
||||
return []pact.MenuItemType{p.pageType(), p.tagType(), p.noteType()}
|
||||
}
|
||||
|
||||
// noteType is acme-note (/notes/:slug), edited in a form without tabs.
|
||||
func (p *pagesPlugin) noteType() pact.MenuItemType {
|
||||
return pact.MenuItemType{
|
||||
Key: notesTypeKey,
|
||||
Label: "Notes",
|
||||
DefaultPattern: "/notes/:slug",
|
||||
Placeholders: []string{"slug"},
|
||||
Resolve: func(context.Context, pact.MenuItemResolve) (*pact.ResolvedMenuItem, error) {
|
||||
return nil, nil
|
||||
},
|
||||
Content: &pact.ContentDescriptor{
|
||||
Controller: notesControllerID,
|
||||
Model: notesModel,
|
||||
Describe: func(ctx context.Context, in pact.ContentDescribe) (*pact.ContentRecord, error) {
|
||||
var rows []note
|
||||
if err := p.db(ctx).Where("id = ?", in.ID).Limit(1).Find(&rows).Error; err != nil || len(rows) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &pact.ContentRecord{ID: in.ID, Published: true, Locales: []pact.ContentLocale{{Locale: "en", URL: "/notes/" + rows[0].Slug, Title: rows[0].Title}}}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// tagType is acme-tag (/pages/tag/:slug): a content type without an admin
|
||||
@@ -307,6 +365,11 @@ func bootPlugins(t *testing.T, ids []string, extraEnv ...string) *testEnv {
|
||||
if err := lagoon.Publish(app, sqlDB, gdb); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bucket := memblob.OpenBucket(nil)
|
||||
t.Cleanup(func() { _ = bucket.Close() })
|
||||
if err := attach.Publish(app, bucket); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plugins, err := party.Activate(app, ids)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
Reference in New Issue
Block a user