Add fediverse social features: followers/following lists, actor timelines
Adds get_followers_paginated and get_actor_timeline to FederationService protocol + SQL implementation + stubs. Includes accumulated federation changes: models, DTOs, delivery handler, webfinger, inline publishing, widget nav templates, and migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
138
alembic/versions/l2j0g6h8i9_add_fediverse_tables.py
Normal file
138
alembic/versions/l2j0g6h8i9_add_fediverse_tables.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""add fediverse social tables
|
||||
|
||||
Revision ID: l2j0g6h8i9
|
||||
Revises: k1i9f5g7h8
|
||||
Create Date: 2026-02-22
|
||||
|
||||
Creates:
|
||||
- ap_remote_actors — cached profiles of remote actors
|
||||
- ap_following — outbound follows (local → remote)
|
||||
- ap_remote_posts — ingested posts from remote actors
|
||||
- ap_local_posts — native posts composed in federation UI
|
||||
- ap_interactions — likes and boosts
|
||||
- ap_notifications — follow/like/boost/mention/reply notifications
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
revision = "l2j0g6h8i9"
|
||||
down_revision = "k1i9f5g7h8"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# -- ap_remote_actors --
|
||||
op.create_table(
|
||||
"ap_remote_actors",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("actor_url", sa.String(512), unique=True, nullable=False),
|
||||
sa.Column("inbox_url", sa.String(512), nullable=False),
|
||||
sa.Column("shared_inbox_url", sa.String(512), nullable=True),
|
||||
sa.Column("preferred_username", sa.String(255), nullable=False),
|
||||
sa.Column("display_name", sa.String(255), nullable=True),
|
||||
sa.Column("summary", sa.Text, nullable=True),
|
||||
sa.Column("icon_url", sa.String(512), nullable=True),
|
||||
sa.Column("public_key_pem", sa.Text, nullable=True),
|
||||
sa.Column("domain", sa.String(255), nullable=False),
|
||||
sa.Column("fetched_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_ap_remote_actor_url", "ap_remote_actors", ["actor_url"], unique=True)
|
||||
op.create_index("ix_ap_remote_actor_domain", "ap_remote_actors", ["domain"])
|
||||
|
||||
# -- ap_following --
|
||||
op.create_table(
|
||||
"ap_following",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("actor_profile_id", sa.Integer, sa.ForeignKey("ap_actor_profiles.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("remote_actor_id", sa.Integer, sa.ForeignKey("ap_remote_actors.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("state", sa.String(20), nullable=False, server_default="pending"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("accepted_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.UniqueConstraint("actor_profile_id", "remote_actor_id", name="uq_following"),
|
||||
)
|
||||
op.create_index("ix_ap_following_actor", "ap_following", ["actor_profile_id"])
|
||||
op.create_index("ix_ap_following_remote", "ap_following", ["remote_actor_id"])
|
||||
|
||||
# -- ap_remote_posts --
|
||||
op.create_table(
|
||||
"ap_remote_posts",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("remote_actor_id", sa.Integer, sa.ForeignKey("ap_remote_actors.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("activity_id", sa.String(512), unique=True, nullable=False),
|
||||
sa.Column("object_id", sa.String(512), unique=True, nullable=False),
|
||||
sa.Column("object_type", sa.String(64), nullable=False, server_default="Note"),
|
||||
sa.Column("content", sa.Text, nullable=True),
|
||||
sa.Column("summary", sa.Text, nullable=True),
|
||||
sa.Column("url", sa.String(512), nullable=True),
|
||||
sa.Column("attachment_data", JSONB, nullable=True),
|
||||
sa.Column("tag_data", JSONB, nullable=True),
|
||||
sa.Column("in_reply_to", sa.String(512), nullable=True),
|
||||
sa.Column("conversation", sa.String(512), nullable=True),
|
||||
sa.Column("published", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("fetched_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_ap_remote_post_actor", "ap_remote_posts", ["remote_actor_id"])
|
||||
op.create_index("ix_ap_remote_post_published", "ap_remote_posts", ["published"])
|
||||
op.create_index("ix_ap_remote_post_object", "ap_remote_posts", ["object_id"], unique=True)
|
||||
|
||||
# -- ap_local_posts --
|
||||
op.create_table(
|
||||
"ap_local_posts",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("actor_profile_id", sa.Integer, sa.ForeignKey("ap_actor_profiles.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("content", sa.Text, nullable=False),
|
||||
sa.Column("visibility", sa.String(20), nullable=False, server_default="public"),
|
||||
sa.Column("in_reply_to", sa.String(512), nullable=True),
|
||||
sa.Column("published", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_ap_local_post_actor", "ap_local_posts", ["actor_profile_id"])
|
||||
op.create_index("ix_ap_local_post_published", "ap_local_posts", ["published"])
|
||||
|
||||
# -- ap_interactions --
|
||||
op.create_table(
|
||||
"ap_interactions",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("actor_profile_id", sa.Integer, sa.ForeignKey("ap_actor_profiles.id", ondelete="CASCADE"), nullable=True),
|
||||
sa.Column("remote_actor_id", sa.Integer, sa.ForeignKey("ap_remote_actors.id", ondelete="CASCADE"), nullable=True),
|
||||
sa.Column("post_type", sa.String(20), nullable=False),
|
||||
sa.Column("post_id", sa.Integer, nullable=False),
|
||||
sa.Column("interaction_type", sa.String(20), nullable=False),
|
||||
sa.Column("activity_id", sa.String(512), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_ap_interaction_post", "ap_interactions", ["post_type", "post_id"])
|
||||
op.create_index("ix_ap_interaction_actor", "ap_interactions", ["actor_profile_id"])
|
||||
op.create_index("ix_ap_interaction_remote", "ap_interactions", ["remote_actor_id"])
|
||||
|
||||
# -- ap_notifications --
|
||||
op.create_table(
|
||||
"ap_notifications",
|
||||
sa.Column("id", sa.Integer, primary_key=True, autoincrement=True),
|
||||
sa.Column("actor_profile_id", sa.Integer, sa.ForeignKey("ap_actor_profiles.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("notification_type", sa.String(20), nullable=False),
|
||||
sa.Column("from_remote_actor_id", sa.Integer, sa.ForeignKey("ap_remote_actors.id", ondelete="SET NULL"), nullable=True),
|
||||
sa.Column("from_actor_profile_id", sa.Integer, sa.ForeignKey("ap_actor_profiles.id", ondelete="SET NULL"), nullable=True),
|
||||
sa.Column("target_activity_id", sa.Integer, sa.ForeignKey("ap_activities.id", ondelete="SET NULL"), nullable=True),
|
||||
sa.Column("target_remote_post_id", sa.Integer, sa.ForeignKey("ap_remote_posts.id", ondelete="SET NULL"), nullable=True),
|
||||
sa.Column("read", sa.Boolean, nullable=False, server_default="false"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
op.create_index("ix_ap_notification_actor", "ap_notifications", ["actor_profile_id"])
|
||||
op.create_index("ix_ap_notification_read", "ap_notifications", ["actor_profile_id", "read"])
|
||||
op.create_index("ix_ap_notification_created", "ap_notifications", ["created_at"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("ap_notifications")
|
||||
op.drop_table("ap_interactions")
|
||||
op.drop_table("ap_local_posts")
|
||||
op.drop_table("ap_remote_posts")
|
||||
op.drop_table("ap_following")
|
||||
op.drop_table("ap_remote_actors")
|
||||
Reference in New Issue
Block a user