diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..89f80b2 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,110 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except TypeError: + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -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(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/1a4b7cd9005e_rename_public_nav_status_to_enable.py b/migrations/versions/1a4b7cd9005e_rename_public_nav_status_to_enable.py new file mode 100644 index 0000000..7b5f258 --- /dev/null +++ b/migrations/versions/1a4b7cd9005e_rename_public_nav_status_to_enable.py @@ -0,0 +1,34 @@ +"""rename public_nav.status to public_nav.enable + +Revision ID: 1a4b7cd9005e +Revises: ec7c5d1aab2e +Create Date: 2026-09-05 18:05:00.000000 + +""" +from alembic import op + + +# revision identifiers, used by Alembic. +revision = '1a4b7cd9005e' +down_revision = 'ec7c5d1aab2e' +branch_labels = None +depends_on = None + + +def upgrade(): + # SQLite 不支持直接 ALTER COLUMN 改名,必须新建 + 拷贝 + 删除 + with op.batch_alter_table('public_nav', schema=None) as batch_op: + batch_op.alter_column( + 'status', + new_column_name='enable', + existing_comment='状态(1启用,0关闭)', + ) + + +def downgrade(): + with op.batch_alter_table('public_nav', schema=None) as batch_op: + batch_op.alter_column( + 'enable', + new_column_name='status', + existing_comment='状态(1启用,0关闭)', + ) diff --git a/migrations/versions/38751d88d42c_add_public_nav_table.py b/migrations/versions/38751d88d42c_add_public_nav_table.py new file mode 100644 index 0000000..d0df555 --- /dev/null +++ b/migrations/versions/38751d88d42c_add_public_nav_table.py @@ -0,0 +1,48 @@ +"""add public_nav table + +Revision ID: 38751d88d42c +Revises: fdbdc3aab8da +Create Date: 2026-09-05 14:54:32.013804 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '38751d88d42c' +down_revision = 'fdbdc3aab8da' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('public_nav', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='导航ID'), + sa.Column('category', sa.String(length=50), nullable=False, comment='分类'), + sa.Column('title', sa.String(length=100), nullable=False, comment='标题'), + sa.Column('url', sa.String(length=255), nullable=False, comment='链接'), + sa.Column('description', sa.String(length=255), nullable=True, comment='简介'), + sa.Column('icon', sa.String(length=100), nullable=True, comment='图标'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('status', sa.Integer(), nullable=True, comment='状态(1启用,0关闭)'), + sa.Column('is_external', sa.Integer(), nullable=True, comment='是否外链'), + sa.Column('create_by', sa.String(length=50), nullable=True, comment='创建者'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('public_nav', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_public_nav_category'), ['category'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('public_nav', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_public_nav_category')) + + op.drop_table('public_nav') + # ### end Alembic commands ### diff --git a/migrations/versions/971bc54196ea_add_site_friend_table.py b/migrations/versions/971bc54196ea_add_site_friend_table.py new file mode 100644 index 0000000..fa6ef3e --- /dev/null +++ b/migrations/versions/971bc54196ea_add_site_friend_table.py @@ -0,0 +1,48 @@ +"""add site_friend table + +Revision ID: 971bc54196ea +Revises: 1a4b7cd9005e +Create Date: 2026-09-06 08:33:13.812008 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '971bc54196ea' +down_revision = '1a4b7cd9005e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('site_friend', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='友情链接ID'), + sa.Column('title', sa.String(length=100), nullable=False, comment='名称'), + sa.Column('url', sa.String(length=255), nullable=False, comment='链接'), + sa.Column('logo', sa.String(length=255), nullable=True, comment='Logo / favicon'), + sa.Column('description', sa.String(length=255), nullable=True, comment='简介'), + sa.Column('category', sa.String(length=50), nullable=True, comment='分组'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('enable', sa.Integer(), nullable=True, comment='状态(1启用,0关闭)'), + sa.Column('is_external', sa.Integer(), nullable=True, comment='是否外链'), + sa.Column('create_by', sa.String(length=50), nullable=True, comment='创建者'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('site_friend', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_site_friend_category'), ['category'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('site_friend', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_site_friend_category')) + + op.drop_table('site_friend') + # ### end Alembic commands ### diff --git a/migrations/versions/afa5cfb23a85_add_nav_category_table.py b/migrations/versions/afa5cfb23a85_add_nav_category_table.py new file mode 100644 index 0000000..af9acb3 --- /dev/null +++ b/migrations/versions/afa5cfb23a85_add_nav_category_table.py @@ -0,0 +1,44 @@ +"""add nav_category table + +Revision ID: afa5cfb23a85 +Revises: cef61b8c58c8 +Create Date: 2026-09-06 10:12:10.238105 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'afa5cfb23a85' +down_revision = 'cef61b8c58c8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('nav_category', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='分类ID'), + sa.Column('name', sa.String(length=50), nullable=False, comment='分类名(前端分类详情 URL)'), + sa.Column('icon', sa.String(length=50), nullable=True, comment='layui-icon class'), + sa.Column('description', sa.String(length=255), nullable=True, comment='分类简介'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('enable', sa.Integer(), nullable=True, comment='状态(1启用,0关闭)'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('nav_category', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_nav_category_name'), ['name'], unique=True) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('nav_category', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_nav_category_name')) + + op.drop_table('nav_category') + # ### end Alembic commands ### diff --git a/migrations/versions/cef61b8c58c8_add_visit_log_and_page_stat_tables.py b/migrations/versions/cef61b8c58c8_add_visit_log_and_page_stat_tables.py new file mode 100644 index 0000000..c517780 --- /dev/null +++ b/migrations/versions/cef61b8c58c8_add_visit_log_and_page_stat_tables.py @@ -0,0 +1,66 @@ +"""add visit_log and page_stat tables + +Revision ID: cef61b8c58c8 +Revises: 971bc54196ea +Create Date: 2026-09-06 08:45:03.775693 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cef61b8c58c8' +down_revision = '971bc54196ea' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('site_page_stat', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('path', sa.String(length=255), nullable=True), + sa.Column('day', sa.String(length=10), nullable=True), + sa.Column('pv', sa.Integer(), nullable=True, comment='Page View'), + sa.Column('uv', sa.Integer(), nullable=True, comment='独立访客数(IP 去重后)'), + sa.Column('uv_ip_set', sa.Text(), nullable=True, comment='独立 IP 列表 CSV'), + sa.Column('last_hit', sa.DateTime(), nullable=True, comment='最近访问时间'), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('site_page_stat', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_site_page_stat_day'), ['day'], unique=False) + batch_op.create_index(batch_op.f('ix_site_page_stat_path'), ['path'], unique=False) + + op.create_table('site_visit_log', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='记录ID'), + sa.Column('path', sa.String(length=255), nullable=True, comment='访问路径'), + sa.Column('referer', sa.String(length=255), nullable=True, comment='来源页'), + sa.Column('ua', sa.String(length=255), nullable=True, comment='UA'), + sa.Column('ip', sa.String(length=64), nullable=True, comment='访问IP'), + sa.Column('day', sa.String(length=10), nullable=True, comment='日期 YYYY-MM-DD'), + sa.Column('visit_at', sa.DateTime(), nullable=True, comment='访问时间'), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('site_visit_log', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_site_visit_log_day'), ['day'], unique=False) + batch_op.create_index(batch_op.f('ix_site_visit_log_path'), ['path'], unique=False) + batch_op.create_index(batch_op.f('ix_site_visit_log_visit_at'), ['visit_at'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('site_visit_log', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_site_visit_log_visit_at')) + batch_op.drop_index(batch_op.f('ix_site_visit_log_path')) + batch_op.drop_index(batch_op.f('ix_site_visit_log_day')) + + op.drop_table('site_visit_log') + with op.batch_alter_table('site_page_stat', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_site_page_stat_path')) + batch_op.drop_index(batch_op.f('ix_site_page_stat_day')) + + op.drop_table('site_page_stat') + # ### end Alembic commands ### diff --git a/migrations/versions/ec7c5d1aab2e_add_site_about_table.py b/migrations/versions/ec7c5d1aab2e_add_site_about_table.py new file mode 100644 index 0000000..09acf13 --- /dev/null +++ b/migrations/versions/ec7c5d1aab2e_add_site_about_table.py @@ -0,0 +1,39 @@ +"""add site_about table + +Revision ID: ec7c5d1aab2e +Revises: 38751d88d42c +Create Date: 2026-09-05 17:50:00.000000 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ec7c5d1aab2e' +down_revision = '38751d88d42c' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'site_about', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='主键'), + sa.Column('site_name', sa.String(length=120), nullable=True, comment='站点名'), + sa.Column('content_md', sa.Text(), nullable=True, comment='关于本站正文 (Markdown)'), + sa.Column('icp', sa.String(length=120), nullable=True, comment='ICP 备案号'), + sa.Column('contact_email', sa.String(length=120), nullable=True, comment='联系邮箱'), + sa.Column('contact_qq', sa.String(length=32), nullable=True, comment='联系 QQ'), + sa.Column('contact_wechat', sa.String(length=120), nullable=True, comment='微信号'), + sa.Column('contact_telegram', sa.String(length=120), nullable=True, comment='Telegram'), + sa.Column('contact_github', sa.String(length=255), nullable=True, comment='GitHub 链接'), + sa.Column('donate_url', sa.String(length=255), nullable=True, comment='捐赠/打赏 URL 或二维码图片地址'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id'), + ) + + +def downgrade(): + op.drop_table('site_about') diff --git a/migrations/versions/fdbdc3aab8da_init_schema.py b/migrations/versions/fdbdc3aab8da_init_schema.py new file mode 100644 index 0000000..38fbb38 --- /dev/null +++ b/migrations/versions/fdbdc3aab8da_init_schema.py @@ -0,0 +1,160 @@ +"""init schema + +Revision ID: fdbdc3aab8da +Revises: +Create Date: 2026-09-05 14:27:59.648487 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'fdbdc3aab8da' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('admin_admin_log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('method', sa.String(length=10), nullable=True), + sa.Column('uid', sa.Integer(), nullable=True), + sa.Column('url', sa.String(length=255), nullable=True), + sa.Column('desc', sa.Text(), nullable=True), + sa.Column('ip', sa.String(length=255), nullable=True), + sa.Column('success', sa.Integer(), nullable=True), + sa.Column('user_agent', sa.Text(), nullable=True), + sa.Column('create_time', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_dept', + sa.Column('id', sa.Integer(), nullable=False, comment='部门ID'), + sa.Column('parent_id', sa.Integer(), nullable=True, comment='父级编号'), + sa.Column('dept_name', sa.String(length=50), nullable=True, comment='部门名称'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('leader', sa.String(length=50), nullable=True, comment='负责人'), + sa.Column('phone', sa.String(length=20), nullable=True, comment='联系方式'), + sa.Column('email', sa.String(length=50), nullable=True, comment='邮箱'), + sa.Column('status', sa.Integer(), nullable=True, comment='状态(1开启,0关闭)'), + sa.Column('remark', sa.Text(), nullable=True, comment='备注'), + sa.Column('address', sa.String(length=255), nullable=True, comment='详细地址'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_dict_data', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('data_label', sa.String(length=255), nullable=True, comment='字典类型名称'), + sa.Column('data_value', sa.String(length=255), nullable=True, comment='字典类型标识'), + sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型描述'), + sa.Column('is_default', sa.Integer(), nullable=True, comment='是否默认'), + sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'), + sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'), + sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_dict_type', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('type_name', sa.String(length=255), nullable=True, comment='字典类型名称'), + sa.Column('type_code', sa.String(length=255), nullable=True, comment='字典类型标识'), + sa.Column('description', sa.String(length=255), nullable=True, comment='字典类型描述'), + sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'), + sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_mail', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='邮件编号'), + sa.Column('receiver', sa.String(length=1024), nullable=True, comment='收件人邮箱'), + sa.Column('subject', sa.String(length=128), nullable=True, comment='邮件主题'), + sa.Column('content', sa.Text(), nullable=True, comment='邮件正文'), + sa.Column('user_id', sa.Integer(), nullable=True, comment='发送人id'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_photo', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=255), nullable=False), + sa.Column('href', sa.String(length=255), nullable=True), + sa.Column('mime', sa.CHAR(length=50), nullable=False), + sa.Column('size', sa.CHAR(length=30), nullable=False), + sa.Column('create_time', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_power', + sa.Column('id', sa.Integer(), nullable=False, comment='权限编号'), + sa.Column('name', sa.String(length=255), nullable=True, comment='权限名称'), + sa.Column('type', sa.String(length=1), nullable=True, comment='权限类型'), + sa.Column('code', sa.String(length=30), nullable=True, comment='权限标识'), + sa.Column('url', sa.String(length=255), nullable=True, comment='权限路径'), + sa.Column('open_type', sa.String(length=10), nullable=True, comment='打开方式'), + sa.Column('parent_id', sa.Integer(), nullable=True, comment='父类编号'), + sa.Column('icon', sa.String(length=128), nullable=True, comment='图标'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'), + sa.Column('enable', sa.Integer(), nullable=True, comment='是否开启'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_role', + sa.Column('id', sa.Integer(), nullable=False, comment='角色ID'), + sa.Column('name', sa.String(length=255), nullable=True, comment='角色名称'), + sa.Column('code', sa.String(length=255), nullable=True, comment='角色标识'), + sa.Column('enable', sa.Integer(), nullable=True, comment='是否启用'), + sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'), + sa.Column('details', sa.String(length=255), nullable=True, comment='详情'), + sa.Column('sort', sa.Integer(), nullable=True, comment='排序'), + sa.Column('create_time', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_time', sa.DateTime(), nullable=True, comment='更新时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_user', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='用户ID'), + sa.Column('username', sa.String(length=20), nullable=True, comment='用户名'), + sa.Column('realname', sa.String(length=20), nullable=True, comment='真实名字'), + sa.Column('avatar', sa.String(length=255), nullable=True, comment='头像'), + sa.Column('remark', sa.String(length=255), nullable=True, comment='备注'), + sa.Column('password_hash', sa.String(length=128), nullable=True, comment='哈希密码'), + sa.Column('enable', sa.Integer(), nullable=True, comment='启用'), + sa.Column('dept_id', sa.Integer(), nullable=True, comment='部门id'), + sa.Column('create_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.Column('update_at', sa.DateTime(), nullable=True, comment='创建时间'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_role_power', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'), + sa.Column('power_id', sa.Integer(), nullable=True, comment='用户编号'), + sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'), + sa.ForeignKeyConstraint(['power_id'], ['admin_power.id'], ), + sa.ForeignKeyConstraint(['role_id'], ['admin_role.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('admin_user_role', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False, comment='标识'), + sa.Column('user_id', sa.Integer(), nullable=True, comment='用户编号'), + sa.Column('role_id', sa.Integer(), nullable=True, comment='角色编号'), + sa.ForeignKeyConstraint(['role_id'], ['admin_role.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['admin_user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('admin_user_role') + op.drop_table('admin_role_power') + op.drop_table('admin_user') + op.drop_table('admin_role') + op.drop_table('admin_power') + op.drop_table('admin_photo') + op.drop_table('admin_mail') + op.drop_table('admin_dict_type') + op.drop_table('admin_dict_data') + op.drop_table('admin_dept') + op.drop_table('admin_admin_log') + # ### end Alembic commands ###