Skip to content

团队管理 API

团队管理相关接口,包括邀请链接、API Key 管理和团队密钥管理。

路由前缀/teams

源码apps/backend/src/routes/teams.ts


认证

所有接口需要 JWT 认证:

Authorization: Bearer <JWT>

端点

1. 生成团队邀请链接

POST /teams/generate-invite-link

生成一个团队邀请 JWT Token,有效期 7 天。仅团队 owner 可操作。

请求体:

字段类型必填说明
teamIdstring团队 ID

响应 200 OK

json
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "expiresAt": "2026-03-14T10:00:00.000Z"
  }
}

错误码:

HTTP错误说明
400teamId is required未提供 teamId
401Unauthorized未认证
403Only team owner can generate invite links非团队 owner
404Team not found团队不存在
500Internal server error服务端错误

2. 接受团队邀请

POST /teams/accept-invite

使用邀请 Token 加入团队。如果用户已是成员(含 owner 自身),返回 alreadyMember: true(幂等)。

进入此端点会先调用 team-seats.service 计算席位状态:

  • 已是 active 成员(含 owner)→ 直接返回,不受 isFull 影响
  • 席位已满(currentSeats >= max_seats,含 owner 自愈补偿)→ 409 拒绝
  • 历史 (team_id, user_id) 行(pending/rejected/软删)走复活分支:显式 role='member' + 清 deleted_at,避免历史 admin 复活后越权

请求体:

字段类型必填说明
tokenstring邀请 JWT Token

响应 200 OK

json
{
  "success": true,
  "data": {
    "team": {
      "id": "uuid",
      "slug": "my-team",
      "name": "My Team"
    },
    "alreadyMember": false
  }
}

错误码:

HTTP错误说明
400token is required未提供 token
400Invalid or expired invite tokenToken 无效或过期
401Unauthorized未认证
404Team not found团队不存在
409team_seats_full席位已满;响应 data: { current, max },前端按 i18n 渲染
500team_plan_missing / team_query_failed / members_query_failed内部数据/查询异常(明确错误,不静默兜底)

席位强 enforcement 限制:route 层为 best-effort(read-then-write 之间存在并发窗口),高并发下两个新成员同时接受可能瞬时超额。强语义需下沉 DB RPC + 行锁(暂未实现,已留 TODO)。


2.1 预览邀请详情

POST /teams/invite-preview

只读端点,前端在 JoinTeam 视图调用,用于在用户点「加入」之前展示团队信息、邀请人、过期时间和当前席位用量。返回 server-verified 字段(避免依赖前端本地解析 JWT)。

请求体:

字段类型必填说明
tokenstring邀请 JWT Token

响应 200 OK

json
{
  "success": true,
  "data": {
    "team": { "id": "uuid", "slug": "my-team", "name": "My Team" },
    "inviter": { "name": "Alice" },
    "expiresAt": "2026-05-02T10:00:00.000Z",
    "currentSeats": 3,
    "maxSeats": 5,
    "isFull": false,
    "alreadyMember": false,
    "canAccept": true,
    "blockReason": null
  }
}
  • currentSeats 已含 owner 自愈补偿(owner 不在 team_members 时 +1)
  • canAccept = alreadyMember || !isFullalreadyMember=true 时即使 isFull 也允许进入(accept 会走幂等分支)
  • blockReason:当前仅 'team_seats_full' | null

错误码: 同 accept-invite。


3. 创建团队 API Key

POST /teams/:teamId/api-keys

为团队创建 API Key。密钥明文仅在创建时返回一次,数据库只存储哈希值。仅团队 owner 可操作。

路径参数:

参数说明
teamId团队 ID

请求体:

字段类型必填说明
namestringKey 名称
descriptionstring描述
limitCreditsnumber | null积分限额

响应 201 Created

json
{
  "success": true,
  "data": {
    "id": "uuid",
    "name": "My API Key",
    "prefix": "wn-",
    "suffix": "abc...defg",
    "apiKey": "wn-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "createdAt": "2026-03-07T10:00:00.000Z"
  },
  "message": "请立即保存此 API Key,它不会再次显示!"
}

