Fix per-app AP delivery, NULL uniqueness, and reverse discovery
- Delivery handler now signs/delivers using the per-app domain that matches the follower's subscription (not always federation domain) - app_domain is NOT NULL with default 'federation' (sentinel replaces NULL to avoid uniqueness constraint edge case) - Aggregate actor advertises per-app actors via alsoKnownAs - Migration backfills existing NULL rows to 'federation' Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -339,7 +339,7 @@ class SqlFederationService:
|
||||
self, session: AsyncSession, username: str,
|
||||
follower_acct: str, follower_inbox: str, follower_actor_url: str,
|
||||
follower_public_key: str | None = None,
|
||||
app_domain: str | None = None,
|
||||
app_domain: str = "federation",
|
||||
) -> APFollowerDTO:
|
||||
actor = (
|
||||
await session.execute(
|
||||
@@ -350,16 +350,15 @@ class SqlFederationService:
|
||||
raise ValueError(f"Actor not found: {username}")
|
||||
|
||||
# Upsert: update if already following this (actor, acct, app_domain)
|
||||
q = select(APFollower).where(
|
||||
APFollower.actor_profile_id == actor.id,
|
||||
APFollower.follower_acct == follower_acct,
|
||||
)
|
||||
if app_domain is not None:
|
||||
q = q.where(APFollower.app_domain == app_domain)
|
||||
else:
|
||||
q = q.where(APFollower.app_domain.is_(None))
|
||||
|
||||
existing = (await session.execute(q)).scalar_one_or_none()
|
||||
existing = (
|
||||
await session.execute(
|
||||
select(APFollower).where(
|
||||
APFollower.actor_profile_id == actor.id,
|
||||
APFollower.follower_acct == follower_acct,
|
||||
APFollower.app_domain == app_domain,
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
existing.follower_inbox = follower_inbox
|
||||
@@ -382,7 +381,7 @@ class SqlFederationService:
|
||||
|
||||
async def remove_follower(
|
||||
self, session: AsyncSession, username: str, follower_acct: str,
|
||||
app_domain: str | None = None,
|
||||
app_domain: str = "federation",
|
||||
) -> bool:
|
||||
actor = (
|
||||
await session.execute(
|
||||
@@ -392,16 +391,13 @@ class SqlFederationService:
|
||||
if actor is None:
|
||||
return False
|
||||
|
||||
filters = [
|
||||
APFollower.actor_profile_id == actor.id,
|
||||
APFollower.follower_acct == follower_acct,
|
||||
]
|
||||
if app_domain is not None:
|
||||
filters.append(APFollower.app_domain == app_domain)
|
||||
else:
|
||||
filters.append(APFollower.app_domain.is_(None))
|
||||
|
||||
result = await session.execute(delete(APFollower).where(*filters))
|
||||
result = await session.execute(
|
||||
delete(APFollower).where(
|
||||
APFollower.actor_profile_id == actor.id,
|
||||
APFollower.follower_acct == follower_acct,
|
||||
APFollower.app_domain == app_domain,
|
||||
)
|
||||
)
|
||||
return result.rowcount > 0
|
||||
|
||||
async def get_followers_paginated(
|
||||
|
||||
@@ -235,10 +235,10 @@ class StubFederationService:
|
||||
|
||||
async def add_follower(self, session, username, follower_acct, follower_inbox,
|
||||
follower_actor_url, follower_public_key=None,
|
||||
app_domain=None):
|
||||
app_domain="federation"):
|
||||
raise RuntimeError("FederationService not available")
|
||||
|
||||
async def remove_follower(self, session, username, follower_acct, app_domain=None):
|
||||
async def remove_follower(self, session, username, follower_acct, app_domain="federation"):
|
||||
return False
|
||||
|
||||
async def get_or_fetch_remote_actor(self, session, actor_url):
|
||||
|
||||
Reference in New Issue
Block a user