@rezamirzapour/pod-sdk 1.0.1 → 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 +220 -169
- 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,81 +277,221 @@ export default async function NewsPage({ searchParams }: NewsPageProps) {
|
|
|
276
277
|
|
|
277
278
|
---
|
|
278
279
|
|
|
279
|
-
|
|
280
|
+
## Deep Dive 2: CMS Product Module (RAD API Swagger)
|
|
280
281
|
|
|
281
|
-
|
|
282
|
-
// app/news/[id]/page.tsx (Server Component)
|
|
283
|
-
import { NewsApi } from '@/services/newsApi';
|
|
284
|
-
import { notFound } from 'next/navigation';
|
|
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.
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
params: { id: string };
|
|
288
|
-
}
|
|
284
|
+
### Accessing Products
|
|
289
285
|
|
|
290
|
-
|
|
291
|
-
const news = await NewsApi.getNewsById(Number(params.id));
|
|
286
|
+
You can access product methods either via `podSdk.product` or `podSdk.cms.products`:
|
|
292
287
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
288
|
+
```typescript
|
|
289
|
+
// Both point to the same CmsProductService instance
|
|
290
|
+
podSdk.product.getProducts(...);
|
|
291
|
+
podSdk.cms.products.getProducts(...);
|
|
292
|
+
```
|
|
296
293
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
</article>
|
|
310
|
-
);
|
|
294
|
+
### 🛍️ Clean Architecture Pattern: Product Data Access Layer (`ProductsApi`)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// services/productsApi.ts
|
|
298
|
+
import { podSdk } from '@/lib/pod';
|
|
299
|
+
|
|
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
|
|
311
306
|
}
|
|
312
|
-
```
|
|
313
307
|
|
|
314
|
-
|
|
308
|
+
// 2. Computed / normalized presentation fields (P)
|
|
309
|
+
export interface LaptopNormalized {
|
|
310
|
+
finalPrice: number;
|
|
311
|
+
discountBadge: string;
|
|
312
|
+
}
|
|
315
313
|
|
|
316
|
-
|
|
314
|
+
// 3. Clean Domain Product Model
|
|
315
|
+
export interface ProductItemModel {
|
|
316
|
+
entityId: number;
|
|
317
|
+
name: string;
|
|
318
|
+
price: number;
|
|
319
|
+
finalPrice: number;
|
|
320
|
+
discountBadge: string;
|
|
321
|
+
photoUrl: string;
|
|
322
|
+
specs: {
|
|
323
|
+
cpu: string;
|
|
324
|
+
ram: string;
|
|
325
|
+
storage: string;
|
|
326
|
+
};
|
|
327
|
+
}
|
|
317
328
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
'use server';
|
|
329
|
+
export class ProductsApi {
|
|
330
|
+
private static readonly PRODUCT_TYPE = 'laptops';
|
|
321
331
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
body,
|
|
335
|
-
imageHash,
|
|
336
|
-
});
|
|
332
|
+
/**
|
|
333
|
+
* Fetches published products with price & discount filters
|
|
334
|
+
*/
|
|
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;
|
|
337
344
|
|
|
338
|
-
|
|
339
|
-
|
|
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
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Retrieves single product by entityId
|
|
388
|
+
*/
|
|
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;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Look up product by barcode scanner
|
|
399
|
+
*/
|
|
400
|
+
public static async getProductByBarcode(barcode: string) {
|
|
401
|
+
return podSdk.product.getProductByBarcode<LaptopFormatted>(barcode);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Adds and publishes a new product
|
|
406
|
+
*/
|
|
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
|
+
});
|
|
424
|
+
}
|
|
340
425
|
}
|
|
341
426
|
```
|
|
342
427
|
|
|
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. |
|
|
485
|
+
|
|
343
486
|
---
|
|
344
487
|
|
|
345
|
-
## Deep Dive: CustomPost & Generic Typed Repositories
|
|
488
|
+
## Deep Dive 3: CustomPost & Generic Typed Repositories
|
|
346
489
|
|
|
347
490
|
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
348
491
|
|
|
349
492
|
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
350
493
|
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
351
494
|
|
|
352
|
-
### 1. Direct Generic Method Usage
|
|
353
|
-
|
|
354
495
|
```typescript
|
|
355
496
|
// lib/blog.ts
|
|
356
497
|
import { podSdk } from '@/lib/pod';
|
|
@@ -360,114 +501,26 @@ export interface BlogPostMeta {
|
|
|
360
501
|
title: string;
|
|
361
502
|
summary: string;
|
|
362
503
|
views: number;
|
|
363
|
-
publishedAt: string;
|
|
364
504
|
}
|
|
365
505
|
|
|
366
|
-
//
|
|
506
|
+
// Search posts with typed metadata query
|
|
367
507
|
export async function getPublishedPosts() {
|
|
368
508
|
const response = await podSdk.customPost.searchTimelineByMetadata<BlogPostMeta>({
|
|
369
509
|
entityName: 'blog_post',
|
|
370
|
-
metadata: { views: 100 },
|
|
510
|
+
metadata: { views: 100 },
|
|
371
511
|
size: 20,
|
|
372
512
|
offset: 0,
|
|
373
513
|
});
|
|
374
514
|
|
|
375
|
-
// response.result[i].item.metadata is strongly typed as BlogPostMeta
|
|
376
515
|
return response.result.map((r) => r.item.metadata);
|
|
377
516
|
}
|
|
378
|
-
|
|
379
|
-
// 2. Add a new custom post with typed metadata
|
|
380
|
-
export async function createBlogPost(meta: BlogPostMeta) {
|
|
381
|
-
return podSdk.customPost.addCustomPost<BlogPostMeta>({
|
|
382
|
-
name: 'blog_post',
|
|
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 in seconds:
|
|
393
|
-
|
|
394
|
-
```typescript
|
|
395
|
-
// lib/products.ts
|
|
396
|
-
import { podSdk } from '@/lib/pod';
|
|
397
|
-
|
|
398
|
-
export interface ProductData {
|
|
399
|
-
title: string;
|
|
400
|
-
sku: string;
|
|
401
|
-
price: number;
|
|
402
|
-
inStock: boolean;
|
|
403
|
-
category: string;
|
|
404
|
-
entityId?: number;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Instantiate typed CRUD service for "product_item"
|
|
408
|
-
export const productCrud = podSdk.createCrud<ProductData>({
|
|
409
|
-
name: 'product_entity',
|
|
410
|
-
type: 'commerce',
|
|
411
|
-
detailedType: 'gadgets',
|
|
412
|
-
});
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
All methods on `productCrud` are fully generic:
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
// app/actions/products.ts
|
|
419
|
-
'use server';
|
|
420
|
-
|
|
421
|
-
import { productCrud } from '@/lib/products';
|
|
422
|
-
import { revalidatePath } from 'next/cache';
|
|
423
|
-
|
|
424
|
-
// 1. Fetch all products
|
|
425
|
-
export async function getProducts() {
|
|
426
|
-
const result = await productCrud.getAll();
|
|
427
|
-
return result.result.map((r) => r.item.metadata.data);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// 2. Get single product by numerical ID
|
|
431
|
-
export async function getProductById(id: number) {
|
|
432
|
-
const res = await productCrud.getById(id);
|
|
433
|
-
return res.result?.[0]?.metadata.data;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// 3. Create product and automatically bind entityId
|
|
437
|
-
export async function saveNewProduct(data: { title: string; sku: string; price: number }) {
|
|
438
|
-
const res = await productCrud.createAndBindEntityId({
|
|
439
|
-
title: data.title,
|
|
440
|
-
sku: data.sku,
|
|
441
|
-
price: data.price,
|
|
442
|
-
inStock: true,
|
|
443
|
-
category: 'electronics',
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
revalidatePath('/products');
|
|
447
|
-
return res;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// 4. Update product
|
|
451
|
-
export async function updateProduct(entityId: number, data: ProductData) {
|
|
452
|
-
return productCrud.update(entityId, data);
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
// 5. Archive (soft-disable without deleting)
|
|
456
|
-
export async function archiveProduct(entityId: number, data: ProductData) {
|
|
457
|
-
return productCrud.archive(entityId, data);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// 6. Delete permanently
|
|
461
|
-
export async function deleteProduct(entityId: number) {
|
|
462
|
-
return productCrud.delete(entityId);
|
|
463
|
-
}
|
|
464
517
|
```
|
|
465
518
|
|
|
466
519
|
---
|
|
467
520
|
|
|
468
521
|
## Other POD Microservices
|
|
469
522
|
|
|
470
|
-
###
|
|
523
|
+
### 4. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
471
524
|
|
|
472
525
|
```typescript
|
|
473
526
|
// app/actions/auth.ts
|
|
@@ -477,7 +530,6 @@ import { podSdk } from '@/lib/pod';
|
|
|
477
530
|
|
|
478
531
|
// Step 1: Handshake and send OTP
|
|
479
532
|
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
480
|
-
// Handshake to obtain keyId
|
|
481
533
|
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
482
534
|
if (handshake.hasError || !handshake.result?.keyId) {
|
|
483
535
|
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
@@ -498,7 +550,6 @@ export async function verifyOtp(authorization: string, phoneNumber: string, code
|
|
|
498
550
|
throw new Error(verify.message || 'OTP verification failed');
|
|
499
551
|
}
|
|
500
552
|
|
|
501
|
-
// Exchange authorization code for JWT tokens
|
|
502
553
|
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
503
554
|
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
504
555
|
}
|
|
@@ -512,7 +563,7 @@ export async function getProfile(accessToken: string) {
|
|
|
512
563
|
|
|
513
564
|
---
|
|
514
565
|
|
|
515
|
-
###
|
|
566
|
+
### 5. Podspace (File Uploads & CDN URLs)
|
|
516
567
|
|
|
517
568
|
```typescript
|
|
518
569
|
// app/actions/upload.ts
|
|
@@ -533,7 +584,7 @@ export async function uploadFile(formData: FormData) {
|
|
|
533
584
|
|
|
534
585
|
---
|
|
535
586
|
|
|
536
|
-
###
|
|
587
|
+
### 6. Notification (SMS Delivery)
|
|
537
588
|
|
|
538
589
|
```typescript
|
|
539
590
|
// app/actions/notify.ts
|
|
@@ -551,7 +602,7 @@ export async function sendSms(phoneNumber: string, text: string) {
|
|
|
551
602
|
|
|
552
603
|
---
|
|
553
604
|
|
|
554
|
-
###
|
|
605
|
+
### 7. Social (Comments & Likes)
|
|
555
606
|
|
|
556
607
|
```typescript
|
|
557
608
|
// app/actions/social.ts
|
|
@@ -573,7 +624,7 @@ export async function likePost(postId: number) {
|
|
|
573
624
|
|
|
574
625
|
---
|
|
575
626
|
|
|
576
|
-
###
|
|
627
|
+
### 8. IUMS (Identity & University Management Service)
|
|
577
628
|
|
|
578
629
|
```typescript
|
|
579
630
|
// app/actions/iums.ts
|