feat: implement Pages as Spaces Phase 1
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 45s

- Add PageConfig model with feature flags (calendar, market)
- Auto-create PageConfig on Ghost page sync
- Add create_page() for Ghost /pages/ API endpoint
- Add /new-page/ route for creating pages
- Add ?type=pages blog filter with Posts|Pages tab toggle
- Add list_pages() to DBClient with PageConfig eager loading
- Add PUT /<slug>/admin/features/ route for feature toggles
- Add feature badges (calendar, market) on page cards
- Add features panel to page admin dashboard
- Update shared_lib submodule with PageConfig model

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-10 14:25:34 +00:00
parent 455a7b1078
commit 8b6320619e
13 changed files with 480 additions and 4 deletions

View File

@@ -73,6 +73,35 @@ async def create_post(
return resp.json()["posts"][0]
async def create_page(
title: str,
lexical_json: str,
status: str = "draft",
feature_image: str | None = None,
custom_excerpt: str | None = None,
feature_image_caption: str | None = None,
) -> dict:
"""Create a new page in Ghost (via /pages/ endpoint). Returns the created page dict."""
page_body: dict = {
"title": title,
"lexical": lexical_json,
"mobiledoc": None,
"status": status,
}
if feature_image:
page_body["feature_image"] = feature_image
if custom_excerpt:
page_body["custom_excerpt"] = custom_excerpt
if feature_image_caption is not None:
page_body["feature_image_caption"] = feature_image_caption
payload = {"pages": [page_body]}
url = f"{GHOST_ADMIN_API_URL}/pages/"
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(url, json=payload, headers=_auth_header())
_check(resp)
return resp.json()["pages"][0]
async def update_post(
ghost_id: str,
lexical_json: str,

View File

@@ -13,6 +13,7 @@ from sqlalchemy.orm.attributes import flag_modified # for non-Mutable JSON colu
from models.ghost_content import (
Post, Author, Tag, PostAuthor, PostTag
)
from models.page_config import PageConfig
# User-centric membership models
from models import User
@@ -238,6 +239,15 @@ async def _upsert_post(sess: AsyncSession, gp: Dict[str, Any], author_map: Dict[
tt = tag_map[t["id"]]
sess.add(PostTag(post_id=obj.id, tag_id=tt.id, sort_order=idx))
# Auto-create PageConfig for pages
if obj.is_page:
existing_pc = (await sess.execute(
select(PageConfig).where(PageConfig.post_id == obj.id)
)).scalar_one_or_none()
if existing_pc is None:
sess.add(PageConfig(post_id=obj.id, features={}))
await sess.flush()
return obj
async def _ghost_find_member_by_email(email: str) -> Optional[dict]: