fix(admin): join REST URLs correctly under plain permalinks

When permalink_structure is empty, rest_url() is
index.php?rest_route=/aksrt/v1. Naive base+endpoint produced
rest_route=/aksrt/v1/banners?kind=banner (query inside route value),
so WP returned rest_no_route and Banner/Hero managers 404'd.
Fold path into rest_route and append extra params as siblings.
This commit is contained in:
2026-07-22 20:32:32 +08:00
parent 5a6f4e427f
commit 79bc4159f5
+74 -4
View File
@@ -1,7 +1,8 @@
/** /**
* AKSRT Admin API Client * AKSRT Admin API Client
* *
* Communicates with the WordPress REST API at /wp-json/aksrt/v1. * Communicates with the WordPress REST API at /wp-json/aksrt/v1
* (or index.php?rest_route=/aksrt/v1 when pretty permalinks are off).
* Methods are organized into domain modules while the client keeps the shared * Methods are organized into domain modules while the client keeps the shared
* request pipeline and schema-domain normalization contract. * request pipeline and schema-domain normalization contract.
*/ */
@@ -15,6 +16,73 @@ function normalizeDomainKey(domain) {
return String(domain || 'global').trim().replace(/-/g, '_') return String(domain || 'global').trim().replace(/-/g, '_')
} }
/**
* Join REST base + endpoint correctly for both pretty and plain permalinks.
*
* Pretty: https://site/wp-json/aksrt/v1 + /banners?kind=x
* → https://site/wp-json/aksrt/v1/banners?kind=x
*
* Plain: https://site/index.php?rest_route=/aksrt/v1 + /banners?kind=x
* → https://site/index.php?rest_route=/aksrt/v1/banners&kind=x
* (must NOT produce rest_route=/aksrt/v1/banners?kind=x)
*
* @param {string} baseUrl
* @param {string} endpoint path starting with /, may include ?query
* @returns {string}
*/
function joinRestUrl(baseUrl, endpoint) {
let path = endpoint == null || endpoint === '' ? '/' : String(endpoint)
let extraQuery = ''
const qIdx = path.indexOf('?')
if (qIdx !== -1) {
extraQuery = path.slice(qIdx + 1)
path = path.slice(0, qIdx)
}
if (!path.startsWith('/')) {
path = '/' + path
}
const base = String(baseUrl || '').trim() || '/wp-json/aksrt/v1'
// Plain permalinks: rest_route query mode
if (base.includes('rest_route=')) {
let u
try {
u = new URL(base, typeof window !== 'undefined' ? window.location.origin : 'https://localhost')
} catch {
// Fallback: naive append (should not hit with valid rest_url)
return base.replace(/\/?$/, '') + path + (extraQuery ? (base.includes('?') ? '&' : '?') + extraQuery : '')
}
const baseRoute = (u.searchParams.get('rest_route') || '/aksrt/v1').replace(/\/$/, '') || '/aksrt/v1'
let fullRoute
if (path === '/') {
fullRoute = baseRoute
} else if (path.startsWith('/aksrt/')) {
fullRoute = path
} else {
fullRoute = baseRoute + path
}
u.searchParams.set('rest_route', fullRoute)
if (extraQuery) {
const extra = new URLSearchParams(extraQuery)
extra.forEach((v, k) => {
u.searchParams.set(k, v)
})
}
return u.toString()
}
// Pretty /wp-json mode
const root = base.replace(/\/$/, '')
let url = root + (path === '/' ? '' : path)
if (extraQuery) {
url += (url.includes('?') ? '&' : '?') + extraQuery
}
return url
}
class ApiClient { class ApiClient {
constructor() { constructor() {
this.baseUrl = API_ROOT this.baseUrl = API_ROOT
@@ -58,11 +126,13 @@ class ApiClient {
endpoint = endpoint.replace('/aksrt/v1', '') || '/' endpoint = endpoint.replace('/aksrt/v1', '') || '/'
} }
let url = `${this.baseUrl}${endpoint}` let url = joinRestUrl(this.baseUrl, endpoint)
if (params && typeof params === 'object') { if (params && typeof params === 'object') {
const qs = new URLSearchParams(params).toString() const qs = new URLSearchParams(params).toString()
if (qs) url += (url.includes('?') ? '&' : '?') + qs if (qs) {
url += (url.includes('?') ? '&' : '?') + qs
}
} }
let finalBody = body let finalBody = body
@@ -116,5 +186,5 @@ class ApiClient {
Object.assign(ApiClient.prototype, apiMethods) Object.assign(ApiClient.prototype, apiMethods)
export { normalizeDomainKey } export { normalizeDomainKey, joinRestUrl }
export default new ApiClient() export default new ApiClient()