📄 blog-admin-api.md 6,561 bytes Apr 21, 2026 📋 Raw

Blog Admin API Specification

Status: Awaiting Socrates implementation
Frontend Status: ✅ Complete (Phase 4 admin UI delivered 2026-04-21)


Overview

Admin API endpoints required to power the HoffDesk Blog admin interface. All endpoints return JSON unless otherwise specified. HTMX requests expect HTML partials in response.


Dev Server

A standalone dev server is available for local preview:

cd blog/
python3 dev_server.py

Access points:
- Public blog: http://localhost:8080/
- Admin panel: http://localhost:8080/admin/
- Editor: http://localhost:8080/admin/posts/the-night-i-broke-dns/edit

The dev server includes mock data for all endpoints so you can preview the UI before Socrates wires the real API.


Authentication

All admin endpoints require authentication. Current assumption: session-based auth via FastAPI (same as main HoffDesk app).


Endpoints

Dashboard

GET /api/blog/admin/stats

Returns dashboard statistics for the admin homepage.

Response:

{
  "total": 12,
  "published": 8,
  "drafts": 4,
  "images": 23,
  "last_build_time": "2026-04-21T10:30:00Z",
  "last_build_iso": "2026-04-21T10:30:00+00:00"
}

GET /api/blog/admin/recent

Returns recent posts for the dashboard activity feed.

Response:

{
  "posts": [
    {
      "slug": "the-night-i-broke-dns",
      "title": "The Night I Broke DNS and My Wife Couldn't Reach Facebook",
      "category": "homelab",
      "status": "published",
      "updated_relative": "2 hours ago"
    }
  ]
}

Posts

GET /api/blog/admin/posts

Returns list of posts with filtering.

Query Parameters:
- status (optional): published | draft
- category (optional): homelab | openclaw | ai-news
- q (optional): search string
- page (optional): page number (default: 1)
- per_page (optional): items per page (default: 20)

Response:

{
  "posts": [
    {
      "slug": "post-slug",
      "title": "Post Title",
      "category": "homelab",
      "status": "published",
      "updated_at": "2026-04-21T10:30:00Z",
      "updated_relative": "2 hours ago",
      "excerpt": "Brief summary..."
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 12,
    "pages": 1
  }
}

GET /api/blog/admin/posts/{slug}

Returns full post data for editing.

Response:

{
  "slug": "post-slug",
  "title": "Post Title",
  "category": "homelab",
  "status": "published",
  "tags": "dns, homelab, fail",
  "excerpt": "Brief summary...",
  "cover_image": "/blog/images/hero.jpg",
  "content": "# Markdown content...",
  "rendered_html": "<h1>HTML content...</h1>",
  "created_at": "2026-04-20T08:00:00Z",
  "updated_at": "2026-04-21T10:30:00Z",
  "updated_relative": "2 hours ago"
}

POST /api/blog/admin/posts/{slug}

Auto-save endpoint for the editor. Creates new post if slug is new.

Request Body:

{
  "title": "Post Title",
  "category": "homelab",
  "tags": "dns, homelab",
  "excerpt": "Brief summary...",
  "cover_image": "/blog/images/hero.jpg",
  "content": "# Markdown content..."
}

Response (HTMX):

<span class="admin-toast-message">Saved</span>

Response (JSON):

{
  "slug": "post-slug",
  "status": "draft",
  "saved": true
}

POST /api/blog/admin/posts/{slug}/publish

Publishes a draft post.

Response (HTMX): Full status widget HTML partial

POST /api/blog/admin/posts/{slug}/unpublish

Reverts a published post to draft.

Response (HTMX): Full status widget HTML partial

POST /api/blog/admin/posts/{slug}/delete

Deletes a post permanently.

Response:
- Success: 302 Redirect to /admin/blog/posts
- HTMX: Toast confirmation

POST /api/blog/admin/posts/{slug}/preview

Returns rendered HTML preview of post content.

Request Body: Same as save endpoint

Response:

{
  "rendered_html": "<h1>Title</h1><p>Content...</p>"
}

Build & Deploy

POST /api/blog/admin/rebuild

Triggers static site rebuild.

Response (HTMX):

<span class="admin-toast-message admin-toast-message--success">Rebuild started</span>

POST /api/blog/admin/deploy

Triggers deployment to production (Cloudflare Pages).

Response (HTMX):

<span class="admin-toast-message admin-toast-message--success">Deploying...</span>

Images

GET /api/blog/admin/images

Returns list of available images in the blog images directory.

Response:

{
  "images": [
    {
      "path": "/blog/images/hero.jpg",
      "filename": "hero.jpg",
      "relative_path": "images/hero.jpg",
      "size_bytes": 45000,
      "dimensions": "1200x600",
      "modified": "2026-04-21T10:30:00Z"
    }
  ]
}

HTMX Conventions

All endpoints support HTMX requests via the HX-Request header:

  • Save endpoints: Return toast message HTML
  • Status changes: Return full component HTML (status widget, post row)
  • Rebuild/Deploy: Return toast confirmation
  • Delete: Return redirect header or toast

Frontend Integration Notes

Auto-save Behavior

  • Debounced 3s after input stops
  • Includes all form fields via hx-include
  • Visual feedback via autosave indicator

Live Preview

  • Client-side: markdown-it (500ms debounce)
  • Server-side: POST /api/blog/admin/posts/{slug}/preview (enhancement)

Status Widget

  • Dynamic partial swap on publish/unpublish
  • Green dot = published, amber dot = draft

Mobile Considerations

  • Preview pane hidden behind toggle on <768px
  • Sidebar collapses to hamburger menu
  • Tables become card layouts

Files Delivered

Templates (Jinja2):
- admin/templates/admin_base.html.j2 — Base layout with sidebar
- admin/templates/admin_dashboard.html.j2 — Stats + recent activity
- admin/templates/admin_post_list.html.j2 — Filterable post table
- admin/templates/admin_editor.html.j2 — Split-pane editor
- admin/templates/admin_images.html.j2 — Image browser
- admin/templates/components/post_row.html.j2 — HTMX partial
- admin/templates/components/status_widget.html.j2 — HTMX partial
- admin/templates/components/preview_pane.html.j2 — HTMX partial

Stylesheets:
- admin/static/admin.css — 28KB, extends blog.css tokens

Scripts:
- Inline JS in admin_editor.html.j2 for client-side preview


Next Steps

  1. Socrates implements above endpoints
  2. Integration testing on dev environment
  3. Matt approves UX flow
  4. Deploy to production