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.
- package/README.md +2 -0
- package/cli/index.js +2 -0
- package/cli.js +206 -0
- package/core/detector/detectAxios.js +107 -0
- package/core/detector/detectFetch.js +148 -0
- package/core/detector/detectForms.js +55 -0
- package/core/detector/detectSocket.js +341 -0
- package/core/generator/generateControllers.js +17 -0
- package/core/generator/generateModels.js +25 -0
- package/core/generator/generateRoutes.js +17 -0
- package/core/generator/generateServer.js +18 -0
- package/core/generator/generateSocket.js +160 -0
- package/core/index.js +14 -0
- package/core/ir/IRTypes.js +25 -0
- package/core/ir/buildIR.js +83 -0
- package/core/parser/parseJS.js +26 -0
- package/core/parser/parseTS.js +27 -0
- package/core/rules/relationRules.js +38 -0
- package/core/rules/resourceRules.js +32 -0
- package/core/rules/schemaInference.js +26 -0
- package/core/scanner/scanProject.js +58 -0
- package/deploy/cloudflare.js +41 -0
- package/deploy/cloudflareWorker.js +122 -0
- package/deploy/connect.js +198 -0
- package/deploy/flyio.js +51 -0
- package/deploy/index.js +322 -0
- package/deploy/netlify.js +29 -0
- package/deploy/railway.js +215 -0
- package/deploy/render.js +195 -0
- package/deploy/utils.js +383 -0
- package/deploy/vercel.js +29 -0
- package/index.js +18 -0
- package/lib/generator/advancedCrudGenerator.js +475 -0
- package/lib/generator/crudCodeGenerator.js +486 -0
- package/lib/generator/irBasedGenerator.js +360 -0
- package/lib/ir-builder/index.js +16 -0
- package/lib/ir-builder/irBuilder.js +330 -0
- package/lib/ir-builder/rulesEngine.js +353 -0
- package/lib/ir-builder/templateEngine.js +193 -0
- package/lib/ir-builder/templates/index.js +14 -0
- package/lib/ir-builder/templates/model.template.js +47 -0
- package/lib/ir-builder/templates/routes-generic.template.js +66 -0
- package/lib/ir-builder/templates/routes-user.template.js +105 -0
- package/lib/ir-builder/templates/routes.template.js +102 -0
- package/lib/ir-builder/templates/validation.template.js +15 -0
- package/lib/ir-integration.js +349 -0
- package/lib/modes/benchmark.js +162 -0
- package/lib/modes/configBasedGenerator.js +2258 -0
- package/lib/modes/connect.js +1125 -0
- package/lib/modes/doctorAi.js +172 -0
- package/lib/modes/generateApi.js +435 -0
- package/lib/modes/interactiveSetup.js +548 -0
- package/lib/modes/offline.clean.js +14 -0
- package/lib/modes/offline.enhanced.js +787 -0
- package/lib/modes/offline.js +295 -0
- package/lib/modes/offline.v2.js +13 -0
- package/lib/modes/sync.js +629 -0
- package/lib/scanner/apiEndpointExtractor.js +387 -0
- package/lib/scanner/authPatternDetector.js +54 -0
- package/lib/scanner/frontendScanner.js +642 -0
- package/lib/utils/apiClientGenerator.js +242 -0
- package/lib/utils/apiScanner.js +95 -0
- package/lib/utils/codeInjector.js +350 -0
- package/lib/utils/doctor.js +381 -0
- package/lib/utils/envGenerator.js +36 -0
- package/lib/utils/loadTester.js +61 -0
- package/lib/utils/performanceAnalyzer.js +298 -0
- package/lib/utils/resourceDetector.js +281 -0
- package/package.json +20 -0
- package/templates/.env.template +31 -0
- package/templates/advanced.model.template.js +201 -0
- package/templates/advanced.route.template.js +341 -0
- package/templates/auth.middleware.template.js +87 -0
- package/templates/auth.routes.template.js +238 -0
- package/templates/auth.user.model.template.js +78 -0
- package/templates/cache.middleware.js +34 -0
- package/templates/chat.models.template.js +260 -0
- package/templates/chat.routes.template.js +478 -0
- package/templates/compression.middleware.js +19 -0
- package/templates/database.config.js +74 -0
- package/templates/errorHandler.middleware.js +54 -0
- package/templates/express/controller.ejs +26 -0
- package/templates/express/model.ejs +9 -0
- package/templates/express/route.ejs +18 -0
- package/templates/express/server.ejs +16 -0
- package/templates/frontend.env.template +14 -0
- package/templates/model.template.js +86 -0
- package/templates/package.production.json +51 -0
- package/templates/package.template.json +41 -0
- package/templates/pagination.utility.js +110 -0
- package/templates/production.server.template.js +233 -0
- package/templates/rateLimiter.middleware.js +36 -0
- package/templates/requestLogger.middleware.js +19 -0
- package/templates/response.helper.js +179 -0
- package/templates/route.template.js +130 -0
- package/templates/security.middleware.js +78 -0
- package/templates/server.template.js +91 -0
- package/templates/socket.server.template.js +433 -0
- package/templates/utils.helper.js +157 -0
- package/templates/validation.middleware.js +63 -0
- package/templates/validation.schema.js +128 -0
- package/utils/fileWriter.js +15 -0
- package/utils/logger.js +18 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Routes Template
|
|
3
|
+
* REST API endpoints for chat functionality
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const chatRoutesTemplate = `import express from 'express';
|
|
7
|
+
import Message from '../models/Message.js';
|
|
8
|
+
import Conversation from '../models/Conversation.js';
|
|
9
|
+
import { authenticateToken } from '../middleware/auth.js';
|
|
10
|
+
|
|
11
|
+
const router = express.Router();
|
|
12
|
+
|
|
13
|
+
// All routes require authentication
|
|
14
|
+
router.use(authenticateToken);
|
|
15
|
+
|
|
16
|
+
// ============ CONVERSATIONS ============
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* GET /api/chat/conversations
|
|
20
|
+
* Get all conversations for current user
|
|
21
|
+
*/
|
|
22
|
+
router.get('/conversations', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const conversations = await Conversation.findByUser(req.user.id)
|
|
25
|
+
.populate('participants', 'name email avatar')
|
|
26
|
+
.populate({
|
|
27
|
+
path: 'lastMessage',
|
|
28
|
+
populate: { path: 'sender', select: 'name email' }
|
|
29
|
+
})
|
|
30
|
+
.lean();
|
|
31
|
+
|
|
32
|
+
res.json({
|
|
33
|
+
success: true,
|
|
34
|
+
count: conversations.length,
|
|
35
|
+
data: conversations
|
|
36
|
+
});
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('Get conversations error:', error);
|
|
39
|
+
res.status(500).json({
|
|
40
|
+
success: false,
|
|
41
|
+
message: 'Failed to fetch conversations'
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* GET /api/chat/conversations/:id
|
|
48
|
+
* Get single conversation details
|
|
49
|
+
*/
|
|
50
|
+
router.get('/conversations/:id', async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const conversation = await Conversation.findOne({
|
|
53
|
+
_id: req.params.id,
|
|
54
|
+
participants: req.user.id
|
|
55
|
+
})
|
|
56
|
+
.populate('participants', 'name email avatar')
|
|
57
|
+
.populate('admins', 'name email')
|
|
58
|
+
.lean();
|
|
59
|
+
|
|
60
|
+
if (!conversation) {
|
|
61
|
+
return res.status(404).json({
|
|
62
|
+
success: false,
|
|
63
|
+
message: 'Conversation not found'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
res.json({
|
|
68
|
+
success: true,
|
|
69
|
+
data: conversation
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error('Get conversation error:', error);
|
|
73
|
+
res.status(500).json({
|
|
74
|
+
success: false,
|
|
75
|
+
message: 'Failed to fetch conversation'
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* POST /api/chat/conversations
|
|
82
|
+
* Create new conversation
|
|
83
|
+
*/
|
|
84
|
+
router.post('/conversations', async (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
const { participantIds, name, type = 'direct' } = req.body;
|
|
87
|
+
|
|
88
|
+
if (!participantIds || !Array.isArray(participantIds)) {
|
|
89
|
+
return res.status(400).json({
|
|
90
|
+
success: false,
|
|
91
|
+
message: 'participantIds array is required'
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Include current user
|
|
96
|
+
const participants = [req.user.id, ...participantIds];
|
|
97
|
+
|
|
98
|
+
// For direct chats, check if exists
|
|
99
|
+
if (type === 'direct' && participants.length === 2) {
|
|
100
|
+
const existing = await Conversation.findDirectConversation(
|
|
101
|
+
participants[0],
|
|
102
|
+
participants[1]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (existing) {
|
|
106
|
+
return res.json({
|
|
107
|
+
success: true,
|
|
108
|
+
data: existing,
|
|
109
|
+
isExisting: true
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Create new conversation
|
|
115
|
+
const conversation = await Conversation.create({
|
|
116
|
+
name,
|
|
117
|
+
type,
|
|
118
|
+
participants,
|
|
119
|
+
createdBy: req.user.id,
|
|
120
|
+
admins: type === 'group' ? [req.user.id] : []
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await conversation.populate('participants', 'name email avatar');
|
|
124
|
+
|
|
125
|
+
res.status(201).json({
|
|
126
|
+
success: true,
|
|
127
|
+
data: conversation
|
|
128
|
+
});
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Create conversation error:', error);
|
|
131
|
+
res.status(500).json({
|
|
132
|
+
success: false,
|
|
133
|
+
message: 'Failed to create conversation'
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* PUT /api/chat/conversations/:id
|
|
140
|
+
* Update conversation (name, settings, etc.)
|
|
141
|
+
*/
|
|
142
|
+
router.put('/conversations/:id', async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const conversation = await Conversation.findOne({
|
|
145
|
+
_id: req.params.id,
|
|
146
|
+
participants: req.user.id
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!conversation) {
|
|
150
|
+
return res.status(404).json({
|
|
151
|
+
success: false,
|
|
152
|
+
message: 'Conversation not found'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Only admins can update group conversations
|
|
157
|
+
if (conversation.type === 'group' && !conversation.isAdmin(req.user.id)) {
|
|
158
|
+
return res.status(403).json({
|
|
159
|
+
success: false,
|
|
160
|
+
message: 'Only admins can update group conversations'
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { name, description, avatar, settings } = req.body;
|
|
165
|
+
|
|
166
|
+
if (name) conversation.name = name;
|
|
167
|
+
if (description) conversation.description = description;
|
|
168
|
+
if (avatar) conversation.avatar = avatar;
|
|
169
|
+
if (settings) conversation.settings = { ...conversation.settings, ...settings };
|
|
170
|
+
|
|
171
|
+
await conversation.save();
|
|
172
|
+
|
|
173
|
+
res.json({
|
|
174
|
+
success: true,
|
|
175
|
+
data: conversation
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error('Update conversation error:', error);
|
|
179
|
+
res.status(500).json({
|
|
180
|
+
success: false,
|
|
181
|
+
message: 'Failed to update conversation'
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* DELETE /api/chat/conversations/:id
|
|
188
|
+
* Delete/leave conversation
|
|
189
|
+
*/
|
|
190
|
+
router.delete('/conversations/:id', async (req, res) => {
|
|
191
|
+
try {
|
|
192
|
+
const conversation = await Conversation.findOne({
|
|
193
|
+
_id: req.params.id,
|
|
194
|
+
participants: req.user.id
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!conversation) {
|
|
198
|
+
return res.status(404).json({
|
|
199
|
+
success: false,
|
|
200
|
+
message: 'Conversation not found'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// For direct chats, just remove the user
|
|
205
|
+
if (conversation.type === 'direct') {
|
|
206
|
+
await conversation.removeParticipant(req.user.id);
|
|
207
|
+
} else {
|
|
208
|
+
// For groups, if creator, delete. Otherwise, just leave.
|
|
209
|
+
if (conversation.createdBy.toString() === req.user.id) {
|
|
210
|
+
conversation.isActive = false;
|
|
211
|
+
await conversation.save();
|
|
212
|
+
} else {
|
|
213
|
+
await conversation.removeParticipant(req.user.id);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
res.json({
|
|
218
|
+
success: true,
|
|
219
|
+
message: 'Left conversation successfully'
|
|
220
|
+
});
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.error('Delete conversation error:', error);
|
|
223
|
+
res.status(500).json({
|
|
224
|
+
success: false,
|
|
225
|
+
message: 'Failed to delete conversation'
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// ============ MESSAGES ============
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* GET /api/chat/conversations/:id/messages
|
|
234
|
+
* Get messages for a conversation
|
|
235
|
+
*/
|
|
236
|
+
router.get('/conversations/:id/messages', async (req, res) => {
|
|
237
|
+
try {
|
|
238
|
+
const { limit = 50, before, after } = req.query;
|
|
239
|
+
|
|
240
|
+
// Verify access
|
|
241
|
+
const conversation = await Conversation.findOne({
|
|
242
|
+
_id: req.params.id,
|
|
243
|
+
participants: req.user.id
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (!conversation) {
|
|
247
|
+
return res.status(404).json({
|
|
248
|
+
success: false,
|
|
249
|
+
message: 'Conversation not found'
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const query = {
|
|
254
|
+
conversation: req.params.id,
|
|
255
|
+
isDeleted: false
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if (before) {
|
|
259
|
+
query.timestamp = { $lt: new Date(before) };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (after) {
|
|
263
|
+
query.timestamp = { $gt: new Date(after) };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const messages = await Message.find(query)
|
|
267
|
+
.sort({ timestamp: -1 })
|
|
268
|
+
.limit(parseInt(limit))
|
|
269
|
+
.populate('sender', 'name email avatar')
|
|
270
|
+
.populate('replyTo', 'content sender')
|
|
271
|
+
.lean();
|
|
272
|
+
|
|
273
|
+
res.json({
|
|
274
|
+
success: true,
|
|
275
|
+
count: messages.length,
|
|
276
|
+
data: messages.reverse(),
|
|
277
|
+
hasMore: messages.length === parseInt(limit)
|
|
278
|
+
});
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Get messages error:', error);
|
|
281
|
+
res.status(500).json({
|
|
282
|
+
success: false,
|
|
283
|
+
message: 'Failed to fetch messages'
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* POST /api/chat/conversations/:id/messages
|
|
290
|
+
* Send message (REST fallback - prefer Socket.io)
|
|
291
|
+
*/
|
|
292
|
+
router.post('/conversations/:id/messages', async (req, res) => {
|
|
293
|
+
try {
|
|
294
|
+
const { content, type = 'text', metadata } = req.body;
|
|
295
|
+
|
|
296
|
+
if (!content) {
|
|
297
|
+
return res.status(400).json({
|
|
298
|
+
success: false,
|
|
299
|
+
message: 'Content is required'
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Verify access
|
|
304
|
+
const conversation = await Conversation.findOne({
|
|
305
|
+
_id: req.params.id,
|
|
306
|
+
participants: req.user.id
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
if (!conversation) {
|
|
310
|
+
return res.status(404).json({
|
|
311
|
+
success: false,
|
|
312
|
+
message: 'Conversation not found'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const message = await Message.create({
|
|
317
|
+
conversation: req.params.id,
|
|
318
|
+
sender: req.user.id,
|
|
319
|
+
content,
|
|
320
|
+
type,
|
|
321
|
+
metadata
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await message.populate('sender', 'name email avatar');
|
|
325
|
+
|
|
326
|
+
// Update conversation
|
|
327
|
+
conversation.lastMessage = message._id;
|
|
328
|
+
conversation.lastMessageAt = new Date();
|
|
329
|
+
await conversation.save();
|
|
330
|
+
|
|
331
|
+
res.status(201).json({
|
|
332
|
+
success: true,
|
|
333
|
+
data: message
|
|
334
|
+
});
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('Send message error:', error);
|
|
337
|
+
res.status(500).json({
|
|
338
|
+
success: false,
|
|
339
|
+
message: 'Failed to send message'
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* PUT /api/chat/messages/:id
|
|
346
|
+
* Edit message
|
|
347
|
+
*/
|
|
348
|
+
router.put('/messages/:id', async (req, res) => {
|
|
349
|
+
try {
|
|
350
|
+
const { content } = req.body;
|
|
351
|
+
|
|
352
|
+
const message = await Message.findOne({
|
|
353
|
+
_id: req.params.id,
|
|
354
|
+
sender: req.user.id
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
if (!message) {
|
|
358
|
+
return res.status(404).json({
|
|
359
|
+
success: false,
|
|
360
|
+
message: 'Message not found or not authorized'
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
message.content = content;
|
|
365
|
+
message.isEdited = true;
|
|
366
|
+
message.editedAt = new Date();
|
|
367
|
+
await message.save();
|
|
368
|
+
|
|
369
|
+
res.json({
|
|
370
|
+
success: true,
|
|
371
|
+
data: message
|
|
372
|
+
});
|
|
373
|
+
} catch (error) {
|
|
374
|
+
console.error('Edit message error:', error);
|
|
375
|
+
res.status(500).json({
|
|
376
|
+
success: false,
|
|
377
|
+
message: 'Failed to edit message'
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* DELETE /api/chat/messages/:id
|
|
384
|
+
* Delete message
|
|
385
|
+
*/
|
|
386
|
+
router.delete('/messages/:id', async (req, res) => {
|
|
387
|
+
try {
|
|
388
|
+
const message = await Message.findOne({
|
|
389
|
+
_id: req.params.id,
|
|
390
|
+
sender: req.user.id
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
if (!message) {
|
|
394
|
+
return res.status(404).json({
|
|
395
|
+
success: false,
|
|
396
|
+
message: 'Message not found or not authorized'
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
message.isDeleted = true;
|
|
401
|
+
message.deletedAt = new Date();
|
|
402
|
+
message.content = '[Message deleted]';
|
|
403
|
+
await message.save();
|
|
404
|
+
|
|
405
|
+
res.json({
|
|
406
|
+
success: true,
|
|
407
|
+
message: 'Message deleted successfully'
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error('Delete message error:', error);
|
|
411
|
+
res.status(500).json({
|
|
412
|
+
success: false,
|
|
413
|
+
message: 'Failed to delete message'
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* POST /api/chat/messages/:id/reactions
|
|
420
|
+
* Add reaction to message
|
|
421
|
+
*/
|
|
422
|
+
router.post('/messages/:id/reactions', async (req, res) => {
|
|
423
|
+
try {
|
|
424
|
+
const { emoji } = req.body;
|
|
425
|
+
|
|
426
|
+
if (!emoji) {
|
|
427
|
+
return res.status(400).json({
|
|
428
|
+
success: false,
|
|
429
|
+
message: 'Emoji is required'
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const message = await Message.findById(req.params.id);
|
|
434
|
+
|
|
435
|
+
if (!message) {
|
|
436
|
+
return res.status(404).json({
|
|
437
|
+
success: false,
|
|
438
|
+
message: 'Message not found'
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Check if user already reacted with this emoji
|
|
443
|
+
const existingReaction = message.reactions.find(
|
|
444
|
+
r => r.user.toString() === req.user.id && r.emoji === emoji
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
if (existingReaction) {
|
|
448
|
+
return res.status(400).json({
|
|
449
|
+
success: false,
|
|
450
|
+
message: 'Already reacted with this emoji'
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
message.reactions.push({
|
|
455
|
+
user: req.user.id,
|
|
456
|
+
emoji,
|
|
457
|
+
createdAt: new Date()
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
await message.save();
|
|
461
|
+
|
|
462
|
+
res.json({
|
|
463
|
+
success: true,
|
|
464
|
+
data: message
|
|
465
|
+
});
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error('Add reaction error:', error);
|
|
468
|
+
res.status(500).json({
|
|
469
|
+
success: false,
|
|
470
|
+
message: 'Failed to add reaction'
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
export default router;
|
|
476
|
+
`;
|
|
477
|
+
|
|
478
|
+
export default chatRoutesTemplate;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compression Middleware
|
|
3
|
+
* Compresses response bodies for all requests that match the filter function
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import compression from 'compression';
|
|
7
|
+
|
|
8
|
+
export const compressionMiddleware = compression({
|
|
9
|
+
filter: (req, res) => {
|
|
10
|
+
if (req.headers['x-no-compression']) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return compression.filter(req, res);
|
|
14
|
+
},
|
|
15
|
+
level: process.env.NODE_ENV === 'production' ? 6 : 3,
|
|
16
|
+
threshold: 1024 // Only compress responses larger than 1KB
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default compressionMiddleware;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Configuration
|
|
3
|
+
* Centralized database connection and configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import mongoose from 'mongoose';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/offbyt_app';
|
|
10
|
+
|
|
11
|
+
export const dbConfig = {
|
|
12
|
+
uri: MONGODB_URI,
|
|
13
|
+
options: {
|
|
14
|
+
useNewUrlParser: true,
|
|
15
|
+
useUnifiedTopology: true,
|
|
16
|
+
maxPoolSize: 10,
|
|
17
|
+
minPoolSize: 5,
|
|
18
|
+
serverSelectionTimeoutMS: 5000,
|
|
19
|
+
socketTimeoutMS: 45000,
|
|
20
|
+
retryWrites: true,
|
|
21
|
+
retryReads: true
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Connection flags
|
|
26
|
+
let isConnected = false;
|
|
27
|
+
|
|
28
|
+
export async function connectDatabase() {
|
|
29
|
+
if (isConnected) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await mongoose.connect(dbConfig.uri, dbConfig.options);
|
|
35
|
+
isConnected = true;
|
|
36
|
+
|
|
37
|
+
mongoose.connection.on('error', (error) => {
|
|
38
|
+
console.error(chalk.red('⌠MongoDB Error:'), error.message);
|
|
39
|
+
isConnected = false;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
mongoose.connection.on('disconnected', () => {
|
|
43
|
+
console.warn(chalk.yellow('âš ï¸ MongoDB Disconnected'));
|
|
44
|
+
isConnected = false;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(chalk.green('✅ MongoDB Connected Successfully'));
|
|
48
|
+
return mongoose.connection;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(chalk.red('⌠MongoDB Connection Failed:'), error.message);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function disconnectDatabase() {
|
|
56
|
+
if (!isConnected) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
await mongoose.disconnect();
|
|
62
|
+
isConnected = false;
|
|
63
|
+
console.log(chalk.yellow('🔌 MongoDB Disconnected'));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(chalk.red('⌠MongoDB Disconnection Error:'), error.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function isDatabaseConnected() {
|
|
70
|
+
return isConnected && mongoose.connection.readyState === 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default { connectDatabase, disconnectDatabase, isDatabaseConnected, dbConfig };
|
|
74
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Error Handler Middleware
|
|
2
|
+
const errorHandler = (err, req, res, next) => {
|
|
3
|
+
console.error('❌ Error:', err.message);
|
|
4
|
+
|
|
5
|
+
// Mongoose Validation Error
|
|
6
|
+
if (err.name === 'ValidationError') {
|
|
7
|
+
return res.status(400).json({
|
|
8
|
+
success: false,
|
|
9
|
+
error: 'Validation Error',
|
|
10
|
+
details: Object.values(err.errors).map(e => e.message)
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Mongoose Cast Error
|
|
15
|
+
if (err.name === 'CastError') {
|
|
16
|
+
return res.status(400).json({
|
|
17
|
+
success: false,
|
|
18
|
+
error: 'Invalid ID format'
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Duplicate Key Error
|
|
23
|
+
if (err.code === 11000) {
|
|
24
|
+
const field = Object.keys(err.keyPattern)[0];
|
|
25
|
+
return res.status(409).json({
|
|
26
|
+
success: false,
|
|
27
|
+
error: `${field} already exists`
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// JWT Errors
|
|
32
|
+
if (err.name === 'JsonWebTokenError') {
|
|
33
|
+
return res.status(401).json({
|
|
34
|
+
success: false,
|
|
35
|
+
error: 'Invalid token'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (err.name === 'TokenExpiredError') {
|
|
40
|
+
return res.status(401).json({
|
|
41
|
+
success: false,
|
|
42
|
+
error: 'Token expired'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Default Error
|
|
47
|
+
res.status(err.statusCode || 500).json({
|
|
48
|
+
success: false,
|
|
49
|
+
error: err.message || 'Internal Server Error',
|
|
50
|
+
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default errorHandler;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import <%= modelName %> from '../models/<%= modelName %>.js';
|
|
2
|
+
|
|
3
|
+
export const list<%= modelNamePlural %> = async (req, res) => {
|
|
4
|
+
const data = await <%= modelName %>.find();
|
|
5
|
+
res.json({ success: true, data });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const get<%= modelName %> = async (req, res) => {
|
|
9
|
+
const data = await <%= modelName %>.findById(req.params.id);
|
|
10
|
+
res.json({ success: true, data });
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const create<%= modelName %> = async (req, res) => {
|
|
14
|
+
const data = await <%= modelName %>.create(req.body);
|
|
15
|
+
res.status(201).json({ success: true, data });
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const update<%= modelName %> = async (req, res) => {
|
|
19
|
+
const data = await <%= modelName %>.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
20
|
+
res.json({ success: true, data });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const delete<%= modelName %> = async (req, res) => {
|
|
24
|
+
await <%= modelName %>.findByIdAndDelete(req.params.id);
|
|
25
|
+
res.json({ success: true });
|
|
26
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const <%= modelVar %>Schema = new mongoose.Schema({
|
|
4
|
+
<% fields.forEach(function(field) { %>
|
|
5
|
+
<%= field.name %>: { type: <%= field.type %><%= field.required ? ', required: true' : '' %> },
|
|
6
|
+
<% }); %>
|
|
7
|
+
}, { timestamps: true });
|
|
8
|
+
|
|
9
|
+
export default mongoose.model('<%= modelName %>', <%= modelVar %>Schema);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import {
|
|
3
|
+
list<%= modelNamePlural %>,
|
|
4
|
+
get<%= modelName %>,
|
|
5
|
+
create<%= modelName %>,
|
|
6
|
+
update<%= modelName %>,
|
|
7
|
+
delete<%= modelName %>
|
|
8
|
+
} from '../controllers/<%= resourceName %>Controller.js';
|
|
9
|
+
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
|
|
12
|
+
router.get('/', list<%= modelNamePlural %>);
|
|
13
|
+
router.get('/:id', get<%= modelName %>);
|
|
14
|
+
router.post('/', create<%= modelName %>);
|
|
15
|
+
router.put('/:id', update<%= modelName %>);
|
|
16
|
+
router.delete('/:id', delete<%= modelName %>);
|
|
17
|
+
|
|
18
|
+
export default router;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
<% resources.forEach(function(r) { %>import <%= r.name %>Routes from './src/routes/<%= r.name %>.routes.js';
|
|
4
|
+
<% }); %>
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
app.use(cors());
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
|
|
10
|
+
<% resources.forEach(function(r) { %>app.use('/api/<%= r.name %>s', <%= r.name %>Routes);
|
|
11
|
+
<% }); %>
|
|
12
|
+
|
|
13
|
+
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
|
14
|
+
|
|
15
|
+
const PORT = process.env.PORT || 3000;
|
|
16
|
+
app.listen(PORT, () => console.log(`Server running on ${PORT}`));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ========== FRONTEND ENVIRONMENT VARIABLES ==========
|
|
2
|
+
# Copy this file as .env in your frontend root directory
|
|
3
|
+
|
|
4
|
+
# API Configuration
|
|
5
|
+
# NOTE: Should NOT include /api path - backend will handle route prefixing
|
|
6
|
+
VITE_API_URL=http://localhost:5000
|
|
7
|
+
|
|
8
|
+
# App Configuration
|
|
9
|
+
VITE_APP_NAME=YourAppName
|
|
10
|
+
VITE_APP_VERSION=1.0.0
|
|
11
|
+
|
|
12
|
+
# Optional: Features
|
|
13
|
+
VITE_ENABLE_ANALYTICS=false
|
|
14
|
+
VITE_ENABLE_LOGGING=true
|