"""add social graph tables (friendships, follows, blocks) Revision ID: 0007_add_social_graph_tables Revises: 0006_add_presence_columns Create Date: 2026-09-15 00:00:00.000000 docs/planning/social.md US-1 / app/models/social.py. Models + migration only -- the request/accept/block business logic lives in the sibling "Friends & follow API router" task. """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = "0007_add_social_graph_tables" down_revision: Union[str, None] = "0006_add_presence_columns" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: friendship_status = postgresql.ENUM( "pending", "accepted", name="friendship_status" ) friendship_status.create(op.get_bind(), checkfirst=True) op.create_table( "friendships", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "requester_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "addressee_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "status", postgresql.ENUM( "pending", "accepted", name="friendship_status", create_type=False ), nullable=False, server_default="pending", ), 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), sa.UniqueConstraint("requester_id", "addressee_id", name="uq_friendships_pair"), ) op.create_index("ix_friendships_requester_id", "friendships", ["requester_id"]) op.create_index("ix_friendships_addressee_id", "friendships", ["addressee_id"]) op.create_table( "follows", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "follower_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "followed_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), 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), sa.UniqueConstraint("follower_id", "followed_id", name="uq_follows_pair"), ) op.create_index("ix_follows_follower_id", "follows", ["follower_id"]) op.create_index("ix_follows_followed_id", "follows", ["followed_id"]) op.create_table( "blocks", sa.Column("id", sa.Integer(), primary_key=True), sa.Column( "blocker_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "blocked_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), 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), sa.UniqueConstraint("blocker_id", "blocked_id", name="uq_blocks_pair"), ) op.create_index("ix_blocks_blocker_id", "blocks", ["blocker_id"]) op.create_index("ix_blocks_blocked_id", "blocks", ["blocked_id"]) def downgrade() -> None: op.drop_index("ix_blocks_blocked_id", table_name="blocks") op.drop_index("ix_blocks_blocker_id", table_name="blocks") op.drop_table("blocks") op.drop_index("ix_follows_followed_id", table_name="follows") op.drop_index("ix_follows_follower_id", table_name="follows") op.drop_table("follows") op.drop_index("ix_friendships_addressee_id", table_name="friendships") op.drop_index("ix_friendships_requester_id", table_name="friendships") op.drop_table("friendships") postgresql.ENUM(name="friendship_status").drop(op.get_bind(), checkfirst=True)