第一次提交

This commit is contained in:
rjb
2026-01-19 00:09:36 +08:00
parent de4b5059e9
commit 6674060f2f
191 changed files with 40940 additions and 0 deletions

65
backend/alembic/env.py Normal file
View File

@@ -0,0 +1,65 @@
"""
Alembic 环境配置
"""
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
from app.core.config import settings
from app.core.database import Base
# 导入所有模型,确保 Alembic 能够检测到
from app.models import (
User, Workflow, WorkflowVersion, Agent, Execution,
ExecutionLog, ModelConfig, DataSource
)
# this is the Alembic Config object
config = context.config
# 设置数据库URL
config.set_main_option("sqlalchemy.url", settings.DATABASE_URL)
# Interpret the config file for Python logging.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
target_metadata = Base.metadata
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode."""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,217 @@
"""initial migration
Revision ID: 001
Revises:
Create Date: 2024-01-01 00:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '001'
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# 创建用户表
op.create_table(
'users',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='用户ID'),
sa.Column('username', sa.String(length=50), nullable=False, comment='用户名'),
sa.Column('email', sa.String(length=100), nullable=False, comment='邮箱'),
sa.Column('password_hash', sa.String(length=255), nullable=False, comment='密码哈希'),
sa.Column('role', sa.String(length=20), server_default='user', nullable=True, comment='角色: admin/user'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
# 创建工作流表
op.create_table(
'workflows',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='工作流ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='工作流名称'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('nodes', sa.JSON(), nullable=False, comment='节点配置'),
sa.Column('edges', sa.JSON(), nullable=False, comment='边配置'),
sa.Column('version', sa.Integer(), server_default='1', nullable=True, comment='版本号'),
sa.Column('status', sa.String(length=20), server_default='draft', nullable=True, comment='状态: draft/published/running/stopped'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=True, comment='创建者ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_workflows_user_id', 'workflows', ['user_id'], unique=False)
# 创建工作流版本表
op.create_table(
'workflow_versions',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='版本ID'),
sa.Column('workflow_id', mysql.CHAR(length=36), nullable=False, comment='工作流ID'),
sa.Column('version', sa.Integer(), nullable=False, comment='版本号'),
sa.Column('name', sa.String(length=100), nullable=False, comment='工作流名称'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('nodes', sa.JSON(), nullable=False, comment='节点配置'),
sa.Column('edges', sa.JSON(), nullable=False, comment='边配置'),
sa.Column('status', sa.String(length=20), server_default='draft', nullable=True, comment='状态: draft/published/running/stopped'),
sa.Column('created_by', mysql.CHAR(length=36), nullable=True, comment='创建者ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('comment', sa.Text(), nullable=True, comment='版本备注'),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['workflow_id'], ['workflows.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_workflow_versions_workflow_id', 'workflow_versions', ['workflow_id'], unique=False)
# 创建智能体表
op.create_table(
'agents',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='智能体ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='智能体名称'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('workflow_config', sa.JSON(), nullable=False, comment='工作流配置'),
sa.Column('version', sa.Integer(), server_default='1', nullable=True, comment='版本号'),
sa.Column('status', sa.String(length=20), server_default='draft', nullable=True, comment='状态: draft/published/running/stopped'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=True, comment='创建者ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_agents_user_id', 'agents', ['user_id'], unique=False)
# 创建执行记录表
op.create_table(
'executions',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='执行ID'),
sa.Column('workflow_id', mysql.CHAR(length=36), nullable=True, comment='工作流ID'),
sa.Column('agent_id', mysql.CHAR(length=36), nullable=True, comment='智能体ID'),
sa.Column('input_data', sa.JSON(), nullable=True, comment='输入数据'),
sa.Column('output_data', sa.JSON(), nullable=True, comment='输出数据'),
sa.Column('status', sa.String(length=20), server_default='pending', nullable=True, comment='状态: pending/running/completed/failed'),
sa.Column('task_id', sa.String(length=255), nullable=True, comment='Celery任务ID'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('execution_time', sa.Integer(), nullable=True, comment='执行时间(毫秒)'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], ),
sa.ForeignKeyConstraint(['workflow_id'], ['workflows.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_executions_workflow_id', 'executions', ['workflow_id'], unique=False)
op.create_index('ix_executions_agent_id', 'executions', ['agent_id'], unique=False)
op.create_index('ix_executions_status', 'executions', ['status'], unique=False)
# 创建执行日志表
op.create_table(
'execution_logs',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='日志ID'),
sa.Column('execution_id', mysql.CHAR(length=36), nullable=False, comment='执行ID'),
sa.Column('node_id', sa.String(length=100), nullable=True, comment='节点ID'),
sa.Column('node_type', sa.String(length=50), nullable=True, comment='节点类型'),
sa.Column('level', sa.String(length=20), nullable=False, comment='日志级别: info/warn/error/debug'),
sa.Column('message', sa.Text(), nullable=False, comment='日志消息'),
sa.Column('timestamp', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='时间戳'),
sa.Column('duration_ms', sa.Integer(), nullable=True, comment='执行时长(毫秒)'),
sa.Column('input_data', sa.JSON(), nullable=True, comment='输入数据'),
sa.Column('output_data', sa.JSON(), nullable=True, comment='输出数据'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('additional_data', sa.JSON(), nullable=True, comment='附加数据'),
sa.ForeignKeyConstraint(['execution_id'], ['executions.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_execution_logs_execution_id', 'execution_logs', ['execution_id'], unique=False)
op.create_index('ix_execution_logs_node_id', 'execution_logs', ['node_id'], unique=False)
op.create_index('ix_execution_logs_level', 'execution_logs', ['level'], unique=False)
# 创建模型配置表
op.create_table(
'model_configs',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='配置ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='配置名称'),
sa.Column('provider', sa.String(length=50), nullable=False, comment='提供商: openai/claude/local'),
sa.Column('model_name', sa.String(length=100), nullable=False, comment='模型名称'),
sa.Column('api_key', sa.String(length=500), nullable=False, comment='API密钥加密存储'),
sa.Column('base_url', sa.String(length=255), nullable=True, comment='API地址'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=True, comment='所属用户ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_model_configs_user_id', 'model_configs', ['user_id'], unique=False)
# 创建数据源表
op.create_table(
'data_sources',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='数据源ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='数据源名称'),
sa.Column('type', sa.String(length=50), nullable=False, comment='数据源类型: mysql/postgresql/mongodb/redis/csv/json/api/s3'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('config', sa.JSON(), nullable=False, comment='连接配置(加密存储敏感信息)'),
sa.Column('status', sa.String(length=20), server_default='active', nullable=True, comment='状态: active/inactive/error'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=False, comment='创建者ID'),
sa.Column('last_connected_at', sa.DateTime(), nullable=True, comment='最后连接时间'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
op.create_index('ix_data_sources_user_id', 'data_sources', ['user_id'], unique=False)
op.create_index('ix_data_sources_type', 'data_sources', ['type'], unique=False)
op.create_index('ix_data_sources_status', 'data_sources', ['status'], unique=False)
def downgrade() -> None:
# 删除所有表(按依赖关系逆序)
op.drop_index('ix_data_sources_status', table_name='data_sources')
op.drop_index('ix_data_sources_type', table_name='data_sources')
op.drop_index('ix_data_sources_user_id', table_name='data_sources')
op.drop_table('data_sources')
op.drop_index('ix_model_configs_user_id', table_name='model_configs')
op.drop_table('model_configs')
op.drop_index('ix_execution_logs_level', table_name='execution_logs')
op.drop_index('ix_execution_logs_node_id', table_name='execution_logs')
op.drop_index('ix_execution_logs_execution_id', table_name='execution_logs')
op.drop_table('execution_logs')
op.drop_index('ix_executions_status', table_name='executions')
op.drop_index('ix_executions_agent_id', table_name='executions')
op.drop_index('ix_executions_workflow_id', table_name='executions')
op.drop_table('executions')
op.drop_index('ix_agents_user_id', table_name='agents')
op.drop_table('agents')
op.drop_index('ix_workflow_versions_workflow_id', table_name='workflow_versions')
op.drop_table('workflow_versions')
op.drop_index('ix_workflows_user_id', table_name='workflows')
op.drop_table('workflows')
op.drop_table('users')

View File

@@ -0,0 +1,83 @@
"""add template market tables
Revision ID: 002
Revises: 001
Create Date: 2024-01-17 12:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '002'
down_revision = '001'
branch_labels = None
depends_on = None
def upgrade() -> None:
# 创建工作流模板表
op.create_table(
'workflow_templates',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='模板ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='模板名称'),
sa.Column('description', sa.Text(), nullable=True, comment='模板描述'),
sa.Column('category', sa.String(length=50), nullable=True, comment='分类: llm/data_processing/automation/integration/other'),
sa.Column('tags', sa.JSON(), nullable=True, comment='标签列表'),
sa.Column('nodes', sa.JSON(), nullable=False, comment='节点配置'),
sa.Column('edges', sa.JSON(), nullable=False, comment='边配置'),
sa.Column('thumbnail', sa.String(length=500), nullable=True, comment='缩略图URL'),
sa.Column('is_public', sa.Boolean(), server_default='1', nullable=True, comment='是否公开'),
sa.Column('is_featured', sa.Boolean(), server_default='0', nullable=True, comment='是否精选'),
sa.Column('view_count', sa.Integer(), server_default='0', nullable=True, comment='查看次数'),
sa.Column('use_count', sa.Integer(), server_default='0', nullable=True, comment='使用次数'),
sa.Column('rating_count', sa.Integer(), server_default='0', nullable=True, comment='评分次数'),
sa.Column('rating_avg', sa.Float(), server_default='0.0', nullable=True, comment='平均评分'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=False, comment='创建者ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
# 创建模板评分表
op.create_table(
'template_ratings',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='评分ID'),
sa.Column('template_id', mysql.CHAR(length=36), nullable=False, comment='模板ID'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=False, comment='用户ID'),
sa.Column('rating', sa.Integer(), nullable=False, comment='评分: 1-5'),
sa.Column('comment', sa.Text(), nullable=True, comment='评论'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['template_id'], ['workflow_templates.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('template_id', 'user_id', name='uq_template_user_rating'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
# 创建模板收藏表
op.create_table(
'template_favorites',
sa.Column('id', mysql.CHAR(length=36), nullable=False, comment='收藏ID'),
sa.Column('template_id', mysql.CHAR(length=36), nullable=False, comment='模板ID'),
sa.Column('user_id', mysql.CHAR(length=36), nullable=False, comment='用户ID'),
sa.Column('created_at', sa.DateTime(), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间'),
sa.ForeignKeyConstraint(['template_id'], ['workflow_templates.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('template_id', 'user_id', name='uq_template_user_favorite'),
mysql_charset='utf8mb4',
mysql_collate='utf8mb4_unicode_ci'
)
def downgrade() -> None:
op.drop_table('template_favorites')
op.drop_table('template_ratings')
op.drop_table('workflow_templates')

View File

@@ -0,0 +1,126 @@
"""添加RBAC权限管理
Revision ID: 003_add_rbac
Revises: 002_add_template_market
Create Date: 2024-01-17
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '003_add_rbac'
down_revision = '002_add_template_market'
branch_labels = None
depends_on = None
def upgrade():
# 创建角色表
op.create_table(
'roles',
sa.Column('id', sa.CHAR(length=36), nullable=False, comment='角色ID'),
sa.Column('name', sa.String(length=50), nullable=False, comment='角色名称'),
sa.Column('description', sa.String(length=255), nullable=True, comment='角色描述'),
sa.Column('is_system', sa.Boolean(), nullable=True, server_default='0', comment='是否系统角色(不可删除)'),
sa.Column('created_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
# 创建权限表
op.create_table(
'permissions',
sa.Column('id', sa.CHAR(length=36), nullable=False, comment='权限ID'),
sa.Column('name', sa.String(length=100), nullable=False, comment='权限名称'),
sa.Column('code', sa.String(length=100), nullable=False, comment='权限代码workflow:create'),
sa.Column('resource', sa.String(length=50), nullable=False, comment='资源类型workflow、agent、execution'),
sa.Column('action', sa.String(length=50), nullable=False, comment='操作类型create、read、update、delete、execute'),
sa.Column('description', sa.String(length=255), nullable=True, comment='权限描述'),
sa.Column('created_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('code'),
sa.UniqueConstraint('name')
)
# 创建用户角色关联表
op.create_table(
'user_roles',
sa.Column('user_id', sa.CHAR(length=36), nullable=False),
sa.Column('role_id', sa.CHAR(length=36), nullable=False),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('user_id', 'role_id')
)
# 创建角色权限关联表
op.create_table(
'role_permissions',
sa.Column('role_id', sa.CHAR(length=36), nullable=False),
sa.Column('permission_id', sa.CHAR(length=36), nullable=False),
sa.ForeignKeyConstraint(['permission_id'], ['permissions.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('role_id', 'permission_id')
)
# 创建工作流权限表
op.create_table(
'workflow_permissions',
sa.Column('id', sa.CHAR(length=36), nullable=False, comment='权限ID'),
sa.Column('workflow_id', sa.CHAR(length=36), nullable=False, comment='工作流ID'),
sa.Column('user_id', sa.CHAR(length=36), nullable=True, comment='用户IDnull表示所有用户'),
sa.Column('role_id', sa.CHAR(length=36), nullable=True, comment='角色IDnull表示所有角色'),
sa.Column('permission_type', sa.String(length=20), nullable=False, comment='权限类型read/write/execute/share'),
sa.Column('granted_by', sa.CHAR(length=36), nullable=False, comment='授权人ID'),
sa.Column('created_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间'),
sa.ForeignKeyConstraint(['granted_by'], ['users.id']),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['workflow_id'], ['workflows.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# 创建Agent权限表
op.create_table(
'agent_permissions',
sa.Column('id', sa.CHAR(length=36), nullable=False, comment='权限ID'),
sa.Column('agent_id', sa.CHAR(length=36), nullable=False, comment='Agent ID'),
sa.Column('user_id', sa.CHAR(length=36), nullable=True, comment='用户IDnull表示所有用户'),
sa.Column('role_id', sa.CHAR(length=36), nullable=True, comment='角色IDnull表示所有角色'),
sa.Column('permission_type', sa.String(length=20), nullable=False, comment='权限类型read/write/execute/deploy'),
sa.Column('granted_by', sa.CHAR(length=36), nullable=False, comment='授权人ID'),
sa.Column('created_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP'), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, server_default=sa.text('CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'), comment='更新时间'),
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['granted_by'], ['users.id']),
sa.ForeignKeyConstraint(['role_id'], ['roles.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
# 创建索引
op.create_index('idx_workflow_permissions_workflow', 'workflow_permissions', ['workflow_id'])
op.create_index('idx_workflow_permissions_user', 'workflow_permissions', ['user_id'])
op.create_index('idx_workflow_permissions_role', 'workflow_permissions', ['role_id'])
op.create_index('idx_agent_permissions_agent', 'agent_permissions', ['agent_id'])
op.create_index('idx_agent_permissions_user', 'agent_permissions', ['user_id'])
op.create_index('idx_agent_permissions_role', 'agent_permissions', ['role_id'])
def downgrade():
op.drop_index('idx_agent_permissions_role', table_name='agent_permissions')
op.drop_index('idx_agent_permissions_user', table_name='agent_permissions')
op.drop_index('idx_agent_permissions_agent', table_name='agent_permissions')
op.drop_index('idx_workflow_permissions_role', table_name='workflow_permissions')
op.drop_index('idx_workflow_permissions_user', table_name='workflow_permissions')
op.drop_index('idx_workflow_permissions_workflow', table_name='workflow_permissions')
op.drop_table('agent_permissions')
op.drop_table('workflow_permissions')
op.drop_table('role_permissions')
op.drop_table('user_roles')
op.drop_table('permissions')
op.drop_table('roles')