Skip to content

Commit 6ccebf8

Browse files
committed
added article - dk vs lumen, slim
Signed-off-by: bidi47 <bidi@apidemia.com>
1 parent b9eb7a4 commit 6ccebf8

6 files changed

Lines changed: 1645 additions & 7271 deletions

File tree

‎README.md‎

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ Steps to publish a new article and regenerate the public artifacts that depend o
44

55
## 1. Add the article data
66

7+
### Create a new article record
8+
79
Add a new entry to the category's `articles` array in `src/App/src/Fixture/articles_cleaned.json`:
810

911
```json
1012
{
1113
"post_title": "Your article title",
1214
"post_date": "YYYY-MM-DD HH:MM:SS",
13-
"post_status": "publish",
15+
"post_status": "published",
1416
"author": {
1517
"display_name": "admin",
1618
"github": "arhimede"
@@ -32,26 +34,23 @@ Add a new entry to the category's `articles` array in `src/App/src/Fixture/artic
3234

3335
`tags` can be left empty, but adding tags that describe the article's subject is useful - they show up on the article page and back its tag-resource pages.
3436

35-
**`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`:
36-
37-
| JSON value | Maps to | Behavior |
38-
| --- | --- | --- |
39-
| `"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). |
40-
| `"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`. |
41-
| `"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. |
42-
| 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`. |
37+
**`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`:
4338

44-
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 4 generators in step 4 so `feed.xml`/`sitemap.xml`/`llms.txt`/`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.
39+
| JSON value | Maps to | Behavior |
40+
|-------------------------------------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
41+
| `"published"` | `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). |
42+
| `"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`. |
43+
| `"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. |
44+
| 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`. |
4545

46-
## 2. Create the templates
46+
## 2. Create the markdown file
4747

48-
- `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig` - the page body, extending `@layout/blog-post.html.twig`.
49-
- `src/Blog/templates/page/JSON-LD/{category-slug}/{article-slug}.jsonld.twig` - the `@graph` of `TechArticle` + `BreadcrumbList` + `FAQPage` structured data.
50-
- `public/md-articles/{category-slug}/{article-slug}.md` - the markdown version, with YAML front matter (`title`, `description`, `author`, `date_published`, `canonical_url`, `category`, `language`) followed by the article body (`TL;DR`, sections, `FAQ`). This feeds `llms-full.txt`.
48+
The filename is in this format: `public/md-articles/{category-slug}/{article-slug}.md`.
49+
The file contains the markdown with YAML front matter (`title`, `description`, `author`, `date_published`, `canonical_url`, `category`, `language`) followed by the article body (`TL;DR`, sections, `FAQ`).
50+
This feeds `llms-full.txt`.
5151

52-
Copy an existing set of these three files in the same category as a starting point, to match the established structure (FAQ block matching the `FAQPage` entries, etc.).
53-
54-
If the article body uses images (via `asset('uploads/article/' ~ article.id ~ '/filename.png')` in the `.html.twig`), just drop the image file anywhere under `public/uploads` - `bin/create-uploads-dir` (step 4) finds it by filename and copies it to the right place. No manual path/folder creation needed.
52+
If the article body uses images (via `asset('uploads/article/' ~ article.id ~ '/filename.png')` in the `.html.twig`), just drop the image file anywhere under `public/uploads` - `bin/create-uploads-dir` (step 4) finds it by filename and copies it to the right place.
53+
No manual path/folder creation needed.
5554

5655
## 3. At deploy - run in this order
5756

@@ -63,6 +62,8 @@ php bin/create-uploads-dir
6362
- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity (with its database-generated UUID) for the new article.
6463
- `bin/create-uploads-dir` must run *after* it - it resolves the post by slug to get that UUID, creates `public/uploads/article/{post-id}/`, and copies each image referenced in the `.html.twig` there from wherever it already lives under `public/uploads`.
6564

65+
`bin/doctrine-fixtures` isn't only for new articles - it matches existing posts by slug, so re-running it after editing any field on an existing entry (`post_title`, `excerpt`, `post_status`, etc.) updates that same `Post` row instead of creating a duplicate.
66+
6667
## 4. Regenerate the public artifacts - any order
6768

