fix(17): WR-04 resolve journal-post only for published posts
journal-post resolved any post by id, so a menu entry or sitemap row pointing at a draft or future post published its title and slug on the anonymous menu API and in sitemap.xml. resolvePost now applies the published filter; the reference list still offers drafts so editors can wire a menu before the post goes live.
This commit is contained in:
@@ -240,7 +240,7 @@ Editor images are uploaded by the admin through the field's upload route and sto
|
||||
|-----|-------|-----------------|---------------|-------------|
|
||||
| `journal-category` | A category from the category tree | `/blog/category/:slug` | | The category's URL; with nesting, its subcategories as sub-items, each once with its URL. |
|
||||
| `all-journal-categories` | Nothing | `/blog/category/:slug` | `journal-category` | No URL of its own; every category ordered by name as items. |
|
||||
| `journal-post` | A post (listed by title) | `/blog/:slug` | | The post's URL, published or not. |
|
||||
| `journal-post` | A post (listed by title, drafts included) | `/blog/:slug` | | The post's URL while it is published with a past publish date; nothing for a draft or a future post, so the menu entry or sitemap row appears once the post goes live. PHP resolved any post. |
|
||||
| `all-journal-posts` | Nothing | `/blog/:slug` | `journal-post` | No URL of its own; the published posts ordered by title as items. |
|
||||
| `category-journal-posts` | A category from the category tree | `/blog/:slug` | `journal-post` | No URL of its own; the published posts of the category and its descendants ordered by title as items. |
|
||||
|
||||
|
||||
@@ -65,7 +65,11 @@ var (
|
||||
// - all-journal-categories: a list without a URL of its own whose items are
|
||||
// every category ordered by name.
|
||||
// - journal-post: one post picked by id (References lists every post by
|
||||
// title); it resolves to the post's URL without a published filter.
|
||||
// title, drafts included, so a menu can be wired before the post goes
|
||||
// live); it resolves to the post's URL only while the post is published
|
||||
// with a past publish date, else to nothing. PHP resolved any post, but
|
||||
// its result now reaches the anonymous menu API and sitemap.xml, so a
|
||||
// draft's title and slug would leak.
|
||||
// - all-journal-posts: a list whose items are the published posts ordered
|
||||
// by title.
|
||||
// - category-journal-posts: a list whose items are the published posts of
|
||||
@@ -185,7 +189,8 @@ func (m menuTypes) postReferences(ctx context.Context) ([]pact.MenuItemReference
|
||||
}
|
||||
|
||||
// resolvePost resolves one journal-post item. An unknown or missing
|
||||
// reference resolves to nothing, as PHP returned null.
|
||||
// reference resolves to nothing, as PHP returned null, and so does a post
|
||||
// that is not published (publishedPosts).
|
||||
func (m menuTypes) resolvePost(ctx context.Context, in pact.MenuItemResolve) (*pact.ResolvedMenuItem, error) {
|
||||
id, ok := menuReferenceID(in.Reference)
|
||||
if !ok {
|
||||
@@ -196,7 +201,7 @@ func (m menuTypes) resolvePost(ctx context.Context, in pact.MenuItemResolve) (*p
|
||||
return nil, err
|
||||
}
|
||||
var posts []models.Post
|
||||
if err := db.Where("id = ?", id).Limit(1).Find(&posts).Error; err != nil {
|
||||
if err := publishedPosts(db).Where("id = ?", id).Limit(1).Find(&posts).Error; err != nil {
|
||||
return nil, fmt.Errorf("golem15.journal: menu item post %d: %w", id, err)
|
||||
}
|
||||
if len(posts) == 0 {
|
||||
|
||||
@@ -194,6 +194,10 @@ func TestMenuItemTypesSmoke(t *testing.T) {
|
||||
if got, err := reg.Resolve(ctx, classes.MenuTypePost, "999999", "", false, ""); err != nil || got != nil {
|
||||
t.Fatalf("unknown post = %+v %v", got, err)
|
||||
}
|
||||
// A draft is offered as a reference but resolves to nothing (WR-04).
|
||||
if got, err := reg.Resolve(ctx, classes.MenuTypePost, id(draft.ID), "", false, ""); err != nil || got != nil {
|
||||
t.Fatalf("draft post = %+v %v", got, err)
|
||||
}
|
||||
|
||||
// journal-category: references are the tree; nesting returns the
|
||||
// subtree, each child once with its first-locale URL.
|
||||
|
||||
@@ -94,9 +94,15 @@ func TestMenuItemTypes(t *testing.T) {
|
||||
t.Fatalf("category-journal-posts references = %+v %v", same, err)
|
||||
}
|
||||
|
||||
// journal-post resolves without a published filter.
|
||||
if got := resolve(t, reg, classes.MenuTypePost, id(seed.draft.ID), "", false); got.URL != "/blog/draft" {
|
||||
t.Fatalf("draft post = %+v", got)
|
||||
// journal-post resolves published posts only (WR-04): a draft or a
|
||||
// future post resolves to nothing.
|
||||
for _, p := range []models.Post{seed.draft, seed.future} {
|
||||
if got, err := reg.Resolve(ctx, classes.MenuTypePost, id(p.ID), "", false, ""); err != nil || got != nil {
|
||||
t.Fatalf("unpublished post %s = %+v %v", p.Slug, got, err)
|
||||
}
|
||||
}
|
||||
if got := resolve(t, reg, classes.MenuTypePost, id(seed.zebra.ID), "", false); got.URL != "/blog/zebra" {
|
||||
t.Fatalf("published post = %+v", got)
|
||||
}
|
||||
// Nesting returns the subtree at every depth; without nesting no items.
|
||||
got := resolve(t, reg, classes.MenuTypeCategory, id(seed.beta.ID), "", true)
|
||||
@@ -135,10 +141,6 @@ func TestMenuItemTypes(t *testing.T) {
|
||||
if got.URL != "/alpha/2026/03/04/apple" {
|
||||
t.Fatalf("published post url = %q", got.URL)
|
||||
}
|
||||
got = resolve(t, reg, classes.MenuTypePost, id(seed.draft.ID), "/:category/:year/:slug", false)
|
||||
if got.URL != "/beta/:year/draft" {
|
||||
t.Fatalf("draft post url = %q", got.URL)
|
||||
}
|
||||
|
||||
// Unknown or malformed references resolve to nothing.
|
||||
for _, key := range []string{classes.MenuTypePost, classes.MenuTypeCategory, classes.MenuTypeCategoryPosts} {
|
||||
|
||||
Reference in New Issue
Block a user