naystack 1.1.16 → 1.1.17
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 +600 -0
- package/package.json +7 -4
package/README.md
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# Naystack
|
|
2
|
+
|
|
3
|
+
> A stack built with tight **Next.js + Drizzle ORM + GraphQL** integration
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/naystack)
|
|
6
|
+
[](https://opensource.org/licenses/ISC)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install naystack
|
|
12
|
+
# or
|
|
13
|
+
pnpm add naystack
|
|
14
|
+
# or
|
|
15
|
+
yarn add naystack
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Modules
|
|
19
|
+
|
|
20
|
+
Naystack provides the following modules, each accessible via its own import path:
|
|
21
|
+
|
|
22
|
+
| Module | Import Path | Description |
|
|
23
|
+
| ----------- | ------------------ | ----------------------------------------------- |
|
|
24
|
+
| **Auth** | `naystack/auth` | Email, Google, and Instagram authentication |
|
|
25
|
+
| **GraphQL** | `naystack/graphql` | GraphQL server initialization with type-graphql |
|
|
26
|
+
| **Client** | `naystack/client` | Client-side hooks and utilities |
|
|
27
|
+
| **File** | `naystack/file` | File upload to AWS S3 |
|
|
28
|
+
| **Socials** | `naystack/socials` | Instagram and Threads API integration |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Auth Module
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import {
|
|
36
|
+
getEmailAuthRoutes,
|
|
37
|
+
initGoogleAuth,
|
|
38
|
+
initInstagramAuth,
|
|
39
|
+
} from "naystack/auth";
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Email Authentication
|
|
43
|
+
|
|
44
|
+
Setup email-based authentication with JWT tokens and optional Turnstile captcha verification.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const emailAuth = getEmailAuthRoutes({
|
|
48
|
+
getUser: async (email: string) => { /* fetch user by email */ },
|
|
49
|
+
createUser: async (user: UserInput) => { /* create new user */ },
|
|
50
|
+
signingKey: process.env.JWT_SIGNING_KEY!,
|
|
51
|
+
refreshKey: process.env.JWT_REFRESH_KEY!,
|
|
52
|
+
turnstileKey?: string, // Optional: Cloudflare Turnstile secret key
|
|
53
|
+
onSignUp: (user) => { /* callback on signup */ },
|
|
54
|
+
onLogout?: (body) => { /* callback on logout */ },
|
|
55
|
+
onError?: (error) => { /* custom error handler */ },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Export in Next.js route handler
|
|
59
|
+
export const { GET, POST, PUT, DELETE, getUserIdFromRequest } = emailAuth;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Options
|
|
63
|
+
|
|
64
|
+
| Option | Type | Required | Description |
|
|
65
|
+
| -------------- | ------------------------------------------------------- | -------- | ------------------------------- |
|
|
66
|
+
| `getUser` | `(email: string) => Promise<UserOutput \| undefined>` | ✅ | Fetch user by email |
|
|
67
|
+
| `createUser` | `(user: UserInput) => Promise<UserOutput \| undefined>` | ✅ | Create new user |
|
|
68
|
+
| `signingKey` | `string` | ✅ | JWT signing key |
|
|
69
|
+
| `refreshKey` | `string` | ✅ | JWT refresh key |
|
|
70
|
+
| `turnstileKey` | `string` | ❌ | Cloudflare Turnstile secret key |
|
|
71
|
+
| `onSignUp` | `(user: UserOutput) => void` | ✅ | Callback when user signs up |
|
|
72
|
+
| `onLogout` | `(body: string) => Promise<void>` | ❌ | Callback on logout |
|
|
73
|
+
| `onError` | `ErrorHandler` | ❌ | Custom error handler |
|
|
74
|
+
|
|
75
|
+
#### Types
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
interface UserInput {
|
|
79
|
+
email: string;
|
|
80
|
+
password: string;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface UserOutput {
|
|
85
|
+
id: number;
|
|
86
|
+
email: string;
|
|
87
|
+
password: string | null;
|
|
88
|
+
[key: string]: unknown;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Google Authentication
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const googleAuth = initGoogleAuth({
|
|
98
|
+
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
99
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
100
|
+
authRoute: "/api/auth/google",
|
|
101
|
+
successRedirectURL: "/dashboard",
|
|
102
|
+
errorRedirectURL: "/login?error=google",
|
|
103
|
+
refreshKey: process.env.JWT_REFRESH_KEY!,
|
|
104
|
+
getUserIdFromEmail: async (userInfo) => {
|
|
105
|
+
/* return user ID or null */
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const { GET } = googleAuth;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### Options
|
|
113
|
+
|
|
114
|
+
| Option | Type | Required | Description |
|
|
115
|
+
| -------------------- | ----------------------------------------------------- | -------- | --------------------------------- |
|
|
116
|
+
| `clientId` | `string` | ✅ | Google OAuth client ID |
|
|
117
|
+
| `clientSecret` | `string` | ✅ | Google OAuth client secret |
|
|
118
|
+
| `authRoute` | `string` | ✅ | OAuth callback route |
|
|
119
|
+
| `successRedirectURL` | `string` | ✅ | Redirect URL on success |
|
|
120
|
+
| `errorRedirectURL` | `string` | ✅ | Redirect URL on error |
|
|
121
|
+
| `refreshKey` | `string` | ✅ | JWT refresh key |
|
|
122
|
+
| `getUserIdFromEmail` | `(email: Schema$Userinfo) => Promise<number \| null>` | ✅ | Get user ID from Google user info |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
### Instagram Authentication
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const instagramAuth = initInstagramAuth({
|
|
130
|
+
clientId: process.env.INSTAGRAM_CLIENT_ID!,
|
|
131
|
+
clientSecret: process.env.INSTAGRAM_CLIENT_SECRET!,
|
|
132
|
+
authRoute: "/api/auth/instagram",
|
|
133
|
+
successRedirectURL: "/dashboard",
|
|
134
|
+
errorRedirectURL: "/login?error=instagram",
|
|
135
|
+
refreshKey: process.env.JWT_REFRESH_KEY!,
|
|
136
|
+
onUser: async (data, userId, accessToken) => {
|
|
137
|
+
/* handle Instagram user */
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export const { GET, getRefreshedAccessToken } = instagramAuth;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Options
|
|
145
|
+
|
|
146
|
+
| Option | Type | Required | Description |
|
|
147
|
+
| -------------------- | ------------------------------------------------------------------------------------------- | -------- | --------------------------------- |
|
|
148
|
+
| `clientId` | `string` | ✅ | Instagram app client ID |
|
|
149
|
+
| `clientSecret` | `string` | ✅ | Instagram app client secret |
|
|
150
|
+
| `authRoute` | `string` | ✅ | OAuth callback route |
|
|
151
|
+
| `successRedirectURL` | `string` | ✅ | Redirect URL on success |
|
|
152
|
+
| `errorRedirectURL` | `string` | ✅ | Redirect URL on error |
|
|
153
|
+
| `refreshKey` | `string` | ✅ | JWT refresh key |
|
|
154
|
+
| `onUser` | `(data: InstagramUser, id: number \| null, accessToken: string) => Promise<string \| void>` | ✅ | Callback with Instagram user data |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## GraphQL Module
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import {
|
|
162
|
+
initGraphQLServer,
|
|
163
|
+
GQLError,
|
|
164
|
+
query,
|
|
165
|
+
field,
|
|
166
|
+
QueryLibrary,
|
|
167
|
+
FieldLibrary,
|
|
168
|
+
} from "naystack/graphql";
|
|
169
|
+
import type { Context, AuthorizedContext } from "naystack/graphql";
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Initialize GraphQL Server
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const { GET, POST } = await initGraphQLServer({
|
|
176
|
+
resolvers: [UserResolver, PostResolver],
|
|
177
|
+
authChecker: ({ context }) => !!context.userId,
|
|
178
|
+
plugins: [], // Optional Apollo plugins
|
|
179
|
+
context: async (req) => ({
|
|
180
|
+
// Custom context builder
|
|
181
|
+
userId: await getUserIdFromRequest(req),
|
|
182
|
+
}),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export { GET, POST };
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### Options
|
|
189
|
+
|
|
190
|
+
| Option | Type | Required | Description |
|
|
191
|
+
| ------------- | ------------------------------------ | -------- | -------------------------------- |
|
|
192
|
+
| `resolvers` | `NonEmptyArray<Function>` | ✅ | Array of TypeGraphQL resolvers |
|
|
193
|
+
| `authChecker` | `AuthChecker<any>` | ❌ | Custom auth checker function |
|
|
194
|
+
| `plugins` | `ApolloServerPlugin[]` | ❌ | Additional Apollo Server plugins |
|
|
195
|
+
| `context` | `(req: NextRequest) => Promise<any>` | ❌ | Context builder function |
|
|
196
|
+
|
|
197
|
+
### Error Handling
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { GQLError } from "naystack/graphql";
|
|
201
|
+
|
|
202
|
+
// Usage in resolvers
|
|
203
|
+
throw GQLError(404, "User not found");
|
|
204
|
+
throw GQLError(403); // "You are not allowed to perform this action"
|
|
205
|
+
throw GQLError(400); // "Please provide all required inputs"
|
|
206
|
+
throw GQLError(); // "Server Error" (500)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Query & Field Helpers
|
|
210
|
+
|
|
211
|
+
Build resolvers functionally using `query`, `field`, `QueryLibrary`, and `FieldLibrary`:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { query, QueryLibrary, field, FieldLibrary } from "naystack/graphql";
|
|
215
|
+
|
|
216
|
+
// Define queries/mutations
|
|
217
|
+
const queries = {
|
|
218
|
+
getUser: query(
|
|
219
|
+
async (ctx, input) => {
|
|
220
|
+
return await db.query.users.findFirst({ where: eq(users.id, input) });
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
output: User,
|
|
224
|
+
input: Number,
|
|
225
|
+
authorized: true,
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
createUser: query(
|
|
229
|
+
async (ctx, input) => {
|
|
230
|
+
/* ... */
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
output: User,
|
|
234
|
+
input: CreateUserInput,
|
|
235
|
+
mutation: true, // Makes this a mutation instead of query
|
|
236
|
+
}
|
|
237
|
+
),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Generate resolver class
|
|
241
|
+
const UserResolver = QueryLibrary(queries);
|
|
242
|
+
|
|
243
|
+
// Define field resolvers
|
|
244
|
+
const fields = {
|
|
245
|
+
posts: field(
|
|
246
|
+
async (root, ctx) => {
|
|
247
|
+
return await db.query.posts.findMany({
|
|
248
|
+
where: eq(posts.userId, root.id),
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
{ output: [Post] }
|
|
252
|
+
),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const UserFieldResolver = FieldLibrary(User, fields);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Types
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
interface Context {
|
|
262
|
+
userId: number | null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
interface AuthorizedContext {
|
|
266
|
+
userId: number;
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Client Module
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import {
|
|
276
|
+
useVisibility,
|
|
277
|
+
useBreakpoint,
|
|
278
|
+
setupSEO,
|
|
279
|
+
getHandleImageUpload,
|
|
280
|
+
getInstagramAuthorizationURLSetup,
|
|
281
|
+
} from "naystack/client";
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Hooks
|
|
285
|
+
|
|
286
|
+
#### `useVisibility`
|
|
287
|
+
|
|
288
|
+
Observe element visibility using Intersection Observer.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
function Component() {
|
|
292
|
+
const ref = useVisibility(() => {
|
|
293
|
+
console.log("Element is visible!");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return <div ref={ref}>Watch me!</div>;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### `useBreakpoint`
|
|
301
|
+
|
|
302
|
+
React to media query changes.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
function Component() {
|
|
306
|
+
const isMobile = useBreakpoint("(max-width: 768px)");
|
|
307
|
+
|
|
308
|
+
return <div>{isMobile ? "Mobile" : "Desktop"}</div>;
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### SEO Helper
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const getSEO = setupSEO({
|
|
316
|
+
title: "My App",
|
|
317
|
+
description: "Default description",
|
|
318
|
+
siteName: "MyApp",
|
|
319
|
+
themeColor: "#000000",
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// In page
|
|
323
|
+
export const metadata = getSEO(
|
|
324
|
+
"Page Title",
|
|
325
|
+
"Page description",
|
|
326
|
+
"/og-image.png"
|
|
327
|
+
);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Instagram Authorization URL
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const getInstagramAuthURL = getInstagramAuthorizationURLSetup(
|
|
334
|
+
process.env.INSTAGRAM_CLIENT_ID!,
|
|
335
|
+
"https://myapp.com/api/auth/instagram"
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
// Usage
|
|
339
|
+
const authURL = getInstagramAuthURL(userToken);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Image Upload Client
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
const uploadImage = getHandleImageUpload("/api/upload");
|
|
346
|
+
|
|
347
|
+
const result = await uploadImage({
|
|
348
|
+
file: imageFile,
|
|
349
|
+
token: authToken,
|
|
350
|
+
type: "avatar",
|
|
351
|
+
data: { userId: 123 }, // Optional additional data
|
|
352
|
+
sync: true, // Optional: wait for processing
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## File Module
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { setupFileUpload } from "naystack/file";
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Setup File Upload
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const fileUpload = setupFileUpload({
|
|
368
|
+
refreshKey: process.env.JWT_REFRESH_KEY!,
|
|
369
|
+
signingKey: process.env.JWT_SIGNING_KEY!,
|
|
370
|
+
region: "us-east-1",
|
|
371
|
+
bucket: "my-bucket",
|
|
372
|
+
awsKey: process.env.AWS_ACCESS_KEY_ID!,
|
|
373
|
+
awsSecret: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
374
|
+
processFile: async ({ url, type, userId, data }) => {
|
|
375
|
+
// Process uploaded file
|
|
376
|
+
return {
|
|
377
|
+
deleteURL: url, // URL to delete if needed
|
|
378
|
+
response: { success: true },
|
|
379
|
+
};
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// Export route handler
|
|
384
|
+
export const { PUT } = fileUpload;
|
|
385
|
+
|
|
386
|
+
// Server-side utilities
|
|
387
|
+
const { getUploadFileURL, uploadImage, deleteImage, getFileURL, uploadFile } =
|
|
388
|
+
fileUpload;
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
#### Options
|
|
392
|
+
|
|
393
|
+
| Option | Type | Required | Description |
|
|
394
|
+
| ------------- | ---------- | -------- | ------------------------ |
|
|
395
|
+
| `refreshKey` | `string` | ✅ | JWT refresh key |
|
|
396
|
+
| `signingKey` | `string` | ✅ | JWT signing key |
|
|
397
|
+
| `region` | `string` | ✅ | AWS S3 region |
|
|
398
|
+
| `bucket` | `string` | ✅ | AWS S3 bucket name |
|
|
399
|
+
| `awsKey` | `string` | ✅ | AWS access key ID |
|
|
400
|
+
| `awsSecret` | `string` | ✅ | AWS secret access key |
|
|
401
|
+
| `processFile` | `Function` | ✅ | File processing callback |
|
|
402
|
+
|
|
403
|
+
#### Returned Utilities
|
|
404
|
+
|
|
405
|
+
| Utility | Description |
|
|
406
|
+
| ------------------ | ------------------------------ |
|
|
407
|
+
| `PUT` | Route handler for file uploads |
|
|
408
|
+
| `getUploadFileURL` | Get presigned URL for upload |
|
|
409
|
+
| `uploadImage` | Upload image to S3 |
|
|
410
|
+
| `deleteImage` | Delete image from S3 |
|
|
411
|
+
| `getFileURL` | Get public URL for a file |
|
|
412
|
+
| `uploadFile` | Upload any file to S3 |
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Socials Module
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import {
|
|
420
|
+
// Instagram
|
|
421
|
+
getInstagramUser,
|
|
422
|
+
getInstagramMedia,
|
|
423
|
+
getInstagramConversations,
|
|
424
|
+
getInstagramConversationsByUser,
|
|
425
|
+
getInstagramConversationByUser,
|
|
426
|
+
getInstagramConversation,
|
|
427
|
+
getInstagramMessage,
|
|
428
|
+
sendInstagramMessage,
|
|
429
|
+
setupInstagramWebhook,
|
|
430
|
+
// Threads
|
|
431
|
+
getThread,
|
|
432
|
+
getThreads,
|
|
433
|
+
getThreadsReplies,
|
|
434
|
+
createThread,
|
|
435
|
+
createThreadsPost,
|
|
436
|
+
setupThreadsWebhook,
|
|
437
|
+
} from "naystack/socials";
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Instagram API
|
|
441
|
+
|
|
442
|
+
#### Get User Data
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
const user = await getInstagramUser(accessToken);
|
|
446
|
+
const user = await getInstagramUser(accessToken, "user_id");
|
|
447
|
+
const user = await getInstagramUser(accessToken, "me", [
|
|
448
|
+
"username",
|
|
449
|
+
"followers_count",
|
|
450
|
+
]);
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Get Media
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
const media = await getInstagramMedia(accessToken);
|
|
457
|
+
const media = await getInstagramMedia(
|
|
458
|
+
accessToken,
|
|
459
|
+
["like_count", "comments_count"],
|
|
460
|
+
24
|
|
461
|
+
);
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### Conversations
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
// Get all conversations
|
|
468
|
+
const { data, fetchMore } = await getInstagramConversations(accessToken);
|
|
469
|
+
|
|
470
|
+
// Get conversations by user
|
|
471
|
+
const conversations = await getInstagramConversationsByUser(
|
|
472
|
+
accessToken,
|
|
473
|
+
userId
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
// Get single conversation
|
|
477
|
+
const conversation = await getInstagramConversationByUser(accessToken, userId);
|
|
478
|
+
|
|
479
|
+
// Get conversation with messages
|
|
480
|
+
const { messages, participants, fetchMore } = await getInstagramConversation(
|
|
481
|
+
accessToken,
|
|
482
|
+
conversationId
|
|
483
|
+
);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### Messages
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Get message details
|
|
490
|
+
const message = await getInstagramMessage(accessToken, messageId);
|
|
491
|
+
|
|
492
|
+
// Send message
|
|
493
|
+
const result = await sendInstagramMessage(accessToken, recipientId, "Hello!");
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### Webhook
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
const instagramWebhook = setupInstagramWebhook({
|
|
500
|
+
secret: process.env.INSTAGRAM_WEBHOOK_SECRET!,
|
|
501
|
+
callback: async (type, value, id) => {
|
|
502
|
+
// Handle webhook events
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
export const { GET, POST } = instagramWebhook;
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
### Threads API
|
|
512
|
+
|
|
513
|
+
#### Get Threads
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
// Get single thread
|
|
517
|
+
const thread = await getThread(accessToken, threadId);
|
|
518
|
+
const thread = await getThread(accessToken, threadId, ["text", "permalink"]);
|
|
519
|
+
|
|
520
|
+
// Get all threads
|
|
521
|
+
const threads = await getThreads(accessToken);
|
|
522
|
+
|
|
523
|
+
// Get thread replies
|
|
524
|
+
const replies = await getThreadsReplies(accessToken, threadId);
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Create Threads
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
// Create single post
|
|
531
|
+
const postId = await createThreadsPost(accessToken, "Hello, Threads!");
|
|
532
|
+
|
|
533
|
+
// Reply to a thread
|
|
534
|
+
const replyId = await createThreadsPost(
|
|
535
|
+
accessToken,
|
|
536
|
+
"Reply text",
|
|
537
|
+
parentThreadId
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
// Create threaded posts (carousel)
|
|
541
|
+
const firstPostId = await createThread(accessToken, [
|
|
542
|
+
"First post",
|
|
543
|
+
"Second post in thread",
|
|
544
|
+
"Third post in thread",
|
|
545
|
+
]);
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Webhook
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const threadsWebhook = setupThreadsWebhook({
|
|
552
|
+
secret: process.env.THREADS_WEBHOOK_SECRET!,
|
|
553
|
+
callback: async (type, value) => {
|
|
554
|
+
// Handle webhook events
|
|
555
|
+
return true; // Return false to indicate failure
|
|
556
|
+
},
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
export const { GET, POST } = threadsWebhook;
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Environment Variables
|
|
565
|
+
|
|
566
|
+
Here's a summary of common environment variables used:
|
|
567
|
+
|
|
568
|
+
```env
|
|
569
|
+
# JWT Keys
|
|
570
|
+
JWT_SIGNING_KEY=your-signing-key
|
|
571
|
+
JWT_REFRESH_KEY=your-refresh-key
|
|
572
|
+
|
|
573
|
+
# Google OAuth
|
|
574
|
+
GOOGLE_CLIENT_ID=your-google-client-id
|
|
575
|
+
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
576
|
+
|
|
577
|
+
# Instagram
|
|
578
|
+
INSTAGRAM_CLIENT_ID=your-instagram-client-id
|
|
579
|
+
INSTAGRAM_CLIENT_SECRET=your-instagram-client-secret
|
|
580
|
+
INSTAGRAM_WEBHOOK_SECRET=your-instagram-webhook-secret
|
|
581
|
+
|
|
582
|
+
# Threads
|
|
583
|
+
THREADS_WEBHOOK_SECRET=your-threads-webhook-secret
|
|
584
|
+
|
|
585
|
+
# AWS S3
|
|
586
|
+
AWS_ACCESS_KEY_ID=your-aws-key
|
|
587
|
+
AWS_SECRET_ACCESS_KEY=your-aws-secret
|
|
588
|
+
|
|
589
|
+
# Cloudflare Turnstile (optional)
|
|
590
|
+
TURNSTILE_SECRET_KEY=your-turnstile-key
|
|
591
|
+
|
|
592
|
+
# App
|
|
593
|
+
NEXT_PUBLIC_BASE_URL=https://your-app.com
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## License
|
|
599
|
+
|
|
600
|
+
ISC © [Abhinay Pandey](mailto:abhinaypandey02@gmail.com)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naystack",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "A stack built with
|
|
3
|
+
"version": "1.1.17",
|
|
4
|
+
"description": "A stack built with Next + GraphQL + S3 + Auth",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"build": "
|
|
13
|
-
"test": "tsx tests/node.ts"
|
|
12
|
+
"build": "tsup"
|
|
14
13
|
},
|
|
15
14
|
"exports": {
|
|
16
15
|
"./auth": {
|
|
@@ -100,5 +99,9 @@
|
|
|
100
99
|
"tsx": "^4.20.5",
|
|
101
100
|
"typescript": "^5.9.2",
|
|
102
101
|
"typescript-eslint": "^8.31.1"
|
|
102
|
+
},
|
|
103
|
+
"repository": {
|
|
104
|
+
"type": "git",
|
|
105
|
+
"url": "https://github.com/abhinaypandey02/naystack.git"
|
|
103
106
|
}
|
|
104
107
|
}
|