注意apiKey 字段包含完整明文密钥,仅在此次响应中返回,请立即保存。

错误码:

HTTP错误说明
400name is required未提供 name
401Unauthorized未认证
403Only team owner can create API keys非团队 owner
404Team not found团队不存在
500Internal server error服务端错误

4. 列出团队密钥

GET /teams/:teamId/secrets

获取团队的所有密钥列表。团队成员(owner 和 member)均可查看。

路径参数:

参数说明
teamId团队 ID

响应 200 OK

json
{
  "success": true,
  "data": [
    {
      "id": "uuid",
      "key": "MY_SECRET_KEY",
      "description": "第三方 API 密钥",
      "created_at": "2026-03-07T10:00:00.000Z",
      "updated_at": "2026-03-07T10:00:00.000Z"
    }
  ]
}

错误码:

HTTP错误说明
401Unauthorized未认证
403Permission denied非团队成员
500Internal server error服务端错误

5. 创建团队密钥

POST /teams/:teamId/secrets

创建团队密钥。仅团队 owner 可操作。

路径参数:

参数说明
teamId团队 ID

请求体:

字段类型必填说明
keystring密钥名(大写字母开头,仅大写字母/数字/下划线,最长 64 字符)
valuestring密钥值
descriptionstring描述

key 格式要求:匹配 ^[A-Z][A-Z0-9_]*$,最大 64 字符。

响应 201 Created

json
{
  "success": true,
  "data": {
    "id": "uuid",
    "key": "MY_SECRET_KEY",
    "description": "描述",
    "created_at": "2026-03-07T10:00:00.000Z"
  }
}

错误码:

HTTP错误说明
400key and value are required缺少必填字段
400key must match ...key 格式不合法
401Unauthorized未认证
403Only team owner can create secrets非团队 owner
409Key already exists in this teamkey 重复
500Internal server error服务端错误

6. 更新团队密钥

PUT /teams/:teamId/secrets/:id

更新团队密钥的值或描述。仅团队 owner 可操作。

路径参数:

参数说明
teamId团队 ID
id密钥 ID

请求体:

字段类型必填说明
valuestring新密钥值
descriptionstring新描述

valuedescription 至少提供一个。

响应 200 OK

json
{
  "success": true,
  "data": {
    "id": "uuid",
    "key": "MY_SECRET_KEY",
    "description": "新描述",
    "updated_at": "2026-03-07T12:00:00.000Z"
  }
}

错误码:

HTTP错误说明
400At least one of value or description is required未提供任何更新字段
401Unauthorized未认证
403Only team owner can update secrets非团队 owner
500Internal server error服务端错误

7. 删除团队密钥

DELETE /teams/:teamId/secrets/:id

删除团队密钥。仅团队 owner 可操作。

路径参数:

参数说明
teamId团队 ID
id密钥 ID

响应 200 OK

json
{
  "success": true
}

错误码:

HTTP错误说明
401Unauthorized未认证
403Only team owner can delete secrets非团队 owner
500Internal server error服务端错误

团队成员管理

三档角色语义:

  • owner:以 teams.owner_user_id 为权威来源;owner 同时存在于 team_members 表(由 teams_auto_add_owner_trg trigger 保证)
  • adminteam_members.role = 'admin',可帮 owner 管理 member;不能互相管理(由 admin_cannot_manage_non_member 分支保护)
  • memberteam_members.role = 'member',默认新成员

8. 列出团队成员

GET /teams/:teamId/members

返回团队内所有 status='accepted' 且未软删的成员。调用方必须是团队成员。

响应 200 OK

json
{
  "success": true,
  "data": [
    {
      "user_id": "uuid",
      "role": "member",
      "is_owner": false,
      "display_name": "KongKang",
      "avatar_url": null,
      "created_at": "2026-04-21T10:00:00.000Z"
    }
  ],
  "can_manage": true
}
  • is_ownerteams.owner_user_id 为权威来源(非 role 推导)
  • 若 owner 因历史脏数据不在 team_members 中,会补一行虚拟记录(后端 warn 日志),保证 UI 不丢 owner
  • can_manage 表示调用方是 owner 或 admin

