fix: squash Alembic migrations into single complete initial migration (48 tables, 99 TIMESTAMPTZ columns)
This commit is contained in:
parent
ed400e63d8
commit
33aecc0cb1
|
|
@ -1,42 +0,0 @@
|
|||
"""add_missing_sentiment_fields
|
||||
|
||||
Revision ID: 059724556401
|
||||
Revises: a7b9c1d3ef67
|
||||
Create Date: 2026-05-23 17:19:50.789398
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '059724556401'
|
||||
down_revision: Union[str, Sequence[str], None] = 'a7b9c1d3ef67'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# 添加情感分析字段到 citation_records 表
|
||||
op.add_column('citation_records',
|
||||
sa.Column('sentiment', sa.String(20), nullable=True,
|
||||
comment='情感倾向: positive / neutral / negative')
|
||||
)
|
||||
op.add_column('citation_records',
|
||||
sa.Column('sentiment_confidence', sa.Float(), nullable=True,
|
||||
comment='情感分析置信度 0.0-1.0')
|
||||
)
|
||||
op.add_column('citation_records',
|
||||
sa.Column('sentiment_key_phrases', sa.JSON(), nullable=True,
|
||||
comment='关键情感短语列表')
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
op.drop_column('citation_records', 'sentiment_key_phrases')
|
||||
op.drop_column('citation_records', 'sentiment_confidence')
|
||||
op.drop_column('citation_records', 'sentiment')
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
"""Initial migration
|
||||
|
||||
Revision ID: 488d0bd5ab01
|
||||
Revises:
|
||||
Create Date: 2026-04-22 18:06:46.629263
|
||||
|
||||
"""
|
||||
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 = '488d0bd5ab01'
|
||||
down_revision: Union[str, Sequence[str], None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# users table
|
||||
op.create_table(
|
||||
'users',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('email', sa.String(255), nullable=False),
|
||||
sa.Column('password_hash', sa.String(255), nullable=False),
|
||||
sa.Column('name', sa.String(100), nullable=True),
|
||||
sa.Column('plan', sa.String(20), server_default='free', nullable=False),
|
||||
sa.Column('max_queries', sa.Integer(), server_default='5', nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), server_default='true', nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('email')
|
||||
)
|
||||
|
||||
# queries table
|
||||
op.create_table(
|
||||
'queries',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('keyword', sa.String(200), nullable=False),
|
||||
sa.Column('target_brand', sa.String(100), nullable=False),
|
||||
sa.Column('brand_aliases', postgresql.JSONB(), server_default='[]', nullable=False),
|
||||
sa.Column('platforms', postgresql.JSONB(), server_default='["wenxin", "kimi"]', nullable=False),
|
||||
sa.Column('frequency', sa.String(20), server_default='weekly', nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='active', nullable=False),
|
||||
sa.Column('last_queried_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('next_query_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_queries_user_id', 'queries', ['user_id'])
|
||||
op.create_index('idx_queries_status', 'queries', ['status'])
|
||||
op.create_index('idx_queries_next_query_at', 'queries', ['next_query_at'])
|
||||
|
||||
# citation_records table
|
||||
op.create_table(
|
||||
'citation_records',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('query_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('platform', sa.String(50), nullable=False),
|
||||
sa.Column('cited', sa.Boolean(), server_default='false', nullable=False),
|
||||
sa.Column('citation_position', sa.Integer(), nullable=True),
|
||||
sa.Column('citation_text', sa.Text(), nullable=True),
|
||||
sa.Column('competitor_brands', postgresql.JSONB(), server_default='[]', nullable=False),
|
||||
sa.Column('raw_response', sa.Text(), nullable=True),
|
||||
sa.Column('queried_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['query_id'], ['queries.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_citation_records_query_id', 'citation_records', ['query_id'])
|
||||
op.create_index('idx_citation_records_queried_at', 'citation_records', ['queried_at'])
|
||||
op.create_index('idx_citation_records_platform', 'citation_records', ['platform'])
|
||||
|
||||
# query_tasks table
|
||||
op.create_table(
|
||||
'query_tasks',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('query_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('platform', sa.String(50), nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='pending', nullable=False),
|
||||
sa.Column('error_message', sa.Text(), nullable=True),
|
||||
sa.Column('scheduled_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['query_id'], ['queries.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_query_tasks_status', 'query_tasks', ['status'])
|
||||
|
||||
# subscriptions table
|
||||
op.create_table(
|
||||
'subscriptions',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('plan', sa.String(20), nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='active', nullable=False),
|
||||
sa.Column('start_date', sa.Date(), nullable=False),
|
||||
sa.Column('end_date', sa.Date(), nullable=False),
|
||||
sa.Column('amount', sa.Numeric(10, 2), nullable=True),
|
||||
sa.Column('payment_method', sa.String(50), nullable=True),
|
||||
sa.Column('payment_id', sa.String(255), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE')
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
op.drop_table('subscriptions')
|
||||
op.drop_index('idx_query_tasks_status', table_name='query_tasks')
|
||||
op.drop_table('query_tasks')
|
||||
op.drop_index('idx_citation_records_platform', table_name='citation_records')
|
||||
op.drop_index('idx_citation_records_queried_at', table_name='citation_records')
|
||||
op.drop_index('idx_citation_records_query_id', table_name='citation_records')
|
||||
op.drop_table('citation_records')
|
||||
op.drop_index('idx_queries_next_query_at', table_name='queries')
|
||||
op.drop_index('idx_queries_status', table_name='queries')
|
||||
op.drop_index('idx_queries_user_id', table_name='queries')
|
||||
op.drop_table('queries')
|
||||
op.drop_table('users')
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
"""sync_users_nullable_state
|
||||
|
||||
Revision ID: 810a29804f5a
|
||||
Revises: e5f7a9b1cd35
|
||||
Create Date: 2026-05-23 21:45:35.491924
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '810a29804f5a'
|
||||
down_revision: Union[str, Sequence[str], None] = 'e5f7a9b1cd35'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('users', 'email_verified',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=False,
|
||||
existing_server_default=sa.text('false'))
|
||||
op.alter_column('users', 'is_admin',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=False,
|
||||
existing_server_default=sa.text('false'))
|
||||
op.drop_index(op.f('idx_users_organization_id'), table_name='users')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_index(op.f('idx_users_organization_id'), 'users', ['organization_id'], unique=False)
|
||||
op.alter_column('users', 'is_admin',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=True,
|
||||
existing_server_default=sa.text('false'))
|
||||
op.alter_column('users', 'email_verified',
|
||||
existing_type=sa.BOOLEAN(),
|
||||
nullable=True,
|
||||
existing_server_default=sa.text('false'))
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
"""add_citation_source_analysis_fields
|
||||
|
||||
Revision ID: 8ccb553ff975
|
||||
Revises: 059724556401
|
||||
Create Date: 2026-05-23 17:23:03.183460
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '8ccb553ff975'
|
||||
down_revision: Union[str, Sequence[str], None] = '059724556401'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# 数据来源类型标记
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('data_source', sa.String(20), nullable=True,
|
||||
comment='数据来源类型: ai_platform / search_engine / unknown')
|
||||
)
|
||||
# 提取的引用URL列表
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('source_urls', sa.JSON(), nullable=True,
|
||||
comment='提取的引用URL列表')
|
||||
)
|
||||
# 提取的引用来源标题列表
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('source_titles', sa.JSON(), nullable=True,
|
||||
comment='提取的引用来源标题列表')
|
||||
)
|
||||
# 引用出现的上下文片段列表
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('citation_contexts', sa.JSON(), nullable=True,
|
||||
comment='引用出现的上下文片段列表')
|
||||
)
|
||||
# AI回答原始文本
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('ai_response_text', sa.Text(), nullable=True,
|
||||
comment='AI回答原始文本(去掉data_source标记后的纯文本)')
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
op.drop_column('citation_records', 'ai_response_text')
|
||||
op.drop_column('citation_records', 'citation_contexts')
|
||||
op.drop_column('citation_records', 'source_titles')
|
||||
op.drop_column('citation_records', 'source_urls')
|
||||
op.drop_column('citation_records', 'data_source')
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,50 +0,0 @@
|
|||
"""Add distribution_schedules table
|
||||
|
||||
Revision ID: a7b9c1d3ef67
|
||||
Revises: f6g8h0i2de56
|
||||
Create Date: 2026-05-23 15:00:00.000000
|
||||
|
||||
"""
|
||||
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 = 'a7b9c1d3ef67'
|
||||
down_revision: Union[str, Sequence[str], None] = 'f6g8h0i2de56'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add distribution_schedules table."""
|
||||
op.create_table(
|
||||
'distribution_schedules',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('content_title', sa.String(500), nullable=False),
|
||||
sa.Column('content_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('platforms', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('tips', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('status', sa.String(20), server_default='pending', nullable=False),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['content_id'], ['contents.id'], ondelete='SET NULL'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_distribution_schedules_organization_id', 'distribution_schedules', ['organization_id'])
|
||||
op.create_index('idx_distribution_schedules_status', 'distribution_schedules', ['status'])
|
||||
op.create_index('idx_distribution_schedules_created_by', 'distribution_schedules', ['created_by'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove distribution_schedules table."""
|
||||
op.drop_index('idx_distribution_schedules_created_by', table_name='distribution_schedules')
|
||||
op.drop_index('idx_distribution_schedules_status', table_name='distribution_schedules')
|
||||
op.drop_index('idx_distribution_schedules_organization_id', table_name='distribution_schedules')
|
||||
op.drop_table('distribution_schedules')
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
"""Add confidence and match_type to citation_records
|
||||
|
||||
Revision ID: b2c4d6e8fa10
|
||||
Revises: 488d0bd5ab01
|
||||
Create Date: 2026-04-23 16:10:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b2c4d6e8fa10'
|
||||
down_revision: Union[str, Sequence[str], None] = '488d0bd5ab01'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add confidence and match_type columns to citation_records."""
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('confidence', sa.Float(), nullable=True)
|
||||
)
|
||||
op.add_column(
|
||||
'citation_records',
|
||||
sa.Column('match_type', sa.String(20), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove confidence and match_type columns from citation_records."""
|
||||
op.drop_column('citation_records', 'match_type')
|
||||
op.drop_column('citation_records', 'confidence')
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
"""Add user management fields
|
||||
|
||||
Revision ID: c3d5e7f9ab12
|
||||
Revises: b2c4d6e8fa10
|
||||
Create Date: 2026-04-24 10:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'c3d5e7f9ab12'
|
||||
down_revision: Union[str, Sequence[str], None] = 'b2c4d6e8fa10'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add user management fields to users table."""
|
||||
op.add_column('users', sa.Column('email_verified', sa.Boolean(), server_default='false', nullable=False))
|
||||
op.add_column('users', sa.Column('verification_code', sa.String(6), nullable=True))
|
||||
op.add_column('users', sa.Column('verification_code_expires', sa.DateTime(), nullable=True))
|
||||
op.add_column('users', sa.Column('reset_token', sa.String(255), nullable=True))
|
||||
op.add_column('users', sa.Column('reset_token_expires', sa.DateTime(), nullable=True))
|
||||
op.add_column('users', sa.Column('avatar_url', sa.String(500), nullable=True))
|
||||
op.add_column('users', sa.Column('is_admin', sa.Boolean(), server_default='false', nullable=False))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove user management fields from users table."""
|
||||
op.drop_column('users', 'is_admin')
|
||||
op.drop_column('users', 'avatar_url')
|
||||
op.drop_column('users', 'reset_token_expires')
|
||||
op.drop_column('users', 'reset_token')
|
||||
op.drop_column('users', 'verification_code_expires')
|
||||
op.drop_column('users', 'verification_code')
|
||||
op.drop_column('users', 'email_verified')
|
||||
|
|
@ -1,397 +0,0 @@
|
|||
"""Add GEO lifecycle tables
|
||||
|
||||
Revision ID: d4f6g8h0ab23
|
||||
Revises: c3d5e7f9ab12
|
||||
Create Date: 2026-05-23 10:00:00.000000
|
||||
|
||||
"""
|
||||
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 = 'd4f6g8h0ab23'
|
||||
down_revision: Union[str, Sequence[str], None] = 'c3d5e7f9ab12'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add organizations, lifecycle, agent, content, platform_rules, brand_knowledge, keywords tables and modify users."""
|
||||
|
||||
# 1. organizations table (must be created first as other tables reference it)
|
||||
op.create_table(
|
||||
'organizations',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('name', sa.String(100), nullable=False),
|
||||
sa.Column('slug', sa.String(50), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('logo_url', sa.String(500), nullable=True),
|
||||
sa.Column('plan', sa.String(20), server_default='free', nullable=False),
|
||||
sa.Column('max_members', sa.Integer(), server_default='5', nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('slug')
|
||||
)
|
||||
op.create_index('idx_organizations_slug', 'organizations', ['slug'])
|
||||
|
||||
# 2. Modify users table - add organization_id and role
|
||||
op.add_column('users', sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=True))
|
||||
op.add_column('users', sa.Column('role', sa.String(20), server_default='owner', nullable=False))
|
||||
op.create_foreign_key('fk_users_organization_id', 'users', 'organizations', ['organization_id'], ['id'], ondelete='SET NULL')
|
||||
op.create_index('idx_users_organization_id', 'users', ['organization_id'])
|
||||
|
||||
# 3. org_members table
|
||||
op.create_table(
|
||||
'org_members',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('role', sa.String(20), server_default='viewer', nullable=False),
|
||||
sa.Column('joined_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('invited_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_org_members_organization_id', 'org_members', ['organization_id'])
|
||||
op.create_index('idx_org_members_user_id', 'org_members', ['user_id'])
|
||||
op.create_index('idx_org_members_org_user', 'org_members', ['organization_id', 'user_id'], unique=True)
|
||||
|
||||
# 4. lifecycle_projects table
|
||||
op.create_table(
|
||||
'lifecycle_projects',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('brand_name', sa.String(100), nullable=False),
|
||||
sa.Column('brand_aliases', postgresql.JSONB(), server_default='[]', nullable=False),
|
||||
sa.Column('current_stage', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='active', nullable=False),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_lifecycle_projects_organization_id', 'lifecycle_projects', ['organization_id'])
|
||||
op.create_index('idx_lifecycle_projects_status', 'lifecycle_projects', ['status'])
|
||||
op.create_index('idx_lifecycle_projects_brand_name', 'lifecycle_projects', ['brand_name'])
|
||||
|
||||
# 5. project_stages table
|
||||
op.create_table(
|
||||
'project_stages',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('project_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('stage_number', sa.Integer(), nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='pending', nullable=False),
|
||||
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('notes', sa.Text(), nullable=True),
|
||||
sa.Column('metrics', postgresql.JSONB(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['lifecycle_projects.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_project_stages_project_id', 'project_stages', ['project_id'])
|
||||
op.create_index('idx_project_stages_status', 'project_stages', ['status'])
|
||||
op.create_index('idx_project_stages_project_stage', 'project_stages', ['project_id', 'stage_number'], unique=True)
|
||||
|
||||
# 6. agent_registry table
|
||||
op.create_table(
|
||||
'agent_registry',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('name', sa.String(50), nullable=False),
|
||||
sa.Column('display_name', sa.String(100), nullable=True),
|
||||
sa.Column('agent_type', sa.String(50), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('version', sa.String(20), nullable=True),
|
||||
sa.Column('endpoint', sa.String(500), nullable=True),
|
||||
sa.Column('status', sa.String(20), server_default='offline', nullable=False),
|
||||
sa.Column('capabilities', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('last_heartbeat', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('name')
|
||||
)
|
||||
op.create_index('idx_agent_registry_name', 'agent_registry', ['name'])
|
||||
op.create_index('idx_agent_registry_agent_type', 'agent_registry', ['agent_type'])
|
||||
op.create_index('idx_agent_registry_status', 'agent_registry', ['status'])
|
||||
|
||||
# 7. agent_configs table
|
||||
op.create_table(
|
||||
'agent_configs',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('agent_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('config_key', sa.String(100), nullable=False),
|
||||
sa.Column('config_value', postgresql.JSONB(), nullable=False),
|
||||
sa.Column('description', sa.String(500), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['agent_id'], ['agent_registry.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['updated_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_agent_configs_agent_id', 'agent_configs', ['agent_id'])
|
||||
op.create_index('idx_agent_configs_agent_key', 'agent_configs', ['agent_id', 'config_key'], unique=True)
|
||||
|
||||
# 8. agent_tasks table
|
||||
op.create_table(
|
||||
'agent_tasks',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('agent_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('task_type', sa.String(50), nullable=False),
|
||||
sa.Column('status', sa.String(20), server_default='pending', nullable=False),
|
||||
sa.Column('priority', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('input_data', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('output_data', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('error_message', sa.Text(), nullable=True),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('project_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('scheduled_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('started_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('completed_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['agent_id'], ['agent_registry.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['lifecycle_projects.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_agent_tasks_agent_id', 'agent_tasks', ['agent_id'])
|
||||
op.create_index('idx_agent_tasks_status', 'agent_tasks', ['status'])
|
||||
op.create_index('idx_agent_tasks_organization_id', 'agent_tasks', ['organization_id'])
|
||||
op.create_index('idx_agent_tasks_project_id', 'agent_tasks', ['project_id'])
|
||||
op.create_index('idx_agent_tasks_created_by', 'agent_tasks', ['created_by'])
|
||||
op.create_index('idx_agent_tasks_task_type', 'agent_tasks', ['task_type'])
|
||||
|
||||
# 9. agent_task_logs table
|
||||
op.create_table(
|
||||
'agent_task_logs',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('task_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('agent_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('log_level', sa.String(10), nullable=False),
|
||||
sa.Column('message', sa.Text(), nullable=False),
|
||||
sa.Column('metadata', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['task_id'], ['agent_tasks.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['agent_id'], ['agent_registry.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_agent_task_logs_task_id', 'agent_task_logs', ['task_id'])
|
||||
op.create_index('idx_agent_task_logs_agent_id', 'agent_task_logs', ['agent_id'])
|
||||
op.create_index('idx_agent_task_logs_created_at', 'agent_task_logs', ['created_at'])
|
||||
|
||||
# 10. contents table
|
||||
op.create_table(
|
||||
'contents',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('project_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('title', sa.String(500), nullable=False),
|
||||
sa.Column('content_type', sa.String(50), nullable=False),
|
||||
sa.Column('body', sa.Text(), nullable=True),
|
||||
sa.Column('status', sa.String(20), server_default='draft', nullable=False),
|
||||
sa.Column('target_platforms', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('keywords', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('metadata', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('current_version', sa.Integer(), server_default='1', nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['lifecycle_projects.id'], ondelete='SET NULL'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_contents_organization_id', 'contents', ['organization_id'])
|
||||
op.create_index('idx_contents_project_id', 'contents', ['project_id'])
|
||||
op.create_index('idx_contents_status', 'contents', ['status'])
|
||||
op.create_index('idx_contents_content_type', 'contents', ['content_type'])
|
||||
op.create_index('idx_contents_created_by', 'contents', ['created_by'])
|
||||
|
||||
# 11. content_versions table
|
||||
op.create_table(
|
||||
'content_versions',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('content_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('version_number', sa.Integer(), nullable=False),
|
||||
sa.Column('title', sa.String(500), nullable=True),
|
||||
sa.Column('body', sa.Text(), nullable=True),
|
||||
sa.Column('change_summary', sa.String(500), nullable=True),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['content_id'], ['contents.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_content_versions_content_id', 'content_versions', ['content_id'])
|
||||
op.create_index('idx_content_versions_content_version', 'content_versions', ['content_id', 'version_number'], unique=True)
|
||||
|
||||
# 12. content_reviews table
|
||||
op.create_table(
|
||||
'content_reviews',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('content_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('reviewer_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('status', sa.String(20), nullable=False),
|
||||
sa.Column('comments', sa.Text(), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['content_id'], ['contents.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['reviewer_id'], ['users.id'], ondelete='CASCADE')
|
||||
)
|
||||
op.create_index('idx_content_reviews_content_id', 'content_reviews', ['content_id'])
|
||||
op.create_index('idx_content_reviews_reviewer_id', 'content_reviews', ['reviewer_id'])
|
||||
|
||||
# 13. platform_rules table
|
||||
op.create_table(
|
||||
'platform_rules',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('platform', sa.String(50), nullable=False),
|
||||
sa.Column('rule_category', sa.String(50), nullable=False),
|
||||
sa.Column('rule_name', sa.String(200), nullable=False),
|
||||
sa.Column('description', sa.Text(), nullable=True),
|
||||
sa.Column('check_criteria', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('severity', sa.String(20), nullable=False),
|
||||
sa.Column('is_active', sa.Boolean(), server_default='true', nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_index('idx_platform_rules_platform', 'platform_rules', ['platform'])
|
||||
op.create_index('idx_platform_rules_rule_category', 'platform_rules', ['rule_category'])
|
||||
op.create_index('idx_platform_rules_is_active', 'platform_rules', ['is_active'])
|
||||
op.create_index('idx_platform_rules_platform_category', 'platform_rules', ['platform', 'rule_category'])
|
||||
|
||||
# 14. brand_knowledge table
|
||||
op.create_table(
|
||||
'brand_knowledge',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('category', sa.String(50), nullable=False),
|
||||
sa.Column('title', sa.String(200), nullable=False),
|
||||
sa.Column('content', sa.Text(), nullable=False),
|
||||
sa.Column('metadata', postgresql.JSONB(), nullable=True),
|
||||
sa.Column('is_active', sa.Boolean(), server_default='true', nullable=False),
|
||||
sa.Column('created_by', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_brand_knowledge_organization_id', 'brand_knowledge', ['organization_id'])
|
||||
op.create_index('idx_brand_knowledge_category', 'brand_knowledge', ['category'])
|
||||
op.create_index('idx_brand_knowledge_is_active', 'brand_knowledge', ['is_active'])
|
||||
|
||||
# 15. keywords table
|
||||
op.create_table(
|
||||
'keywords',
|
||||
sa.Column('id', postgresql.UUID(as_uuid=True), server_default=sa.text('gen_random_uuid()'), nullable=False),
|
||||
sa.Column('organization_id', postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column('project_id', postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column('keyword', sa.String(200), nullable=False),
|
||||
sa.Column('category', sa.String(50), nullable=False),
|
||||
sa.Column('priority', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('search_volume', sa.Integer(), nullable=True),
|
||||
sa.Column('competition_level', sa.String(20), nullable=True),
|
||||
sa.Column('status', sa.String(20), server_default='active', nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('NOW()'), nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.ForeignKeyConstraint(['organization_id'], ['organizations.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['project_id'], ['lifecycle_projects.id'], ondelete='SET NULL')
|
||||
)
|
||||
op.create_index('idx_keywords_organization_id', 'keywords', ['organization_id'])
|
||||
op.create_index('idx_keywords_project_id', 'keywords', ['project_id'])
|
||||
op.create_index('idx_keywords_category', 'keywords', ['category'])
|
||||
op.create_index('idx_keywords_status', 'keywords', ['status'])
|
||||
op.create_index('idx_keywords_keyword', 'keywords', ['keyword'])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove all GEO lifecycle tables and revert users table changes."""
|
||||
|
||||
# Drop tables in reverse order (respecting foreign key dependencies)
|
||||
op.drop_index('idx_keywords_keyword', table_name='keywords')
|
||||
op.drop_index('idx_keywords_status', table_name='keywords')
|
||||
op.drop_index('idx_keywords_category', table_name='keywords')
|
||||
op.drop_index('idx_keywords_project_id', table_name='keywords')
|
||||
op.drop_index('idx_keywords_organization_id', table_name='keywords')
|
||||
op.drop_table('keywords')
|
||||
|
||||
op.drop_index('idx_brand_knowledge_is_active', table_name='brand_knowledge')
|
||||
op.drop_index('idx_brand_knowledge_category', table_name='brand_knowledge')
|
||||
op.drop_index('idx_brand_knowledge_organization_id', table_name='brand_knowledge')
|
||||
op.drop_table('brand_knowledge')
|
||||
|
||||
op.drop_index('idx_platform_rules_platform_category', table_name='platform_rules')
|
||||
op.drop_index('idx_platform_rules_is_active', table_name='platform_rules')
|
||||
op.drop_index('idx_platform_rules_rule_category', table_name='platform_rules')
|
||||
op.drop_index('idx_platform_rules_platform', table_name='platform_rules')
|
||||
op.drop_table('platform_rules')
|
||||
|
||||
op.drop_index('idx_content_reviews_reviewer_id', table_name='content_reviews')
|
||||
op.drop_index('idx_content_reviews_content_id', table_name='content_reviews')
|
||||
op.drop_table('content_reviews')
|
||||
|
||||
op.drop_index('idx_content_versions_content_version', table_name='content_versions')
|
||||
op.drop_index('idx_content_versions_content_id', table_name='content_versions')
|
||||
op.drop_table('content_versions')
|
||||
|
||||
op.drop_index('idx_contents_created_by', table_name='contents')
|
||||
op.drop_index('idx_contents_content_type', table_name='contents')
|
||||
op.drop_index('idx_contents_status', table_name='contents')
|
||||
op.drop_index('idx_contents_project_id', table_name='contents')
|
||||
op.drop_index('idx_contents_organization_id', table_name='contents')
|
||||
op.drop_table('contents')
|
||||
|
||||
op.drop_index('idx_agent_task_logs_created_at', table_name='agent_task_logs')
|
||||
op.drop_index('idx_agent_task_logs_agent_id', table_name='agent_task_logs')
|
||||
op.drop_index('idx_agent_task_logs_task_id', table_name='agent_task_logs')
|
||||
op.drop_table('agent_task_logs')
|
||||
|
||||
op.drop_index('idx_agent_tasks_task_type', table_name='agent_tasks')
|
||||
op.drop_index('idx_agent_tasks_created_by', table_name='agent_tasks')
|
||||
op.drop_index('idx_agent_tasks_project_id', table_name='agent_tasks')
|
||||
op.drop_index('idx_agent_tasks_organization_id', table_name='agent_tasks')
|
||||
op.drop_index('idx_agent_tasks_status', table_name='agent_tasks')
|
||||
op.drop_index('idx_agent_tasks_agent_id', table_name='agent_tasks')
|
||||
op.drop_table('agent_tasks')
|
||||
|
||||
op.drop_index('idx_agent_configs_agent_key', table_name='agent_configs')
|
||||
op.drop_index('idx_agent_configs_agent_id', table_name='agent_configs')
|
||||
op.drop_table('agent_configs')
|
||||
|
||||
op.drop_index('idx_agent_registry_status', table_name='agent_registry')
|
||||
op.drop_index('idx_agent_registry_agent_type', table_name='agent_registry')
|
||||
op.drop_index('idx_agent_registry_name', table_name='agent_registry')
|
||||
op.drop_table('agent_registry')
|
||||
|
||||
op.drop_index('idx_project_stages_project_stage', table_name='project_stages')
|
||||
op.drop_index('idx_project_stages_status', table_name='project_stages')
|
||||
op.drop_index('idx_project_stages_project_id', table_name='project_stages')
|
||||
op.drop_table('project_stages')
|
||||
|
||||
op.drop_index('idx_lifecycle_projects_brand_name', table_name='lifecycle_projects')
|
||||
op.drop_index('idx_lifecycle_projects_status', table_name='lifecycle_projects')
|
||||
op.drop_index('idx_lifecycle_projects_organization_id', table_name='lifecycle_projects')
|
||||
op.drop_table('lifecycle_projects')
|
||||
|
||||
op.drop_index('idx_org_members_org_user', table_name='org_members')
|
||||
op.drop_index('idx_org_members_user_id', table_name='org_members')
|
||||
op.drop_index('idx_org_members_organization_id', table_name='org_members')
|
||||
op.drop_table('org_members')
|
||||
|
||||
# Revert users table changes
|
||||
op.drop_index('idx_users_organization_id', table_name='users')
|
||||
op.drop_constraint('fk_users_organization_id', 'users', type_='foreignkey')
|
||||
op.drop_column('users', 'role')
|
||||
op.drop_column('users', 'organization_id')
|
||||
|
||||
op.drop_index('idx_organizations_slug', table_name='organizations')
|
||||
op.drop_table('organizations')
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
"""Add alerts and alert_settings tables
|
||||
|
||||
Revision ID: e5f7a9b1cd34
|
||||
Revises: 8ccb553ff975
|
||||
Create Date: 2026-05-20 10:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'e5f7a9b1cd34'
|
||||
down_revision: Union[str, Sequence[str], None] = '8ccb553ff975'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Create alerts and alert_settings tables."""
|
||||
# 创建 alerts 表
|
||||
op.create_table(
|
||||
'alerts',
|
||||
sa.Column('id', sa.Uuid(as_uuid=True), primary_key=True),
|
||||
sa.Column('brand_id', sa.Uuid(as_uuid=True),
|
||||
sa.ForeignKey('brands.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(as_uuid=True), nullable=False),
|
||||
sa.Column('alert_type', sa.String(50), nullable=False,
|
||||
comment='告警类型: score_drop / score_rise / negative_sentiment / competitor_overtake / new_platform_mention'),
|
||||
sa.Column('severity', sa.String(20), nullable=False,
|
||||
comment='严重程度: critical / warning / info'),
|
||||
sa.Column('title', sa.String(200), nullable=False),
|
||||
sa.Column('message', sa.Text(), nullable=False),
|
||||
sa.Column('data', sa.JSON(), nullable=True,
|
||||
comment='告警相关数据(JSON)'),
|
||||
sa.Column('is_read', sa.Boolean(), default=False, nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
)
|
||||
|
||||
# 创建 alerts 表的索引
|
||||
op.create_index('idx_alerts_user_id', 'alerts', ['user_id'])
|
||||
op.create_index('idx_alerts_brand_id', 'alerts', ['brand_id'])
|
||||
op.create_index('idx_alerts_alert_type', 'alerts', ['alert_type'])
|
||||
op.create_index('idx_alerts_is_read', 'alerts', ['is_read'])
|
||||
op.create_index('idx_alerts_created_at', 'alerts', ['created_at'])
|
||||
op.create_index('idx_alerts_user_read', 'alerts', ['user_id', 'is_read'])
|
||||
|
||||
# 创建 alert_settings 表
|
||||
op.create_table(
|
||||
'alert_settings',
|
||||
sa.Column('id', sa.Uuid(as_uuid=True), primary_key=True),
|
||||
sa.Column('brand_id', sa.Uuid(as_uuid=True),
|
||||
sa.ForeignKey('brands.id', ondelete='CASCADE'), nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(as_uuid=True), nullable=False),
|
||||
sa.Column('alert_type', sa.String(50), nullable=False,
|
||||
comment='告警类型: score_drop / score_rise / negative_sentiment / competitor_overtake / new_platform_mention'),
|
||||
sa.Column('enabled', sa.Boolean(), default=True, nullable=False),
|
||||
sa.Column('threshold', sa.Float(), nullable=True,
|
||||
comment='阈值(如评分下降超过5分触发)'),
|
||||
sa.Column('created_at', sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column('updated_at', sa.DateTime(), server_default=sa.func.now(), onupdate=sa.func.now(), nullable=False),
|
||||
)
|
||||
|
||||
# 创建 alert_settings 表的索引
|
||||
op.create_index('idx_alert_settings_brand_id', 'alert_settings', ['brand_id'])
|
||||
op.create_index('idx_alert_settings_user_id', 'alert_settings', ['user_id'])
|
||||
op.create_index('idx_alert_settings_brand_type', 'alert_settings', ['brand_id', 'alert_type'], unique=True)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Drop alerts and alert_settings tables."""
|
||||
op.drop_index('idx_alert_settings_brand_type', table_name='alert_settings')
|
||||
op.drop_index('idx_alert_settings_user_id', table_name='alert_settings')
|
||||
op.drop_index('idx_alert_settings_brand_id', table_name='alert_settings')
|
||||
op.drop_table('alert_settings')
|
||||
|
||||
op.drop_index('idx_alerts_user_read', table_name='alerts')
|
||||
op.drop_index('idx_alerts_created_at', table_name='alerts')
|
||||
op.drop_index('idx_alerts_is_read', table_name='alerts')
|
||||
op.drop_index('idx_alerts_alert_type', table_name='alerts')
|
||||
op.drop_index('idx_alerts_brand_id', table_name='alerts')
|
||||
op.drop_index('idx_alerts_user_id', table_name='alerts')
|
||||
op.drop_table('alerts')
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
"""add suggestions table
|
||||
|
||||
Revision ID: e5f7a9b1cd35
|
||||
Revises: e5f7a9b1cd34
|
||||
Create Date: 2025-01-20 10:00:00.000000
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
# revision identifiers
|
||||
revision = "e5f7a9b1cd35"
|
||||
down_revision = "e5f7a9b1cd34"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"suggestions",
|
||||
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("brand_id", UUID(as_uuid=True), sa.ForeignKey("brands.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("type", sa.String(50), nullable=False, comment="建议类型"),
|
||||
sa.Column("priority", sa.String(20), nullable=False, server_default="medium", comment="优先级"),
|
||||
sa.Column("title", sa.String(200), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=False),
|
||||
sa.Column("action", sa.Text, nullable=True, comment="具体操作步骤"),
|
||||
sa.Column("expected_impact", sa.String(200), nullable=True, comment="预期效果"),
|
||||
sa.Column("difficulty", sa.String(20), nullable=False, server_default="medium", comment="难度"),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default="pending", comment="状态"),
|
||||
sa.Column("generated_at", sa.DateTime, server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime, server_default=sa.func.now(), onupdate=sa.func.now(), nullable=False),
|
||||
sa.Column("batch_id", UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("source", sa.String(20), nullable=False, server_default="rule", comment="生成来源"),
|
||||
)
|
||||
|
||||
op.create_index("idx_suggestions_brand_id", "suggestions", ["brand_id"])
|
||||
op.create_index("idx_suggestions_status", "suggestions", ["status"])
|
||||
op.create_index("idx_suggestions_type", "suggestions", ["type"])
|
||||
op.create_index("idx_suggestions_batch_id", "suggestions", ["batch_id"])
|
||||
op.create_index("idx_suggestions_brand_status", "suggestions", ["brand_id", "status"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_suggestions_brand_status", table_name="suggestions")
|
||||
op.drop_index("idx_suggestions_batch_id", table_name="suggestions")
|
||||
op.drop_index("idx_suggestions_type", table_name="suggestions")
|
||||
op.drop_index("idx_suggestions_status", table_name="suggestions")
|
||||
op.drop_index("idx_suggestions_brand_id", table_name="suggestions")
|
||||
op.drop_table("suggestions")
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
"""Add knowledge base tables with pgvector support
|
||||
|
||||
Revision ID: e5f7g9h1cd45
|
||||
Revises: d4f6g8h0ab23
|
||||
Create Date: 2026-05-23 12:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "e5f7g9h1cd45"
|
||||
down_revision: Union[str, None] = "d4f6g8h0ab23"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ------------------------------------------------------------------ #
|
||||
# 1. Enable pgvector extension
|
||||
# ------------------------------------------------------------------ #
|
||||
op.execute("CREATE EXTENSION IF NOT EXISTS vector")
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 2. knowledge_bases
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_bases",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"organization_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("name", sa.String(200), nullable=False),
|
||||
sa.Column("type", sa.String(20), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("document_count", sa.Integer, server_default="0", nullable=False),
|
||||
sa.Column("status", sa.String(20), server_default="active", nullable=False),
|
||||
sa.Column(
|
||||
"created_by",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index("idx_knowledge_bases_organization_id", "knowledge_bases", ["organization_id"])
|
||||
op.create_index("idx_knowledge_bases_type", "knowledge_bases", ["type"])
|
||||
op.create_index("idx_knowledge_bases_status", "knowledge_bases", ["status"])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 3. knowledge_documents
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_documents",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"knowledge_base_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_bases.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("title", sa.String(500), nullable=False),
|
||||
sa.Column("source_type", sa.String(20), nullable=False),
|
||||
sa.Column("source_url", sa.String(2000), nullable=True),
|
||||
sa.Column("content", sa.Text, nullable=False),
|
||||
sa.Column("content_hash", sa.String(64), nullable=False),
|
||||
sa.Column("chunk_count", sa.Integer, server_default="0", nullable=False),
|
||||
sa.Column("status", sa.String(20), server_default="processing", nullable=False),
|
||||
sa.Column("error_message", sa.Text, nullable=True),
|
||||
sa.Column("metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_knowledge_documents_knowledge_base_id",
|
||||
"knowledge_documents",
|
||||
["knowledge_base_id"],
|
||||
)
|
||||
op.create_index("idx_knowledge_documents_status", "knowledge_documents", ["status"])
|
||||
op.create_index(
|
||||
"idx_knowledge_documents_content_hash", "knowledge_documents", ["content_hash"]
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 4. knowledge_chunks (embedding column via raw SQL for vector type)
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_chunks",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"document_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_documents.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("content", sa.Text, nullable=False),
|
||||
sa.Column("chunk_index", sa.Integer, nullable=False),
|
||||
sa.Column("token_count", sa.Integer, server_default="0", nullable=False),
|
||||
sa.Column("metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
|
||||
# Add vector embedding column via raw SQL (pgvector type not in SA dialect)
|
||||
# Dimension 1536 matches OpenAI text-embedding-3-small
|
||||
op.execute(
|
||||
"ALTER TABLE knowledge_chunks ADD COLUMN embedding vector(1536)"
|
||||
)
|
||||
|
||||
op.create_index("idx_knowledge_chunks_document_id", "knowledge_chunks", ["document_id"])
|
||||
op.create_index(
|
||||
"idx_knowledge_chunks_chunk_index",
|
||||
"knowledge_chunks",
|
||||
["document_id", "chunk_index"],
|
||||
)
|
||||
|
||||
# HNSW index for approximate nearest-neighbor cosine similarity search
|
||||
op.execute(
|
||||
"CREATE INDEX ix_knowledge_chunks_embedding "
|
||||
"ON knowledge_chunks USING hnsw (embedding vector_cosine_ops)"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 5. knowledge_search_logs
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_search_logs",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"organization_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("organizations.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"user_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("query", sa.Text, nullable=False),
|
||||
sa.Column("knowledge_base_ids", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("results_count", sa.Integer, server_default="0", nullable=False),
|
||||
sa.Column("latency_ms", sa.Integer, server_default="0", nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_knowledge_search_logs_organization_id",
|
||||
"knowledge_search_logs",
|
||||
["organization_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_knowledge_search_logs_user_id", "knowledge_search_logs", ["user_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"idx_knowledge_search_logs_created_at", "knowledge_search_logs", ["created_at"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Drop tables in reverse dependency order
|
||||
op.drop_table("knowledge_search_logs")
|
||||
op.execute("DROP INDEX IF EXISTS ix_knowledge_chunks_embedding")
|
||||
op.drop_table("knowledge_chunks")
|
||||
op.drop_table("knowledge_documents")
|
||||
op.drop_table("knowledge_bases")
|
||||
# Note: we do NOT drop the vector extension as other tables might rely on it
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
"""add monetization tables: diagnosis_records, attribution_records, payment_orders
|
||||
|
||||
Revision ID: f063b3da67b6
|
||||
Revises: g1h2i3j4kl56
|
||||
Create Date: 2026-06-01 07:40:07.419407
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "f063b3da67b6"
|
||||
down_revision: Union[str, Sequence[str], None] = "g1h2i3j4kl56"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"diagnosis_records",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("brand_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("user_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("diagnosis_type", sa.String(length=20), nullable=False),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column("overall_score", sa.Float(), nullable=True),
|
||||
sa.Column("result_json", sa.JSON(), nullable=True),
|
||||
sa.Column("error_message", sa.Text(), nullable=True),
|
||||
sa.Column("collection_metadata", sa.JSON(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("completed_at", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["brand_id"], ["brands.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_diagnosis_records_brand_id", "diagnosis_records", ["brand_id"])
|
||||
op.create_index("idx_diagnosis_records_user_id", "diagnosis_records", ["user_id"])
|
||||
op.create_index("idx_diagnosis_records_status", "diagnosis_records", ["status"])
|
||||
op.create_index("idx_diagnosis_records_created_at", "diagnosis_records", ["created_at"])
|
||||
|
||||
op.create_table(
|
||||
"attribution_records",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("user_id", sa.Text(), nullable=False),
|
||||
sa.Column("brand_id", sa.Uuid(), nullable=False),
|
||||
sa.Column("content_id", sa.Uuid(), nullable=True),
|
||||
sa.Column("baseline_score", sa.Float(), nullable=False),
|
||||
sa.Column("current_score", sa.Float(), nullable=True),
|
||||
sa.Column("score_delta", sa.Float(), nullable=True),
|
||||
sa.Column("attribution_window_days", sa.Integer(), server_default="28", nullable=False),
|
||||
sa.Column("published_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("window_end_at", sa.DateTime(), nullable=True),
|
||||
sa.Column("status", sa.String(length=20), server_default="tracking", nullable=False),
|
||||
sa.Column("attributed_dimensions", sa.JSON(), nullable=True),
|
||||
sa.Column("roi_percentage", sa.Float(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
|
||||
sa.ForeignKeyConstraint(["brand_id"], ["brands.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["content_id"], ["contents.id"], ondelete="SET NULL"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_attribution_records_brand_id", "attribution_records", ["brand_id"])
|
||||
op.create_index("idx_attribution_records_user_id", "attribution_records", ["user_id"])
|
||||
op.create_index("idx_attribution_records_status", "attribution_records", ["status"])
|
||||
op.create_index("idx_attribution_records_content_id", "attribution_records", ["content_id"])
|
||||
|
||||
op.drop_table("payment_orders")
|
||||
op.create_table(
|
||||
"payment_orders",
|
||||
sa.Column("id", sa.Uuid(), nullable=False),
|
||||
sa.Column("user_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("plan", sa.String(length=20), nullable=False),
|
||||
sa.Column("amount", sa.Float(), nullable=False),
|
||||
sa.Column("currency", sa.String(length=10), nullable=False),
|
||||
sa.Column("payment_provider", sa.String(length=20), nullable=False),
|
||||
sa.Column("payment_id", sa.String(length=255), nullable=True),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column("pay_url", sa.String(length=1024), nullable=True),
|
||||
sa.Column("callback_data", sa.JSON(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("paid_at", sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("payment_orders")
|
||||
op.create_table(
|
||||
"payment_orders",
|
||||
sa.Column("id", sa.TEXT(), nullable=False),
|
||||
sa.Column("orderNo", sa.TEXT(), nullable=False),
|
||||
sa.Column("userId", sa.TEXT(), nullable=False),
|
||||
sa.Column("channelId", sa.TEXT(), nullable=True),
|
||||
sa.Column("subject", sa.TEXT(), nullable=False),
|
||||
sa.Column("body", sa.TEXT(), nullable=True),
|
||||
sa.Column("amount", sa.NUMERIC(precision=12, scale=2), nullable=False),
|
||||
sa.Column("currency", sa.TEXT(), server_default="'CNY'", nullable=False),
|
||||
sa.Column("status", sa.TEXT(), server_default="'pending'", nullable=False),
|
||||
sa.Column("clientIp", sa.TEXT(), nullable=True),
|
||||
sa.Column("userAgent", sa.TEXT(), nullable=True),
|
||||
sa.Column("metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
|
||||
sa.Column("channelOrderNo", sa.TEXT(), nullable=True),
|
||||
sa.Column("createdAt", postgresql.TIMESTAMP(precision=3), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
|
||||
sa.Column("updatedAt", postgresql.TIMESTAMP(precision=3), nullable=False),
|
||||
sa.Column("paidAt", postgresql.TIMESTAMP(precision=3), nullable=True),
|
||||
sa.Column("cancelledAt", postgresql.TIMESTAMP(precision=3), nullable=True),
|
||||
sa.ForeignKeyConstraint(["channelId"], ["payment_channels.id"], onupdate="CASCADE", ondelete="SET NULL"),
|
||||
sa.ForeignKeyConstraint(["userId"], ["users.id"], onupdate="CASCADE", ondelete="CASCADE"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(op.f("payment_orders_userId_idx"), "payment_orders", ["userId"])
|
||||
op.create_index(op.f("payment_orders_status_idx"), "payment_orders", ["status"])
|
||||
op.create_index(op.f("payment_orders_orderNo_key"), "payment_orders", ["orderNo"], unique=True)
|
||||
op.create_index(op.f("payment_orders_orderNo_idx"), "payment_orders", ["orderNo"])
|
||||
op.create_index(op.f("payment_orders_createdAt_idx"), "payment_orders", ["createdAt"])
|
||||
|
||||
op.drop_index("idx_attribution_records_content_id", table_name="attribution_records")
|
||||
op.drop_index("idx_attribution_records_status", table_name="attribution_records")
|
||||
op.drop_index("idx_attribution_records_user_id", table_name="attribution_records")
|
||||
op.drop_index("idx_attribution_records_brand_id", table_name="attribution_records")
|
||||
op.drop_table("attribution_records")
|
||||
|
||||
op.drop_index("idx_diagnosis_records_created_at", table_name="diagnosis_records")
|
||||
op.drop_index("idx_diagnosis_records_status", table_name="diagnosis_records")
|
||||
op.drop_index("idx_diagnosis_records_user_id", table_name="diagnosis_records")
|
||||
op.drop_index("idx_diagnosis_records_brand_id", table_name="diagnosis_records")
|
||||
op.drop_table("diagnosis_records")
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
"""Add analytics tables (publish_records, content_metrics, optimization_insights)
|
||||
|
||||
Revision ID: f6g8h0i2de56
|
||||
Revises: e5f7g9h1cd45
|
||||
Create Date: 2026-05-23 14:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "f6g8h0i2de56"
|
||||
down_revision: Union[str, None] = "e5f7g9h1cd45"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ------------------------------------------------------------------ #
|
||||
# 1. publish_records
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"publish_records",
|
||||
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
|
||||
sa.Column("organization_id", sa.String(36), nullable=False),
|
||||
sa.Column("content_title", sa.String(200), nullable=False),
|
||||
sa.Column("content_id", sa.String(36), nullable=True),
|
||||
sa.Column("platform", sa.String(50), nullable=False),
|
||||
sa.Column("published_url", sa.String(500), nullable=True),
|
||||
sa.Column("status", sa.String(20), server_default="draft", nullable=False),
|
||||
sa.Column("published_at", sa.DateTime(), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index("idx_publish_records_organization_id", "publish_records", ["organization_id"])
|
||||
op.create_index("idx_publish_records_platform", "publish_records", ["platform"])
|
||||
op.create_index("idx_publish_records_status", "publish_records", ["status"])
|
||||
op.create_index("idx_publish_records_created_at", "publish_records", ["created_at"])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 2. content_metrics
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"content_metrics",
|
||||
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
|
||||
sa.Column(
|
||||
"publish_record_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("publish_records.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"recorded_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
# 互动指标
|
||||
sa.Column("views", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("likes", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("comments", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("shares", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("bookmarks", sa.Integer(), server_default="0", nullable=False),
|
||||
# GEO核心指标
|
||||
sa.Column("ai_citation_count", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("search_impressions", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("search_clicks", sa.Integer(), server_default="0", nullable=False),
|
||||
# 阅读指标
|
||||
sa.Column("avg_read_duration", sa.Float(), server_default="0.0", nullable=False),
|
||||
sa.Column("read_completion_rate", sa.Float(), server_default="0.0", nullable=False),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_content_metrics_publish_record_id", "content_metrics", ["publish_record_id"]
|
||||
)
|
||||
op.create_index("idx_content_metrics_recorded_at", "content_metrics", ["recorded_at"])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 3. optimization_insights
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"optimization_insights",
|
||||
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
|
||||
sa.Column("organization_id", sa.String(36), nullable=False),
|
||||
sa.Column("content_id", sa.String(36), nullable=True),
|
||||
sa.Column("insight_type", sa.String(30), nullable=False),
|
||||
sa.Column("title", sa.String(200), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=False),
|
||||
sa.Column("recommendation", sa.Text(), nullable=False),
|
||||
sa.Column("severity", sa.String(20), server_default="info", nullable=False),
|
||||
sa.Column("applied", sa.Boolean(), server_default="false", nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_optimization_insights_organization_id",
|
||||
"optimization_insights",
|
||||
["organization_id"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_optimization_insights_insight_type",
|
||||
"optimization_insights",
|
||||
["insight_type"],
|
||||
)
|
||||
op.create_index(
|
||||
"idx_optimization_insights_created_at",
|
||||
"optimization_insights",
|
||||
["created_at"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("optimization_insights")
|
||||
op.drop_table("content_metrics")
|
||||
op.drop_table("publish_records")
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
"""Add knowledge graph tables
|
||||
|
||||
Revision ID: f7a8b9c0de56
|
||||
Revises: e5f7g9h1cd45
|
||||
Create Date: 2026-05-24 12:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
import enum
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "f7a8b9c0de56"
|
||||
down_revision: Union[str, None] = "810a29804f5a"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ------------------------------------------------------------------ #
|
||||
# 1. 创建实体类型枚举
|
||||
# ------------------------------------------------------------------ #
|
||||
entity_type_enum = enum.Enum(
|
||||
'EntityType',
|
||||
[
|
||||
'ORGANIZATION', 'PRODUCT', 'PERSON', 'LOCATION',
|
||||
'TECHNOLOGY', 'BRAND', 'EVENT', 'CONCEPT', 'OTHER'
|
||||
]
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 2. 创建关系类型枚举
|
||||
# ------------------------------------------------------------------ #
|
||||
relation_type_enum = enum.Enum(
|
||||
'RelationType',
|
||||
[
|
||||
'COMPETES_WITH', 'PARTNERS_WITH', 'ACQUIRES', 'SUBSIDIARY_OF',
|
||||
'PRODUCES', 'USES_TECHNOLOGY', 'PART_OF',
|
||||
'LOCATED_IN', 'FOUNDED_IN',
|
||||
'CEO_OF', 'FOUNDER_OF',
|
||||
'RELATED_TO', 'MENTIONED_IN', 'ALSO_KNOWN_AS'
|
||||
]
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 3. knowledge_entities 表
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_entities",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"knowledge_base_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_bases.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("name", sa.String(500), nullable=False),
|
||||
sa.Column("entity_type", sa.Enum(entity_type_enum, name="entitytype"), nullable=False),
|
||||
sa.Column("description", sa.Text, nullable=True),
|
||||
sa.Column("properties", postgresql.JSONB(astext_type=sa.Text()), nullable=True, server_default="{}"),
|
||||
sa.Column(
|
||||
"source_chunk_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_chunks.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("confidence", sa.String(20), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index("ix_entities_name", "knowledge_entities", ["name"])
|
||||
op.create_index("ix_entities_kb_name", "knowledge_entities", ["knowledge_base_id", "name"])
|
||||
op.create_index("ix_entities_kb_type", "knowledge_entities", ["knowledge_base_id", "entity_type"])
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 4. knowledge_relations 表
|
||||
# ------------------------------------------------------------------ #
|
||||
op.create_table(
|
||||
"knowledge_relations",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"source_entity_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_entities.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"target_entity_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_entities.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("relation_type", sa.Enum(relation_type_enum, name="relationtype"), nullable=False),
|
||||
sa.Column("properties", postgresql.JSONB(astext_type=sa.Text()), nullable=True, server_default="{}"),
|
||||
sa.Column(
|
||||
"source_chunk_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("knowledge_chunks.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("confidence", sa.String(20), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=False,
|
||||
),
|
||||
)
|
||||
op.create_index("ix_relations_source", "knowledge_relations", ["source_entity_id"])
|
||||
op.create_index("ix_relations_target", "knowledge_relations", ["target_entity_id"])
|
||||
op.create_index("ix_relations_type", "knowledge_relations", ["relation_type"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# 删除表(注意外键约束会自动处理)
|
||||
op.drop_table("knowledge_relations")
|
||||
op.drop_table("knowledge_entities")
|
||||
# 删除枚举类型
|
||||
op.execute("DROP TYPE IF EXISTS relationtype")
|
||||
op.execute("DROP TYPE IF EXISTS entitytype")
|
||||
|
|
@ -1,157 +0,0 @@
|
|||
"""Add missing tables: brands, competitors, api_keys, usage_records, platform_rule_versions, detection_tasks
|
||||
|
||||
Revision ID: g1h2i3j4kl56
|
||||
Revises: f7a8b9c0de56
|
||||
Create Date: 2026-05-26 10:00:00.000000
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "g1h2i3j4kl56"
|
||||
down_revision: Union[str, Sequence[str], None] = "f7a8b9c0de56"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"brands",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("name", sa.String(50), nullable=False),
|
||||
sa.Column("aliases", postgresql.JSONB(), server_default="[]", nullable=False),
|
||||
sa.Column("website", sa.String(500), nullable=True),
|
||||
sa.Column("industry", sa.String(50), nullable=True),
|
||||
sa.Column("platforms", postgresql.JSONB(), server_default="[]", nullable=False),
|
||||
sa.Column("frequency", sa.String(20), server_default="weekly", nullable=False),
|
||||
sa.Column("status", sa.String(20), server_default="active", nullable=False),
|
||||
sa.Column("last_queried_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("next_query_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_brands_user_id", "brands", ["user_id"])
|
||||
|
||||
op.create_table(
|
||||
"competitors",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("brand_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("name", sa.String(50), nullable=False),
|
||||
sa.Column("aliases", postgresql.JSONB(), server_default="[]", nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.ForeignKeyConstraint(["brand_id"], ["brands.id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("idx_competitors_brand_id", "competitors", ["brand_id"])
|
||||
|
||||
op.create_table(
|
||||
"api_keys",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("engine_type", sa.String(20), nullable=False),
|
||||
sa.Column("encrypted_key", sa.String(500), nullable=False),
|
||||
sa.Column("key_hint", sa.String(50), nullable=False),
|
||||
sa.Column("key_source", sa.String(10), server_default="user", nullable=True),
|
||||
sa.Column("status", sa.String(20), server_default="active", nullable=True),
|
||||
sa.Column("priority", sa.Integer(), server_default="0", nullable=True),
|
||||
sa.Column("last_verified_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_api_keys_user_id", "api_keys", ["user_id"])
|
||||
op.create_index("idx_api_keys_user_engine", "api_keys", ["user_id", "engine_type"])
|
||||
op.create_index("idx_api_keys_engine_status", "api_keys", ["engine_type", "status"])
|
||||
|
||||
op.create_table(
|
||||
"usage_records",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("brand_id", postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column("engine_type", sa.String(20), nullable=False),
|
||||
sa.Column("query", sa.String(500), nullable=False),
|
||||
sa.Column("input_tokens", sa.Integer(), server_default="0", nullable=True),
|
||||
sa.Column("output_tokens", sa.Integer(), server_default="0", nullable=True),
|
||||
sa.Column("cost", sa.Float(), server_default="0.0", nullable=True),
|
||||
sa.Column("extra_data", postgresql.JSONB(), server_default="{}", nullable=True),
|
||||
sa.Column("timestamp", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.ForeignKeyConstraint(["brand_id"], ["brands.id"], ondelete="SET NULL"),
|
||||
)
|
||||
op.create_index("idx_usage_records_user_id", "usage_records", ["user_id"])
|
||||
op.create_index("idx_usage_records_timestamp", "usage_records", ["timestamp"])
|
||||
op.create_index("idx_usage_records_user_engine", "usage_records", ["user_id", "engine_type"])
|
||||
op.create_index("idx_usage_records_user_timestamp", "usage_records", ["user_id", "timestamp"])
|
||||
op.create_index("idx_usage_records_engine_timestamp", "usage_records", ["engine_type", "timestamp"])
|
||||
|
||||
op.create_table(
|
||||
"platform_rule_versions",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("rule_id", sa.String(100), nullable=False),
|
||||
sa.Column("platform", sa.String(50), nullable=False),
|
||||
sa.Column("version", sa.Integer(), nullable=False),
|
||||
sa.Column("rule_data", postgresql.JSONB(), nullable=False),
|
||||
sa.Column("change_summary", sa.String(500), nullable=True),
|
||||
sa.Column("created_by", sa.String(100), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=True),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index("idx_rule_versions_rule_id", "platform_rule_versions", ["rule_id"])
|
||||
op.create_index("idx_rule_versions_platform", "platform_rule_versions", ["platform"])
|
||||
|
||||
op.create_table(
|
||||
"detection_tasks",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False),
|
||||
sa.Column("brand_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("name", sa.String(200), nullable=False),
|
||||
sa.Column("frequency", sa.String(20), nullable=False),
|
||||
sa.Column("engines", postgresql.JSONB(), server_default="[]", nullable=False),
|
||||
sa.Column("queries", postgresql.JSONB(), server_default="[]", nullable=False),
|
||||
sa.Column("competitor_names", postgresql.JSONB(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), server_default="true", nullable=False),
|
||||
sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("next_run_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.ForeignKeyConstraint(["brand_id"], ["brands.id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("idx_detection_tasks_brand_id", "detection_tasks", ["brand_id"])
|
||||
op.create_index("idx_detection_tasks_user_id", "detection_tasks", ["user_id"])
|
||||
op.create_index("idx_detection_tasks_is_active", "detection_tasks", ["is_active"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_detection_tasks_is_active", table_name="detection_tasks")
|
||||
op.drop_index("idx_detection_tasks_user_id", table_name="detection_tasks")
|
||||
op.drop_index("idx_detection_tasks_brand_id", table_name="detection_tasks")
|
||||
op.drop_table("detection_tasks")
|
||||
|
||||
op.drop_index("idx_rule_versions_platform", table_name="platform_rule_versions")
|
||||
op.drop_index("idx_rule_versions_rule_id", table_name="platform_rule_versions")
|
||||
op.drop_table("platform_rule_versions")
|
||||
|
||||
op.drop_index("idx_usage_records_engine_timestamp", table_name="usage_records")
|
||||
op.drop_index("idx_usage_records_user_timestamp", table_name="usage_records")
|
||||
op.drop_index("idx_usage_records_user_engine", table_name="usage_records")
|
||||
op.drop_index("idx_usage_records_timestamp", table_name="usage_records")
|
||||
op.drop_index("idx_usage_records_user_id", table_name="usage_records")
|
||||
op.drop_table("usage_records")
|
||||
|
||||
op.drop_index("idx_api_keys_engine_status", table_name="api_keys")
|
||||
op.drop_index("idx_api_keys_user_engine", table_name="api_keys")
|
||||
op.drop_index("idx_api_keys_user_id", table_name="api_keys")
|
||||
op.drop_table("api_keys")
|
||||
|
||||
op.drop_index("idx_competitors_brand_id", table_name="competitors")
|
||||
op.drop_table("competitors")
|
||||
|
||||
op.drop_index("idx_brands_user_id", table_name="brands")
|
||||
op.drop_table("brands")
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
"""Add timezone to all datetime columns
|
||||
|
||||
Revision ID: h3i4j5k6mn78
|
||||
Revises: f063b3da67b6
|
||||
Create Date: 2026-06-01
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = 'h3i4j5k6mn78'
|
||||
down_revision: Union[str, Sequence[str], None] = 'f063b3da67b6'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.alter_column('users', 'lastLoginAt', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='lastLoginAt AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('users', 'createdAt', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='createdAt AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('users', 'updatedAt', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updatedAt AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('users', 'lockedUntil', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='lockedUntil AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('brands', 'last_queried_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_queried_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('brands', 'next_query_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='next_query_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('brands', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('brands', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('queries', 'last_queried_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_queried_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('queries', 'next_query_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='next_query_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('queries', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('queries', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('citation_records', 'queried_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='queried_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('attribution_records', 'published_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='published_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('attribution_records', 'window_end_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='window_end_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('attribution_records', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('attribution_records', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('contents', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('contents', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('content_versions', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('content_reviews', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('geo_plans', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('geo_plans', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('geo_plan_actions', 'completed_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='completed_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('geo_plan_actions', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('geo_plan_actions', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('suggestions', 'generated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='generated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('suggestions', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('competitors', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('competitor_insights', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('competitor_insights', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('distribution_schedules', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('distribution_schedules', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('brand_knowledge', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('brand_knowledge', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('keywords', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('agent_registry', 'last_heartbeat', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_heartbeat AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_registry', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_registry', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_configs', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_tasks', 'scheduled_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='scheduled_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_tasks', 'started_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='started_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_tasks', 'completed_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='completed_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_tasks', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('agent_task_logs', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('detection_tasks', 'last_run_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_run_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('detection_tasks', 'next_run_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='next_run_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('detection_tasks', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('detection_tasks', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('monitoring_records', 'last_checked_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_checked_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('monitoring_records', 'next_check_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='next_check_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('monitoring_records', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('monitoring_records', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('content_baselines', 'recorded_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='recorded_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('trend_insights', 'period_start', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='period_start AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('trend_insights', 'period_end', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='period_end AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('trend_insights', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('trend_insights', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('query_tasks', 'scheduled_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='scheduled_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('query_tasks', 'started_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='started_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('query_tasks', 'completed_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='completed_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('usage_records', 'timestamp', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='timestamp AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('usage_records', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('api_keys', 'last_verified_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='last_verified_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('api_keys', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('api_keys', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('knowledge_bases', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_bases', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_documents', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_documents', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_chunks', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_search_logs', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('knowledge_entities', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_entities', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('knowledge_relations', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('organizations', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('organizations', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('org_members', 'joined_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='joined_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('lifecycle_projects', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('lifecycle_projects', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('project_stages', 'started_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='started_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('project_stages', 'completed_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='completed_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('alerts', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('alert_settings', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('alert_settings', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('platform_rules', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('platform_rule_versions', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('schema_suggestions', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('schema_suggestions', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('subscriptions', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('payment_orders', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('payment_orders', 'updated_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='updated_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('payment_orders', 'paid_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='paid_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('diagnosis_records', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('diagnosis_records', 'completed_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='completed_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
op.alter_column('publish_records', 'published_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='published_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('publish_records', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('content_metrics', 'recorded_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='recorded_at AT TIME ZONE \'UTC\'')
|
||||
op.alter_column('optimization_insights', 'created_at', type_=sa.DateTime(timezone=True), existing_type=sa.DateTime(), postgresql_using='created_at AT TIME ZONE \'UTC\'')
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.alter_column('users', 'lastLoginAt', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('users', 'createdAt', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('users', 'updatedAt', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('users', 'lockedUntil', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('brands', 'last_queried_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('brands', 'next_query_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('brands', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('brands', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('queries', 'last_queried_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('queries', 'next_query_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('queries', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('queries', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('citation_records', 'queried_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('attribution_records', 'published_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('attribution_records', 'window_end_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('attribution_records', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('attribution_records', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('contents', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('contents', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('content_versions', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('content_reviews', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('geo_plans', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('geo_plans', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('geo_plan_actions', 'completed_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('geo_plan_actions', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('geo_plan_actions', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('suggestions', 'generated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('suggestions', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('competitors', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('competitor_insights', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('competitor_insights', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('distribution_schedules', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('distribution_schedules', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('brand_knowledge', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('brand_knowledge', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('keywords', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('agent_registry', 'last_heartbeat', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_registry', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_registry', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_configs', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_tasks', 'scheduled_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_tasks', 'started_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_tasks', 'completed_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_tasks', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('agent_task_logs', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('detection_tasks', 'last_run_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('detection_tasks', 'next_run_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('detection_tasks', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('detection_tasks', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('monitoring_records', 'last_checked_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('monitoring_records', 'next_check_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('monitoring_records', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('monitoring_records', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('content_baselines', 'recorded_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('trend_insights', 'period_start', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('trend_insights', 'period_end', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('trend_insights', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('trend_insights', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('query_tasks', 'scheduled_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('query_tasks', 'started_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('query_tasks', 'completed_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('usage_records', 'timestamp', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('usage_records', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('api_keys', 'last_verified_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('api_keys', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('api_keys', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('knowledge_bases', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_bases', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_documents', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_documents', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_chunks', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_search_logs', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('knowledge_entities', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_entities', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('knowledge_relations', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('organizations', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('organizations', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('org_members', 'joined_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('lifecycle_projects', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('lifecycle_projects', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('project_stages', 'started_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('project_stages', 'completed_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('alerts', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('alert_settings', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('alert_settings', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('platform_rules', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('platform_rule_versions', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('schema_suggestions', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('schema_suggestions', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('subscriptions', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('payment_orders', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('payment_orders', 'updated_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('payment_orders', 'paid_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('diagnosis_records', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('diagnosis_records', 'completed_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
|
||||
op.alter_column('publish_records', 'published_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('publish_records', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('content_metrics', 'recorded_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
op.alter_column('optimization_insights', 'created_at', type_=sa.DateTime(), existing_type=sa.DateTime(timezone=True))
|
||||
Loading…
Reference in New Issue