Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion public/md-articles/dotkernel/test-article.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 4 additions & 3 deletions src/App/src/Fixture/PostLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'] ?? '';
Expand Down
4 changes: 2 additions & 2 deletions src/App/src/Fixture/articles_cleaned.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
}
]
Expand Down Expand Up @@ -2875,4 +2875,4 @@
}
]
}
]
]
Original file line number Diff line number Diff line change
Expand Up @@ -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." } }
]
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block body %}
<img src="{{ asset('uploads/article/' ~ article.id ~ '/test-article.png') }}" alt="">

<p>This is a test article used for fixture and testing purposes.</p>
<p>This is a test article used for fixture and testing purposes. This article is now updated.</p>

<h2 id="user-content-faq">Frequently Asked Questions</h2>

Expand Down