9. 修改成员角色

PATCH /teams/:teamId/members/:userId/role

切换成员 member ↔ admin仅 owner 可调

请求体: { "role": "member" | "admin" }

错误码(节选): not_owner (403), cannot_change_owner_role (400), cannot_change_self_role (400), target_not_member (404), invalid_role (400)

10. 查询成员的项目权限足迹

GET /teams/:teamId/members/:userId/project-permissions

列出该成员在团队所有项目里的显式角色和直接权限。

权限:owner / admin 可查任意 userId;member 只能查自己(userId = auth.uid()),否则 403 insufficient_privilege

响应 200 OK

json
{
  "success": true,
  "data": {
    "explicit_projects": [
      {
        "project_id": "uuid",
        "project_name": "My Project",
        "project_slug": "my-project",
        "roles": [{ "id": "uuid", "code": "project_editor", "name": "项目编辑者" }],
        "direct_permissions": [{ "code": "read" }, { "code": "write" }]
      }
    ]
  }
}

隐式团队访问(团队成员身份 → 团队共享资源)由前端自行提示,不在响应体中返回。

11. 移除团队成员

DELETE /teams/:teamId/members/:userId

从团队移除成员:单事务原子级联软删 user_roles / user_permissions / team_members。底层调用 remove_team_member RPC。

鉴权与保护(RPC 内部):

  • caller 必须是 owner 或 admin
  • admin 仅能移除 member(不能管理 admin)
  • 不能移除 owner / 不能移除自己
  • Ownership preflight:目标在本团队下不能拥有活跃 projects.owner_user_id 或活跃 documents.owner_user_id

成功响应 200 OK

json
{ "success": true, "cleanup": { "roles": 3, "permissions": 2 } }

member_owns_resources 错误 409 Conflict

json
{
  "success": false,
  "error": "member_owns_resources",
  "owned_projects": [{ "id": "uuid", "name": "...", "slug": "..." }],
  "owned_documents": [
    { "id": "uuid", "title": "...", "project_id": "uuid", "project_name": "..." }
  ]
}

其他错误码: cannot_remove_self (400), cannot_remove_owner (403), target_not_member (404), admin_cannot_manage_non_memberinsufficient_privilege (403)

12. 转让团队所有权

POST /teams/:teamId/transfer-ownership

将团队所有权从当前 owner 转让给另一位成员。底层调用 transfer_team_ownership RPC。

请求体:

json
{
  "new_owner_id": "uuid",
  "team_name_confirmation": "My Team"
}
  • team_name_confirmation 必须与 teams.name 匹配(忽略空白/大小写)作为二次确认
  • new_owner 必须是 status='accepted' 的团队成员

RPC 副作用:UPDATE teams.owner_user_id;原 owner 的 team_members 行通过 ON CONFLICT DO UPDATE 自愈为 role='admin', status='accepted', deleted_at=NULL(兼容脏数据)。

成功响应 200 OK

json
{ "success": true, "new_owner_id": "uuid" }

错误码: not_owner (403), target_is_self (400), team_name_mismatch (400), target_not_accepted_member (404)


审计

以下权限边界变更在成功路径上异步写入 audit_logs(fire-and-forget,失败仅 warn):

Action触发端点关键 details
team_member_role_changePATCH role{teamId, targetUserId, oldRole, newRole}
team_member_removeDELETE member{teamId, targetUserId, cleanup: {roles, permissions}}
team_ownership_transferPOST transfer-ownership{teamId, oldOwnerId, newOwnerId}

底层 RPC

本次新增两个 SECURITY DEFINER RPC(supabase/sql/20260421_team_member_rpcs.sql),要求调用方为 authenticated 角色(持用户 JWT),以便 auth.uid() 可用。后端路由使用 createUserClient(serverMode, rawJwt) 按请求创建 user-scoped 客户端调用,不在共享 supabaseAdmin 上做 setSession

  • remove_team_member(team_id, user_id) → jsonb
  • transfer_team_ownership(team_id, new_owner_id, team_name_confirmation) → jsonb

AI Workflow Editor