diff --git a/app/api/routers/rbac_org.py b/app/api/routers/rbac_org.py index 4aa8a1c..e662e8e 100644 --- a/app/api/routers/rbac_org.py +++ b/app/api/routers/rbac_org.py @@ -6,11 +6,14 @@ import logging from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel +from sqlalchemy import select from ..dependencies import get_db, get_user_organizations, get_user_primary_org from ..schemas.org import OrgMemberAdd from ...rbac import require_roles from ...infrastructure.repositories import Database +from ...infrastructure.oss import resolve_url +from ...infrastructure.models import User from ...park import tenants as tnt log = logging.getLogger("rbac.org") @@ -18,6 +21,28 @@ log = logging.getLogger("rbac.org") router = APIRouter(tags=["org"]) +async def _normalize_org_member(raw: dict) -> dict: + """统一机构/企业成员返回格式,确保前端 OrgMember 类型字段齐全。 + + 前端期望字段:user_id, name, username, avatar, role, is_admin + 企业成员(tnt.company_members)返回:id, username, nickname, avatar, role, is_admin + 机构成员(db.org_members.list_for_org)返回:user_id, username, nickname, role, is_admin(无avatar) + """ + user_id = str(raw.get("user_id") or raw.get("id") or "") + username = raw.get("username") or "" + nickname = raw.get("nickname") or "" + name = nickname or username or user_id + avatar = raw.get("avatar") or "" + return { + "user_id": user_id, + "name": name, + "username": username, + "avatar": resolve_url(avatar), + "role": raw.get("role") or "", + "is_admin": bool(raw.get("is_admin", False)), + } + + @router.get("/org/me", summary="当前组织信息") async def org_me( db: Database = Depends(get_db), @@ -75,20 +100,55 @@ async def org_members( # 叠加制权限:operator 全量;本 org 的载体机构管理员;本企业的企业成员(org_id 亦可能是 company_id) caps = user.get("capabilities") or [] if "operator" in caps: - pass + log.info(f"[org_members] operator access, org_id={org_id}, user_id={user['id']}") elif await db.org_members.is_admin(org_id, user["id"]): - pass + log.info(f"[org_members] org admin access, org_id={org_id}, user_id={user['id']}") elif await db.company_members.get(user["id"], org_id): - pass + log.info(f"[org_members] company member access, org_id={org_id}, user_id={user['id']}") else: + log.warning(f"[org_members] 403 forbidden, org_id={org_id}, user_id={user['id']}, caps={caps}") raise HTTPException(status_code=403, detail="无权查看该组织成员") # org_id 可能是 organization_members.org_id(载体机构),也可能被前端当作 company_id 传入: # 优先返回企业成员(命中企业时),否则回退到机构成员。 - company_members = await tnt.company_members(org_id) + try: + company_members = await tnt.company_members(org_id) + log.info(f"[org_members] tnt.company_members returned {len(company_members)} members for org_id={org_id}") + except Exception as e: + log.error(f"[org_members] tnt.company_members failed for org_id={org_id}: {e}", exc_info=True) + company_members = [] + if company_members: - return {"items": company_members} - return {"items": await db.org_members.list_for_org(org_id)} + items = [await _normalize_org_member(m) for m in company_members] + log.info(f"[org_members] returning {len(items)} company members for org_id={org_id}") + return {"items": items} + + try: + org_members_list = await db.org_members.list_for_org(org_id) + log.info(f"[org_members] db.org_members.list_for_org returned {len(org_members_list)} members for org_id={org_id}") + except Exception as e: + log.error(f"[org_members] db.org_members.list_for_org failed for org_id={org_id}: {e}", exc_info=True) + org_members_list = [] + + # 机构成员没有 avatar 字段,批量从 users 表补查 + user_ids = [str(m.get("user_id") or "") for m in org_members_list if m.get("user_id")] + avatar_map = {} + if user_ids: + try: + users = (await db.session.execute( + select(User).where(User.id.in_(user_ids)) + )).scalars().all() + avatar_map = {u.id: (u.avatar or "") for u in users} + except Exception as e: + log.error(f"[org_members] failed to fetch avatars: {e}", exc_info=True) + avatar_map = {} + for m in org_members_list: + uid = str(m.get("user_id") or "") + if uid and not m.get("avatar"): + m["avatar"] = avatar_map.get(uid, "") + items = [await _normalize_org_member(m) for m in org_members_list] + log.info(f"[org_members] returning {len(items)} org members for org_id={org_id}") + return {"items": items} @router.post("/org/{org_id}/members", summary="添加机构成员(子账号)")