@rezamirzapour/pod-sdk 1.0.4 → 1.0.5
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 +742 -713
- package/dist/index.d.mts +49 -6
- package/dist/index.d.ts +49 -6
- package/dist/index.js +80 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +78 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +60 -60
package/README.md
CHANGED
|
@@ -1,713 +1,742 @@
|
|
|
1
|
-
# @rezamirzapour/pod-sdk
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@rezamirzapour/pod-sdk)
|
|
6
|
-
[](LICENSE)
|
|
7
|
-
[](https://www.typescriptlang.org/)
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Features
|
|
12
|
-
|
|
13
|
-
- 🌐 **Unified Multi-Service Architecture**: Single entry-point managing official POD microservices with official endpoints and configurations.
|
|
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 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.
|
|
18
|
-
- 📦 **Generic CRUD Repository for CustomPost**: Instantiate typed data repositories on POD CustomPost in 2 lines with `create`, `getById`, `getAll`, `createAndBindEntityId`, and `archive`.
|
|
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).
|
|
20
|
-
- 🚀 **Next.js App Router Ready**: Seamless support for Server Components, Server Actions, Route Handlers, and Client Components.
|
|
21
|
-
- 🌳 **Treeshakeable & Dual ESM/CJS**: Ships clean ES modules (`.mjs`) and CommonJS (`.js`) with 100% complete TypeScript declarations (`.d.ts`).
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Microservices Included
|
|
26
|
-
|
|
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. |
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## Installation
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
npm install @rezamirzapour/pod-sdk @rezamirzapour/http @rezamirzapour/logger
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
---
|
|
49
|
-
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
id: item.id,
|
|
195
|
-
entityId: item.entityId,
|
|
196
|
-
title: item.formatted.title,
|
|
197
|
-
coverImage: item.formatted.coverImage,
|
|
198
|
-
summary: item.
|
|
199
|
-
body: item.formatted.body,
|
|
200
|
-
readTime:
|
|
201
|
-
publishedAt: item.timestamp,
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
|
464
|
-
|
|
|
465
|
-
| `
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `
|
|
469
|
-
| `
|
|
470
|
-
| `
|
|
471
|
-
| `
|
|
472
|
-
| `
|
|
473
|
-
| `
|
|
474
|
-
| `
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
479
|
-
| `
|
|
480
|
-
| `
|
|
481
|
-
| `
|
|
482
|
-
| `
|
|
483
|
-
| `
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1
|
+
# @rezamirzapour/pod-sdk
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@rezamirzapour/pod-sdk)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🌐 **Unified Multi-Service Architecture**: Single entry-point managing official POD microservices with official endpoints and configurations.
|
|
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 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.
|
|
18
|
+
- 📦 **Generic CRUD Repository for CustomPost**: Instantiate typed data repositories on POD CustomPost in 2 lines with `create`, `getById`, `getAll`, `createAndBindEntityId`, and `archive`.
|
|
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).
|
|
20
|
+
- 🚀 **Next.js App Router Ready**: Seamless support for Server Components, Server Actions, Route Handlers, and Client Components.
|
|
21
|
+
- 🌳 **Treeshakeable & Dual ESM/CJS**: Ships clean ES modules (`.mjs`) and CommonJS (`.js`) with 100% complete TypeScript declarations (`.d.ts`).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Microservices Included
|
|
26
|
+
|
|
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. |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @rezamirzapour/pod-sdk @rezamirzapour/http @rezamirzapour/logger
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Environment Variables & URL Configuration
|
|
51
|
+
|
|
52
|
+
The SDK natively discovers official POD Platform URLs from your Next.js / Node.js `.env` configuration, or falls back to production defaults automatically:
|
|
53
|
+
|
|
54
|
+
```env
|
|
55
|
+
# POD Platform Production URLs
|
|
56
|
+
NEXT_PUBLIC_ACCOUNTS_URL=https://accounts.pod.ir
|
|
57
|
+
NEXT_PUBLIC_API_POD_URL=https://api.pod.ir
|
|
58
|
+
NEXT_PUBLIC_API_POD_SANDBOX_URL=https://api.sandpod.ir
|
|
59
|
+
NEXT_PUBLIC_CMS_URL=https://api.pod.ir/srv/cms-server
|
|
60
|
+
NEXT_PUBLIC_IUMS_URL=https://indra.khatam.ac.ir/srv
|
|
61
|
+
NEXT_PUBLIC_NOTIFICATION_URL=https://api.pod.ir/srv/notification
|
|
62
|
+
NEXT_PUBLIC_PODREPORT_URL=https://reporting.pod.ir
|
|
63
|
+
NEXT_PUBLIC_PODSPACE_URL=https://podspace.pod.ir
|
|
64
|
+
|
|
65
|
+
# Sandbox Endpoints (Development & Staging)
|
|
66
|
+
NEXT_PUBLIC_SANDBOX_CMS_URL=http://api.sandpod.ir/srv/cms-sandbox
|
|
67
|
+
NEXT_PUBLIC_SANDBOX_CMS_CLIENT_URL=https://api.sandpod.ir/srv/cms-sandbox
|
|
68
|
+
NEXT_PUBLIC_SANDBOX_PODSPACE_URL=http://podspace.sandpod.ir
|
|
69
|
+
NEXT_PUBLIC_SANDBOX_STREAM_URL=https://sandbox-offline-stream.sandpod.ir
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
> [!TIP]
|
|
73
|
+
> If you have defined these `NEXT_PUBLIC_*` variables in your `.env` or `.env.local`, `@rezamirzapour/pod-sdk` will automatically detect and apply them without any manual URL mapping.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Quickstart
|
|
78
|
+
|
|
79
|
+
### 1. Initialize the Client
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// lib/pod.ts
|
|
83
|
+
import { createPodSdk, DEFAULT_POD_URLS, SANDBOX_POD_URLS } from '@rezamirzapour/pod-sdk';
|
|
84
|
+
|
|
85
|
+
export const podSdk = createPodSdk({
|
|
86
|
+
apiToken: process.env.POD_API_TOKEN,
|
|
87
|
+
clientId: process.env.POD_CLIENT_ID,
|
|
88
|
+
clientSecret: process.env.POD_CLIENT_SECRET,
|
|
89
|
+
privateKeyPem: process.env.POD_PRIVATE_KEY_PEM, // RSA Private Key for SSO OTP signing
|
|
90
|
+
revalidate: 3600, // Default ISR revalidation in seconds
|
|
91
|
+
// sandbox: true, // Optional: automatically routes to POD Sandbox endpoints
|
|
92
|
+
urls: {
|
|
93
|
+
// Optional programmatic overrides (defaults to official POD endpoints & env vars)
|
|
94
|
+
accounts: 'https://accounts.pod.ir',
|
|
95
|
+
apiPod: 'https://api.pod.ir',
|
|
96
|
+
cms: 'https://api.pod.ir/srv/cms-server',
|
|
97
|
+
iums: 'https://indra.khatam.ac.ir/srv',
|
|
98
|
+
notification: 'https://api.pod.ir/srv/notification',
|
|
99
|
+
podreport: 'https://reporting.pod.ir',
|
|
100
|
+
podspace: 'https://podspace.pod.ir',
|
|
101
|
+
podform: 'https://podform.pod.ir',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Deep Dive 1: CMS Content & Data Formatting
|
|
109
|
+
|
|
110
|
+
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`).
|
|
111
|
+
|
|
112
|
+
> [!NOTE]
|
|
113
|
+
> `getContent2` has been removed. For authenticated/management content retrieval (using `Access-Token`), call `podSdk.cms.getAllContents()` or pass headers to `getContent()`.
|
|
114
|
+
|
|
115
|
+
### 🏗️ Clean Architecture Pattern: Dedicated Data Access Layer (`NewsApi`)
|
|
116
|
+
|
|
117
|
+
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`).
|
|
118
|
+
|
|
119
|
+
#### Step 1: Create the Data Access Layer (`services/newsApi.ts`)
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// services/newsApi.ts
|
|
123
|
+
import { podSdk } from '@/lib/pod';
|
|
124
|
+
|
|
125
|
+
// 1. Define the raw CMS Formatted Metadata schema (T)
|
|
126
|
+
export interface NewsArticleFormatted {
|
|
127
|
+
title: string;
|
|
128
|
+
coverImage: string; // Auto-transformed from hash to full Podspace CDN URL!
|
|
129
|
+
lead: string;
|
|
130
|
+
body: string;
|
|
131
|
+
author?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Define computed / normalized domain attributes (P)
|
|
135
|
+
export interface NewsArticleNormalized {
|
|
136
|
+
summary: string;
|
|
137
|
+
readTime: number;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 3. Define the Clean Domain Model consumed by your React Components
|
|
141
|
+
export interface NewsItem {
|
|
142
|
+
id: number;
|
|
143
|
+
entityId: number;
|
|
144
|
+
title: string;
|
|
145
|
+
coverImage: string;
|
|
146
|
+
summary: string;
|
|
147
|
+
body: string;
|
|
148
|
+
readTime: number;
|
|
149
|
+
publishedAt?: number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface GetNewsListParams {
|
|
153
|
+
page?: number;
|
|
154
|
+
pageSize?: number;
|
|
155
|
+
tagTrees?: string[];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class NewsApi {
|
|
159
|
+
private static readonly CONTENT_TYPE = 'company_news';
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Fetches published news articles with Next.js App Router ISR Caching
|
|
163
|
+
*/
|
|
164
|
+
public static async getNewsList({
|
|
165
|
+
page = 1,
|
|
166
|
+
pageSize = 10,
|
|
167
|
+
tagTrees,
|
|
168
|
+
}: GetNewsListParams = {}): Promise<{ items: NewsItem[]; total: number }> {
|
|
169
|
+
const offset = (page - 1) * pageSize;
|
|
170
|
+
|
|
171
|
+
// Call getContent with exact generics: <NewsArticleFormatted, NewsArticleNormalized>
|
|
172
|
+
const response = await podSdk.cms.getContent<NewsArticleFormatted, NewsArticleNormalized>(
|
|
173
|
+
{
|
|
174
|
+
contentTypeUniqueId: this.CONTENT_TYPE,
|
|
175
|
+
size: pageSize,
|
|
176
|
+
offset,
|
|
177
|
+
tagTrees,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
// Custom normalizer executed during data formatting
|
|
181
|
+
normalizer: (item) => ({
|
|
182
|
+
summary: item.formatted.lead || item.formatted.title,
|
|
183
|
+
readTime: Math.max(1, Math.ceil((item.formatted.body?.length || 0) / 500)),
|
|
184
|
+
}),
|
|
185
|
+
revalidate: 1800, // Next.js ISR: cache for 30 minutes
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (response.hasError || !Array.isArray(response.result)) {
|
|
190
|
+
return { items: [], total: 0 };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const items: NewsItem[] = response.result.map((item) => ({
|
|
194
|
+
id: item.id,
|
|
195
|
+
entityId: item.entityId,
|
|
196
|
+
title: item.formatted.title,
|
|
197
|
+
coverImage: item.formatted.coverImage,
|
|
198
|
+
summary: item.__normalized.summary,
|
|
199
|
+
body: item.formatted.body,
|
|
200
|
+
readTime: item.__normalized.readTime,
|
|
201
|
+
publishedAt: item.timestamp,
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
items,
|
|
206
|
+
total: response.count || items.length,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Fetches a single news article by its entity ID
|
|
212
|
+
*/
|
|
213
|
+
public static async getNewsById(entityId: number): Promise<NewsItem | null> {
|
|
214
|
+
const response = await podSdk.cms.getContentByEntityId<NewsArticleFormatted, NewsArticleNormalized>({
|
|
215
|
+
entityId,
|
|
216
|
+
contentTypeUniqueId: this.CONTENT_TYPE,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
const item = response.result?.[0];
|
|
220
|
+
if (!item) return null;
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
id: item.id,
|
|
224
|
+
entityId: item.entityId,
|
|
225
|
+
title: item.formatted.title,
|
|
226
|
+
coverImage: item.formatted.coverImage,
|
|
227
|
+
summary: item.formatted.lead || item.formatted.title,
|
|
228
|
+
body: item.formatted.body,
|
|
229
|
+
readTime: Math.max(1, Math.ceil((item.formatted.body?.length || 0) / 500)),
|
|
230
|
+
publishedAt: item.timestamp,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Publishes a new article to CMS
|
|
236
|
+
*/
|
|
237
|
+
public static async publishNews(data: {
|
|
238
|
+
title: string;
|
|
239
|
+
lead: string;
|
|
240
|
+
body: string;
|
|
241
|
+
imageHash: string;
|
|
242
|
+
}) {
|
|
243
|
+
return podSdk.cms.addContent({
|
|
244
|
+
contentTypeUniqueId: this.CONTENT_TYPE,
|
|
245
|
+
body: {
|
|
246
|
+
name: data.title,
|
|
247
|
+
fieldCode: ['title', 'lead', 'body', 'coverImage'],
|
|
248
|
+
fieldValue: [data.title, data.lead, data.body, data.imageHash],
|
|
249
|
+
canComment: true,
|
|
250
|
+
canLike: true,
|
|
251
|
+
enable: true,
|
|
252
|
+
isSearchable: true,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Step 2: Consume `NewsApi` in Next.js Server Components (`app/news/page.tsx`)
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
// app/news/page.tsx (Server Component)
|
|
263
|
+
import { NewsApi } from '@/services/newsApi';
|
|
264
|
+
import Link from 'next/link';
|
|
265
|
+
|
|
266
|
+
interface NewsPageProps {
|
|
267
|
+
searchParams: { page?: string };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default async function NewsPage({ searchParams }: NewsPageProps) {
|
|
271
|
+
const page = Number(searchParams.page) || 1;
|
|
272
|
+
const { items, total } = await NewsApi.getNewsList({ page, pageSize: 12 });
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<main className="container mx-auto py-8 px-4">
|
|
276
|
+
<h1 className="text-3xl font-bold mb-6">Company News</h1>
|
|
277
|
+
|
|
278
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
279
|
+
{items.map((news) => (
|
|
280
|
+
<article key={news.entityId} className="border rounded-xl overflow-hidden shadow-sm hover:shadow-md transition">
|
|
281
|
+
{news.coverImage && (
|
|
282
|
+
<img
|
|
283
|
+
src={news.coverImage}
|
|
284
|
+
alt={news.title}
|
|
285
|
+
className="w-full h-48 object-cover"
|
|
286
|
+
/>
|
|
287
|
+
)}
|
|
288
|
+
<div className="p-4">
|
|
289
|
+
<h2 className="text-xl font-semibold mb-2">
|
|
290
|
+
<Link href={`/news/${news.entityId}`}>{news.title}</Link>
|
|
291
|
+
</h2>
|
|
292
|
+
<p className="text-gray-600 text-sm line-clamp-2">{news.summary}</p>
|
|
293
|
+
<div className="mt-4 flex items-center justify-between text-xs text-gray-400">
|
|
294
|
+
<span>{news.readTime} min read</span>
|
|
295
|
+
<Link href={`/news/${news.entityId}`} className="text-blue-600 font-medium hover:underline">
|
|
296
|
+
Read more →
|
|
297
|
+
</Link>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</article>
|
|
301
|
+
))}
|
|
302
|
+
</div>
|
|
303
|
+
</main>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Deep Dive 2: CMS Product Module (RAD API Swagger)
|
|
311
|
+
|
|
312
|
+
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
|
+
|
|
314
|
+
### Accessing Products
|
|
315
|
+
|
|
316
|
+
You can access product methods either via `podSdk.product` or `podSdk.cms.products`:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// Both point to the same CmsProductService instance
|
|
320
|
+
podSdk.product.getProducts(...);
|
|
321
|
+
podSdk.cms.products.getProducts(...);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 🛍️ Clean Architecture Pattern: Product Data Access Layer (`ProductsApi`)
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// services/productsApi.ts
|
|
328
|
+
import { podSdk } from '@/lib/pod';
|
|
329
|
+
|
|
330
|
+
// 1. Raw formatted product metadata (T)
|
|
331
|
+
export interface LaptopFormatted {
|
|
332
|
+
cpu: string;
|
|
333
|
+
ram: string;
|
|
334
|
+
storage: string;
|
|
335
|
+
displayPhoto: string; // Auto-transformed from hash to Podspace CDN URL
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 2. Computed / normalized presentation fields (P)
|
|
339
|
+
export interface LaptopNormalized {
|
|
340
|
+
finalPrice: number;
|
|
341
|
+
discountBadge: string;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 3. Clean Domain Product Model
|
|
345
|
+
export interface ProductItemModel {
|
|
346
|
+
entityId: number;
|
|
347
|
+
name: string;
|
|
348
|
+
price: number;
|
|
349
|
+
finalPrice: number;
|
|
350
|
+
discountBadge: string;
|
|
351
|
+
photoUrl: string;
|
|
352
|
+
specs: {
|
|
353
|
+
cpu: string;
|
|
354
|
+
ram: string;
|
|
355
|
+
storage: string;
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
export class ProductsApi {
|
|
360
|
+
private static readonly PRODUCT_TYPE = 'laptops';
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Fetches published products with price & discount filters
|
|
364
|
+
*/
|
|
365
|
+
public static async getProducts(params: {
|
|
366
|
+
fromPrice?: number;
|
|
367
|
+
toPrice?: number;
|
|
368
|
+
barcode?: string;
|
|
369
|
+
page?: number;
|
|
370
|
+
pageSize?: number;
|
|
371
|
+
} = {}): Promise<ProductItemModel[]> {
|
|
372
|
+
const { fromPrice, toPrice, barcode, page = 1, pageSize = 20 } = params;
|
|
373
|
+
const offset = (page - 1) * pageSize;
|
|
374
|
+
|
|
375
|
+
const response = await podSdk.product.getProducts<LaptopFormatted, LaptopNormalized>(
|
|
376
|
+
{
|
|
377
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
378
|
+
fromPrice,
|
|
379
|
+
toPrice,
|
|
380
|
+
barcode,
|
|
381
|
+
size: pageSize,
|
|
382
|
+
offset,
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
normalizer: (item) => {
|
|
386
|
+
const discount = item.discount || 0;
|
|
387
|
+
const finalPrice = item.price ? item.price * (1 - discount / 100) : 0;
|
|
388
|
+
return {
|
|
389
|
+
finalPrice,
|
|
390
|
+
discountBadge: discount > 0 ? `${discount}% OFF` : '',
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
revalidate: 3600, // Next.js ISR: cache for 1 hour
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
if (response.hasError || !Array.isArray(response.result)) {
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return response.result.map((item) => ({
|
|
402
|
+
entityId: item.entityId,
|
|
403
|
+
name: item.name || '',
|
|
404
|
+
price: item.price || 0,
|
|
405
|
+
finalPrice: item.__normalized.finalPrice,
|
|
406
|
+
discountBadge: item.__normalized.discountBadge,
|
|
407
|
+
photoUrl: item.formatted.displayPhoto,
|
|
408
|
+
specs: {
|
|
409
|
+
cpu: item.formatted.cpu,
|
|
410
|
+
ram: item.formatted.ram,
|
|
411
|
+
storage: item.formatted.storage,
|
|
412
|
+
},
|
|
413
|
+
}));
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Retrieves single product by entityId
|
|
418
|
+
*/
|
|
419
|
+
public static async getProductById(entityId: number) {
|
|
420
|
+
const res = await podSdk.product.getProductByEntityId<LaptopFormatted, LaptopNormalized>({
|
|
421
|
+
entityId,
|
|
422
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
423
|
+
});
|
|
424
|
+
return res.result?.[0] || null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Look up product by barcode scanner
|
|
429
|
+
*/
|
|
430
|
+
public static async getProductByBarcode(barcode: string) {
|
|
431
|
+
return podSdk.product.getProductByBarcode<LaptopFormatted>(barcode);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Adds and publishes a new product
|
|
436
|
+
*/
|
|
437
|
+
public static async createProduct(data: {
|
|
438
|
+
name: string;
|
|
439
|
+
price: number;
|
|
440
|
+
cpu: string;
|
|
441
|
+
ram: string;
|
|
442
|
+
storage: string;
|
|
443
|
+
photoHash: string;
|
|
444
|
+
}) {
|
|
445
|
+
return podSdk.product.addProduct({
|
|
446
|
+
productTypeUniqueId: this.PRODUCT_TYPE,
|
|
447
|
+
body: {
|
|
448
|
+
name: data.name,
|
|
449
|
+
enable: true,
|
|
450
|
+
fieldCode: ['cpu', 'ram', 'storage', 'displayPhoto', 'price'],
|
|
451
|
+
fieldValue: [data.cpu, data.ram, data.storage, data.photoHash, data.price],
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### CMS Methods Reference
|
|
459
|
+
|
|
460
|
+
#### Content Methods (`podSdk.cms`)
|
|
461
|
+
|
|
462
|
+
| Method | HTTP | Path | Description |
|
|
463
|
+
| :--- | :--- | :--- | :--- |
|
|
464
|
+
| `getContent<T, P>(params, opts)` | `GET` | `/api/core/contents/enable` | Get published enabled contents with generic typing. |
|
|
465
|
+
| `getAllContents<T, P>(params, opts)` | `GET` | `/api/core/contents` | Get contents with managing credentials (`Access-Token`). |
|
|
466
|
+
| `getMyContents<T, P>(params, opts)` | `GET` | `/api/core/contents/my` | Get current user/client contents. |
|
|
467
|
+
| `getContentsByType<T, P>(typeId, params)` | `GET` | `/api/core/contents/{contentTypeUniqueId}` | Get contents by ContentType. |
|
|
468
|
+
| `getContentByEntityId<T, P>(params)` | `GET` | `/api/core/contents/enable/{entityId}` | Get single published content item. |
|
|
469
|
+
| `getContentByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/contents/{uniqueId}/byContentUniqueId/enable` | Get content by uniqueId. |
|
|
470
|
+
| `searchContent<T, P>(params)` | `GET` | `/api/core/contents/search` | Search published contents by keyword. |
|
|
471
|
+
| `addContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}/add-publish` | Add and publish content item. |
|
|
472
|
+
| `createContent<T, P>(params)` | `POST` | `/api/core/contents/{contentTypeUniqueId}` | Create unpublished/draft content. |
|
|
473
|
+
| `editContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}/edit-publish` | Update and publish content item. |
|
|
474
|
+
| `updateContent<T, P>(entityId, params)` | `POST` | `/api/core/contents/{typeId}/{entityId}` | Update content item. |
|
|
475
|
+
| `patchContent<T, P>(entityId, params)` | `PATCH` | `/api/core/contents/{typeId}/{entityId}` | Partial update content. |
|
|
476
|
+
| `publishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/publish/{entityId}` | Publish single content item. |
|
|
477
|
+
| `unpublishContent(typeId, entityId)` | `POST` | `/api/core/contents/{typeId}/unpublish/{entityId}` | Unpublish single content item. |
|
|
478
|
+
| `batchPublish(params)` | `PUT` | `/api/core/contents/{typeId}/publish` | Batch publish multiple contents. |
|
|
479
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/contents/{typeId}/unpublish` | Batch unpublish multiple contents. |
|
|
480
|
+
| `archiveContent(params)` | `POST` | `/api/core/contents/{typeId}/archive` | Archive content items. |
|
|
481
|
+
| `unarchiveContent(params)` | `POST` | `/api/core/contents/{typeId}/unarchive` | Unarchive content items. |
|
|
482
|
+
| `getArchivedContents(params)` | `GET` | `/api/core/contents/archive` | Get archived contents list. |
|
|
483
|
+
| `getDrafts(params)` | `GET` | `/api/core/contents/draft` | Get drafts list. |
|
|
484
|
+
| `createDraft(params)` | `POST` | `/api/core/contents/{typeId}/draft` | Create a draft. |
|
|
485
|
+
| `getComments(typeId, entityId)` | `GET` | `/api/core/contents/{typeId}/comments/{entityId}` | Get comments on content. |
|
|
486
|
+
| `getLikes(uniqueId, entityId)` | `GET` | `/api/core/contents/{uniqueId}/like/{entityId}` | Get likes of content item. |
|
|
487
|
+
| `getCategories(params)` | `GET` | `/api/core/tags/root/tree/enable` | Get category/tag tree. |
|
|
488
|
+
| `timelineSearch(query)` | `GET` | `/api/core/contents/ai/timeline-search/enable` | Advanced AI timeline search. |
|
|
489
|
+
|
|
490
|
+
#### Product Methods (`podSdk.product` / `podSdk.cms.products`)
|
|
491
|
+
|
|
492
|
+
| Method | HTTP | Path | Description |
|
|
493
|
+
| :--- | :--- | :--- | :--- |
|
|
494
|
+
| `getProducts<T, P>(params, opts)` | `GET` | `/api/core/products/enable` | Get enabled published products with price/discount filters. |
|
|
495
|
+
| `getAllProducts<T, P>(params, opts)` | `GET` | `/api/core/products` | Get products with provider credentials (`Access-Token`). |
|
|
496
|
+
| `getManageProducts<T, P>(params, opts)`| `GET` | `/api/core/products/manage` | Get products for manager view. |
|
|
497
|
+
| `getProductsByType<T, P>(typeId, params)`| `GET` | `/api/core/products/{productTypeUniqueId}` | Get products by ProductType. |
|
|
498
|
+
| `getProductByEntityId<T, P>(params)` | `GET` | `/api/core/products/enable/{entityId}` | Get single product by entityId. |
|
|
499
|
+
| `getProductByUniqueId<T, P>(uniqueId)` | `GET` | `/api/core/products/{uniqueId}/byProductUniqueId/enable` | Get product by uniqueId. |
|
|
500
|
+
| `getProductByBarcode<T, P>(barcode)` | `GET` | `/api/core/products/barcode/enable` | Lookup products matching a barcode. |
|
|
501
|
+
| `searchProducts<T, P>(params)` | `GET` | `/api/core/products/enable/search` | Search published products. |
|
|
502
|
+
| `addProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}/add-publish` | Create and publish new product. |
|
|
503
|
+
| `createProduct<T, P>(params)` | `POST` | `/api/core/products/{productTypeUniqueId}` | Create draft/unpublished product. |
|
|
504
|
+
| `editProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}/edit-publish` | Edit and publish existing product. |
|
|
505
|
+
| `updateProduct<T, P>(entityId, params)` | `POST` | `/api/core/products/{typeId}/{entityId}` | Update existing product. |
|
|
506
|
+
| `patchProduct<T, P>(entityId, params)` | `PATCH` | `/api/core/products/{typeId}/{entityId}` | Partial update product. |
|
|
507
|
+
| `publishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/publish/{entityId}` | Publish product. |
|
|
508
|
+
| `unpublishProduct(typeId, entityId)` | `POST` | `/api/core/products/{typeId}/unpublish/{entityId}` | Unpublish product. |
|
|
509
|
+
| `batchPublish(params)` | `PUT` | `/api/core/products/publish` | Batch publish multiple products. |
|
|
510
|
+
| `batchUnpublish(params)` | `PUT` | `/api/core/products/unpublish` | Batch unpublish multiple products. |
|
|
511
|
+
| `archiveProduct(params)` | `POST` | `/api/core/products/archive` | Archive products. |
|
|
512
|
+
| `unarchiveProduct(params)` | `POST` | `/api/core/products/unarchive` | Unarchive products. |
|
|
513
|
+
| `getArchivedProducts(params)` | `GET` | `/api/core/products/archive` | Get archived products list. |
|
|
514
|
+
| `timelineSearch(query)` | `GET` | `/api/core/products/ai/timeline-search/enable` | AI timeline search on products. |
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## Deep Dive 3: CMS Tags & Tag Categories (RAD API Swagger: tag=tags)
|
|
519
|
+
|
|
520
|
+
Manage tag categories and multi-level hierarchical tag trees ([Swagger documentation](https://rad-sandbox.sandpod.ir/api/documentation?tag=tags)).
|
|
521
|
+
|
|
522
|
+
### Accessing Tag Services
|
|
523
|
+
|
|
524
|
+
Access via `podSdk.tags` or `podSdk.cms.tags`:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
// Category Management (with Access-Token)
|
|
528
|
+
const categories = await podSdk.tags.getTagCategories({ size: 20 });
|
|
529
|
+
await podSdk.tags.createTagCategory({ name: 'Technology', desc: 'Tech articles' });
|
|
530
|
+
await podSdk.tags.publishTagCategory(categoryId);
|
|
531
|
+
|
|
532
|
+
// Public Hierarchical Tag Tree (without token, defaults to 'root')
|
|
533
|
+
const rootTree = await podSdk.tags.getTagTree('root', { levelCount: 3 });
|
|
534
|
+
|
|
535
|
+
// Backward-compatible shortcut
|
|
536
|
+
const categoriesTree = await podSdk.cms.getCategories({ levelCount: 3 });
|
|
537
|
+
|
|
538
|
+
// Tree Node Management (create, update parent, code, ancestors)
|
|
539
|
+
await podSdk.tags.createTagTreeItem('technology', {
|
|
540
|
+
name: 'Next.js',
|
|
541
|
+
code: 'NEXTJS',
|
|
542
|
+
parentId: 10,
|
|
543
|
+
});
|
|
544
|
+
await podSdk.tags.updateTagTreeParent('technology', nodeId, newParentId);
|
|
545
|
+
await podSdk.tags.updateTagTreeCode('technology', nodeId, 'NEW_CODE');
|
|
546
|
+
const ancestors = await podSdk.tags.getTagTreeAncestors('technology', nodeId);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Tag Methods Reference (`podSdk.tags` / `podSdk.cms.tags`)
|
|
550
|
+
|
|
551
|
+
| Method | HTTP | Path | Description |
|
|
552
|
+
| :--- | :--- | :--- | :--- |
|
|
553
|
+
| `getTagCategories(params)` | `GET` | `/api/core/tags/category` | List tag categories (manage). |
|
|
554
|
+
| `createTagCategory(params)` | `POST` | `/api/core/tags/category` | Create new tag category. |
|
|
555
|
+
| `getTagCategory(id)` | `GET` | `/api/core/tags/category/{id}` | Show single tag category. |
|
|
556
|
+
| `updateTagCategory(id, params)` | `PUT` | `/api/core/tags/category/{id}` | Update tag category. |
|
|
557
|
+
| `publishTagCategory(id)` | `PUT` | `/api/core/tags/category/{id}/publish` | Publish tag category. |
|
|
558
|
+
| `unpublishTagCategory(id)` | `PUT` | `/api/core/tags/category/{id}/unpublish` | Unpublish tag category. |
|
|
559
|
+
| `getTagTree(categoryId, params)` | `GET` | `/api/core/tags/{categoryId}/tree/enable` | Get enabled tag tree (public). |
|
|
560
|
+
| `getManageTagTree(categoryId, params)` | `GET` | `/api/core/tags/{categoryId}/tree/manage` | Get tag tree (manage with token). |
|
|
561
|
+
| `createTagTreeItem(categoryId, params)` | `POST` | `/api/core/tags/{categoryId}/tree` | Create tag tree node for category. |
|
|
562
|
+
| `getTagTreeItem(categoryId, id)` | `GET` | `/api/core/tags/{categoryId}/tree/{id}/enable` | Show enabled tag tree node. |
|
|
563
|
+
| `getManageTagTreeItem(categoryId, id)` | `GET` | `/api/core/tags/{categoryId}/tree/{id}/manage` | Show tag tree node (manage). |
|
|
564
|
+
| `updateTagTreeItem(categoryId, id, params)` | `PUT` | `/api/core/tags/{categoryId}/tree/{id}` | Update tag tree node. |
|
|
565
|
+
| `publishTagTreeItem(categoryId, id)` | `PUT` | `/api/core/tags/{categoryId}/tree/publish/{id}` | Publish tag tree node. |
|
|
566
|
+
| `unpublishTagTreeItem(categoryId, id)` | `PUT` | `/api/core/tags/{categoryId}/tree/unpublish/{id}` | Unpublish tag tree node. |
|
|
567
|
+
| `updateTagTreeParent(categoryId, id, parentId)` | `PUT` | `/api/core/tags/{categoryId}/tree/parent/{id}` | Update parent of tag tree node. |
|
|
568
|
+
| `getTagTreeAncestors(categoryId, id, params)` | `GET` | `/api/core/tags/{categoryId}/tree/parent/{id}/enable` | Get node ancestors (public). |
|
|
569
|
+
| `getManageTagTreeAncestors(categoryId, id, params)`| `GET` | `/api/core/tags/{categoryId}/tree/parent/{id}/manage` | Get node ancestors (manage). |
|
|
570
|
+
| `updateTagTreeCode(categoryId, id, code)` | `PUT` | `/api/core/tags/{categoryId}/tree/code/{id}` | Update code of tag tree node. |
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## Deep Dive 4: CustomPost & Generic Typed Repositories
|
|
575
|
+
|
|
576
|
+
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
577
|
+
|
|
578
|
+
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
579
|
+
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// lib/blog.ts
|
|
583
|
+
import { podSdk } from '@/lib/pod';
|
|
584
|
+
|
|
585
|
+
export interface BlogPostMeta {
|
|
586
|
+
slug: string;
|
|
587
|
+
title: string;
|
|
588
|
+
summary: string;
|
|
589
|
+
views: number;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Search posts with typed metadata query
|
|
593
|
+
export async function getPublishedPosts() {
|
|
594
|
+
const response = await podSdk.customPost.searchTimelineByMetadata<BlogPostMeta>({
|
|
595
|
+
entityName: 'blog_post',
|
|
596
|
+
metadata: { views: 100 },
|
|
597
|
+
size: 20,
|
|
598
|
+
offset: 0,
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
return response.result.map((r) => r.item.metadata);
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## Other POD Microservices
|
|
608
|
+
|
|
609
|
+
### 4. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
610
|
+
|
|
611
|
+
```typescript
|
|
612
|
+
// app/actions/auth.ts
|
|
613
|
+
'use server';
|
|
614
|
+
|
|
615
|
+
import { podSdk } from '@/lib/pod';
|
|
616
|
+
|
|
617
|
+
// Step 1: Handshake and send OTP
|
|
618
|
+
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
619
|
+
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
620
|
+
if (handshake.hasError || !handshake.result?.keyId) {
|
|
621
|
+
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Automatic RSA-SHA256 signature using Web Crypto API
|
|
625
|
+
const otpRes = await podSdk.sso.sendOtpCode(handshake.result.keyId, phoneNumber);
|
|
626
|
+
return {
|
|
627
|
+
success: !otpRes.hasError,
|
|
628
|
+
authorization: otpRes.authorization,
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Step 2: Verify OTP code & exchange for tokens
|
|
633
|
+
export async function verifyOtp(authorization: string, phoneNumber: string, code: string) {
|
|
634
|
+
const verify = await podSdk.sso.verifyOtpCode(authorization, phoneNumber, code);
|
|
635
|
+
if (verify.hasError || !verify.result?.code) {
|
|
636
|
+
throw new Error(verify.message || 'OTP verification failed');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
640
|
+
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Step 3: Fetch profile with access token
|
|
644
|
+
export async function getProfile(accessToken: string) {
|
|
645
|
+
const profile = await podSdk.sso.getUserProfile(accessToken);
|
|
646
|
+
return profile.result;
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
### 5. Podspace (File Uploads & CDN URLs)
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
// app/actions/upload.ts
|
|
656
|
+
'use server';
|
|
657
|
+
|
|
658
|
+
import { podSdk } from '@/lib/pod';
|
|
659
|
+
|
|
660
|
+
export async function uploadFile(formData: FormData) {
|
|
661
|
+
const res = await podSdk.podspace.uploadFile(formData, '/uploads', true);
|
|
662
|
+
if (res.hasError) {
|
|
663
|
+
throw new Error(res.message || 'Upload failed');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const publicUrl = podSdk.podspace.getFileUrl(res.result.hash, true);
|
|
667
|
+
return { url: publicUrl, hash: res.result.hash };
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
### 6. Notification (SMS Delivery)
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
// app/actions/notify.ts
|
|
677
|
+
'use server';
|
|
678
|
+
|
|
679
|
+
import { podSdk } from '@/lib/pod';
|
|
680
|
+
|
|
681
|
+
export async function sendSms(phoneNumber: string, text: string) {
|
|
682
|
+
return podSdk.notification.sendSms({
|
|
683
|
+
receptor: phoneNumber,
|
|
684
|
+
message: text,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
---
|
|
690
|
+
|
|
691
|
+
### 7. Social (Comments & Likes)
|
|
692
|
+
|
|
693
|
+
```typescript
|
|
694
|
+
// app/actions/social.ts
|
|
695
|
+
'use server';
|
|
696
|
+
|
|
697
|
+
import { podSdk } from '@/lib/pod';
|
|
698
|
+
|
|
699
|
+
export async function addComment(postId: number, content: string) {
|
|
700
|
+
return podSdk.social.addComment({
|
|
701
|
+
postId,
|
|
702
|
+
text: content,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
export async function likePost(postId: number) {
|
|
707
|
+
return podSdk.social.likePost(postId);
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
### 8. IUMS (Identity & University Management Service)
|
|
714
|
+
|
|
715
|
+
```typescript
|
|
716
|
+
// app/actions/iums.ts
|
|
717
|
+
'use server';
|
|
718
|
+
|
|
719
|
+
import { podSdk } from '@/lib/pod';
|
|
720
|
+
|
|
721
|
+
export async function getStudentByNationalCode(nationalCode: string) {
|
|
722
|
+
return podSdk.iums.getStudentInformationByNationalCode(nationalCode);
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
---
|
|
727
|
+
|
|
728
|
+
## Environment Variables Reference
|
|
729
|
+
|
|
730
|
+
```env
|
|
731
|
+
# .env.local
|
|
732
|
+
POD_API_TOKEN=your_api_token_here
|
|
733
|
+
POD_CLIENT_ID=your_client_id_here
|
|
734
|
+
POD_CLIENT_SECRET=your_client_secret_here
|
|
735
|
+
POD_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## License
|
|
741
|
+
|
|
742
|
+
MIT © [Reza](https://github.com/rezamirzapour2)
|