"""增加文档恢复审计动作和回收站查询索引。 Revision ID: 0002_add_restore_audit_action Revises: 0001_initial_schema Create Date: 2026-07-26 """ from __future__ import annotations from collections.abc import Sequence from alembic import op import sqlalchemy as sa revision: str = "0002_add_restore_audit_action" down_revision: str | None = "0001_initial_schema" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None AUDIT_ACTIONS_V1 = ( "LOGIN", "LOGOUT", "VIEW_DOCUMENT", "DOWNLOAD_DOCUMENT", "UPLOAD_DOCUMENT", "BATCH_IMPORT", "EDIT_DOCUMENT", "DELETE_DOCUMENT", "CREATE_CATEGORY", "EDIT_CATEGORY", "DELETE_CATEGORY", "CHANGE_PERMISSION", "BIND_ATTACHMENT", "UNBIND_ATTACHMENT", ) RESTORE_ACTION = "RESTORE_DOCUMENT" AUDIT_CONSTRAINT = "audit_action_type" RECYCLE_INDEX = "ix_doc_document_recycle_deleted" def _check_sql(actions: tuple[str, ...]) -> str: values = ", ".join(f"'{action}'" for action in actions) return f"action_type IN ({values})" def _check_exists() -> bool: inspector = sa.inspect(op.get_bind()) return any( item.get("name") in {AUDIT_CONSTRAINT, f"ck_sys_audit_log_{AUDIT_CONSTRAINT}"} for item in inspector.get_check_constraints("sys_audit_log") ) def _index_exists() -> bool: inspector = sa.inspect(op.get_bind()) return any( item.get("name") == RECYCLE_INDEX for item in inspector.get_indexes("doc_document") ) def upgrade() -> None: if _check_exists(): op.drop_constraint( AUDIT_CONSTRAINT, "sys_audit_log", type_="check", ) op.create_check_constraint( AUDIT_CONSTRAINT, "sys_audit_log", _check_sql((*AUDIT_ACTIONS_V1, RESTORE_ACTION)), ) if not _index_exists(): op.create_index( RECYCLE_INDEX, "doc_document", ["is_deleted", "deleted_at", "id"], unique=False, ) def downgrade() -> None: restore_count = op.get_bind().scalar( sa.text( "SELECT COUNT(*) FROM sys_audit_log " "WHERE action_type = :action_type" ), {"action_type": RESTORE_ACTION}, ) if restore_count: raise RuntimeError( "存在RESTORE_DOCUMENT历史审计,拒绝无损降级;" "请保留0002或先按数据治理流程处理" ) if _index_exists(): op.drop_index(RECYCLE_INDEX, table_name="doc_document") if _check_exists(): op.drop_constraint( AUDIT_CONSTRAINT, "sys_audit_log", type_="check", ) op.create_check_constraint( AUDIT_CONSTRAINT, "sys_audit_log", _check_sql(AUDIT_ACTIONS_V1), )