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,238 @@
1
+ import express from 'express';
2
+ import jwt from 'jsonwebtoken';
3
+ import User from '../models/User.js';
4
+ import { authenticateToken } from '../middleware/auth.js';
5
+
6
+ const router = express.Router();
7
+
8
+ // Ensure JWT_SECRET is consistent across all auth checks
9
+ const JWT_SECRET = process.env.JWT_SECRET || 'your_super_secret_jwt_key_change_this_in_production';
10
+
11
+ /**
12
+ * Generate JWT Token
13
+ */
14
+ const generateToken = (user) => {
15
+ return jwt.sign(
16
+ {
17
+ id: user._id,
18
+ email: user.email,
19
+ name: user.name,
20
+ role: user.role
21
+ },
22
+ JWT_SECRET,
23
+ { expiresIn: process.env.JWT_EXPIRE || process.env.JWT_EXPIRES_IN || '7d' }
24
+ );
25
+ };
26
+
27
+ /**
28
+ * POST /api/auth/signup
29
+ * Register new user
30
+ */
31
+ router.post('/signup', async (req, res, next) => {
32
+ try {
33
+ const { name, email, password } = req.body;
34
+
35
+ // Validation
36
+ if (!name || !email || !password) {
37
+ return res.status(400).json({
38
+ success: false,
39
+ message: 'Please provide name, email, and password',
40
+ code: 'MISSING_FIELDS'
41
+ });
42
+ }
43
+
44
+ // Check if user already exists
45
+ const existingUser = await User.findOne({ email });
46
+ if (existingUser) {
47
+ return res.status(409).json({
48
+ success: false,
49
+ message: 'User with this email already exists',
50
+ code: 'EMAIL_EXISTS'
51
+ });
52
+ }
53
+
54
+ // Validate email format
55
+ const emailRegex = /^\S+@\S+\.\S+$/;
56
+ if (!emailRegex.test(email)) {
57
+ return res.status(400).json({
58
+ success: false,
59
+ message: 'Please provide a valid email',
60
+ code: 'INVALID_EMAIL'
61
+ });
62
+ }
63
+
64
+ // Validate password length
65
+ if (password.length < 6) {
66
+ return res.status(400).json({
67
+ success: false,
68
+ message: 'Password must be at least 6 characters',
69
+ code: 'WEAK_PASSWORD'
70
+ });
71
+ }
72
+
73
+ // Create new user (password will be hashed by pre-save hook)
74
+ const user = new User({
75
+ name: name.trim(),
76
+ email: email.toLowerCase().trim(),
77
+ password
78
+ });
79
+
80
+ await user.save();
81
+
82
+ // Generate JWT token
83
+ const token = generateToken(user);
84
+
85
+ res.status(201).json({
86
+ success: true,
87
+ message: 'Account created successfully',
88
+ data: {
89
+ token,
90
+ user: user.toJSON()
91
+ }
92
+ });
93
+ } catch (error) {
94
+ if (error.name === 'ValidationError') {
95
+ return res.status(400).json({
96
+ success: false,
97
+ message: 'Validation Error',
98
+ code: 'VALIDATION_ERROR',
99
+ errors: Object.values(error.errors).map(e => e.message)
100
+ });
101
+ }
102
+ next(error);
103
+ }
104
+ });
105
+
106
+ /**
107
+ * POST /api/auth/login
108
+ * User login with email and password
109
+ */
110
+ router.post('/login', async (req, res, next) => {
111
+ try {
112
+ const { email, password } = req.body;
113
+
114
+ // Validation
115
+ if (!email || !password) {
116
+ return res.status(400).json({
117
+ success: false,
118
+ message: 'Please provide email and password',
119
+ code: 'MISSING_FIELDS'
120
+ });
121
+ }
122
+
123
+ // Find user and include password (excluded by default)
124
+ const user = await User.findOne({ email }).select('+password');
125
+
126
+ if (!user || !user.isActive) {
127
+ return res.status(401).json({
128
+ success: false,
129
+ message: 'Invalid email or password',
130
+ code: 'INVALID_CREDENTIALS'
131
+ });
132
+ }
133
+
134
+ // Verify password
135
+ const isPasswordValid = await user.comparePassword(password);
136
+
137
+ if (!isPasswordValid) {
138
+ return res.status(401).json({
139
+ success: false,
140
+ message: 'Invalid email or password',
141
+ code: 'INVALID_CREDENTIALS'
142
+ });
143
+ }
144
+
145
+ // Update last login
146
+ user.lastLogin = new Date();
147
+ await user.save();
148
+
149
+ // Generate JWT token
150
+ const token = generateToken(user);
151
+
152
+ res.status(200).json({
153
+ success: true,
154
+ message: 'Login successful',
155
+ data: {
156
+ token,
157
+ user: user.toJSON()
158
+ }
159
+ });
160
+ } catch (error) {
161
+ next(error);
162
+ }
163
+ });
164
+
165
+ /**
166
+ * GET /api/auth/profile
167
+ * Get current user profile (Protected route)
168
+ */
169
+ router.get('/profile', authenticateToken, async (req, res, next) => {
170
+ try {
171
+ const user = await User.findById(req.user.id);
172
+
173
+ if (!user || !user.isActive) {
174
+ return res.status(404).json({
175
+ success: false,
176
+ message: 'User not found',
177
+ code: 'USER_NOT_FOUND'
178
+ });
179
+ }
180
+
181
+ res.status(200).json({
182
+ success: true,
183
+ data: {
184
+ user: user.toJSON()
185
+ }
186
+ });
187
+ } catch (error) {
188
+ next(error);
189
+ }
190
+ });
191
+
192
+ /**
193
+ * PUT /api/auth/profile
194
+ * Update user profile (Protected route)
195
+ */
196
+ router.put('/profile', authenticateToken, async (req, res, next) => {
197
+ try {
198
+ const { name } = req.body;
199
+ const user = await User.findById(req.user.id);
200
+
201
+ if (!user) {
202
+ return res.status(404).json({
203
+ success: false,
204
+ message: 'User not found',
205
+ code: 'USER_NOT_FOUND'
206
+ });
207
+ }
208
+
209
+ if (name) {
210
+ user.name = name.trim();
211
+ }
212
+
213
+ await user.save();
214
+
215
+ res.status(200).json({
216
+ success: true,
217
+ message: 'Profile updated successfully',
218
+ data: {
219
+ user: user.toJSON()
220
+ }
221
+ });
222
+ } catch (error) {
223
+ next(error);
224
+ }
225
+ });
226
+
227
+ /**
228
+ * POST /api/auth/logout
229
+ * Logout user (clears token on frontend)
230
+ */
231
+ router.post('/logout', authenticateToken, (req, res) => {
232
+ res.status(200).json({
233
+ success: true,
234
+ message: 'Logged out successfully'
235
+ });
236
+ });
237
+
238
+ export default router;
@@ -0,0 +1,78 @@
1
+ import mongoose from 'mongoose';
2
+ import bcrypt from 'bcryptjs';
3
+
4
+ const userSchema = new mongoose.Schema({
5
+ name: {
6
+ type: String,
7
+ required: [true, 'Please provide a name'],
8
+ trim: true,
9
+ minlength: [2, 'Name must be at least 2 characters']
10
+ },
11
+ email: {
12
+ type: String,
13
+ required: [true, 'Please provide an email'],
14
+ unique: true,
15
+ lowercase: true,
16
+ match: [/^\S+@\S+\.\S+$/, 'Please provide a valid email'],
17
+ index: true
18
+ },
19
+ password: {
20
+ type: String,
21
+ required: [true, 'Please provide a password'],
22
+ minlength: [6, 'Password must be at least 6 characters'],
23
+ select: false // Don't return password by default
24
+ },
25
+ role: {
26
+ type: String,
27
+ enum: ['student', 'organizer', 'admin'],
28
+ default: 'student'
29
+ },
30
+ isActive: {
31
+ type: Boolean,
32
+ default: true
33
+ },
34
+ lastLogin: Date,
35
+ createdAt: {
36
+ type: Date,
37
+ default: Date.now
38
+ }
39
+ }, {
40
+ timestamps: true,
41
+ collection: 'users'
42
+ });
43
+
44
+ // Hash password before saving
45
+ userSchema.pre('save', async function(next) {
46
+ if (!this.isModified('password')) {
47
+ return next();
48
+ }
49
+
50
+ try {
51
+ const salt = await bcrypt.genSalt(parseInt(process.env.BCRYPT_ROUNDS || 10));
52
+ this.password = await bcrypt.hash(this.password, salt);
53
+ next();
54
+ } catch (error) {
55
+ next(error);
56
+ }
57
+ });
58
+
59
+ // Method to compare password
60
+ userSchema.methods.comparePassword = async function(candidatePassword) {
61
+ try {
62
+ return await bcrypt.compare(candidatePassword, this.password);
63
+ } catch (error) {
64
+ throw new Error('Error comparing passwords');
65
+ }
66
+ };
67
+
68
+ // Return user without password
69
+ userSchema.methods.toJSON = function() {
70
+ const obj = this.toObject();
71
+ delete obj.password;
72
+ delete obj.__v;
73
+ return obj;
74
+ };
75
+
76
+ const User = mongoose.model('User', userSchema);
77
+
78
+ export default User;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Caching Middleware
3
+ * Implements HTTP caching for GET requests
4
+ */
5
+
6
+ export const cacheMiddleware = (duration = 60) => {
7
+ return (req, res, next) => {
8
+ // Only cache GET requests
9
+ if (req.method !== 'GET') {
10
+ return next();
11
+ }
12
+
13
+ // Set cache headers
14
+ res.set('Cache-Control', `public, max-age=${duration}`);
15
+
16
+ // Skip if no-cache header is set
17
+ if (req.headers['cache-control'] === 'no-cache') {
18
+ res.set('Cache-Control', 'no-cache');
19
+ return next();
20
+ }
21
+
22
+ next();
23
+ };
24
+ };
25
+
26
+ // No-cache for sensitive endpoints
27
+ export const noCacheMiddleware = (req, res, next) => {
28
+ res.set('Cache-Control', 'private, no-cache, no-store, must-revalidate');
29
+ res.set('Pragma', 'no-cache');
30
+ res.set('Expires', '0');
31
+ next();
32
+ };
33
+
34
+ export default cacheMiddleware;
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Chat Models Templates
3
+ *
4
+ * Provides Mongoose models for:
5
+ * - Message
6
+ * - Conversation
7
+ * - MessageReaction (optional)
8
+ */
9
+
10
+ export const messageModelTemplate = `import mongoose from 'mongoose';
11
+
12
+ const messageSchema = new mongoose.Schema({
13
+ conversation: {
14
+ type: mongoose.Schema.Types.ObjectId,
15
+ ref: 'Conversation',
16
+ required: true,
17
+ index: true
18
+ },
19
+ sender: {
20
+ type: mongoose.Schema.Types.ObjectId,
21
+ ref: 'User',
22
+ required: true,
23
+ index: true
24
+ },
25
+ content: {
26
+ type: String,
27
+ required: true,
28
+ maxlength: 10000
29
+ },
30
+ type: {
31
+ type: String,
32
+ enum: ['text', 'image', 'video', 'audio', 'file', 'system'],
33
+ default: 'text'
34
+ },
35
+ metadata: {
36
+ type: mongoose.Schema.Types.Mixed,
37
+ default: {}
38
+ },
39
+ // For file messages
40
+ fileUrl: String,
41
+ fileName: String,
42
+ fileSize: Number,
43
+ fileType: String,
44
+
45
+ // Message status
46
+ status: {
47
+ type: String,
48
+ enum: ['sent', 'delivered', 'read'],
49
+ default: 'sent'
50
+ },
51
+
52
+ // Read receipts
53
+ readBy: [{
54
+ type: mongoose.Schema.Types.ObjectId,
55
+ ref: 'User'
56
+ }],
57
+ readAt: Date,
58
+
59
+ // Edited
60
+ isEdited: {
61
+ type: Boolean,
62
+ default: false
63
+ },
64
+ editedAt: Date,
65
+
66
+ // Deleted
67
+ isDeleted: {
68
+ type: Boolean,
69
+ default: false
70
+ },
71
+ deletedAt: Date,
72
+
73
+ // Reply/Thread
74
+ replyTo: {
75
+ type: mongoose.Schema.Types.ObjectId,
76
+ ref: 'Message'
77
+ },
78
+
79
+ // Reactions
80
+ reactions: [{
81
+ user: {
82
+ type: mongoose.Schema.Types.ObjectId,
83
+ ref: 'User'
84
+ },
85
+ emoji: String,
86
+ createdAt: {
87
+ type: Date,
88
+ default: Date.now
89
+ }
90
+ }],
91
+
92
+ timestamp: {
93
+ type: Date,
94
+ default: Date.now,
95
+ index: true
96
+ }
97
+ }, {
98
+ timestamps: true
99
+ });
100
+
101
+ // Indexes for performance
102
+ messageSchema.index({ conversation: 1, timestamp: -1 });
103
+ messageSchema.index({ sender: 1, timestamp: -1 });
104
+ messageSchema.index({ conversation: 1, timestamp: 1 });
105
+
106
+ // Virtuals
107
+ messageSchema.virtual('readCount').get(function() {
108
+ return this.readBy ? this.readBy.length : 0;
109
+ });
110
+
111
+ messageSchema.set('toJSON', { virtuals: true });
112
+ messageSchema.set('toObject', { virtuals: true });
113
+
114
+ const Message = mongoose.model('Message', messageSchema);
115
+
116
+ export default Message;
117
+ `;
118
+
119
+ export const conversationModelTemplate = `import mongoose from 'mongoose';
120
+
121
+ const conversationSchema = new mongoose.Schema({
122
+ name: {
123
+ type: String,
124
+ trim: true
125
+ },
126
+ type: {
127
+ type: String,
128
+ enum: ['direct', 'group', 'channel'],
129
+ default: 'direct',
130
+ required: true
131
+ },
132
+ participants: [{
133
+ type: mongoose.Schema.Types.ObjectId,
134
+ ref: 'User',
135
+ required: true
136
+ }],
137
+ admins: [{
138
+ type: mongoose.Schema.Types.ObjectId,
139
+ ref: 'User'
140
+ }],
141
+ createdBy: {
142
+ type: mongoose.Schema.Types.ObjectId,
143
+ ref: 'User',
144
+ required: true
145
+ },
146
+
147
+ // Last message info
148
+ lastMessage: {
149
+ type: mongoose.Schema.Types.ObjectId,
150
+ ref: 'Message'
151
+ },
152
+ lastMessageAt: {
153
+ type: Date,
154
+ default: Date.now,
155
+ index: true
156
+ },
157
+
158
+ // Group/Channel settings
159
+ description: String,
160
+ avatar: String,
161
+
162
+ // Muted users
163
+ mutedBy: [{
164
+ user: {
165
+ type: mongoose.Schema.Types.ObjectId,
166
+ ref: 'User'
167
+ },
168
+ mutedUntil: Date
169
+ }],
170
+
171
+ // Archived
172
+ archivedBy: [{
173
+ type: mongoose.Schema.Types.ObjectId,
174
+ ref: 'User'
175
+ }],
176
+
177
+ // Pinned messages
178
+ pinnedMessages: [{
179
+ type: mongoose.Schema.Types.ObjectId,
180
+ ref: 'Message'
181
+ }],
182
+
183
+ // Group settings
184
+ settings: {
185
+ allowMemberMessages: {
186
+ type: Boolean,
187
+ default: true
188
+ },
189
+ allowFileSharing: {
190
+ type: Boolean,
191
+ default: true
192
+ },
193
+ maxMembers: {
194
+ type: Number,
195
+ default: 100
196
+ }
197
+ },
198
+
199
+ isActive: {
200
+ type: Boolean,
201
+ default: true
202
+ }
203
+ }, {
204
+ timestamps: true
205
+ });
206
+
207
+ // Indexes
208
+ conversationSchema.index({ participants: 1, lastMessageAt: -1 });
209
+ conversationSchema.index({ type: 1, lastMessageAt: -1 });
210
+ conversationSchema.index({ createdBy: 1 });
211
+
212
+ // Methods
213
+ conversationSchema.methods.isParticipant = function(userId) {
214
+ return this.participants.some(p => p.toString() === userId.toString());
215
+ };
216
+
217
+ conversationSchema.methods.isAdmin = function(userId) {
218
+ return this.admins.some(a => a.toString() === userId.toString());
219
+ };
220
+
221
+ conversationSchema.methods.addParticipant = async function(userId) {
222
+ if (!this.isParticipant(userId)) {
223
+ this.participants.push(userId);
224
+ await this.save();
225
+ }
226
+ return this;
227
+ };
228
+
229
+ conversationSchema.methods.removeParticipant = async function(userId) {
230
+ this.participants = this.participants.filter(
231
+ p => p.toString() !== userId.toString()
232
+ );
233
+ await this.save();
234
+ return this;
235
+ };
236
+
237
+ // Statics
238
+ conversationSchema.statics.findByUser = function(userId) {
239
+ return this.find({ participants: userId, isActive: true })
240
+ .populate('participants', 'name email avatar')
241
+ .populate('lastMessage')
242
+ .sort({ lastMessageAt: -1 });
243
+ };
244
+
245
+ conversationSchema.statics.findDirectConversation = function(user1Id, user2Id) {
246
+ return this.findOne({
247
+ type: 'direct',
248
+ participants: { $all: [user1Id, user2Id], $size: 2 }
249
+ });
250
+ };
251
+
252
+ const Conversation = mongoose.model('Conversation', conversationSchema);
253
+
254
+ export default Conversation;
255
+ `;
256
+
257
+ export const chatModelsExport = `export { messageModelTemplate, conversationModelTemplate };
258
+ `;
259
+
260
+ export default { messageModelTemplate, conversationModelTemplate };