"""给指定运营账号授予推介材料生成权限。""" from __future__ import annotations import argparse import asyncio import sys from datetime import UTC, datetime, timedelta from pathlib import Path from sqlalchemy import text PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from app.core.contracts import RequestContext # noqa: E402 from app.infrastructure.db import SessionFactory, engine # noqa: E402 from app.service.identity_service import IdentityService # noqa: E402 PROMOTION_ROLE_CODE = "promotion_operator" PROMOTION_ROLE_NAME = "推广材料运营" DEFAULT_USER_ID = 9006 GRANTED_CODES = ("promotion:read", "promotion:write") async def apply(user_id: int, *, dry_run: bool) -> int: now = datetime.now(UTC).replace(tzinfo=None) assigned_at = now - timedelta(seconds=5) async with SessionFactory() as session, session.begin(): user = ( await session.execute( text( "SELECT id, username, status FROM sys_user WHERE id=:user_id" ), {"user_id": user_id}, ) ).mappings().first() if user is None: raise SystemExit(f"未找到用户 id={user_id}") if user["status"] != "正常": raise SystemExit(f"用户 id={user_id} 未启用,拒绝授权") role_id = await session.scalar( text("SELECT id FROM sys_role WHERE role_code=:code"), {"code": PROMOTION_ROLE_CODE}, ) permission_rows = ( await session.execute( text( "SELECT permission_code, id FROM sys_permission " "WHERE permission_code IN ('promotion:read', 'promotion:write')" ) ) ).all() permission_ids = {str(code): int(permission_id) for code, permission_id in permission_rows} missing = [code for code in GRANTED_CODES if code not in permission_ids] if missing: raise SystemExit(f"数据库缺少权限码,请先运行权限种子:{missing}") print(f"用户:id={user_id} username={user['username']}") print( f"角色:{PROMOTION_ROLE_CODE} " f"{'已存在 id=' + str(role_id) if role_id else '将新建'}" ) print(f"授权范围:{', '.join(GRANTED_CODES)}") if dry_run: print("[dry-run] 未写入任何数据") return 0 if role_id is None: await session.execute( text( """ INSERT INTO sys_role (role_code, role_name, status, created_at, updated_at) VALUES (:code, :name, 'active', :now, :now) """ ), {"code": PROMOTION_ROLE_CODE, "name": PROMOTION_ROLE_NAME, "now": now}, ) role_id = await session.scalar( text("SELECT id FROM sys_role WHERE role_code=:code"), {"code": PROMOTION_ROLE_CODE}, ) role_id = int(role_id) for code in GRANTED_CODES: exists = await session.scalar( text( """ SELECT COUNT(*) FROM sys_role_permission WHERE role_id=:role_id AND permission_id=:permission_id """ ), {"role_id": role_id, "permission_id": permission_ids[code]}, ) if not exists: await session.execute( text( """ INSERT INTO sys_role_permission (role_id, permission_id, created_at) VALUES (:role_id, :permission_id, :now) """ ), { "role_id": role_id, "permission_id": permission_ids[code], "now": now, }, ) await session.execute( text( """ INSERT INTO sys_user_role (user_id, role_id, assigned_at) VALUES (:user_id, :role_id, :assigned_at) ON DUPLICATE KEY UPDATE assigned_at=:assigned_at """ ), {"user_id": user_id, "role_id": role_id, "assigned_at": assigned_at}, ) return await verify(user_id) async def verify(user_id: int) -> int: context = await IdentityService().resolve( RequestContext(user_id=str(user_id), trace_id="grant-promotion-operator") ) expected = set(GRANTED_CODES) actual = set(context.permissions) print(f"真实解析角色:{sorted(context.roles)}") print(f"真实解析权限:{sorted(actual)}") missing = expected - actual if missing: raise SystemExit(f"授权验证失败,缺少权限:{sorted(missing)}") print("推介材料生成权限已生效:promotion:read、promotion:write") return 0 async def main() -> int: parser = argparse.ArgumentParser(description="授予运营账号推介材料生成权限") parser.add_argument("--user-id", type=int, default=DEFAULT_USER_ID) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() try: return await apply(args.user_id, dry_run=args.dry_run) finally: await engine.dispose() if __name__ == "__main__": sys.exit(asyncio.run(main()))