Files
server-core/app/models.py
T
Pine 26fa546f67 feat: 核心服务端基础框架(身份/平台 API)
- 七端口 RBAC、select-identity、JWT、审计
- FastAPI + SQLAlchemy + SQLite,/auth /opc /admin /agents 等路由
2026-08-23 22:35:59 +08:00

744 lines
30 KiB
Python

# -*- coding: utf-8 -*-
"""SQLAlchemy ORM 模型(auth/RBAC 域)+ Pydantic 请求/响应模型。
- ORM 模型对应 SQLite 各表(users/roles/permissions/organizations/regions/
sessions/audit_logs/agents)。
- Pydantic 模型与 PineAgents 主后端 ``src/pineagents/app/routers/auth.py``
中的请求/响应模型一一对应,转发层透传时不会丢字段。
"""
from __future__ import annotations
from pydantic import BaseModel, Field
from sqlalchemy import (
Boolean,
Float,
ForeignKey,
Integer,
PrimaryKeyConstraint,
String,
Text,
)
from sqlalchemy.orm import Mapped, mapped_column
from .db import Base
# ===========================================================================
# SQLAlchemy ORM 模型
# ===========================================================================
class User(Base):
__tablename__ = "users"
__table_args__ = {"sqlite_autoincrement": True}
id: Mapped[str] = mapped_column(String, primary_key=True)
username: Mapped[str] = mapped_column(String, unique=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String, nullable=False)
password_salt: Mapped[str] = mapped_column(String, nullable=False)
nickname: Mapped[str] = mapped_column(String, default="")
account: Mapped[str] = mapped_column(String, default="")
company: Mapped[str] = mapped_column(String, default="")
room: Mapped[str] = mapped_column(String, default="")
avatar: Mapped[str] = mapped_column(String, default="")
company_avatar: Mapped[str] = mapped_column(String, default="")
# RBAC
role: Mapped[str] = mapped_column(String, default="opc_member") # 业务角色
sub_role: Mapped[str] = mapped_column(String, nullable=True) # 政务级/运营方内部
org_id: Mapped[str | None] = mapped_column(ForeignKey("organizations.id"), nullable=True)
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
status: Mapped[str] = mapped_column(String, default="active") # active | disabled
token_version: Mapped[int] = mapped_column(Integer, default=0) # revoke-all 递增
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class Role(Base):
__tablename__ = "roles"
id: Mapped[str] = mapped_column(String, primary_key=True) # 角色码
name: Mapped[str] = mapped_column(String, nullable=False) # 展示名
scope_type: Mapped[str] = mapped_column(String, default="business") # business|operator-internal|government-level
description: Mapped[str] = mapped_column(String, default="")
class Permission(Base):
__tablename__ = "permissions"
id: Mapped[str] = mapped_column(String, primary_key=True) # 权限码 menu:xx / action:xx
name: Mapped[str] = mapped_column(String, nullable=False)
category: Mapped[str] = mapped_column(String, default="action") # menu | action
module: Mapped[str] = mapped_column(String, default="") # admin|government|...
description: Mapped[str] = mapped_column(String, default="")
class RolePermission(Base):
__tablename__ = "role_permissions"
role_id: Mapped[str] = mapped_column(
ForeignKey("roles.id", ondelete="CASCADE"), primary_key=True,
)
permission_id: Mapped[str] = mapped_column(
ForeignKey("permissions.id", ondelete="CASCADE"), primary_key=True,
)
class Organization(Base):
__tablename__ = "organizations"
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
type: Mapped[str] = mapped_column(String, default="enterprise") # enterprise|carrier|provider
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
parent_id: Mapped[str | None] = mapped_column(ForeignKey("organizations.id"), nullable=True)
status: Mapped[str] = mapped_column(String, default="active")
created_at: Mapped[str] = mapped_column(String, default="")
class OrganizationMember(Base):
"""机构成员(机构主账号 + 机构内子账号):一个账号可挂多个机构。"""
__tablename__ = "organization_members"
org_id: Mapped[str] = mapped_column(
ForeignKey("organizations.id", ondelete="CASCADE"), primary_key=True,
)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), primary_key=True,
)
role: Mapped[str] = mapped_column(String, default="member") # admin/finance/publisher/service_executor/service_deliverer/clerk/...
is_admin: Mapped[bool] = mapped_column(Boolean, default=False) # 机构主账号/管理员
status: Mapped[str] = mapped_column(String, default="active")
joined_at: Mapped[str] = mapped_column(String, default="")
class Region(Base):
__tablename__ = "regions"
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String, nullable=False)
level: Mapped[str] = mapped_column(String, default="district") # province|city|district
parent_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
created_at: Mapped[str] = mapped_column(String, default="")
class SessionToken(Base):
__tablename__ = "sessions"
id: Mapped[str] = mapped_column(String, primary_key=True)
jti: Mapped[str] = mapped_column(String, unique=True, nullable=False)
token: Mapped[str] = mapped_column(String, default="") # 完整 JWT(调试用)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
username: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
expires_at: Mapped[str] = mapped_column(String, default="")
revoked: Mapped[bool] = mapped_column(Boolean, default=False)
class AuditLog(Base):
__tablename__ = "audit_logs"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
action: Mapped[str] = mapped_column(String, default="") # login|role.assign|data.view|...
resource: Mapped[str] = mapped_column(String, default="") # user|role|enterprise|region|...
resource_id: Mapped[str] = mapped_column(String, default="")
detail: Mapped[str] = mapped_column(Text, default="")
ip: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class Agent(Base):
__tablename__ = "agents"
# id + user_id + port 复合主键:每个用户在每个端口持有独立的默认/QA 智能体(多端口彻底隔离)
__table_args__ = (PrimaryKeyConstraint("id", "user_id", "port"),)
id: Mapped[str] = mapped_column(String)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
port: Mapped[str] = mapped_column(String, default="opc") # opc/operator/government/... 端口隔离
name: Mapped[str] = mapped_column(String, default="")
description: Mapped[str] = mapped_column(Text, default="")
language: Mapped[str] = mapped_column(String, default="zh")
model_name: Mapped[str] = mapped_column(String, default="")
deletable: Mapped[bool] = mapped_column(Boolean, default=True)
use_fixed_soul: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
# ── 运营端业务模型(平台运营/任务/服务商/内容/配置)────────────────────────
class Task(Base):
"""平台任务(运营端可审核/下架;后续各端口接入)。"""
__tablename__ = "tasks"
id: Mapped[str] = mapped_column(String, primary_key=True)
title: Mapped[str] = mapped_column(String, default="")
category: Mapped[str] = mapped_column(String, default="") # 设计创意/技术开发/...
sub_category: Mapped[str] = mapped_column(String, default="")
description: Mapped[str] = mapped_column(Text, default="")
mode: Mapped[str] = mapped_column(String, default="grab") # grab/bid/designated/dispatch
budget_min: Mapped[int] = mapped_column(Integer, default=0)
budget_max: Mapped[int] = mapped_column(Integer, default=0)
deadline: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="draft") # draft/pending/review/published/completed/cancelled
publisher_org_id: Mapped[str | None] = mapped_column(ForeignKey("organizations.id"), nullable=True)
publisher_name: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class ServiceProvider(Base):
"""服务商(运营端审核/评级/考核)。"""
__tablename__ = "service_providers"
id: Mapped[str] = mapped_column(String, primary_key=True)
name: Mapped[str] = mapped_column(String, default="")
category: Mapped[str] = mapped_column(String, default="") # 财税/工商/法务/知识产权/云服务...
org_id: Mapped[str | None] = mapped_column(ForeignKey("organizations.id"), nullable=True)
level: Mapped[str] = mapped_column(String, default="certified") # certified/premium/gold/official
rating: Mapped[float] = mapped_column(Float, default=5.0)
order_count: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String, default="pending") # pending/active/blacklisted
contact: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class ContentItem(Base):
"""平台内容(政策/课程/资讯/公告/活动)。"""
__tablename__ = "content_items"
id: Mapped[str] = mapped_column(String, primary_key=True)
type: Mapped[str] = mapped_column(String, default="news") # policy/course/news/notice/activity
title: Mapped[str] = mapped_column(String, default="")
summary: Mapped[str] = mapped_column(String, default="")
body: Mapped[str] = mapped_column(Text, default="")
publisher_id: Mapped[str | None] = mapped_column(ForeignKey("users.id"), nullable=True)
status: Mapped[str] = mapped_column(String, default="draft") # draft/published/offline
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class SystemConfig(Base):
"""平台系统配置(键值)。"""
__tablename__ = "system_configs"
key: Mapped[str] = mapped_column(String, primary_key=True)
value: Mapped[str] = mapped_column(String, default="")
description: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class PortalDashboard(Base):
"""端口工作台聚合数据(按端口存 JSON 载荷,全部由服务端提供)。"""
__tablename__ = "portal_dashboards"
port: Mapped[str] = mapped_column(String, primary_key=True)
payload: Mapped[str] = mapped_column(Text, default="{}")
updated_at: Mapped[str] = mapped_column(String, default="")
class PortalPage(Base):
"""端口子页面数据(按 端口+页面 存 JSON 载荷,全部由服务端提供)。"""
__tablename__ = "portal_pages"
port: Mapped[str] = mapped_column(String, primary_key=True)
page: Mapped[str] = mapped_column(String, primary_key=True)
payload: Mapped[str] = mapped_column(Text, default="{}")
updated_at: Mapped[str] = mapped_column(String, default="")
# ── 投资人端(投融资 · 路演 · 培训)──────────────────────────────────────
class InvestorPreference(Base):
"""投资人投资方向偏好。"""
__tablename__ = "investor_preferences"
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), primary_key=True,
)
industries: Mapped[str] = mapped_column(String, default="[]") # JSON list
stage: Mapped[str] = mapped_column(String, default="") # seed/angel/series_a/...
amount_min: Mapped[int] = mapped_column(Integer, default=0)
amount_max: Mapped[int] = mapped_column(Integer, default=0)
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
updated_at: Mapped[str] = mapped_column(String, default="")
class Roadshow(Base):
"""投融资路演活动(线上/线下/混合),支持按发布主体与范围审核。"""
__tablename__ = "roadshows"
id: Mapped[str] = mapped_column(String, primary_key=True)
publisher_id: Mapped[str] = mapped_column(String, default="")
publisher_role: Mapped[str] = mapped_column(String, default="investor") # investor/government/operator/carrier/enterprise
title: Mapped[str] = mapped_column(String, default="")
summary: Mapped[str] = mapped_column(String, default="")
activity_type: Mapped[str] = mapped_column(String, default="online") # online/offline/hybrid
scope_type: Mapped[str] = mapped_column(String, default="all") # all/region/org
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
start_at: Mapped[str] = mapped_column(String, default="")
end_at: Mapped[str] = mapped_column(String, default="")
register_deadline: Mapped[str] = mapped_column(String, default="")
quota: Mapped[int] = mapped_column(Integer, default=0)
venue: Mapped[str] = mapped_column(String, default="") # 线下地点/直播间
live_url: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="draft") # draft/submitted/reviewing/published/registering/ongoing/ended/archived/rejected/cancelled
review_comment: Mapped[str] = mapped_column(String, default="")
need_review: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class RoadshowRegistration(Base):
"""路演活动报名(项目方/投资人)。"""
__tablename__ = "roadshow_registrations"
id: Mapped[str] = mapped_column(String, primary_key=True)
roadshow_id: Mapped[str] = mapped_column(
ForeignKey("roadshows.id", ondelete="CASCADE"), nullable=False,
)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
role: Mapped[str] = mapped_column(String, default="investor") # investor/project
status: Mapped[str] = mapped_column(String, default="applying") # applying/approved/attended/absent
note: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class InvestmentIntent(Base):
"""投资意向/约谈/尽调。"""
__tablename__ = "investment_intents"
id: Mapped[str] = mapped_column(String, primary_key=True)
investor_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
project_id: Mapped[str] = mapped_column(String, default="")
project_name: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="interested") # interested/contacted/meeting/term_sheet/invested/declined
message: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class Bid(Base):
"""任务竞标(企业端竞标模式发标)。"""
__tablename__ = "bids"
id: Mapped[str] = mapped_column(String, primary_key=True)
task_id: Mapped[str] = mapped_column(
ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False,
)
opc_id: Mapped[str] = mapped_column(String, default="")
opc_name: Mapped[str] = mapped_column(String, default="")
quote: Mapped[int] = mapped_column(Integer, default=0)
plan: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="submitted") # submitted/win/lost
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class SubsidyApplication(Base):
"""补贴申报(政务三级审批:区县→市→省→发放)。"""
__tablename__ = "subsidy_applications"
id: Mapped[str] = mapped_column(String, primary_key=True)
opc_id: Mapped[str] = mapped_column(String, default="")
opc_name: Mapped[str] = mapped_column(String, default="")
title: Mapped[str] = mapped_column(String, default="")
amount: Mapped[int] = mapped_column(Integer, default=0)
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
status: Mapped[str] = mapped_column(String, default="applying") # applying/district_review/city_review/province_review/approved/paid/rejected
comment: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class ServiceReferral(Base):
"""载体-服务商引荐(三方绑定:载体引荐服务商→服务园区内 OPC)。"""
__tablename__ = "service_referrals"
id: Mapped[str] = mapped_column(String, primary_key=True)
carrier_id: Mapped[str] = mapped_column(String, default="")
provider_id: Mapped[str] = mapped_column(String, default="")
provider_name: Mapped[str] = mapped_column(String, default="")
opc_id: Mapped[str] = mapped_column(String, default="")
opc_name: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="referred") # referred/accepted/completed/declined
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class TrainingEnrollment(Base):
"""投融资培训报名/证书。"""
__tablename__ = "training_enrollments"
id: Mapped[str] = mapped_column(String, primary_key=True)
training_id: Mapped[str] = mapped_column(String, default="")
training_name: Mapped[str] = mapped_column(String, default="")
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
status: Mapped[str] = mapped_column(String, default="applying") # applying/enrolled/completed/certificate
created_at: Mapped[str] = mapped_column(String, default="")
# ── 横切能力:结算/合同/争议/评价/通知 ────────────────────────────────────
class Escrow(Base):
"""任务资金托管/结算。"""
__tablename__ = "escrows"
id: Mapped[str] = mapped_column(String, primary_key=True)
task_id: Mapped[str] = mapped_column(
ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False,
)
task_title: Mapped[str] = mapped_column(String, default="")
amount: Mapped[int] = mapped_column(Integer, default=0)
commission: Mapped[int] = mapped_column(Integer, default=0) # 平台佣金
status: Mapped[str] = mapped_column(String, default="deposited") # deposited/frozen/released/refunded
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class Contract(Base):
"""任务电子合同。"""
__tablename__ = "contracts"
id: Mapped[str] = mapped_column(String, primary_key=True)
task_id: Mapped[str] = mapped_column(
ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False,
)
task_title: Mapped[str] = mapped_column(String, default="")
enterprise_id: Mapped[str] = mapped_column(String, default="")
opc_id: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="signed") # draft/signed/terminated
content: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class Dispute(Base):
"""任务争议。"""
__tablename__ = "disputes"
id: Mapped[str] = mapped_column(String, primary_key=True)
task_id: Mapped[str] = mapped_column(
ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False,
)
task_title: Mapped[str] = mapped_column(String, default="")
initiator: Mapped[str] = mapped_column(String, default="")
reason: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="opened") # opened/mediating/resolved/closed
resolution: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class Rating(Base):
"""任务互评。"""
__tablename__ = "ratings"
id: Mapped[str] = mapped_column(String, primary_key=True)
task_id: Mapped[str] = mapped_column(
ForeignKey("tasks.id", ondelete="CASCADE"), nullable=False,
)
from_id: Mapped[str] = mapped_column(String, default="")
to_id: Mapped[str] = mapped_column(String, default="")
score: Mapped[int] = mapped_column(Integer, default=5) # 1..5
comment: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class Notification(Base):
"""站内通知。"""
__tablename__ = "notifications"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
type: Mapped[str] = mapped_column(String, default="system") # task/policy/order/subsidy/system
title: Mapped[str] = mapped_column(String, default="")
content: Mapped[str] = mapped_column(String, default="")
read: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[str] = mapped_column(String, default="")
# ── OPC 超级个体业务模型(工作台/我的任务/财务/消息)────────────────────
class OpcProfile(Base):
"""OPC 超级个体档案(信用评分等)。"""
__tablename__ = "opc_profiles"
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), primary_key=True,
)
credit_score: Mapped[int] = mapped_column(Integer, default=80)
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class FinanceRecord(Base):
"""OPC 财务流水(收入/支出)。"""
__tablename__ = "finance_records"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
category: Mapped[str] = mapped_column(String, default="income") # income | expense
amount: Mapped[int] = mapped_column(Integer, default=0)
date: Mapped[str] = mapped_column(String, default="") # YYYY-MM-DD
note: Mapped[str] = mapped_column(String, default="")
created_at: Mapped[str] = mapped_column(String, default="")
class Message(Base):
"""OPC 站内消息。"""
__tablename__ = "messages"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
msg_type: Mapped[str] = mapped_column(String, default="system") # task/policy/service/agent/system
title: Mapped[str] = mapped_column(String, default="")
content: Mapped[str] = mapped_column(String, default="")
read: Mapped[bool] = mapped_column(Boolean, default=False)
created_at: Mapped[str] = mapped_column(String, default="")
class OpcTask(Base):
"""OPC 个人任务看板(「我的任务」:进行中/已完成/紧急)。"""
__tablename__ = "opc_tasks"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
title: Mapped[str] = mapped_column(String, default="")
status: Mapped[str] = mapped_column(String, default="in_progress") # in_progress/completed/urgent
budget: Mapped[int] = mapped_column(Integer, default=0)
deadline: Mapped[str] = mapped_column(String, default="")
progress: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
class UserIdentity(Base):
"""账号↔端口绑定身份:一个账号可绑定多个端口/身份。
身份即权限来源:``port`` 为业务端口(operator/government/provider/
enterprise/carrier/opc),``role``=端口角色码(默认等于 port,供
require_roles 匹配),``sub_role`` 为端口内细分角色(如 op_super_admin、
gov_province、企业/园区/服务商的 admin/operator、OPC 的 certified/
independent 等)。一个账号对应多行,登录后按所选身份签发令牌。
"""
__tablename__ = "user_identities"
id: Mapped[str] = mapped_column(String, primary_key=True)
user_id: Mapped[str] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), nullable=False,
)
port: Mapped[str] = mapped_column(String, default="opc") # operator/government/provider/enterprise/carrier/opc
role: Mapped[str] = mapped_column(String, default="opc_member") # RBAC 角色码
sub_role: Mapped[str | None] = mapped_column(String, nullable=True) # 端口内细分角色
name: Mapped[str] = mapped_column(String, default="") # 身份展示名
org_id: Mapped[str | None] = mapped_column(ForeignKey("organizations.id"), nullable=True)
region_id: Mapped[str | None] = mapped_column(ForeignKey("regions.id"), nullable=True)
status: Mapped[str] = mapped_column(String, default="active") # active | disabled
created_at: Mapped[str] = mapped_column(String, default="")
updated_at: Mapped[str] = mapped_column(String, default="")
# ===========================================================================
# Pydantic 请求/响应模型
# ===========================================================================
class LoginRequest(BaseModel):
username: str
password: str
expires_in: int | None = Field(
default=None,
description="令牌有效期(秒),-1/0 表示永久,省略为 7 天",
)
class RegisterRequest(BaseModel):
username: str
password: str
expires_in: int | None = Field(
default=None,
description="令牌有效期(秒),-1/0 表示永久,省略为 7 天",
)
class UpdateProfileRequest(BaseModel):
"""更新资料/凭据。``current_password`` 仅在修改用户名/密码时校验。"""
current_password: str = ""
new_username: str | None = None
new_password: str | None = None
expires_in: int | None = None
# 演示用户资料字段(账户管理页可编辑)
nickname: str | None = None
account: str | None = None
company: str | None = None
room: str | None = None
avatar: str | None = None
company_avatar: str | None = None
class RevokeTokenRequest(BaseModel):
token: str | None = Field(
default=None,
description="要吊销的令牌;省略则吊销当前令牌",
)
class AgentCreate(BaseModel):
"""创建智能体身份。"""
name: str
description: str = ""
language: str = "zh"
model_name: str = ""
class AgentUpdate(BaseModel):
"""更新智能体身份字段(可空字段,仅提交需要变更的项)。"""
name: str | None = None
description: str | None = None
language: str | None = None
model_name: str | None = None
use_fixed_soul: bool | None = None
class AgentInfo(BaseModel):
"""智能体身份记录(按用户隔离)。
- ``deletable``:是否允许删除(默认智能体由服务端保护为不可删)。
- ``use_fixed_soul``:该智能体是否**必须**使用服务端托管、最高优先级的
``fixed_soul`` 不可变段(否则忽略)。
"""
id: str
user_id: str
port: str = "opc"
name: str
description: str = ""
language: str = "zh"
model_name: str = ""
created_at: str = ""
updated_at: str = ""
deletable: bool = True
use_fixed_soul: bool = False
class AuthStatusResponse(BaseModel):
enabled: bool
has_users: bool
class VerifyResponse(BaseModel):
valid: bool
username: str = ""
class UserProfile(BaseModel):
"""用户资料(登录/me/update-profile 均返回该结构)。"""
username: str = ""
nickname: str = ""
account: str = ""
company: str = ""
room: str = ""
avatar: str = ""
company_avatar: str = ""
# RBAC(登录/me 返回;update-profile 不重签时不返回权限明细)
role: str = "opc_member"
sub_role: str | None = None
org_id: str | None = None
region_id: str | None = None
permissions: list[str] = Field(default_factory=list)
scope_region_ids: list[str] = Field(default_factory=list)
# 当前生效的端口身份(多身份绑定)
identity_id: str | None = None
port: str | None = None
identity_name: str = ""
class LoginResponse(UserProfile):
"""登录/注册成功后的响应:资料 + 新令牌。"""
token: str = ""
identities: list[IdentityInfo] = Field(default_factory=list)
class ProfileResponse(UserProfile):
"""update-profile 成功后的响应;修改凭据时附带新令牌。"""
token: str = ""
identities: list[IdentityInfo] = Field(default_factory=list)
class IdentityInfo(BaseModel):
"""账号绑定的一端口身份摘要(登录/选择身份时返回)。"""
id: str
port: str = "opc"
role: str = "opc_member"
sub_role: str | None = None
name: str = ""
org_id: str | None = None
region_id: str | None = None
org_name: str | None = None
region_name: str | None = None
status: str = "active"
class SelectIdentityRequest(BaseModel):
"""切换到指定端口身份:为新身份签发令牌。"""
identity_id: str
# RBAC 管理请求/响应
class SetUserRoleRequest(BaseModel):
role: str
sub_role: str | None = None
org_id: str | None = None
region_id: str | None = None
class SetUserStatusRequest(BaseModel):
status: str = "active" # active | disabled