@rezamirzapour/pod-sdk 1.0.2 → 1.0.4
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 +266 -205
- package/dist/index.d.mts +849 -51
- package/dist/index.d.ts +849 -51
- package/dist/index.js +1295 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1294 -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,18 @@
|
|
|
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
|
+
| **CMS Tags & Tree** | `sdk.tags` / `sdk.cms.tags` | `tags` | Manage tag categories and hierarchical tag trees (nodes, parent updates, ancestors, codes). |
|
|
32
|
+
| **CustomPost** | `sdk.customPost` | - | Search timeline by metadata `<T>`, custom post CRUD, and high-level typed repository. |
|
|
33
|
+
| **SSO** | `sdk.sso` | - | OAuth2 handshake, OTP dispatch with digital RSA signature, OTP verify, token generation, and user profile. |
|
|
34
|
+
| **Podspace** | `sdk.podspace` | - | File upload (FormData) and public/private download URL resolution. |
|
|
35
|
+
| **Podform** | `sdk.podform` | - | Survey & form response submission, question/form structure retrieval. |
|
|
36
|
+
| **Notification** | `sdk.notification` | - | SMS delivery and bulk messaging with tracking. |
|
|
37
|
+
| **Social** | `sdk.social` | - | User comments, reactions (likes/dislikes), rates, and social post interactions. |
|
|
38
|
+
| **IUMS** | `sdk.iums` | - | Student and user identity inspection by national code or student ID. |
|
|
36
39
|
|
|
37
40
|
---
|
|
38
41
|
|
|
@@ -73,10 +76,13 @@ export const podSdk = createPodSdk({
|
|
|
73
76
|
|
|
74
77
|
---
|
|
75
78
|
|
|
76
|
-
## Deep Dive: CMS &
|
|
79
|
+
## Deep Dive 1: CMS Content & Data Formatting
|
|
77
80
|
|
|
78
81
|
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
82
|
|
|
83
|
+
> [!NOTE]
|
|
84
|
+
> `getContent2` has been removed. For authenticated/management content retrieval (using `Access-Token`), call `podSdk.cms.getAllContents()` or pass headers to `getContent()`.
|
|
85
|
+
|
|
80
86
|
### 🏗️ Clean Architecture Pattern: Dedicated Data Access Layer (`NewsApi`)
|
|
81
87
|
|
|
82
88
|
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 +227,8 @@ export class NewsApi {
|
|
|
221
227
|
}
|
|
222
228
|
```
|
|
223
229
|
|
|
224
|
-
---
|
|
225
|
-
|
|
226
230
|
#### Step 2: Consume `NewsApi` in Next.js Server Components (`app/news/page.tsx`)
|
|
227
231
|
|
|
228
|
-
Notice how clean the UI layer becomes! No low-level SDK parameters, headers, or raw hashes leaked into your views:
|
|
229
|
-
|
|
230
232
|
```tsx
|
|
231
233
|
// app/news/page.tsx (Server Component)
|
|
232
234
|
import { NewsApi } from '@/services/newsApi';
|
|
@@ -276,237 +278,298 @@ export default async function NewsPage({ searchParams }: NewsPageProps) {
|
|
|
276
278
|
|
|
277
279
|
---
|
|
278
280
|
|
|
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
|
-
}
|
|
281
|
+
## Deep Dive 2: CMS Product Module (RAD API Swagger)
|
|
296
282
|
|
|
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
|
-
```
|
|
283
|
+
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
284
|
|
|
314
|
-
|
|
285
|
+
### Accessing Products
|
|
315
286
|
|
|
316
|
-
|
|
287
|
+
You can access product methods either via `podSdk.product` or `podSdk.cms.products`:
|
|
317
288
|
|
|
318
289
|
```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
|
-
}
|
|
290
|
+
// Both point to the same CmsProductService instance
|
|
291
|
+
podSdk.product.getProducts(...);
|
|
292
|
+
podSdk.cms.products.getProducts(...);
|
|
341
293
|
```
|
|
342
294
|
|
|
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
|
|
295
|
+
### 🛍️ Clean Architecture Pattern: Product Data Access Layer (`ProductsApi`)
|
|
353
296
|
|
|
354
297
|
```typescript
|
|
355
|
-
//
|
|
298
|
+
// services/productsApi.ts
|
|
356
299
|
import { podSdk } from '@/lib/pod';
|
|
357
300
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
301
|
+
// 1. Raw formatted product metadata (T)
|
|
302
|
+
export interface LaptopFormatted {
|
|
303
|
+
cpu: string;
|
|
304
|
+
ram: string;
|
|
305
|
+
storage: string;
|
|
306
|
+
displayPhoto: string; // Auto-transformed from hash to Podspace CDN URL
|
|
364
307
|
}
|
|
365
308
|
|
|
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);
|
|
309
|
+
// 2. Computed / normalized presentation fields (P)
|
|
310
|
+
export interface LaptopNormalized {
|
|
311
|
+
finalPrice: number;
|
|
312
|
+
discountBadge: string;
|
|
377
313
|
}
|
|
378
314
|
|
|
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;
|
|
315
|
+
// 3. Clean Domain Product Model
|
|
316
|
+
export interface ProductItemModel {
|
|
317
|
+
entityId: number;
|
|
318
|
+
name: string;
|
|
403
319
|
price: number;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
320
|
+
finalPrice: number;
|
|
321
|
+
discountBadge: string;
|
|
322
|
+
photoUrl: string;
|
|
323
|
+
specs: {
|
|
324
|
+
cpu: string;
|
|
325
|
+
ram: string;
|
|
326
|
+
storage: string;
|
|
327
|
+
};
|
|
407
328
|
}
|
|
408
329
|
|
|
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
330
|
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
|
-
}
|
|
331
|
+
private static readonly PRODUCT_TYPE = 'laptops';
|
|
425
332
|
|
|
426
333
|
/**
|
|
427
|
-
*
|
|
334
|
+
* Fetches published products with price & discount filters
|
|
428
335
|
*/
|
|
429
|
-
public static async
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
336
|
+
public static async getProducts(params: {
|
|
337
|
+
fromPrice?: number;
|
|
338
|
+
toPrice?: number;
|
|
339
|
+
barcode?: string;
|
|
340
|
+
page?: number;
|
|
341
|
+
pageSize?: number;
|
|
342
|
+
} = {}): Promise<ProductItemModel[]> {
|
|
343
|
+
const { fromPrice, toPrice, barcode, page = 1, pageSize = 20 } = params;
|
|
344
|
+
const offset = (page - 1) * pageSize;
|
|
433
345
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
346
|
+
const response = await podSdk.product.getProducts<LaptopFormatted, LaptopNormalized>(
|
|
347
|
+
{
|
|
348
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
349
|
+
fromPrice,
|
|
350
|
+
toPrice,
|
|
351
|
+
barcode,
|
|
352
|
+
size: pageSize,
|
|
353
|
+
offset,
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
normalizer: (item) => {
|
|
357
|
+
const discount = item.discount || 0;
|
|
358
|
+
const finalPrice = item.price ? item.price * (1 - discount / 100) : 0;
|
|
359
|
+
return {
|
|
360
|
+
finalPrice,
|
|
361
|
+
discountBadge: discount > 0 ? `${discount}% OFF` : '',
|
|
362
|
+
};
|
|
363
|
+
},
|
|
364
|
+
revalidate: 3600, // Next.js ISR: cache for 1 hour
|
|
365
|
+
}
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
if (response.hasError || !Array.isArray(response.result)) {
|
|
369
|
+
return [];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return response.result.map((item) => ({
|
|
373
|
+
entityId: item.entityId,
|
|
374
|
+
name: item.name || '',
|
|
375
|
+
price: item.price || 0,
|
|
376
|
+
finalPrice: item.__normalized.finalPrice,
|
|
377
|
+
discountBadge: item.__normalized.discountBadge,
|
|
378
|
+
photoUrl: item.formatted.displayPhoto,
|
|
379
|
+
specs: {
|
|
380
|
+
cpu: item.formatted.cpu,
|
|
381
|
+
ram: item.formatted.ram,
|
|
382
|
+
storage: item.formatted.storage,
|
|
383
|
+
},
|
|
384
|
+
}));
|
|
442
385
|
}
|
|
443
386
|
|
|
444
387
|
/**
|
|
445
|
-
*
|
|
388
|
+
* Retrieves single product by entityId
|
|
446
389
|
*/
|
|
447
|
-
public static async
|
|
448
|
-
|
|
390
|
+
public static async getProductById(entityId: number) {
|
|
391
|
+
const res = await podSdk.product.getProductByEntityId<LaptopFormatted, LaptopNormalized>({
|
|
392
|
+
entityId,
|
|
393
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
394
|
+
});
|
|
395
|
+
return res.result?.[0] || null;
|
|
449
396
|
}
|
|
450
397
|
|
|
451
398
|
/**
|
|
452
|
-
*
|
|
399
|
+
* Look up product by barcode scanner
|
|
453
400
|
*/
|
|
454
|
-
public static async
|
|
455
|
-
return
|
|
401
|
+
public static async getProductByBarcode(barcode: string) {
|
|
402
|
+
return podSdk.product.getProductByBarcode<LaptopFormatted>(barcode);
|
|
456
403
|
}
|
|
457
404
|
|
|
458
405
|
/**
|
|
459
|
-
*
|
|
406
|
+
* Adds and publishes a new product
|
|
460
407
|
*/
|
|
461
|
-
public static async
|
|
462
|
-
|
|
408
|
+
public static async createProduct(data: {
|
|
409
|
+
name: string;
|
|
410
|
+
price: number;
|
|
411
|
+
cpu: string;
|
|
412
|
+
ram: string;
|
|
413
|
+
storage: string;
|
|
414
|
+
photoHash: string;
|
|
415
|
+
}) {
|
|
416
|
+
return podSdk.product.addProduct({
|
|
417
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
418
|
+
body: {
|
|
419
|
+
name: data.name,
|
|
420
|
+
enable: true,
|
|
421
|
+
fieldCode: ['cpu', 'ram', 'storage', 'displayPhoto', 'price'],
|
|
422
|
+
fieldValue: [data.cpu, data.ram, data.storage, data.photoHash, data.price],
|
|
423
|
+
},
|
|
424
|
+
});
|
|
463
425
|
}
|
|
464
426
|
}
|
|
465
427
|
```
|
|
466
428
|
|
|
429
|
+
### CMS Methods Reference
|
|
430
|
+
|
|
431
|
+
#### Content Methods (`podSdk.cms`)
|
|
432
|
+
|
|
433
|
+
| Method | HTTP | Path | Description |
|
|
434
|
+
| :--- | :--- | :--- | :--- |
|
|
435
|
+
| `getContent<T, P>(params, opts)` | `GET` | `/api/core/contents/enable` | Get published enabled contents with generic typing. |
|
|
436
|
+
| `getAllContents<T, P>(params, opts)` | `GET` | `/api/core/contents` | Get contents with managing credentials (`Access-Token`). |
|
|
437
|
+
| `getMyContents<T, P>(params, opts)` | `GET` | `/api/core/contents/my` | Get current user/client contents. |
|
|
438
|
+
| `getContentsByType<T, P>(typeId, params)` | `GET` | `/api/core/contents/{contentTypeUniqueId}` | Get contents by ContentType. |
|
|
439
|
+
| `getContentByEntityId<T, P>(params)` | `GET` | `/api/core/contents/enable/{entityId}` | Get single published content item. |
|
|
440
|
+
| `getContentByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/contents/{uniqueId}/byContentUniqueId/enable` | Get content by uniqueId. |
|
|
441
|
+
| `searchContent<T, P>(params)` | `GET` | `/api/core/contents/search` | Search published contents by keyword. |
|
|
442
|
+
| `addContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}/add-publish` | Add and publish content item. |
|
|
443
|
+
| `createContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}` | Create unpublished/draft content. |
|
|
444
|
+
| `editContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}/edit-publish` | Update and publish content item. |
|
|
445
|
+
| `updateContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}` | Update content item. |
|
|
446
|
+
| `patchContent<T, P>(entityId, params)` | `PATCH` | `/api/core/contents/{typeId}/{entityId}` | Partial update content. |
|
|
447
|
+
| `publishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/publish/{entityId}` | Publish single content item. |
|
|
448
|
+
| `unpublishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/unpublish/{entityId}` | Unpublish single content item. |
|
|
449
|
+
| `batchPublish(params)` | `PUT` | `/api/core/contents/{typeId}/publish` | Batch publish multiple contents. |
|
|
450
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/contents/{typeId}/unpublish` | Batch unpublish multiple contents. |
|
|
451
|
+
| `archiveContent(params)` | `POST` | `/api/core/contents/{typeId}/archive` | Archive content items. |
|
|
452
|
+
| `unarchiveContent(params)` | `POST` | `/api/core/contents/{typeId}/unarchive` | Unarchive content items. |
|
|
453
|
+
| `getArchivedContents(params)` | `GET` | `/api/core/contents/archive` | Get archived contents list. |
|
|
454
|
+
| `getDrafts(params)` | `GET` | `/api/core/contents/draft` | Get drafts list. |
|
|
455
|
+
| `createDraft(params)` | `POST` | `/api/core/contents/{typeId}/draft` | Create a draft. |
|
|
456
|
+
| `getComments(typeId, entityId)` | `GET` | `/api/core/contents/{typeId}/comments/{entityId}` | Get comments on content. |
|
|
457
|
+
| `getLikes(uniqueId, entityId)` | `GET` | `/api/core/contents/{uniqueId}/like/{entityId}` | Get likes of content item. |
|
|
458
|
+
| `getCategories(params)` | `GET` | `/api/core/tags/root/tree/enable` | Get category/tag tree. |
|
|
459
|
+
| `timelineSearch(query)` | `GET` | `/api/core/contents/ai/timeline-search/enable` | Advanced AI timeline search. |
|
|
460
|
+
|
|
461
|
+
#### Product Methods (`podSdk.product` / `podSdk.cms.products`)
|
|
462
|
+
|
|
463
|
+
| Method | HTTP | Path | Description |
|
|
464
|
+
| :--- | :--- | :--- | :--- |
|
|
465
|
+
| `getProducts<T, P>(params, opts)` | `GET` | `/api/core/products/enable` | Get enabled published products with price/discount filters. |
|
|
466
|
+
| `getAllProducts<T, P>(params, opts)` | `GET` | `/api/core/products` | Get products with provider credentials (`Access-Token`). |
|
|
467
|
+
| `getManageProducts<T, P>(params, opts)`| `GET` | `/api/core/products/manage` | Get products for manager view. |
|
|
468
|
+
| `getProductsByType<T, P>(typeId, params)`| `GET` | `/api/core/products/{productTypeUniqueId}` | Get products by ProductType. |
|
|
469
|
+
| `getProductByEntityId<T, P>(params)` | `GET` | `/api/core/products/enable/{entityId}` | Get single product by entityId. |
|
|
470
|
+
| `getProductByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/products/{uniqueId}/byProductUniqueId/enable` | Get product by uniqueId. |
|
|
471
|
+
| `getProductByBarcode<T, P>(barcode)` | `GET` | `/api/core/products/barcode/enable` | Lookup products matching a barcode. |
|
|
472
|
+
| `searchProducts<T, P>(params)` | `GET` | `/api/core/products/enable/search` | Search published products. |
|
|
473
|
+
| `addProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}/add-publish` | Create and publish new product. |
|
|
474
|
+
| `createProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}` | Create draft/unpublished product. |
|
|
475
|
+
| `editProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}/edit-publish` | Edit and publish existing product. |
|
|
476
|
+
| `updateProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}` | Update existing product. |
|
|
477
|
+
| `patchProduct<T, P>(entityId, params)` | `PATCH` | `/api/core/products/{typeId}/{entityId}` | Partial update product. |
|
|
478
|
+
| `publishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/publish/{entityId}` | Publish product. |
|
|
479
|
+
| `unpublishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/unpublish/{entityId}` | Unpublish product. |
|
|
480
|
+
| `batchPublish(params)` | `PUT` | `/api/core/products/publish` | Batch publish multiple products. |
|
|
481
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/products/unpublish` | Batch unpublish multiple products. |
|
|
482
|
+
| `archiveProduct(params)` | `POST` | `/api/core/products/archive` | Archive products. |
|
|
483
|
+
| `unarchiveProduct(params)` | `POST` | `/api/core/products/unarchive` | Unarchive products. |
|
|
484
|
+
| `getArchivedProducts(params)` | `GET` | `/api/core/products/archive` | Get archived products list. |
|
|
485
|
+
| `timelineSearch(query)` | `GET` | `/api/core/products/ai/timeline-search/enable` | AI timeline search on products. |
|
|
486
|
+
|
|
467
487
|
---
|
|
468
488
|
|
|
469
|
-
|
|
489
|
+
## Deep Dive 3: CMS Tags & Tag Categories (RAD API Swagger: tag=tags)
|
|
470
490
|
|
|
471
|
-
|
|
472
|
-
// app/products/page.tsx (Server Component)
|
|
473
|
-
import { ProductsApi } from '@/services/productsApi';
|
|
491
|
+
Manage tag categories and multi-level hierarchical tag trees ([Swagger documentation](https://rad-sandbox.sandpod.ir/api/documentation?tag=tags)).
|
|
474
492
|
|
|
475
|
-
|
|
476
|
-
const products = await ProductsApi.getProducts();
|
|
493
|
+
### Accessing Tag Services
|
|
477
494
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
495
|
+
Access via `podSdk.tags` or `podSdk.cms.tags`:
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// Category Management (with Access-Token)
|
|
499
|
+
const categories = await podSdk.tags.getTagCategories({ size: 20 });
|
|
500
|
+
await podSdk.tags.createTagCategory({ name: 'Technology', desc: 'Tech articles' });
|
|
501
|
+
await podSdk.tags.publishTagCategory(categoryId);
|
|
502
|
+
|
|
503
|
+
// Public Hierarchical Tag Tree (without token, defaults to 'root')
|
|
504
|
+
const rootTree = await podSdk.tags.getTagTree('root', { levelCount: 3 });
|
|
505
|
+
|
|
506
|
+
// Backward-compatible shortcut
|
|
507
|
+
const categoriesTree = await podSdk.cms.getCategories({ levelCount: 3 });
|
|
508
|
+
|
|
509
|
+
// Tree Node Management (create, update parent, code, ancestors)
|
|
510
|
+
await podSdk.tags.createTagTreeItem('technology', {
|
|
511
|
+
name: 'Next.js',
|
|
512
|
+
code: 'NEXTJS',
|
|
513
|
+
parentId: 10,
|
|
514
|
+
});
|
|
515
|
+
await podSdk.tags.updateTagTreeParent('technology', nodeId, newParentId);
|
|
516
|
+
await podSdk.tags.updateTagTreeCode('technology', nodeId, 'NEW_CODE');
|
|
517
|
+
const ancestors = await podSdk.tags.getTagTreeAncestors('technology', nodeId);
|
|
493
518
|
```
|
|
494
519
|
|
|
520
|
+
### Tag Methods Reference (`podSdk.tags` / `podSdk.cms.tags`)
|
|
521
|
+
|
|
522
|
+
| Method | HTTP | Path | Description |
|
|
523
|
+
| :--- | :--- | :--- | :--- |
|
|
524
|
+
| `getTagCategories(params)` | `GET` | `/api/core/tags/category` | List tag categories (manage). |
|
|
525
|
+
| `createTagCategory(params)` | `POST` | `/api/core/tags/category` | Create new tag category. |
|
|
526
|
+
| `getTagCategory(id)` | `GET` | `/api/core/tags/category/{id}` | Show single tag category. |
|
|
527
|
+
| `updateTagCategory(id, params)` | `PUT` | `/api/core/tags/category/{id}` | Update tag category. |
|
|
528
|
+
| `publishTagCategory(id)` | `PUT` | `/api/core/tags/category/{id}/publish` | Publish tag category. |
|
|
529
|
+
| `unpublishTagCategory(id)` | `PUT` | `/api/core/tags/category/{id}/unpublish` | Unpublish tag category. |
|
|
530
|
+
| `getTagTree(categoryId, params)` | `GET` | `/api/core/tags/{categoryId}/tree/enable` | Get enabled tag tree (public). |
|
|
531
|
+
| `getManageTagTree(categoryId, params)` | `GET` | `/api/core/tags/{categoryId}/tree/manage` | Get tag tree (manage with token). |
|
|
532
|
+
| `createTagTreeItem(categoryId, params)` | `POST` | `/api/core/tags/{categoryId}/tree` | Create tag tree node for category. |
|
|
533
|
+
| `getTagTreeItem(categoryId, id)` | `GET` | `/api/core/tags/{categoryId}/tree/{id}/enable` | Show enabled tag tree node. |
|
|
534
|
+
| `getManageTagTreeItem(categoryId, id)` | `GET` | `/api/core/tags/{categoryId}/tree/{id}/manage` | Show tag tree node (manage). |
|
|
535
|
+
| `updateTagTreeItem(categoryId, id, params)` | `PUT` | `/api/core/tags/{categoryId}/tree/{id}` | Update tag tree node. |
|
|
536
|
+
| `publishTagTreeItem(categoryId, id)` | `PUT` | `/api/core/tags/{categoryId}/tree/publish/{id}` | Publish tag tree node. |
|
|
537
|
+
| `unpublishTagTreeItem(categoryId, id)` | `PUT` | `/api/core/tags/{categoryId}/tree/unpublish/{id}` | Unpublish tag tree node. |
|
|
538
|
+
| `updateTagTreeParent(categoryId, id, parentId)` | `PUT` | `/api/core/tags/{categoryId}/tree/parent/{id}` | Update parent of tag tree node. |
|
|
539
|
+
| `getTagTreeAncestors(categoryId, id, params)` | `GET` | `/api/core/tags/{categoryId}/tree/parent/{id}/enable` | Get node ancestors (public). |
|
|
540
|
+
| `getManageTagTreeAncestors(categoryId, id, params)`| `GET` | `/api/core/tags/{categoryId}/tree/parent/{id}/manage` | Get node ancestors (manage). |
|
|
541
|
+
| `updateTagTreeCode(categoryId, id, code)` | `PUT` | `/api/core/tags/{categoryId}/tree/code/{id}` | Update code of tag tree node. |
|
|
542
|
+
|
|
495
543
|
---
|
|
496
544
|
|
|
497
|
-
|
|
545
|
+
## Deep Dive 4: CustomPost & Generic Typed Repositories
|
|
546
|
+
|
|
547
|
+
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
548
|
+
|
|
549
|
+
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
550
|
+
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
498
551
|
|
|
499
552
|
```typescript
|
|
500
|
-
//
|
|
501
|
-
|
|
553
|
+
// lib/blog.ts
|
|
554
|
+
import { podSdk } from '@/lib/pod';
|
|
502
555
|
|
|
503
|
-
|
|
504
|
-
|
|
556
|
+
export interface BlogPostMeta {
|
|
557
|
+
slug: string;
|
|
558
|
+
title: string;
|
|
559
|
+
summary: string;
|
|
560
|
+
views: number;
|
|
561
|
+
}
|
|
505
562
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
563
|
+
// Search posts with typed metadata query
|
|
564
|
+
export async function getPublishedPosts() {
|
|
565
|
+
const response = await podSdk.customPost.searchTimelineByMetadata<BlogPostMeta>({
|
|
566
|
+
entityName: 'blog_post',
|
|
567
|
+
metadata: { views: 100 },
|
|
568
|
+
size: 20,
|
|
569
|
+
offset: 0,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return response.result.map((r) => r.item.metadata);
|
|
510
573
|
}
|
|
511
574
|
```
|
|
512
575
|
|
|
@@ -514,7 +577,7 @@ export async function addProduct(data: { title: string; sku: string; price: numb
|
|
|
514
577
|
|
|
515
578
|
## Other POD Microservices
|
|
516
579
|
|
|
517
|
-
###
|
|
580
|
+
### 4. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
518
581
|
|
|
519
582
|
```typescript
|
|
520
583
|
// app/actions/auth.ts
|
|
@@ -524,7 +587,6 @@ import { podSdk } from '@/lib/pod';
|
|
|
524
587
|
|
|
525
588
|
// Step 1: Handshake and send OTP
|
|
526
589
|
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
527
|
-
// Handshake to obtain keyId
|
|
528
590
|
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
529
591
|
if (handshake.hasError || !handshake.result?.keyId) {
|
|
530
592
|
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
@@ -545,7 +607,6 @@ export async function verifyOtp(authorization: string, phoneNumber: string, code
|
|
|
545
607
|
throw new Error(verify.message || 'OTP verification failed');
|
|
546
608
|
}
|
|
547
609
|
|
|
548
|
-
// Exchange authorization code for JWT tokens
|
|
549
610
|
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
550
611
|
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
551
612
|
}
|
|
@@ -559,7 +620,7 @@ export async function getProfile(accessToken: string) {
|
|
|
559
620
|
|
|
560
621
|
---
|
|
561
622
|
|
|
562
|
-
###
|
|
623
|
+
### 5. Podspace (File Uploads & CDN URLs)
|
|
563
624
|
|
|
564
625
|
```typescript
|
|
565
626
|
// app/actions/upload.ts
|
|
@@ -580,7 +641,7 @@ export async function uploadFile(formData: FormData) {
|
|
|
580
641
|
|
|
581
642
|
---
|
|
582
643
|
|
|
583
|
-
###
|
|
644
|
+
### 6. Notification (SMS Delivery)
|
|
584
645
|
|
|
585
646
|
```typescript
|
|
586
647
|
// app/actions/notify.ts
|
|
@@ -598,7 +659,7 @@ export async function sendSms(phoneNumber: string, text: string) {
|
|
|
598
659
|
|
|
599
660
|
---
|
|
600
661
|
|
|
601
|
-
###
|
|
662
|
+
### 7. Social (Comments & Likes)
|
|
602
663
|
|
|
603
664
|
```typescript
|
|
604
665
|
// app/actions/social.ts
|
|
@@ -620,7 +681,7 @@ export async function likePost(postId: number) {
|
|
|
620
681
|
|
|
621
682
|
---
|
|
622
683
|
|
|
623
|
-
###
|
|
684
|
+
### 8. IUMS (Identity & University Management Service)
|
|
624
685
|
|
|
625
686
|
```typescript
|
|
626
687
|
// app/actions/iums.ts
|