6869
```shell
@@ -74,10 +75,13 @@ php bin/generate-llms-full
7475

7576
- `bin/generate-feed` rewrites `public/feed.xml` from the published posts in the database.
7677
- `bin/sitemap` rewrites `public/sitemap.xml` from the published posts in the database.
77-
- `bin/generate-llms` rewrites `public/llms.txt`, the short curated index (one line per article, grouped by category) — built from the published posts in the database, grouped by category (known categories first, in a fixed order, then any remaining categories by post count) and sorted alphabetically by title within each category. Requires the `llms.sourceDir` / `llms.indexFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, same as for `bin/generate-llms-full` below.
78-
- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path, then appending each `public/*.md` file — the markdown versions of the static pages. Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, and omitting it leaves the page sections out.
78+
- `bin/generate-llms` rewrites `public/llms.txt`, the short curated index (one line per article, grouped by category) - built from the published posts in the database, grouped by category (known categories first, in a fixed order, then any remaining categories by post count) and sorted alphabetically by title within each category. Requires the `llms.sourceDir` / `llms.indexFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, same as for `bin/generate-llms-full` below.
79+
- `bin/generate-llms-full` rewrites `public/llms-full.txt` by concatenating `public/md-articles/index.md` and every other `public/md-articles/*/*.md` file, sorted by path, then appending each `public/*.md` file - the markdown versions of the static pages.
80+
Requires the `llms.sourceDir` / `llms.outputFile` keys in `config/autoload/local.php` (see `local.php.dist`); the `llms.pagesDir` key is optional, and omitting it leaves the page sections out.
81+
It reads straight from the `.md` files on disk and does **not** check `post_status` - a non-published article's `.md` file will still be included unless you also remove or rename that file.
7982

8083
These four have no ordering dependency on each other, only on step 3 being done first.
84+
Re-run all four after *any* edit to `articles_cleaned.json` (title, excerpt, status, date, etc.), not just when adding a new article - otherwise the committed `feed.xml`/`sitemap.xml`/`llms.txt`/`llms-full.txt` drift from the database.
8185

8286
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. **This must be run manually as part of every deploy** whenever `articles_cleaned.json` changed since the last deploy. `public/feed.xml`, `public/sitemap.xml`, `public/llms.txt`, and `public/llms-full.txt` are committed generated artifacts, so re-running these scripts leaves them modified in git until committed.
8387

@@ -86,26 +90,26 @@ None of this is wired into an automated deploy pipeline in this repository - the
8690
Steps to edit an existing article (change its status, text, or both) and get the change live.
8791

8892
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.
89-
- 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`.
93+
- 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`.
9094
2. **Edit the content, if the body itself changed.** Update the matching files for that article's category/slug:
9195
- `public/md-articles/{category-slug}/{article-slug}.md`
9296
- `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig`
93-
- `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)
97+
- `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)
9498
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.
9599

96100
## How to move an article to a different category
97101

98-
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.
102+
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.
99103

100-
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.
104+
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.
101105
2. **Physically move the three per-article files** from the old `{category-slug}` folder to the new one, keeping the same filename:
102106
- `public/md-articles/{old-category-slug}/{article-slug}.md` → `public/md-articles/{new-category-slug}/{article-slug}.md`
103107
- `src/Blog/templates/page/blog-resource/{old-category-slug}/{article-slug}.html.twig` → `.../{new-category-slug}/{article-slug}.html.twig`
104108
- `src/Blog/templates/page/JSON-LD/{old-category-slug}/{article-slug}.jsonld.twig` → `.../{new-category-slug}/{article-slug}.jsonld.twig`
105-
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.
109+
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.
106110
4. **Re-run `bin/doctrine-fixtures`**, then the 3 generators (`bin/generate-feed`, `bin/sitemap`, `bin/generate-llms-full`), same as any other update.
107111

108-
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.
112+
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.
109113

110114
## 5. Scheduled jobs (cron)
111115

@@ -116,6 +120,6 @@ Note: this changes the article's URL (`/{categorySlug}/{slug}/`), so the old URL
116120
- Runs daily at **04:00**.
117121
- Exits non-zero without touching the data file if the run can't be trusted, so the previously generated listing keeps serving.
118122
- Logs to `log/generate-packages.log`.
119-
- **`bin/generate-feed`** (RSS, `public/feed.xml`) — **manual only**, no cron. Run it as part of the publish flow in step 4, right after `bin/doctrine-fixtures`/`bin/create-uploads-dir`, whenever an article is added, edited, or its `post_status` changes.
120-
- **`bin/sitemap`** (`public/sitemap.xml`) — **manual only**, no cron. Same trigger as `bin/generate-feed`: re-run after any change to `articles_cleaned.json`.
121-
- **`bin/generate-llms-full`** (`public/llms-full.txt`) — **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/` or the static-page `.md` files at the `public/` root, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). The static-page `.md` files are hand-maintained alongside the templates in `src/Page/templates/page/` — editing a page template means updating its `.md` and re-running this.
123+
- **`bin/generate-feed`** (RSS, `public/feed.xml`) - **manual only**, no cron. Run it as part of the publish flow in step 4, right after `bin/doctrine-fixtures`/`bin/create-uploads-dir`, whenever an article is added, edited, or its `post_status` changes.
124+
- **`bin/sitemap`** (`public/sitemap.xml`) - **manual only**, no cron. Same trigger as `bin/generate-feed`: re-run after any change to `articles_cleaned.json`.
125+
- **`bin/generate-llms-full`** (`public/llms-full.txt`) - **manual only**, no cron. Re-run after adding/editing a `.md` file under `public/md-articles/` or the static-page `.md` files at the `public/` root, or after removing/renaming a draft's `.md` file (it doesn't check `post_status`, see step 1). The static-page `.md` files are hand-maintained alongside the templates in `src/Page/templates/page/` - editing a page template means updating its `.md` and re-running this.

0 commit comments

Comments
 (0)