@foru-ms/sdk 1.1.0 → 1.2.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/README.md +375 -38
- package/dist/Client.d.ts +63 -4
- package/dist/Client.js +124 -22
- package/dist/errors.d.ts +52 -0
- package/dist/errors.js +95 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/resources/Integrations.d.ts +7 -0
- package/dist/resources/Integrations.js +6 -0
- package/dist/resources/Posts.d.ts +12 -0
- package/dist/resources/Posts.js +44 -0
- package/dist/resources/PrivateMessages.d.ts +4 -0
- package/dist/resources/PrivateMessages.js +6 -0
- package/dist/resources/SSO.d.ts +10 -0
- package/dist/resources/SSO.js +11 -0
- package/dist/resources/Tags.d.ts +8 -0
- package/dist/resources/Tags.js +24 -0
- package/dist/resources/Threads.d.ts +20 -0
- package/dist/resources/Threads.js +85 -0
- package/dist/resources/Users.d.ts +10 -0
- package/dist/resources/Users.js +26 -0
- package/dist/resources/Webhooks.d.ts +69 -0
- package/dist/resources/Webhooks.js +115 -0
- package/dist/response-types.d.ts +105 -0
- package/dist/response-types.js +2 -0
- package/dist/utils.d.ts +80 -0
- package/dist/utils.js +138 -0
- package/examples/README.md +38 -0
- package/examples/authentication.ts +79 -0
- package/examples/error-handling.ts +133 -0
- package/examples/managing-threads.ts +130 -0
- package/examples/pagination.ts +81 -0
- package/examples/webhooks.ts +176 -0
- package/package.json +1 -1
- package/src/Client.ts +165 -25
- package/src/errors.ts +95 -0
- package/src/index.ts +3 -0
- package/src/resources/Integrations.ts +11 -0
- package/src/resources/Posts.ts +56 -0
- package/src/resources/PrivateMessages.ts +10 -0
- package/src/resources/SSO.ts +17 -0
- package/src/resources/Tags.ts +32 -0
- package/src/resources/Threads.ts +109 -0
- package/src/resources/Users.ts +36 -0
- package/src/resources/Webhooks.ts +131 -0
- package/src/response-types.ts +113 -0
- package/src/utils.ts +182 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { ForumClient } from '@foru-ms/sdk';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Example: Webhooks
|
|
6
|
+
* Demonstrates setting up, handling, and verifying webhooks
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
async function setupWebhooks() {
|
|
10
|
+
const client = new ForumClient({
|
|
11
|
+
apiKey: process.env.FORU_API_KEY || 'your_api_key',
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Example 1: Create a Webhook
|
|
15
|
+
console.log('=== Creating a Webhook ===');
|
|
16
|
+
const webhook = await client.webhooks.create({
|
|
17
|
+
name: 'Thread Activity Monitor',
|
|
18
|
+
url: 'https://your-domain.com/webhooks/foru',
|
|
19
|
+
events: [
|
|
20
|
+
'thread.created',
|
|
21
|
+
'thread.updated',
|
|
22
|
+
'thread.deleted',
|
|
23
|
+
'post.created',
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
console.log('Webhook created:', webhook.webhook.id);
|
|
27
|
+
console.log('Secret:', webhook.webhook.secret); // Save this securely!
|
|
28
|
+
|
|
29
|
+
// Example 2: List All Webhooks
|
|
30
|
+
console.log('\n=== Listing Webhooks ===');
|
|
31
|
+
const webhooks = await client.webhooks.list();
|
|
32
|
+
console.log('Total webhooks:', webhooks.webhooks.length);
|
|
33
|
+
|
|
34
|
+
// Example 3: Update a Webhook
|
|
35
|
+
console.log('\n=== Updating a Webhook ===');
|
|
36
|
+
await client.webhooks.update(webhook.webhook.id, {
|
|
37
|
+
active: true,
|
|
38
|
+
events: [
|
|
39
|
+
'thread.created',
|
|
40
|
+
'thread.updated',
|
|
41
|
+
'thread.deleted',
|
|
42
|
+
'post.created',
|
|
43
|
+
'post.updated', // Added new event
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
console.log('Webhook updated');
|
|
47
|
+
|
|
48
|
+
// Example 4: Get Webhook Deliveries
|
|
49
|
+
console.log('\n=== Getting Webhook Deliveries ===');
|
|
50
|
+
const deliveries = await client.webhooks.getDeliveries(webhook.webhook.id);
|
|
51
|
+
console.log('Total deliveries:', deliveries.total);
|
|
52
|
+
console.log('Recent deliveries:', deliveries.deliveries.slice(0, 5));
|
|
53
|
+
|
|
54
|
+
return webhook.webhook.secret;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Example webhook handler using Express
|
|
59
|
+
*/
|
|
60
|
+
function setupWebhookHandler(secret: string) {
|
|
61
|
+
const app = express();
|
|
62
|
+
const client = new ForumClient({
|
|
63
|
+
apiKey: process.env.FORU_API_KEY || 'your_api_key',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Important: Use raw body for signature verification
|
|
67
|
+
app.use(express.json({
|
|
68
|
+
verify: (req: any, res, buf) => {
|
|
69
|
+
req.rawBody = buf.toString();
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
app.post('/webhooks/foru', (req: any, res) => {
|
|
74
|
+
// Example 5: Verify Webhook Signature with Timestamp
|
|
75
|
+
const signature = req.headers['x-webhook-signature'];
|
|
76
|
+
const timestamp = req.headers['x-webhook-timestamp'];
|
|
77
|
+
const event = req.headers['x-webhook-event'];
|
|
78
|
+
|
|
79
|
+
// Verify timestamp age (prevent replay attacks)
|
|
80
|
+
const age = Date.now() - parseInt(timestamp);
|
|
81
|
+
if (age > 5 * 60 * 1000) { // 5 minutes
|
|
82
|
+
console.error('Webhook timestamp too old');
|
|
83
|
+
return res.status(400).send('Webhook timestamp too old');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Verify signature
|
|
87
|
+
const isValid = client.webhooks.verifySignature(
|
|
88
|
+
req.body,
|
|
89
|
+
signature,
|
|
90
|
+
timestamp,
|
|
91
|
+
secret
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (!isValid) {
|
|
95
|
+
console.error('Invalid webhook signature');
|
|
96
|
+
return res.status(401).send('Invalid signature');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Process webhook payload
|
|
100
|
+
console.log('Received webhook event:', event);
|
|
101
|
+
const payload = req.body;
|
|
102
|
+
|
|
103
|
+
switch (event) {
|
|
104
|
+
case 'thread.created':
|
|
105
|
+
handleThreadCreated(payload);
|
|
106
|
+
break;
|
|
107
|
+
case 'thread.updated':
|
|
108
|
+
handleThreadUpdated(payload);
|
|
109
|
+
break;
|
|
110
|
+
case 'thread.deleted':
|
|
111
|
+
handleThreadDeleted(payload);
|
|
112
|
+
break;
|
|
113
|
+
case 'post.created':
|
|
114
|
+
handlePostCreated(payload);
|
|
115
|
+
break;
|
|
116
|
+
default:
|
|
117
|
+
console.log('Unhandled event type:', event);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Always respond quickly to acknowledge receipt
|
|
121
|
+
res.status(200).send('OK');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const PORT = process.env.PORT || 3000;
|
|
125
|
+
app.listen(PORT, () => {
|
|
126
|
+
console.log(`\nWebhook handler listening on port ${PORT}`);
|
|
127
|
+
console.log(`Endpoint: http://localhost:${PORT}/webhooks/foru`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Event handler functions
|
|
132
|
+
function handleThreadCreated(thread: any) {
|
|
133
|
+
console.log('New thread created:', thread.id);
|
|
134
|
+
console.log('Title:', thread.title);
|
|
135
|
+
console.log('Author:', thread.userId);
|
|
136
|
+
|
|
137
|
+
// Example: Send notification, update database, etc.
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function handleThreadUpdated(thread: any) {
|
|
141
|
+
console.log('Thread updated:', thread.id);
|
|
142
|
+
console.log('Title:', thread.title);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function handleThreadDeleted(thread: any) {
|
|
146
|
+
console.log('Thread deleted:', thread.id);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function handlePostCreated(post: any) {
|
|
150
|
+
console.log('New post created:', post.id);
|
|
151
|
+
console.log('Thread:', post.threadId);
|
|
152
|
+
console.log('Author:', post.userId);
|
|
153
|
+
|
|
154
|
+
// Example: Check for spam, auto-moderate, etc.
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Main execution
|
|
158
|
+
async function main() {
|
|
159
|
+
try {
|
|
160
|
+
// Setup webhooks
|
|
161
|
+
const secret = await setupWebhooks();
|
|
162
|
+
|
|
163
|
+
// Start webhook handler server
|
|
164
|
+
// setupWebhookHandler(secret);
|
|
165
|
+
|
|
166
|
+
console.log('\n=== Webhook Setup Complete ===');
|
|
167
|
+
console.log('Remember to save your webhook secret securely!');
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('Error setting up webhooks:', error);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// For demonstration, we'll just setup webhooks
|
|
175
|
+
// Uncomment the line above to also start the webhook handler server
|
|
176
|
+
main().catch(console.error);
|
package/package.json
CHANGED
package/src/Client.ts
CHANGED
|
@@ -12,14 +12,58 @@ import { PrivateMessagesResource } from './resources/PrivateMessages';
|
|
|
12
12
|
import { ReportsResource } from './resources/Reports';
|
|
13
13
|
import { RolesResource } from './resources/Roles';
|
|
14
14
|
import { SSOResource } from './resources/SSO';
|
|
15
|
+
import {
|
|
16
|
+
ForumAPIError,
|
|
17
|
+
AuthenticationError,
|
|
18
|
+
AuthorizationError,
|
|
19
|
+
NotFoundError,
|
|
20
|
+
ValidationError,
|
|
21
|
+
RateLimitError,
|
|
22
|
+
ServerError,
|
|
23
|
+
NetworkError
|
|
24
|
+
} from './errors';
|
|
25
|
+
import { PaginationHelper, RetryHelper } from './utils';
|
|
26
|
+
import { RateLimitInfo } from './response-types';
|
|
15
27
|
|
|
16
28
|
// Polyfill fetch if needed (e.g. older Node versions)
|
|
17
29
|
const fetch = globalThis.fetch || require('cross-fetch');
|
|
18
30
|
|
|
31
|
+
export interface ClientOptions {
|
|
32
|
+
/** API key for authentication */
|
|
33
|
+
apiKey: string;
|
|
34
|
+
/** Base URL for the API (default: https://api.foru.ms/v1) */
|
|
35
|
+
baseUrl?: string;
|
|
36
|
+
/** Maximum number of retry attempts for failed requests (default: 3) */
|
|
37
|
+
maxRetries?: number;
|
|
38
|
+
/** Enable automatic retry for rate limits and server errors (default: true) */
|
|
39
|
+
enableRetry?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main client for interacting with the Foru.ms API
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const client = new ForumClient({
|
|
47
|
+
* apiKey: 'your_api_key',
|
|
48
|
+
* baseUrl: 'https://api.foru.ms/v1'
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // Set user token
|
|
52
|
+
* client.setToken('user_jwt_token');
|
|
53
|
+
*
|
|
54
|
+
* // Use resources
|
|
55
|
+
* const threads = await client.threads.list();
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
19
58
|
export class ForumClient {
|
|
20
59
|
public apiKey: string;
|
|
21
60
|
public token: string | null = null;
|
|
22
61
|
public baseUrl: string;
|
|
62
|
+
public maxRetries: number;
|
|
63
|
+
public enableRetry: boolean;
|
|
64
|
+
|
|
65
|
+
/** Last known rate limit info */
|
|
66
|
+
public lastRateLimitInfo?: RateLimitInfo;
|
|
23
67
|
|
|
24
68
|
public auth: AuthResource;
|
|
25
69
|
public threads: ThreadsResource;
|
|
@@ -36,9 +80,14 @@ export class ForumClient {
|
|
|
36
80
|
public roles: RolesResource;
|
|
37
81
|
public sso: SSOResource;
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
/** Pagination helper for auto-paginating through results */
|
|
84
|
+
public pagination: PaginationHelper;
|
|
85
|
+
|
|
86
|
+
constructor(options: ClientOptions) {
|
|
40
87
|
this.apiKey = options.apiKey;
|
|
41
88
|
this.baseUrl = options.baseUrl || 'https://api.foru.ms/v1';
|
|
89
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
90
|
+
this.enableRetry = options.enableRetry ?? true;
|
|
42
91
|
|
|
43
92
|
this.auth = new AuthResource(this);
|
|
44
93
|
this.threads = new ThreadsResource(this);
|
|
@@ -54,40 +103,131 @@ export class ForumClient {
|
|
|
54
103
|
this.reports = new ReportsResource(this);
|
|
55
104
|
this.roles = new RolesResource(this);
|
|
56
105
|
this.sso = new SSOResource(this);
|
|
106
|
+
|
|
107
|
+
this.pagination = new PaginationHelper();
|
|
57
108
|
}
|
|
58
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Make an HTTP request to the API
|
|
112
|
+
* @param path - API endpoint path
|
|
113
|
+
* @param options - Fetch options
|
|
114
|
+
* @returns Promise resolving to the response data
|
|
115
|
+
* @throws {ForumAPIError} When the API returns an error
|
|
116
|
+
* @throws {NetworkError} When the network request fails
|
|
117
|
+
*/
|
|
59
118
|
public async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
119
|
+
const makeRequest = async (): Promise<T> => {
|
|
120
|
+
const headers: Record<string, string> = {
|
|
121
|
+
'Content-Type': 'application/json',
|
|
122
|
+
'x-api-key': this.apiKey,
|
|
123
|
+
...(options.headers as Record<string, string>),
|
|
124
|
+
};
|
|
65
125
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
126
|
+
if (this.token) {
|
|
127
|
+
headers['Authorization'] = `Bearer ${this.token}`;
|
|
128
|
+
}
|
|
69
129
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
130
|
+
let response: Response;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
response = await fetch(`${this.baseUrl}${path}`, {
|
|
134
|
+
...options,
|
|
135
|
+
headers,
|
|
136
|
+
});
|
|
137
|
+
} catch (error: any) {
|
|
138
|
+
throw new NetworkError('Network request failed', error);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Extract rate limit info
|
|
142
|
+
this.extractRateLimitInfo(response);
|
|
143
|
+
|
|
144
|
+
const contentType = response.headers.get('content-type');
|
|
145
|
+
let data;
|
|
146
|
+
if (contentType && contentType.includes('application/json')) {
|
|
147
|
+
data = await response.json();
|
|
148
|
+
} else {
|
|
149
|
+
data = await response.text();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
this.handleErrorResponse(response.status, data);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return data as T;
|
|
157
|
+
};
|
|
82
158
|
|
|
83
|
-
if
|
|
84
|
-
|
|
159
|
+
// Apply retry logic if enabled
|
|
160
|
+
if (this.enableRetry) {
|
|
161
|
+
return RetryHelper.withRetry(makeRequest, this.maxRetries);
|
|
85
162
|
}
|
|
86
163
|
|
|
87
|
-
return
|
|
164
|
+
return makeRequest();
|
|
88
165
|
}
|
|
89
166
|
|
|
90
|
-
|
|
167
|
+
/**
|
|
168
|
+
* Set the authentication token for user-scoped requests
|
|
169
|
+
* @param token - JWT token
|
|
170
|
+
*/
|
|
171
|
+
public setToken(token: string): void {
|
|
91
172
|
this.token = token;
|
|
92
173
|
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Clear the authentication token
|
|
177
|
+
*/
|
|
178
|
+
public clearToken(): void {
|
|
179
|
+
this.token = null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if client is authenticated
|
|
184
|
+
*/
|
|
185
|
+
public isAuthenticated(): boolean {
|
|
186
|
+
return this.token !== null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Extract and store rate limit information from response headers
|
|
191
|
+
*/
|
|
192
|
+
private extractRateLimitInfo(response: Response): void {
|
|
193
|
+
const limit = response.headers.get('x-ratelimit-limit');
|
|
194
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
195
|
+
const reset = response.headers.get('x-ratelimit-reset');
|
|
196
|
+
|
|
197
|
+
if (limit && remaining && reset) {
|
|
198
|
+
this.lastRateLimitInfo = {
|
|
199
|
+
limit: parseInt(limit, 10),
|
|
200
|
+
remaining: parseInt(remaining, 10),
|
|
201
|
+
reset: parseInt(reset, 10),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Handle error responses by throwing appropriate error types
|
|
208
|
+
*/
|
|
209
|
+
private handleErrorResponse(status: number, data: any): never {
|
|
210
|
+
const message = (data && data.message) || data.error || 'API Error';
|
|
211
|
+
|
|
212
|
+
switch (status) {
|
|
213
|
+
case 401:
|
|
214
|
+
throw new AuthenticationError(message, data);
|
|
215
|
+
case 403:
|
|
216
|
+
throw new AuthorizationError(message, data);
|
|
217
|
+
case 404:
|
|
218
|
+
throw new NotFoundError(message, data);
|
|
219
|
+
case 422:
|
|
220
|
+
throw new ValidationError(message, data);
|
|
221
|
+
case 429:
|
|
222
|
+
const retryAfter = data.retryAfter || 60;
|
|
223
|
+
throw new RateLimitError(message, retryAfter, data);
|
|
224
|
+
case 500:
|
|
225
|
+
case 502:
|
|
226
|
+
case 503:
|
|
227
|
+
case 504:
|
|
228
|
+
throw new ServerError(message, status, data);
|
|
229
|
+
default:
|
|
230
|
+
throw new ForumAPIError(message, status, data);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
93
233
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Foru.ms API errors
|
|
3
|
+
*/
|
|
4
|
+
export class ForumAPIError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
message: string,
|
|
7
|
+
public statusCode: number,
|
|
8
|
+
public response?: any
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'ForumAPIError';
|
|
12
|
+
Object.setPrototypeOf(this, ForumAPIError.prototype);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when authentication fails or token is invalid
|
|
18
|
+
*/
|
|
19
|
+
export class AuthenticationError extends ForumAPIError {
|
|
20
|
+
constructor(message: string = 'Authentication failed', response?: any) {
|
|
21
|
+
super(message, 401, response);
|
|
22
|
+
this.name = 'AuthenticationError';
|
|
23
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Error thrown when user doesn't have permission for an action
|
|
29
|
+
*/
|
|
30
|
+
export class AuthorizationError extends ForumAPIError {
|
|
31
|
+
constructor(message: string = 'Permission denied', response?: any) {
|
|
32
|
+
super(message, 403, response);
|
|
33
|
+
this.name = 'AuthorizationError';
|
|
34
|
+
Object.setPrototypeOf(this, AuthorizationError.prototype);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when a requested resource is not found
|
|
40
|
+
*/
|
|
41
|
+
export class NotFoundError extends ForumAPIError {
|
|
42
|
+
constructor(message: string = 'Resource not found', response?: any) {
|
|
43
|
+
super(message, 404, response);
|
|
44
|
+
this.name = 'NotFoundError';
|
|
45
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Error thrown when request validation fails
|
|
51
|
+
*/
|
|
52
|
+
export class ValidationError extends ForumAPIError {
|
|
53
|
+
constructor(message: string = 'Validation failed', response?: any) {
|
|
54
|
+
super(message, 422, response);
|
|
55
|
+
this.name = 'ValidationError';
|
|
56
|
+
Object.setPrototypeOf(this, ValidationError.prototype);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Error thrown when rate limit is exceeded
|
|
62
|
+
*/
|
|
63
|
+
export class RateLimitError extends ForumAPIError {
|
|
64
|
+
constructor(
|
|
65
|
+
message: string = 'Rate limit exceeded',
|
|
66
|
+
public retryAfter?: number,
|
|
67
|
+
response?: any
|
|
68
|
+
) {
|
|
69
|
+
super(message, 429, response);
|
|
70
|
+
this.name = 'RateLimitError';
|
|
71
|
+
Object.setPrototypeOf(this, RateLimitError.prototype);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Error thrown when server encounters an error
|
|
77
|
+
*/
|
|
78
|
+
export class ServerError extends ForumAPIError {
|
|
79
|
+
constructor(message: string = 'Server error', statusCode: number = 500, response?: any) {
|
|
80
|
+
super(message, statusCode, response);
|
|
81
|
+
this.name = 'ServerError';
|
|
82
|
+
Object.setPrototypeOf(this, ServerError.prototype);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Error thrown when network request fails
|
|
88
|
+
*/
|
|
89
|
+
export class NetworkError extends Error {
|
|
90
|
+
constructor(message: string = 'Network request failed', public cause?: Error) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = 'NetworkError';
|
|
93
|
+
Object.setPrototypeOf(this, NetworkError.prototype);
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,17 @@ export class IntegrationsResource {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
async update(id: string, payload: {
|
|
35
|
+
name?: string;
|
|
36
|
+
config?: any;
|
|
37
|
+
active?: boolean;
|
|
38
|
+
}): Promise<{ integration: Integration }> {
|
|
39
|
+
return this.client.request<{ integration: Integration }>(`/integrations/${id}`, {
|
|
40
|
+
method: 'PATCH',
|
|
41
|
+
body: JSON.stringify(payload),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
async delete(id: string): Promise<{ success: boolean }> {
|
|
35
46
|
return this.client.request<{ success: boolean }>(`/integrations/${id}`, {
|
|
36
47
|
method: 'DELETE',
|
package/src/resources/Posts.ts
CHANGED
|
@@ -84,6 +84,20 @@ export class PostsResource {
|
|
|
84
84
|
});
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
async getLikes(id: string, params?: {
|
|
88
|
+
cursor?: string;
|
|
89
|
+
}): Promise<any> {
|
|
90
|
+
const searchParams = new URLSearchParams();
|
|
91
|
+
if (params) {
|
|
92
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
93
|
+
if (value !== undefined) {
|
|
94
|
+
searchParams.append(key, value as string);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return this.client.request(`/post/${id}/likes?${searchParams.toString()}`, { method: 'GET' });
|
|
99
|
+
}
|
|
100
|
+
|
|
87
101
|
async dislike(id: string, userId?: string, extendedData?: any): Promise<any> {
|
|
88
102
|
return this.client.request(`/post/${id}/dislikes`, {
|
|
89
103
|
method: 'POST',
|
|
@@ -97,6 +111,20 @@ export class PostsResource {
|
|
|
97
111
|
});
|
|
98
112
|
}
|
|
99
113
|
|
|
114
|
+
async getDislikes(id: string, params?: {
|
|
115
|
+
cursor?: string;
|
|
116
|
+
}): Promise<any> {
|
|
117
|
+
const searchParams = new URLSearchParams();
|
|
118
|
+
if (params) {
|
|
119
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
120
|
+
if (value !== undefined) {
|
|
121
|
+
searchParams.append(key, value as string);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return this.client.request(`/post/${id}/dislikes?${searchParams.toString()}`, { method: 'GET' });
|
|
126
|
+
}
|
|
127
|
+
|
|
100
128
|
async upvote(id: string, userId?: string, extendedData?: any): Promise<any> {
|
|
101
129
|
return this.client.request(`/post/${id}/upvotes`, {
|
|
102
130
|
method: 'POST',
|
|
@@ -110,6 +138,20 @@ export class PostsResource {
|
|
|
110
138
|
});
|
|
111
139
|
}
|
|
112
140
|
|
|
141
|
+
async getUpvotes(id: string, params?: {
|
|
142
|
+
cursor?: string;
|
|
143
|
+
}): Promise<any> {
|
|
144
|
+
const searchParams = new URLSearchParams();
|
|
145
|
+
if (params) {
|
|
146
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
147
|
+
if (value !== undefined) {
|
|
148
|
+
searchParams.append(key, value as string);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return this.client.request(`/post/${id}/upvotes?${searchParams.toString()}`, { method: 'GET' });
|
|
153
|
+
}
|
|
154
|
+
|
|
113
155
|
async downvote(id: string, userId?: string, extendedData?: any): Promise<any> {
|
|
114
156
|
return this.client.request(`/post/${id}/downvotes`, {
|
|
115
157
|
method: 'POST',
|
|
@@ -122,4 +164,18 @@ export class PostsResource {
|
|
|
122
164
|
method: 'DELETE',
|
|
123
165
|
});
|
|
124
166
|
}
|
|
167
|
+
|
|
168
|
+
async getDownvotes(id: string, params?: {
|
|
169
|
+
cursor?: string;
|
|
170
|
+
}): Promise<any> {
|
|
171
|
+
const searchParams = new URLSearchParams();
|
|
172
|
+
if (params) {
|
|
173
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
174
|
+
if (value !== undefined) {
|
|
175
|
+
searchParams.append(key, value as string);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return this.client.request(`/post/${id}/downvotes?${searchParams.toString()}`, { method: 'GET' });
|
|
180
|
+
}
|
|
125
181
|
}
|
|
@@ -59,6 +59,16 @@ export class PrivateMessagesResource {
|
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
async update(id: string, payload: {
|
|
63
|
+
read?: boolean;
|
|
64
|
+
extendedData?: Record<string, any>;
|
|
65
|
+
}): Promise<PrivateMessage> {
|
|
66
|
+
return this.client.request<PrivateMessage>(`/private-message/${id}`, {
|
|
67
|
+
method: 'PATCH',
|
|
68
|
+
body: JSON.stringify(payload),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
62
72
|
async delete(id: string): Promise<PrivateMessage & { deleted: boolean }> {
|
|
63
73
|
return this.client.request<PrivateMessage & { deleted: boolean }>(`/private-message/${id}`, {
|
|
64
74
|
method: 'DELETE',
|
package/src/resources/SSO.ts
CHANGED
|
@@ -25,6 +25,23 @@ export class SSOResource {
|
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
async retrieve(id: string): Promise<{ ssoProvider: SSOProvider }> {
|
|
29
|
+
return this.client.request<{ ssoProvider: SSOProvider }>(`/sso/${id}`, {
|
|
30
|
+
method: 'GET',
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async update(id: string, payload: {
|
|
35
|
+
domain?: string;
|
|
36
|
+
config?: any;
|
|
37
|
+
active?: boolean;
|
|
38
|
+
}): Promise<{ ssoProvider: SSOProvider }> {
|
|
39
|
+
return this.client.request<{ ssoProvider: SSOProvider }>(`/sso/${id}`, {
|
|
40
|
+
method: 'PATCH',
|
|
41
|
+
body: JSON.stringify(payload),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
28
45
|
async delete(id: string): Promise<{ success: boolean }> {
|
|
29
46
|
return this.client.request<{ success: boolean }>(`/sso/${id}`, {
|
|
30
47
|
method: 'DELETE',
|