Monorepo: consolidate 7 repos into one
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Combines shared, blog, market, cart, events, federation, and account into a single repository. Eliminates submodule sync, sibling model copying at build time, and per-app CI orchestration. Changes: - Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs - Remove stale sibling model copies from each app - Update all 6 Dockerfiles for monorepo build context (root = .) - Add build directives to docker-compose.yml - Add single .gitea/workflows/ci.yml with change detection - Add .dockerignore for monorepo build context - Create __init__.py for federation and account (cross-app imports)
This commit is contained in:
92
shared/services/federation_publish.py
Normal file
92
shared/services/federation_publish.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""Inline federation publication — called at write time, not via async handler.
|
||||
|
||||
The originating service calls try_publish() directly, which creates the
|
||||
APActivity (with process_state='pending') in the same DB transaction.
|
||||
The EventProcessor picks it up and the delivery wildcard handler POSTs
|
||||
to follower inboxes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
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 in ("Delete", "Update"):
|
||||
return # never published, nothing to delete/update
|
||||
|
||||
# Stable object ID within a publish cycle. After Delete + re-Create
|
||||
# we append a version suffix so remote servers (Mastodon) treat it as
|
||||
# a brand-new post rather than ignoring the tombstoned ID.
|
||||
domain = os.getenv("AP_DOMAIN", "federation.rose-ash.com")
|
||||
base_object_id = (
|
||||
f"https://{domain}/users/{actor.preferred_username}"
|
||||
f"/objects/{source_type.lower()}/{source_id}"
|
||||
)
|
||||
if activity_type == "Create" and existing and existing.activity_type == "Delete":
|
||||
# Count prior Creates to derive a version number
|
||||
create_count = await services.federation.count_activities_for_source(
|
||||
session, source_type, source_id, activity_type="Create",
|
||||
)
|
||||
object_data["id"] = f"{base_object_id}/v{create_count + 1}"
|
||||
elif activity_type in ("Update", "Delete") and existing and existing.object_data:
|
||||
# Use the same object ID as the most recent activity
|
||||
object_data["id"] = existing.object_data.get("id", base_object_id)
|
||||
else:
|
||||
object_data["id"] = base_object_id
|
||||
|
||||
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)
|
||||
Reference in New Issue
Block a user