@rezamirzapour/pod-sdk 1.0.2 → 1.0.3
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 +214 -210
- package/dist/index.d.mts +593 -44
- package/dist/index.d.ts +593 -44
- package/dist/index.js +903 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +903 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @rezamirzapour/pod-sdk
|
|
2
2
|
|
|
3
|
-
> Unified, type-safe, enterprise-grade SDK for **POD Platform** microservices (SSO, CustomPost, Podspace, Podform, Notification, Social,
|
|
3
|
+
> Unified, type-safe, enterprise-grade SDK for **POD Platform** microservices (CMS Content, CMS Product, SSO, CustomPost, Podspace, Podform, Notification, Social, IUMS), engineered specifically for **Next.js App Router**, **Server Components**, and **Server Actions**.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@rezamirzapour/pod-sdk)
|
|
6
6
|
[](LICENSE)
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- 🌐 **Unified Multi-Service Architecture**: Single entry-point managing
|
|
13
|
+
- 🌐 **Unified Multi-Service Architecture**: Single entry-point managing official POD microservices with official endpoints and configurations.
|
|
14
14
|
- ⚡ **Powered by `@rezamirzapour/http`**: Automatic exponential backoff retries, Next.js incremental static regeneration (ISR) caching (`next.revalidate`, tags), and isomorphic execution.
|
|
15
|
-
- 🧬 **First-Class TypeScript Generics (`<T, P>`)**: CMS and CustomPost endpoints allow you to define and receive strongly typed metadata structures according to your business schemas.
|
|
16
|
-
- 🎨 **Automatic CMS Data Formatter**: Automatically translates
|
|
15
|
+
- 🧬 **First-Class TypeScript Generics (`<T, P>`)**: CMS Content, CMS Product, and CustomPost endpoints allow you to define and receive strongly typed metadata structures according to your business schemas.
|
|
16
|
+
- 🎨 **Automatic CMS Data Formatter**: Automatically translates nested `metadata.content` and `metadata.product` arrays into flat, strongly typed objects (`formatted[fieldCode]`) and resolves image hashes to Podspace CDN URLs.
|
|
17
|
+
- 🛍️ **Full CMS Product Module (RAD API Swagger)**: Complete support for published products, price/discount filters, barcodes, batch publish/unpublish, archiving, and AI timeline search.
|
|
17
18
|
- 📦 **Generic CRUD Repository for CustomPost**: Instantiate typed data repositories on POD CustomPost in 2 lines with `create`, `getById`, `getAll`, `createAndBindEntityId`, and `archive`.
|
|
18
19
|
- 🔐 **Zero-Dependency Web Crypto RSA Signing**: Built-in RSA-SHA256 signature calculation for POD SSO OTP handshakes using standard Web Crypto API (Node.js, Edge Runtime, Browsers).
|
|
19
20
|
- 🚀 **Next.js App Router Ready**: Seamless support for Server Components, Server Actions, Route Handlers, and Client Components.
|
|
@@ -23,16 +24,17 @@
|
|
|
23
24
|
|
|
24
25
|
## Microservices Included
|
|
25
26
|
|
|
26
|
-
| Service |
|
|
27
|
-
| :--- | :--- | :--- |
|
|
28
|
-
| **CMS** | `sdk.cms` | Fetch, publish, edit, and categorize CMS articles with generic metadata `<T, P
|
|
29
|
-
| **
|
|
30
|
-
| **
|
|
31
|
-
| **
|
|
32
|
-
| **
|
|
33
|
-
| **
|
|
34
|
-
| **
|
|
35
|
-
| **
|
|
27
|
+
| Service | Accessor | Swagger Tag | Description |
|
|
28
|
+
| :--- | :--- | :--- | :--- |
|
|
29
|
+
| **CMS Content** | `sdk.cms` | `content` | Fetch, publish, edit, draft, archive, and categorize CMS articles with generic metadata `<T, P>`. |
|
|
30
|
+
| **CMS Product** | `sdk.product` / `sdk.cms.products` | `product` | Product catalog, price & discount range filters, barcode lookup, batch publish, and AI search. |
|
|
31
|
+
| **CustomPost** | `sdk.customPost` | - | Search timeline by metadata `<T>`, custom post CRUD, and high-level typed repository. |
|
|
32
|
+
| **SSO** | `sdk.sso` | - | OAuth2 handshake, OTP dispatch with digital RSA signature, OTP verify, token generation, and user profile. |
|
|
33
|
+
| **Podspace** | `sdk.podspace` | - | File upload (FormData) and public/private download URL resolution. |
|
|
34
|
+
| **Podform** | `sdk.podform` | - | Survey & form response submission, question/form structure retrieval. |
|
|
35
|
+
| **Notification** | `sdk.notification` | - | SMS delivery and bulk messaging with tracking. |
|
|
36
|
+
| **Social** | `sdk.social` | - | User comments, reactions (likes/dislikes), rates, and social post interactions. |
|
|
37
|
+
| **IUMS** | `sdk.iums` | - | Student and user identity inspection by national code or student ID. |
|
|
36
38
|
|
|
37
39
|
---
|
|
38
40
|
|
|
@@ -73,10 +75,13 @@ export const podSdk = createPodSdk({
|
|
|
73
75
|
|
|
74
76
|
---
|
|
75
77
|
|
|
76
|
-
## Deep Dive: CMS &
|
|
78
|
+
## Deep Dive 1: CMS Content & Data Formatting
|
|
77
79
|
|
|
78
80
|
POD CMS stores dynamic fields inside a `metadata.content` array of `{ code, value, type }` objects. `@rezamirzapour/pod-sdk` automatically formats these into clean typed key-value pairs (`item.formatted`) and allows an optional custom `normalizer` function (`item.__normalized`).
|
|
79
81
|
|
|
82
|
+
> [!NOTE]
|
|
83
|
+
> `getContent2` has been removed. For authenticated/management content retrieval (using `Access-Token`), call `podSdk.cms.getAllContents()` or pass headers to `getContent()`.
|
|
84
|
+
|
|
80
85
|
### 🏗️ Clean Architecture Pattern: Dedicated Data Access Layer (`NewsApi`)
|
|
81
86
|
|
|
82
87
|
In enterprise Next.js applications, avoid calling SDK clients directly inside UI components. Instead, encapsulate CMS queries, generics, normalizers, and caching logic inside a dedicated Data Access Layer (`services/newsApi.ts`).
|
|
@@ -221,12 +226,8 @@ export class NewsApi {
|
|
|
221
226
|
}
|
|
222
227
|
```
|
|
223
228
|
|
|
224
|
-
---
|
|
225
|
-
|
|
226
229
|
#### Step 2: Consume `NewsApi` in Next.js Server Components (`app/news/page.tsx`)
|
|
227
230
|
|
|
228
|
-
Notice how clean the UI layer becomes! No low-level SDK parameters, headers, or raw hashes leaked into your views:
|
|
229
|
-
|
|
230
231
|
```tsx
|
|
231
232
|
// app/news/page.tsx (Server Component)
|
|
232
233
|
import { NewsApi } from '@/services/newsApi';
|
|
@@ -276,237 +277,242 @@ export default async function NewsPage({ searchParams }: NewsPageProps) {
|
|
|
276
277
|
|
|
277
278
|
---
|
|
278
279
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
```tsx
|
|
282
|
-
// app/news/[id]/page.tsx (Server Component)
|
|
283
|
-
import { NewsApi } from '@/services/newsApi';
|
|
284
|
-
import { notFound } from 'next/navigation';
|
|
285
|
-
|
|
286
|
-
interface NewsDetailPageProps {
|
|
287
|
-
params: { id: string };
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export default async function NewsDetailPage({ params }: NewsDetailPageProps) {
|
|
291
|
-
const news = await NewsApi.getNewsById(Number(params.id));
|
|
292
|
-
|
|
293
|
-
if (!news) {
|
|
294
|
-
notFound();
|
|
295
|
-
}
|
|
280
|
+
## Deep Dive 2: CMS Product Module (RAD API Swagger)
|
|
296
281
|
|
|
297
|
-
|
|
298
|
-
<article className="max-w-3xl mx-auto py-10 px-4">
|
|
299
|
-
{news.coverImage && (
|
|
300
|
-
<img
|
|
301
|
-
src={news.coverImage}
|
|
302
|
-
alt={news.title}
|
|
303
|
-
className="w-full h-72 object-cover rounded-xl mb-6"
|
|
304
|
-
/>
|
|
305
|
-
)}
|
|
306
|
-
<h1 className="text-3xl font-bold mb-3">{news.title}</h1>
|
|
307
|
-
<p className="text-sm text-gray-500 mb-6">{news.readTime} min read</p>
|
|
308
|
-
<div className="prose lg:prose-lg" dangerouslySetInnerHTML={{ __html: news.body }} />
|
|
309
|
-
</article>
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
```
|
|
282
|
+
The CMS Product module connects directly to POD RAD API endpoints ([Swagger documentation](https://rad-sandbox.sandpod.ir/api/documentation?tag=product)). It handles products with prices, discounts, barcodes, dynamic `metadata.product` field formatting, and batch management.
|
|
313
283
|
|
|
314
|
-
|
|
284
|
+
### Accessing Products
|
|
315
285
|
|
|
316
|
-
|
|
286
|
+
You can access product methods either via `podSdk.product` or `podSdk.cms.products`:
|
|
317
287
|
|
|
318
288
|
```typescript
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
import { NewsApi } from '@/services/newsApi';
|
|
323
|
-
import { revalidatePath } from 'next/cache';
|
|
324
|
-
|
|
325
|
-
export async function publishArticle(formData: FormData) {
|
|
326
|
-
const title = formData.get('title') as string;
|
|
327
|
-
const lead = formData.get('lead') as string;
|
|
328
|
-
const body = formData.get('body') as string;
|
|
329
|
-
const imageHash = formData.get('imageHash') as string;
|
|
330
|
-
|
|
331
|
-
const result = await NewsApi.publishNews({
|
|
332
|
-
title,
|
|
333
|
-
lead,
|
|
334
|
-
body,
|
|
335
|
-
imageHash,
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
revalidatePath('/news');
|
|
339
|
-
return { success: !result.hasError };
|
|
340
|
-
}
|
|
289
|
+
// Both point to the same CmsProductService instance
|
|
290
|
+
podSdk.product.getProducts(...);
|
|
291
|
+
podSdk.cms.products.getProducts(...);
|
|
341
292
|
```
|
|
342
293
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
## Deep Dive: CustomPost & Generic Typed Repositories
|
|
346
|
-
|
|
347
|
-
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
348
|
-
|
|
349
|
-
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
350
|
-
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
351
|
-
|
|
352
|
-
### 1. Direct Generic Method Usage
|
|
294
|
+
### 🛍️ Clean Architecture Pattern: Product Data Access Layer (`ProductsApi`)
|
|
353
295
|
|
|
354
296
|
```typescript
|
|
355
|
-
//
|
|
297
|
+
// services/productsApi.ts
|
|
356
298
|
import { podSdk } from '@/lib/pod';
|
|
357
299
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
300
|
+
// 1. Raw formatted product metadata (T)
|
|
301
|
+
export interface LaptopFormatted {
|
|
302
|
+
cpu: string;
|
|
303
|
+
ram: string;
|
|
304
|
+
storage: string;
|
|
305
|
+
displayPhoto: string; // Auto-transformed from hash to Podspace CDN URL
|
|
364
306
|
}
|
|
365
307
|
|
|
366
|
-
//
|
|
367
|
-
export
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
metadata: { views: 100 }, // Partial match or query
|
|
371
|
-
size: 20,
|
|
372
|
-
offset: 0,
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// response.result[i].item.metadata is strongly typed as BlogPostMeta
|
|
376
|
-
return response.result.map((r) => r.item.metadata);
|
|
308
|
+
// 2. Computed / normalized presentation fields (P)
|
|
309
|
+
export interface LaptopNormalized {
|
|
310
|
+
finalPrice: number;
|
|
311
|
+
discountBadge: string;
|
|
377
312
|
}
|
|
378
313
|
|
|
379
|
-
//
|
|
380
|
-
export
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
content: meta.title,
|
|
384
|
-
metadata: meta, // Strongly typed
|
|
385
|
-
enable: true,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
### 2. High-Level Generic CRUD Repository (`createCrud<DataType>`)
|
|
391
|
-
|
|
392
|
-
Create an ORM-like typed repository for any entity type and expose it through a dedicated service layer:
|
|
393
|
-
|
|
394
|
-
#### Step 1: Create the Products Repository & API (`services/productsApi.ts`)
|
|
395
|
-
|
|
396
|
-
```typescript
|
|
397
|
-
// services/productsApi.ts
|
|
398
|
-
import { podSdk } from '@/lib/pod';
|
|
399
|
-
|
|
400
|
-
export interface ProductData {
|
|
401
|
-
title: string;
|
|
402
|
-
sku: string;
|
|
314
|
+
// 3. Clean Domain Product Model
|
|
315
|
+
export interface ProductItemModel {
|
|
316
|
+
entityId: number;
|
|
317
|
+
name: string;
|
|
403
318
|
price: number;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
319
|
+
finalPrice: number;
|
|
320
|
+
discountBadge: string;
|
|
321
|
+
photoUrl: string;
|
|
322
|
+
specs: {
|
|
323
|
+
cpu: string;
|
|
324
|
+
ram: string;
|
|
325
|
+
storage: string;
|
|
326
|
+
};
|
|
407
327
|
}
|
|
408
328
|
|
|
409
|
-
// 1. Instantiate typed CRUD service for "product_entity"
|
|
410
|
-
const productCrud = podSdk.createCrud<ProductData>({
|
|
411
|
-
name: 'product_entity',
|
|
412
|
-
type: 'commerce',
|
|
413
|
-
detailedType: 'gadgets',
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
// 2. Encapsulate business queries and domain operations
|
|
417
329
|
export class ProductsApi {
|
|
418
|
-
|
|
419
|
-
* Retrieves all products formatted as clean domain objects
|
|
420
|
-
*/
|
|
421
|
-
public static async getProducts(): Promise<ProductData[]> {
|
|
422
|
-
const result = await productCrud.getAll();
|
|
423
|
-
return result.result.map((r) => r.item.metadata.data);
|
|
424
|
-
}
|
|
330
|
+
private static readonly PRODUCT_TYPE = 'laptops';
|
|
425
331
|
|
|
426
332
|
/**
|
|
427
|
-
*
|
|
333
|
+
* Fetches published products with price & discount filters
|
|
428
334
|
*/
|
|
429
|
-
public static async
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
335
|
+
public static async getProducts(params: {
|
|
336
|
+
fromPrice?: number;
|
|
337
|
+
toPrice?: number;
|
|
338
|
+
barcode?: string;
|
|
339
|
+
page?: number;
|
|
340
|
+
pageSize?: number;
|
|
341
|
+
} = {}): Promise<ProductItemModel[]> {
|
|
342
|
+
const { fromPrice, toPrice, barcode, page = 1, pageSize = 20 } = params;
|
|
343
|
+
const offset = (page - 1) * pageSize;
|
|
433
344
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
345
|
+
const response = await podSdk.product.getProducts<LaptopFormatted, LaptopNormalized>(
|
|
346
|
+
{
|
|
347
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
348
|
+
fromPrice,
|
|
349
|
+
toPrice,
|
|
350
|
+
barcode,
|
|
351
|
+
size: pageSize,
|
|
352
|
+
offset,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
normalizer: (item) => {
|
|
356
|
+
const discount = item.discount || 0;
|
|
357
|
+
const finalPrice = item.price ? item.price * (1 - discount / 100) : 0;
|
|
358
|
+
return {
|
|
359
|
+
finalPrice,
|
|
360
|
+
discountBadge: discount > 0 ? `${discount}% OFF` : '',
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
revalidate: 3600, // Next.js ISR: cache for 1 hour
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
if (response.hasError || !Array.isArray(response.result)) {
|
|
368
|
+
return [];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return response.result.map((item) => ({
|
|
372
|
+
entityId: item.entityId,
|
|
373
|
+
name: item.name || '',
|
|
374
|
+
price: item.price || 0,
|
|
375
|
+
finalPrice: item.__normalized.finalPrice,
|
|
376
|
+
discountBadge: item.__normalized.discountBadge,
|
|
377
|
+
photoUrl: item.formatted.displayPhoto,
|
|
378
|
+
specs: {
|
|
379
|
+
cpu: item.formatted.cpu,
|
|
380
|
+
ram: item.formatted.ram,
|
|
381
|
+
storage: item.formatted.storage,
|
|
382
|
+
},
|
|
383
|
+
}));
|
|
442
384
|
}
|
|
443
385
|
|
|
444
386
|
/**
|
|
445
|
-
*
|
|
387
|
+
* Retrieves single product by entityId
|
|
446
388
|
*/
|
|
447
|
-
public static async
|
|
448
|
-
|
|
389
|
+
public static async getProductById(entityId: number) {
|
|
390
|
+
const res = await podSdk.product.getProductByEntityId<LaptopFormatted, LaptopNormalized>({
|
|
391
|
+
entityId,
|
|
392
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
393
|
+
});
|
|
394
|
+
return res.result?.[0] || null;
|
|
449
395
|
}
|
|
450
396
|
|
|
451
397
|
/**
|
|
452
|
-
*
|
|
398
|
+
* Look up product by barcode scanner
|
|
453
399
|
*/
|
|
454
|
-
public static async
|
|
455
|
-
return
|
|
400
|
+
public static async getProductByBarcode(barcode: string) {
|
|
401
|
+
return podSdk.product.getProductByBarcode<LaptopFormatted>(barcode);
|
|
456
402
|
}
|
|
457
403
|
|
|
458
404
|
/**
|
|
459
|
-
*
|
|
405
|
+
* Adds and publishes a new product
|
|
460
406
|
*/
|
|
461
|
-
public static async
|
|
462
|
-
|
|
407
|
+
public static async createProduct(data: {
|
|
408
|
+
name: string;
|
|
409
|
+
price: number;
|
|
410
|
+
cpu: string;
|
|
411
|
+
ram: string;
|
|
412
|
+
storage: string;
|
|
413
|
+
photoHash: string;
|
|
414
|
+
}) {
|
|
415
|
+
return podSdk.product.addProduct({
|
|
416
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
417
|
+
body: {
|
|
418
|
+
name: data.name,
|
|
419
|
+
enable: true,
|
|
420
|
+
fieldCode: ['cpu', 'ram', 'storage', 'displayPhoto', 'price'],
|
|
421
|
+
fieldValue: [data.cpu, data.ram, data.storage, data.photoHash, data.price],
|
|
422
|
+
},
|
|
423
|
+
});
|
|
463
424
|
}
|
|
464
425
|
}
|
|
465
426
|
```
|
|
466
427
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
####
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
428
|
+
### CMS Methods Reference
|
|
429
|
+
|
|
430
|
+
#### Content Methods (`podSdk.cms`)
|
|
431
|
+
|
|
432
|
+
| Method | HTTP | Path | Description |
|
|
433
|
+
| :--- | :--- | :--- | :--- |
|
|
434
|
+
| `getContent<T, P>(params, opts)` | `GET` | `/api/core/contents/enable` | Get published enabled contents with generic typing. |
|
|
435
|
+
| `getAllContents<T, P>(params, opts)` | `GET` | `/api/core/contents` | Get contents with managing credentials (`Access-Token`). |
|
|
436
|
+
| `getMyContents<T, P>(params, opts)` | `GET` | `/api/core/contents/my` | Get current user/client contents. |
|
|
437
|
+
| `getContentsByType<T, P>(typeId, params)` | `GET` | `/api/core/contents/{contentTypeUniqueId}` | Get contents by ContentType. |
|
|
438
|
+
| `getContentByEntityId<T, P>(params)` | `GET` | `/api/core/contents/enable/{entityId}` | Get single published content item. |
|
|
439
|
+
| `getContentByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/contents/{uniqueId}/byContentUniqueId/enable` | Get content by uniqueId. |
|
|
440
|
+
| `searchContent<T, P>(params)` | `GET` | `/api/core/contents/search` | Search published contents by keyword. |
|
|
441
|
+
| `addContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}/add-publish` | Add and publish content item. |
|
|
442
|
+
| `createContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}` | Create unpublished/draft content. |
|
|
443
|
+
| `editContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}/edit-publish` | Update and publish content item. |
|
|
444
|
+
| `updateContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}` | Update content item. |
|
|
445
|
+
| `patchContent<T, P>(entityId, params)` | `PATCH` | `/api/core/contents/{typeId}/{entityId}` | Partial update content. |
|
|
446
|
+
| `publishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/publish/{entityId}` | Publish single content item. |
|
|
447
|
+
| `unpublishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/unpublish/{entityId}` | Unpublish single content item. |
|
|
448
|
+
| `batchPublish(params)` | `PUT` | `/api/core/contents/{typeId}/publish` | Batch publish multiple contents. |
|
|
449
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/contents/{typeId}/unpublish` | Batch unpublish multiple contents. |
|
|
450
|
+
| `archiveContent(params)` | `POST` | `/api/core/contents/{typeId}/archive` | Archive content items. |
|
|
451
|
+
| `unarchiveContent(params)` | `POST` | `/api/core/contents/{typeId}/unarchive` | Unarchive content items. |
|
|
452
|
+
| `getArchivedContents(params)` | `GET` | `/api/core/contents/archive` | Get archived contents list. |
|
|
453
|
+
| `getDrafts(params)` | `GET` | `/api/core/contents/draft` | Get drafts list. |
|
|
454
|
+
| `createDraft(params)` | `POST` | `/api/core/contents/{typeId}/draft` | Create a draft. |
|
|
455
|
+
| `getComments(typeId, entityId)` | `GET` | `/api/core/contents/{typeId}/comments/{entityId}` | Get comments on content. |
|
|
456
|
+
| `getLikes(uniqueId, entityId)` | `GET` | `/api/core/contents/{uniqueId}/like/{entityId}` | Get likes of content item. |
|
|
457
|
+
| `getCategories(params)` | `GET` | `/api/core/tags/root/tree/enable` | Get category/tag tree. |
|
|
458
|
+
| `timelineSearch(query)` | `GET` | `/api/core/contents/ai/timeline-search/enable` | Advanced AI timeline search. |
|
|
459
|
+
|
|
460
|
+
#### Product Methods (`podSdk.product` / `podSdk.cms.products`)
|
|
461
|
+
|
|
462
|
+
| Method | HTTP | Path | Description |
|
|
463
|
+
| :--- | :--- | :--- | :--- |
|
|
464
|
+
| `getProducts<T, P>(params, opts)` | `GET` | `/api/core/products/enable` | Get enabled published products with price/discount filters. |
|
|
465
|
+
| `getAllProducts<T, P>(params, opts)` | `GET` | `/api/core/products` | Get products with provider credentials (`Access-Token`). |
|
|
466
|
+
| `getManageProducts<T, P>(params, opts)`| `GET` | `/api/core/products/manage` | Get products for manager view. |
|
|
467
|
+
| `getProductsByType<T, P>(typeId, params)`| `GET` | `/api/core/products/{productTypeUniqueId}` | Get products by ProductType. |
|
|
468
|
+
| `getProductByEntityId<T, P>(params)` | `GET` | `/api/core/products/enable/{entityId}` | Get single product by entityId. |
|
|
469
|
+
| `getProductByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/products/{uniqueId}/byProductUniqueId/enable` | Get product by uniqueId. |
|
|
470
|
+
| `getProductByBarcode<T, P>(barcode)` | `GET` | `/api/core/products/barcode/enable` | Lookup products matching a barcode. |
|
|
471
|
+
| `searchProducts<T, P>(params)` | `GET` | `/api/core/products/enable/search` | Search published products. |
|
|
472
|
+
| `addProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}/add-publish` | Create and publish new product. |
|
|
473
|
+
| `createProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}` | Create draft/unpublished product. |
|
|
474
|
+
| `editProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}/edit-publish` | Edit and publish existing product. |
|
|
475
|
+
| `updateProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}` | Update existing product. |
|
|
476
|
+
| `patchProduct<T, P>(entityId, params)` | `PATCH` | `/api/core/products/{typeId}/{entityId}` | Partial update product. |
|
|
477
|
+
| `publishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/publish/{entityId}` | Publish product. |
|
|
478
|
+
| `unpublishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/unpublish/{entityId}` | Unpublish product. |
|
|
479
|
+
| `batchPublish(params)` | `PUT` | `/api/core/products/publish` | Batch publish multiple products. |
|
|
480
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/products/unpublish` | Batch unpublish multiple products. |
|
|
481
|
+
| `archiveProduct(params)` | `POST` | `/api/core/products/archive` | Archive products. |
|
|
482
|
+
| `unarchiveProduct(params)` | `POST` | `/api/core/products/unarchive` | Unarchive products. |
|
|
483
|
+
| `getArchivedProducts(params)` | `GET` | `/api/core/products/archive` | Get archived products list. |
|
|
484
|
+
| `timelineSearch(query)` | `GET` | `/api/core/products/ai/timeline-search/enable` | AI timeline search on products. |
|
|
474
485
|
|
|
475
|
-
|
|
476
|
-
const products = await ProductsApi.getProducts();
|
|
486
|
+
---
|
|
477
487
|
|
|
478
|
-
|
|
479
|
-
<main className="container mx-auto py-8 px-4">
|
|
480
|
-
<h1 className="text-2xl font-bold mb-6">Products Catalog</h1>
|
|
481
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
482
|
-
{products.map((p) => (
|
|
483
|
-
<div key={p.sku} className="border p-4 rounded-lg shadow-sm">
|
|
484
|
-
<h2 className="font-semibold text-lg">{p.title}</h2>
|
|
485
|
-
<p className="text-gray-500 text-sm">SKU: {p.sku}</p>
|
|
486
|
-
<p className="text-blue-600 font-bold mt-2">${p.price.toLocaleString()}</p>
|
|
487
|
-
</div>
|
|
488
|
-
))}
|
|
489
|
-
</div>
|
|
490
|
-
</main>
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
```
|
|
488
|
+
## Deep Dive 3: CustomPost & Generic Typed Repositories
|
|
494
489
|
|
|
495
|
-
|
|
490
|
+
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
496
491
|
|
|
497
|
-
|
|
492
|
+
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
493
|
+
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
498
494
|
|
|
499
495
|
```typescript
|
|
500
|
-
//
|
|
501
|
-
|
|
496
|
+
// lib/blog.ts
|
|
497
|
+
import { podSdk } from '@/lib/pod';
|
|
502
498
|
|
|
503
|
-
|
|
504
|
-
|
|
499
|
+
export interface BlogPostMeta {
|
|
500
|
+
slug: string;
|
|
501
|
+
title: string;
|
|
502
|
+
summary: string;
|
|
503
|
+
views: number;
|
|
504
|
+
}
|
|
505
505
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
// Search posts with typed metadata query
|
|
507
|
+
export async function getPublishedPosts() {
|
|
508
|
+
const response = await podSdk.customPost.searchTimelineByMetadata<BlogPostMeta>({
|
|
509
|
+
entityName: 'blog_post',
|
|
510
|
+
metadata: { views: 100 },
|
|
511
|
+
size: 20,
|
|
512
|
+
offset: 0,
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
return response.result.map((r) => r.item.metadata);
|
|
510
516
|
}
|
|
511
517
|
```
|
|
512
518
|
|
|
@@ -514,7 +520,7 @@ export async function addProduct(data: { title: string; sku: string; price: numb
|
|
|
514
520
|
|
|
515
521
|
## Other POD Microservices
|
|
516
522
|
|
|
517
|
-
###
|
|
523
|
+
### 4. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
518
524
|
|
|
519
525
|
```typescript
|
|
520
526
|
// app/actions/auth.ts
|
|
@@ -524,7 +530,6 @@ import { podSdk } from '@/lib/pod';
|
|
|
524
530
|
|
|
525
531
|
// Step 1: Handshake and send OTP
|
|
526
532
|
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
527
|
-
// Handshake to obtain keyId
|
|
528
533
|
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
529
534
|
if (handshake.hasError || !handshake.result?.keyId) {
|
|
530
535
|
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
@@ -545,7 +550,6 @@ export async function verifyOtp(authorization: string, phoneNumber: string, code
|
|
|
545
550
|
throw new Error(verify.message || 'OTP verification failed');
|
|
546
551
|
}
|
|
547
552
|
|
|
548
|
-
// Exchange authorization code for JWT tokens
|
|
549
553
|
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
550
554
|
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
551
555
|
}
|
|
@@ -559,7 +563,7 @@ export async function getProfile(accessToken: string) {
|
|
|
559
563
|
|
|
560
564
|
---
|
|
561
565
|
|
|
562
|
-
###
|
|
566
|
+
### 5. Podspace (File Uploads & CDN URLs)
|
|
563
567
|
|
|
564
568
|
```typescript
|
|
565
569
|
// app/actions/upload.ts
|
|
@@ -580,7 +584,7 @@ export async function uploadFile(formData: FormData) {
|
|
|
580
584
|
|
|
581
585
|
---
|
|
582
586
|
|
|
583
|
-
###
|
|
587
|
+
### 6. Notification (SMS Delivery)
|
|
584
588
|
|
|
585
589
|
```typescript
|
|
586
590
|
// app/actions/notify.ts
|
|
@@ -598,7 +602,7 @@ export async function sendSms(phoneNumber: string, text: string) {
|
|
|
598
602
|
|
|
599
603
|
---
|
|
600
604
|
|
|
601
|
-
###
|
|
605
|
+
### 7. Social (Comments & Likes)
|
|
602
606
|
|
|
603
607
|
```typescript
|
|
604
608
|
// app/actions/social.ts
|
|
@@ -620,7 +624,7 @@ export async function likePost(postId: number) {
|
|
|
620
624
|
|
|
621
625
|
---
|
|
622
626
|
|
|
623
|
-
###
|
|
627
|
+
### 8. IUMS (Identity & University Management Service)
|
|
624
628
|
|
|
625
629
|
```typescript
|
|
626
630
|
// app/actions/iums.ts
|