diff --git a/README.md b/README.md index 97300cc..6d1f997 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/classes/menu_item_types.go b/classes/menu_item_types.go index a68e2f9..a1a7672 100644 --- a/classes/menu_item_types.go +++ b/classes/menu_item_types.go @@ -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 { diff --git a/menu_item_types_smoke_test.go b/menu_item_types_smoke_test.go index f38c814..72a5a49 100644 --- a/menu_item_types_smoke_test.go +++ b/menu_item_types_smoke_test.go @@ -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. diff --git a/menu_item_types_test.go b/menu_item_types_test.go index 65697f8..a1fe39a 100644 --- a/menu_item_types_test.go +++ b/menu_item_types_test.go @@ -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} {