feat(user): 用户认证状态/所属/账号类型 分类字段
- users 增: certification_status(uncertified|pending|certified|rejected)+certification_time, affiliation(independent|park)+park_id/park_name, account_type(opc_default|park_staff|platform_staff|provider|enterprise); alembic 0007
- repo: set_classification; _user_to_dict 含全部新字段; profile 返回认证/所属/账号类型
- admin: POST /admin/users/{id}/classification(认证/分类设置, action:user.manage) + UserClassificationRequest schema
- 迁移已应用, 导入通过
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
"""user classification/certification
|
||||
|
||||
Revision ID: 0007_user_classification
|
||||
Revises: 0006_user_source_fields
|
||||
Create Date: 2026-08-25
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0007_user_classification"
|
||||
down_revision = "0006_user_source_fields"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _add(table, col):
|
||||
conn = op.get_bind()
|
||||
cols = {r[1] for r in conn.execute(sa.text(f"PRAGMA table_info({table})"))}
|
||||
if col.name not in cols:
|
||||
op.add_column(table, col)
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
_add("users", sa.Column("certification_status", sa.String, server_default="uncertified"))
|
||||
_add("users", sa.Column("certification_time", sa.String, server_default=""))
|
||||
_add("users", sa.Column("affiliation", sa.String, server_default="independent"))
|
||||
_add("users", sa.Column("park_id", sa.String, server_default=""))
|
||||
_add("users", sa.Column("park_name", sa.String, server_default=""))
|
||||
_add("users", sa.Column("account_type", sa.String, server_default="opc_default"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
@@ -153,6 +153,11 @@ async def _profile_for(
|
||||
profile["compute_provisioned"] = bool(user.get("compute_provisioned", False))
|
||||
profile["compute_quota"] = user.get("compute_quota", 0)
|
||||
profile["compute_used_quota"] = user.get("compute_used_quota", 0)
|
||||
profile["certification_status"] = user.get("certification_status", "uncertified")
|
||||
profile["affiliation"] = user.get("affiliation", "independent")
|
||||
profile["park_id"] = user.get("park_id", "")
|
||||
profile["park_name"] = user.get("park_name", "")
|
||||
profile["account_type"] = user.get("account_type", "opc_default")
|
||||
profile["role"] = role
|
||||
profile["sub_role"] = sub_role
|
||||
profile["org_id"] = org_id
|
||||
|
||||
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..dependencies import get_db
|
||||
from ..schemas.admin import SetUserRoleRequest, SetUserStatusRequest
|
||||
from ..schemas.admin import SetUserRoleRequest, SetUserStatusRequest, UserClassificationRequest
|
||||
from ..schemas.admin import UserCreateRequest, RolePermissionRequest, UpdateUserRequest
|
||||
from ...rbac import require_permission, require_roles, write_audit
|
||||
from ...infrastructure.repositories import Database
|
||||
@@ -82,6 +82,23 @@ async def set_user_role(
|
||||
return {"ok": True, "user": await db.users.to_profile(updated)}
|
||||
|
||||
|
||||
@router.post("/users/{user_id}/classification", summary="设置用户认证状态/所属/账号类型")
|
||||
async def set_user_classification(
|
||||
user_id: str,
|
||||
req: UserClassificationRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:user.manage")),
|
||||
):
|
||||
fields = req.model_dump(exclude_none=True)
|
||||
updated = await db.users.set_classification(user_id, **fields)
|
||||
if updated is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
await write_audit(db, action="user.classification", resource="user", resource_id=user_id,
|
||||
detail=str(fields), user=actor, request=request)
|
||||
return {"ok": True, "user": updated}
|
||||
|
||||
|
||||
@router.post("/users/{user_id}/status", summary="禁用/启用用户")
|
||||
async def set_user_status(
|
||||
user_id: str,
|
||||
|
||||
@@ -9,6 +9,16 @@ class SetUserRoleRequest(BaseModel):
|
||||
class SetUserStatusRequest(BaseModel):
|
||||
status: str = "active" # active | disabled
|
||||
|
||||
|
||||
class UserClassificationRequest(BaseModel):
|
||||
"""用户 认证状态/所属/账号类型 分类字段。"""
|
||||
certification_status: str | None = None # uncertified|pending|certified|rejected
|
||||
certification_time: str | None = None
|
||||
affiliation: str | None = None # independent|park
|
||||
park_id: str | None = None
|
||||
park_name: str | None = None
|
||||
account_type: str | None = None # opc_default|park_staff|platform_staff|provider|enterprise
|
||||
|
||||
class UpdateUserRequest(BaseModel):
|
||||
nickname: str | None = None
|
||||
role: str | None = None
|
||||
|
||||
@@ -67,6 +67,13 @@ class User(Base):
|
||||
compute_username: Mapped[str] = mapped_column(String, default="") # 引擎用户名(= username)
|
||||
compute_quota: Mapped[int] = mapped_column(Integer, default=0) # 缓存引擎可用额度
|
||||
compute_used_quota: Mapped[int] = mapped_column(Integer, default=0) # 缓存引擎已用额度
|
||||
# ── 状态与身份分类 ──
|
||||
certification_status: Mapped[str] = mapped_column(String, default="uncertified") # uncertified|pending|certified|rejected
|
||||
certification_time: Mapped[str] = mapped_column(String, default="")
|
||||
affiliation: Mapped[str] = mapped_column(String, default="independent") # independent:独立OPC | park:所属园区
|
||||
park_id: Mapped[str] = mapped_column(String, default="") # 所属园区(id)
|
||||
park_name: Mapped[str] = mapped_column(String, default="") # 所属园区(名)
|
||||
account_type: Mapped[str] = mapped_column(String, default="opc_default") # opc_default|park_staff|platform_staff|provider|enterprise
|
||||
created_at: Mapped[str] = mapped_column(String, default="")
|
||||
updated_at: Mapped[str] = mapped_column(String, default="")
|
||||
|
||||
|
||||
@@ -167,6 +167,12 @@ def _user_to_dict(u: User) -> dict:
|
||||
"compute_username": u.compute_username,
|
||||
"compute_quota": u.compute_quota,
|
||||
"compute_used_quota": u.compute_used_quota,
|
||||
"certification_status": u.certification_status,
|
||||
"certification_time": u.certification_time,
|
||||
"affiliation": u.affiliation,
|
||||
"park_id": u.park_id,
|
||||
"park_name": u.park_name,
|
||||
"account_type": u.account_type,
|
||||
"created_at": u.created_at,
|
||||
"updated_at": u.updated_at,
|
||||
}
|
||||
@@ -287,6 +293,21 @@ class UserRepository:
|
||||
u.updated_at = utcnow_iso()
|
||||
await self.session.commit()
|
||||
|
||||
async def set_classification(self, user_id: str, **fields) -> dict | None:
|
||||
"""更新用户 认证状态/所属/账号类型 等分类字段。"""
|
||||
u = await self.session.get(User, user_id)
|
||||
if u is None:
|
||||
return None
|
||||
for k, v in fields.items():
|
||||
if k in {
|
||||
"certification_status", "certification_time", "affiliation",
|
||||
"park_id", "park_name", "account_type",
|
||||
}:
|
||||
setattr(u, k, v)
|
||||
u.updated_at = utcnow_iso()
|
||||
await self.session.commit()
|
||||
return _user_to_dict(u)
|
||||
|
||||
async def set_compute_mirror(self, user_id: str, *, provisioned: bool, username: str,
|
||||
quota: int, used_quota: int) -> None:
|
||||
"""回写算力引擎镜像(全局用户一致)。"""
|
||||
|
||||
Reference in New Issue
Block a user