This repository has been archived on 2026-02-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
shared/services/federation_publish.py
giles a626dd849d Fix AP Delete: Tombstone id must match original Create object id
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>
2026-02-22 09:25:30 +00:00

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)