80 lines
5.4 KiB
Markdown
80 lines
5.4 KiB
Markdown
---
|
|
type: guide
|
|
title: Blog Publishing and Reading
|
|
description: How administrators create, edit, publish, and delete Markdown posts and how users read published posts.
|
|
tags: [guide, blog, admin, markdown, tester]
|
|
timestamp: 2026-07-23T10:36:45Z
|
|
---
|
|
|
|
# User views
|
|
|
|
Published posts are available in two places:
|
|
|
|
| View | Access | Expected content |
|
|
|---|---|---|
|
|
| `/login` | Unauthenticated users | The landing-page blog section lists published posts below the Login button. |
|
|
| `/blog` | Signed-in users | The Blog page loads and lists all published posts. |
|
|
|
|
Both views use `BlogPresenterComponent` to show the post title, publication date, and sanitized Markdown body. Draft posts never appear in either public list. The `/blog` page shows `Loading blog posts…` while loading, `No announcements yet.` when no posts are published, and an error message if loading fails.
|
|
|
|
# Admin workflow
|
|
|
|
1. Sign in as an administrator.
|
|
2. Open **Admin**, then select **Blog**, or navigate directly to `/admin/blog`.
|
|
3. Confirm the page displays a table with **Title**, **Status**, **Created**, **Updated**, **Published**, and **Actions** columns. If no posts exist, it displays `No posts yet.`.
|
|
4. Click **+ New post**.
|
|
5. Enter a required title and an optional Markdown body. The live preview updates beside the editor.
|
|
6. Click **Save draft** to keep the post hidden from users, or **Publish** to expose it in `/blog` and on `/login`.
|
|
7. Confirm a success banner displays `Draft saved.` or `Post published.`, and the refreshed table contains the post with the correct status badge.
|
|
|
|
# Editing and deleting
|
|
|
|
* Click the pencil action on a row to open the prefilled **Edit post** modal. It shows created, updated, and publication timestamps where available.
|
|
* Saving as draft changes the status to **Draft** and hides the post from public lists. Republishing preserves the post's original publication timestamp.
|
|
* Click the trash action to open **Delete post**. The confirmation includes the title and warns that deletion cannot be undone.
|
|
* Click **Delete** to remove the post. The table refreshes and displays `Post deleted.`. Click **Cancel** or the modal backdrop to leave the post unchanged.
|
|
|
|
# Closing modals with Escape
|
|
|
|
Both the **New post / Edit post** modal and the **Delete post** confirmation honor the platform-wide Escape-to-close convention:
|
|
|
|
* Pressing **Escape** while the modal is open closes it without saving or deleting.
|
|
* Escape is suppressed while a save/publish request is in flight (the modal stays open until the current operation completes), so an in-flight create or update cannot be silently cancelled by an accidental keypress.
|
|
* Escape, the **Cancel** / **Delete** buttons, and clicking the modal backdrop all close the modal via the same `onCancel()` → `cancel` output flow; no partial post is ever created or modified when Escape is pressed.
|
|
|
|
# Validation and expected errors
|
|
|
|
| Input or operation | Expected behavior |
|
|
|---|---|
|
|
| Empty or whitespace-only title | `Title is required`; save and publish remain unavailable. |
|
|
| Title over 200 characters | `Title must be at most 200 characters`. |
|
|
| Markdown body over 200,000 characters | `Body must be at most 200000 characters`. |
|
|
| API list failure | An error banner replaces the table state. |
|
|
| Create or update failure | The form stays open and displays the server message. |
|
|
| Delete failure | The confirmation stays open and displays the server message. |
|
|
|
|
# Architecture map
|
|
|
|
| Layer | File | Responsibility |
|
|
|---|---|---|
|
|
| Routes | `frontend/src/app/app.routes.ts` | Registers authenticated `/blog` and admin-only `/admin/blog`. |
|
|
| Admin page | `frontend/src/app/features/admin/blog/blog.component.ts` | Loads all posts and coordinates create, edit, publish, draft, and delete operations. |
|
|
| Admin form | `frontend/src/app/features/admin/blog/blog-form-modal.component.ts` | Reactive title/body editor with sanitized live Markdown preview. |
|
|
| Admin form logic | `frontend/src/app/features/admin/blog/blog-form.pure.ts` | Validation, form synchronization, request cleanup, and status labels. |
|
|
| Delete confirmation | `frontend/src/app/features/admin/blog/blog-delete-modal.component.ts` | Confirms destructive deletion and displays failures. |
|
|
| User page | `frontend/src/app/features/blog/blog.page.ts` | Loads published posts and selects loading, error, empty, or list state. |
|
|
| Shared presenter | `frontend/src/app/features/blog/blog-presenter.component.ts` | Renders a published post consistently on `/blog` and `/login`. |
|
|
| HTTP client | `frontend/src/app/core/services/blog.service.ts` | Calls public list and admin CRUD endpoints. |
|
|
| Backend wiring | `backend/src/modules/blog/blog.module.ts` | Registers public and admin controllers and services with the blog repository. |
|
|
| Admin API | `backend/src/modules/blog/admin-blog.controller.ts` | Registers guarded CRUD routes under `/api/v1/admin/blog/posts`. |
|
|
| Persistence logic | `backend/src/modules/blog/admin-blog.service.ts` | Queries and mutates `blog_post` rows and controls publication timestamps. |
|
|
|
|
The Angular client uses HTTP JSON calls. Existing auth and CSRF interceptors attach the administrator JWT and CSRF token to unsafe requests. The backend controller is protected by `AdminGuard` and `@Roles('admin')`, then delegates to `AdminBlogService`, which uses the TypeORM `BlogPostEntity` repository.
|
|
|
|
# See also
|
|
|
|
- [Blog API](/api/blog.md)
|
|
- [Blog Post Table](/database/blog-posts.md)
|
|
- [Landing Page](/guides/landing-page.md)
|
|
- [Admin Shell & Side Navigation](/guides/admin-shell.md)
|