@rezamirzapour/pod-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +470 -0
- package/dist/index.d.mts +902 -0
- package/dist/index.d.ts +902 -0
- package/dist/index.js +1114 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1096 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reza
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# @rezamirzapour/pod-sdk
|
|
2
|
+
|
|
3
|
+
> Unified, type-safe, enterprise-grade SDK for **POD Platform** microservices (SSO, CustomPost, Podspace, Podform, Notification, Social, CMS, 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 8 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 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 POD CMS nested `metadata.content` arrays into flat, strongly typed objects (`formatted[fieldCode]`) and resolves image hashes to CDN URLs.
|
|
17
|
+
- 📦 **Generic CRUD Repository for CustomPost**: Instantiate typed data repositories on POD CustomPost in 2 lines with `create`, `getById`, `getAll`, `createAndBindEntityId`, and `archive`.
|
|
18
|
+
- 🔐 **Zero-Dependency Web Crypto RSA Signing**: Built-in RSA-SHA256 signature calculation for POD SSO OTP handshakes using standard Web Crypto API (Node.js, Edge Runtime, Browsers).
|
|
19
|
+
- 🚀 **Next.js App Router Ready**: Seamless support for Server Components, Server Actions, Route Handlers, and Client Components.
|
|
20
|
+
- 🌳 **Treeshakeable & Dual ESM/CJS**: Ships clean ES modules (`.mjs`) and CommonJS (`.js`) with 100% complete TypeScript declarations (`.d.ts`).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Microservices Included
|
|
25
|
+
|
|
26
|
+
| Service | Property | Description |
|
|
27
|
+
| :--- | :--- | :--- |
|
|
28
|
+
| **CMS** | `sdk.cms` | Fetch, publish, edit, and categorize CMS articles with generic metadata `<T, P>` and automatic image formatting. |
|
|
29
|
+
| **CustomPost** | `sdk.customPost` | Search timeline by metadata `<T>`, custom post CRUD, and high-level typed repository. |
|
|
30
|
+
| **SSO** | `sdk.sso` | OAuth2 handshake, OTP dispatch with digital RSA signature, OTP verify, token generation, and user profile. |
|
|
31
|
+
| **Podspace** | `sdk.podspace` | File upload (FormData) and public/private download URL resolution. |
|
|
32
|
+
| **Podform** | `sdk.podform` | Survey & form response submission, question/form structure retrieval. |
|
|
33
|
+
| **Notification** | `sdk.notification` | SMS delivery and bulk messaging with tracking. |
|
|
34
|
+
| **Social** | `sdk.social` | User comments, reactions (likes/dislikes), rates, and social post interactions. |
|
|
35
|
+
| **IUMS** | `sdk.iums` | Student and user identity inspection by national code or student ID. |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install @rezamirzapour/pod-sdk @rezamirzapour/http @rezamirzapour/logger
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
### 1. Initialize the Client
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// lib/pod.ts
|
|
53
|
+
import { createPodSdk } from '@rezamirzapour/pod-sdk';
|
|
54
|
+
|
|
55
|
+
export const podSdk = createPodSdk({
|
|
56
|
+
apiToken: process.env.POD_API_TOKEN,
|
|
57
|
+
clientId: process.env.POD_CLIENT_ID,
|
|
58
|
+
clientSecret: process.env.POD_CLIENT_SECRET,
|
|
59
|
+
privateKeyPem: process.env.POD_PRIVATE_KEY_PEM, // RSA Private Key for SSO OTP signing
|
|
60
|
+
revalidate: 3600, // Default ISR revalidation in seconds
|
|
61
|
+
urls: {
|
|
62
|
+
// Optional overrides (defaults to official POD endpoints)
|
|
63
|
+
accounts: 'https://accounts.pod.ir',
|
|
64
|
+
apiPod: 'https://api.pod.ir',
|
|
65
|
+
cms: 'https://cms.pod.ir',
|
|
66
|
+
podspace: 'https://podspace.pod.ir',
|
|
67
|
+
podform: 'https://podform.pod.ir',
|
|
68
|
+
notification: 'https://notification.pod.ir',
|
|
69
|
+
iums: 'https://iums.pod.ir',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Deep Dive: CMS & Generic Data Formatting
|
|
77
|
+
|
|
78
|
+
POD CMS stores dynamic fields inside a `metadata.content` array of `{ code, value, type }` objects. `@rezamirzapour/pod-sdk` automatically formats these into clean typed key-value pairs (`item.formatted`) and allows an optional custom `normalizer` function (`item.__normalized`).
|
|
79
|
+
|
|
80
|
+
### 1. Defining Types and Fetching CMS Articles with `getContent<T, P>`
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// app/news/page.tsx (Server Component)
|
|
84
|
+
import { podSdk } from '@/lib/pod';
|
|
85
|
+
|
|
86
|
+
// 1. Define the formatted structure of your CMS content type
|
|
87
|
+
interface NewsArticle {
|
|
88
|
+
title: string;
|
|
89
|
+
coverImage: string; // Auto-transformed from hash to full Podspace CDN URL!
|
|
90
|
+
lead: string;
|
|
91
|
+
body: string;
|
|
92
|
+
publishDate: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 2. Optional: Define normalized helper fields computed on the fly
|
|
96
|
+
interface NewsNormalized {
|
|
97
|
+
summary: string;
|
|
98
|
+
readTimeEstimate: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default async function NewsPage() {
|
|
102
|
+
// Call getContent with exact generic parameters: <T, P>
|
|
103
|
+
const response = await podSdk.cms.getContent<NewsArticle, NewsNormalized>(
|
|
104
|
+
{
|
|
105
|
+
contentTypeUniqueId: 'company_news',
|
|
106
|
+
size: 10,
|
|
107
|
+
offset: 0,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
// Optional: compute custom normalized fields
|
|
111
|
+
normalizer: (item) => ({
|
|
112
|
+
summary: item.formatted.lead || item.formatted.title,
|
|
113
|
+
readTimeEstimate: Math.ceil((item.formatted.body?.length || 0) / 500),
|
|
114
|
+
}),
|
|
115
|
+
// Next.js ISR options
|
|
116
|
+
revalidate: 1800,
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<div className="container mx-auto p-6">
|
|
122
|
+
<h1 className="text-2xl font-bold mb-4">Latest News</h1>
|
|
123
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
124
|
+
{response.result.map((item) => (
|
|
125
|
+
<article key={item.id} className="border rounded-lg p-4 shadow">
|
|
126
|
+
{/* item.formatted is 100% typed as NewsArticle */}
|
|
127
|
+
{item.formatted.coverImage && (
|
|
128
|
+
<img
|
|
129
|
+
src={item.formatted.coverImage}
|
|
130
|
+
alt={item.formatted.title}
|
|
131
|
+
className="w-full h-48 object-cover rounded"
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
<h2 className="text-xl font-semibold mt-2">{item.formatted.title}</h2>
|
|
135
|
+
<p className="text-gray-600 mt-1">{item.__normalized.summary}</p>
|
|
136
|
+
<span className="text-xs text-gray-400">
|
|
137
|
+
Estimated read: {item.__normalized.readTimeEstimate} min
|
|
138
|
+
</span>
|
|
139
|
+
</article>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2. Fetching Single Content by Entity ID: `getContentByEntityId<T>`
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// app/news/[id]/page.tsx
|
|
151
|
+
import { podSdk } from '@/lib/pod';
|
|
152
|
+
import { notFound } from 'next/navigation';
|
|
153
|
+
|
|
154
|
+
interface ArticleDetail {
|
|
155
|
+
title: string;
|
|
156
|
+
content: string;
|
|
157
|
+
author: string;
|
|
158
|
+
coverImage: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default async function NewsDetailPage({ params }: { params: { id: string } }) {
|
|
162
|
+
const res = await podSdk.cms.getContentByEntityId<ArticleDetail>({
|
|
163
|
+
entityId: Number(params.id),
|
|
164
|
+
contentTypeUniqueId: 'company_news',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const article = res.result?.[0];
|
|
168
|
+
if (!article) notFound();
|
|
169
|
+
|
|
170
|
+
return (
|
|
171
|
+
<main>
|
|
172
|
+
<h1>{article.formatted.title}</h1>
|
|
173
|
+
<p>By {article.formatted.author}</p>
|
|
174
|
+
<div dangerouslySetInnerHTML={{ __html: article.formatted.content }} />
|
|
175
|
+
</main>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 3. Adding and Editing CMS Content: `addContent<T>` & `editContent<T>`
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// app/actions/cms.ts
|
|
184
|
+
'use server';
|
|
185
|
+
|
|
186
|
+
import { podSdk } from '@/lib/pod';
|
|
187
|
+
|
|
188
|
+
export async function createNewsArticle(data: {
|
|
189
|
+
title: string;
|
|
190
|
+
body: string;
|
|
191
|
+
imageHash: string;
|
|
192
|
+
}) {
|
|
193
|
+
return podSdk.cms.addContent({
|
|
194
|
+
contentTypeUniqueId: 'company_news',
|
|
195
|
+
body: {
|
|
196
|
+
name: data.title,
|
|
197
|
+
fieldCode: ['title', 'body', 'coverImage'],
|
|
198
|
+
fieldValue: [data.title, data.body, data.imageHash],
|
|
199
|
+
canComment: true,
|
|
200
|
+
canLike: true,
|
|
201
|
+
enable: true,
|
|
202
|
+
isSearchable: true,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Deep Dive: CustomPost & Generic Typed Repositories
|
|
211
|
+
|
|
212
|
+
`CustomPost` allows persisting arbitrary JSON schemas on the POD Platform. `@rezamirzapour/pod-sdk` provides two complementary approaches:
|
|
213
|
+
|
|
214
|
+
1. **Direct Service Method Calls with Generics (`searchTimelineByMetadata<T>`, `getCustomPost<T>`, `addCustomPost<T>`)**
|
|
215
|
+
2. **High-Level Typed CRUD Repository (`CustomPostCrudService<DataType>`)**
|
|
216
|
+
|
|
217
|
+
### 1. Direct Generic Method Usage
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// lib/blog.ts
|
|
221
|
+
import { podSdk } from '@/lib/pod';
|
|
222
|
+
|
|
223
|
+
export interface BlogPostMeta {
|
|
224
|
+
slug: string;
|
|
225
|
+
title: string;
|
|
226
|
+
summary: string;
|
|
227
|
+
views: number;
|
|
228
|
+
publishedAt: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 1. Search posts with typed metadata query
|
|
232
|
+
export async function getPublishedPosts() {
|
|
233
|
+
const response = await podSdk.customPost.searchTimelineByMetadata<BlogPostMeta>({
|
|
234
|
+
entityName: 'blog_post',
|
|
235
|
+
metadata: { views: 100 }, // Partial match or query
|
|
236
|
+
size: 20,
|
|
237
|
+
offset: 0,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// response.result[i].item.metadata is strongly typed as BlogPostMeta
|
|
241
|
+
return response.result.map((r) => r.item.metadata);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 2. Add a new custom post with typed metadata
|
|
245
|
+
export async function createBlogPost(meta: BlogPostMeta) {
|
|
246
|
+
return podSdk.customPost.addCustomPost<BlogPostMeta>({
|
|
247
|
+
name: 'blog_post',
|
|
248
|
+
content: meta.title,
|
|
249
|
+
metadata: meta, // Strongly typed
|
|
250
|
+
enable: true,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 2. High-Level Generic CRUD Repository (`createCrud<DataType>`)
|
|
256
|
+
|
|
257
|
+
Create an ORM-like typed repository for any entity type in seconds:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
// lib/products.ts
|
|
261
|
+
import { podSdk } from '@/lib/pod';
|
|
262
|
+
|
|
263
|
+
export interface ProductData {
|
|
264
|
+
title: string;
|
|
265
|
+
sku: string;
|
|
266
|
+
price: number;
|
|
267
|
+
inStock: boolean;
|
|
268
|
+
category: string;
|
|
269
|
+
entityId?: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Instantiate typed CRUD service for "product_item"
|
|
273
|
+
export const productCrud = podSdk.createCrud<ProductData>({
|
|
274
|
+
name: 'product_entity',
|
|
275
|
+
type: 'commerce',
|
|
276
|
+
detailedType: 'gadgets',
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
All methods on `productCrud` are fully generic:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// app/actions/products.ts
|
|
284
|
+
'use server';
|
|
285
|
+
|
|
286
|
+
import { productCrud } from '@/lib/products';
|
|
287
|
+
import { revalidatePath } from 'next/cache';
|
|
288
|
+
|
|
289
|
+
// 1. Fetch all products
|
|
290
|
+
export async function getProducts() {
|
|
291
|
+
const result = await productCrud.getAll();
|
|
292
|
+
return result.result.map((r) => r.item.metadata.data);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 2. Get single product by numerical ID
|
|
296
|
+
export async function getProductById(id: number) {
|
|
297
|
+
const res = await productCrud.getById(id);
|
|
298
|
+
return res.result?.[0]?.metadata.data;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 3. Create product and automatically bind entityId
|
|
302
|
+
export async function saveNewProduct(data: { title: string; sku: string; price: number }) {
|
|
303
|
+
const res = await productCrud.createAndBindEntityId({
|
|
304
|
+
title: data.title,
|
|
305
|
+
sku: data.sku,
|
|
306
|
+
price: data.price,
|
|
307
|
+
inStock: true,
|
|
308
|
+
category: 'electronics',
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
revalidatePath('/products');
|
|
312
|
+
return res;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 4. Update product
|
|
316
|
+
export async function updateProduct(entityId: number, data: ProductData) {
|
|
317
|
+
return productCrud.update(entityId, data);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 5. Archive (soft-disable without deleting)
|
|
321
|
+
export async function archiveProduct(entityId: number, data: ProductData) {
|
|
322
|
+
return productCrud.archive(entityId, data);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 6. Delete permanently
|
|
326
|
+
export async function deleteProduct(entityId: number) {
|
|
327
|
+
return productCrud.delete(entityId);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Other POD Microservices
|
|
334
|
+
|
|
335
|
+
### 3. POD SSO (OTP Handshake with Web Crypto RSA Signature)
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// app/actions/auth.ts
|
|
339
|
+
'use server';
|
|
340
|
+
|
|
341
|
+
import { podSdk } from '@/lib/pod';
|
|
342
|
+
|
|
343
|
+
// Step 1: Handshake and send OTP
|
|
344
|
+
export async function sendOtp(phoneNumber: string, clientIp: string) {
|
|
345
|
+
// Handshake to obtain keyId
|
|
346
|
+
const handshake = await podSdk.sso.handshake(`web-${Date.now()}`, clientIp);
|
|
347
|
+
if (handshake.hasError || !handshake.result?.keyId) {
|
|
348
|
+
throw new Error(handshake.message || 'SSO Handshake failed');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Automatic RSA-SHA256 signature using Web Crypto API
|
|
352
|
+
const otpRes = await podSdk.sso.sendOtpCode(handshake.result.keyId, phoneNumber);
|
|
353
|
+
return {
|
|
354
|
+
success: !otpRes.hasError,
|
|
355
|
+
authorization: otpRes.authorization,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Step 2: Verify OTP code & exchange for tokens
|
|
360
|
+
export async function verifyOtp(authorization: string, phoneNumber: string, code: string) {
|
|
361
|
+
const verify = await podSdk.sso.verifyOtpCode(authorization, phoneNumber, code);
|
|
362
|
+
if (verify.hasError || !verify.result?.code) {
|
|
363
|
+
throw new Error(verify.message || 'OTP verification failed');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Exchange authorization code for JWT tokens
|
|
367
|
+
const tokens = await podSdk.sso.generateToken(verify.result.code);
|
|
368
|
+
return tokens.result; // { access_token, refresh_token, expires_in }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Step 3: Fetch profile with access token
|
|
372
|
+
export async function getProfile(accessToken: string) {
|
|
373
|
+
const profile = await podSdk.sso.getUserProfile(accessToken);
|
|
374
|
+
return profile.result;
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
### 4. Podspace (File Uploads & CDN URLs)
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// app/actions/upload.ts
|
|
384
|
+
'use server';
|
|
385
|
+
|
|
386
|
+
import { podSdk } from '@/lib/pod';
|
|
387
|
+
|
|
388
|
+
export async function uploadFile(formData: FormData) {
|
|
389
|
+
const res = await podSdk.podspace.uploadFile(formData, '/uploads', true);
|
|
390
|
+
if (res.hasError) {
|
|
391
|
+
throw new Error(res.message || 'Upload failed');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const publicUrl = podSdk.podspace.getFileUrl(res.result.hash, true);
|
|
395
|
+
return { url: publicUrl, hash: res.result.hash };
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
### 5. Notification (SMS Delivery)
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
// app/actions/notify.ts
|
|
405
|
+
'use server';
|
|
406
|
+
|
|
407
|
+
import { podSdk } from '@/lib/pod';
|
|
408
|
+
|
|
409
|
+
export async function sendSms(phoneNumber: string, text: string) {
|
|
410
|
+
return podSdk.notification.sendSms({
|
|
411
|
+
receptor: phoneNumber,
|
|
412
|
+
message: text,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
### 6. Social (Comments & Likes)
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// app/actions/social.ts
|
|
423
|
+
'use server';
|
|
424
|
+
|
|
425
|
+
import { podSdk } from '@/lib/pod';
|
|
426
|
+
|
|
427
|
+
export async function addComment(postId: number, content: string) {
|
|
428
|
+
return podSdk.social.addComment({
|
|
429
|
+
postId,
|
|
430
|
+
text: content,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function likePost(postId: number) {
|
|
435
|
+
return podSdk.social.likePost(postId);
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
### 7. IUMS (Identity & University Management Service)
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
// app/actions/iums.ts
|
|
445
|
+
'use server';
|
|
446
|
+
|
|
447
|
+
import { podSdk } from '@/lib/pod';
|
|
448
|
+
|
|
449
|
+
export async function getStudentByNationalCode(nationalCode: string) {
|
|
450
|
+
return podSdk.iums.getStudentInformationByNationalCode(nationalCode);
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## Environment Variables Reference
|
|
457
|
+
|
|
458
|
+
```env
|
|
459
|
+
# .env.local
|
|
460
|
+
POD_API_TOKEN=your_api_token_here
|
|
461
|
+
POD_CLIENT_ID=your_client_id_here
|
|
462
|
+
POD_CLIENT_SECRET=your_client_secret_here
|
|
463
|
+
POD_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## License
|
|
469
|
+
|
|
470
|
+
MIT © [Reza](https://github.com/rezamirzapour2)
|