diff --git a/bp/blog/ghost/ghost_sync.py b/bp/blog/ghost/ghost_sync.py index f9b8d76..dbb8ab9 100644 --- a/bp/blog/ghost/ghost_sync.py +++ b/bp/blog/ghost/ghost_sync.py @@ -209,12 +209,15 @@ def _apply_ghost_fields(obj: Post, gp: Dict[str, Any], author_map: Dict[str, Aut obj.primary_tag_id = tag_map[pt["id"].strip()].id if (pt and pt["id"] in tag_map) else None # type: ignore[index] -async def _upsert_post(sess: AsyncSession, gp: Dict[str, Any], author_map: Dict[str, Author], tag_map: Dict[str, Tag]) -> Post: +async def _upsert_post(sess: AsyncSession, gp: Dict[str, Any], author_map: Dict[str, Author], tag_map: Dict[str, Tag]) -> tuple[Post, str | None]: + """Upsert a post. Returns (post, old_status) where old_status is None for new rows.""" from sqlalchemy.exc import IntegrityError res = await sess.execute(select(Post).where(Post.ghost_id == gp["id"])) obj = res.scalar_one_or_none() + old_status = obj.status if obj is not None else None + if obj is not None: # Row exists — just update _apply_ghost_fields(obj, gp, author_map, tag_map) @@ -1018,25 +1021,40 @@ async def sync_single_post(sess: AsyncSession, ghost_id: str) -> None: tag_obj = await _upsert_tag(sess, pt) tag_map[pt["id"]] = tag_obj - post = await _upsert_post(sess, gp, author_map, tag_map) + post, old_status = await _upsert_post(sess, gp, author_map, tag_map) - # Emit federation event for published posts (not pages, not drafts) - if post.status == "published" and not post.is_page and post.user_id: + # Emit federation events for posts (not pages) + if not post.is_page and post.user_id: from shared.events import emit_event from shared.infrastructure.urls import app_url - event_type = "post.published" if post.created_at == post.updated_at else "post.updated" - await emit_event( - sess, - event_type=event_type, - aggregate_type="Post", - aggregate_id=post.id, - payload={ - "user_id": post.user_id, - "title": post.title or "", - "excerpt": post.custom_excerpt or post.excerpt or "", - "url": app_url("coop", f"/{post.slug}/"), - }, - ) + post_url = app_url("coop", f"/{post.slug}/") + + if post.status == "published": + event_type = "post.published" if old_status != "published" else "post.updated" + await emit_event( + sess, + event_type=event_type, + aggregate_type="Post", + aggregate_id=post.id, + payload={ + "user_id": post.user_id, + "title": post.title or "", + "excerpt": post.custom_excerpt or post.excerpt or "", + "url": post_url, + }, + ) + elif old_status == "published" and post.status != "published": + # Unpublished — notify federation to send Delete + await emit_event( + sess, + event_type="post.unpublished", + aggregate_type="Post", + aggregate_id=post.id, + payload={ + "user_id": post.user_id, + "url": post_url, + }, + ) async def sync_single_page(sess: AsyncSession, ghost_id: str) -> None: @@ -1069,25 +1087,39 @@ async def sync_single_page(sess: AsyncSession, ghost_id: str) -> None: tag_obj = await _upsert_tag(sess, pt) tag_map[pt["id"]] = tag_obj - post = await _upsert_post(sess, gp, author_map, tag_map) + post, old_status = await _upsert_post(sess, gp, author_map, tag_map) - # Emit federation event for published pages - if post.status == "published" and post.user_id: + # Emit federation events for pages + if post.user_id: from shared.events import emit_event from shared.infrastructure.urls import app_url - event_type = "post.published" if post.created_at == post.updated_at else "post.updated" - await emit_event( - sess, - event_type=event_type, - aggregate_type="Post", - aggregate_id=post.id, - payload={ - "user_id": post.user_id, - "title": post.title or "", - "excerpt": post.custom_excerpt or post.excerpt or "", - "url": app_url("coop", f"/{post.slug}/"), - }, - ) + post_url = app_url("coop", f"/{post.slug}/") + + if post.status == "published": + event_type = "post.published" if old_status != "published" else "post.updated" + await emit_event( + sess, + event_type=event_type, + aggregate_type="Post", + aggregate_id=post.id, + payload={ + "user_id": post.user_id, + "title": post.title or "", + "excerpt": post.custom_excerpt or post.excerpt or "", + "url": post_url, + }, + ) + elif old_status == "published" and post.status != "published": + await emit_event( + sess, + event_type="post.unpublished", + aggregate_type="Post", + aggregate_id=post.id, + payload={ + "user_id": post.user_id, + "url": post_url, + }, + ) async def sync_single_author(sess: AsyncSession, ghost_id: str) -> None: diff --git a/shared b/shared index a28add8..18410c4 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit a28add8640549c9100f6f6f786a92f4b02abea9b +Subproject commit 18410c4b16bee0ded450d5db00df2766c711e18d