Track status changes for unpublish + edit federation events
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m0s
- _upsert_post returns (post, old_status) to detect status transitions - Emit post.unpublished when published→draft (triggers Delete activity) - Emit post.updated only when already-published posts are edited - Emit post.published only for new publishes (not re-syncs) - Same logic for pages via sync_single_page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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]
|
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
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
res = await sess.execute(select(Post).where(Post.ghost_id == gp["id"]))
|
res = await sess.execute(select(Post).where(Post.ghost_id == gp["id"]))
|
||||||
obj = res.scalar_one_or_none()
|
obj = res.scalar_one_or_none()
|
||||||
|
|
||||||
|
old_status = obj.status if obj is not None else None
|
||||||
|
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
# Row exists — just update
|
# Row exists — just update
|
||||||
_apply_ghost_fields(obj, gp, author_map, tag_map)
|
_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_obj = await _upsert_tag(sess, pt)
|
||||||
tag_map[pt["id"]] = tag_obj
|
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)
|
# Emit federation events for posts (not pages)
|
||||||
if post.status == "published" and not post.is_page and post.user_id:
|
if not post.is_page and post.user_id:
|
||||||
from shared.events import emit_event
|
from shared.events import emit_event
|
||||||
from shared.infrastructure.urls import app_url
|
from shared.infrastructure.urls import app_url
|
||||||
event_type = "post.published" if post.created_at == post.updated_at else "post.updated"
|
post_url = app_url("coop", f"/{post.slug}/")
|
||||||
await emit_event(
|
|
||||||
sess,
|
if post.status == "published":
|
||||||
event_type=event_type,
|
event_type = "post.published" if old_status != "published" else "post.updated"
|
||||||
aggregate_type="Post",
|
await emit_event(
|
||||||
aggregate_id=post.id,
|
sess,
|
||||||
payload={
|
event_type=event_type,
|
||||||
"user_id": post.user_id,
|
aggregate_type="Post",
|
||||||
"title": post.title or "",
|
aggregate_id=post.id,
|
||||||
"excerpt": post.custom_excerpt or post.excerpt or "",
|
payload={
|
||||||
"url": app_url("coop", f"/{post.slug}/"),
|
"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:
|
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_obj = await _upsert_tag(sess, pt)
|
||||||
tag_map[pt["id"]] = tag_obj
|
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
|
# Emit federation events for pages
|
||||||
if post.status == "published" and post.user_id:
|
if post.user_id:
|
||||||
from shared.events import emit_event
|
from shared.events import emit_event
|
||||||
from shared.infrastructure.urls import app_url
|
from shared.infrastructure.urls import app_url
|
||||||
event_type = "post.published" if post.created_at == post.updated_at else "post.updated"
|
post_url = app_url("coop", f"/{post.slug}/")
|
||||||
await emit_event(
|
|
||||||
sess,
|
if post.status == "published":
|
||||||
event_type=event_type,
|
event_type = "post.published" if old_status != "published" else "post.updated"
|
||||||
aggregate_type="Post",
|
await emit_event(
|
||||||
aggregate_id=post.id,
|
sess,
|
||||||
payload={
|
event_type=event_type,
|
||||||
"user_id": post.user_id,
|
aggregate_type="Post",
|
||||||
"title": post.title or "",
|
aggregate_id=post.id,
|
||||||
"excerpt": post.custom_excerpt or post.excerpt or "",
|
payload={
|
||||||
"url": app_url("coop", f"/{post.slug}/"),
|
"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:
|
async def sync_single_author(sess: AsyncSession, ghost_id: str) -> None:
|
||||||
|
|||||||
2
shared
2
shared
Submodule shared updated: a28add8640...18410c4b16
Reference in New Issue
Block a user