按 slug 解析项目 API
将 team slug + project slug 翻译为 project UUID。仅供前端路由 guard 使用: 当 URL /o/<team-slug>/p/<project-slug>/... 进入时,前端会先调本接口拿到 UUID,再 router.replace 把地址规范化为 UUID 形态,避免下游代码把 slug 直接喂给 Supabase 触发 PostgREST 400。
端点
GET /projects/by-slug?team=<teamSlug>&slug=<projectSlug>认证
仅支持 JWT Bearer Token(与 teams 路由一致)。
Authorization: Bearer <jwt>
X-Server-Mode: cloud | selfhosted不接受 API Key / agent token——这是 UI 专属接口,agent 调用方应直接持有 UUID。
请求参数
| 参数 | 位置 | 必填 | 说明 |
|---|---|---|---|
team | query | 是 | 团队 slug,允许小写字母/数字/中文片段,连字符分隔,长度 1~48 |
slug | query | 是 | 项目 slug,必须匹配 /^[a-z][a-z0-9-]{1,63}$/,与 DB projects_slug_format 约束一致 |
响应
200 成功
json
{
"success": true,
"data": {
"id": "uuid",
"slug": "my-project",
"name": "我的项目",
"team_id": "uuid",
"access": "private"
}
}仅返回 5 字段白名单。不暴露 owner_user_id / config / description 等。
200 未找到 / 无权限
json
{ "success": true, "data": null }为避免泄漏项目存在性,下列情况均返回 data: null(不返回 404):
- 团队 slug 不存在
- 当前用户不是该团队成员
- 项目 slug 不存在或已软删除
调用方需自行处理 data === null 的情况(前端 guard 会 redirect 到 dashboard)。
400 参数非法
json
{ "error": "invalid_request" }team 或 slug 缺失、为空、格式不符。
401 未认证
json
{ "error": "unauthorized" }500 服务端错误
json
{ "error": "internal_error" }日志带 prefix [projects/by-slug],不记录 slug / team 原文(脱敏)。
行为细节
- team 鉴权复用
apps/backend/src/services/team-membership.ts中的checkTeamMember,与teams/by-slug行为一致 - project 查询使用 service-role 客户端(绕过 RLS),但仅在通过 team membership 校验后才查询;查询条件
(team_id, slug) WHERE deleted_at IS NULL对应唯一索引idx_projects_slug_team - 错误分支保证幂等:网络层失败返 500,业务层无果返
data: null
源码
- 路由:
apps/backend/src/routes/projects-by-slug.ts - slug 校验:
apps/backend/src/utils/slug-validation.ts - team membership:
apps/backend/src/services/team-membership.ts
调用方
- 前端路由 guard:
apps/frontend/src/router/index.ts全局beforeEach中识别非 UUID 形态的:projectId路由参数,调用本接口翻译 - 前端 service:
projectService.getProjectBySlug(teamSlug, projectSlug)