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 3bde451ce9 Inline federation publication, remove async handlers
Federation activities are now created at write time via try_publish()
instead of relying on async event handlers. Fixes race condition where
multiple EventProcessors could consume post.published events in apps
that couldn't meaningfully process them.

AP delivery (federation.activity_created → inbox POST) stays async.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:54:14 +00:00

72 lines
2.3 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
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)