@xenterprises/fastify-xconfig 0.0.10 → 1.0.1

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.
Files changed (99) hide show
  1. package/.github/workflows/ci.yml +19 -0
  2. package/.taprc +3 -0
  3. package/README.md +18 -134
  4. package/index.d.ts +13 -0
  5. package/index.js +9 -0
  6. package/package.json +33 -39
  7. package/test/index.test-d.ts +13 -0
  8. package/test/index.test.js +14 -0
  9. package/test/xConfig.js +115 -0
  10. package/tsconfig.json +9 -0
  11. package/dist/integrations/cloudinary.d.ts +0 -1
  12. package/dist/integrations/cloudinary.js +0 -25
  13. package/dist/integrations/cloudinary.js.map +0 -1
  14. package/dist/integrations/prisma.d.ts +0 -1
  15. package/dist/integrations/prisma.js +0 -13
  16. package/dist/integrations/prisma.js.map +0 -1
  17. package/dist/integrations/sendgrid.d.ts +0 -1
  18. package/dist/integrations/sendgrid.js +0 -22
  19. package/dist/integrations/sendgrid.js.map +0 -1
  20. package/dist/integrations/stripe.d.ts +0 -1
  21. package/dist/integrations/stripe.js +0 -15
  22. package/dist/integrations/stripe.js.map +0 -1
  23. package/dist/integrations/twilio.d.ts +0 -1
  24. package/dist/integrations/twilio.js +0 -17
  25. package/dist/integrations/twilio.js.map +0 -1
  26. package/dist/middleware/bugsnag.d.ts +0 -2
  27. package/dist/middleware/bugsnag.js +0 -9
  28. package/dist/middleware/bugsnag.js.map +0 -1
  29. package/dist/middleware/cors.d.ts +0 -2
  30. package/dist/middleware/cors.js +0 -11
  31. package/dist/middleware/cors.js.map +0 -1
  32. package/dist/middleware/errorHandler.d.ts +0 -2
  33. package/dist/middleware/errorHandler.js +0 -19
  34. package/dist/middleware/errorHandler.js.map +0 -1
  35. package/dist/middleware/multipart.d.ts +0 -2
  36. package/dist/middleware/multipart.js +0 -7
  37. package/dist/middleware/multipart.js.map +0 -1
  38. package/dist/middleware/rateLimit.d.ts +0 -2
  39. package/dist/middleware/rateLimit.js +0 -7
  40. package/dist/middleware/rateLimit.js.map +0 -1
  41. package/dist/middleware/underPressure.d.ts +0 -2
  42. package/dist/middleware/underPressure.js +0 -7
  43. package/dist/middleware/underPressure.js.map +0 -1
  44. package/dist/utils/colorize.d.ts +0 -4
  45. package/dist/utils/colorize.js +0 -33
  46. package/dist/utils/colorize.js.map +0 -1
  47. package/dist/utils/formatBytes.d.ts +0 -1
  48. package/dist/utils/formatBytes.js +0 -10
  49. package/dist/utils/formatBytes.js.map +0 -1
  50. package/dist/utils/randomUUID.d.ts +0 -1
  51. package/dist/utils/randomUUID.js +0 -3
  52. package/dist/utils/randomUUID.js.map +0 -1
  53. package/dist/utils/statAsync.d.ts +0 -2
  54. package/dist/utils/statAsync.js +0 -4
  55. package/dist/utils/statAsync.js.map +0 -1
  56. package/dist/xConfig.d.ts +0 -3
  57. package/dist/xConfig.js +0 -9
  58. package/dist/xConfig.js.map +0 -1
  59. package/server/app.js +0 -92
  60. package/src/auth/admin.js +0 -241
  61. package/src/auth/portal.js +0 -286
  62. package/src/integrations/cloudinary.js +0 -98
  63. package/src/integrations/geocode.js +0 -43
  64. package/src/integrations/prisma.js +0 -30
  65. package/src/integrations/sendgrid.js +0 -58
  66. package/src/integrations/twilio.js +0 -146
  67. package/src/lifecycle/xFastifyAfter.js +0 -27
  68. package/src/middleware/bugsnag.js +0 -10
  69. package/src/middleware/cors.js +0 -10
  70. package/src/middleware/fancyErrors.js +0 -26
  71. package/src/middleware/multipart.js +0 -6
  72. package/src/middleware/rateLimit.js +0 -6
  73. package/src/middleware/underPressure.js +0 -6
  74. package/src/utils/colorize.js +0 -37
  75. package/src/utils/cookie.js +0 -5
  76. package/src/utils/formatBytes.js +0 -16
  77. package/src/utils/health.js +0 -126
  78. package/src/utils/xEcho.js +0 -12
  79. package/src/utils/xSlugify.js +0 -20
  80. package/src/utils/xUUID.js +0 -14
  81. package/src/xConfig.js +0 -117
  82. package/test/index.js +0 -17
  83. package/ts-reference/integrations/cloudinary.ts +0 -26
  84. package/ts-reference/integrations/prisma.ts +0 -13
  85. package/ts-reference/integrations/sendgrid.ts +0 -27
  86. package/ts-reference/integrations/stripe.ts +0 -15
  87. package/ts-reference/integrations/twilio.ts +0 -20
  88. package/ts-reference/middleware/bugsnag.ts +0 -10
  89. package/ts-reference/middleware/cors.ts +0 -13
  90. package/ts-reference/middleware/errorHandler.ts +0 -24
  91. package/ts-reference/middleware/multipart.ts +0 -8
  92. package/ts-reference/middleware/rateLimit.ts +0 -8
  93. package/ts-reference/middleware/underPressure.ts +0 -11
  94. package/ts-reference/utils/colorize.ts +0 -45
  95. package/ts-reference/utils/formatBytes.ts +0 -8
  96. package/ts-reference/utils/randomUUID.ts +0 -3
  97. package/ts-reference/utils/statAsync.ts +0 -4
  98. package/xConfigReference.js +0 -1495
  99. package/xConfigWorkingList.js +0 -720
