mirror of
https://github.com/iflytek/skillhub.git
synced 2026-08-27 11:14:59 +00:00
fix: change api/v1/cli to api/v1
This commit is contained in:
parent
57c4f77d25
commit
bf62b340ab
14 changed files with 42 additions and 42 deletions
|
|
@ -233,7 +233,7 @@ public class SecurityConfig {
|
|||
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
|
||||
.csrf(csrf -> csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.ignoringRequestMatchers("/api/v1/cli/**"))
|
||||
.ignoringRequestMatchers("/api/v1/**"))
|
||||
// ...
|
||||
;
|
||||
}
|
||||
|
|
@ -458,7 +458,7 @@ Session 中存储以下字段:
|
|||
- 后端设置 `XSRF-TOKEN` Cookie(`HttpOnly=false`)
|
||||
- 前端从 Cookie 读取 Token,放入请求 Header `X-XSRF-TOKEN`
|
||||
- 后端校验 Header 与 Cookie 是否一致
|
||||
- CLI API(`/api/v1/cli/**`)与兼容层(`/api/compat/v1/**`)豁免 CSRF(使用 Bearer Token,无 Cookie)
|
||||
- CLI API(`/api/v1/**`)与兼容层(`/api/compat/v1/**`)豁免 CSRF(使用 Bearer Token,无 Cookie)
|
||||
|
||||
## 9. 前端权限控制
|
||||
|
||||
|
|
@ -609,8 +609,8 @@ window.location.href = '/oauth2/authorization/github'
|
|||
|
||||
| 接口 | 所需凭证 | 额外判定 |
|
||||
|------|---------|---------|
|
||||
| `GET /api/v1/cli/whoami` | 任意有效 Bearer Token | 无 |
|
||||
| `POST /api/v1/cli/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上 |
|
||||
| `GET /api/v1/whoami` | 任意有效 Bearer Token | 无 |
|
||||
| `POST /api/v1/publish` | Bearer Token + `skill:publish` | 用户是目标 namespace 的 MEMBER 以上 |
|
||||
|
||||
### 10.4 Admin API
|
||||
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Phase 3 在不改变发布入口的前提下,把后半段切换为“创建 DR
|
|||
- 审核通过后才转为 `PUBLISHED`
|
||||
- 审核拒绝后转为 `REJECTED`
|
||||
- 例外:提交人持有 `SUPER_ADMIN` 平台角色时,发布入口直接创建 `skill_version(status=PUBLISHED)`,跳过 `review_task` 创建,同时不再要求其必须是目标 namespace 成员
|
||||
- 上述例外必须对 Web、`/api/v1/cli/publish`、`/api/compat/v1/publish` 保持一致
|
||||
- 上述例外必须对 Web、`/api/v1/publish`、`/api/compat/v1/publish` 保持一致
|
||||
|
||||
### 对象存储写入策略
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ Phase 3 在不改变发布入口的前提下,把后半段切换为“创建 DR
|
|||
### CLI publish 请求规范
|
||||
|
||||
```
|
||||
POST /api/v1/cli/publish
|
||||
POST /api/v1/publish
|
||||
Content-Type: multipart/form-data
|
||||
Parts:
|
||||
- file: zip 包(必需)
|
||||
|
|
@ -81,7 +81,7 @@ Phase 3 CLI 默认行为:上传 → 创建 DRAFT → 自动提交审核。
|
|||
如果调用方持有 `SUPER_ADMIN`,则直接发布为 `PUBLISHED`。
|
||||
Web 端可保留“发布后再提交审核”的两段式体验,但这属于 Phase 3 能力。
|
||||
|
||||
`/api/v1/cli/publish` 响应:
|
||||
`/api/v1/publish` 响应:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ Public API 的可见性规则:
|
|||
发布状态约束:
|
||||
|
||||
- 普通用户发布成功后,`status` 为 `PENDING_REVIEW`
|
||||
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/cli/publish`、`/api/compat/v1/publish` 发布时,`status` 为 `PUBLISHED`,且不要求其必须是目标 namespace 成员
|
||||
- 持有 `SUPER_ADMIN` 的用户通过 Web、`/api/v1/publish`、`/api/compat/v1/publish` 发布时,`status` 为 `PUBLISHED`,且不要求其必须是目标 namespace 成员
|
||||
|
||||
## 7.4 Token API(需登录)
|
||||
|
||||
|
|
@ -253,10 +253,10 @@ Public API 的可见性规则:
|
|||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/v1/cli/whoami` | 当前 Bearer Token 对应的用户信息 |
|
||||
| POST | `/api/v1/cli/publish` | 发布技能包(Phase 2 直接返回 `PUBLISHED`,Phase 3 恢复审核流;`SUPER_ADMIN` 始终直发) |
|
||||
| GET | `/api/v1/cli/resolve/{namespace}/{slug}` | 解析版本 |
|
||||
| GET | `/api/v1/cli/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 |
|
||||
| GET | `/api/v1/whoami` | 当前 Bearer Token 对应的用户信息 |
|
||||
| POST | `/api/v1/publish` | 发布技能包(Phase 2 直接返回 `PUBLISHED`,Phase 3 恢复审核流;`SUPER_ADMIN` 始终直发) |
|
||||
| GET | `/api/v1/resolve/{namespace}/{slug}` | 解析版本 |
|
||||
| GET | `/api/v1/check/{namespace}/{slug}/{version}` | 本地哈希与远端比对 |
|
||||
|
||||
### ClawHub CLI 协议兼容层
|
||||
|
||||
|
|
@ -266,7 +266,7 @@ Public API 的可见性规则:
|
|||
- 范围:一期聚焦覆盖 ClawHub CLI 所依赖的核心接口:查询、版本解析、下载、发布、whoami
|
||||
- 要求:兼容层优先保持 ClawHub CLI 既有请求/响应语义;若内部领域模型不同,通过 adapter 层完成协议转换,而不是要求客户端适配 skillhub 私有协议
|
||||
- 要求:兼容层纳入 OpenAPI 或独立兼容协议文档,并作为正式对外契约维护
|
||||
- 要求:兼容层与 skillhub 自有 `/api/v1/cli/**` 并存,二者共享同一套权限、审计、限流与领域服务
|
||||
- 要求:兼容层与 skillhub 自有 `/api/v1/**` 并存,二者共享同一套权限、审计、限流与领域服务
|
||||
- 非目标:前端页面不直接依赖兼容层;兼容层用于服务已有 ClawHub CLI 和相关自动化脚本
|
||||
|
||||
兼容层最少需要覆盖的能力类别:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/cli")
|
||||
@RequestMapping("/api/v1")
|
||||
public class CliController extends BaseApiController {
|
||||
|
||||
private final SkillPackageValidator skillPackageValidator;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
|||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/cli/auth/device")
|
||||
@RequestMapping("/api/v1/auth/device")
|
||||
public class DeviceAuthController extends BaseApiController {
|
||||
|
||||
private final DeviceAuthService deviceAuthService;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import java.io.IOException;
|
|||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/cli")
|
||||
@RequestMapping("/api/v1")
|
||||
public class CliPublishController extends BaseApiController {
|
||||
|
||||
private final SkillPublishService skillPublishService;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class CliControllerTest {
|
|||
|
||||
@Test
|
||||
void whoamiShouldReturnUnauthorizedForAnonymousRequest() throws Exception {
|
||||
mockMvc.perform(get("/api/v1/cli/whoami"))
|
||||
mockMvc.perform(get("/api/v1/whoami"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
}
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ class CliControllerTest {
|
|||
List.of(new SimpleGrantedAuthority("ROLE_SKILL_ADMIN"))
|
||||
);
|
||||
|
||||
mockMvc.perform(get("/api/v1/cli/whoami").with(authentication(auth)))
|
||||
mockMvc.perform(get("/api/v1/whoami").with(authentication(auth)))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.msg").isNotEmpty())
|
||||
|
|
@ -88,7 +88,7 @@ class CliControllerTest {
|
|||
zipBytes
|
||||
);
|
||||
|
||||
mockMvc.perform(multipart("/api/v1/cli/check").file(file))
|
||||
mockMvc.perform(multipart("/api/v1/check").file(file))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.valid").value(true))
|
||||
|
|
@ -107,7 +107,7 @@ class CliControllerTest {
|
|||
zipBytes
|
||||
);
|
||||
|
||||
mockMvc.perform(multipart("/api/v1/cli/check").file(file))
|
||||
mockMvc.perform(multipart("/api/v1/check").file(file))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.valid").value(false))
|
||||
|
|
@ -125,7 +125,7 @@ class CliControllerTest {
|
|||
zipBytes
|
||||
);
|
||||
|
||||
mockMvc.perform(multipart("/api/v1/cli/check").file(file))
|
||||
mockMvc.perform(multipart("/api/v1/check").file(file))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.valid").value(false))
|
||||
|
|
@ -142,7 +142,7 @@ class CliControllerTest {
|
|||
zipBytes
|
||||
);
|
||||
|
||||
mockMvc.perform(multipart("/api/v1/cli/check").file(file))
|
||||
mockMvc.perform(multipart("/api/v1/check").file(file))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
.andExpect(jsonPath("$.data.valid").value(false))
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class DeviceAuthControllerTest {
|
|||
|
||||
given(deviceAuthService.generateDeviceCode()).willReturn(response);
|
||||
|
||||
mockMvc.perform(post("/api/v1/cli/auth/device/code")
|
||||
mockMvc.perform(post("/api/v1/auth/device/code")
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.code").value(0))
|
||||
|
|
@ -61,7 +61,7 @@ class DeviceAuthControllerTest {
|
|||
|
||||
given(deviceAuthService.pollToken("device_abc123")).willReturn(response);
|
||||
|
||||
mockMvc.perform(post("/api/v1/cli/auth/device/token")
|
||||
mockMvc.perform(post("/api/v1/auth/device/token")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"deviceCode\": \"device_abc123\"}"))
|
||||
.andExpect(status().isOk())
|
||||
|
|
@ -77,7 +77,7 @@ class DeviceAuthControllerTest {
|
|||
|
||||
given(deviceAuthService.pollToken("device_abc123")).willReturn(response);
|
||||
|
||||
mockMvc.perform(post("/api/v1/cli/auth/device/token")
|
||||
mockMvc.perform(post("/api/v1/auth/device/token")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content("{\"deviceCode\": \"device_abc123\"}"))
|
||||
.andExpect(status().isOk())
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class SecurityConfig {
|
|||
.csrf(csrf -> csrf
|
||||
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
|
||||
.csrfTokenRequestHandler(csrfHandler)
|
||||
.ignoringRequestMatchers("/api/v1/cli/**", "/api/compat/**")
|
||||
.ignoringRequestMatchers("/api/v1/**", "/api/compat/**")
|
||||
)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers(
|
||||
|
|
@ -81,8 +81,8 @@ public class SecurityConfig {
|
|||
"/api/v1/auth/session/bootstrap",
|
||||
"/api/v1/auth/direct/login",
|
||||
"/api/v1/auth/local/**",
|
||||
"/api/v1/cli/auth/device/**",
|
||||
"/api/v1/cli/check",
|
||||
"/api/v1/auth/device/**",
|
||||
"/api/v1/check",
|
||||
"/actuator/health",
|
||||
"/actuator/prometheus",
|
||||
"/v3/api-docs/**",
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ public class ApiTokenScopeService {
|
|||
ScopeRule.allow(null, "/api/v1/health"),
|
||||
ScopeRule.allow(null, "/api/v1/auth/providers"),
|
||||
ScopeRule.allow(null, "/api/v1/auth/me"),
|
||||
ScopeRule.allow(null, "/api/v1/cli/auth/device/**"),
|
||||
ScopeRule.allow(null, "/api/v1/cli/check"),
|
||||
ScopeRule.allow("GET", "/api/v1/cli/whoami"),
|
||||
ScopeRule.allow(null, "/api/v1/auth/device/**"),
|
||||
ScopeRule.allow(null, "/api/v1/check"),
|
||||
ScopeRule.allow("GET", "/api/v1/whoami"),
|
||||
ScopeRule.allow("GET", "/api/v1/skills"),
|
||||
ScopeRule.allow("GET", "/api/v1/skills/**"),
|
||||
ScopeRule.allow("GET", "/api/v1/namespaces"),
|
||||
|
|
@ -39,7 +39,7 @@ public class ApiTokenScopeService {
|
|||
ScopeRule.require(null, "/api/v1/tokens", "token:manage"),
|
||||
ScopeRule.require(null, "/api/v1/tokens/**", "token:manage"),
|
||||
ScopeRule.require("POST", "/api/v1/skills/*/publish", "skill:publish"),
|
||||
ScopeRule.require("POST", "/api/v1/cli/publish", "skill:publish"),
|
||||
ScopeRule.require("POST", "/api/v1/publish", "skill:publish"),
|
||||
ScopeRule.require("POST", "/api/compat/v1/publish", "skill:publish")
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class ApiTokenAuthenticationFilterTest {
|
|||
when(role.getCode()).thenReturn("SKILL_ADMIN");
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI("/api/v1/cli/whoami");
|
||||
request.setRequestURI("/api/v1/whoami");
|
||||
request.addHeader("Authorization", "Bearer raw-token");
|
||||
|
||||
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||
|
|
@ -84,7 +84,7 @@ class ApiTokenAuthenticationFilterTest {
|
|||
when(userAccountRepository.findById("user-2")).thenReturn(Optional.of(user));
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setRequestURI("/api/v1/cli/publish");
|
||||
request.setRequestURI("/api/v1/publish");
|
||||
request.addHeader("Authorization", "Bearer raw-token");
|
||||
|
||||
filter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class ApiTokenScopeFilterTest {
|
|||
);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/cli/publish");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/api/v1/publish");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain chain = mock(FilterChain.class);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ApiTokenScopeServiceTest {
|
|||
void authorizeShouldAllowCliWhoamiWithoutScope() {
|
||||
ApiTokenScopeService.AuthorizationDecision decision = scopeService.authorize(
|
||||
"GET",
|
||||
"/api/v1/cli/whoami",
|
||||
"/api/v1/whoami",
|
||||
Set.of()
|
||||
);
|
||||
|
||||
|
|
|
|||
12
web/src/api/generated/schema.d.ts
vendored
12
web/src/api/generated/schema.d.ts
vendored
|
|
@ -308,7 +308,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/publish": {
|
||||
"/api/v1/publish": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
@ -324,7 +324,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/check": {
|
||||
"/api/v1/check": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
@ -340,7 +340,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/auth/device/token": {
|
||||
"/api/v1/auth/device/token": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
@ -356,7 +356,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/auth/device/code": {
|
||||
"/api/v1/auth/device/code": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
@ -916,7 +916,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/whoami": {
|
||||
"/api/v1/whoami": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
@ -932,7 +932,7 @@ export interface paths {
|
|||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/cli/resolve/{namespace}/{slug}": {
|
||||
"/api/v1/resolve/{namespace}/{slug}": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue