From 2b6f4e335eb65b762023e69b1e978af7f43889ad Mon Sep 17 00:00:00 2001 From: yun-zhi-ztl <15071461069@163.com> Date: Sun, 15 Mar 2026 15:48:14 +0800 Subject: [PATCH] refactor(web): move portal APIs under api/web --- .../controller/portal/MeController.java | 2 +- .../portal/NamespaceController.java | 2 +- .../portal/PromotionController.java | 2 +- .../controller/portal/ReviewController.java | 2 +- .../controller/portal/SkillController.java | 2 +- .../portal/SkillPublishController.java | 2 +- .../portal/SkillRatingController.java | 2 +- .../portal/SkillSearchController.java | 2 +- .../portal/SkillStarController.java | 2 +- .../controller/portal/SkillTagController.java | 5 +++- .../skillhub/auth/config/SecurityConfig.java | 30 ++++++++++++++++--- .../auth/token/ApiTokenScopeService.java | 7 ++++- web/src/api/client.ts | 20 +++++++------ web/src/features/social/use-rating.ts | 6 ++-- web/src/features/social/use-star.ts | 8 ++--- web/src/pages/skill-detail.tsx | 4 +-- web/src/shared/hooks/use-skill-queries.ts | 22 +++++++------- 17 files changed, 76 insertions(+), 44 deletions(-) diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/MeController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/MeController.java index 630cc91c..50f7e1cd 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/MeController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/MeController.java @@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController -@RequestMapping("/api/v1/me") +@RequestMapping({"/api/v1/me", "/api/web/me"}) public class MeController extends BaseApiController { private final MySkillAppService mySkillAppService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/NamespaceController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/NamespaceController.java index d3995006..5735db3f 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/NamespaceController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/NamespaceController.java @@ -11,7 +11,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/v1/namespaces") +@RequestMapping({"/api/v1/namespaces", "/api/web/namespaces"}) public class NamespaceController extends BaseApiController { private final NamespaceService namespaceService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/PromotionController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/PromotionController.java index 14628e63..5617f21c 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/PromotionController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/PromotionController.java @@ -41,7 +41,7 @@ import java.util.Map; import java.util.Set; @RestController -@RequestMapping("/api/v1/promotions") +@RequestMapping({"/api/v1/promotions", "/api/web/promotions"}) public class PromotionController extends BaseApiController { private final PromotionService promotionService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/ReviewController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/ReviewController.java index 61a13e40..54a9ca7d 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/ReviewController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/ReviewController.java @@ -42,7 +42,7 @@ import java.util.Map; import java.util.Set; @RestController -@RequestMapping("/api/v1/reviews") +@RequestMapping({"/api/v1/reviews", "/api/web/reviews"}) public class ReviewController extends BaseApiController { private final ReviewService reviewService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillController.java index 94f32657..bc294609 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillController.java @@ -32,7 +32,7 @@ import java.util.Map; import java.util.stream.Collectors; @RestController -@RequestMapping("/api/v1/skills") +@RequestMapping({"/api/v1/skills", "/api/web/skills"}) public class SkillController extends BaseApiController { private final SkillQueryService skillQueryService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java index b7faaeae..ced3ec4d 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.util.List; @RestController -@RequestMapping("/api/v1/skills") +@RequestMapping({"/api/v1/skills", "/api/web/skills"}) public class SkillPublishController extends BaseApiController { private final SkillPublishService skillPublishService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillRatingController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillRatingController.java index b9f1e022..88a48ca6 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillRatingController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillRatingController.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.*; import java.util.Optional; @RestController -@RequestMapping("/api/v1/skills") +@RequestMapping({"/api/v1/skills", "/api/web/skills"}) public class SkillRatingController extends BaseApiController { private final SkillRatingService skillRatingService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillSearchController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillSearchController.java index 505229ee..f338c17d 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillSearchController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillSearchController.java @@ -11,7 +11,7 @@ import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController -@RequestMapping("/api/v1/skills") +@RequestMapping({"/api/v1/skills", "/api/web/skills"}) public class SkillSearchController extends BaseApiController { private final SkillSearchAppService skillSearchAppService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillStarController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillStarController.java index 4a3e77f1..8ffb88a9 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillStarController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillStarController.java @@ -9,7 +9,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/api/v1/skills") +@RequestMapping({"/api/v1/skills", "/api/web/skills"}) public class SkillStarController extends BaseApiController { private final SkillStarService skillStarService; diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillTagController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillTagController.java index eab43d3f..1edd7036 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillTagController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillTagController.java @@ -17,7 +17,10 @@ import java.util.Map; import java.util.stream.Collectors; @RestController -@RequestMapping("/api/v1/skills/{namespace}/{slug}/tags") +@RequestMapping({ + "/api/v1/skills/{namespace}/{slug}/tags", + "/api/web/skills/{namespace}/{slug}/tags" +}) public class SkillTagController extends BaseApiController { private final SkillTagService skillTagService; diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java index c9afab70..b08917e1 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/config/SecurityConfig.java @@ -70,7 +70,7 @@ public class SecurityConfig { .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(csrfHandler) - .ignoringRequestMatchers("/api/v1/**", "/api/compat/**") + .ignoringRequestMatchers("/api/v1/**", "/api/web/**", "/api/compat/**") ) .authorizeHttpRequests(auth -> auth .requestMatchers( @@ -92,7 +92,13 @@ public class SecurityConfig { "/api/compat/v1/resolve/**", "/api/compat/v1/download/**" ).permitAll() - .requestMatchers(HttpMethod.GET, "/api/v1/skills/*/star", "/api/v1/skills/*/rating").authenticated() + .requestMatchers( + HttpMethod.GET, + "/api/v1/skills/*/star", + "/api/v1/skills/*/rating", + "/api/web/skills/*/star", + "/api/web/skills/*/rating" + ).authenticated() .requestMatchers( HttpMethod.GET, "/api/v1/skills", @@ -104,9 +110,25 @@ public class SecurityConfig { "/api/v1/skills/*/*/resolve", "/api/v1/skills/*/*/tags", "/api/v1/skills/*/*/tags/*/files", - "/api/v1/skills/*/*/tags/*/file" + "/api/v1/skills/*/*/tags/*/file", + "/api/web/skills", + "/api/web/skills/*/*", + "/api/web/skills/*/*/versions", + "/api/web/skills/*/*/versions/*", + "/api/web/skills/*/*/versions/*/files", + "/api/web/skills/*/*/versions/*/file", + "/api/web/skills/*/*/resolve", + "/api/web/skills/*/*/tags", + "/api/web/skills/*/*/tags/*/files", + "/api/web/skills/*/*/tags/*/file" + ).permitAll() + .requestMatchers( + HttpMethod.GET, + "/api/v1/namespaces", + "/api/v1/namespaces/*", + "/api/web/namespaces", + "/api/web/namespaces/*" ).permitAll() - .requestMatchers(HttpMethod.GET, "/api/v1/namespaces", "/api/v1/namespaces/*").permitAll() .requestMatchers("/api/v1/admin/**").hasAnyRole("SUPER_ADMIN", "SKILL_ADMIN", "USER_ADMIN", "AUDITOR") .anyRequest().authenticated() ) diff --git a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java index d18571df..c58b2af7 100644 --- a/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java +++ b/server/skillhub-auth/src/main/java/com/iflytek/skillhub/auth/token/ApiTokenScopeService.java @@ -24,8 +24,12 @@ public class ApiTokenScopeService { ScopeRule.allow("GET", "/api/v1/whoami"), ScopeRule.allow("GET", "/api/v1/skills"), ScopeRule.allow("GET", "/api/v1/skills/**"), + ScopeRule.allow("GET", "/api/web/skills"), + ScopeRule.allow("GET", "/api/web/skills/**"), ScopeRule.allow("GET", "/api/v1/namespaces"), ScopeRule.allow("GET", "/api/v1/namespaces/*"), + ScopeRule.allow("GET", "/api/web/namespaces"), + ScopeRule.allow("GET", "/api/web/namespaces/*"), ScopeRule.allow("GET", "/api/compat/v1/search"), ScopeRule.allow("GET", "/api/compat/v1/resolve/**"), ScopeRule.allow("GET", "/api/compat/v1/whoami"), @@ -39,6 +43,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/web/skills/*/publish", "skill:publish"), ScopeRule.require("POST", "/api/v1/publish", "skill:publish"), ScopeRule.require("POST", "/api/compat/v1/publish", "skill:publish") ); @@ -96,7 +101,7 @@ public class ApiTokenScopeService { } private boolean isApiPath(String path) { - return path != null && (path.startsWith("/api/v1/") || path.startsWith("/api/compat/")); + return path != null && (path.startsWith("/api/v1/") || path.startsWith("/api/web/") || path.startsWith("/api/compat/")); } public record AuthorizationDecision(boolean allowed, String requiredScope, String message) { diff --git a/web/src/api/client.ts b/web/src/api/client.ts index eef1dd50..af43b166 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -24,6 +24,8 @@ import i18n from '@/i18n/config' export { ApiError } +export const WEB_API_PREFIX = '/api/web' + type RuntimeConfig = { apiBaseUrl?: string appBaseUrl?: string @@ -495,16 +497,16 @@ export const reviewApi = { searchParams.set('page', String(params.page ?? 0)) searchParams.set('size', String(params.size ?? 20)) return fetchJson<{ items: ReviewTask[]; total: number; page: number; size: number }>( - `/api/v1/reviews?${searchParams.toString()}`, + `${WEB_API_PREFIX}/reviews?${searchParams.toString()}`, ) }, async get(id: number): Promise { - return fetchJson(`/api/v1/reviews/${id}`) + return fetchJson(`${WEB_API_PREFIX}/reviews/${id}`) }, async approve(id: number, comment?: string): Promise { - await fetchJson(`/api/v1/reviews/${id}/approve`, { + await fetchJson(`${WEB_API_PREFIX}/reviews/${id}/approve`, { method: 'POST', headers: getCsrfHeaders({ 'Content-Type': 'application/json', @@ -514,7 +516,7 @@ export const reviewApi = { }, async reject(id: number, comment: string): Promise { - await fetchJson(`/api/v1/reviews/${id}/reject`, { + await fetchJson(`${WEB_API_PREFIX}/reviews/${id}/reject`, { method: 'POST', headers: getCsrfHeaders({ 'Content-Type': 'application/json', @@ -531,16 +533,16 @@ export const promotionApi = { searchParams.set('page', String(params.page ?? 0)) searchParams.set('size', String(params.size ?? 20)) return fetchJson<{ items: PromotionTask[]; total: number; page: number; size: number }>( - `/api/v1/promotions?${searchParams.toString()}`, + `${WEB_API_PREFIX}/promotions?${searchParams.toString()}`, ) }, async get(id: number): Promise { - return fetchJson(`/api/v1/promotions/${id}`) + return fetchJson(`${WEB_API_PREFIX}/promotions/${id}`) }, async approve(id: number, comment?: string): Promise { - await fetchJson(`/api/v1/promotions/${id}/approve`, { + await fetchJson(`${WEB_API_PREFIX}/promotions/${id}/approve`, { method: 'POST', headers: getCsrfHeaders({ 'Content-Type': 'application/json', @@ -550,7 +552,7 @@ export const promotionApi = { }, async reject(id: number, comment?: string): Promise { - await fetchJson(`/api/v1/promotions/${id}/reject`, { + await fetchJson(`${WEB_API_PREFIX}/promotions/${id}/reject`, { method: 'POST', headers: getCsrfHeaders({ 'Content-Type': 'application/json', @@ -562,7 +564,7 @@ export const promotionApi = { export const meApi = { async getStars(): Promise { - return fetchJson('/api/v1/me/stars') + return fetchJson(`${WEB_API_PREFIX}/me/stars`) }, } diff --git a/web/src/features/social/use-rating.ts b/web/src/features/social/use-rating.ts index 6ebe43cc..7529f0d3 100644 --- a/web/src/features/social/use-rating.ts +++ b/web/src/features/social/use-rating.ts @@ -1,5 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { ApiError, fetchJson, getCsrfHeaders } from '@/api/client' +import { ApiError, fetchJson, getCsrfHeaders, WEB_API_PREFIX } from '@/api/client' interface UserRating { score: number @@ -8,7 +8,7 @@ interface UserRating { async function getUserRating(skillId: number): Promise { try { - return await fetchJson(`/api/v1/skills/${skillId}/rating`) + return await fetchJson(`${WEB_API_PREFIX}/skills/${skillId}/rating`) } catch (error) { if (error instanceof ApiError && error.status === 401) { return { score: 0, rated: false } @@ -18,7 +18,7 @@ async function getUserRating(skillId: number): Promise { } async function rateSkill(skillId: number, rating: number): Promise { - await fetchJson(`/api/v1/skills/${skillId}/rating`, { + await fetchJson(`${WEB_API_PREFIX}/skills/${skillId}/rating`, { method: 'PUT', headers: getCsrfHeaders({ 'Content-Type': 'application/json', diff --git a/web/src/features/social/use-star.ts b/web/src/features/social/use-star.ts index d12331b6..64e1c5f8 100644 --- a/web/src/features/social/use-star.ts +++ b/web/src/features/social/use-star.ts @@ -1,5 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { ApiError, fetchJson, getCsrfHeaders } from '@/api/client' +import { ApiError, fetchJson, getCsrfHeaders, WEB_API_PREFIX } from '@/api/client' interface StarStatus { starred: boolean @@ -7,7 +7,7 @@ interface StarStatus { async function getStarStatus(skillId: number): Promise { try { - const starred = await fetchJson(`/api/v1/skills/${skillId}/star`) + const starred = await fetchJson(`${WEB_API_PREFIX}/skills/${skillId}/star`) return { starred } } catch (error) { if (error instanceof ApiError && error.status === 401) { @@ -19,12 +19,12 @@ async function getStarStatus(skillId: number): Promise { async function toggleStar(skillId: number, starred: boolean): Promise { if (starred) { - await fetchJson(`/api/v1/skills/${skillId}/star`, { + await fetchJson(`${WEB_API_PREFIX}/skills/${skillId}/star`, { method: 'DELETE', headers: getCsrfHeaders(), }) } else { - await fetchJson(`/api/v1/skills/${skillId}/star`, { + await fetchJson(`${WEB_API_PREFIX}/skills/${skillId}/star`, { method: 'PUT', headers: getCsrfHeaders(), }) diff --git a/web/src/pages/skill-detail.tsx b/web/src/pages/skill-detail.tsx index fb0d2f38..e0b70dc8 100644 --- a/web/src/pages/skill-detail.tsx +++ b/web/src/pages/skill-detail.tsx @@ -8,7 +8,7 @@ import { InstallCommand } from '@/features/skill/install-command' import { RatingInput } from '@/features/social/rating-input' import { StarButton } from '@/features/social/star-button' import { useAuth } from '@/features/auth/use-auth' -import { adminApi } from '@/api/client' +import { adminApi, WEB_API_PREFIX } from '@/api/client' import { formatLocalDateTime } from '@/shared/lib/date-time' import { formatCompactCount } from '@/shared/lib/number-format' import { NamespaceBadge } from '@/shared/components/namespace-badge' @@ -67,7 +67,7 @@ export function SkillDetailPage() { return } const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace - const downloadUrl = `/api/v1/skills/${cleanNamespace}/${slug}/versions/${latestVersion.version}/download` + const downloadUrl = `${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${latestVersion.version}/download` window.open(downloadUrl, '_blank') } diff --git a/web/src/shared/hooks/use-skill-queries.ts b/web/src/shared/hooks/use-skill-queries.ts index 092a7382..a23d3c9f 100644 --- a/web/src/shared/hooks/use-skill-queries.ts +++ b/web/src/shared/hooks/use-skill-queries.ts @@ -1,6 +1,6 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import type { SkillSummary, SkillDetail, SkillVersion, SkillFile, SearchParams, PagedResponse, PublishResult, Namespace, NamespaceMember } from '@/api/types' -import { fetchJson, fetchText, getCsrfHeaders, meApi } from '@/api/client' +import { fetchJson, fetchText, getCsrfHeaders, meApi, WEB_API_PREFIX } from '@/api/client' const PUBLISH_REQUEST_TIMEOUT_MS = 60_000 @@ -15,36 +15,36 @@ async function searchSkills(params: SearchParams): Promise>(`/api/v1/skills?${queryParams.toString()}`) + return fetchJson>(`${WEB_API_PREFIX}/skills?${queryParams.toString()}`) } async function getSkillDetail(namespace: string, slug: string): Promise { const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace - return fetchJson(`/api/v1/skills/${cleanNamespace}/${slug}`) + return fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}`) } async function getSkillVersions(namespace: string, slug: string): Promise { const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace - const page = await fetchJson>(`/api/v1/skills/${cleanNamespace}/${slug}/versions`) + const page = await fetchJson>(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions`) return page.items } async function getSkillFiles(namespace: string, slug: string, version: string): Promise { const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace - return fetchJson(`/api/v1/skills/${cleanNamespace}/${slug}/versions/${version}/files`) + return fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${version}/files`) } async function getSkillReadme(namespace: string, slug: string, version: string): Promise { const cleanNamespace = namespace.startsWith('@') ? namespace.slice(1) : namespace try { - return await fetchText(`/api/v1/skills/${cleanNamespace}/${slug}/versions/${version}/file?path=SKILL.md`) + return await fetchText(`${WEB_API_PREFIX}/skills/${cleanNamespace}/${slug}/versions/${version}/file?path=SKILL.md`) } catch { return '' } } async function getMySkills(): Promise { - return fetchJson('/api/v1/me/skills') + return fetchJson(`${WEB_API_PREFIX}/me/skills`) } async function getMyStars(): Promise { @@ -52,18 +52,18 @@ async function getMyStars(): Promise { } async function getMyNamespaces(): Promise { - const page = await fetchJson>('/api/v1/namespaces') + const page = await fetchJson>(`${WEB_API_PREFIX}/namespaces`) return page.items } async function getNamespaceDetail(slug: string): Promise { const cleanSlug = slug.startsWith('@') ? slug.slice(1) : slug - return fetchJson(`/api/v1/namespaces/${cleanSlug}`) + return fetchJson(`${WEB_API_PREFIX}/namespaces/${cleanSlug}`) } async function getNamespaceMembers(slug: string): Promise { const cleanSlug = slug.startsWith('@') ? slug.slice(1) : slug - const page = await fetchJson>(`/api/v1/namespaces/${cleanSlug}/members`) + const page = await fetchJson>(`${WEB_API_PREFIX}/namespaces/${cleanSlug}/members`) return page.items } @@ -73,7 +73,7 @@ async function publishSkill(params: { namespace: string; file: File; visibility: formData.append('file', params.file) formData.append('visibility', params.visibility) - return fetchJson(`/api/v1/skills/${cleanNamespace}/publish`, { + return fetchJson(`${WEB_API_PREFIX}/skills/${cleanNamespace}/publish`, { method: 'POST', headers: getCsrfHeaders(), body: formData,