"""add federation tables Revision ID: k1i9f5g7h8 Revises: j0h8e4f6g7 Create Date: 2026-02-21 Creates: - ap_actor_profiles — AP identity per user - ap_activities — local + remote AP activities - ap_followers — remote followers - ap_inbox_items — raw incoming AP activities - ap_anchors — OpenTimestamps merkle batches - ipfs_pins — IPFS content tracking (platform-wide) """ from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql revision = "k1i9f5g7h8" down_revision = "j0h8e4f6g7" branch_labels = None depends_on = None def upgrade() -> None: # -- ap_anchors (referenced by ap_activities) ---------------------------- op.create_table( "ap_anchors", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("merkle_root", sa.String(128), nullable=False), sa.Column("tree_ipfs_cid", sa.String(128), nullable=True), sa.Column("ots_proof_cid", sa.String(128), nullable=True), sa.Column("activity_count", sa.Integer(), nullable=False, server_default="0"), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("confirmed_at", sa.DateTime(timezone=True), nullable=True), sa.Column("bitcoin_txid", sa.String(128), nullable=True), sa.PrimaryKeyConstraint("id"), ) # -- ap_actor_profiles --------------------------------------------------- op.create_table( "ap_actor_profiles", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("preferred_username", sa.String(64), nullable=False), sa.Column("display_name", sa.String(255), nullable=True), sa.Column("summary", sa.Text(), nullable=True), sa.Column("public_key_pem", sa.Text(), nullable=False), sa.Column("private_key_pem", sa.Text(), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("preferred_username"), sa.UniqueConstraint("user_id"), ) op.create_index("ix_ap_actor_user_id", "ap_actor_profiles", ["user_id"], unique=True) op.create_index("ix_ap_actor_username", "ap_actor_profiles", ["preferred_username"], unique=True) # -- ap_activities ------------------------------------------------------- op.create_table( "ap_activities", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("activity_id", sa.String(512), nullable=False), sa.Column("activity_type", sa.String(64), nullable=False), sa.Column("actor_profile_id", sa.Integer(), nullable=False), sa.Column("object_type", sa.String(64), nullable=True), sa.Column("object_data", postgresql.JSONB(), nullable=True), sa.Column("published", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("signature", postgresql.JSONB(), nullable=True), sa.Column("is_local", sa.Boolean(), nullable=False, server_default="true"), sa.Column("source_type", sa.String(64), nullable=True), sa.Column("source_id", sa.Integer(), nullable=True), sa.Column("ipfs_cid", sa.String(128), nullable=True), sa.Column("anchor_id", sa.Integer(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.ForeignKeyConstraint(["actor_profile_id"], ["ap_actor_profiles.id"], ondelete="CASCADE"), sa.ForeignKeyConstraint(["anchor_id"], ["ap_anchors.id"], ondelete="SET NULL"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("activity_id"), ) op.create_index("ix_ap_activity_actor", "ap_activities", ["actor_profile_id"]) op.create_index("ix_ap_activity_source", "ap_activities", ["source_type", "source_id"]) op.create_index("ix_ap_activity_published", "ap_activities", ["published"]) # -- ap_followers -------------------------------------------------------- op.create_table( "ap_followers", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("actor_profile_id", sa.Integer(), nullable=False), sa.Column("follower_acct", sa.String(512), nullable=False), sa.Column("follower_inbox", sa.String(512), nullable=False), sa.Column("follower_actor_url", sa.String(512), nullable=False), sa.Column("follower_public_key", sa.Text(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.ForeignKeyConstraint(["actor_profile_id"], ["ap_actor_profiles.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("actor_profile_id", "follower_acct", name="uq_follower_acct"), ) op.create_index("ix_ap_follower_actor", "ap_followers", ["actor_profile_id"]) # -- ap_inbox_items ------------------------------------------------------ op.create_table( "ap_inbox_items", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("actor_profile_id", sa.Integer(), nullable=False), sa.Column("raw_json", postgresql.JSONB(), nullable=False), sa.Column("activity_type", sa.String(64), nullable=True), sa.Column("from_actor", sa.String(512), nullable=True), sa.Column("state", sa.String(20), nullable=False, server_default="pending"), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.Column("processed_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint(["actor_profile_id"], ["ap_actor_profiles.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_index("ix_ap_inbox_state", "ap_inbox_items", ["state"]) op.create_index("ix_ap_inbox_actor", "ap_inbox_items", ["actor_profile_id"]) # -- ipfs_pins ----------------------------------------------------------- op.create_table( "ipfs_pins", sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("content_hash", sa.String(128), nullable=False), sa.Column("ipfs_cid", sa.String(128), nullable=False), sa.Column("pin_type", sa.String(64), nullable=False), sa.Column("source_type", sa.String(64), nullable=True), sa.Column("source_id", sa.Integer(), nullable=True), sa.Column("size_bytes", sa.BigInteger(), nullable=True), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("ipfs_cid"), ) op.create_index("ix_ipfs_pin_source", "ipfs_pins", ["source_type", "source_id"]) op.create_index("ix_ipfs_pin_cid", "ipfs_pins", ["ipfs_cid"], unique=True) def downgrade() -> None: op.drop_table("ipfs_pins") op.drop_table("ap_inbox_items") op.drop_table("ap_followers") op.drop_table("ap_activities") op.drop_table("ap_actor_profiles") op.drop_table("ap_anchors")