perspectapi-ts-sdk 2.6.0 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/dist/index.d.mts +370 -2
- package/dist/index.d.ts +370 -2
- package/dist/index.js +377 -2
- package/dist/index.mjs +376 -2
- package/package.json +1 -1
- package/src/client/content-client.ts +67 -1
- package/src/client/products-client.ts +123 -0
- package/src/client/site-users-client.ts +352 -0
- package/src/index.ts +13 -1
- package/src/perspect-api-client.ts +3 -0
- package/src/types/index.ts +106 -1
- package/src/utils/validators.ts +1 -2
package/dist/index.mjs
CHANGED
|
@@ -707,7 +707,7 @@ var AuthClient = class extends BaseClient {
|
|
|
707
707
|
|
|
708
708
|
// src/utils/validators.ts
|
|
709
709
|
var MAX_API_QUERY_LIMIT = 100;
|
|
710
|
-
var ALLOWED_CONTENT_TYPES = ["post", "page"];
|
|
710
|
+
var ALLOWED_CONTENT_TYPES = ["post", "page", "block"];
|
|
711
711
|
function validateLimit(limit, context) {
|
|
712
712
|
if (typeof limit !== "number" || Number.isNaN(limit) || !Number.isFinite(limit)) {
|
|
713
713
|
throw new Error(`[PerspectAPI] ${context} limit must be a finite number.`);
|
|
@@ -841,6 +841,50 @@ var ContentClient = class extends BaseClient {
|
|
|
841
841
|
() => this.http.get(path)
|
|
842
842
|
);
|
|
843
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Get content by category slug for a site
|
|
846
|
+
*/
|
|
847
|
+
async getContentByCategorySlug(siteName, categorySlug, params, cachePolicy) {
|
|
848
|
+
const endpoint = this.siteScopedEndpoint(
|
|
849
|
+
siteName,
|
|
850
|
+
`/category/${encodeURIComponent(categorySlug)}`
|
|
851
|
+
);
|
|
852
|
+
const path = this.buildPath(endpoint);
|
|
853
|
+
const normalizedParams = params ? { ...params } : void 0;
|
|
854
|
+
if (normalizedParams) {
|
|
855
|
+
const validatedLimit = validateOptionalLimit(
|
|
856
|
+
normalizedParams.limit,
|
|
857
|
+
"content category query"
|
|
858
|
+
);
|
|
859
|
+
if (validatedLimit !== void 0) {
|
|
860
|
+
normalizedParams.limit = validatedLimit;
|
|
861
|
+
} else {
|
|
862
|
+
delete normalizedParams.limit;
|
|
863
|
+
}
|
|
864
|
+
const validatedPageType = validateOptionalContentType(
|
|
865
|
+
normalizedParams.page_type,
|
|
866
|
+
"content category query"
|
|
867
|
+
);
|
|
868
|
+
if (validatedPageType !== void 0) {
|
|
869
|
+
normalizedParams.page_type = validatedPageType;
|
|
870
|
+
} else {
|
|
871
|
+
delete normalizedParams.page_type;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return this.fetchWithCache(
|
|
875
|
+
endpoint,
|
|
876
|
+
normalizedParams,
|
|
877
|
+
this.buildContentTags(
|
|
878
|
+
siteName,
|
|
879
|
+
void 0,
|
|
880
|
+
void 0,
|
|
881
|
+
normalizedParams?.slug_prefix,
|
|
882
|
+
categorySlug
|
|
883
|
+
),
|
|
884
|
+
cachePolicy,
|
|
885
|
+
() => this.http.get(path, normalizedParams)
|
|
886
|
+
);
|
|
887
|
+
}
|
|
844
888
|
/**
|
|
845
889
|
* Get content by slug for a site
|
|
846
890
|
*/
|
|
@@ -916,7 +960,7 @@ var ContentClient = class extends BaseClient {
|
|
|
916
960
|
async duplicateContent(id) {
|
|
917
961
|
return this.create(`/content/${id}/duplicate`, {});
|
|
918
962
|
}
|
|
919
|
-
buildContentTags(siteName, slug, id, slugPrefix) {
|
|
963
|
+
buildContentTags(siteName, slug, id, slugPrefix, categorySlug) {
|
|
920
964
|
const tags = /* @__PURE__ */ new Set(["content"]);
|
|
921
965
|
if (siteName) {
|
|
922
966
|
tags.add(`content:site:${siteName}`);
|
|
@@ -930,6 +974,10 @@ var ContentClient = class extends BaseClient {
|
|
|
930
974
|
if (slugPrefix) {
|
|
931
975
|
tags.add(`content:prefix:${slugPrefix}`);
|
|
932
976
|
}
|
|
977
|
+
if (categorySlug && siteName) {
|
|
978
|
+
tags.add("content:category");
|
|
979
|
+
tags.add(`content:category:${siteName}:${categorySlug}`);
|
|
980
|
+
}
|
|
933
981
|
return Array.from(tags.values());
|
|
934
982
|
}
|
|
935
983
|
/**
|
|
@@ -1413,6 +1461,64 @@ var ProductsClient = class extends BaseClient {
|
|
|
1413
1461
|
}
|
|
1414
1462
|
return void 0;
|
|
1415
1463
|
}
|
|
1464
|
+
// ============================================================================
|
|
1465
|
+
// PRODUCT OPTIONS AND SKUS (for variants)
|
|
1466
|
+
// ============================================================================
|
|
1467
|
+
/**
|
|
1468
|
+
* Get all options (and their values) for a product
|
|
1469
|
+
*/
|
|
1470
|
+
async getProductOptions(siteName, productId) {
|
|
1471
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1472
|
+
siteName,
|
|
1473
|
+
`/products/${productId}/options`,
|
|
1474
|
+
{ includeSitesSegment: false }
|
|
1475
|
+
);
|
|
1476
|
+
return this.getSingle(endpoint);
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Create a new product option (e.g., "Size", "Color")
|
|
1480
|
+
*/
|
|
1481
|
+
async createProductOption(siteName, productId, data) {
|
|
1482
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1483
|
+
siteName,
|
|
1484
|
+
`/products/${productId}/options`,
|
|
1485
|
+
{ includeSitesSegment: false }
|
|
1486
|
+
);
|
|
1487
|
+
return this.create(endpoint, data);
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Create a new value for a product option (e.g., "Small", "Red")
|
|
1491
|
+
*/
|
|
1492
|
+
async createProductOptionValue(siteName, productId, optionId, data) {
|
|
1493
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1494
|
+
siteName,
|
|
1495
|
+
`/products/${productId}/options/${optionId}/values`,
|
|
1496
|
+
{ includeSitesSegment: false }
|
|
1497
|
+
);
|
|
1498
|
+
return this.create(endpoint, data);
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Get all SKUs for a product (with their option value combinations)
|
|
1502
|
+
*/
|
|
1503
|
+
async getProductSkus(siteName, productId) {
|
|
1504
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1505
|
+
siteName,
|
|
1506
|
+
`/products/${productId}/skus`,
|
|
1507
|
+
{ includeSitesSegment: false }
|
|
1508
|
+
);
|
|
1509
|
+
return this.getSingle(endpoint);
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Create or update a SKU for a product variant combination
|
|
1513
|
+
*/
|
|
1514
|
+
async createProductSku(siteName, productId, data) {
|
|
1515
|
+
const endpoint = this.siteScopedEndpoint(
|
|
1516
|
+
siteName,
|
|
1517
|
+
`/products/${productId}/skus`,
|
|
1518
|
+
{ includeSitesSegment: false }
|
|
1519
|
+
);
|
|
1520
|
+
return this.create(endpoint, data);
|
|
1521
|
+
}
|
|
1416
1522
|
};
|
|
1417
1523
|
|
|
1418
1524
|
// src/client/categories-client.ts
|
|
@@ -2127,6 +2233,271 @@ var NewsletterClient = class extends BaseClient {
|
|
|
2127
2233
|
}
|
|
2128
2234
|
};
|
|
2129
2235
|
|
|
2236
|
+
// src/client/site-users-client.ts
|
|
2237
|
+
var SiteUsersClient = class extends BaseClient {
|
|
2238
|
+
constructor(http, cache) {
|
|
2239
|
+
super(http, "/api/v1", cache);
|
|
2240
|
+
}
|
|
2241
|
+
/**
|
|
2242
|
+
* Build a site user endpoint scoped to a site (without /sites prefix)
|
|
2243
|
+
*/
|
|
2244
|
+
siteUserEndpoint(siteName, endpoint) {
|
|
2245
|
+
return this.siteScopedEndpoint(siteName, endpoint, { includeSitesSegment: false });
|
|
2246
|
+
}
|
|
2247
|
+
// ============================================================================
|
|
2248
|
+
// PUBLIC ENDPOINTS (OTP authentication)
|
|
2249
|
+
// ============================================================================
|
|
2250
|
+
/**
|
|
2251
|
+
* Request OTP for login/signup
|
|
2252
|
+
* @param siteName - The site name
|
|
2253
|
+
* @param data - Email address
|
|
2254
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
2255
|
+
*/
|
|
2256
|
+
async requestOtp(siteName, data, csrfToken) {
|
|
2257
|
+
if (typeof window !== "undefined" && !csrfToken) {
|
|
2258
|
+
console.warn("CSRF token recommended for browser-based OTP requests");
|
|
2259
|
+
}
|
|
2260
|
+
return this.create(
|
|
2261
|
+
this.siteUserEndpoint(siteName, "/users/request-otp"),
|
|
2262
|
+
data,
|
|
2263
|
+
csrfToken
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
2266
|
+
/**
|
|
2267
|
+
* Verify OTP and get JWT token
|
|
2268
|
+
*
|
|
2269
|
+
* For cross-domain authentication, you must manually set the token after verification:
|
|
2270
|
+
* ```typescript
|
|
2271
|
+
* const response = await client.siteUsers.verifyOtp('mysite', { email, code });
|
|
2272
|
+
* const { token, user } = response.data;
|
|
2273
|
+
*
|
|
2274
|
+
* // Store token securely (choose one):
|
|
2275
|
+
* // Option 1: Memory (lost on refresh, most secure)
|
|
2276
|
+
* client.setAuth(token);
|
|
2277
|
+
*
|
|
2278
|
+
* // Option 2: httpOnly cookie on YOUR domain (recommended for production)
|
|
2279
|
+
* await fetch('/your-api/set-auth-cookie', {
|
|
2280
|
+
* method: 'POST',
|
|
2281
|
+
* body: JSON.stringify({ token })
|
|
2282
|
+
* });
|
|
2283
|
+
* client.setAuth(token);
|
|
2284
|
+
*
|
|
2285
|
+
* // Option 3: localStorage (vulnerable to XSS, not recommended)
|
|
2286
|
+
* localStorage.setItem('site_user_token', token);
|
|
2287
|
+
* client.setAuth(token);
|
|
2288
|
+
* ```
|
|
2289
|
+
*
|
|
2290
|
+
* For convenience, use `verifyOtpAndSetAuth()` to automatically set the token in memory.
|
|
2291
|
+
*
|
|
2292
|
+
* @param siteName - The site name
|
|
2293
|
+
* @param data - Email and code
|
|
2294
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
2295
|
+
*/
|
|
2296
|
+
async verifyOtp(siteName, data, csrfToken) {
|
|
2297
|
+
if (typeof window !== "undefined" && !csrfToken) {
|
|
2298
|
+
console.warn("CSRF token recommended for browser-based OTP verification");
|
|
2299
|
+
}
|
|
2300
|
+
return this.create(
|
|
2301
|
+
this.siteUserEndpoint(siteName, "/users/verify-otp"),
|
|
2302
|
+
data,
|
|
2303
|
+
csrfToken
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Verify OTP and automatically set the token for subsequent requests
|
|
2308
|
+
*
|
|
2309
|
+
* Convenience method that:
|
|
2310
|
+
* 1. Verifies the OTP
|
|
2311
|
+
* 2. Automatically calls setAuth() with the returned token
|
|
2312
|
+
*
|
|
2313
|
+
* Note: Token is stored in memory only and will be lost on page refresh.
|
|
2314
|
+
* For persistent auth, use verifyOtp() and store the token yourself.
|
|
2315
|
+
*
|
|
2316
|
+
* @param siteName - The site name
|
|
2317
|
+
* @param data - Email and code
|
|
2318
|
+
* @param csrfToken - CSRF token (required for browser-based submissions)
|
|
2319
|
+
*/
|
|
2320
|
+
async verifyOtpAndSetAuth(siteName, data, csrfToken) {
|
|
2321
|
+
const response = await this.verifyOtp(siteName, data, csrfToken);
|
|
2322
|
+
if (response.data?.token) {
|
|
2323
|
+
this.http.setAuth(response.data.token);
|
|
2324
|
+
}
|
|
2325
|
+
return response;
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Logout (clear session cookie)
|
|
2329
|
+
* @param siteName - The site name
|
|
2330
|
+
*/
|
|
2331
|
+
async logout(siteName) {
|
|
2332
|
+
return this.create(
|
|
2333
|
+
this.siteUserEndpoint(siteName, "/users/logout"),
|
|
2334
|
+
{}
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
// ============================================================================
|
|
2338
|
+
// AUTHENTICATED ENDPOINTS (site user JWT required)
|
|
2339
|
+
// ============================================================================
|
|
2340
|
+
/**
|
|
2341
|
+
* Get current user profile
|
|
2342
|
+
* @param siteName - The site name
|
|
2343
|
+
*/
|
|
2344
|
+
async getMe(siteName) {
|
|
2345
|
+
return this.getSingle(
|
|
2346
|
+
this.siteUserEndpoint(siteName, "/users/me")
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* Update current user profile
|
|
2351
|
+
* @param siteName - The site name
|
|
2352
|
+
* @param data - Fields to update
|
|
2353
|
+
* @param csrfToken - CSRF token (required)
|
|
2354
|
+
*/
|
|
2355
|
+
async updateMe(siteName, data, csrfToken) {
|
|
2356
|
+
return this.patch(
|
|
2357
|
+
this.siteUserEndpoint(siteName, "/users/me"),
|
|
2358
|
+
data,
|
|
2359
|
+
csrfToken
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Get all profile key-values
|
|
2364
|
+
* @param siteName - The site name
|
|
2365
|
+
*/
|
|
2366
|
+
async getProfile(siteName) {
|
|
2367
|
+
return this.getSingle(
|
|
2368
|
+
this.siteUserEndpoint(siteName, "/users/me/profile")
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
/**
|
|
2372
|
+
* Set a profile key-value
|
|
2373
|
+
* @param siteName - The site name
|
|
2374
|
+
* @param key - Profile key (e.g., 'phone', 'whatsapp', 'address_shipping')
|
|
2375
|
+
* @param value - Profile value (string or JSON string)
|
|
2376
|
+
* @param csrfToken - CSRF token (required)
|
|
2377
|
+
*/
|
|
2378
|
+
async setProfileValue(siteName, key, value, csrfToken) {
|
|
2379
|
+
return this.update(
|
|
2380
|
+
this.siteUserEndpoint(siteName, `/users/me/profile/${encodeURIComponent(key)}`),
|
|
2381
|
+
{ value },
|
|
2382
|
+
csrfToken
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
/**
|
|
2386
|
+
* Delete a profile key-value
|
|
2387
|
+
* @param siteName - The site name
|
|
2388
|
+
* @param key - Profile key to delete
|
|
2389
|
+
* @param csrfToken - CSRF token (required)
|
|
2390
|
+
*/
|
|
2391
|
+
async deleteProfileValue(siteName, key, csrfToken) {
|
|
2392
|
+
return this.delete(
|
|
2393
|
+
this.siteUserEndpoint(siteName, `/users/me/profile/${encodeURIComponent(key)}`),
|
|
2394
|
+
csrfToken
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Get transaction/order history
|
|
2399
|
+
* @param siteName - The site name
|
|
2400
|
+
* @param params - Pagination params
|
|
2401
|
+
*/
|
|
2402
|
+
async getOrders(siteName, params) {
|
|
2403
|
+
return this.http.get(
|
|
2404
|
+
this.buildPath(this.siteUserEndpoint(siteName, "/users/me/orders")),
|
|
2405
|
+
params
|
|
2406
|
+
);
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* Get single order detail
|
|
2410
|
+
* @param siteName - The site name
|
|
2411
|
+
* @param orderId - Order ID or session ID
|
|
2412
|
+
*/
|
|
2413
|
+
async getOrder(siteName, orderId) {
|
|
2414
|
+
return this.getSingle(
|
|
2415
|
+
this.siteUserEndpoint(siteName, `/users/me/orders/${encodeURIComponent(orderId)}`)
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Get payment subscriptions
|
|
2420
|
+
* @param siteName - The site name
|
|
2421
|
+
* @param params - Pagination params
|
|
2422
|
+
*/
|
|
2423
|
+
async getSubscriptions(siteName, params) {
|
|
2424
|
+
return this.http.get(
|
|
2425
|
+
this.buildPath(this.siteUserEndpoint(siteName, "/users/me/subscriptions")),
|
|
2426
|
+
params
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Get single subscription detail
|
|
2431
|
+
* @param siteName - The site name
|
|
2432
|
+
* @param id - Subscription ID
|
|
2433
|
+
*/
|
|
2434
|
+
async getSubscription(siteName, id) {
|
|
2435
|
+
return this.getSingle(
|
|
2436
|
+
this.siteUserEndpoint(siteName, `/users/me/subscriptions/${encodeURIComponent(id)}`)
|
|
2437
|
+
);
|
|
2438
|
+
}
|
|
2439
|
+
/**
|
|
2440
|
+
* Cancel a subscription (marks for cancellation at period end)
|
|
2441
|
+
* @param siteName - The site name
|
|
2442
|
+
* @param id - Subscription ID
|
|
2443
|
+
* @param csrfToken - CSRF token (required)
|
|
2444
|
+
*/
|
|
2445
|
+
async cancelSubscription(siteName, id, csrfToken) {
|
|
2446
|
+
return this.create(
|
|
2447
|
+
this.siteUserEndpoint(siteName, `/users/me/subscriptions/${encodeURIComponent(id)}/cancel`),
|
|
2448
|
+
{},
|
|
2449
|
+
csrfToken
|
|
2450
|
+
);
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* Get linked newsletter subscriptions
|
|
2454
|
+
* @param siteName - The site name
|
|
2455
|
+
*/
|
|
2456
|
+
async getNewsletterSubscriptions(siteName) {
|
|
2457
|
+
return this.getSingle(
|
|
2458
|
+
this.siteUserEndpoint(siteName, "/users/me/newsletters")
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
// ============================================================================
|
|
2462
|
+
// ADMIN ENDPOINTS (API key auth required)
|
|
2463
|
+
// ============================================================================
|
|
2464
|
+
/**
|
|
2465
|
+
* List all site users (admin only)
|
|
2466
|
+
* @param siteName - The site name
|
|
2467
|
+
* @param params - Query params (limit, offset, status)
|
|
2468
|
+
*/
|
|
2469
|
+
async listUsers(siteName, params) {
|
|
2470
|
+
return this.http.get(
|
|
2471
|
+
this.buildPath(this.siteUserEndpoint(siteName, "/users")),
|
|
2472
|
+
params
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* Get user detail (admin only)
|
|
2477
|
+
* @param siteName - The site name
|
|
2478
|
+
* @param userId - User ID
|
|
2479
|
+
*/
|
|
2480
|
+
async getUser(siteName, userId) {
|
|
2481
|
+
return this.getSingle(
|
|
2482
|
+
this.siteUserEndpoint(siteName, `/users/${encodeURIComponent(userId)}`)
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* Update user status (admin only)
|
|
2487
|
+
* @param siteName - The site name
|
|
2488
|
+
* @param userId - User ID
|
|
2489
|
+
* @param status - New status
|
|
2490
|
+
* @param csrfToken - CSRF token (required)
|
|
2491
|
+
*/
|
|
2492
|
+
async updateUserStatus(siteName, userId, status, csrfToken) {
|
|
2493
|
+
return this.patch(
|
|
2494
|
+
this.siteUserEndpoint(siteName, `/users/${encodeURIComponent(userId)}/status`),
|
|
2495
|
+
{ status },
|
|
2496
|
+
csrfToken
|
|
2497
|
+
);
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
|
|
2130
2501
|
// src/perspect-api-client.ts
|
|
2131
2502
|
var PerspectApiClient = class {
|
|
2132
2503
|
http;
|
|
@@ -2143,6 +2514,7 @@ var PerspectApiClient = class {
|
|
|
2143
2514
|
checkout;
|
|
2144
2515
|
contact;
|
|
2145
2516
|
newsletter;
|
|
2517
|
+
siteUsers;
|
|
2146
2518
|
constructor(config) {
|
|
2147
2519
|
if (!config.baseUrl) {
|
|
2148
2520
|
throw new Error("baseUrl is required in PerspectApiConfig");
|
|
@@ -2160,6 +2532,7 @@ var PerspectApiClient = class {
|
|
|
2160
2532
|
this.checkout = new CheckoutClient(this.http, this.cache);
|
|
2161
2533
|
this.contact = new ContactClient(this.http, this.cache);
|
|
2162
2534
|
this.newsletter = new NewsletterClient(this.http, this.cache);
|
|
2535
|
+
this.siteUsers = new SiteUsersClient(this.http, this.cache);
|
|
2163
2536
|
}
|
|
2164
2537
|
/**
|
|
2165
2538
|
* Update authentication token
|
|
@@ -2848,6 +3221,7 @@ export {
|
|
|
2848
3221
|
OrganizationsClient,
|
|
2849
3222
|
PerspectApiClient,
|
|
2850
3223
|
ProductsClient,
|
|
3224
|
+
SiteUsersClient,
|
|
2851
3225
|
SitesClient,
|
|
2852
3226
|
WebhooksClient,
|
|
2853
3227
|
buildImageUrl,
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ContentQueryParams,
|
|
13
13
|
PaginatedResponse,
|
|
14
14
|
ApiResponse,
|
|
15
|
+
ContentCategoryResponse,
|
|
15
16
|
} from '../types';
|
|
16
17
|
import { validateOptionalContentType, validateOptionalLimit } from '../utils/validators';
|
|
17
18
|
|
|
@@ -139,6 +140,61 @@ export class ContentClient extends BaseClient {
|
|
|
139
140
|
);
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Get content by category slug for a site
|
|
145
|
+
*/
|
|
146
|
+
async getContentByCategorySlug(
|
|
147
|
+
siteName: string,
|
|
148
|
+
categorySlug: string,
|
|
149
|
+
params?: ContentQueryParams,
|
|
150
|
+
cachePolicy?: CachePolicy
|
|
151
|
+
): Promise<ApiResponse<ContentCategoryResponse>> {
|
|
152
|
+
const endpoint = this.siteScopedEndpoint(
|
|
153
|
+
siteName,
|
|
154
|
+
`/category/${encodeURIComponent(categorySlug)}`
|
|
155
|
+
);
|
|
156
|
+
const path = this.buildPath(endpoint);
|
|
157
|
+
const normalizedParams: ContentQueryParams | undefined = params
|
|
158
|
+
? { ...params }
|
|
159
|
+
: undefined;
|
|
160
|
+
|
|
161
|
+
if (normalizedParams) {
|
|
162
|
+
const validatedLimit = validateOptionalLimit(
|
|
163
|
+
normalizedParams.limit,
|
|
164
|
+
'content category query',
|
|
165
|
+
);
|
|
166
|
+
if (validatedLimit !== undefined) {
|
|
167
|
+
normalizedParams.limit = validatedLimit;
|
|
168
|
+
} else {
|
|
169
|
+
delete normalizedParams.limit;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const validatedPageType = validateOptionalContentType(
|
|
173
|
+
normalizedParams.page_type,
|
|
174
|
+
'content category query',
|
|
175
|
+
);
|
|
176
|
+
if (validatedPageType !== undefined) {
|
|
177
|
+
normalizedParams.page_type = validatedPageType;
|
|
178
|
+
} else {
|
|
179
|
+
delete normalizedParams.page_type;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return this.fetchWithCache<ApiResponse<ContentCategoryResponse>>(
|
|
184
|
+
endpoint,
|
|
185
|
+
normalizedParams,
|
|
186
|
+
this.buildContentTags(
|
|
187
|
+
siteName,
|
|
188
|
+
undefined,
|
|
189
|
+
undefined,
|
|
190
|
+
normalizedParams?.slug_prefix,
|
|
191
|
+
categorySlug
|
|
192
|
+
),
|
|
193
|
+
cachePolicy,
|
|
194
|
+
() => this.http.get<ContentCategoryResponse>(path, normalizedParams)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
142
198
|
/**
|
|
143
199
|
* Get content by slug for a site
|
|
144
200
|
*/
|
|
@@ -230,7 +286,13 @@ export class ContentClient extends BaseClient {
|
|
|
230
286
|
return this.create<Record<string, never>, Content>(`/content/${id}/duplicate`, {});
|
|
231
287
|
}
|
|
232
288
|
|
|
233
|
-
private buildContentTags(
|
|
289
|
+
private buildContentTags(
|
|
290
|
+
siteName?: string,
|
|
291
|
+
slug?: string,
|
|
292
|
+
id?: number,
|
|
293
|
+
slugPrefix?: string,
|
|
294
|
+
categorySlug?: string
|
|
295
|
+
): string[] {
|
|
234
296
|
const tags = new Set<string>(['content']);
|
|
235
297
|
if (siteName) {
|
|
236
298
|
tags.add(`content:site:${siteName}`);
|
|
@@ -244,6 +306,10 @@ export class ContentClient extends BaseClient {
|
|
|
244
306
|
if (slugPrefix) {
|
|
245
307
|
tags.add(`content:prefix:${slugPrefix}`);
|
|
246
308
|
}
|
|
309
|
+
if (categorySlug && siteName) {
|
|
310
|
+
tags.add('content:category');
|
|
311
|
+
tags.add(`content:category:${siteName}:${categorySlug}`);
|
|
312
|
+
}
|
|
247
313
|
return Array.from(tags.values());
|
|
248
314
|
}
|
|
249
315
|
|
|
@@ -371,4 +371,127 @@ export class ProductsClient extends BaseClient {
|
|
|
371
371
|
}
|
|
372
372
|
return undefined;
|
|
373
373
|
}
|
|
374
|
+
|
|
375
|
+
// ============================================================================
|
|
376
|
+
// PRODUCT OPTIONS AND SKUS (for variants)
|
|
377
|
+
// ============================================================================
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Get all options (and their values) for a product
|
|
381
|
+
*/
|
|
382
|
+
async getProductOptions(
|
|
383
|
+
siteName: string,
|
|
384
|
+
productId: number
|
|
385
|
+
): Promise<ApiResponse<Array<{
|
|
386
|
+
option_id: number;
|
|
387
|
+
option_name: string;
|
|
388
|
+
display_order: number;
|
|
389
|
+
values: Array<{
|
|
390
|
+
value_id: number;
|
|
391
|
+
value: string;
|
|
392
|
+
display_order: number;
|
|
393
|
+
}>;
|
|
394
|
+
}>>> {
|
|
395
|
+
const endpoint = this.siteScopedEndpoint(
|
|
396
|
+
siteName,
|
|
397
|
+
`/products/${productId}/options`,
|
|
398
|
+
{ includeSitesSegment: false }
|
|
399
|
+
);
|
|
400
|
+
return this.getSingle(endpoint);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Create a new product option (e.g., "Size", "Color")
|
|
405
|
+
*/
|
|
406
|
+
async createProductOption(
|
|
407
|
+
siteName: string,
|
|
408
|
+
productId: number,
|
|
409
|
+
data: { option_name: string; display_order?: number }
|
|
410
|
+
): Promise<ApiResponse<{
|
|
411
|
+
option_id: number;
|
|
412
|
+
option_name: string;
|
|
413
|
+
display_order: number;
|
|
414
|
+
}>> {
|
|
415
|
+
const endpoint = this.siteScopedEndpoint(
|
|
416
|
+
siteName,
|
|
417
|
+
`/products/${productId}/options`,
|
|
418
|
+
{ includeSitesSegment: false }
|
|
419
|
+
);
|
|
420
|
+
return this.create(endpoint, data);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Create a new value for a product option (e.g., "Small", "Red")
|
|
425
|
+
*/
|
|
426
|
+
async createProductOptionValue(
|
|
427
|
+
siteName: string,
|
|
428
|
+
productId: number,
|
|
429
|
+
optionId: number,
|
|
430
|
+
data: { value: string; display_order?: number }
|
|
431
|
+
): Promise<ApiResponse<{
|
|
432
|
+
value_id: number;
|
|
433
|
+
value: string;
|
|
434
|
+
display_order: number;
|
|
435
|
+
}>> {
|
|
436
|
+
const endpoint = this.siteScopedEndpoint(
|
|
437
|
+
siteName,
|
|
438
|
+
`/products/${productId}/options/${optionId}/values`,
|
|
439
|
+
{ includeSitesSegment: false }
|
|
440
|
+
);
|
|
441
|
+
return this.create(endpoint, data);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Get all SKUs for a product (with their option value combinations)
|
|
446
|
+
*/
|
|
447
|
+
async getProductSkus(
|
|
448
|
+
siteName: string,
|
|
449
|
+
productId: number
|
|
450
|
+
): Promise<ApiResponse<Array<{
|
|
451
|
+
sku_id: number;
|
|
452
|
+
sku: string;
|
|
453
|
+
price?: number;
|
|
454
|
+
sale_price?: number;
|
|
455
|
+
stock_quantity?: number;
|
|
456
|
+
combination_key: string;
|
|
457
|
+
value_ids: number[];
|
|
458
|
+
created_at: string;
|
|
459
|
+
updated_at: string;
|
|
460
|
+
}>>> {
|
|
461
|
+
const endpoint = this.siteScopedEndpoint(
|
|
462
|
+
siteName,
|
|
463
|
+
`/products/${productId}/skus`,
|
|
464
|
+
{ includeSitesSegment: false }
|
|
465
|
+
);
|
|
466
|
+
return this.getSingle(endpoint);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Create or update a SKU for a product variant combination
|
|
471
|
+
*/
|
|
472
|
+
async createProductSku(
|
|
473
|
+
siteName: string,
|
|
474
|
+
productId: number,
|
|
475
|
+
data: {
|
|
476
|
+
sku: string;
|
|
477
|
+
price?: number | null;
|
|
478
|
+
sale_price?: number | null;
|
|
479
|
+
stock_quantity?: number | null;
|
|
480
|
+
value_ids: number[];
|
|
481
|
+
}
|
|
482
|
+
): Promise<ApiResponse<{
|
|
483
|
+
sku_id: number;
|
|
484
|
+
sku: string;
|
|
485
|
+
price?: number;
|
|
486
|
+
sale_price?: number;
|
|
487
|
+
stock_quantity?: number;
|
|
488
|
+
combination_key: string;
|
|
489
|
+
}>> {
|
|
490
|
+
const endpoint = this.siteScopedEndpoint(
|
|
491
|
+
siteName,
|
|
492
|
+
`/products/${productId}/skus`,
|
|
493
|
+
{ includeSitesSegment: false }
|
|
494
|
+
);
|
|
495
|
+
return this.create(endpoint, data);
|
|
496
|
+
}
|
|
374
497
|
}
|