Add per-app ActivityPub actors via shared AP blueprint

Each AP-enabled app (blog, market, events, federation) now serves its
own webfinger, actor profile, inbox, outbox, and followers endpoints.
Per-app actors are virtual projections of the same ActorProfile/keypair,
scoped by APFollower.app_domain and APActivity.origin_app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-23 19:02:30 +00:00
parent 001cbffd74
commit f2262f702b
10 changed files with 1087 additions and 35 deletions

View File

@@ -127,7 +127,11 @@ class APActivity(Base):
class APFollower(Base):
"""A remote follower of a local actor."""
"""A remote follower of a local actor.
``app_domain`` scopes the follow to a specific app (e.g. "blog").
NULL means the follower subscribes to the aggregate (all activities).
"""
__tablename__ = "ap_followers"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
@@ -138,6 +142,7 @@ class APFollower(Base):
follower_inbox: Mapped[str] = mapped_column(String(512), nullable=False)
follower_actor_url: Mapped[str] = mapped_column(String(512), nullable=False)
follower_public_key: Mapped[str | None] = mapped_column(Text, nullable=True)
app_domain: Mapped[str | None] = mapped_column(String(64), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), nullable=False, server_default=func.now(),
)
@@ -146,8 +151,12 @@ class APFollower(Base):
actor_profile = relationship("ActorProfile", back_populates="followers")
__table_args__ = (
UniqueConstraint("actor_profile_id", "follower_acct", name="uq_follower_acct"),
UniqueConstraint(
"actor_profile_id", "follower_acct", "app_domain",
name="uq_follower_acct_app",
),
Index("ix_ap_follower_actor", "actor_profile_id"),
Index("ix_ap_follower_app_domain", "actor_profile_id", "app_domain"),
)
def __repr__(self) -> str: