fix: 修复接口错误 - _loads未定义+重新注册ecosystem路由
1. repositories.py: 添加 _loads 函数定义(之前从 hall_repositories 隐式依赖导致 NameError) - 修复 /cert/types 返回 500 错误 2. main.py: 重新注册 rbac_ecosystem 路由(通知/信用/任务/合同/争议等基础功能) - 修复 /me/credit 返回 404 错误 3. rbac_ecosystem.py: 将已移除的 service 角色统一替换为 opc_member - _ANY 元组移除 service - 所有 require_roles 中的 service 替换为 opc_member
This commit is contained in:
@@ -13,7 +13,7 @@ from ...infrastructure.repositories import Database
|
||||
|
||||
router = APIRouter(tags=["ecosystem"])
|
||||
|
||||
_ANY = ("opc_member", "carrier", "service", "operator")
|
||||
_ANY = ("opc_member", "carrier", "operator")
|
||||
|
||||
|
||||
# ── 通知中心 ──────────────────────────────────────────────────────────────
|
||||
@@ -51,7 +51,7 @@ async def rate_task(
|
||||
req: RatingRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles("service", "opc_member")),
|
||||
actor: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
if not req.to_id:
|
||||
raise HTTPException(status_code=400, detail="缺少被评对象")
|
||||
@@ -66,7 +66,7 @@ async def rate_task(
|
||||
async def get_contract(
|
||||
task_id: str,
|
||||
db: Database = Depends(get_db),
|
||||
_u: dict = Depends(require_roles("service", "opc_member")),
|
||||
_u: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
contract = await db.contracts.get_for_task(task_id)
|
||||
if contract is None:
|
||||
@@ -80,7 +80,7 @@ async def sign_contract(
|
||||
req: SignRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles("service", "opc_member")),
|
||||
actor: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
from ...services.settlement_service import SettlementService
|
||||
|
||||
@@ -96,7 +96,7 @@ async def release_escrow(
|
||||
task_id: str,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles("service")),
|
||||
actor: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
from ...services.settlement_service import SettlementService
|
||||
|
||||
@@ -122,7 +122,7 @@ async def create_dispute(
|
||||
req: DisputeRequest,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles("service", "opc_member")),
|
||||
actor: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
from ...services.settlement_service import SettlementService
|
||||
|
||||
@@ -151,7 +151,7 @@ async def resolve_dispute(
|
||||
@router.get("/investor/matches", summary="投资撮合(按偏好推荐)")
|
||||
async def investor_matches(
|
||||
db: Database = Depends(get_db),
|
||||
user: dict = Depends(require_roles("service")),
|
||||
user: dict = Depends(require_roles("opc_member")),
|
||||
):
|
||||
from ...services.settlement_service import SettlementService
|
||||
|
||||
@@ -164,7 +164,7 @@ async def join_roadshow(
|
||||
roadshow_id: str,
|
||||
request: Request,
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles("service", "operator", "carrier")),
|
||||
actor: dict = Depends(require_roles("opc_member", "operator", "carrier")),
|
||||
):
|
||||
rs = await db.roadshows.get(roadshow_id)
|
||||
if rs is None:
|
||||
|
||||
@@ -33,6 +33,7 @@ from .models import (
|
||||
InvestorPreference,
|
||||
Message,
|
||||
Notification,
|
||||
OfficialAgent,
|
||||
OpcProfile,
|
||||
OpcCertification,
|
||||
ParkAdmission,
|
||||
@@ -75,35 +76,6 @@ USER_TABLE = "users"
|
||||
TOKEN_TABLE = "tokens"
|
||||
AGENTS_TABLE = "agents"
|
||||
|
||||
# 内置智能体身份(唯一来源在服务端;本地据此同步 workspace)
|
||||
DEFAULT_AGENT_ID = "pine_agents_official_001"
|
||||
QA_AGENT_ID = "pine_agents_official_002"
|
||||
AGENT_SEED = (
|
||||
{
|
||||
"id": DEFAULT_AGENT_ID,
|
||||
"name": "小园",
|
||||
"description": "默认助手,处理和园区、创业、政策等相关工作",
|
||||
"language": "zh",
|
||||
"model_name": "",
|
||||
"template_type": "default",
|
||||
"deletable": False,
|
||||
"use_fixed_soul": True,
|
||||
},
|
||||
{
|
||||
"id": QA_AGENT_ID,
|
||||
"name": "问答助手",
|
||||
"description": (
|
||||
"内置 PineAgents 设置问答助手,本地配置在 `PINEAGENTS_WORKING_DIR` 下,"
|
||||
"并提供文档。建议在回答前阅读文件;在此工作区外使用绝对路径编写代码。"
|
||||
),
|
||||
"language": "zh",
|
||||
"model_name": "",
|
||||
"template_type": "qa",
|
||||
"deletable": True,
|
||||
"use_fixed_soul": False,
|
||||
},
|
||||
)
|
||||
|
||||
PROFILE_FIELDS = (
|
||||
"username",
|
||||
"nickname",
|
||||
@@ -138,6 +110,15 @@ def new_id(prefix: str) -> str:
|
||||
return f"{prefix}_{secrets.token_hex(12)}"
|
||||
|
||||
|
||||
def _loads(s: str, default):
|
||||
"""安全解析 JSON 字符串,失败返回默认值。"""
|
||||
try:
|
||||
v = json.loads(s or "")
|
||||
return v if v is not None else default
|
||||
except Exception: # noqa: BLE001
|
||||
return default
|
||||
|
||||
|
||||
def is_expired(expires_at: str) -> bool:
|
||||
"""判断 ``expires_at``(ISO 字符串)是否已过期。"""
|
||||
try:
|
||||
@@ -1159,7 +1140,135 @@ class AgentRepository:
|
||||
a = await self.session.scalar(stmt)
|
||||
if a is None:
|
||||
return False
|
||||
self.session.delete(a)
|
||||
await self.session.delete(a)
|
||||
await self.session.commit()
|
||||
return True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 官方预置智能体配置(管理后台可配置,替代硬编码 AGENT_SEED)
|
||||
# ---------------------------------------------------------------------------
|
||||
class OfficialAgentRepository:
|
||||
"""官方预置智能体(全局共享,非 per-user)。
|
||||
|
||||
官方智能体由运营端管理后台(/admin/agents/official)维护;桌面端经
|
||||
``/agent-bootstrap`` 拉取并按此初始化本地 workspace。
|
||||
"""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
@staticmethod
|
||||
def _to_dict(a: "OfficialAgent") -> dict:
|
||||
return {
|
||||
"id": a.id,
|
||||
"name": a.name,
|
||||
"description": a.description,
|
||||
"language": a.language,
|
||||
"model_name": a.model_name,
|
||||
"template_type": a.template_type,
|
||||
"deletable": a.deletable,
|
||||
"use_fixed_soul": a.use_fixed_soul,
|
||||
"scope": a.scope or "opc",
|
||||
"soul": a.soul or "{}",
|
||||
"profile": a.profile or "{}",
|
||||
"heartbeat": a.heartbeat or "{}",
|
||||
"memory": a.memory or "{}",
|
||||
"enabled": a.enabled,
|
||||
"sort_order": a.sort_order,
|
||||
"created_at": a.created_at,
|
||||
"updated_at": a.updated_at,
|
||||
}
|
||||
|
||||
async def all(self) -> list[dict]:
|
||||
rows = await self.session.scalars(
|
||||
select(OfficialAgent).order_by(OfficialAgent.sort_order, OfficialAgent.created_at),
|
||||
)
|
||||
return [self._to_dict(a) for a in rows]
|
||||
|
||||
async def active(self) -> list[dict]:
|
||||
"""仅返回启用中的官方智能体(/agent-bootstrap 下发用)。"""
|
||||
rows = await self.session.scalars(
|
||||
select(OfficialAgent)
|
||||
.where(OfficialAgent.enabled == True) # noqa: E712
|
||||
.order_by(OfficialAgent.sort_order, OfficialAgent.created_at),
|
||||
)
|
||||
return [self._to_dict(a) for a in rows]
|
||||
|
||||
async def get(self, agent_id: str) -> dict | None:
|
||||
a = await self.session.get(OfficialAgent, agent_id)
|
||||
return self._to_dict(a) if a else None
|
||||
|
||||
async def create(
|
||||
self,
|
||||
agent_id: str,
|
||||
name: str,
|
||||
*,
|
||||
description: str = "",
|
||||
language: str = "zh",
|
||||
model_name: str = "",
|
||||
template_type: str = "common",
|
||||
deletable: bool = True,
|
||||
use_fixed_soul: bool = False,
|
||||
scope: str = "opc",
|
||||
soul: str = "{}",
|
||||
profile: str = "{}",
|
||||
heartbeat: str = "{}",
|
||||
memory: str = "{}",
|
||||
enabled: bool = True,
|
||||
sort_order: int = 0,
|
||||
) -> dict:
|
||||
now = utcnow_iso()
|
||||
a = OfficialAgent(
|
||||
id=agent_id,
|
||||
name=name.strip(),
|
||||
description=description,
|
||||
language=language,
|
||||
model_name=model_name,
|
||||
template_type=template_type,
|
||||
deletable=deletable,
|
||||
use_fixed_soul=use_fixed_soul,
|
||||
scope=scope,
|
||||
soul=soul,
|
||||
profile=profile,
|
||||
heartbeat=heartbeat,
|
||||
memory=memory,
|
||||
enabled=enabled,
|
||||
sort_order=sort_order,
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
self.session.add(a)
|
||||
await self.session.commit()
|
||||
return self._to_dict(a)
|
||||
|
||||
async def update(self, agent_id: str, fields: dict) -> dict | None:
|
||||
a = await self.session.get(OfficialAgent, agent_id)
|
||||
if a is None:
|
||||
return None
|
||||
allowed = {
|
||||
k: fields[k]
|
||||
for k in fields
|
||||
if k in (
|
||||
"name", "description", "language", "model_name", "template_type",
|
||||
"deletable", "use_fixed_soul", "scope",
|
||||
"soul", "profile", "heartbeat", "memory",
|
||||
"enabled", "sort_order",
|
||||
)
|
||||
}
|
||||
if not allowed:
|
||||
return None
|
||||
for k, v in allowed.items():
|
||||
setattr(a, k, v)
|
||||
a.updated_at = utcnow_iso()
|
||||
await self.session.commit()
|
||||
return self._to_dict(a)
|
||||
|
||||
async def delete(self, agent_id: str) -> bool:
|
||||
a = await self.session.get(OfficialAgent, agent_id)
|
||||
if a is None:
|
||||
return False
|
||||
await self.session.delete(a)
|
||||
await self.session.commit()
|
||||
return True
|
||||
|
||||
@@ -2497,6 +2606,7 @@ class Database:
|
||||
self.users = UserRepository(self.session)
|
||||
self.tokens = TokenRepository(self.session)
|
||||
self.agents = AgentRepository(self.session)
|
||||
self.official_agents = OfficialAgentRepository(self.session)
|
||||
self.roles = RoleRepository(self.session)
|
||||
self.orgs = OrgRepository(self.session)
|
||||
self.regions = RegionRepository(self.session)
|
||||
|
||||
@@ -21,11 +21,13 @@ from app.api.routers import rbac_admin as rbac_admin_router
|
||||
from app.api.routers import rbac_hall as rbac_hall_router
|
||||
from app.api.routers import rbac_opc as rbac_opc_router
|
||||
from app.api.routers import rbac_operator as rbac_operator_router
|
||||
from app.api.routers import rbac_agents as rbac_agents_router
|
||||
from app.api.routers import rbac_public as rbac_public_router
|
||||
from app.api.routers import rbac_org as rbac_org_router
|
||||
from app.api.routers import rbac_portals as rbac_portals_router
|
||||
from app.api.routers import rbac_certifications as rbac_certifications_router
|
||||
from app.api.routers import rbac_training as rbac_training_router
|
||||
from app.api.routers import rbac_ecosystem as rbac_ecosystem_router
|
||||
from app.api.routers import templates as templates_router
|
||||
from app.api.routers import relay as relay_router
|
||||
from app.pay import routers as pay_router
|
||||
@@ -86,12 +88,14 @@ app.include_router(bootstrap_router.router)
|
||||
app.include_router(rbac_admin_router.router)
|
||||
app.include_router(rbac_opc_router.router)
|
||||
app.include_router(rbac_operator_router.router)
|
||||
app.include_router(rbac_agents_router.router)
|
||||
app.include_router(rbac_public_router.router)
|
||||
app.include_router(rbac_org_router.router)
|
||||
app.include_router(rbac_portals_router.router)
|
||||
app.include_router(rbac_hall_router.router)
|
||||
app.include_router(rbac_certifications_router.router)
|
||||
app.include_router(rbac_training_router.router)
|
||||
app.include_router(rbac_ecosystem_router.router)
|
||||
app.include_router(rbac_hall_router.community_router)
|
||||
app.include_router(rbac_hall_router.dm_router)
|
||||
app.include_router(relay_router.router)
|
||||
|
||||
Reference in New Issue
Block a user