Head Hen CMS end-to-end: dashboard lists all posts (drafts + published), Markdown editor with live preview + drag-drop image upload, Pillow media pipeline re-encoding every upload to JPEG, post CRUD + publish toggle + hard delete, About page edit, and double-submit CSRF cookie enforced on every admin mutating endpoint (Phase 3's TODO markers resolved). Slug auto-generated on create and server-locked once a post has been published. Unpublish preserves `published_at` so re-publish keeps original date ordering. Every admin write invalidates the read-side Post/Page TTL caches and records an `auth_events` audit row. CSRF middleware is narrow by design — issues/refreshes the `cb_csrf` cookie only on `GET /admin*`, and mutating endpoints opt in via `require_csrf_form` or `require_csrf_header` Depends. Public routes, healthz, and pre-auth login stay untouched. 64 new tests cover slugs, CSRF, media, admin posts/pages services, and end-to-end CMS routes. Tests never mock the DB — real temp SQLite files per the CLAUDE.md mandate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
44 lines
1.5 KiB
HTML
44 lines
1.5 KiB
HTML
{#
|
|
Single row of the admin dashboard post table.
|
|
|
|
Context (inherited from the parent):
|
|
- post : app.models.entities.Post
|
|
- csrf_token : str
|
|
#}
|
|
<tr class="post-table__row post-table__row--{{ post.status.value }}">
|
|
<td>
|
|
<a href="/admin/posts/{{ post.id }}/edit">{{ post.title }}</a>
|
|
</td>
|
|
<td><code>{{ post.slug }}</code></td>
|
|
<td>
|
|
<span class="status-badge status-badge--{{ post.status.value }}">
|
|
{{ post.status.value|capitalize }}
|
|
</span>
|
|
</td>
|
|
<td>
|
|
<time datetime="{{ post.updated_at.isoformat() }}">
|
|
{{ post.updated_at.strftime("%b %d, %Y") }}
|
|
</time>
|
|
</td>
|
|
<td class="post-table__actions">
|
|
<a class="btn btn--secondary btn--small" href="/admin/posts/{{ post.id }}/edit">Edit</a>
|
|
|
|
<form class="post-table__inline-form"
|
|
action="/admin/posts/{{ post.id }}/publish"
|
|
method="post">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn--secondary btn--small">
|
|
{% if post.status.value == "published" %}Unpublish{% else %}Publish{% endif %}
|
|
</button>
|
|
</form>
|
|
|
|
<form class="post-table__inline-form"
|
|
action="/admin/posts/{{ post.id }}/delete"
|
|
method="post"
|
|
onsubmit="return confirm('Delete this post? This cannot be undone.');">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
|
<button type="submit" class="btn btn--danger btn--small">Delete</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|