@rezamirzapour/pod-sdk 1.0.5 → 1.0.8

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @rezamirzapour/pod-sdk
2
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**.
3
+ > Unified, type-safe, enterprise-grade SDK for **POD Platform** microservices (CMS Content, CMS Product, SSO, Captcha, CustomPost, Podspace, Podform, Notification, Social, IUMS), engineered specifically for **Next.js App Router**, **Server Components**, and **Server Actions**.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@rezamirzapour/pod-sdk.svg)](https://www.npmjs.com/package/@rezamirzapour/pod-sdk)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
@@ -8,8 +8,9 @@
8
8
 
9
9
  ---
10
10
 
11
- ## Features
12
-
11
+ ## Features
12
+
13
+ - **Captcha Protection**: Generate configurable SVG/PNG challenges and validate user answers with a single, typed service.
13
14
  - 🌐 **Unified Multi-Service Architecture**: Single entry-point managing official POD microservices with official endpoints and configurations.
14
15
  - ⚡ **Powered by `@rezamirzapour/http`**: Automatic exponential backoff retries, Next.js incremental static regeneration (ISR) caching (`next.revalidate`, tags), and isomorphic execution.
15
16
  - 🧬 **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.
@@ -20,18 +21,19 @@
20
21
  - 🚀 **Next.js App Router Ready**: Seamless support for Server Components, Server Actions, Route Handlers, and Client Components.
21
22
  - 🌳 **Treeshakeable & Dual ESM/CJS**: Ships clean ES modules (`.mjs`) and CommonJS (`.js`) with 100% complete TypeScript declarations (`.d.ts`).
22
23
 
23
- ---
24
-
25
- ## Microservices Included
24
+ ---
25
+
26
+ ## Microservices Included
26
27
 
27
28
  | Service | Accessor | Swagger Tag | Description |
28
29
  | :--- | :--- | :--- | :--- |
29
30
  | **CMS Content** | `sdk.cms` | `content` | Fetch, publish, edit, draft, archive, and categorize CMS articles with generic metadata `<T, P>`. |
30
31
  | **CMS Product** | `sdk.product` / `sdk.cms.products` | `product` | Product catalog, price & discount range filters, barcode lookup, batch publish, and AI search. |
31
32
  | **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. |
33
+ | **CustomPost** | `sdk.customPost` | - | Search timeline by metadata `<T>`, custom post CRUD, and high-level typed repository. |
34
+ | **SSO** | `sdk.sso` | - | OAuth2 handshake, OTP dispatch with digital RSA signature, OTP verify, token generation, and user profile. |
35
+ | **Captcha** | `sdk.captcha` | `captcha` | Generate SVG/PNG captcha images with configurable options and verify submitted values. |
36
+ | **Podspace** | `sdk.podspace` | `public-apis` | Complete Cloud Storage: file/image uploads, resumable chunked uploads, folders, versions, share links, workspaces, trash, tags, & metadata. |
35
37
  | **Podform** | `sdk.podform` | - | Survey & form response submission, question/form structure retrieval. |
36
38
  | **Notification** | `sdk.notification` | - | SMS delivery and bulk messaging with tracking. |
37
39
  | **Social** | `sdk.social` | - | User comments, reactions (likes/dislikes), rates, and social post interactions. |
@@ -58,9 +60,10 @@ NEXT_PUBLIC_API_POD_URL=https://api.pod.ir
58
60
  NEXT_PUBLIC_API_POD_SANDBOX_URL=https://api.sandpod.ir
59
61
  NEXT_PUBLIC_CMS_URL=https://api.pod.ir/srv/cms-server
60
62
  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
63
+ NEXT_PUBLIC_NOTIFICATION_URL=https://api.pod.ir/srv/notification
64
+ NEXT_PUBLIC_PODREPORT_URL=https://reporting.pod.ir
65
+ NEXT_PUBLIC_PODSPACE_URL=https://podspace.pod.ir
66
+ NEXT_PUBLIC_PODFORM_URL=https://podform.pod.ir
64
67
 
65
68
  # Sandbox Endpoints (Development & Staging)
66
69
  NEXT_PUBLIC_SANDBOX_CMS_URL=http://api.sandpod.ir/srv/cms-sandbox
@@ -69,8 +72,10 @@ NEXT_PUBLIC_SANDBOX_PODSPACE_URL=http://podspace.sandpod.ir
69
72
  NEXT_PUBLIC_SANDBOX_STREAM_URL=https://sandbox-offline-stream.sandpod.ir
70
73
  ```
71
74
 
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.
75
+ > [!TIP]
76
+ > 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.
77
+
78
+ Captcha requests use the resolved `apiPod` URL (`NEXT_PUBLIC_API_POD_URL` in production or `NEXT_PUBLIC_API_POD_SANDBOX_URL` when `sandbox: true`); there is no separate captcha URL setting.
74
79
 
75
80
  ---
76
81
 
@@ -604,9 +609,259 @@ export async function getPublishedPosts() {
604
609
 
605
610
  ---
606
611
 
607
- ## Other POD Microservices
612
+ ## Deep Dive 5: Podspace Cloud Storage (Official Swagger API)
613
+
614
+ 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.
615
+
616
+ ### Sub-Services Architecture
617
+
618
+ All Podspace operations are accessible via specialized, modular sub-services under `podSdk.podspace`:
619
+
620
+ ```typescript
621
+ podSdk.podspace.files // File details, rename, move, copy, search, versions, zip
622
+ podSdk.podspace.folders // Folder creation, nested paths, children, actions
623
+ podSdk.podspace.resumable // Chunked/large file upload (tus protocol: create, append, finalize)
624
+ podSdk.podspace.links // Public/password-protected shareable download links
625
+ podSdk.podspace.shares // Sharing files/folders with specific users & permissions
626
+ podSdk.podspace.trash // Recycle bin: list, restore, empty, auto-cleanup
627
+ podSdk.podspace.bookmarks // Starred / favorite files and folders
628
+ podSdk.podspace.tags // File & folder tagging
629
+ podSdk.podspace.metadata // Custom key-value metadata & descriptions
630
+ podSdk.podspace.userGroups // Group storage, member uploads & quota usage
631
+ podSdk.podspace.workspaces // Team workspaces & member roles
632
+ podSdk.podspace.me // User storage usage, plans, personal/chat folders
633
+ ```
634
+
635
+ > [!TIP]
636
+ > Commonly used methods (`uploadFile`, `downloadFile`, `getFileUrl`, `getImageUrl`, `getThumbnailUrl`, `createFolder`, `searchFiles`) are also directly available on `podSdk.podspace` for maximum developer convenience.
637
+
638
+ ---
639
+
640
+ ### Key Usage Examples
641
+
642
+ #### 1. File Upload & CDN URLs (100% Backward Compatible)
643
+
644
+ ```typescript
645
+ // app/actions/upload.ts
646
+ 'use server';
647
+
648
+ import { podSdk } from '@/lib/pod';
649
+
650
+ export async function uploadDocument(formData: FormData) {
651
+ // Supports both positional args (formData, path, isPublic) and options object
652
+ const res = await podSdk.podspace.uploadFile(formData, {
653
+ path: '/documents/invoices',
654
+ isPublic: true,
655
+ });
656
+
657
+ if (res.hasError) {
658
+ throw new Error(res.message || 'Upload failed');
659
+ }
660
+
661
+ const file = res.result;
662
+
663
+ // Resolve direct download URL
664
+ const downloadUrl = podSdk.podspace.getFileUrl(file.hash, true);
665
+
666
+ return {
667
+ hash: file.hash,
668
+ name: file.name,
669
+ size: file.size,
670
+ url: downloadUrl,
671
+ };
672
+ }
673
+ ```
674
+
675
+ #### 2. Image & Thumbnail CDN URL Helper
676
+
677
+ ```typescript
678
+ // Helper functions generate instant CDN URLs without network calls:
679
+ const fullImageUrl = podSdk.podspace.getImageUrl(imageHash, {
680
+ isPublic: true,
681
+ size: '1200x800',
682
+ quality: 85,
683
+ crop: true,
684
+ });
685
+
686
+ const thumbUrl = podSdk.podspace.getThumbnailUrl(imageHash, {
687
+ isPublic: true,
688
+ size: '150x150',
689
+ });
690
+ ```
691
+
692
+ #### 3. Base64 Image Upload
693
+
694
+ ```typescript
695
+ // Upload image directly from a Base64 data URL
696
+ const uploadRes = await podSdk.podspace.uploadImageBase64({
697
+ base64: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAE...',
698
+ filename: 'avatar.png',
699
+ path: '/avatars',
700
+ isPublic: true,
701
+ });
702
+ ```
703
+
704
+ #### 4. Resumable Chunked Upload (Large Files)
705
+
706
+ For reliable multi-gigabyte uploads with pause/resume support:
707
+
708
+ ```typescript
709
+ // Step 1: Initialize resumable session
710
+ const session = await podSdk.podspace.resumable.create({
711
+ filename: 'presentation.mp4',
712
+ fileSize: totalBytes,
713
+ path: '/videos',
714
+ isPublic: false,
715
+ });
716
+
717
+ const uploadUrl = session.uploadUrl;
718
+
719
+ // Step 2: Check current server offset if resuming
720
+ const offset = await podSdk.podspace.resumable.getStatus(uploadUrl);
721
+
722
+ // Step 3: Append chunks
723
+ await podSdk.podspace.resumable.append({
724
+ uploadUrl,
725
+ offset,
726
+ chunk: chunkBuffer,
727
+ });
728
+
729
+ // Step 4: Finalize when all bytes are uploaded
730
+ const completedFile = await podSdk.podspace.resumable.finalizeUpload(uploadUrl);
731
+ ```
732
+
733
+ #### 5. Folder Hierarchy & Listing
734
+
735
+ ```typescript
736
+ // Create a folder
737
+ await podSdk.podspace.folders.createFolder({
738
+ name: 'Reports-2026',
739
+ path: '/finance',
740
+ });
741
+
742
+ // Recursively create nested directory paths
743
+ await podSdk.podspace.folders.createDirectories('/finance/2026/Q1/receipts');
744
+
745
+ // List folder contents with pagination
746
+ const contents = await podSdk.podspace.folders.getFolderChildren({
747
+ path: '/finance/Reports-2026',
748
+ offset: 0,
749
+ size: 50,
750
+ });
751
+ ```
752
+
753
+ #### 6. Shareable Public / Password-Protected Links
754
+
755
+ ```typescript
756
+ // Create an expiring, password-protected download link
757
+ const linkRes = await podSdk.podspace.links.createLink(fileHash, {
758
+ type: 'DOWNLOAD',
759
+ password: 'SecurePassword123!',
760
+ expiresAt: Date.now() + 7 * 24 * 3600 * 1000, // 7 days
761
+ });
762
+
763
+ // Share with a specific user
764
+ await podSdk.podspace.shares.shareWithUser(fileHash, {
765
+ username: 'john_doe',
766
+ permission: 'READ',
767
+ });
768
+ ```
769
+
770
+ #### 7. File Versions & Rollback
771
+
772
+ ```typescript
773
+ // List previous versions of a file
774
+ const versions = await podSdk.podspace.files.getFileVersions(fileHash);
775
+
776
+ // Rollback to a specific historical version
777
+ await podSdk.podspace.files.rollbackFileVersion(fileHash, versionId);
778
+ ```
779
+
780
+ #### 8. Trash & Recycling
781
+
782
+ ```typescript
783
+ // Move file or folder to trash
784
+ await podSdk.podspace.files.trashEntity(fileHash);
785
+
786
+ // List recycle bin
787
+ const trashItems = await podSdk.podspace.trash.getTrashList({ size: 20 });
788
+
789
+ // Restore or permanently delete
790
+ await podSdk.podspace.trash.restore(fileHash);
791
+ await podSdk.podspace.trash.deletePermanently(fileHash);
792
+ ```
793
+
794
+ ---
795
+
796
+ ### Podspace Methods Reference
797
+
798
+ | Sub-Service | Method | HTTP | Path | Description |
799
+ | :--- | :--- | :--- | :--- | :--- |
800
+ | **Upload / Download** | `uploadFile(formData, params)` | `POST` | `/api/files` | Upload single file with metadata. |
801
+ | | `uploadMultipleFiles(formData, params)` | `POST` | `/api/files/batch` | Upload multiple files simultaneously. |
802
+ | | `uploadImageBase64(params)` | `POST` | `/api/images/base64` | Upload base64 encoded image. |
803
+ | | `replaceFile(hash, formData)` | `PUT` | `/api/files/{hash}` | Overwrite existing file content. |
804
+ | | `uploadByLink(params)` | `POST` | `/api/files/link` | Download remote file into Podspace. |
805
+ | | `downloadFile(hash, opts)` | `GET` | `/api/files/{hash}` | Download raw binary file. |
806
+ | | `downloadImage(hash, opts)` | `GET` | `/api/images/{hash}` | Download transformed image. |
807
+ | | `downloadThumbnail(hash, opts)` | `GET` | `/api/images/thumbnails/{hash}` | Download image thumbnail. |
808
+ | | `downloadFilesAsZip(hashes)` | `POST` | `/api/files/zip` | Batch download files as a ZIP archive. |
809
+ | | `getFileUrl(hash, isPublic)` | - | Client Helper | Generates direct file CDN link. |
810
+ | | `getImageUrl(hash, opts)` | - | Client Helper | Generates transformed image CDN link. |
811
+ | | `getThumbnailUrl(hash, opts)` | - | Client Helper | Generates thumbnail CDN link. |
812
+ | **Files** | `getFileDetail(hash)` | `GET` | `/api/files/{hash}/metadata` | Fetch full metadata for file. |
813
+ | | `getFileDetailByPath(path)` | `GET` | `/api/files/path` | Fetch metadata by absolute path. |
814
+ | | `checkEntityExist(hash)` | `GET` | `/api/entities/{hash}/exist` | Check existence of file/folder. |
815
+ | | `renameEntity(hash, newName)` | `PUT` | `/api/entities/{hash}/rename` | Rename file or folder. |
816
+ | | `moveEntity(hash, targetPath)` | `PUT` | `/api/entities/{hash}/move` | Move file or folder. |
817
+ | | `copyEntity(hash, targetPath)` | `POST` | `/api/entities/{hash}/copy` | Duplicate file or folder. |
818
+ | | `trashEntity(hash)` | `DELETE` | `/api/entities/{hash}` | Move file/folder to trash. |
819
+ | | `searchFiles(params)` | `GET` | `/api/files/search` | Search files by name, type, date, or tags. |
820
+ | | `getFileVersions(hash)` | `GET` | `/api/files/{hash}/versions` | List all historical versions. |
821
+ | | `rollbackFileVersion(hash, id)` | `POST` | `/api/files/{hash}/versions/{id}/rollback` | Rollback to specific version. |
822
+ | | `compressToZip(params)` | `POST` | `/api/files/compress` | Compress files into a ZIP archive. |
823
+ | | `extractZip(params)` | `POST` | `/api/files/extract` | Extract ZIP archive in Podspace. |
824
+ | **Folders** | `createFolder(params)` | `POST` | `/api/folders` | Create a new folder. |
825
+ | | `createDirectories(path)` | `POST` | `/api/folders/directories` | Create nested directory tree. |
826
+ | | `getFolderChildren(params)` | `GET` | `/api/folders/{hash}/children` | List folder contents. |
827
+ | | `getRecentFolders(params)` | `GET` | `/api/folders/recent` | List recently accessed folders. |
828
+ | **Resumable** | `create(params)` | `POST` | `/api/files/resumable` | Initialize resumable upload. |
829
+ | | `append(params)` | `PATCH` | `{uploadUrl}` | Upload chunk to offset. |
830
+ | | `getStatus(uploadUrl)` | `HEAD` | `{uploadUrl}` | Get current uploaded byte offset. |
831
+ | | `finalizeUpload(uploadUrl)` | `POST` | `{uploadUrl}/finalize` | Complete resumable upload. |
832
+ | **Links** | `createLink(hash, params)` | `POST` | `/api/entities/{hash}/links` | Create shareable download link. |
833
+ | | `getUserLinks(params)` | `GET` | `/api/links` | List all created links. |
834
+ | | `revokeLink(linkHash)` | `DELETE` | `/api/links/{linkHash}` | Deactivate share link. |
835
+ | **Shares** | `shareWithUser(hash, params)` | `POST` | `/api/entities/{hash}/shares` | Share with another user. |
836
+ | | `makePublic(hash, isPublic)` | `PUT` | `/api/entities/{hash}/public` | Toggle public/private access. |
837
+ | | `getSharedWithMe(params)` | `GET` | `/api/shares/shared-with-me` | List items shared with user. |
838
+ | **Trash** | `getTrashList(params)` | `GET` | `/api/trash` | List items in recycle bin. |
839
+ | | `restore(hash)` | `POST` | `/api/trash/{hash}/restore` | Restore file/folder. |
840
+ | | `deletePermanently(hash)` | `DELETE` | `/api/trash/{hash}` | Permanently destroy file/folder. |
841
+ | | `emptyTrash()` | `DELETE` | `/api/trash` | Empty entire recycle bin. |
842
+ | **Bookmarks** | `getBookmarks(params)` | `GET` | `/api/bookmarks` | List starred files/folders. |
843
+ | | `addBookmark(hash)` | `POST` | `/api/bookmarks/{hash}` | Star a file or folder. |
844
+ | | `removeBookmark(hash)` | `DELETE` | `/api/bookmarks/{hash}` | Unstar a file or folder. |
845
+ | **Tags** | `addTags(hash, tags)` | `POST` | `/api/entities/{hash}/tags` | Add tags to entity. |
846
+ | | `getEntityTags(hash)` | `GET` | `/api/entities/{hash}/tags` | List entity tags. |
847
+ | | `getEntitiesByTag(tag)` | `GET` | `/api/tags/{tag}/entities` | Search entities by tag. |
848
+ | **Metadata** | `setMetadata(hash, metadata)` | `PUT` | `/api/entities/{hash}/metadata` | Set custom key-value metadata. |
849
+ | | `setDescription(hash, desc)` | `PUT` | `/api/entities/{hash}/description` | Set entity description. |
850
+ | **User Groups** | `createUserGroup(params)` | `POST` | `/api/user-groups` | Create user group. |
851
+ | | `uploadToUserGroup(group, fd)`| `POST` | `/api/user-groups/{group}/files` | Upload file to user group. |
852
+ | | `getUserGroupUsage(group)` | `GET` | `/api/user-groups/{group}/usage` | Get storage quota & usage. |
853
+ | **Workspaces** | `createWorkspace(params)` | `POST` | `/api/workspaces` | Create team workspace. |
854
+ | | `getWorkspaceMembers(id)` | `GET` | `/api/workspaces/{id}/members` | List workspace members. |
855
+ | **Me** | `getUser()` | `GET` | `/api/users/me` | Current user profile. |
856
+ | | `getUserUsageReport()` | `GET` | `/api/users/me/usage` | Total storage usage report. |
857
+ | | `getPersonalFolder()` | `GET` | `/api/users/me/personal-folder` | Get or create root personal folder. |
858
+ | | `getOrCreateChatFolder()` | `GET` | `/api/users/me/chat-folder` | Get or create chat attachments folder. |
608
859
 
609
- ### 4. POD SSO (OTP Handshake with Web Crypto RSA Signature)
860
+ ---
861
+
862
+ ## Other POD Microservices
863
+
864
+ ### 1. POD SSO (OTP Handshake with Web Crypto RSA Signature)
610
865
 
611
866
  ```typescript
612
867
  // app/actions/auth.ts
@@ -644,33 +899,97 @@ export async function verifyOtp(authorization: string, phoneNumber: string, code
644
899
  export async function getProfile(accessToken: string) {
645
900
  const profile = await podSdk.sso.getUserProfile(accessToken);
646
901
  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)
902
+ }
903
+ ```
904
+
905
+ ---
906
+
907
+ ### 2. Captcha (Generate and Verify)
908
+
909
+ The captcha service exposes the POD captcha API through `podSdk.captcha`. It creates a short-lived challenge image and checks the value entered by the user. Both calls return the SDK's standard `PodResponse<T>` shape (`hasError`, `result`, `message`, and `errorCode`).
910
+
911
+ `Client-Id` and `Access-Token` headers are added automatically from `createPodSdk({ clientId, apiToken })`. Keep these credentials on the server and call captcha methods from a Server Action or Route Handler; do not expose the SDK token in a Client Component.
912
+
913
+ #### Generate a captcha
914
+
915
+ ```typescript
916
+ // app/actions/captcha.ts
917
+ 'use server';
918
+
919
+ import { podSdk } from '@/lib/pod';
920
+
921
+ export async function createCaptcha() {
922
+ const response = await podSdk.captcha.get({
923
+ width: 280,
924
+ height: 96,
925
+ length: 6,
926
+ type: 'alphanumeric',
927
+ format: 'png',
928
+ noise: true,
929
+ bgColor: '#ffffff',
930
+ fontColor: '#1f2937',
931
+ fontSize: 36,
932
+ });
933
+
934
+ if (response.hasError || !response.result) {
935
+ throw new Error(response.message || 'Captcha generation failed');
936
+ }
937
+
938
+ // `image` is a data URI or SVG string, depending on `format`.
939
+ return {
940
+ captchaId: response.result.captchaId,
941
+ image: response.result.image,
942
+ mimeType: response.result.mimeType,
943
+ expiresAt: response.result.expiresAt,
944
+ };
945
+ }
946
+ ```
947
+
948
+ #### Verify the submitted value
949
+
950
+ ```typescript
951
+ import { podSdk } from '@/lib/pod';
952
+
953
+ export async function verifyCaptcha(captchaId: string, captchaValue: string) {
954
+ const response = await podSdk.captcha.check(captchaId, { captchaValue });
955
+
956
+ if (response.hasError) {
957
+ return { valid: false, message: response.message || 'Captcha verification failed' };
958
+ }
959
+
960
+ return {
961
+ valid: response.result?.valid === true,
962
+ message: response.result?.message,
963
+ };
964
+ }
965
+ ```
966
+
967
+ #### Captcha methods
968
+
969
+ | Method | HTTP | Path | Description |
970
+ | :--- | :--- | :--- | :--- |
971
+ | `get(options?)` | `POST` | `/api/core/captchas/generate` | Generate a captcha image. Options are sent as query parameters. |
972
+ | `check(captchaId, { captchaValue })` | `POST` | `/api/core/captchas/check/{captchaId}` | Verify the value entered for a generated captcha. |
973
+
974
+ `GenerateCaptchaOptions` supports the following fields:
975
+
976
+ | Option | Type | Default | Description |
977
+ | :--- | :--- | :--- | :--- |
978
+ | `width` | `number` | `200` | Image width in pixels. |
979
+ | `height` | `number` | `80` | Image height in pixels. |
980
+ | `length` | `number` | `5` | Number of characters in the challenge. |
981
+ | `type` | `string` | Server default | Challenge type, such as `numeric` or `alphanumeric`. |
982
+ | `format` | `'svg' \| 'png'` | `'svg'` | Image output format. |
983
+ | `noise` | `boolean` | `true` | Add visual noise to the image. |
984
+ | `bgColor` | `string` | Server default | Background color in hex format. |
985
+ | `fontColor` | `string` | Server default | Character color in hex format. |
986
+ | `fontSize` | `number` | Server default | Character font size. |
987
+
988
+ The generated response contains `captchaId`, `image`, `expiresAt`, and `mimeType`; `image` is returned as a data URI or SVG string. Always treat the challenge as temporary and request a new one after expiration or a failed attempt, according to your server-side policy.
989
+
990
+ ---
991
+
992
+ ### 3. Notification (SMS Delivery)
674
993
 
675
994
  ```typescript
676
995
  // app/actions/notify.ts
@@ -688,7 +1007,7 @@ export async function sendSms(phoneNumber: string, text: string) {
688
1007
 
689
1008
  ---
690
1009
 
691
- ### 7. Social (Comments & Likes)
1010
+ ### 4. Social (Comments & Likes)
692
1011
 
693
1012
  ```typescript
694
1013
  // app/actions/social.ts
@@ -710,7 +1029,7 @@ export async function likePost(postId: number) {
710
1029
 
711
1030
  ---
712
1031
 
713
- ### 8. IUMS (Identity & University Management Service)
1032
+ ### 5. IUMS (Identity & University Management Service)
714
1033
 
715
1034
  ```typescript
716
1035
  // app/actions/iums.ts