diff --git a/events/handlers/ap_delivery_handler.py b/events/handlers/ap_delivery_handler.py index 907f38b..7dbf9ad 100644 --- a/events/handlers/ap_delivery_handler.py +++ b/events/handlers/ap_delivery_handler.py @@ -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", diff --git a/events/handlers/federation_handlers.py b/events/handlers/federation_handlers.py index 1f4c6b5..dc355f1 100644 --- a/events/handlers/federation_handlers.py +++ b/events/handlers/federation_handlers.py @@ -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)