offbyt 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +2 -0
  2. package/cli/index.js +2 -0
  3. package/cli.js +206 -0
  4. package/core/detector/detectAxios.js +107 -0
  5. package/core/detector/detectFetch.js +148 -0
  6. package/core/detector/detectForms.js +55 -0
  7. package/core/detector/detectSocket.js +341 -0
  8. package/core/generator/generateControllers.js +17 -0
  9. package/core/generator/generateModels.js +25 -0
  10. package/core/generator/generateRoutes.js +17 -0
  11. package/core/generator/generateServer.js +18 -0
  12. package/core/generator/generateSocket.js +160 -0
  13. package/core/index.js +14 -0
  14. package/core/ir/IRTypes.js +25 -0
  15. package/core/ir/buildIR.js +83 -0
  16. package/core/parser/parseJS.js +26 -0
  17. package/core/parser/parseTS.js +27 -0
  18. package/core/rules/relationRules.js +38 -0
  19. package/core/rules/resourceRules.js +32 -0
  20. package/core/rules/schemaInference.js +26 -0
  21. package/core/scanner/scanProject.js +58 -0
  22. package/deploy/cloudflare.js +41 -0
  23. package/deploy/cloudflareWorker.js +122 -0
  24. package/deploy/connect.js +198 -0
  25. package/deploy/flyio.js +51 -0
  26. package/deploy/index.js +322 -0
  27. package/deploy/netlify.js +29 -0
  28. package/deploy/railway.js +215 -0
  29. package/deploy/render.js +195 -0
  30. package/deploy/utils.js +383 -0
  31. package/deploy/vercel.js +29 -0
  32. package/index.js +18 -0
  33. package/lib/generator/advancedCrudGenerator.js +475 -0
  34. package/lib/generator/crudCodeGenerator.js +486 -0
  35. package/lib/generator/irBasedGenerator.js +360 -0
  36. package/lib/ir-builder/index.js +16 -0
  37. package/lib/ir-builder/irBuilder.js +330 -0
  38. package/lib/ir-builder/rulesEngine.js +353 -0
  39. package/lib/ir-builder/templateEngine.js +193 -0
  40. package/lib/ir-builder/templates/index.js +14 -0
  41. package/lib/ir-builder/templates/model.template.js +47 -0
  42. package/lib/ir-builder/templates/routes-generic.template.js +66 -0
  43. package/lib/ir-builder/templates/routes-user.template.js +105 -0
  44. package/lib/ir-builder/templates/routes.template.js +102 -0
  45. package/lib/ir-builder/templates/validation.template.js +15 -0
  46. package/lib/ir-integration.js +349 -0
  47. package/lib/modes/benchmark.js +162 -0
  48. package/lib/modes/configBasedGenerator.js +2258 -0
  49. package/lib/modes/connect.js +1125 -0
  50. package/lib/modes/doctorAi.js +172 -0
  51. package/lib/modes/generateApi.js +435 -0
  52. package/lib/modes/interactiveSetup.js +548 -0
  53. package/lib/modes/offline.clean.js +14 -0
  54. package/lib/modes/offline.enhanced.js +787 -0
  55. package/lib/modes/offline.js +295 -0
  56. package/lib/modes/offline.v2.js +13 -0
  57. package/lib/modes/sync.js +629 -0
  58. package/lib/scanner/apiEndpointExtractor.js +387 -0
  59. package/lib/scanner/authPatternDetector.js +54 -0
  60. package/lib/scanner/frontendScanner.js +642 -0
  61. package/lib/utils/apiClientGenerator.js +242 -0
  62. package/lib/utils/apiScanner.js +95 -0
  63. package/lib/utils/codeInjector.js +350 -0
  64. package/lib/utils/doctor.js +381 -0
  65. package/lib/utils/envGenerator.js +36 -0
  66. package/lib/utils/loadTester.js +61 -0
  67. package/lib/utils/performanceAnalyzer.js +298 -0
  68. package/lib/utils/resourceDetector.js +281 -0
  69. package/package.json +20 -0
  70. package/templates/.env.template +31 -0
  71. package/templates/advanced.model.template.js +201 -0
  72. package/templates/advanced.route.template.js +341 -0
  73. package/templates/auth.middleware.template.js +87 -0
  74. package/templates/auth.routes.template.js +238 -0
  75. package/templates/auth.user.model.template.js +78 -0
  76. package/templates/cache.middleware.js +34 -0
  77. package/templates/chat.models.template.js +260 -0
  78. package/templates/chat.routes.template.js +478 -0
  79. package/templates/compression.middleware.js +19 -0
  80. package/templates/database.config.js +74 -0
  81. package/templates/errorHandler.middleware.js +54 -0
  82. package/templates/express/controller.ejs +26 -0
  83. package/templates/express/model.ejs +9 -0
  84. package/templates/express/route.ejs +18 -0
  85. package/templates/express/server.ejs +16 -0
  86. package/templates/frontend.env.template +14 -0
  87. package/templates/model.template.js +86 -0
  88. package/templates/package.production.json +51 -0
  89. package/templates/package.template.json +41 -0
  90. package/templates/pagination.utility.js +110 -0
  91. package/templates/production.server.template.js +233 -0
  92. package/templates/rateLimiter.middleware.js +36 -0
  93. package/templates/requestLogger.middleware.js +19 -0
  94. package/templates/response.helper.js +179 -0
  95. package/templates/route.template.js +130 -0
  96. package/templates/security.middleware.js +78 -0
  97. package/templates/server.template.js +91 -0
  98. package/templates/socket.server.template.js +433 -0
  99. package/templates/utils.helper.js +157 -0
  100. package/templates/validation.middleware.js +63 -0
  101. package/templates/validation.schema.js +128 -0
  102. package/utils/fileWriter.js +15 -0
  103. package/utils/logger.js +18 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Socket.io Server Template
