feat: workspace CRUD at platform scope + drop audit_logs
新增系统管理模块 /api/v1/platform/*: - workspace 实体 CRUD(创建/列表/详情/更新/软删除) - workspace 成员 CRUD(添加/列表/更新/移除) - SystemAdminContext 依赖,仅 platform_role_id 指向 admin 角色的用户可访问 - /api/v1/auth/me 与 /auth/login 增 is_system_admin 派生字段 - 不变量:每个 workspace 至少保留一个 admin;系统管理员无法自我移除成员 - 软删除 workspace 级联软删除其成员 清理 audit_logs(无运行时写入,纯死特性): - baseline 移除 audit_logs 建表与三索引(20 → 19 tables) - 删除 AuditLogs 模型定义与 __init__.py 导出 - 清理 migrate_system_json / migrate_legacy_workspaces 中的 audit 写入与回填代码 API.md 增 §七系统管理,§七/§八/§九 顺延为 §八/§九/§十,附录 A/B 同步更新。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c263ae6a5f
commit
45f0ff534f
@@ -6,7 +6,6 @@ import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
@@ -17,7 +16,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from common.db import create_database_engine, create_session_factory
|
||||
from common.db.models import (
|
||||
AuditLogs,
|
||||
Roles,
|
||||
Users,
|
||||
WorkspaceMembers,
|
||||
@@ -95,7 +93,6 @@ def new_stats() -> dict[str, int]:
|
||||
"workspaces_updated": 0,
|
||||
"workspace_members_inserted": 0,
|
||||
"workspace_members_updated": 0,
|
||||
"audit_logs_workspace_backfilled": 0,
|
||||
}
|
||||
|
||||
|
||||
@@ -266,44 +263,6 @@ async def migrate_members(
|
||||
stats["workspace_members_updated"] += 1
|
||||
|
||||
|
||||
async def backfill_audit_workspaces(
|
||||
session: AsyncSession,
|
||||
source: list[LegacyWorkspace],
|
||||
workspace_ids: dict[str, str],
|
||||
users: dict[str, Users],
|
||||
stats: dict[str, int],
|
||||
) -> None:
|
||||
workspace_codes_by_username: dict[str, set[str]] = defaultdict(set)
|
||||
for workspace in source:
|
||||
for username in workspace.userIds:
|
||||
workspace_codes_by_username[username].add(workspace.id)
|
||||
|
||||
workspace_by_user_id = {
|
||||
users[username].user_id: workspace_ids[next(iter(codes))]
|
||||
for username, codes in workspace_codes_by_username.items()
|
||||
if len(codes) == 1
|
||||
}
|
||||
|
||||
audit_logs = (
|
||||
await session.scalars(
|
||||
select(AuditLogs).where(AuditLogs.workspace_id.is_(None))
|
||||
)
|
||||
).all()
|
||||
for audit_log in audit_logs:
|
||||
if (
|
||||
not isinstance(audit_log.detail_json, dict)
|
||||
or audit_log.detail_json.get("migration_source")
|
||||
!= "platform_data/system.json"
|
||||
):
|
||||
continue
|
||||
workspace_id = workspace_by_user_id.get(
|
||||
audit_log.actor_user_id
|
||||
)
|
||||
if workspace_id is not None:
|
||||
audit_log.workspace_id = workspace_id
|
||||
stats["audit_logs_workspace_backfilled"] += 1
|
||||
|
||||
|
||||
async def run_migration(
|
||||
database_url: str,
|
||||
source: list[LegacyWorkspace],
|
||||
@@ -339,13 +298,6 @@ async def run_migration(
|
||||
users,
|
||||
stats,
|
||||
)
|
||||
await backfill_audit_workspaces(
|
||||
session,
|
||||
source,
|
||||
workspace_ids,
|
||||
users,
|
||||
stats,
|
||||
)
|
||||
|
||||
if apply_changes:
|
||||
await session.commit()
|
||||
|
||||
Reference in New Issue
Block a user