Require CLAUDE.md changes to keep docs/ in sync with new features or architecture.
64 lines
3.6 KiB
Markdown
64 lines
3.6 KiB
Markdown
# Content model & admin panel
|
|
|
|
## Content types
|
|
|
|
Defined in `cms/src/api/*/content-types/*/schema.json`. Both use plain Strapi factory
|
|
defaults (`createCoreRouter`/`createCoreController`/`createCoreService`) — no custom controllers,
|
|
routes, services, or policies exist for either type.
|
|
|
|
| Type | Fields |
|
|
|---|---|
|
|
| **Article** | `title` (string, required, max 160), `slug` (UID generated from title), `content` (richtext — Markdown source), `cover` (single media/image), `category` (many-to-one relation to Category) |
|
|
| **Category** | `name` (string, required, unique), `slug` (UID from name), `articles` (inverse one-to-many) |
|
|
|
|
Deliberately minimal: no Author (the only author is the admin account), no tags, no separate SEO
|
|
fields. Don't add these back without a concrete need — see `CLAUDE.md`:
|
|
|
|
- Meta description is derived at request time from the article body (`summarise()` in
|
|
`frontend/server/utils/strapi.ts`).
|
|
- Publish date is Draft & Publish's `publishedAt`.
|
|
- Social preview image is the cover.
|
|
- The article byline is the admin user's name (`createdBy`, via `populateCreatorFields: true`),
|
|
not a content field.
|
|
|
|
Draft & Publish is enabled on Article (`draftAndPublish: true`) and disabled on Category
|
|
(`draftAndPublish: false`) — categories aren't drafted. Public URLs always use the slug, never the
|
|
numeric id.
|
|
|
|
## Admin login & permissions
|
|
|
|
- The admin panel lives at `/admin`, routed by Caddy straight to the `cms` container
|
|
([architecture.md](./architecture.md#path-routing-caddy)). It's Strapi's standard
|
|
email/password admin authentication — no custom auth code in this repo.
|
|
- Public visitors **never** authenticate. There is no visitor account system, no comments, no
|
|
public write access of any kind.
|
|
- `cms/src/index.ts` (`bootstrap`) runs two idempotent setup steps on every Strapi start:
|
|
1. **`grantPublicReadAccess`** — grants the `public` role exactly `find`/`findOne` on Article
|
|
and Category, and nothing else (no create/update/delete, no other content type). This is
|
|
what lets the Nitro endpoints read published content without a token.
|
|
2. **`disablePublicSignUp`** — turns off `users-permissions`' public registration
|
|
(`allow_register: false`), since no front-end user accounts should ever exist.
|
|
- Any permission beyond `find`/`findOne` for Public needs to be justified explicitly — this is a
|
|
deliberate least-privilege boundary, not an oversight.
|
|
- `cms/config/plugins.ts` further restricts the `upload` plugin's allowed MIME types (images,
|
|
video, audio, PDF, office docs, text/CSV) and explicitly denies executables.
|
|
|
|
## Editorial workflow
|
|
|
|
1. Log into `/admin`.
|
|
2. Create/edit a Category if needed (name → slug is generated automatically).
|
|
3. Create/edit an Article: title (→ slug), Markdown content, cover image, category.
|
|
4. Publish (Draft & Publish). Unpublished drafts are never served by the `find`/`findOne`
|
|
permissions above — Strapi's default behavior already excludes non-published entries from the
|
|
public API.
|
|
5. The change is live immediately: the public site has no cache layer to invalidate (see
|
|
[architecture.md](./architecture.md#request-flow-reading-an-article)).
|
|
|
|
## Why Markdown, not a rich-text/WYSIWYG field
|
|
|
|
`content` is a plain richtext (Markdown) field. Strapi stores the raw Markdown; conversion to HTML
|
|
happens once, server-side, in the Nuxt Nitro endpoint (`renderMarkdown()`, using `marked`) — never
|
|
in Strapi and never in the browser. This keeps `marked` out of the client bundle and keeps HTML
|
|
generation in one place. There is no sanitization step: this is intentional, since the only author
|
|
is the trusted admin, not arbitrary users.
|