diff --git a/README.md b/README.md index 78c4a3ec..17b0a132 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,16 @@ Add a new entry to the category's `articles` array in `src/App/src/Fixture/artic `opengraph_img` is the image shown as the social-media (Twitter/OG) preview card. Leave it `null` to fall back to the site-wide default image (`config/autoload/local.php` → `application.meta.image`). To set one, put the image file at `public/opengraph/article/your-image.png` and reference it here as a root-relative path: `"opengraph_img": "/opengraph/article/your-image.png"`. This is unrelated to the in-article images described in step 3 — it is placed by hand, not by `bin/create-uploads-dir`. -**Important:** you can set `"post_status": "draft"` instead of `"publish"` to keep an article out of sight — anything other than `publish`/`private` is treated as a draft by `PostLoader`, and `getPublishedPosts()` (used by both `bin/generate-feed` and `bin/sitemap`) only returns posts with `publish` status. After changing it, follow the same steps: re-run `bin/doctrine-fixtures`, then `bin/generate-feed` and `bin/sitemap`. This applies generally, not just to status changes — **any** edit to `articles_cleaned.json` (title, excerpt, status, date, etc.) needs `bin/doctrine-fixtures` re-run to update the database, followed by re-running the 3 generators in step 4 so `feed.xml`/`sitemap.xml`/`llms-full.txt` reflect it. One exception: `bin/generate-llms-full` reads straight from the `.md` files on disk and does **not** check `post_status` at all — a `draft` article's `.md` file will still be included in `llms-full.txt` unless you also remove or rename that file. +**`post_status` values.** `PostLoader` (`src/App/src/Fixture/PostLoader.php`) only recognizes 3 JSON strings — anything else (including the literal `"draft"`) falls through to `Draft`: + +| JSON value | Maps to | Behavior | +| --- | --- | --- | +| `"publish"` | `PostStatusEnum::Published` | The only status shown in listings, the RSS feed, and the sitemap (`getPublishedPosts()` and every category/tag/author query filter on `Published` only). | +| `"private"` | `PostStatusEnum::Private` | Not published: excluded from listings/feed/sitemap same as a draft, and its own page returns `404` — there is currently no route or view that treats `Private` differently from `Draft`. | +| `"archived"` | `PostStatusEnum::Archived` | Not published: excluded from listings/feed/sitemap, but its own page returns `410 Gone` instead of `404` — use this for content that existed and was intentionally removed (outdated articles, leftover test content, etc.), as opposed to content that was never public. | +| anything else (including `"draft"`) | `PostStatusEnum::Draft` | Not published: excluded from listings/feed/sitemap, its own page returns `404`. This is also the fallback for typos in `post_status`. | + +After changing `post_status`, follow the same steps: re-run `bin/doctrine-fixtures`, then `bin/generate-feed` and `bin/sitemap`. This applies generally, not just to status changes — **any** edit to `articles_cleaned.json` (title, excerpt, status, date, etc.) needs `bin/doctrine-fixtures` re-run to update the database, followed by re-running the 3 generators in step 4 so `feed.xml`/`sitemap.xml`/`llms-full.txt` reflect it. One exception: `bin/generate-llms-full` reads straight from the `.md` files on disk and does **not** check `post_status` at all — a non-published article's `.md` file will still be included in `llms-full.txt` unless you also remove or rename that file. ## 2. Create the templates @@ -64,6 +73,32 @@ These three have no ordering dependency on each other, only on step 3 being done None of this is wired into an automated deploy pipeline in this repository — there is no `deploy` script or CI job that runs these `bin/` scripts. `public/feed.xml`, `public/sitemap.xml`, and `public/llms-full.txt` are committed generated artifacts, so re-running these scripts leaves them modified in git until committed. +## How to update an article + +Steps to edit an existing article (change its status, text, or both) and get the change live. + +1. **Edit the article's data.** Find its entry under the category's `articles` array in `src/App/src/Fixture/articles_cleaned.json` and change whatever needs updating: `post_title`, `excerpt`, `tl_dr`, `post_status`, `isObsolete`, etc. `PostLoader` matches the existing article by slug (derived from `post_title`), so as long as you don't change the title, it updates the same `Post` row instead of creating a new one. + - See the `post_status` values table in step 1 above for what each status does — e.g. `"archived"` is the right choice for content that existed and was intentionally removed (outdated content, a leftover test article, etc.), as it serves `410 Gone` instead of `404`. +2. **Edit the content, if the body itself changed.** Update the matching files for that article's category/slug: + - `public/md-articles/{category-slug}/{article-slug}.md` + - `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig` + - `src/Blog/templates/page/JSON-LD/{category-slug}/{article-slug}.jsonld.twig` (only if it has hardcoded text outside of `article.*`/`meta.*` variables — most of its fields pull straight from the database and update automatically) +3. **Re-run the same commands as step 3 and step 4 above** (`bin/doctrine-fixtures`, then `bin/generate-feed` / `bin/sitemap` / `bin/generate-llms-full`) so the database and the generated artifacts reflect the change. `bin/create-uploads-dir` only needs to run again if you added a new image. + +## How to move an article to a different category + +An article's category isn't a field on the article itself — it's whichever top-level category object its entry sits under in `articles_cleaned.json`. Moving it is a structural move, not a value change, and the article's page/JSON-LD templates are resolved dynamically off the *current* category at render time (`GetPostResourceHandler` renders `page::blog-resource/{article.category.slug}/{article.slug}`, and the layout includes `@jsonld/{article.category.slug}/{article.slug}.jsonld.twig`) — there's no fallback if a file is missing at that path, so skipping any of the steps below leaves the article **404**ing at both the old and the new URL. + +1. **Cut the article's JSON object from its current category's `articles` array and paste it into the target category's `articles` array**, in `src/App/src/Fixture/articles_cleaned.json`. The target category must already exist as a top-level entry. Nothing else in the object needs to change — `PostLoader` matches the existing `Post` by slug and updates its category on the next run. +2. **Physically move the three per-article files** from the old `{category-slug}` folder to the new one, keeping the same filename: + - `public/md-articles/{old-category-slug}/{article-slug}.md` → `public/md-articles/{new-category-slug}/{article-slug}.md` + - `src/Blog/templates/page/blog-resource/{old-category-slug}/{article-slug}.html.twig` → `.../{new-category-slug}/{article-slug}.html.twig` + - `src/Blog/templates/page/JSON-LD/{old-category-slug}/{article-slug}.jsonld.twig` → `.../{new-category-slug}/{article-slug}.jsonld.twig` +3. **Check the moved `.html.twig` for a hardcoded `json_ld` block override.** Most articles leave that block untouched, so it resolves dynamically via the layout — but a few hardcode a literal path (e.g. `{% block json_ld %}{{ include('@jsonld/some-category/some-slug.jsonld.twig') }}{% endblock %}`). If yours does, update that literal path to the new category too. +4. **Re-run `bin/doctrine-fixtures`**, then the 3 generators (`bin/generate-feed`, `bin/sitemap`, `bin/generate-llms-full`), same as any other update. + +Note: this changes the article's URL (`/{categorySlug}/{slug}/`), so the old URL will start 404ing — there is no redirect set up for a category move in this app. + ## 5. Scheduled jobs (cron) - **`bin/generate-packages`** — the only script here actually wired into a cron job. It rebuilds the Dotkernel packages listing from the GitHub organisation, which changes independently of this repo, so it runs on a schedule instead of at deploy time: diff --git a/public/md-articles/dotkernel/test-article.md b/public/md-articles/dotkernel/test-article.md index 038b11a8..610f7998 100644 --- a/public/md-articles/dotkernel/test-article.md +++ b/public/md-articles/dotkernel/test-article.md @@ -12,7 +12,7 @@ language: "en" ## TL;DR -This is a test article added to the dotkernel category for testing purposes. +This is a test article added to the dotkernel category for testing purposes. This article is now updated. ## Overview diff --git a/src/App/src/Fixture/PostLoader.php b/src/App/src/Fixture/PostLoader.php index 02e4c574..4ebd08df 100644 --- a/src/App/src/Fixture/PostLoader.php +++ b/src/App/src/Fixture/PostLoader.php @@ -67,9 +67,10 @@ public function load(ObjectManager $manager): void } $status = match ($articleData['post_status']) { - 'publish' => PostStatusEnum::Published, - 'private' => PostStatusEnum::Private, - default => PostStatusEnum::Draft, + 'publish' => PostStatusEnum::Published, + 'private' => PostStatusEnum::Private, + 'archived' => PostStatusEnum::Archived, + default => PostStatusEnum::Draft, }; $rawDate = $articleData['post_date'] ?? ''; diff --git a/src/App/src/Fixture/articles_cleaned.json b/src/App/src/Fixture/articles_cleaned.json index 642f3cb8..b8e69604 100644 --- a/src/App/src/Fixture/articles_cleaned.json +++ b/src/App/src/Fixture/articles_cleaned.json @@ -986,7 +986,7 @@ "isObsolete": false, "opengraph_img": "/opengraph/article/twitter-card-symfony-mailer.png", "excerpt": "This is a test article used for fixture and testing purposes.", - "tl_dr": "This is a test article added to the dotkernel category for testing purposes.", + "tl_dr": "This is a test article added to the dotkernel category for testing purposes. This article is now updated.", "tags": [] } ] @@ -2875,4 +2875,4 @@ } ] } -] \ No newline at end of file +] diff --git a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig b/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig index 9ab8095f..a6d6888b 100644 --- a/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig +++ b/src/Blog/templates/page/JSON-LD/dotkernel/test-article.jsonld.twig @@ -41,7 +41,7 @@ "@type": "FAQPage", "@id": "{{ app.url ~ path('page::blog-resource', {categorySlug: article.category.slug, slug: article.slug}) }}#faq", "mainEntity": [ - { "@type": "Question", "name": "What is this article for?", "acceptedAnswer": { "@type": "Answer", "text": "This is a test article added to the Dotkernel category for fixture and testing purposes." } } + { "@type": "Question", "name": "What is this article for?", "acceptedAnswer": { "@type": "Answer", "text": "This is a test article added to the Dotkernel category for fixture and testing purposes. This article is now updated." } } ] } ] diff --git a/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig b/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig index 012bb94e..bac62a68 100644 --- a/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig +++ b/src/Blog/templates/page/blog-resource/dotkernel/test-article.html.twig @@ -3,7 +3,7 @@ {% block body %} -

This is a test article used for fixture and testing purposes.

+

This is a test article used for fixture and testing purposes. This article is now updated.

Frequently Asked Questions