@@ -1,286 +0,0 @@
1
- import jwt from "@fastify/jwt";
2
- import bcrypt from "bcrypt";
3
- import { config } from "process";
4
- const isProduction = process.env.NODE_ENV === 'production';
5
- export async function setupAuth(fastify, options) {
6
- if (options.user?.active !== false) {
7
- // Ensure the user JWT secret is provided
8
- if (!options.user.secret) {
9
- throw new Error("User JWT secret must be provided.");
10
- }
11
-
12
- const userAuthOptions = options.user;
13
- const userCookieName = userAuthOptions.cookieOptions?.name || "userToken";
14
- const userRefreshCookieName =
15
- userAuthOptions.cookieOptions?.refreshTokenName || "userRefreshToken";
16
- const userCookieOptions = {
17
- httpOnly: true, // Ensures the cookie is not accessible via JavaScript
18
- secure: isProduction, // true in production (HTTPS), false in development (HTTP)
19
- sameSite: isProduction ? 'None' : 'Lax', // 'None' for cross-origin, 'Lax' for development
20
- path: '/', // Ensure cookies are valid for the entire site
21
- };
22
- const userExcludedPaths = userAuthOptions.excludedPaths || [
23
- "/portal/auth/login",
24
- "/portal/auth/logout",
25
- "/portal/auth/register",
26
- "/portal/auth/forgot-password",
27
- "/portal/auth/reset-password"
28
- ];
29
-
30
- // Register JWT for user
31
- await fastify.register(jwt, {
32
- secret: userAuthOptions.secret,
33
- sign: { algorithm: 'HS256', expiresIn: userAuthOptions.expiresIn || "15m" },
34
- cookie: { cookieName: userCookieName, signed: false },
35
- namespace: "userJwt",
36
- jwtVerify: "userJwtVerify",
37
- jwtSign: "userJwtSign",
38
- });
39
-
40
- // Common function to set tokens as cookies
41
- const setAuthCookies = (reply, accessToken, refreshToken) => {
42
- reply.setCookie(userCookieName, accessToken, userCookieOptions);
43
- reply.setCookie(userRefreshCookieName, refreshToken, {
44
- // ...userCookieOptions,
45
- httpOnly: true, // Ensures the cookie is not accessible via JavaScript
46
- secure: isProduction, // true in production (HTTPS), false in development (HTTP)
47
- sameSite: isProduction ? 'None' : 'Lax', // 'None' for cross-origin, 'Lax' for development
48
- path: '/', // Ensure cookies are valid for the entire site
49
- });
50
- };
51
-
52
- // User authentication hook
53
- fastify.addHook("onRequest", async (request, reply) => {
54
- const url = request.url;
55
-
56
- // Skip authentication for excluded paths
57
- if (userExcludedPaths.some((path) => url.startsWith(path))) {
58
- return;
59
- }
60
-
61
- if (url.startsWith("/portal")) {
62
- try {
63
- // Extract token from cookie or Authorization header
64
- const authHeader = request.headers.authorization;
65
- const authToken =
66
- authHeader && authHeader.startsWith("Bearer ")
67
- ? authHeader.slice(7)
68
- : null;
69
- const token = request.cookies[userCookieName] || authToken;
70
-
71
- if (!token) {
72
- throw fastify.httpErrors.unauthorized(
73
- "User access token not provided"
74
- );
75
- }
76
-
77
- // Verify access token using the namespaced verify method
78
- const decoded = await request.userJwtVerify(token);
79
- request.userAuth = decoded; // Attach user auth context
80
- } catch (err) {
81
- // Use built-in HTTP error handling
82
- reply.send(
83
- fastify.httpErrors.unauthorized("Invalid or expired access token")
84
- );
85
- }
86
- }
87
- });
88
-
89
- // User registration route
90
- fastify.post("/portal/auth/register", async (req, reply) => {
91
- try {
92
- const { email, password, firstName, lastName } = req.body;
93
-
94
- // Validate input
95
- if (!email || !password || !firstName || !lastName) {
96
- throw fastify.httpErrors.badRequest("Missing required fields");
97
- }
98
-
99
- // Check if user already exists
100
- const existingUser = await fastify.prisma.users.findUnique({
101
- where: { email },
102
- });
103
- if (existingUser) {
104
- throw fastify.httpErrors.conflict("Email already in use");
105
- }
106
-
107
- // Hash the password
108
- const hashedPassword = await bcrypt.hash(password, 10);
109
-
110
- // Create the user
111
- const user = await fastify.prisma.users.create({
112
- data: {
113
- email,
114
- password: hashedPassword,
115
- firstName,
116
- lastName,
117
- },
118
- });
119
-
120
- // // Send welcome email
121
- // if (options.user?.sendWelcomeEmail) {
122
- // await fastify.sendGrid.sendEmail(email, auth?.user?.registerEmail?.subject, auth?.user?.registerEmail?.templateId, { name: firstName, email });
123
- // }
124
-
125
-
126
- // Issue access token
127
- const accessToken = await reply.userJwtSign({ id: user.id });
128
-
129
- // Generate refresh token
130
- const refreshToken = await fastify.randomUUID();
131
- const hashedRefreshToken = await bcrypt.hash(refreshToken, 10);
132
-
133
- // Store hashed refresh token in the database
134
- await fastify.prisma.users.update({
135
- where: { id: user.id },
136
- data: { refreshToken: hashedRefreshToken },
137
- });
138
-
139
- // Set tokens as cookies
140
- setAuthCookies(reply, accessToken, refreshToken);
141
-
142
- reply.send({ accessToken });
143
- } catch (err) {
144
- reply.send(err);
145
- }
146
- });
147
-
148
- // User login route
149
- fastify.post("/portal/auth/login", async (req, reply) => {
150
- try {
151
- const { email, password } = req.body;
152
-
153
- if (!email || !password) {
154
- throw fastify.httpErrors.badRequest(
155
- "Email and password are required"
156
- );
157
- }
158
-
159
- // Fetch user from the database
160
- const user = await fastify.prisma.users.findUnique({
161
- where: { email },
162
- });
163
- if (!user) {
164
- throw fastify.httpErrors.unauthorized("Invalid credentials");
165
- }
166
-
167
- // Compare passwords using bcrypt
168
- const isValidPassword = await bcrypt.compare(password, user.password);
169
- if (!isValidPassword) {
170
- throw fastify.httpErrors.unauthorized("Invalid credentials");
171
- }
172
-
173
- // Issue access token
174
- const accessToken = await reply.userJwtSign({ id: user.id });
175
-
176
- // Generate refresh token
177
- const refreshToken = await fastify.randomUUID();
178
- const hashedRefreshToken = await bcrypt.hash(refreshToken, 10);
179
-
180
- // Store hashed refresh token in the database
181
- await fastify.prisma.users.update({
182
- where: { id: user.id },
183
- data: { refreshToken: hashedRefreshToken },
184
- });
185
-
186
- // Set tokens as cookies
187
- setAuthCookies(reply, accessToken, refreshToken);
188
-
189
- reply.send({ accessToken });
190
- } catch (err) {
191
- reply.send(err);
192
- }
193
- });
194
-
195
- // User refresh token route
196
- fastify.post("/portal/auth/refresh", async (req, reply) => {
197
- try {
198
- const userAuth = req.userAuth;
199
- const refreshToken = req.cookies[userRefreshCookieName];
200
- if (!refreshToken) {
201
- throw fastify.httpErrors.unauthorized("Refresh token not provided");
202
- }
203
-
204
- // Fetch user from the database using the refresh token
205
- const user = await fastify.prisma.users.findFirst({
206
- where: { id: userAuth?.id, refreshToken: { not: null } },
207
- });
208
-
209
- if (!user) {
210
- throw fastify.httpErrors.unauthorized("Invalid refresh token");
211
- }
212
-
213
- // Verify the refresh token
214
- const isValid = await bcrypt.compare(refreshToken, user.refreshToken);
215
- if (!isValid) {
216
- throw fastify.httpErrors.unauthorized("Invalid refresh token");
217
- }
218
-
219
- // Issue new access token
220
- const accessToken = await reply.userJwtSign({ id: user.id });
221
-
222
- // Generate new refresh token
223
- const newRefreshToken = fastify.randomUUID();
224
- const hashedNewRefreshToken = await bcrypt.hash(newRefreshToken, 10);
225
-
226
- // Update refresh token in the database
227
- await fastify.prisma.users.update({
228
- where: { id: user.id },
229
- data: { refreshToken: hashedNewRefreshToken },
230
- });
231
-
232
- // Set new tokens as cookies
233
- setAuthCookies(reply, accessToken, newRefreshToken);
234
-
235
- reply.send({ accessToken });
236
- } catch (err) {
237
- reply.send(err);
238
- }
239
- });
240
-
241
- // User logout route
242
- fastify.post("/portal/auth/logout", async (req, reply) => {
243
- try {
244
- const userAuth = req.userAuth;
245
- if (userAuth) {
246
- // Delete refresh token from the database
247
- await fastify.prisma.users.update({
248
- where: { id: userAuth.id },
249
- data: { refreshToken: null },
250
- });
251
- }
252
-
253
- // Clear cookies
254
- reply.clearCookie(userCookieName, { path: "/" });
255
- reply.clearCookie(userRefreshCookieName, { path: "/" });
256
-
257
- reply.send({ message: "Logged out successfully" });
258
- } catch (err) {
259
- reply.send(err);
260
- }
261
- });
262
-
263
- // User authentication status route
264
- fastify.get("/portal/auth/me", async (req, reply) => {
265
- try {
266
- const userAuth = req.userAuth;
267
-
268
- // Fetch user details from database
269
- const user = await fastify.prisma.users.findUnique({
270
- where: { id: userAuth.id },
271
- select: { id: true, email: true, firstName: true, lastName: true, ...userAuthOptions.me },
272
- });
273
-
274
- if (!user) {
275
- throw fastify.httpErrors.notFound("User not found");
276
- }
277
-
278
- reply.send(user);
279
- } catch (err) {
280
- reply.send(err);
281
- }
282
- });
283
-
284
- console.info(" ✅ Auth User Enabled");
285
- }
286
- }
@@ -1,98 +0,0 @@
1
- import { v2 as Cloudinary } from "cloudinary";
2
-
3
- export async function setupCloudinary(fastify, options) {
4
-
5
- if (options.active !== false) {
6
- if (
7
- !options.cloudName ||
8
- !options.apiKey ||
9
- !options.apiSecret
10
- ) {
11
- throw new Error(
12
- "Cloudinary cloudName, apiKey, and apiSecret must be provided."
13
- );
14
- }
15
-
16
- Cloudinary.config({
17
- cloud_name: options.cloudName,
18
- api_key: options.apiKey,
19
- api_secret: options.apiSecret,
20
- },
21
- );
22
-
23
- fastify.decorate('cloudinary', {
24
- upload: async (fileStream, options = {}) => {
25
- return new Promise((resolve, reject) => {
26
- if (options.folder) {
27
- options.folder = options.folder || options.folder;
28
- }
29
- options.resource_type = options.resource_type || 'auto';
30
- options.timestamp = Math.floor(Date.now() / 1000); // Add timestamp to options
31
- options.use_filename = options.use_filename !== undefined ? options.use_filename : true;
32
- options.unique_filename = options.unique_filename !== undefined ? options.unique_filename : true;
33
-
34
- const uploadStream = Cloudinary.uploader.upload_stream(options, (error, result) => {
35
- if (error) {
36
- fastify.log.error("Cloudinary upload failed:", error);
37
- reject(new Error(`Failed to upload to Cloudinary: ${JSON.stringify(error)}`));
38
- } else {
39
- resolve(result); // Resolve with the result from Cloudinary
40
- }
41
- });
42
-
43
- fileStream.pipe(uploadStream)
44
- .on('error', (streamError) => {
45
- fastify.log.error("Stream error:", streamError);
46
- reject(new Error(`Stream error during Cloudinary upload: ${streamError.message}`));
47
- })
48
- .on('end', () => {
49
- fastify.log.info("Stream ended successfully.");
50
- });
51
- });
52
- },
53
-
54
- uploadLarge: async (fileStream, options = {}) => {
55
- return new Promise((resolve, reject) => {
56
- if (options.folder) {
57
- options.folder = options.folder || options.folder;
58
- }
59
- options.resource_type = options.resource_type || 'auto';
60
- options.timestamp = Math.floor(Date.now() / 1000);
61
- options.use_filename = options.use_filename !== undefined ? options.use_filename : true;
62
- options.unique_filename = options.unique_filename !== undefined ? options.unique_filename : true;
63
- options.overwrite = options.overwrite !== undefined ? options.overwrite : false;
64
-
65
- const uploadStream = Cloudinary.uploader.upload_stream(options, (error, result) => {
66
- if (error) {
67
- fastify.log.error("Cloudinary upload failed:", error);
68
- reject(new Error(`Failed to upload to Cloudinary: ${JSON.stringify(error)}`));
69
- } else {
70
- resolve(result);
71
- }
72
- });
73
-
74
- fileStream.pipe(uploadStream)
75
- .on('error', (streamError) => {
76
- fastify.log.error("Stream error:", streamError);
77
- reject(new Error(`Stream error during Cloudinary upload: ${streamError.message}`));
78
- })
79
- .on('end', () => {
80
- fastify.log.info("Stream ended successfully.");
81
- });
82
- });
83
- },
84
-
85
- delete: async (publicId) => {
86
- try {
87
- const response = await Cloudinary.uploader.destroy(publicId);
88
- return response;
89
- } catch (error) {
90
- fastify.log.error("Cloudinary delete failed:", error);
91
- throw new Error("Failed to delete from Cloudinary.");
92
- }
93
- }
94
- });
95
-
96
- console.info(" ✅ Cloudinary Enabled");
97
- }
98
- }
@@ -1,43 +0,0 @@
1
- export async function setupGeocode(fastify, options) {
2
- if (options.active !== false) {
3
- if (!options.apiKey) {
4
- throw new Error("Geocode API key must be provided.");
5
- }
6
- const geocodioEndpoint = 'https://api.geocod.io/v1.7/geocode';
7
- const geocodioApiKey = options.apiKey;
8
-
9
- fastify.decorate('geocode', {
10
- // Method to get lat/long and county from a zip code
11
- async getLatLongByZip(zipCode) {
12
- try {
13
- const url = `${geocodioEndpoint}?q=${zipCode}&fields=cd,stateleg&api_key=${geocodioApiKey}`;
14
- const response = await fetch(url);
15
- const result = await response.json();
16
-
17
- if (result.results && result.results.length > 0) {
18
- const location = result.results[0].location;
19
- const addressComponents = result.results[0].address_components;
20
-
21
- return {
22
- zip: zipCode,
23
- lat: location.lat,
24
- lng: location.lng,
25
- city: addressComponents.city,
26
- county: addressComponents.county,
27
- country: addressComponents.country,
28
- state: addressComponents.state,
29
- addressComponents: addressComponents
30
- };
31
- } else {
32
- throw new Error('No results found for the provided zip code.');
33
- }
34
- } catch (error) {
35
- fastify.log.error('Failed to fetch geolocation data:', error);
36
- throw new Error('Failed to fetch geolocation data.');
37
- }
38
- }
39
- });
40
-
41
- console.info(' ✅ Geocodio Enabled');
42
- }
43
- }
@@ -1,30 +0,0 @@
1
-
2
- import { PrismaClient } from "@prisma/client";
3
- export async function setupPrisma(fastify, options) {
4
- if (options.active !== false) {
5
- delete options.active;
6
- const prisma = new PrismaClient(options);
7
-
8
- // Connect to the database
9
- await prisma.$connect();
10
-
11
- // Decorate Fastify instance with Prisma Client
12
- fastify.decorate("prisma", prisma);
13
-
14
- // Disconnect Prisma Client when Fastify closes
15
- fastify.addHook("onClose", async () => {
16
- await fastify.prisma.$disconnect();
17
- });
18
-
19
- console.info(" ✅ Prisma Enabled");
20
- }
21
- /*
22
- ===== Ensure Proper Cleanup on Server Shutdown =====
23
- */
24
- fastify.addHook("onClose", async () => {
25
- if (fastify.prisma) {
26
- await fastify.prisma.$disconnect();
27
- }
28
- // Add additional cleanup for other services if necessary
29
- });
30
- }
@@ -1,58 +0,0 @@
1
- import Sendgrid from '@sendgrid/mail';
2
- import sgClient from '@sendgrid/client';
3
- export async function setupSendgrid(fastify, options) {
4
- if (options.active !== false) {
5
- if (!options.apiKey)
6
- throw new Error("SendGrid API key must be provided.");
7
-
8
- Sendgrid.setApiKey(options.apiKey);
9
- sgClient.setApiKey(options.apiKeyEmailValidation);
10
- // Decorator to group SendGrid-related methods under fastify.sendGrid
11
- fastify.decorate('sendgrid', {
12
- sendEmail: async (to, subject, templateId, dynamicTemplateData) => {
13
- try {
14
-
15
- const msg = {
16
- to: to,
17
- from: options.fromEmail,
18
- subject: subject,
19
- templateId: templateId,
20
- dynamicTemplateData: { ...dynamicTemplateData, subject: subject },
21
- };
22
- await Sendgrid.send(msg);
23
- return true;
24
- } catch (error) {
25
- fastify.log.error("SendGrid send email failed:", error);
26
- throw new Error("Failed to send email.");
27
- }
28
- },
29
- validateEmail: async (email) => {
30
- const data = {
31
- email: email,
32
- // source: 'signup',
33
- };
34
-
35
- const request = {
36
- url: `/v3/validations/email`,
37
- method: 'POST',
38
- body: data,
39
- };
40
-
41
- try {
42
- const [response, body] = await sgClient.request(request);
43
- console.log(body)
44
- if (response.statusCode === 200) {
45
- return body?.result; // Return the validation result
46
- } else {
47
- throw new Error(body.errors ? body.errors.map(err => err.message).join(', ') : 'Failed to validate email.');
48
- }
49
- } catch (error) {
50
- fastify.log.error('SendGrid email validation failed:', error);
51
- throw new Error('Failed to validate email.');
52
- }
53
- }
54
- });
55
-
56
- console.info(" ✅ SendGrid Enabled");
57
- }
58
- }
@@ -1,146 +0,0 @@
1
- import Twilio from "twilio";
2
- export async function setupTwilio(fastify, options) {
3
- if (options.active !== false) {
4
- // Return if missing Twilio credentials
5
- if (
6
- !options.accountSid ||
7
- !options.authToken ||
8
- !options.phoneNumber
9
- )
10
- throw new Error(
11
- "Twilio accountSid, authToken, and phoneNumber must be provided."
12
- );
13
-
14
- const twilioClient = Twilio(
15
- options.accountSid,
16
- options.authToken
17
- );
18
-
19
- // Decorator to group Twilio-related methods under fastify.twilio
20
- fastify.decorate('twilio', {
21
- sendSMS: async (to, body) => {
22
- try {
23
- const message = await twilioClient.messages.create({
24
- body,
25
- to,
26
- from: options.phoneNumber, // Use the Twilio phone number from options
27
- });
28
- return message;
29
- } catch (error) {
30
- fastify.log.error("Twilio send SMS failed:", error);
31
- throw new Error("Failed to send SMS.");
32
- }
33
- },
34
- sendMMS: async (to, body, mediaUrl) => {
35
- try {
36
- const message = await twilioClient.messages.create({
37
- body,
38
- to,
39
- from: options.phoneNumber,
40
- mediaUrl: [mediaUrl], // Array of media URLs
41
- });
42
- return message;
43
- } catch (error) {
44
- fastify.log.error("Twilio send MMS failed:", error);
45
- throw new Error("Failed to send MMS.");
46
- }
47
- },
48
- makeVoiceCall: async (to, twimlUrl) => {
49
- try {
50
- const call = await twilioClient.calls.create({
51
- to,
52
- from: options.phoneNumber,
53
- url: twimlUrl, // URL that provides TwiML instructions
54
- });
55
- return call;
56
- } catch (error) {
57
- fastify.log.error("Twilio make voice call failed:", error);
58
- throw new Error("Failed to make voice call.");
59
- }
60
- },
61
- sendVerificationCode: async (to, channel = 'sms') => {
62
- try {
63
- const verification = await twilioClient.verify.services(options.verifyServiceSid)
64
- .verifications
65
- .create({ to, channel }); // channel can be 'sms' or 'call'
66
- return verification;
67
- } catch (error) {
68
- fastify.log.error("Twilio send verification code failed:", error);
69
- throw new Error("Failed to send verification code.");
70
- }
71
- },
72
- verifyCode: async (to, code) => {
73
- try {
74
- const verificationCheck = await twilioClient.verify.services(options.verifyServiceSid)
75
- .verificationChecks
76
- .create({ to, code });
77
- return verificationCheck;
78
- } catch (error) {
79
- fastify.log.error("Twilio verify code failed:", error);
80
- throw new Error("Failed to verify code.");
81
- }
82
- },
83
- fetchSMSHistory: async (fromDate, toDate) => {
84
- try {
85
- const messages = await twilioClient.messages.list({
86
- dateSentAfter: fromDate,
87
- dateSentBefore: toDate,
88
- limit: 100,
89
- });
90
- return messages;
91
- } catch (error) {
92
- fastify.log.error("Twilio fetch SMS history failed:", error);
93
- throw new Error("Failed to fetch SMS history.");
94
- }
95
- },
96
- handleIncomingSMS: (req, reply) => {
97
- const { Body, From } = req.body;
98
-
99
- // Process incoming message
100
- console.log(`Message received from ${From}: ${Body}`);
101
-
102
- reply.type('text/xml');
103
- reply.send('<Response><Message>Thanks for your message!</Message></Response>');
104
- },
105
- checkCallStatus: async (callSid) => {
106
- try {
107
- const call = await twilioClient.calls(callSid).fetch();
108
- return call;
109
- } catch (error) {
110
- fastify.log.error("Twilio check call status failed:", error);
111
- throw new Error("Failed to check call status.");
112
- }
113
- },
114
- validatePhoneNumber: async (phoneNumber) => {
115
- try {
116
- // Perform phone number validation with Twilio
117
- const validation = await twilioClient.lookups.v2
118
- .phoneNumbers(phoneNumber)
119
- .fetch();
120
-
121
- // Check if the phone number is valid (Twilio may return "valid": true/false)
122
- const isValid = validation.valid !== undefined ? validation.valid : true;
123
-
124
- // Return the required structure
125
- return {
126
- sms: phoneNumber, // The phone number
127
- smsValidate: validation, // The full validation payload from Twilio
128
- isSmsValidated: isValid // Set true/false based on the validation
129
- };
130
- } catch (error) {
131
- fastify.log.error("Twilio phone number validation failed:", error);
132
-
133
- // Return the structure with failed validation
134
- return {
135
- sms: phoneNumber, // The phone number
136
- smsValidate: {}, // Empty object as validation failed
137
- isSmsValidated: false // Validation failed
138
- };
139
- }
140
- }
141
- });
142
-
143
- console.info(" ✅ Twilio Enabled");
144
- }
145
-
146
- }
@@ -1,27 +0,0 @@
1
- // src/lifecycle/xFastifyAfter.js
2
- import { printRoutes } from "../utils/colorize.js";
3
- export async function xFastifyAfter(fastify, options) {
4
-
5
- /*
6
- ===== LIST ROUTES AFTER ALL PLUGINS =====
7
- Use the after() method to ensure this runs after all plugins are registered.
8
- */
9
- fastify.after(() => {
10
- if (options.professional !== true) {
11
- console.info(" ✅ Listing Routes:");
12
- fastify.ready(() => {
13
- printRoutes(options.routes, options.colors !== false);
14
- // Add rocket emoji
15
- console.info(
16
- `🚀 Server is ready on port ${process.env.PORT || 3000}\n\n`
17
- );
18
- // Add goodbye emoji for server shutting down
19
- fastify.addHook("onClose", () =>
20
- console.info("Server shutting down... Goodbye 👋")
21
- );
22
- });
23
- }
24
- });
25
-
26
-
27
- }