feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,709 @@
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
|
||||
from sqlalchemy import select, delete as sa_delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from zq_smart_table.permission.model import (
|
||||
SmartTableRole, SmartTableCollaborator, SmartTableFieldPerm, SmartTableRowRule,
|
||||
SYSTEM_CAPABILITIES, ROLE_PRIORITY,
|
||||
)
|
||||
from zq_smart_table.model import SmartTable
|
||||
from utils.context import get_current_user_info_from_context
|
||||
|
||||
|
||||
SUPERADMIN_CAPABILITIES = {k: True for k in SYSTEM_CAPABILITIES["owner"]}
|
||||
|
||||
|
||||
class PermissionService:
|
||||
"""多维表格权限判定服务"""
|
||||
|
||||
# ─── 系统预置角色 ───
|
||||
|
||||
@classmethod
|
||||
async def ensure_system_roles(cls, db: AsyncSession, table_id: str) -> Dict[str, str]:
|
||||
"""确保表拥有 4 个系统预置角色,返回 {role_type: role_id}"""
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.table_id == table_id,
|
||||
SmartTableRole.is_system == True, # noqa: E712
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
existing = {r.role_type: r for r in result.scalars().all()}
|
||||
|
||||
role_map: Dict[str, str] = {}
|
||||
names = {"owner": "所有者", "manager": "管理者", "editor": "编辑者", "viewer": "只读者"}
|
||||
|
||||
for rt, caps in SYSTEM_CAPABILITIES.items():
|
||||
if rt in existing:
|
||||
role_map[rt] = existing[rt].id
|
||||
else:
|
||||
role = SmartTableRole(
|
||||
table_id=table_id,
|
||||
name=names[rt],
|
||||
role_type=rt,
|
||||
capabilities=caps,
|
||||
is_system=True,
|
||||
)
|
||||
db.add(role)
|
||||
await db.flush()
|
||||
role_map[rt] = role.id
|
||||
|
||||
await db.commit()
|
||||
return role_map
|
||||
|
||||
# ─── 初始化所有者 ───
|
||||
|
||||
@classmethod
|
||||
async def init_table_owner(cls, db: AsyncSession, table_id: str, user_id: str) -> None:
|
||||
"""创建表时自动将创建者设为所有者"""
|
||||
role_map = await cls.ensure_system_roles(db, table_id)
|
||||
owner_role_id = role_map["owner"]
|
||||
|
||||
existing = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.subject_type == "user",
|
||||
SmartTableCollaborator.subject_id == user_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
return
|
||||
|
||||
collab = SmartTableCollaborator(
|
||||
table_id=table_id,
|
||||
subject_type="user",
|
||||
subject_id=user_id,
|
||||
role_id=owner_role_id,
|
||||
)
|
||||
db.add(collab)
|
||||
await db.commit()
|
||||
|
||||
# ─── 获取有效角色 ───
|
||||
|
||||
@classmethod
|
||||
async def get_effective_role(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
user_id: str, dept_id: Optional[str] = None,
|
||||
role_ids: Optional[List[str]] = None,
|
||||
is_superuser: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取用户对某表的有效角色。
|
||||
优先级: superadmin > user直接 > dept > system_role
|
||||
多个匹配时取权限最高的角色。
|
||||
"""
|
||||
if is_superuser:
|
||||
return {
|
||||
"role_type": "superadmin",
|
||||
"role_name": "超级管理员",
|
||||
"capabilities": SUPERADMIN_CAPABILITIES,
|
||||
"role_id": None,
|
||||
}
|
||||
|
||||
# 查同表创建者(兜底:如果 collaborator 记录缺失但用户是表的创建者)
|
||||
table = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
table_obj = table.scalar_one_or_none()
|
||||
if table_obj and table_obj.sys_creator_id == user_id:
|
||||
role_map = await cls.ensure_system_roles(db, table_id)
|
||||
owner_role_id = role_map["owner"]
|
||||
return {
|
||||
"role_type": "owner",
|
||||
"role_name": "所有者",
|
||||
"capabilities": dict(SYSTEM_CAPABILITIES["owner"]),
|
||||
"role_id": owner_role_id,
|
||||
}
|
||||
|
||||
# 查询所有匹配的协作者记录
|
||||
conditions = [
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
]
|
||||
|
||||
subject_conditions = [
|
||||
(SmartTableCollaborator.subject_type == "user") & (SmartTableCollaborator.subject_id == user_id)
|
||||
]
|
||||
if dept_id:
|
||||
subject_conditions.append(
|
||||
(SmartTableCollaborator.subject_type == "dept") & (SmartTableCollaborator.subject_id == dept_id)
|
||||
)
|
||||
if role_ids:
|
||||
for rid in role_ids:
|
||||
subject_conditions.append(
|
||||
(SmartTableCollaborator.subject_type == "role") & (SmartTableCollaborator.subject_id == rid)
|
||||
)
|
||||
|
||||
from sqlalchemy import or_
|
||||
conditions.append(or_(*subject_conditions))
|
||||
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(*conditions)
|
||||
)
|
||||
collabs = list(result.scalars().all())
|
||||
|
||||
if not collabs:
|
||||
return None
|
||||
|
||||
# 加载对应角色,取优先级最高的
|
||||
best_collab = None
|
||||
best_role = None
|
||||
best_priority = -1
|
||||
|
||||
for c in collabs:
|
||||
role_result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == c.role_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = role_result.scalar_one_or_none()
|
||||
if not role:
|
||||
continue
|
||||
|
||||
# user 直接授权优先级额外 +10
|
||||
priority = ROLE_PRIORITY.get(role.role_type, 50)
|
||||
if c.subject_type == "user":
|
||||
priority += 10
|
||||
|
||||
if priority > best_priority:
|
||||
best_priority = priority
|
||||
best_collab = c
|
||||
best_role = role
|
||||
|
||||
if not best_role:
|
||||
return None
|
||||
|
||||
return {
|
||||
"role_type": best_role.role_type,
|
||||
"role_name": best_role.name,
|
||||
"capabilities": dict(best_role.capabilities) if best_role.capabilities else {},
|
||||
"role_id": best_role.id,
|
||||
}
|
||||
|
||||
# ─── 能力检查 ───
|
||||
|
||||
@classmethod
|
||||
async def check_capability(
|
||||
cls, db: AsyncSession, table_id: str, capability: str,
|
||||
) -> bool:
|
||||
"""检查当前用户是否具有某项能力"""
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
return False
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
return False
|
||||
|
||||
return role_info["capabilities"].get(capability, False)
|
||||
|
||||
@classmethod
|
||||
async def require_capability(
|
||||
cls, db: AsyncSession, table_id: str, capability: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""检查能力,无权限则抛 403"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="未登录")
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
raise HTTPException(status_code=403, detail="无权访问此表")
|
||||
|
||||
if not role_info["capabilities"].get(capability, False):
|
||||
raise HTTPException(status_code=403, detail=f"无权执行此操作({capability})")
|
||||
|
||||
return role_info
|
||||
|
||||
@classmethod
|
||||
async def require_table_access(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""检查表访问权限(至少 viewer),无权限则抛 403"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="未登录")
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
raise HTTPException(status_code=403, detail="无权访问此表")
|
||||
|
||||
return role_info
|
||||
|
||||
# ─── 列权限 ───
|
||||
|
||||
@classmethod
|
||||
async def get_field_permissions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
) -> Dict[str, str]:
|
||||
"""获取某角色的列权限映射: {field_id: access}"""
|
||||
result = await db.execute(
|
||||
select(SmartTableFieldPerm).where(
|
||||
SmartTableFieldPerm.table_id == table_id,
|
||||
SmartTableFieldPerm.role_id == role_id,
|
||||
SmartTableFieldPerm.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return {fp.field_id: fp.access for fp in result.scalars().all()}
|
||||
|
||||
@classmethod
|
||||
async def get_accessible_field_ids(
|
||||
cls, db: AsyncSession, table_id: str, role_id: Optional[str],
|
||||
mode: str = "read",
|
||||
) -> Optional[List[str]]:
|
||||
"""
|
||||
获取角色可访问的字段ID列表。
|
||||
mode="read" 返回 access != "hidden" 的字段
|
||||
mode="write" 返回 access == "write" 的字段
|
||||
如果没有配置列权限,返回 None 表示不限制。
|
||||
"""
|
||||
if not role_id:
|
||||
return None
|
||||
|
||||
perms = await cls.get_field_permissions(db, table_id, role_id)
|
||||
if not perms:
|
||||
return None
|
||||
|
||||
if mode == "write":
|
||||
return [fid for fid, acc in perms.items() if acc == "write"]
|
||||
else:
|
||||
return [fid for fid, acc in perms.items() if acc != "hidden"]
|
||||
|
||||
@classmethod
|
||||
async def batch_set_field_permissions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
permissions: List[Dict[str, str]],
|
||||
) -> None:
|
||||
"""批量设置列权限"""
|
||||
await db.execute(
|
||||
sa_delete(SmartTableFieldPerm).where(
|
||||
SmartTableFieldPerm.table_id == table_id,
|
||||
SmartTableFieldPerm.role_id == role_id,
|
||||
)
|
||||
)
|
||||
|
||||
for p in permissions:
|
||||
fp = SmartTableFieldPerm(
|
||||
table_id=table_id,
|
||||
role_id=role_id,
|
||||
field_id=p["field_id"],
|
||||
access=p.get("access", "write"),
|
||||
)
|
||||
db.add(fp)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# ─── 行权限 ───
|
||||
|
||||
@classmethod
|
||||
async def get_row_rules(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
) -> List[SmartTableRowRule]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRowRule).where(
|
||||
SmartTableRowRule.table_id == table_id,
|
||||
SmartTableRowRule.role_id == role_id,
|
||||
SmartTableRowRule.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def upsert_row_rule(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
rule_type: str, mode: str, conditions: List[Dict[str, Any]],
|
||||
) -> SmartTableRowRule:
|
||||
"""创建或更新行权限规则"""
|
||||
result = await db.execute(
|
||||
select(SmartTableRowRule).where(
|
||||
SmartTableRowRule.table_id == table_id,
|
||||
SmartTableRowRule.role_id == role_id,
|
||||
SmartTableRowRule.rule_type == rule_type,
|
||||
SmartTableRowRule.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
rule = result.scalar_one_or_none()
|
||||
|
||||
if rule:
|
||||
rule.mode = mode
|
||||
rule.conditions = conditions
|
||||
else:
|
||||
rule = SmartTableRowRule(
|
||||
table_id=table_id,
|
||||
role_id=role_id,
|
||||
rule_type=rule_type,
|
||||
mode=mode,
|
||||
conditions=conditions,
|
||||
)
|
||||
db.add(rule)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
return rule
|
||||
|
||||
# ─── 协作者管理 ───
|
||||
|
||||
@classmethod
|
||||
async def get_collaborators(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> List[SmartTableCollaborator]:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
).order_by(SmartTableCollaborator.sys_create_datetime)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def add_collaborator(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
subject_type: str, subject_id: str, role_id: str,
|
||||
) -> SmartTableCollaborator:
|
||||
existing = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.subject_type == subject_type,
|
||||
SmartTableCollaborator.subject_id == subject_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = existing.scalar_one_or_none()
|
||||
if collab:
|
||||
collab.role_id = role_id
|
||||
else:
|
||||
collab = SmartTableCollaborator(
|
||||
table_id=table_id,
|
||||
subject_type=subject_type,
|
||||
subject_id=subject_id,
|
||||
role_id=role_id,
|
||||
)
|
||||
db.add(collab)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(collab)
|
||||
return collab
|
||||
|
||||
@classmethod
|
||||
async def update_collaborator_role(
|
||||
cls, db: AsyncSession, collab_id: str, role_id: str,
|
||||
) -> Optional[SmartTableCollaborator]:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.id == collab_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = result.scalar_one_or_none()
|
||||
if not collab:
|
||||
return None
|
||||
collab.role_id = role_id
|
||||
await db.commit()
|
||||
await db.refresh(collab)
|
||||
return collab
|
||||
|
||||
@classmethod
|
||||
async def remove_collaborator(
|
||||
cls, db: AsyncSession, collab_id: str,
|
||||
) -> bool:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.id == collab_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = result.scalar_one_or_none()
|
||||
if not collab:
|
||||
return False
|
||||
collab.is_deleted = True
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
# ─── 角色管理 ───
|
||||
|
||||
@classmethod
|
||||
async def get_roles(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> List[SmartTableRole]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.table_id == table_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
).order_by(SmartTableRole.sys_create_datetime)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def create_custom_role(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
name: str, capabilities: Dict[str, bool],
|
||||
) -> SmartTableRole:
|
||||
role = SmartTableRole(
|
||||
table_id=table_id,
|
||||
name=name,
|
||||
role_type="custom",
|
||||
capabilities=capabilities,
|
||||
is_system=False,
|
||||
)
|
||||
db.add(role)
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
async def update_role(
|
||||
cls, db: AsyncSession, role_id: str,
|
||||
name: Optional[str] = None, capabilities: Optional[Dict[str, bool]] = None,
|
||||
) -> Optional[SmartTableRole]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == role_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
return None
|
||||
if name is not None:
|
||||
role.name = name
|
||||
if capabilities is not None:
|
||||
role.capabilities = capabilities
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
async def delete_role(
|
||||
cls, db: AsyncSession, role_id: str,
|
||||
) -> bool:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == role_id,
|
||||
SmartTableRole.is_system == False, # noqa: E712
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
return False
|
||||
role.is_deleted = True
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
# ─── 获取 my-permission ───
|
||||
|
||||
@classmethod
|
||||
async def get_my_permission(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""获取当前用户对某表的完整权限信息"""
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
return {
|
||||
"role_type": "none",
|
||||
"role_name": "无权限",
|
||||
"capabilities": {},
|
||||
"field_permissions": {},
|
||||
"row_view_mode": "none",
|
||||
"row_edit_mode": "none",
|
||||
}
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
|
||||
if not role_info:
|
||||
return {
|
||||
"role_type": "none",
|
||||
"role_name": "无权限",
|
||||
"capabilities": {},
|
||||
"field_permissions": {},
|
||||
"row_view_mode": "none",
|
||||
"row_edit_mode": "none",
|
||||
}
|
||||
|
||||
role_id = role_info.get("role_id")
|
||||
|
||||
field_perms: Dict[str, str] = {}
|
||||
row_view_mode = "all"
|
||||
row_edit_mode = "all"
|
||||
|
||||
if role_id:
|
||||
field_perms = await cls.get_field_permissions(db, table_id, role_id)
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
for rr in row_rules:
|
||||
if rr.rule_type == "view":
|
||||
row_view_mode = rr.mode
|
||||
elif rr.rule_type == "edit":
|
||||
row_edit_mode = rr.mode
|
||||
|
||||
out: Dict[str, Any] = {
|
||||
"role_type": role_info["role_type"],
|
||||
"role_name": role_info["role_name"],
|
||||
"capabilities": role_info["capabilities"],
|
||||
"field_permissions": field_perms,
|
||||
"row_view_mode": row_view_mode,
|
||||
"row_edit_mode": row_edit_mode,
|
||||
}
|
||||
|
||||
# 文档无列/行数据维度,列权限与行规则不对文档内容生效;归一化返回值以免与多维表格混淆
|
||||
tbl_row = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
tbl_obj = tbl_row.scalar_one_or_none()
|
||||
if tbl_obj and getattr(tbl_obj, "type", "table") == "document":
|
||||
out["field_permissions"] = {}
|
||||
out["row_view_mode"] = "all"
|
||||
out["row_edit_mode"] = "all"
|
||||
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
async def require_multidimensional_table(cls, db: AsyncSession, table_id: str) -> None:
|
||||
"""列权限、行权限仅适用于多维表格(type=table),文档页应使用协作者角色与能力位。"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
result = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
tbl = result.scalar_one_or_none()
|
||||
if not tbl:
|
||||
raise HTTPException(status_code=404, detail="表不存在")
|
||||
if getattr(tbl, "type", "table") == "document":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="文档不支持列权限与行权限配置,请通过协作者角色控制访问",
|
||||
)
|
||||
|
||||
# ─── 行权限过滤 ───
|
||||
|
||||
@classmethod
|
||||
async def build_row_filter_conditions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: Optional[str],
|
||||
user_id: str, rule_type: str = "view",
|
||||
) -> list:
|
||||
"""
|
||||
根据行权限规则构建 SQLAlchemy 过滤条件列表。
|
||||
rule_type: "view" 或 "edit"
|
||||
返回空列表表示不限制。
|
||||
"""
|
||||
from zq_smart_table.model import SmartRecord
|
||||
from app.db_compat import json_extract
|
||||
|
||||
if not role_id:
|
||||
return []
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
rule = next((r for r in row_rules if r.rule_type == rule_type), None)
|
||||
if not rule or rule.mode == "all":
|
||||
return []
|
||||
|
||||
if rule.mode == "creator_only":
|
||||
return [SmartRecord.sys_creator_id == user_id]
|
||||
|
||||
if rule.mode == "conditions" and rule.conditions:
|
||||
from sqlalchemy import or_, and_
|
||||
conds = []
|
||||
for c in rule.conditions:
|
||||
fid = c.get("field_id")
|
||||
op = c.get("operator", "equals")
|
||||
val = c.get("value")
|
||||
if not fid:
|
||||
continue
|
||||
col = json_extract(SmartRecord.values, fid)
|
||||
if op == "equals":
|
||||
conds.append(col == str(val) if val is not None else col == None) # noqa: E711
|
||||
elif op == "contains":
|
||||
conds.append(col.ilike(f"%{val}%") if val else col == col)
|
||||
elif op == "isEmpty":
|
||||
conds.append(or_(col == None, col == "")) # noqa: E711
|
||||
elif op == "isNotEmpty":
|
||||
conds.append(and_(col != None, col != "")) # noqa: E711
|
||||
elif op == "greaterThan":
|
||||
from sqlalchemy import cast, String
|
||||
conds.append(cast(col, String) > str(val))
|
||||
elif op == "lessThan":
|
||||
from sqlalchemy import cast, String
|
||||
conds.append(cast(col, String) < str(val))
|
||||
return conds
|
||||
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def check_row_edit_permission(
|
||||
cls, db: AsyncSession, table_id: str, record, role_id: Optional[str], user_id: str,
|
||||
) -> bool:
|
||||
"""检查当前用户是否有权编辑指定记录(基于行编辑权限规则)"""
|
||||
if not role_id:
|
||||
return True
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
rule = next((r for r in row_rules if r.rule_type == "edit"), None)
|
||||
if not rule or rule.mode == "all":
|
||||
return True
|
||||
|
||||
if rule.mode == "creator_only":
|
||||
return getattr(record, "sys_creator_id", None) == user_id
|
||||
|
||||
if rule.mode == "conditions" and rule.conditions:
|
||||
from app.db_compat import json_extract
|
||||
values = record.values or {}
|
||||
for c in rule.conditions:
|
||||
fid = c.get("field_id")
|
||||
op = c.get("operator", "equals")
|
||||
val = c.get("value")
|
||||
if not fid:
|
||||
continue
|
||||
cell_val = str(values.get(fid, ""))
|
||||
if op == "equals" and cell_val != str(val):
|
||||
return False
|
||||
if op == "contains" and str(val or "") not in cell_val:
|
||||
return False
|
||||
if op == "isEmpty" and cell_val != "":
|
||||
return False
|
||||
if op == "isNotEmpty" and cell_val == "":
|
||||
return False
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
# ─── 过滤记录值(去掉不可见字段) ───
|
||||
|
||||
@classmethod
|
||||
def filter_record_values(
|
||||
cls, values: Dict[str, Any], accessible_fields: Optional[List[str]],
|
||||
) -> Dict[str, Any]:
|
||||
"""过滤记录中不可见的字段值"""
|
||||
if accessible_fields is None:
|
||||
return values
|
||||
return {k: v for k, v in values.items() if k in accessible_fields}
|
||||
Reference in New Issue
Block a user