Add unpublish (Delete) support + improve object IDs

- on_post_unpublished handler sends Delete/Tombstone activity
- Create/Update objects use post URL as id (for Delete reference)
- Delete objects use Tombstone type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-21 23:26:56 +00:00
parent a28add8640
commit 18410c4b16
2 changed files with 51 additions and 4 deletions

View File

@@ -27,10 +27,16 @@ def _build_activity_json(activity: APActivity, actor: ActorProfile, domain: str)
actor_url = f"https://{domain}/users/{username}"
obj = dict(activity.object_data or {})
obj.setdefault("id", activity.activity_id + "/object")
obj.setdefault("type", activity.object_type)
obj.setdefault("attributedTo", actor_url)
obj.setdefault("published", activity.published.isoformat() if activity.published else None)
if activity.activity_type == "Delete":
# Delete: object is a Tombstone with just id + type
obj.setdefault("type", "Tombstone")
else:
# Create/Update: full object with attribution
obj.setdefault("id", obj.get("url") or (activity.activity_id + "/object"))
obj.setdefault("type", activity.object_type)
obj.setdefault("attributedTo", actor_url)
obj.setdefault("published", activity.published.isoformat() if activity.published else None)
return {
"@context": "https://www.w3.org/ns/activitystreams",

View File

@@ -163,10 +163,51 @@ async def on_product_listed(event: DomainEvent, session: AsyncSession) -> None:
)
# -- Post unpublished (delete from federation) ---------------------------------
async def on_post_unpublished(event: DomainEvent, session: AsyncSession) -> None:
"""Send a Delete activity when a post is unpublished."""
p = event.payload
user_id = p.get("user_id")
post_url = p.get("url", "")
if not services.has("federation") or not user_id:
return
actor = await services.federation.get_actor_by_user_id(session, user_id)
if not actor:
return
# Find the original Create activity for this post
existing = await services.federation.get_activity_for_source(
session, "Post", event.aggregate_id,
)
if not existing:
return # Never published to federation, nothing to delete
try:
await services.federation.publish_activity(
session,
actor_user_id=user_id,
activity_type="Delete",
object_type="Tombstone",
object_data={
"id": post_url,
"formerType": "Article",
},
source_type="Post",
source_id=event.aggregate_id,
)
log.warning("Published Delete for Post#%d", event.aggregate_id)
except Exception:
log.exception("Failed to publish Delete for Post#%d", event.aggregate_id)
# -- Registration --------------------------------------------------------------
register_handler("post.published", on_post_published)
register_handler("post.updated", on_post_updated)
register_handler("post.unpublished", on_post_unpublished)
register_handler("calendar_entry.created", on_calendar_entry_created)
register_handler("calendar_entry.updated", on_calendar_entry_updated)
register_handler("product.listed", on_product_listed)