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,353 @@
1
+ /**
2
+ * Rules Engine - Field Detection & Type Resolution
3
+ * Professional approach: Rules define how to detect & validate fields
4
+ * NOT hardcoded if/else logic
5
+ */
6
+
7
+ /**
8
+ * Core Rules - What defines each field type
9
+ * Pattern: fieldNamePattern → fieldType + validators + properties
10
+ */
11
+ const FIELD_RULES = {
12
+ // Email Field
13
+ email: {
14
+ patterns: ['email', 'emailaddress', 'e-mail', 'user_email'],
15
+ type: 'String',
16
+ properties: {
17
+ lowercase: true,
18
+ trim: true,
19
+ match: '/.+\\@.+\\..+/'
20
+ },
21
+ validators: ['email', 'required'],
22
+ description: 'Email field with validation'
23
+ },
24
+
25
+ // Password Fields
26
+ password: {
27
+ patterns: ['password', 'passwd', 'pwd', 'confirmpassword', 'newpassword'],
28
+ type: 'String',
29
+ properties: {
30
+ select: false, // Don't return in queries
31
+ minlength: 6
32
+ },
33
+ validators: ['required', 'hash'],
34
+ hooks: ['hash-before-save'],
35
+ description: 'Password field - auto hashed'
36
+ },
37
+
38
+ // Phone Numbers
39
+ phone: {
40
+ patterns: ['phone', 'phonenumber', 'mobile', 'mobilenumber', 'contact'],
41
+ type: 'String',
42
+ properties: {
43
+ match: '/^\\d{10,}$/'
44
+ },
45
+ validators: ['phone'],
46
+ description: 'Phone number with validation'
47
+ },
48
+
49
+ // URLs
50
+ url: {
51
+ patterns: ['url', 'website', 'link', 'profileurl', 'avatar', 'image', 'photo'],
52
+ type: 'String',
53
+ properties: {
54
+ trim: true
55
+ },
56
+ validators: ['url'],
57
+ description: 'URL field with validation'
58
+ },
59
+
60
+ // Dates
61
+ date: {
62
+ patterns: ['date', 'createddate', 'duedate', 'deadline', 'startdate', 'enddate', 'publishdate', 'birthdate'],
63
+ type: 'Date',
64
+ properties: {},
65
+ validators: ['date'],
66
+ description: 'Date field'
67
+ },
68
+
69
+ // Boolean Fields
70
+ boolean: {
71
+ patterns: ['active', 'inactive', 'completed', 'published', 'verified', 'approved', 'enabled', 'disabled', 'public', 'private', 'is_', 'has_'],
72
+ type: 'Boolean',
73
+ properties: {
74
+ default: false
75
+ },
76
+ validators: [],
77
+ description: 'Boolean flag field'
78
+ },
79
+
80
+ // Numbers - Price/Cost
81
+ price: {
82
+ patterns: ['price', 'cost', 'amount', 'rate', 'salary', 'fee', 'charge', 'total'],
83
+ type: 'Number',
84
+ properties: {
85
+ default: 0,
86
+ min: 0
87
+ },
88
+ validators: ['number', 'positive'],
89
+ description: 'Monetary value'
90
+ },
91
+
92
+ // Numbers - Count/Rating
93
+ count: {
94
+ patterns: ['count', 'views', 'likes', 'downloads', 'rating', 'score', 'points', 'stars'],
95
+ type: 'Number',
96
+ properties: {
97
+ default: 0,
98
+ min: 0
99
+ },
100
+ validators: ['number'],
101
+ description: 'Numeric counter field'
102
+ },
103
+
104
+ // ID/Reference Fields
105
+ reference: {
106
+ patterns: ['id', 'userid', 'authorid', 'postid', 'productid', 'categoryid', 'ownerid', 'createdby', 'updatedby', '_id', 'ref_'],
107
+ type: 'ObjectId',
108
+ properties: {
109
+ ref: 'AUTO' // Ref determined by field name
110
+ },
111
+ validators: [],
112
+ hooks: ['resolve-reference'],
113
+ description: 'Reference to another document'
114
+ },
115
+
116
+ // Status Enum
117
+ status: {
118
+ patterns: ['status', 'state', 'condition'],
119
+ type: 'String',
120
+ properties: {
121
+ enum: ['active', 'inactive', 'pending', 'archived', 'deleted'],
122
+ default: 'active'
123
+ },
124
+ validators: ['enum'],
125
+ description: 'Status field with predefined values'
126
+ },
127
+
128
+ // Array of Strings
129
+ tags: {
130
+ patterns: ['tags', 'categories', 'keywords', 'labels', 'skills', 'interests'],
131
+ type: 'Array',
132
+ properties: {
133
+ itemType: 'String'
134
+ },
135
+ validators: ['array'],
136
+ description: 'List of string values'
137
+ },
138
+
139
+ // Rich Text/Markdown
140
+ text: {
141
+ patterns: ['description', 'content', 'body', 'bio', 'summary', 'intro', 'notes', 'remarks', 'feedback'],
142
+ type: 'String',
143
+ properties: {
144
+ trim: true
145
+ },
146
+ validators: ['text'],
147
+ description: 'Long text field'
148
+ },
149
+
150
+ // Default String
151
+ string: {
152
+ patterns: ['name', 'title', 'username', 'firstname', 'lastname', 'fullname', 'displayname'],
153
+ type: 'String',
154
+ properties: {
155
+ trim: true,
156
+ required: true
157
+ },
158
+ validators: ['required', 'string'],
159
+ description: 'Standard string field'
160
+ }
161
+ };
162
+
163
+ /**
164
+ * Apply rules to detect field type
165
+ * @param {string} fieldName - The field name from frontend
166
+ * @returns {Object} - Detected field configuration
167
+ */
168
+ export function detectFieldType(fieldName) {
169
+ const normalizedName = fieldName.toLowerCase();
170
+
171
+ // Check each rule
172
+ for (const [ruleKey, rule] of Object.entries(FIELD_RULES)) {
173
+ // Check if field name matches any pattern
174
+ const matches = rule.patterns.some(pattern => {
175
+ if (pattern.startsWith('is_') || pattern.startsWith('has_')) {
176
+ return normalizedName.startsWith(pattern);
177
+ }
178
+ return normalizedName.includes(pattern);
179
+ });
180
+
181
+ if (matches) {
182
+ return {
183
+ fieldName,
184
+ rule: ruleKey,
185
+ type: rule.type,
186
+ properties: { ...rule.properties },
187
+ validators: [...rule.validators],
188
+ hooks: rule.hooks || [],
189
+ description: rule.description
190
+ };
191
+ }
192
+ }
193
+
194
+ // Default: treat as string
195
+ return {
196
+ fieldName,
197
+ rule: 'string',
198
+ type: 'String',
199
+ properties: { trim: true },
200
+ validators: ['string'],
201
+ hooks: [],
202
+ description: 'Default string field'
203
+ };
204
+ }
205
+
206
+ /**
207
+ * Detect if field needs indexing
208
+ * @param {string} fieldName
209
+ * @returns {boolean}
210
+ */
211
+ export function shouldIndex(fieldName) {
212
+ const indexPatterns = ['email', 'username', 'userid', 'status', 'active', 'createdby'];
213
+ return indexPatterns.some(
214
+ pattern => fieldName.toLowerCase().includes(pattern)
215
+ );
216
+ }
217
+
218
+ /**
219
+ * Detect relationships between resources
220
+ * Enhanced to auto-detect common relationship patterns
221
+ * @param {string} fieldName
222
+ * @param {string} resourceName
223
+ * @param {Array} allResources - List of all available resources
224
+ * @returns {Object|null}
225
+ */
226
+ export function detectRelationship(fieldName, resourceName, allResources = []) {
227
+ const normalized = fieldName.toLowerCase();
228
+
229
+ // Smart role-based user references (admin, organizer, owner, creator, author)
230
+ const userRolePatterns = ['admin', 'organizer', 'owner', 'creator', 'author', 'user', 'createdby', 'updatedby', 'assignedto'];
231
+ for (const pattern of userRolePatterns) {
232
+ if (normalized === pattern || normalized.startsWith(pattern)) {
233
+ return {
234
+ fieldName,
235
+ type: 'ObjectId',
236
+ ref: 'User',
237
+ isRequired: ['admin', 'organizer', 'owner', 'creator', 'user'].includes(normalized)
238
+ };
239
+ }
240
+ }
241
+
242
+ // Array of user references (members, participants, attendees, followers, subscribers, admins, organizers)
243
+ const userArrayPatterns = ['members', 'participants', 'attendees', 'followers', 'subscribers', 'admins', 'organizers', 'users', 'authors', 'contributors'];
244
+ if (userArrayPatterns.includes(normalized)) {
245
+ return {
246
+ fieldName,
247
+ type: 'Array',
248
+ arrayItemType: 'ObjectId',
249
+ ref: 'User',
250
+ isRequired: false
251
+ };
252
+ }
253
+
254
+ // Direct resource name references (club, event, product, category, etc.)
255
+ // Check if field name matches any resource name
256
+ const resourceMatch = allResources.find(r =>
257
+ normalized === r.name.toLowerCase() ||
258
+ normalized === r.singular.toLowerCase() ||
259
+ normalized === r.plural.toLowerCase()
260
+ );
261
+
262
+ if (resourceMatch) {
263
+ // Check if it's plural (array) or singular (single ref)
264
+ const isArray = normalized === resourceMatch.plural.toLowerCase();
265
+
266
+ return {
267
+ fieldName,
268
+ type: isArray ? 'Array' : 'ObjectId',
269
+ arrayItemType: isArray ? 'ObjectId' : undefined,
270
+ ref: resourceMatch.singular.charAt(0).toUpperCase() + resourceMatch.singular.slice(1),
271
+ isRequired: !isArray && ['club', 'event', 'product', 'category', 'post'].includes(normalized)
272
+ };
273
+ }
274
+
275
+ // UserId → Reference to User (existing pattern)
276
+ // ProductId → Reference to Product
277
+ // AuthorId → Reference to User
278
+ const idMatch = normalized.match(/(\w+)id$/i);
279
+ if (idMatch) {
280
+ let refModel = idMatch[1];
281
+
282
+ // Smart mapping
283
+ const commonMappings = {
284
+ 'author': 'User',
285
+ 'user': 'User',
286
+ 'owner': 'User',
287
+ 'creator': 'User',
288
+ 'admin': 'User',
289
+ 'organizer': 'User'
290
+ };
291
+
292
+ refModel = commonMappings[refModel] ||
293
+ refModel.charAt(0).toUpperCase() + refModel.slice(1);
294
+
295
+ return {
296
+ fieldName,
297
+ type: 'ObjectId',
298
+ ref: refModel,
299
+ isRequired: false
300
+ };
301
+ }
302
+
303
+ return null;
304
+ }
305
+
306
+ /**
307
+ * Build complete field configuration from rules
308
+ * @param {string} fieldName
309
+ * @param {string} resourceName
310
+ * @param {Array} allResources - List of all resources for relationship detection
311
+ * @returns {Object}
312
+ */
313
+ export function buildFieldConfig(fieldName, resourceName = '', allResources = []) {
314
+ const detected = detectFieldType(fieldName);
315
+ const relationship = detectRelationship(fieldName, resourceName, allResources);
316
+
317
+ return {
318
+ name: fieldName,
319
+ ...detected,
320
+ relationship,
321
+ shouldIndex: shouldIndex(fieldName),
322
+ isRequired:
323
+ fieldName.toLowerCase().includes('required') ||
324
+ fieldName.toLowerCase().includes('email') ||
325
+ fieldName.toLowerCase().includes('name') ||
326
+ (relationship && relationship.isRequired)
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Get all rules (for rule engine UI/admin)
332
+ */
333
+ export function getAllRules() {
334
+ return Object.entries(FIELD_RULES).map(([key, rule]) => ({
335
+ id: key,
336
+ ...rule
337
+ }));
338
+ }
339
+
340
+ /**
341
+ * Add custom rule (for extensibility)
342
+ */
343
+ export function addCustomRule(identity, rule) {
344
+ FIELD_RULES[identity] = rule;
345
+ return true;
346
+ }
347
+
348
+ /**
349
+ * Get rule by ID
350
+ */
351
+ export function getRule(ruleId) {
352
+ return FIELD_RULES[ruleId] || null;
353
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Template Engine - IR + Templates → Generated Code
3
+ * Uses simple but powerful template syntax
4
+ */
5
+
6
+ /**
7
+ * Render template with IR data
8
+ * Supports: <%= %> for variables, <% %> for logic
9
+ */
10
+ export function renderTemplate(template, ir, resourceName) {
11
+ if (!template) return '';
12
+
13
+ const resource = ir.resources.find(r => r.name === resourceName);
14
+ if (!resource) throw new Error(`Resource ${resourceName} not found in IR`);
15
+
16
+ // Create template context with helpers
17
+ const context = {
18
+ resource,
19
+ ir,
20
+ ...getTemplateHelpers()
21
+ };
22
+
23
+ // Simple template rendering
24
+ return compileTemplate(template, context);
25
+ }
26
+
27
+ /**
28
+ * Compile template with context
29
+ * Supports: <%= expression %>, <% code %>, <%# comment %>
30
+ */
31
+ function compileTemplate(template, context) {
32
+ let result = template;
33
+
34
+ // Remove comments <%# ... %>
35
+ result = result.replace(/<%#[\s\S]*?%>/g, '');
36
+
37
+ // Process output blocks <%= ... %> FIRST (before code blocks)
38
+ result = result.replace(/<%=([\s\S]*?)%>/g, (match, expr) => {
39
+ try {
40
+ const trimmedExpr = expr.trim();
41
+ const func = new Function(...Object.keys(context), `return ${trimmedExpr}`);
42
+ const value = func(...Object.values(context));
43
+ return value ?? '';
44
+ } catch (e) {
45
+ console.warn(`Template expression error at: ${expr.substring(0, 50)}... - ${e.message}`);
46
+ return '';
47
+ }
48
+ });
49
+
50
+ // Process code blocks <% ... %> AFTER output blocks
51
+ result = result.replace(/<%(?!=)([\s\S]*?)%>/g, (match, code) => {
52
+ try {
53
+ // Create function with context access
54
+ const func = new Function(...Object.keys(context), code);
55
+ func(...Object.values(context));
56
+ return '';
57
+ } catch (e) {
58
+ console.warn(`Template code block error: ${e.message}`);
59
+ return '';
60
+ }
61
+ });
62
+
63
+ return result;
64
+ }
65
+
66
+ /**
67
+ * Template helper functions (available in templates)
68
+ */
69
+ function getTemplateHelpers() {
70
+ return {
71
+ // String helpers
72
+ capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
73
+ camelCase: (str) => str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()),
74
+ snakeCase: (str) => str.replace(/([A-Z])/g, '_$1').toLowerCase(),
75
+ kebabCase: (str) => str.replace(/([A-Z])/g, '-$1').toLowerCase(),
76
+
77
+ // Field helpers
78
+ fieldsByType: (fields, type) => fields.filter(f => f.type === type),
79
+ requiredFields: (fields) => fields.filter(f => f.isRequired),
80
+ indexedFields: (fields) => fields.filter(f => f.shouldIndex),
81
+ fieldsWithValidators: (fields) => fields.filter(f => f.validators?.length > 0),
82
+
83
+ // Array helpers
84
+ forEach: (items, callback) => items.map(callback).join(''),
85
+ join: (items, separator = ', ') => items.join(separator),
86
+
87
+ // Conditional - use 'conditional' instead of 'if' to avoid reserved keyword
88
+ conditional: (condition, trueValue, falseValue = '') => condition ? trueValue : falseValue,
89
+
90
+ // Field schema generator for Mongoose models
91
+ generateFieldSchema: (field) => {
92
+ let type = field.type === 'ObjectId' ? 'mongoose.Types.ObjectId'
93
+ : field.type === 'Date' ? 'Date'
94
+ : field.type === 'Number' ? 'Number'
95
+ : field.type === 'Boolean' ? 'Boolean'
96
+ : 'String';
97
+ let def = field.name + ': { type: ' + type;
98
+ if (field.type === 'ObjectId' && field.relationship) {
99
+ def += ", ref: '" + field.relationship.ref + "'";
100
+ }
101
+ if (field.isRequired) def += ', required: true';
102
+ if (field.shouldIndex) def += ', index: true';
103
+ def += ' }';
104
+ return def;
105
+ },
106
+
107
+ // Validation generator for Joi
108
+ generateJoiValidation: (field) => {
109
+ let type = field.type === 'ObjectId' ? 'string()'
110
+ : field.type === 'String' ? 'string()'
111
+ : field.type === 'Number' ? 'number()'
112
+ : field.type === 'Date' ? 'date()'
113
+ : field.type === 'Boolean' ? 'boolean()'
114
+ : 'string()';
115
+ let def = field.name + ': Joi.' + type;
116
+ if (field.isRequired) def += '.required()';
117
+ else def += '.optional()';
118
+ return def;
119
+ }
120
+ };
121
+ }
122
+
123
+ /**
124
+ * Batch render multiple templates (model, routes, etc.)
125
+ */
126
+ export function renderAllTemplates(ir, templates) {
127
+ const generated = {};
128
+
129
+ for (const [templateName, template] of Object.entries(templates)) {
130
+ try {
131
+ for (const resource of ir.resources) {
132
+ const key = `${resource.name}.${templateName}`;
133
+ generated[key] = renderTemplate(template, ir, resource.name);
134
+ }
135
+ } catch (error) {
136
+ console.error(`Failed to render ${templateName}: ${error.message}`);
137
+ }
138
+ }
139
+
140
+ return generated;
141
+ }
142
+
143
+ /**
144
+ * Validate template syntax
145
+ */
146
+ export function validateTemplate(template) {
147
+ const errors = [];
148
+
149
+ // Check for mismatched delimiters
150
+ const openTags = (template.match(/<%/g) || []).length;
151
+ const closeTags = (template.match(/%>/g) || []).length;
152
+
153
+ if (openTags !== closeTags) {
154
+ errors.push(`Mismatched template tags: ${openTags} opening, ${closeTags} closing`);
155
+ }
156
+
157
+ // Try simple parse
158
+ try {
159
+ new Function('resource, ir', template);
160
+ } catch (e) {
161
+ errors.push(`Template syntax error: ${e.message}`);
162
+ }
163
+
164
+ return {
165
+ valid: errors.length === 0,
166
+ errors
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Advanced: Iterator helper for templates
172
+ */
173
+ export class TemplateIterator {
174
+ constructor(items) {
175
+ this.items = items;
176
+ this.index = 0;
177
+ }
178
+
179
+ each(callback) {
180
+ return this.items.map((item, i) => {
181
+ this.index = i;
182
+ return callback(item, i, this.items);
183
+ }).join('');
184
+ }
185
+
186
+ map(callback) {
187
+ return this.items.map(callback);
188
+ }
189
+
190
+ filter(predicate) {
191
+ return this.items.filter(predicate);
192
+ }
193
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * All IR-based Templates Export
3
+ */
4
+
5
+ export { MODEL_TEMPLATE } from './model.template.js';
6
+ export { ROUTES_TEMPLATE } from './routes.template.js';
7
+ export { VALIDATION_TEMPLATE } from './validation.template.js';
8
+
9
+ // Template map for easy access
10
+ export const TEMPLATES = {
11
+ model: () => import('./model.template.js').then(m => m.MODEL_TEMPLATE),
12
+ routes: () => import('./routes.template.js').then(m => m.ROUTES_TEMPLATE),
13
+ validation: () => import('./validation.template.js').then(m => m.VALIDATION_TEMPLATE),
14
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Mongoose Model Template
3
+ */
4
+
5
+ export const MODEL_TEMPLATE = `import mongoose from 'mongoose';
6
+
7
+ const <%= capitalize(resource.singular) %>Schema = new mongoose.Schema({
8
+ <%= resource.name === 'user' ? \`
9
+ username: { type: String, unique: true, sparse: true },
10
+ email: { type: String, unique: true, required: true },
11
+ password: { type: String, required: true },
12
+ firstName: { type: String },
13
+ lastName: { type: String },
14
+ role: { type: String, enum: ['student', 'organizer', 'admin'], default: 'student' },
15
+ avatar: { type: String },
16
+ \` : resource.name === 'club' ? \`
17
+ name: { type: String, required: true },
18
+ description: { type: String },
19
+ admin: { type: mongoose.Types.ObjectId, ref: 'User', required: true },
20
+ members: [{ type: mongoose.Types.ObjectId, ref: 'User' }],
21
+ image: { type: String },
22
+ \` : resource.name === 'event' ? \`
23
+ title: { type: String, required: true },
24
+ description: { type: String },
25
+ club: { type: mongoose.Types.ObjectId, ref: 'Club', required: true },
26
+ date: { type: Date, required: true },
27
+ location: { type: String },
28
+ capacity: { type: Number },
29
+ organizer: { type: mongoose.Types.ObjectId, ref: 'User' },
30
+ image: { type: String },
31
+ \` : resource.name === 'registration' ? \`
32
+ event: { type: mongoose.Types.ObjectId, ref: 'Event', required: true },
33
+ user: { type: mongoose.Types.ObjectId, ref: 'User', required: true },
34
+ status: { type: String, enum: ['confirmed', 'waitlist', 'cancelled'], default: 'confirmed' },
35
+ \` : \`
36
+ name: { type: String, required: true },
37
+ email: { type: String },
38
+ \` %>
39
+ createdAt: { type: Date, default: Date.now },
40
+ updatedAt: { type: Date, default: Date.now },
41
+ isActive: { type: Boolean, default: true },
42
+ isDeleted: { type: Boolean, default: false }
43
+ }, { timestamps: true, collection: '<%= resource.plural.toLowerCase() %>' });
44
+
45
+ const <%= capitalize(resource.singular) %> = mongoose.model('<%= capitalize(resource.singular) %>', <%= capitalize(resource.singular) %>Schema);
46
+ export default <%= capitalize(resource.singular) %>;
47
+ `;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Generic Resource Routes Template - CRUD only
3
+ */
4
+
5
+ export const ROUTES_GENERIC_TEMPLATE = `import express from 'express';
6
+ import <%= capitalize(resource.singular) %> from '../models/<%= capitalize(resource.singular) %>.model.js';
7
+
8
+ const router = express.Router();
9
+
10
+ // GET all <%= resource.plural %>
11
+ router.get('/', async (req, res) => {
12
+ try {
13
+ const { skip = 0, limit = 100 } = req.query;
14
+ const data = await <%= capitalize(resource.singular) %>.find({ isDeleted: false }).skip(parseInt(skip)).limit(parseInt(limit));
15
+ const total = await <%= capitalize(resource.singular) %>.countDocuments({ isDeleted: false });
16
+ res.json({ success: true, data, total, skip: parseInt(skip), limit: parseInt(limit) });
17
+ } catch (error) {
18
+ res.status(500).json({ success: false, error: error.message });
19
+ }
20
+ });
21
+
22
+ // GET single <%= resource.singular %>
23
+ router.get('/:id', async (req, res) => {
24
+ try {
25
+ const data = await <%= capitalize(resource.singular) %>.findById(req.params.id);
26
+ if (!data) return res.status(404).json({ success: false, error: 'Not found' });
27
+ res.json({ success: true, data });
28
+ } catch (error) {
29
+ res.status(500).json({ success: false, error: error.message });
30
+ }
31
+ });
32
+
33
+ // POST create <%= resource.singular %>
34
+ router.post('/', async (req, res) => {
35
+ try {
36
+ const data = new <%= capitalize(resource.singular) %>(req.body);
37
+ await data.save();
38
+ res.status(201).json({ success: true, data, message: 'Created successfully' });
39
+ } catch (error) {
40
+ res.status(400).json({ success: false, error: error.message });
41
+ }
42
+ });
43
+
44
+ // PUT update <%= resource.singular %>
45
+ router.put('/:id', async (req, res) => {
46
+ try {
47
+ const data = await <%= capitalize(resource.singular) %>.findByIdAndUpdate(req.params.id, req.body, { new: true });
48
+ if (!data) return res.status(404).json({ success: false, error: 'Not found' });
49
+ res.json({ success: true, data, message: 'Updated successfully' });
50
+ } catch (error) {
51
+ res.status(400).json({ success: false, error: error.message });
52
+ }
53
+ });
54
+
55
+ // DELETE <%= resource.singular %>
56
+ router.delete('/:id', async (req, res) => {
57
+ try {
58
+ await <%= capitalize(resource.singular) %>.findByIdAndUpdate(req.params.id, { isDeleted: true });
59
+ res.json({ success: true, message: 'Deleted successfully' });
60
+ } catch (error) {
61
+ res.status(500).json({ success: false, error: error.message });
62
+ }
63
+ });
64
+
65
+ export default router;
66
+ `;