Mastodon ignored Delete activities because the Tombstone id was the post URL, not the object id from the original Create activity. Now looks up the existing Create activity and uses its object id. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
"""Inline federation publication — called at write time, not via async handler.
|
|
|
|
Replaces the old pattern where emit_event("post.published") → async handler →
|
|
publish_activity(). Now the originating service calls try_publish() directly,
|
|
which creates the APActivity in the same DB transaction. AP delivery
|
|
(federation.activity_created → inbox POST) stays async.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from shared.services.registry import services
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
async def try_publish(
|
|
session: AsyncSession,
|
|
*,
|
|
user_id: int | None,
|
|
activity_type: str,
|
|
object_type: str,
|
|
object_data: dict,
|
|
source_type: str,
|
|
source_id: int,
|
|
) -> None:
|
|
"""Publish an AP activity if federation is available and user has a profile.
|
|
|
|
Safe to call from any app — returns silently if federation isn't wired
|
|
or the user has no actor profile.
|
|
"""
|
|
if not services.has("federation"):
|
|
return
|
|
|
|
if not user_id:
|
|
return
|
|
|
|
actor = await services.federation.get_actor_by_user_id(session, user_id)
|
|
if not actor:
|
|
return
|
|
|
|
# Dedup: don't re-Create if already published, don't re-Delete if already deleted
|
|
existing = await services.federation.get_activity_for_source(
|
|
session, source_type, source_id,
|
|
)
|
|
if existing:
|
|
if activity_type == "Create" and existing.activity_type != "Delete":
|
|
return # already published (allow re-Create after Delete/unpublish)
|
|
if activity_type == "Delete" and existing.activity_type == "Delete":
|
|
return # already deleted
|
|
elif activity_type == "Delete":
|
|
return # never published, nothing to delete
|
|
|
|
# Delete must reference the same object id Mastodon received in Create
|
|
if activity_type == "Delete" and existing:
|
|
object_data["id"] = existing.activity_id + "/object"
|
|
|
|
try:
|
|
await services.federation.publish_activity(
|
|
session,
|
|
actor_user_id=user_id,
|
|
activity_type=activity_type,
|
|
object_type=object_type,
|
|
object_data=object_data,
|
|
source_type=source_type,
|
|
source_id=source_id,
|
|
)
|
|
log.info(
|
|
"Published %s/%s for %s#%d by user %d",
|
|
activity_type, object_type, source_type, source_id, user_id,
|
|
)
|
|
except Exception:
|
|
log.exception("Failed to publish activity for %s#%d", source_type, source_id)
|