Skip to content

[Blog] Admin-gated CRUD routes for /news#221

Merged
gregv merged 1 commit into
developfrom
claude/dazzling-faraday-3ca3eb-blog-admin
May 17, 2026
Merged

[Blog] Admin-gated CRUD routes for /news#221
gregv merged 1 commit into
developfrom
claude/dazzling-faraday-3ca3eb-blog-admin

Conversation

@gregv
Copy link
Copy Markdown
Contributor

@gregv gregv commented May 17, 2026

What does the PR do?

Adds admin-gated CRUD routes for the news Firestore collection so the new frontend /admin/blog CMS (opportunity-hack/frontend-ohack.dev#293) can create, edit, publish/unpublish, and delete blog posts. Today the only write path is POST /api/messages/news with X-Api-Key (the Slack integration); that path is not touched so the Slack flow keeps working.

New routes (all under /api/messages/admin/news, gated with volunteer.admin)

  • GET /admin/news?limit=&status= — admin list, includes drafts and archived posts
  • POST /admin/news — create (default status="draft", skips OpenAI image-gen when featured_image is supplied)
  • PATCH /admin/news/<id> — partial update (allowlisted fields only, clears get_news cache)
  • DELETE /admin/news/<id> — hard delete

Service / firebase changes

  • services/news_service.py adds admin_create_news, admin_update_news, admin_delete_news, admin_list_news. The allowlist _ADMIN_ALLOWED_KEYS is the only way new fields get through PATCH. New helper _is_publicly_visible filters status in ("draft", "archived") out of the public get_news for both list and single-item reads (over-fetches by 3x so the post-filter count still hits the requested limit).
  • common/utils/firebase.py adds update_news_partial, delete_news, and get_all_news_admin.

New optional fields on a news doc (legacy docs unchanged)

content_markdown, content_format ("html" | "markdown"), featured_image, author{name,email,propel_user_id,db_id}, tags[], slug, status ("draft" | "published" | "archived"), published_at (ISO), seo{title,description,keywords[],canonical,og_image}, last_updated_by, created_by.

Type of change

  • Breaking Change
  • Bug Fix
  • New Feature

Linked Issue

Frontend companion: opportunity-hack/frontend-ohack.dev#293 — must merge alongside this so the admin UI is functional end-to-end.

Make sure you have

  • Pulled from the default branch
  • Documented your changes (see the new "Blog Admin" section in frontend-ohack.dev/CLAUDE.md)
  • Linked the Issue
  • Appointed a reviewer (if any)

Reviewer notes

  • The public POST /api/messages/news endpoint (X-Api-Key) is deliberately untouched — the Slack integration depends on it. Only new admin routes are added.
  • get_news now drops status=draft|archived from public responses; admins reading via the new /admin/news routes see everything.
  • DELETE is hard delete (Firestore doc removed). The frontend confirm dialog spells out the post title to make accidental deletes hard.
  • admin_create_news skips OpenAI image generation when featured_image is supplied — saves an API call and lets admins use their own images.
  • slack_ts is stamped to time.time() on admin-created posts so existing ordering keeps working without an explicit Slack message.

🤖 Generated with Claude Code

Backs the new frontend /admin/blog CMS
(opportunity-hack/frontend-ohack.dev#293). The existing public POST
/api/messages/news with X-Api-Key auth is untouched so the Slack
integration keeps working.

- New routes under /api/messages/admin/news (all gated with
  volunteer.admin via auth.require_org_member_with_permission):
  - GET    /admin/news?limit=&status=  — admin list, includes drafts
  - POST   /admin/news                  — create (default status=draft,
    skips OpenAI image-gen when featured_image supplied)
  - PATCH  /admin/news/<id>             — partial update (allowlisted
    fields only, clears get_news cache)
  - DELETE /admin/news/<id>             — hard delete

- services/news_service.py adds admin_create_news, admin_update_news,
  admin_delete_news, admin_list_news plus _ADMIN_ALLOWED_KEYS allowlist
  and _is_publicly_visible filter so the public get_news drops
  status=draft|archived (over-fetches 3x to keep the post-filter count).

- common/utils/firebase.py adds update_news_partial, delete_news, and
  get_all_news_admin.

New optional fields on a news doc (all optional, legacy docs unchanged):
content_markdown, content_format, featured_image, author, tags, slug,
status, published_at, seo{}, last_updated_by, created_by.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@gregv gregv merged commit 2310f89 into develop May 17, 2026
3 checks passed
@gregv gregv deleted the claude/dazzling-faraday-3ca3eb-blog-admin branch May 17, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant