@rezamirzapour/pod-sdk 1.0.4 → 1.0.7
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 +971 -713
- package/dist/index.d.mts +1460 -20
- package/dist/index.d.ts +1460 -20
- package/dist/index.js +1726 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1711 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +60 -60
package/README.md
CHANGED
|
@@ -1,713 +1,971 @@
|
|
|
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` | - |
|
|
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` | `public-apis` | Complete Cloud Storage: file/image uploads, resumable chunked uploads, folders, versions, share links, workspaces, trash, tags, & metadata. |
|
|
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
|
+
## Deep Dive 5: Podspace Cloud Storage (Official Swagger API)
|
|
608
|
+
|
|
609
|
+
The **Podspace** module provides complete enterprise integration with the official POD Podspace Cloud Storage service ([Swagger documentation: `podspace.sandpod.ir/api/docs`](http://podspace.sandpod.ir/api/docs)). It supports standard and resumable uploads, public/private files, dynamic CDN image transformations, folder hierarchy, file versioning, public share links, trash recovery, team workspaces, user groups, and custom metadata.
|
|
610
|
+
|
|
611
|
+
### Sub-Services Architecture
|
|
612
|
+
|
|
613
|
+
All Podspace operations are accessible via specialized, modular sub-services under `podSdk.podspace`:
|
|
614
|
+
|
|
615
|
+
```typescript
|
|
616
|
+
podSdk.podspace.files // File details, rename, move, copy, search, versions, zip
|
|
617
|
+
podSdk.podspace.folders // Folder creation, nested paths, children, actions
|
|
618
|
+
podSdk.podspace.resumable // Chunked/large file upload (tus protocol: create, append, finalize)
|
|
619
|
+
podSdk.podspace.links // Public/password-protected shareable download links
|
|
620
|
+
podSdk.podspace.shares // Sharing files/folders with specific users & permissions
|
|
621
|
+
podSdk.podspace.trash // Recycle bin: list, restore, empty, auto-cleanup
|
|
622
|
+
podSdk.podspace.bookmarks // Starred / favorite files and folders
|
|
623
|
+
podSdk.podspace.tags // File & folder tagging
|
|
624
|
+
podSdk.podspace.metadata // Custom key-value metadata & descriptions
|
|
625
|
+
podSdk.podspace.userGroups // Group storage, member uploads & quota usage
|
|
626
|
+
podSdk.podspace.workspaces // Team workspaces & member roles
|
|
627
|
+
podSdk.podspace.me // User storage usage, plans, personal/chat folders
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
> [!TIP]
|
|
631
|
+
> Commonly used methods (`uploadFile`, `downloadFile`, `getFileUrl`, `getImageUrl`, `getThumbnailUrl`, `createFolder`, `searchFiles`) are also directly available on `podSdk.podspace` for maximum developer convenience.
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
### Key Usage Examples
|
|
636
|
+
|
|
637
|
+
#### 1. File Upload & CDN URLs (100% Backward Compatible)
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
// app/actions/upload.ts
|
|
641
|
+
'use server';
|
|
642
|
+
|
|
643
|
+
import { podSdk } from '@/lib/pod';
|
|
644
|
+
|
|
645
|
+
export async function uploadDocument(formData: FormData) {
|
|
646
|
+
// Supports both positional args (formData, path, isPublic) and options object
|
|
647
|
+
const res = await podSdk.podspace.uploadFile(formData, {
|
|
648
|
+
path: '/documents/invoices',
|
|
649
|
+
isPublic: true,
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
if (res.hasError) {
|
|
653
|
+
throw new Error(res.message || 'Upload failed');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const file = res.result;
|
|
657
|
+
|
|
658
|
+
// Resolve direct download URL
|
|
659
|
+
const downloadUrl = podSdk.podspace.getFileUrl(file.hash, true);
|
|
660
|
+
|
|
661
|
+
return {
|
|
662
|
+
hash: file.hash,
|
|
663
|
+
name: file.name,
|
|
664
|
+
size: file.size,
|
|
665
|
+
url: downloadUrl,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
#### 2. Image & Thumbnail CDN URL Helper
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
// Helper functions generate instant CDN URLs without network calls:
|
|
674
|
+
const fullImageUrl = podSdk.podspace.getImageUrl(imageHash, {
|
|
675
|
+
isPublic: true,
|
|
676
|
+
size: '1200x800',
|
|
677
|
+
quality: 85,
|
|
678
|
+
crop: true,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const thumbUrl = podSdk.podspace.getThumbnailUrl(imageHash, {
|
|
682
|
+
isPublic: true,
|
|
683
|
+
size: '150x150',
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### 3. Base64 Image Upload
|
|
688
|
+
|
|
689
|
+
```typescript
|
|
690
|
+
// Upload image directly from a Base64 data URL
|
|
691
|
+
const uploadRes = await podSdk.podspace.uploadImageBase64({
|
|
692
|
+
base64: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAE...',
|
|
693
|
+
filename: 'avatar.png',
|
|
694
|
+
path: '/avatars',
|
|
695
|
+
isPublic: true,
|
|
696
|
+
});
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
#### 4. Resumable Chunked Upload (Large Files)
|
|
700
|
+
|
|
701
|
+
For reliable multi-gigabyte uploads with pause/resume support:
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
// Step 1: Initialize resumable session
|
|
705
|
+
const session = await podSdk.podspace.resumable.create({
|
|
706
|
+
filename: 'presentation.mp4',
|
|
707
|
+
fileSize: totalBytes,
|
|
708
|
+
path: '/videos',
|
|
709
|
+
isPublic: false,
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
const uploadUrl = session.uploadUrl;
|
|
713
|
+
|
|
714
|
+
// Step 2: Check current server offset if resuming
|
|
715
|
+
const offset = await podSdk.podspace.resumable.getStatus(uploadUrl);
|
|
716
|
+
|
|
717
|
+
// Step 3: Append chunks
|
|
718
|
+
await podSdk.podspace.resumable.append({
|
|
719
|
+
uploadUrl,
|
|
720
|
+
offset,
|
|
721
|
+
chunk: chunkBuffer,
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
// Step 4: Finalize when all bytes are uploaded
|
|
725
|
+
const completedFile = await podSdk.podspace.resumable.finalizeUpload(uploadUrl);
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### 5. Folder Hierarchy & Listing
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
// Create a folder
|
|
732
|
+
await podSdk.podspace.folders.createFolder({
|
|
733
|
+
name: 'Reports-2026',
|
|
734
|
+
path: '/finance',
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
// Recursively create nested directory paths
|
|
738
|
+
await podSdk.podspace.folders.createDirectories('/finance/2026/Q1/receipts');
|
|
739
|
+
|
|
740
|
+
// List folder contents with pagination
|
|
741
|
+
const contents = await podSdk.podspace.folders.getFolderChildren({
|
|
742
|
+
path: '/finance/Reports-2026',
|
|
743
|
+
offset: 0,
|
|
744
|
+
size: 50,
|
|
745
|
+
});
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
#### 6. Shareable Public / Password-Protected Links
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
// Create an expiring, password-protected download link
|
|
752
|
+
const linkRes = await podSdk.podspace.links.createLink(fileHash, {
|
|
753
|
+
type: 'DOWNLOAD',
|
|
754
|
+
password: 'SecurePassword123!',
|
|
755
|
+
expiresAt: Date.now() + 7 * 24 * 3600 * 1000, // 7 days
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// Share with a specific user
|
|
759
|
+
await podSdk.podspace.shares.shareWithUser(fileHash, {
|
|
760
|
+
username: 'john_doe',
|
|
761
|
+
permission: 'READ',
|
|
762
|
+
});
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
#### 7. File Versions & Rollback
|
|
766
|
+
|
|
767
|
+
```typescript
|
|
768
|
+
// List previous versions of a file
|
|
769
|
+
const versions = await podSdk.podspace.files.getFileVersions(fileHash);
|
|
770
|
+
|
|
771
|
+
// Rollback to a specific historical version
|
|
772
|
+
await podSdk.podspace.files.rollbackFileVersion(fileHash, versionId);
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
#### 8. Trash & Recycling
|
|
776
|
+
|
|
777
|
+
```typescript
|
|
778
|
+
// Move file or folder to trash
|
|
779
|
+
await podSdk.podspace.files.trashEntity(fileHash);
|
|
780
|
+
|
|
781
|
+
// List recycle bin
|
|
782
|
+
const trashItems = await podSdk.podspace.trash.getTrashList({ size: 20 });
|
|
783
|
+
|
|
784
|
+
// Restore or permanently delete
|
|
785
|
+
await podSdk.podspace.trash.restore(fileHash);
|
|
786
|
+
await podSdk.podspace.trash.deletePermanently(fileHash);
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
### Podspace Methods Reference
|
|
792
|
+
|
|
793
|
+
| Sub-Service | Method | HTTP | Path | Description |
|
|
794
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
795
|
+
| **Upload / Download** | `uploadFile(formData, params)` | `POST` | `/api/files` | Upload single file with metadata. |
|
|
796
|
+
| | `uploadMultipleFiles(formData, params)` | `POST` | `/api/files/batch` | Upload multiple files simultaneously. |
|
|
797
|
+
| | `uploadImageBase64(params)` | `POST` | `/api/images/base64` | Upload base64 encoded image. |
|
|
798
|
+
| | `replaceFile(hash, formData)` | `PUT` | `/api/files/{hash}` | Overwrite existing file content. |
|
|
799
|
+
| | `uploadByLink(params)` | `POST` | `/api/files/link` | Download remote file into Podspace. |
|
|
800
|
+
| | `downloadFile(hash, opts)` | `GET` | `/api/files/{hash}` | Download raw binary file. |
|
|
801
|
+
| | `downloadImage(hash, opts)` | `GET` | `/api/images/{hash}` | Download transformed image. |
|
|
802
|
+
| | `downloadThumbnail(hash, opts)` | `GET` | `/api/images/thumbnails/{hash}` | Download image thumbnail. |
|
|
803
|
+
| | `downloadFilesAsZip(hashes)` | `POST` | `/api/files/zip` | Batch download files as a ZIP archive. |
|
|
804
|
+
| | `getFileUrl(hash, isPublic)` | - | Client Helper | Generates direct file CDN link. |
|
|
805
|
+
| | `getImageUrl(hash, opts)` | - | Client Helper | Generates transformed image CDN link. |
|
|
806
|
+
| | `getThumbnailUrl(hash, opts)` | - | Client Helper | Generates thumbnail CDN link. |
|
|
807
|
+
| **Files** | `getFileDetail(hash)` | `GET` | `/api/files/{hash}/metadata` | Fetch full metadata for file. |
|
|
808
|
+
| | `getFileDetailByPath(path)` | `GET` | `/api/files/path` | Fetch metadata by absolute path. |
|
|
809
|
+
| | `checkEntityExist(hash)` | `GET` | `/api/entities/{hash}/exist` | Check existence of file/folder. |
|
|
810
|
+
| | `renameEntity(hash, newName)` | `PUT` | `/api/entities/{hash}/rename` | Rename file or folder. |
|
|
811
|
+
| | `moveEntity(hash, targetPath)` | `PUT` | `/api/entities/{hash}/move` | Move file or folder. |
|
|
812
|
+
| | `copyEntity(hash, targetPath)` | `POST` | `/api/entities/{hash}/copy` | Duplicate file or folder. |
|
|
813
|
+
| | `trashEntity(hash)` | `DELETE` | `/api/entities/{hash}` | Move file/folder to trash. |
|
|
814
|
+
| | `searchFiles(params)` | `GET` | `/api/files/search` | Search files by name, type, date, or tags. |
|
|
815
|
+
| | `getFileVersions(hash)` | `GET` | `/api/files/{hash}/versions` | List all historical versions. |
|
|
816
|
+
| | `rollbackFileVersion(hash, id)` | `POST` | `/api/files/{hash}/versions/{id}/rollback` | Rollback to specific version. |
|
|
817
|
+
| | `compressToZip(params)` | `POST` | `/api/files/compress` | Compress files into a ZIP archive. |
|
|
818
|
+
| | `extractZip(params)` | `POST` | `/api/files/extract` | Extract ZIP archive in Podspace. |
|
|
819
|
+
| **Folders** | `createFolder(params)` | `POST` | `/api/folders` | Create a new folder. |
|
|
820
|
+
| | `createDirectories(path)` | `POST` | `/api/folders/directories` | Create nested directory tree. |
|
|
821
|
+
| | `getFolderChildren(params)` | `GET` | `/api/folders/{hash}/children` | List folder contents. |
|
|
822
|
+
| | `getRecentFolders(params)` | `GET` | `/api/folders/recent` | List recently accessed folders. |
|
|
823
|
+
| **Resumable** | `create(params)` | `POST` | `/api/files/resumable` | Initialize resumable upload. |
|
|
824
|
+
| | `append(params)` | `PATCH` | `{uploadUrl}` | Upload chunk to offset. |
|
|
825
|
+
| | `getStatus(uploadUrl)` | `HEAD` | `{uploadUrl}` | Get current uploaded byte offset. |
|
|
826
|
+
| | `finalizeUpload(uploadUrl)` | `POST` | `{uploadUrl}/finalize` | Complete resumable upload. |
|
|
827
|
+
| **Links** | `createLink(hash, params)` | `POST` | `/api/entities/{hash}/links` | Create shareable download link. |
|
|
828
|
+
| | `getUserLinks(params)` | `GET` | `/api/links` | List all created links. |
|
|
829
|
+
| | `revokeLink(linkHash)` | `DELETE` | `/api/links/{linkHash}` | Deactivate share link. |
|
|
830
|
+
| **Shares** | `shareWithUser(hash, params)` | `POST` | `/api/entities/{hash}/shares` | Share with another user. |
|
|
831
|
+
| | `makePublic(hash, isPublic)` | `PUT` | `/api/entities/{hash}/public` | Toggle public/private access. |
|
|
832
|
+
| | `getSharedWithMe(params)` | `GET` | `/api/shares/shared-with-me` | List items shared with user. |
|
|
833
|
+
| **Trash** | `getTrashList(params)` | `GET` | `/api/trash` | List items in recycle bin. |
|
|
834
|
+
| | `restore(hash)` | `POST` | `/api/trash/{hash}/restore` | Restore file/folder. |
|
|
835
|
+
| | `deletePermanently(hash)` | `DELETE` | `/api/trash/{hash}` | Permanently destroy file/folder. |
|
|
836
|
+
| | `emptyTrash()` | `DELETE` | `/api/trash` | Empty entire recycle bin. |
|
|
837
|
+
| **Bookmarks** | `getBookmarks(params)` | `GET` | `/api/bookmarks` | List starred files/folders. |
|
|
838
|
+
| | `addBookmark(hash)` | `POST` | `/api/bookmarks/{hash}` | Star a file or folder. |
|
|
839
|
+
| | `removeBookmark(hash)` | `DELETE` | `/api/bookmarks/{hash}` | Unstar a file or folder. |
|
|
840
|
+
| **Tags** | `addTags(hash, tags)` | `POST` | `/api/entities/{hash}/tags` | Add tags to entity. |
|
|
841
|
+
| | `getEntityTags(hash)` | `GET` | `/api/entities/{hash}/tags` | List entity tags. |
|
|
842
|
+
| | `getEntitiesByTag(tag)` | `GET` | `/api/tags/{tag}/entities` | Search entities by tag. |
|
|
843
|
+
| **Metadata** | `setMetadata(hash, metadata)` | `PUT` | `/api/entities/{hash}/metadata` | Set custom key-value metadata. |
|
|
844
|
+
| | `setDescription(hash, desc)` | `PUT` | `/api/entities/{hash}/description` | Set entity description. |
|
|
845
|
+
| **User Groups** | `createUserGroup(params)` | `POST` | `/api/user-groups` | Create user group. |
|
|
846
|
+
| | `uploadToUserGroup(group, fd)`| `POST` | `/api/user-groups/{group}/files` | Upload file to user group. |
|
|
847
|
+
| | `getUserGroupUsage(group)` | `GET` | `/api/user-groups/{group}/usage` | Get storage quota & usage. |
|
|
848
|
+
| **Workspaces** | `createWorkspace(params)` | `POST` | `/api/workspaces` | Create team workspace. |
|
|
849
|
+
| | `getWorkspaceMembers(id)` | `GET` | `/api/workspaces/{id}/members` | List workspace members. |
|
|
850
|
+
| **Me** | `getUser()` | `GET` | `/api/users/me` | Current user profile. |
|
|
851
|
+
| | `getUserUsageReport()` | `GET` | `/api/users/me/usage` | Total storage usage report. |
|
|
852
|
+
| | `getPersonalFolder()` | `GET` | `/api/users/me/personal-folder` | Get or create root personal folder. |
|
|
853
|
+
| | `getOrCreateChatFolder()` | `GET` | `/api/users/me/chat-folder` | Get or create chat attachments folder. |
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## Other POD Microservices
|
|
858
|
+
|
|
859
|
+
### 1. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
// app/actions/auth.ts
|
|
863
|
+
'use server';
|
|
864
|
+
|
|
865
|
+
import { podSdk } from '@/lib/pod';
|
|
866
|
+
|
|
867
|
+
// Step 1: Handshake and send OTP
|
|
868
|
+
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
869
|
+
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
870
|
+
if (handshake.hasError || !handshake.result?.keyId) {
|
|
871
|
+
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Automatic RSA-SHA256 signature using Web Crypto API
|
|
875
|
+
const otpRes = await podSdk.sso.sendOtpCode(handshake.result.keyId, phoneNumber);
|
|
876
|
+
return {
|
|
877
|
+
success: !otpRes.hasError,
|
|
878
|
+
authorization: otpRes.authorization,
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Step 2: Verify OTP code & exchange for tokens
|
|
883
|
+
export async function verifyOtp(authorization: string, phoneNumber: string, code: string) {
|
|
884
|
+
const verify = await podSdk.sso.verifyOtpCode(authorization, phoneNumber, code);
|
|
885
|
+
if (verify.hasError || !verify.result?.code) {
|
|
886
|
+
throw new Error(verify.message || 'OTP verification failed');
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
890
|
+
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Step 3: Fetch profile with access token
|
|
894
|
+
export async function getProfile(accessToken: string) {
|
|
895
|
+
const profile = await podSdk.sso.getUserProfile(accessToken);
|
|
896
|
+
return profile.result;
|
|
897
|
+
}
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
### 2. Notification (SMS Delivery)
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
// app/actions/notify.ts
|
|
906
|
+
'use server';
|
|
907
|
+
|
|
908
|
+
import { podSdk } from '@/lib/pod';
|
|
909
|
+
|
|
910
|
+
export async function sendSms(phoneNumber: string, text: string) {
|
|
911
|
+
return podSdk.notification.sendSms({
|
|
912
|
+
receptor: phoneNumber,
|
|
913
|
+
message: text,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
### 3. Social (Comments & Likes)
|
|
921
|
+
|
|
922
|
+
```typescript
|
|
923
|
+
// app/actions/social.ts
|
|
924
|
+
'use server';
|
|
925
|
+
|
|
926
|
+
import { podSdk } from '@/lib/pod';
|
|
927
|
+
|
|
928
|
+
export async function addComment(postId: number, content: string) {
|
|
929
|
+
return podSdk.social.addComment({
|
|
930
|
+
postId,
|
|
931
|
+
text: content,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export async function likePost(postId: number) {
|
|
936
|
+
return podSdk.social.likePost(postId);
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
---
|
|
941
|
+
|
|
942
|
+
### 4. IUMS (Identity & University Management Service)
|
|
943
|
+
|
|
944
|
+
```typescript
|
|
945
|
+
// app/actions/iums.ts
|
|
946
|
+
'use server';
|
|
947
|
+
|
|
948
|
+
import { podSdk } from '@/lib/pod';
|
|
949
|
+
|
|
950
|
+
export async function getStudentByNationalCode(nationalCode: string) {
|
|
951
|
+
return podSdk.iums.getStudentInformationByNationalCode(nationalCode);
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Environment Variables Reference
|
|
958
|
+
|
|
959
|
+
```env
|
|
960
|
+
# .env.local
|
|
961
|
+
POD_API_TOKEN=your_api_token_here
|
|
962
|
+
POD_CLIENT_ID=your_client_id_here
|
|
963
|
+
POD_CLIENT_SECRET=your_client_secret_here
|
|
964
|
+
POD_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
---
|
|
968
|
+
|
|
969
|
+
## License
|
|
970
|
+
|
|
971
|
+
MIT © [Reza](https://github.com/rezamirzapour2)
|