3
+ *
4
+ * Full-featured Socket.io server with:
5
+ * - Authentication middleware
6
+ * - Room/channel management
7
+ * - Presence tracking
8
+ * - Message history
9
+ * - Typing indicators
10
+ * - File sharing support
11
+ * - Rate limiting
12
+ */
13
+
14
+ export const socketServerTemplate = `import { Server } from 'socket.io';
15
+ import jwt from 'jsonwebtoken';
16
+ import Message from '../models/Message.js';
17
+ import Conversation from '../models/Conversation.js';
18
+ import User from '../models/User.js';
19
+
20
+ // Ensure JWT_SECRET is consistent across all auth checks
21
+ const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_jwt_key_change_this_in_production';
22
+
23
+ // Active users map (userId -> socketId)
24
+ const activeUsers = new Map();
25
+
26
+ // Typing indicators (roomId -> Set of userIds)
27
+ const typingUsers = new Map();
28
+
29
+ /**
30
+ * Initialize Socket.io server
31
+ */
32
+ export function initializeSocket(httpServer) {
33
+ const io = new Server(httpServer, {
34
+ cors: {
35
+ origin: process.env.CLIENT_URL || process.env.REACT_APP_API_URL,
36
+ credentials: true
37
+ },
38
+ pingTimeout: 60000,
39
+ pingInterval: 25000
40
+ });
41
+
42
+ // ============ AUTHENTICATION MIDDLEWARE ============
43
+ io.use(async (socket, next) => {
44
+ try {
45
+ const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.split(' ')[1];
46
+
47
+ console.log(\`\n🔐 SOCKET.IO AUTH CHECK\`);
48
+ console.log(\` Token received: \${token ? 'YES ✅' : 'NO ❌'}\`);
49
+ console.log(\` Token preview: \${token ? token.substring(0, 30) + '...' : 'N/A'}\`);
50
+
51
+ if (!token) {
52
+ console.log(\` ❌ Error: No token provided\`);
53
+ return next(new Error('Authentication required'));
54
+ }
55
+
56
+ let decoded;
57
+ try {
58
+ decoded = jwt.verify(token, JWT_SECRET);
59
+ console.log(\` ✅ JWT verified successfully\`);
60
+ console.log(\` User ID from token: \${decoded.id || decoded.userId}\`);
61
+ } catch (jwtError) {
62
+ console.log(\` ❌ JWT verification failed: \${jwtError.message}\`);
63
+ console.log(\` Error type: \${jwtError.name}\`);
64
+ throw jwtError;
65
+ }
66
+
67
+ const user = await User.findById(decoded.id || decoded.userId).select('-password');
68
+
69
+ if (!user) {
70
+ console.log(\` ❌ User not found in database\`);
71
+ return next(new Error('User not found'));
72
+ }
73
+
74
+ console.log(\` ✅ User found: \${user.email}\`);
75
+ console.log(\` ✅ Socket authentication successful!\n\`);
76
+
77
+ socket.userId = user._id.toString();
78
+ socket.user = user;
79
+ next();
80
+ } catch (error) {
81
+ console.log(\` ❌ Authentication error: \${error.message}\n\`);
82
+ next(new Error(\`Socket auth failed: \${error.message}\`));
83
+ }
84
+ });
85
+
86
+ // ============ CONNECTION HANDLER ============
87
+ io.on('connection', (socket) => {
88
+ console.log(\`\n✅ USER CONNECTED\`);
89
+ console.log(\` User ID: \${socket.userId}\`);
90
+ console.log(\` Socket ID: \${socket.id}\`);
91
+ console.log(\` User email: \${socket.user?.email}\`);
92
+ console.log(\` Total connected users: \${io.engine.clientsCount}\n\`);
93
+
94
+ // Register user as online
95
+ activeUsers.set(socket.userId, socket.id);
96
+ io.emit('user:online', { userId: socket.userId, socketId: socket.id });
97
+
98
+ // ============ JOIN CONVERSATION/ROOM ============
99
+ socket.on('conversation:join', async (data) => {
100
+ try {
101
+ const { conversationId } = data;
102
+
103
+ console.log(\`\n🚪 JOIN CONVERSATION REQUEST\`);
104
+ console.log(\` User ID: \${socket.userId}\`);
105
+ console.log(\` Socket ID: \${socket.id}\`);
106
+ console.log(\` Conversation ID: \${conversationId}\`);
107
+
108
+ // Verify user has access to conversation
109
+ const conversation = await Conversation.findOne({
110
+ _id: conversationId,
111
+ participants: socket.userId
112
+ });
113
+
114
+ if (!conversation) {
115
+ console.log(\` ❌ FAILED: User NOT found in conversation participants\`);
116
+ return socket.emit('error', { message: 'Conversation not found or access denied' });
117
+ }
118
+
119
+ socket.join(conversationId);
120
+ const socketsInRoom = io.sockets.adapter.rooms.get(conversationId);
121
+ const participantCount = socketsInRoom?.size || 0;
122
+
123
+ console.log(\` ✅ SUCCESS: User joined room!\`);
124
+ console.log(\` 📊 Room "\${conversationId}" status:\`);
125
+ console.log(\` - Active socket connections: \${participantCount}\`);
126
+ console.log(\` - Conversation participants: \${conversation.participants.length}\`);
127
+ console.log(\` - Participant IDs: \${conversation.participants.join(', ')}\`);
128
+
129
+ // List all sockets in the room
130
+ const socketsInRoomArray = Array.from(socketsInRoom || []);
131
+ console.log(\` - Socket IDs in room: \${socketsInRoomArray.join(', ')}\`);
132
+
133
+ // Map socket IDs to user IDs
134
+ const usersInRoom = socketsInRoomArray.map(socketId => {
135
+ const s = io.sockets.sockets.get(socketId);
136
+ return s?.userId || 'unknown';
137
+ });
138
+ console.log(\` - User IDs in room: \${usersInRoom.join(', ')}\n\`);
139
+
140
+ socket.emit('conversation:joined', { conversationId });
141
+ } catch (error) {
142
+ console.error('❌ Join conversation error:', error);
143
+ socket.emit('error', { message: 'Failed to join conversation' });
144
+ }
145
+ });
146
+
147
+ // ============ LEAVE CONVERSATION/ROOM ============
148
+ socket.on('conversation:leave', (data) => {
149
+ const { conversationId } = data;
150
+ socket.leave(conversationId);
151
+ console.log(\`User \${socket.userId} left conversation \${conversationId}\`);
152
+ });
153
+
154
+ // ============ SEND MESSAGE ============
155
+ socket.on('message:send', async (data) => {
156
+ try {
157
+ const { conversationId, content, type = 'text', metadata } = data;
158
+
159
+ console.log(\`\n📨 MESSAGE SEND REQUEST\`);
160
+ console.log(\` Sender: \${socket.userId}\`);
161
+ console.log(\` Conversation: \${conversationId}\`);
162
+ console.log(\` Content: "\${content}\"\`);
163
+
164
+ // Verify user has access
165
+ const conversation = await Conversation.findOne({
166
+ _id: conversationId,
167
+ participants: socket.userId
168
+ });
169
+
170
+ if (!conversation) {
171
+ console.log(\`❌ User \${socket.userId} is NOT a participant in conversation \${conversationId}\`);
172
+ return socket.emit('error', { message: 'Conversation not found' });
173
+ }
174
+
175
+ console.log(\`✅ User \${socket.userId} is a participant. Participants: \${conversation.participants.join(', ')}\`);
176
+
177
+ // Create message
178
+ const message = await Message.create({
179
+ conversation: conversationId,
180
+ sender: socket.userId,
181
+ content,
182
+ type,
183
+ metadata,
184
+ timestamp: new Date()
185
+ });
186
+
187
+ // Populate sender details
188
+ await message.populate('sender', 'name email avatar');
189
+
190
+ // Update conversation last message
191
+ conversation.lastMessage = message._id;
192
+ conversation.lastMessageAt = new Date();
193
+ await conversation.save();
194
+
195
+ // Check who is in the room
196
+ const socketsInRoom = io.sockets.adapter.rooms.get(conversationId);
197
+ const usersInRoom = socketsInRoom ? Array.from(socketsInRoom).map(socketId => {
198
+ const s = io.sockets.sockets.get(socketId);
199
+ return s?.userId || 'unknown';
200
+ }) : [];
201
+
202
+ console.log(\`📊 Broadcasting to room "\${conversationId}"\`);
203
+ console.log(\` Users in room: \${usersInRoom.length > 0 ? usersInRoom.join(', ') : 'NOBODY'}\`);
204
+ console.log(\` Expected participants: \${conversation.participants.join(', ')}\`);
205
+
206
+ if (usersInRoom.length === 0) {
207
+ console.log(\` ⚠️ WARNING: No users currently in room! Message will not be delivered in real-time\`);
208
+ }
209
+
210
+ // Emit to all users in conversation
211
+ io.to(conversationId).emit('message:new', {
212
+ message: message.toObject(),
213
+ conversationId
214
+ });
215
+
216
+ console.log(\`✅ Message broadcasted to room. Message ID: \${message._id}\n\`);
217
+
218
+ // Send delivery confirmation to sender
219
+ socket.emit('message:sent', {
220
+ tempId: data.tempId,
221
+ message: message.toObject()
222
+ });
223
+ } catch (error) {
224
+ console.error('❌ Send message error:', error);
225
+ socket.emit('error', { message: 'Failed to send message' });
226
+ }
227
+ });
228
+
229
+ // ============ TYPING INDICATOR ============
230
+ socket.on('typing:start', (data) => {
231
+ const { conversationId } = data;
232
+
233
+ if (!typingUsers.has(conversationId)) {
234
+ typingUsers.set(conversationId, new Set());
235
+ }
236
+
237
+ typingUsers.get(conversationId).add(socket.userId);
238
+
239
+ // Broadcast to others in conversation
240
+ socket.to(conversationId).emit('typing:update', {
241
+ conversationId,
242
+ userId: socket.userId,
243
+ isTyping: true
244
+ });
245
+ });
246
+
247
+ socket.on('typing:stop', (data) => {
248
+ const { conversationId } = data;
249
+
250
+ if (typingUsers.has(conversationId)) {
251
+ typingUsers.get(conversationId).delete(socket.userId);
252
+ }
253
+
254
+ socket.to(conversationId).emit('typing:update', {
255
+ conversationId,
256
+ userId: socket.userId,
257
+ isTyping: false
258
+ });
259
+ });
260
+
261
+ // ============ MESSAGE READ/DELIVERY STATUS ============
262
+ socket.on('message:read', async (data) => {
263
+ try {
264
+ const { messageId, conversationId } = data;
265
+
266
+ const message = await Message.findById(messageId);
267
+ if (!message) {
268
+ return socket.emit('error', { message: 'Message not found' });
269
+ }
270
+
271
+ // Add to read receipts
272
+ if (!message.readBy.includes(socket.userId)) {
273
+ message.readBy.push(socket.userId);
274
+ message.readAt = new Date();
275
+ await message.save();
276
+ }
277
+
278
+ // Notify sender
279
+ io.to(conversationId).emit('message:read:update', {
280
+ messageId,
281
+ userId: socket.userId,
282
+ readAt: message.readAt
283
+ });
284
+ } catch (error) {
285
+ console.error('Message read error:', error);
286
+ }
287
+ });
288
+
289
+ // ============ GET CONVERSATION HISTORY ============
290
+ socket.on('messages:fetch', async (data) => {
291
+ try {
292
+ const { conversationId, limit = 50, before } = data;
293
+
294
+ const query = { conversation: conversationId };
295
+ if (before) {
296
+ query.timestamp = { $lt: new Date(before) };
297
+ }
298
+
299
+ const messages = await Message.find(query)
300
+ .sort({ timestamp: -1 })
301
+ .limit(limit)
302
+ .populate('sender', 'name email avatar')
303
+ .lean();
304
+
305
+ socket.emit('messages:history', {
306
+ conversationId,
307
+ messages: messages.reverse(),
308
+ hasMore: messages.length === limit
309
+ });
310
+ } catch (error) {
311
+ console.error('Fetch messages error:', error);
312
+ socket.emit('error', { message: 'Failed to fetch messages' });
313
+ }
314
+ });
315
+
316
+ // ============ CREATE CONVERSATION ============
317
+ socket.on('conversation:create', async (data) => {
318
+ try {
319
+ const { participantIds, name, type = 'direct' } = data;
320
+
321
+ // Include current user
322
+ const participants = [socket.userId, ...participantIds];
323
+
324
+ // For direct chats, check if conversation already exists
325
+ if (type === 'direct' && participants.length === 2) {
326
+ const existing = await Conversation.findOne({
327
+ type: 'direct',
328
+ participants: { $all: participants, $size: 2 }
329
+ });
330
+
331
+ if (existing) {
332
+ return socket.emit('conversation:created', { conversation: existing });
333
+ }
334
+ }
335
+
336
+ // Create new conversation
337
+ const conversation = await Conversation.create({
338
+ name,
339
+ type,
340
+ participants,
341
+ createdBy: socket.userId
342
+ });
343
+
344
+ await conversation.populate('participants', 'name email avatar');
345
+
346
+ // Join all participants to the room
347
+ participants.forEach(participantId => {
348
+ const socketId = activeUsers.get(participantId);
349
+ if (socketId) {
350
+ io.sockets.sockets.get(socketId)?.join(conversation._id.toString());
351
+ }
352
+ });
353
+
354
+ // Notify all participants
355
+ io.to(conversation._id.toString()).emit('conversation:new', {
356
+ conversation: conversation.toObject()
357
+ });
358
+
359
+ socket.emit('conversation:created', { conversation });
360
+ } catch (error) {
361
+ console.error('Create conversation error:', error);
362
+ socket.emit('error', { message: 'Failed to create conversation' });
363
+ }
364
+ });
365
+
366
+ // ============ FILE UPLOAD (metadata only) ============
367
+ socket.on('file:upload', async (data) => {
368
+ try {
369
+ const { conversationId, fileName, fileSize, fileType, fileUrl } = data;
370
+
371
+ const message = await Message.create({
372
+ conversation: conversationId,
373
+ sender: socket.userId,
374
+ content: fileName,
375
+ type: 'file',
376
+ metadata: {
377
+ fileName,
378
+ fileSize,
379
+ fileType,
380
+ fileUrl
381
+ },
382
+ timestamp: new Date()
383
+ });
384
+
385
+ await message.populate('sender', 'name email avatar');
386
+
387
+ io.to(conversationId).emit('message:new', {
388
+ message: message.toObject(),
389
+ conversationId
390
+ });
391
+ } catch (error) {
392
+ console.error('File upload error:', error);
393
+ socket.emit('error', { message: 'Failed to process file' });
394
+ }
395
+ });
396
+
397
+ // ============ DISCONNECT HANDLER ============
398
+ socket.on('disconnect', () => {
399
+ console.log(\`❌ User disconnected: \${socket.userId} (\${socket.id})\`);
400
+
401
+ // Remove from active users
402
+ activeUsers.delete(socket.userId);
403
+
404
+ // Remove from typing indicators
405
+ typingUsers.forEach((users, conversationId) => {
406
+ if (users.has(socket.userId)) {
407
+ users.delete(socket.userId);
408
+ io.to(conversationId).emit('typing:update', {
409
+ conversationId,
410
+ userId: socket.userId,
411
+ isTyping: false
412
+ });
413
+ }
414
+ });
415
+
416
+ // Broadcast offline status
417
+ io.emit('user:offline', { userId: socket.userId });
418
+ });
419
+
420
+ // ============ ERROR HANDLER ============
421
+ socket.on('error', (error) => {
422
+ console.error('Socket error:', error);
423
+ });
424
+ });
425
+
426
+ console.log('✅ Socket.io initialized');
427
+ return io;
428
+ }
429
+
430
+ export default initializeSocket;
431
+ `;
432
+
433
+ export default socketServerTemplate;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Response Utilities
3
+ * Standardized response formatting across the application
4
+ */
5
+
6
+ export class ResponseHelper {
7
+ static success(res, data, message = 'Success', statusCode = 200) {
8
+ return res.status(statusCode).json({
9
+ success: true,
10
+ message,
11
+ data,
12
+ timestamp: new Date().toISOString()
13
+ });
14
+ }
15
+
16
+ static paginated(res, data, pagination, message = 'Success', statusCode = 200) {
17
+ return res.status(statusCode).json({
18
+ success: true,
19
+ message,
20
+ data,
21
+ pagination,
22
+ timestamp: new Date().toISOString()
23
+ });
24
+ }
25
+
26
+ static error(res, message = 'Error', errors = [], statusCode = 500) {
27
+ return res.status(statusCode).json({
28
+ success: false,
29
+ message,
30
+ errors: Array.isArray(errors) ? errors : [errors],
31
+ timestamp: new Date().toISOString()
32
+ });
33
+ }
34
+
35
+ static validationError(res, errors) {
36
+ return res.status(400).json({
37
+ success: false,
38
+ message: 'Validation Error',
39
+ errors,
40
+ timestamp: new Date().toISOString()
41
+ });
42
+ }
43
+
44
+ static notFound(res, resource = 'Resource') {
45
+ return res.status(404).json({
46
+ success: false,
47
+ message: `${resource} not found`,
48
+ timestamp: new Date().toISOString()
49
+ });
50
+ }
51
+
52
+ static unauthorized(res) {
53
+ return res.status(401).json({
54
+ success: false,
55
+ message: 'Unauthorized access',
56
+ timestamp: new Date().toISOString()
57
+ });
58
+ }
59
+
60
+ static forbidden(res) {
61
+ return res.status(403).json({
62
+ success: false,
63
+ message: 'Forbidden',
64
+ timestamp: new Date().toISOString()
65
+ });
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Error Helper
71
+ * Custom error classes for better error handling
72
+ */
73
+ export class AppError extends Error {
74
+ constructor(message, statusCode = 500) {
75
+ super(message);
76
+ this.statusCode = statusCode;
77
+ Error.captureStackTrace(this, this.constructor);
78
+ }
79
+ }
80
+
81
+ export class ValidationError extends AppError {
82
+ constructor(message = 'Validation Error', errors = []) {
83
+ super(message, 400);
84
+ this.errors = errors;
85
+ }
86
+ }
87
+
88
+ export class NotFoundError extends AppError {
89
+ constructor(resource = 'Resource') {
90
+ super(`${resource} not found`, 404);
91
+ }
92
+ }
93
+
94
+ export class UnauthorizedError extends AppError {
95
+ constructor(message = 'Unauthorized') {
96
+ super(message, 401);
97
+ }
98
+ }
99
+
100
+ export class ForbiddenError extends AppError {
101
+ constructor(message = 'Forbidden') {
102
+ super(message, 403);
103
+ }
104
+ }
105
+
106
+ export class ConflictError extends AppError {
107
+ constructor(message = 'Conflict') {
108
+ super(message, 409);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Logger Utility
114
+ * Structured logging with timestamps
115
+ */
116
+ export class Logger {
117
+ static info(message, data = {}) {
118
+ console.log(JSON.stringify({
119
+ level: 'INFO',
120
+ message,
121
+ data,
122
+ timestamp: new Date().toISOString()
123
+ }));
124
+ }
125
+
126
+ static error(message, error = {}) {
127
+ console.error(JSON.stringify({
128
+ level: 'ERROR',
129
+ message,
130
+ error: error.message || error,
131
+ stack: error.stack,
132
+ timestamp: new Date().toISOString()
133
+ }));
134
+ }
135
+
136
+ static warn(message, data = {}) {
137
+ console.warn(JSON.stringify({
138
+ level: 'WARN',
139
+ message,
140
+ data,
141
+ timestamp: new Date().toISOString()
142
+ }));
143
+ }
144
+
145
+ static debug(message, data = {}) {
146
+ if (process.env.NODE_ENV === 'development') {
147
+ console.debug(JSON.stringify({
148
+ level: 'DEBUG',
149
+ message,
150
+ data,
151
+ timestamp: new Date().toISOString()
152
+ }));
153
+ }
154
+ }
155
+ }
156
+
157
+ export default { ResponseHelper, AppError, Logger };
@@ -0,0 +1,63 @@
1
+ import { validationResult } from 'express-validator';
2
+
3
+ /**
4
+ * Validation Error Middleware
5
+ * Checks for validation errors and returns them if found
6
+ */
7
+ export const validateErrors = (req, res, next) => {
8
+ const errors = validationResult(req);
9
+
10
+ if (!errors.isEmpty()) {
11
+ return res.status(400).json({
12
+ success: false,
13
+ message: 'Validation failed',
14
+ errors: errors.array().map(err => ({
15
+ field: err.param || err.path,
16
+ message: err.msg,
17
+ value: err.value
18
+ }))
19
+ });
20
+ }
21
+
22
+ next();
23
+ };
24
+
25
+ /**
26
+ * Custom Validation Helpers
27
+ */
28
+ export const ValidationHelpers = {
29
+ /**
30
+ * Validate email format
31
+ */
32
+ isValidEmail: (email) => {
33
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
34
+ return emailRegex.test(email);
35
+ },
36
+
37
+ /**
38
+ * Validate phone format
39
+ */
40
+ isValidPhone: (phone) => {
41
+ const phoneRegex = /^[\d\s\-\+\(\)]{10,}$/;
42
+ return phoneRegex.test(phone);
43
+ },
44
+
45
+ /**
46
+ * Validate URL format
47
+ */
48
+ isValidUrl: (url) => {
49
+ try {
50
+ new URL(url);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ },
56
+
57
+ /**
58
+ * Check if string length is within range
59
+ */
60
+ isLengthBetween: (str, min, max) => {
61
+ return str && str.length >= min && str.length <= max;
62
+ }
63
+ };