|
|
|
|
@@ -1,388 +0,0 @@
|
|
|
|
|
"""add target_lists table
|
|
|
|
|
|
|
|
|
|
Revision ID: 853ebea730ab
|
|
|
|
|
Revises: 0f1ca2af3647
|
|
|
|
|
Create Date: 2026-02-17 11:12:14.598660
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
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 = '853ebea730ab'
|
|
|
|
|
down_revision: Union[str, None] = '0f1ca2af3647'
|
|
|
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
|
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def upgrade() -> None:
|
|
|
|
|
# ### commands auto generated by Alembic - please adjust! ###
|
|
|
|
|
op.create_table('target_lists',
|
|
|
|
|
sa.Column('id', sa.UUID(), nullable=False),
|
|
|
|
|
sa.Column('team_id', sa.UUID(), nullable=False),
|
|
|
|
|
sa.Column('created_by', sa.UUID(), nullable=True),
|
|
|
|
|
sa.Column('name', sa.String(length=255), nullable=False),
|
|
|
|
|
sa.Column('description', sa.Text(), nullable=True),
|
|
|
|
|
sa.Column('targets', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
|
|
|
|
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False),
|
|
|
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False),
|
|
|
|
|
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL'),
|
|
|
|
|
sa.ForeignKeyConstraint(['team_id'], ['teams.id'], ondelete='CASCADE'),
|
|
|
|
|
sa.PrimaryKeyConstraint('id')
|
|
|
|
|
)
|
|
|
|
|
op.create_index(op.f('ix_target_lists_team_id'), 'target_lists', ['team_id'], unique=False)
|
|
|
|
|
op.drop_index(op.f('ix_tree_shares_created_by'), table_name='tree_shares')
|
|
|
|
|
op.drop_index(op.f('ix_tree_shares_expires_at'), table_name='tree_shares')
|
|
|
|
|
op.drop_index(op.f('ix_tree_shares_share_token'), table_name='tree_shares')
|
|
|
|
|
op.drop_index(op.f('ix_tree_shares_tree_id'), table_name='tree_shares')
|
|
|
|
|
op.drop_table('tree_shares')
|
|
|
|
|
op.drop_table('_team_account_mapping')
|
|
|
|
|
op.drop_index(op.f('ix_account_feature_overrides_account_id'), table_name='account_feature_overrides')
|
|
|
|
|
op.drop_index(op.f('ix_account_invites_account_id'), table_name='account_invites')
|
|
|
|
|
op.drop_index(op.f('ix_account_invites_email'), table_name='account_invites')
|
|
|
|
|
op.drop_index(op.f('ix_account_limit_overrides_account_id'), table_name='account_limit_overrides')
|
|
|
|
|
op.alter_column('accounts', 'allow_public_shares',
|
|
|
|
|
existing_type=sa.BOOLEAN(),
|
|
|
|
|
comment='Policy: engineers can create public shares. Only affects NEW shares (grandfathered).',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('true'))
|
|
|
|
|
op.alter_column('attachments', 'uploaded_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('audit_logs', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.drop_index(op.f('ix_audit_logs_created_at'), table_name='audit_logs')
|
|
|
|
|
op.drop_index(op.f('ix_plan_feature_defaults_plan'), table_name='plan_feature_defaults')
|
|
|
|
|
op.alter_column('refresh_tokens', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.drop_index(op.f('ix_session_ratings_account_created'), table_name='session_ratings')
|
|
|
|
|
op.drop_index(op.f('ix_session_ratings_tree_created'), table_name='session_ratings')
|
|
|
|
|
op.alter_column('session_share_views', 'session_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment='Denormalized from share for analytics queries',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.alter_column('session_share_views', 'viewer_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment='NULL for public shares (unauthenticated views)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('session_shares', 'account_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment='Account that owns this share (denormalized from session at creation)',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.alter_column('session_shares', 'share_token',
|
|
|
|
|
existing_type=sa.VARCHAR(length=64),
|
|
|
|
|
comment='URL-safe random token (48 bytes -> 64 base64 chars)',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.alter_column('session_shares', 'share_name',
|
|
|
|
|
existing_type=sa.VARCHAR(length=100),
|
|
|
|
|
comment="Optional label: 'Training link', 'Customer escalation #1234'",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('session_shares', 'visibility',
|
|
|
|
|
existing_type=sa.VARCHAR(length=20),
|
|
|
|
|
comment='public = anyone with link, account = account members only',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text("'public'::character varying"))
|
|
|
|
|
op.alter_column('session_shares', 'expires_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
comment='Optional expiration for time-limited shares',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.drop_constraint(op.f('session_shares_share_token_key'), 'session_shares', type_='unique')
|
|
|
|
|
op.drop_index(op.f('ix_session_shares_share_token'), table_name='session_shares')
|
|
|
|
|
op.create_index(op.f('ix_session_shares_share_token'), 'session_shares', ['share_token'], unique=True)
|
|
|
|
|
op.alter_column('sessions', 'started_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('sessions', 'completed_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.drop_index(op.f('ix_sessions_client_name'), table_name='sessions')
|
|
|
|
|
op.drop_index(op.f('ix_sessions_completed'), table_name='sessions')
|
|
|
|
|
op.drop_index(op.f('ix_sessions_ticket_number'), table_name='sessions')
|
|
|
|
|
op.drop_index(op.f('ix_sessions_tree_snapshot_gin'), table_name='sessions', postgresql_using='gin')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_category_id'), table_name='step_library')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_created_by'), table_name='step_library')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_search'), table_name='step_library', postgresql_using='gin')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_tags'), table_name='step_library', postgresql_using='gin')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_team_id'), table_name='step_library')
|
|
|
|
|
op.drop_index(op.f('ix_step_library_visibility'), table_name='step_library', postgresql_where='(is_active = true)')
|
|
|
|
|
op.drop_index(op.f('ix_step_ratings_step_helpful'), table_name='step_ratings')
|
|
|
|
|
op.drop_index(op.f('ix_step_ratings_step_id'), table_name='step_ratings')
|
|
|
|
|
op.drop_index(op.f('ix_step_ratings_user_id'), table_name='step_ratings')
|
|
|
|
|
op.drop_constraint(op.f('uq_step_ratings_step_user'), 'step_ratings', type_='unique')
|
|
|
|
|
op.drop_index(op.f('ix_step_usage_log_step_id'), table_name='step_usage_log')
|
|
|
|
|
op.drop_index(op.f('ix_step_usage_log_user_step'), table_name='step_usage_log')
|
|
|
|
|
op.drop_index(op.f('ix_subscriptions_account_id'), table_name='subscriptions')
|
|
|
|
|
op.drop_index(op.f('ix_subscriptions_plan'), table_name='subscriptions')
|
|
|
|
|
op.alter_column('teams', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('tree_categories', 'color',
|
|
|
|
|
existing_type=sa.VARCHAR(length=7),
|
|
|
|
|
comment='Hex color for category dot indicator',
|
|
|
|
|
existing_nullable=True,
|
|
|
|
|
existing_server_default=sa.text("'#3b82f6'::character varying"))
|
|
|
|
|
op.drop_index(op.f('ix_tree_tag_assignments_tag_id'), table_name='tree_tag_assignments')
|
|
|
|
|
op.drop_index(op.f('ix_tree_tag_assignments_tree_id'), table_name='tree_tag_assignments')
|
|
|
|
|
op.alter_column('trees', 'tree_type',
|
|
|
|
|
existing_type=sa.VARCHAR(length=20),
|
|
|
|
|
comment='Tree type: troubleshooting (branching decision tree) or procedural (linear step-by-step flow)',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text("'troubleshooting'::character varying"))
|
|
|
|
|
op.alter_column('trees', 'intake_form',
|
|
|
|
|
existing_type=postgresql.JSONB(astext_type=sa.Text()),
|
|
|
|
|
comment='Intake form field definitions for procedural flows (JSONB array)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('trees', 'updated_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(),
|
|
|
|
|
type_=sa.DateTime(timezone=True),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('trees', 'fork_reason',
|
|
|
|
|
existing_type=sa.VARCHAR(length=255),
|
|
|
|
|
comment="Brief reason: 'Added Cisco Meraki steps for our network'",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'parent_updated_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
comment="Snapshot of parent's updated_at when fork created. Compare to detect parent updates.",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'root_tree_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment='Original tree at root of fork chain (NULL for non-forked trees)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'fork_depth',
|
|
|
|
|
existing_type=sa.INTEGER(),
|
|
|
|
|
comment='Fork depth: 0 = original, 1 = direct fork, 2 = fork of fork, etc.',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('0'))
|
|
|
|
|
op.drop_index(op.f('idx_trees_fts'), table_name='trees', postgresql_using='gin')
|
|
|
|
|
op.drop_index(op.f('ix_trees_fork_analytics'), table_name='trees')
|
|
|
|
|
op.drop_index(op.f('ix_user_folder_trees_folder_id'), table_name='user_folder_trees')
|
|
|
|
|
op.drop_index(op.f('ix_user_folder_trees_tree_id'), table_name='user_folder_trees')
|
|
|
|
|
op.alter_column('user_pinned_trees', 'pinned_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.drop_index(op.f('idx_user_pinned_trees_tree'), table_name='user_pinned_trees')
|
|
|
|
|
op.drop_index(op.f('idx_user_pinned_trees_user'), table_name='user_pinned_trees')
|
|
|
|
|
op.create_index(op.f('ix_user_pinned_trees_tree_id'), 'user_pinned_trees', ['tree_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_user_pinned_trees_user_id'), 'user_pinned_trees', ['user_id'], unique=False)
|
|
|
|
|
op.alter_column('users', 'account_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
nullable=True)
|
|
|
|
|
op.drop_constraint(op.f('fk_users_account_id'), 'users', type_='foreignkey')
|
|
|
|
|
op.create_foreign_key(None, 'users', 'accounts', ['account_id'], ['id'], ondelete='RESTRICT')
|
|
|
|
|
# ### end Alembic commands ###
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def downgrade() -> None:
|
|
|
|
|
# ### commands auto generated by Alembic - please adjust! ###
|
|
|
|
|
op.drop_constraint(None, 'users', type_='foreignkey')
|
|
|
|
|
op.create_foreign_key(op.f('fk_users_account_id'), 'users', 'accounts', ['account_id'], ['id'], ondelete='CASCADE')
|
|
|
|
|
op.alter_column('users', 'account_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
nullable=False)
|
|
|
|
|
op.drop_index(op.f('ix_user_pinned_trees_user_id'), table_name='user_pinned_trees')
|
|
|
|
|
op.drop_index(op.f('ix_user_pinned_trees_tree_id'), table_name='user_pinned_trees')
|
|
|
|
|
op.create_index(op.f('idx_user_pinned_trees_user'), 'user_pinned_trees', ['user_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('idx_user_pinned_trees_tree'), 'user_pinned_trees', ['tree_id'], unique=False)
|
|
|
|
|
op.alter_column('user_pinned_trees', 'pinned_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=True,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.create_index(op.f('ix_user_folder_trees_tree_id'), 'user_folder_trees', ['tree_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_user_folder_trees_folder_id'), 'user_folder_trees', ['folder_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_trees_fork_analytics'), 'trees', ['root_tree_id', 'fork_depth'], unique=False)
|
|
|
|
|
op.create_index(op.f('idx_trees_fts'), 'trees', [sa.literal_column("to_tsvector('english'::regconfig, (COALESCE(name, ''::character varying)::text || ' '::text) || COALESCE(description, ''::text))")], unique=False, postgresql_using='gin')
|
|
|
|
|
op.alter_column('trees', 'fork_depth',
|
|
|
|
|
existing_type=sa.INTEGER(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Fork depth: 0 = original, 1 = direct fork, 2 = fork of fork, etc.',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('0'))
|
|
|
|
|
op.alter_column('trees', 'root_tree_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Original tree at root of fork chain (NULL for non-forked trees)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'parent_updated_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment="Snapshot of parent's updated_at when fork created. Compare to detect parent updates.",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'fork_reason',
|
|
|
|
|
existing_type=sa.VARCHAR(length=255),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment="Brief reason: 'Added Cisco Meraki steps for our network'",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'updated_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('trees', 'created_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('trees', 'intake_form',
|
|
|
|
|
existing_type=postgresql.JSONB(astext_type=sa.Text()),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Intake form field definitions for procedural flows (JSONB array)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('trees', 'tree_type',
|
|
|
|
|
existing_type=sa.VARCHAR(length=20),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Tree type: troubleshooting (branching decision tree) or procedural (linear step-by-step flow)',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text("'troubleshooting'::character varying"))
|
|
|
|
|
op.create_index(op.f('ix_tree_tag_assignments_tree_id'), 'tree_tag_assignments', ['tree_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_tree_tag_assignments_tag_id'), 'tree_tag_assignments', ['tag_id'], unique=False)
|
|
|
|
|
op.alter_column('tree_categories', 'color',
|
|
|
|
|
existing_type=sa.VARCHAR(length=7),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Hex color for category dot indicator',
|
|
|
|
|
existing_nullable=True,
|
|
|
|
|
existing_server_default=sa.text("'#3b82f6'::character varying"))
|
|
|
|
|
op.alter_column('teams', 'created_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.create_index(op.f('ix_subscriptions_plan'), 'subscriptions', ['plan'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_subscriptions_account_id'), 'subscriptions', ['account_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_usage_log_user_step'), 'step_usage_log', ['user_id', 'step_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_usage_log_step_id'), 'step_usage_log', ['step_id'], unique=False)
|
|
|
|
|
op.create_unique_constraint(op.f('uq_step_ratings_step_user'), 'step_ratings', ['step_id', 'user_id'], postgresql_nulls_not_distinct=False)
|
|
|
|
|
op.create_index(op.f('ix_step_ratings_user_id'), 'step_ratings', ['user_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_ratings_step_id'), 'step_ratings', ['step_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_ratings_step_helpful'), 'step_ratings', ['step_id', 'was_helpful'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_library_visibility'), 'step_library', ['visibility'], unique=False, postgresql_where='(is_active = true)')
|
|
|
|
|
op.create_index(op.f('ix_step_library_team_id'), 'step_library', ['team_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_library_tags'), 'step_library', ['tags'], unique=False, postgresql_using='gin')
|
|
|
|
|
op.create_index(op.f('ix_step_library_search'), 'step_library', [sa.literal_column("to_tsvector('english'::regconfig, title::text)")], unique=False, postgresql_using='gin')
|
|
|
|
|
op.create_index(op.f('ix_step_library_created_by'), 'step_library', ['created_by'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_step_library_category_id'), 'step_library', ['category_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_sessions_tree_snapshot_gin'), 'sessions', ['tree_snapshot'], unique=False, postgresql_using='gin')
|
|
|
|
|
op.create_index(op.f('ix_sessions_ticket_number'), 'sessions', ['ticket_number'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_sessions_completed'), 'sessions', ['completed_at'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_sessions_client_name'), 'sessions', ['client_name'], unique=False)
|
|
|
|
|
op.alter_column('sessions', 'completed_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('sessions', 'started_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.drop_index(op.f('ix_session_shares_share_token'), table_name='session_shares')
|
|
|
|
|
op.create_index(op.f('ix_session_shares_share_token'), 'session_shares', ['share_token'], unique=False)
|
|
|
|
|
op.create_unique_constraint(op.f('session_shares_share_token_key'), 'session_shares', ['share_token'], postgresql_nulls_not_distinct=False)
|
|
|
|
|
op.alter_column('session_shares', 'expires_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Optional expiration for time-limited shares',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('session_shares', 'visibility',
|
|
|
|
|
existing_type=sa.VARCHAR(length=20),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='public = anyone with link, account = account members only',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text("'public'::character varying"))
|
|
|
|
|
op.alter_column('session_shares', 'share_name',
|
|
|
|
|
existing_type=sa.VARCHAR(length=100),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment="Optional label: 'Training link', 'Customer escalation #1234'",
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('session_shares', 'share_token',
|
|
|
|
|
existing_type=sa.VARCHAR(length=64),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='URL-safe random token (48 bytes -> 64 base64 chars)',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.alter_column('session_shares', 'account_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Account that owns this share (denormalized from session at creation)',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.alter_column('session_share_views', 'viewer_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='NULL for public shares (unauthenticated views)',
|
|
|
|
|
existing_nullable=True)
|
|
|
|
|
op.alter_column('session_share_views', 'session_id',
|
|
|
|
|
existing_type=sa.UUID(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Denormalized from share for analytics queries',
|
|
|
|
|
existing_nullable=False)
|
|
|
|
|
op.create_index(op.f('ix_session_ratings_tree_created'), 'session_ratings', ['tree_id', 'created_at'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_session_ratings_account_created'), 'session_ratings', ['account_id', 'created_at'], unique=False)
|
|
|
|
|
op.alter_column('refresh_tokens', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=True,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.create_index(op.f('ix_plan_feature_defaults_plan'), 'plan_feature_defaults', ['plan'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_audit_logs_created_at'), 'audit_logs', ['created_at'], unique=False)
|
|
|
|
|
op.alter_column('audit_logs', 'created_at',
|
|
|
|
|
existing_type=postgresql.TIMESTAMP(timezone=True),
|
|
|
|
|
nullable=True,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('attachments', 'uploaded_at',
|
|
|
|
|
existing_type=sa.DateTime(timezone=True),
|
|
|
|
|
type_=postgresql.TIMESTAMP(),
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('now()'))
|
|
|
|
|
op.alter_column('accounts', 'allow_public_shares',
|
|
|
|
|
existing_type=sa.BOOLEAN(),
|
|
|
|
|
comment=None,
|
|
|
|
|
existing_comment='Policy: engineers can create public shares. Only affects NEW shares (grandfathered).',
|
|
|
|
|
existing_nullable=False,
|
|
|
|
|
existing_server_default=sa.text('true'))
|
|
|
|
|
op.create_index(op.f('ix_account_limit_overrides_account_id'), 'account_limit_overrides', ['account_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_account_invites_email'), 'account_invites', ['email'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_account_invites_account_id'), 'account_invites', ['account_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_account_feature_overrides_account_id'), 'account_feature_overrides', ['account_id'], unique=False)
|
|
|
|
|
op.create_table('_team_account_mapping',
|
|
|
|
|
sa.Column('team_id', sa.UUID(), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('account_id', sa.UUID(), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('owner_user_id', sa.UUID(), autoincrement=False, nullable=True),
|
|
|
|
|
sa.PrimaryKeyConstraint('team_id', name=op.f('_team_account_mapping_pkey'))
|
|
|
|
|
)
|
|
|
|
|
op.create_table('tree_shares',
|
|
|
|
|
sa.Column('id', sa.UUID(), server_default=sa.text('gen_random_uuid()'), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('tree_id', sa.UUID(), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('share_token', sa.VARCHAR(length=64), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('created_by', sa.UUID(), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('allow_forking', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False),
|
|
|
|
|
sa.Column('expires_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
|
|
|
|
|
sa.ForeignKeyConstraint(['created_by'], ['users.id'], name=op.f('tree_shares_created_by_fkey'), ondelete='CASCADE'),
|
|
|
|
|
sa.ForeignKeyConstraint(['tree_id'], ['trees.id'], name=op.f('tree_shares_tree_id_fkey'), ondelete='CASCADE'),
|
|
|
|
|
sa.PrimaryKeyConstraint('id', name=op.f('tree_shares_pkey')),
|
|
|
|
|
sa.UniqueConstraint('share_token', name=op.f('tree_shares_share_token_key'), postgresql_include=[], postgresql_nulls_not_distinct=False)
|
|
|
|
|
)
|
|
|
|
|
op.create_index(op.f('ix_tree_shares_tree_id'), 'tree_shares', ['tree_id'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_tree_shares_share_token'), 'tree_shares', ['share_token'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_tree_shares_expires_at'), 'tree_shares', ['expires_at'], unique=False)
|
|
|
|
|
op.create_index(op.f('ix_tree_shares_created_by'), 'tree_shares', ['created_by'], unique=False)
|
|
|
|
|
op.drop_index(op.f('ix_target_lists_team_id'), table_name='target_lists')
|
|
|
|
|
op.drop_table('target_lists')
|
|
|
|
|
# ### end Alembic commands ###
|