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,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Endpoint Extractor - Enhanced Version
|
|
3
|
+
* Properly handles complex routes, nested actions, and query parameters
|
|
4
|
+
* Groups endpoints by actual resources (not nested actions)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function extractAllApiEndpoints(content, detectedApiCalls = []) {
|
|
8
|
+
const resources = {};
|
|
9
|
+
|
|
10
|
+
for (const call of detectedApiCalls) {
|
|
11
|
+
let endpoint, method, hasAuth = false, queryParams = [];
|
|
12
|
+
|
|
13
|
+
if (typeof call === 'object' && call.route) {
|
|
14
|
+
endpoint = call.route.startsWith('/') ? call.route : '/' + call.route;
|
|
15
|
+
method = call.method || 'GET';
|
|
16
|
+
hasAuth = true; // Assume protected by default
|
|
17
|
+
queryParams = call.queryParams || [];
|
|
18
|
+
} else if (typeof call === 'string') {
|
|
19
|
+
endpoint = extractEndpoint(call);
|
|
20
|
+
method = extractMethod(call);
|
|
21
|
+
hasAuth = requiresAuth(call);
|
|
22
|
+
} else {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!endpoint) continue;
|
|
27
|
+
if (isSystemEndpoint(endpoint)) continue;
|
|
28
|
+
|
|
29
|
+
// Smart resource extraction (ignores nested actions)
|
|
30
|
+
const resource = extractResourceSmart(endpoint);
|
|
31
|
+
const action = extractActionFromEndpoint(endpoint);
|
|
32
|
+
const hasParam = hasParameter(endpoint);
|
|
33
|
+
|
|
34
|
+
if (!resources[resource]) {
|
|
35
|
+
resources[resource] = {
|
|
36
|
+
name: resource,
|
|
37
|
+
endpoints: [],
|
|
38
|
+
methods: new Set(),
|
|
39
|
+
hasAuth: false,
|
|
40
|
+
fields: new Set(),
|
|
41
|
+
actions: new Set(),
|
|
42
|
+
queryParams: new Set()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Add action if it's a nested action
|
|
47
|
+
if (action) {
|
|
48
|
+
resources[resource].actions.add(action);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
resources[resource].endpoints.push({
|
|
52
|
+
path: endpoint,
|
|
53
|
+
method: method,
|
|
54
|
+
requiresAuth: hasAuth,
|
|
55
|
+
hasParam: hasParam,
|
|
56
|
+
action: action,
|
|
57
|
+
queryParams: queryParams
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
resources[resource].methods.add(method);
|
|
61
|
+
if (hasAuth) resources[resource].hasAuth = true;
|
|
62
|
+
|
|
63
|
+
// Add query params to resource
|
|
64
|
+
queryParams.forEach(p => resources[resource].queryParams.add(p));
|
|
65
|
+
|
|
66
|
+
// Extract field names
|
|
67
|
+
let fields = [];
|
|
68
|
+
const callContent = (typeof call === 'object' && call.content) ? call.content : '';
|
|
69
|
+
|
|
70
|
+
if (typeof call === 'object' && call.fields) {
|
|
71
|
+
fields = Array.isArray(call.fields) ? call.fields : Object.keys(call.fields || {});
|
|
72
|
+
} else if (typeof call === 'string') {
|
|
73
|
+
fields = extractFieldNames(endpoint, call, content);
|
|
74
|
+
} else if (typeof call === 'object') {
|
|
75
|
+
// Extract from the full file content
|
|
76
|
+
fields = extractFieldNames(endpoint, JSON.stringify(call), callContent || content);
|
|
77
|
+
}
|
|
78
|
+
fields.forEach(f => resources[resource].fields.add(f));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Convert Sets to Arrays and enrich with defaults
|
|
82
|
+
const result = {};
|
|
83
|
+
for (const [key, resource] of Object.entries(resources)) {
|
|
84
|
+
const methods = Array.from(resource.methods);
|
|
85
|
+
const fields = Array.from(resource.fields);
|
|
86
|
+
|
|
87
|
+
// Add default fields if none detected
|
|
88
|
+
const allFields = fields.length > 0
|
|
89
|
+
? fields
|
|
90
|
+
: getDefaultFieldsForResource(resource.name);
|
|
91
|
+
|
|
92
|
+
result[key] = {
|
|
93
|
+
name: resource.name,
|
|
94
|
+
endpoints: resource.endpoints,
|
|
95
|
+
methods: methods,
|
|
96
|
+
hasAuth: resource.hasAuth,
|
|
97
|
+
fields: allFields,
|
|
98
|
+
actions: Array.from(resource.actions),
|
|
99
|
+
queryParams: Array.from(resource.queryParams),
|
|
100
|
+
isCrudResource: detectCrudOperations(methods),
|
|
101
|
+
isComplexResource: resource.actions.size > 0 || resource.queryParams.size > 0
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isSystemEndpoint(endpoint) {
|
|
109
|
+
const normalized = endpoint
|
|
110
|
+
.split('?')[0]
|
|
111
|
+
.trim()
|
|
112
|
+
.replace(/\/+$/, '')
|
|
113
|
+
.toLowerCase();
|
|
114
|
+
|
|
115
|
+
return normalized === '/health' ||
|
|
116
|
+
normalized === `/api/health` ||
|
|
117
|
+
normalized === `/api/status` ||
|
|
118
|
+
normalized === `/api`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Smart resource extraction - ignores nested actions
|
|
123
|
+
* Examples:
|
|
124
|
+
* - /api/products → products
|
|
125
|
+
* - /api/products/:id → products
|
|
126
|
+
* - /api/products/:id/reviews → products (NOT a separate resource!)
|
|
127
|
+
* - /api/reviews/:id/approve → reviews (approve is an action)
|
|
128
|
+
* - /api/orders/:id/cancel → orders (cancel is an action)
|
|
129
|
+
* - /api/cart/clear → cart (clear is an action)
|
|
130
|
+
*/
|
|
131
|
+
function extractResourceSmart(endpoint) {
|
|
132
|
+
// Remove /api prefix
|
|
133
|
+
let path = endpoint.replace(/^\/api\//, '');
|
|
134
|
+
|
|
135
|
+
// Split into segments
|
|
136
|
+
const segments = path.split('/').filter(s => s && s !== ':id');
|
|
137
|
+
|
|
138
|
+
if (segments.length === 0) return 'api';
|
|
139
|
+
|
|
140
|
+
// First segment is always the resource
|
|
141
|
+
const resource = segments[0];
|
|
142
|
+
|
|
143
|
+
// Common action keywords that should NOT be resources
|
|
144
|
+
const actionKeywords = [
|
|
145
|
+
'approve', 'reject', 'cancel', 'confirm', 'verify',
|
|
146
|
+
'activate', 'deactivate', 'enable', 'disable',
|
|
147
|
+
'clear', 'reset', 'refresh', 'sync',
|
|
148
|
+
'search', 'filter', 'sort', 'export', 'import',
|
|
149
|
+
'list', 'stats', 'analytics', 'dashboard'
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
// If second segment is an action keyword, return first segment
|
|
153
|
+
if (segments.length > 1 && actionKeywords.includes(segments[1].toLowerCase())) {
|
|
154
|
+
return resource;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If resource is an action keyword, it's likely being used as a resource name
|
|
158
|
+
// (e.g., /api/approve) - check context
|
|
159
|
+
if (actionKeywords.includes(resource.toLowerCase())) {
|
|
160
|
+
// If there's a preceding resource, it's an action
|
|
161
|
+
// Otherwise, treat as resource (might be miscategorized)
|
|
162
|
+
return resource;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return resource;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract action from endpoint
|
|
170
|
+
* Examples:
|
|
171
|
+
* - /api/reviews/:id/approve → approve
|
|
172
|
+
* - /api/orders/:id/cancel → cancel
|
|
173
|
+
* - /api/cart/clear → clear
|
|
174
|
+
* - /api/products/search → search
|
|
175
|
+
*/
|
|
176
|
+
function extractActionFromEndpoint(endpoint) {
|
|
177
|
+
const path = endpoint.replace(/^\/api\//, '');
|
|
178
|
+
const segments = path.split('/').filter(s => s && s !== ':id');
|
|
179
|
+
|
|
180
|
+
if (segments.length <= 1) return null;
|
|
181
|
+
|
|
182
|
+
// Last segment is likely the action (if not :id)
|
|
183
|
+
const lastSegment = segments[segments.length - 1];
|
|
184
|
+
|
|
185
|
+
const actionKeywords = [
|
|
186
|
+
'approve', 'reject', 'cancel', 'confirm', 'verify',
|
|
187
|
+
'activate', 'deactivate', 'enable', 'disable',
|
|
188
|
+
'clear', 'reset', 'refresh', 'sync',
|
|
189
|
+
'search', 'filter', 'sort', 'export', 'import',
|
|
190
|
+
'stats', 'analytics', 'dashboard', 'count'
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
if (actionKeywords.includes(lastSegment.toLowerCase())) {
|
|
194
|
+
return lastSegment;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Extract HTTP method from API call
|
|
202
|
+
*/
|
|
203
|
+
function extractMethod(call) {
|
|
204
|
+
if (typeof call !== 'string') return 'GET';
|
|
205
|
+
|
|
206
|
+
if (call.includes("method: 'POST'") || call.includes('method: "POST"') || call.includes('.post(')) {
|
|
207
|
+
return 'POST';
|
|
208
|
+
}
|
|
209
|
+
if (call.includes("method: 'PUT'") || call.includes('method: "PUT"') || call.includes('.put(')) {
|
|
210
|
+
return 'PUT';
|
|
211
|
+
}
|
|
212
|
+
if (call.includes("method: 'DELETE'") || call.includes('method: "DELETE"') || call.includes('.delete(')) {
|
|
213
|
+
return 'DELETE';
|
|
214
|
+
}
|
|
215
|
+
if (call.includes("method: 'GET'") || call.includes('method: "GET"') || call.includes('.get(')) {
|
|
216
|
+
return 'GET';
|
|
217
|
+
}
|
|
218
|
+
if (call.includes('body:')) {
|
|
219
|
+
return 'POST'; // Default POST if body present
|
|
220
|
+
}
|
|
221
|
+
return 'GET'; // Default GET
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Check if route requires authentication
|
|
226
|
+
*/
|
|
227
|
+
function requiresAuth(call) {
|
|
228
|
+
if (typeof call !== 'string') return false;
|
|
229
|
+
|
|
230
|
+
const authIndicators = [
|
|
231
|
+
'Authorization',
|
|
232
|
+
'Bearer',
|
|
233
|
+
'token',
|
|
234
|
+
'jwt',
|
|
235
|
+
'headers:'
|
|
236
|
+
];
|
|
237
|
+
return authIndicators.some(indicator => call.toLowerCase().includes(indicator.toLowerCase()));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Check if endpoint has parameters
|
|
242
|
+
*/
|
|
243
|
+
function hasParameter(endpoint) {
|
|
244
|
+
return endpoint.includes(':id') || endpoint.includes('${') || /\/\d+$/.test(endpoint);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Extract potential field names from endpoint and context
|
|
249
|
+
*/
|
|
250
|
+
function extractFieldNames(endpoint, call, fullContent) {
|
|
251
|
+
const fields = new Set();
|
|
252
|
+
|
|
253
|
+
// Extract from JSON.stringify patterns
|
|
254
|
+
if (typeof call === 'string') {
|
|
255
|
+
const jsonPattern = /JSON\.stringify\(\s*\{([^}]+)\}/g;
|
|
256
|
+
let match;
|
|
257
|
+
while ((match = jsonPattern.exec(call)) !== null) {
|
|
258
|
+
const jsonContent = match[1];
|
|
259
|
+
const fields_in_json = jsonContent.match(/(\w+)\s*[,:]/g);
|
|
260
|
+
if (fields_in_json) {
|
|
261
|
+
fields_in_json.forEach(f => {
|
|
262
|
+
fields.add(f.replace(/[,:]/g, '').trim());
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Look for form input names in full content
|
|
269
|
+
if (fullContent && typeof fullContent === 'string') {
|
|
270
|
+
// Pattern 1: <input name="fieldName"
|
|
271
|
+
const inputPattern = /<input[^>]*name=['"`](\w+)['"`]/g;
|
|
272
|
+
let match;
|
|
273
|
+
while ((match = inputPattern.exec(fullContent)) !== null) {
|
|
274
|
+
fields.add(match[1]);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Pattern 2: formData.append('fieldName'
|
|
278
|
+
const formDataPattern = /formData\.append\s*\(\s*['"`](\w+)['"`]/g;
|
|
279
|
+
while ((match = formDataPattern.exec(fullContent)) !== null) {
|
|
280
|
+
fields.add(match[1]);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Pattern 3: useState for field names
|
|
284
|
+
const statePattern = /const\s*\[(\w+),\s*set\w+\]\s*=\s*useState/g;
|
|
285
|
+
while ((match = statePattern.exec(fullContent)) !== null) {
|
|
286
|
+
const stateName = match[1];
|
|
287
|
+
// Only add if looks like a field (not loading, error, etc.)
|
|
288
|
+
if (!['loading', 'error', 'data', 'response', 'success'].includes(stateName)) {
|
|
289
|
+
fields.add(stateName);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Pattern 4: Object shorthand in body ({ name, email, price })
|
|
294
|
+
const bodyPattern = /body\s*:\s*JSON\.stringify\s*\(\s*\{([^}]+)\}\s*\)/g;
|
|
295
|
+
while ((match = bodyPattern.exec(fullContent)) !== null) {
|
|
296
|
+
const bodyContent = match[1];
|
|
297
|
+
const fieldMatches = bodyContent.match(/(\w+)\s*[,:}]/g);
|
|
298
|
+
if (fieldMatches) {
|
|
299
|
+
fieldMatches.forEach(f => {
|
|
300
|
+
const fieldName = f.replace(/[,:}]/g, '').trim();
|
|
301
|
+
if (fieldName && fieldName !== 'headers') {
|
|
302
|
+
fields.add(fieldName);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return Array.from(fields);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Detect if resource has CRUD operations based on methods
|
|
314
|
+
*/
|
|
315
|
+
function detectCrudOperations(methods) {
|
|
316
|
+
// Convert Set to Array if needed
|
|
317
|
+
const methodsArray = Array.isArray(methods) ? methods : Array.from(methods);
|
|
318
|
+
|
|
319
|
+
const has_get = methodsArray.includes('GET');
|
|
320
|
+
const has_post = methodsArray.includes('POST');
|
|
321
|
+
const has_put = methodsArray.includes('PUT');
|
|
322
|
+
const has_delete = methodsArray.includes('DELETE');
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
create: has_post,
|
|
326
|
+
read: has_get,
|
|
327
|
+
update: has_put,
|
|
328
|
+
delete: has_delete
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get default fields for a resource based on name
|
|
334
|
+
*/
|
|
335
|
+
export function getDefaultFieldsForResource(resourceName) {
|
|
336
|
+
const fieldsMap = {
|
|
337
|
+
products: ['name', 'description', 'price', 'category', 'stock'],
|
|
338
|
+
posts: ['title', 'content', 'author', 'views', 'likes'],
|
|
339
|
+
comments: ['text', 'author', 'postId', 'likes'],
|
|
340
|
+
users: ['name', 'email', 'phone', 'avatar'],
|
|
341
|
+
orders: ['orderNumber', 'items', 'total', 'status', 'shippingAddress'],
|
|
342
|
+
categories: ['name', 'description', 'icon'],
|
|
343
|
+
tags: ['name', 'description'],
|
|
344
|
+
reviews: ['rating', 'text', 'author', 'productId'],
|
|
345
|
+
articles: ['title', 'content', 'author', 'tags', 'published'],
|
|
346
|
+
todos: ['title', 'completed', 'priority', 'dueDate'],
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
return fieldsMap[resourceName.toLowerCase()] || ['name', 'description'];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Group endpoints by operation type
|
|
354
|
+
*/
|
|
355
|
+
export function groupEndpointsByOperation(resources) {
|
|
356
|
+
const grouped = {
|
|
357
|
+
create: [],
|
|
358
|
+
read: [],
|
|
359
|
+
update: [],
|
|
360
|
+
delete: [],
|
|
361
|
+
list: [],
|
|
362
|
+
other: []
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
for (const [resourceName, resource] of Object.entries(resources)) {
|
|
366
|
+
for (const endpoint of resource.endpoints) {
|
|
367
|
+
const path = endpoint.path.toLowerCase();
|
|
368
|
+
const method = endpoint.method;
|
|
369
|
+
|
|
370
|
+
if (method === 'POST' && (path.includes('create') || path.includes('add'))) {
|
|
371
|
+
grouped.create.push({ resource: resourceName, ...endpoint });
|
|
372
|
+
} else if (method === 'GET' && (path.includes('list') || path.endsWith(`/api/${resourceName}`))) {
|
|
373
|
+
grouped.list.push({ resource: resourceName, ...endpoint });
|
|
374
|
+
} else if (method === 'GET' && path.includes(':id')) {
|
|
375
|
+
grouped.read.push({ resource: resourceName, ...endpoint });
|
|
376
|
+
} else if (method === 'PUT' || method === 'PATCH') {
|
|
377
|
+
grouped.update.push({ resource: resourceName, ...endpoint });
|
|
378
|
+
} else if (method === 'DELETE') {
|
|
379
|
+
grouped.delete.push({ resource: resourceName, ...endpoint });
|
|
380
|
+
} else {
|
|
381
|
+
grouped.other.push({ resource: resourceName, ...endpoint });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return grouped;
|
|
387
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Pattern Detection
|
|
3
|
+
* Analyzes frontend code to detect authentication patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function detectAuthPatterns(content, apiCalls = []) {
|
|
7
|
+
const authPatterns = {
|
|
8
|
+
hasSignup: false,
|
|
9
|
+
hasLogin: false,
|
|
10
|
+
hasLogout: false,
|
|
11
|
+
hasProfile: false,
|
|
12
|
+
hasTokenStorage: false,
|
|
13
|
+
hasPasswordField: false,
|
|
14
|
+
authEndpoints: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Detect signup
|
|
18
|
+
if (/signup|register|createAccount/i.test(content)) {
|
|
19
|
+
authPatterns.hasSignup = true;
|
|
20
|
+
const signupCall = apiCalls.find(c => /signup|register|createAccount/i.test(c.route));
|
|
21
|
+
if (signupCall) authPatterns.authEndpoints.push({ type: 'signup', ...signupCall });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Detect login
|
|
25
|
+
if (/login|authenticate|signIn/i.test(content)) {
|
|
26
|
+
authPatterns.hasLogin = true;
|
|
27
|
+
const loginCall = apiCalls.find(c => /login|authenticate|signIn/i.test(c.route));
|
|
28
|
+
if (loginCall) authPatterns.authEndpoints.push({ type: 'login', ...loginCall });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Detect logout
|
|
32
|
+
if (/logout|signOut/i.test(content)) {
|
|
33
|
+
authPatterns.hasLogout = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Detect profile/user endpoints
|
|
37
|
+
if (/profile|user|account|me(?!thod)/i.test(content)) {
|
|
38
|
+
authPatterns.hasProfile = true;
|
|
39
|
+
const profileCall = apiCalls.find(c => /profile|user|account|\/me/i.test(c.route) && c.method === 'GET');
|
|
40
|
+
if (profileCall) authPatterns.authEndpoints.push({ type: 'profile', ...profileCall });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Detect token/storage
|
|
44
|
+
if (/localStorage|sessionStorage|jwt|token|Bearer/i.test(content)) {
|
|
45
|
+
authPatterns.hasTokenStorage = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Detect password field
|
|
49
|
+
if (/password|pwd|passcode/i.test(content)) {
|
|
50
|
+
authPatterns.hasPasswordField = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return authPatterns;
|
|
54
|
+
}
|