@currentjs/gen 0.3.2 → 0.5.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 (69) hide show
  1. package/CHANGELOG.md +10 -611
  2. package/README.md +623 -427
  3. package/dist/cli.js +2 -1
  4. package/dist/commands/commit.js +25 -42
  5. package/dist/commands/createApp.js +1 -0
  6. package/dist/commands/createModule.js +151 -45
  7. package/dist/commands/diff.js +27 -40
  8. package/dist/commands/generateAll.js +141 -291
  9. package/dist/commands/migrateCommit.js +6 -18
  10. package/dist/commands/migratePush.d.ts +1 -0
  11. package/dist/commands/migratePush.js +135 -0
  12. package/dist/commands/migrateUpdate.d.ts +1 -0
  13. package/dist/commands/migrateUpdate.js +147 -0
  14. package/dist/commands/newGenerateAll.d.ts +4 -0
  15. package/dist/commands/newGenerateAll.js +336 -0
  16. package/dist/generators/controllerGenerator.d.ts +43 -19
  17. package/dist/generators/controllerGenerator.js +547 -329
  18. package/dist/generators/domainLayerGenerator.d.ts +21 -0
  19. package/dist/generators/domainLayerGenerator.js +276 -0
  20. package/dist/generators/dtoGenerator.d.ts +21 -0
  21. package/dist/generators/dtoGenerator.js +518 -0
  22. package/dist/generators/newControllerGenerator.d.ts +55 -0
  23. package/dist/generators/newControllerGenerator.js +644 -0
  24. package/dist/generators/newServiceGenerator.d.ts +19 -0
  25. package/dist/generators/newServiceGenerator.js +266 -0
  26. package/dist/generators/newStoreGenerator.d.ts +39 -0
  27. package/dist/generators/newStoreGenerator.js +408 -0
  28. package/dist/generators/newTemplateGenerator.d.ts +29 -0
  29. package/dist/generators/newTemplateGenerator.js +510 -0
  30. package/dist/generators/serviceGenerator.d.ts +16 -51
  31. package/dist/generators/serviceGenerator.js +167 -586
  32. package/dist/generators/storeGenerator.d.ts +35 -32
  33. package/dist/generators/storeGenerator.js +291 -238
  34. package/dist/generators/storeGeneratorV2.d.ts +31 -0
  35. package/dist/generators/storeGeneratorV2.js +190 -0
  36. package/dist/generators/templateGenerator.d.ts +21 -21
  37. package/dist/generators/templateGenerator.js +393 -268
  38. package/dist/generators/templates/appTemplates.d.ts +3 -1
  39. package/dist/generators/templates/appTemplates.js +15 -10
  40. package/dist/generators/templates/data/appYamlTemplate +5 -2
  41. package/dist/generators/templates/data/cursorRulesTemplate +315 -221
  42. package/dist/generators/templates/data/frontendScriptTemplate +45 -11
  43. package/dist/generators/templates/data/mainViewTemplate +1 -1
  44. package/dist/generators/templates/data/systemTsTemplate +5 -0
  45. package/dist/generators/templates/index.d.ts +0 -3
  46. package/dist/generators/templates/index.js +0 -3
  47. package/dist/generators/templates/newStoreTemplates.d.ts +5 -0
  48. package/dist/generators/templates/newStoreTemplates.js +141 -0
  49. package/dist/generators/templates/storeTemplates.d.ts +1 -5
  50. package/dist/generators/templates/storeTemplates.js +102 -219
  51. package/dist/generators/templates/viewTemplates.js +1 -1
  52. package/dist/generators/useCaseGenerator.d.ts +13 -0
  53. package/dist/generators/useCaseGenerator.js +188 -0
  54. package/dist/types/configTypes.d.ts +148 -0
  55. package/dist/types/configTypes.js +10 -0
  56. package/dist/utils/childEntityUtils.d.ts +18 -0
  57. package/dist/utils/childEntityUtils.js +78 -0
  58. package/dist/utils/commandUtils.d.ts +43 -0
  59. package/dist/utils/commandUtils.js +124 -0
  60. package/dist/utils/commitUtils.d.ts +4 -1
  61. package/dist/utils/constants.d.ts +10 -0
  62. package/dist/utils/constants.js +13 -1
  63. package/dist/utils/diResolver.d.ts +32 -0
  64. package/dist/utils/diResolver.js +204 -0
  65. package/dist/utils/new_parts_of_migrationUtils.d.ts +0 -0
  66. package/dist/utils/new_parts_of_migrationUtils.js +164 -0
  67. package/dist/utils/typeUtils.d.ts +19 -0
  68. package/dist/utils/typeUtils.js +70 -0
  69. package/package.json +7 -3
@@ -37,389 +37,607 @@ exports.ControllerGenerator = void 0;
37
37
  const yaml_1 = require("yaml");
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
- const controllerTemplates_1 = require("./templates/controllerTemplates");
41
40
  const generationRegistry_1 = require("../utils/generationRegistry");
42
41
  const colors_1 = require("../utils/colors");
42
+ const configTypes_1 = require("../types/configTypes");
43
+ const childEntityUtils_1 = require("../utils/childEntityUtils");
44
+ const typeUtils_1 = require("../utils/typeUtils");
43
45
  const constants_1 = require("../utils/constants");
44
46
  class ControllerGenerator {
45
- hasPermissions(config) {
46
- if (config.modules) {
47
- return Object.values(config.modules).some(module => module.permissions && module.permissions.length > 0);
47
+ getHttpDecorator(method) {
48
+ switch (method.toUpperCase()) {
49
+ case 'GET': return 'Get';
50
+ case 'POST': return 'Post';
51
+ case 'PUT': return 'Put';
52
+ case 'PATCH': return 'Patch';
53
+ case 'DELETE': return 'Delete';
54
+ default: return 'Get';
48
55
  }
49
- const module = config;
50
- return !!(module.permissions && module.permissions.length > 0);
51
56
  }
52
- getActionPermissions(moduleName, moduleConfig) {
53
- if (!moduleConfig.permissions || !moduleConfig.actions) {
54
- return {};
55
- }
56
- const actionPermissions = {};
57
- Object.keys(moduleConfig.actions).forEach(action => {
58
- actionPermissions[action] = [];
59
- });
60
- (moduleConfig.permissions || []).forEach(permission => {
61
- (permission.actions || []).forEach(action => {
62
- if (actionPermissions[action]) {
63
- actionPermissions[action].push(permission.role);
64
- }
65
- });
66
- });
67
- return actionPermissions;
68
- }
69
- shouldGenerateMethod(action, roles) {
70
- return !roles.includes('none');
57
+ parseUseCase(useCase) {
58
+ const [model, action] = useCase.split(':');
59
+ return { model, action };
71
60
  }
72
- needsUserParam(roles) {
73
- return roles.length > 0 && !roles.includes('all');
61
+ /**
62
+ * Normalize auth config to an array of roles
63
+ */
64
+ normalizeAuth(auth) {
65
+ if (!auth)
66
+ return [];
67
+ if (Array.isArray(auth))
68
+ return auth;
69
+ return [auth];
74
70
  }
75
- getHttpMethodName(_httpMethod, action) {
76
- return action;
71
+ /**
72
+ * Check if auth config includes owner permission
73
+ */
74
+ hasOwnerAuth(auth) {
75
+ const roles = this.normalizeAuth(auth);
76
+ return roles.includes(constants_1.AUTH_ROLES.OWNER);
77
77
  }
78
- getHttpDecorator(httpMethod) {
79
- switch ((httpMethod || 'GET').toUpperCase()) {
80
- case 'GET':
81
- return 'Get';
82
- case 'POST':
83
- return 'Post';
84
- case 'PUT':
85
- return 'Put';
86
- case 'PATCH':
87
- return 'Patch';
88
- case 'DELETE':
89
- return 'Delete';
90
- default:
91
- return 'Get';
78
+ /**
79
+ * Generate pre-fetch authentication/authorization check code.
80
+ * This runs before fetching the entity and validates authentication and role-based access.
81
+ * @param auth - The auth requirement: 'all', 'authenticated', 'owner', role names, or array of roles
82
+ * @returns Code string for the auth check, or empty string if no check needed
83
+ */
84
+ generateAuthCheck(auth) {
85
+ const roles = this.normalizeAuth(auth);
86
+ if (roles.length === 0 || (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.ALL)) {
87
+ return ''; // No check needed - public access
92
88
  }
93
- }
94
- getMethodCallParams(action, entityName) {
95
- const dtoType = entityName ? `${entityName}DTO` : 'any';
96
- switch (action) {
97
- case 'list':
98
- return 'page, limit';
99
- case 'get':
100
- case 'getById':
101
- return 'id';
102
- case 'create':
103
- return `context.request.body as ${dtoType}`;
104
- case 'update':
105
- return `id, context.request.body as ${dtoType}`;
106
- case 'delete':
107
- return 'id';
108
- default:
109
- return '/* custom params */';
89
+ // If only 'authenticated' is specified
90
+ if (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.AUTHENTICATED) {
91
+ return `if (!context.request.user) {
92
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
93
+ }`;
110
94
  }
111
- }
112
- generateParameterExtractions(action) {
113
- switch (action) {
114
- case 'list':
115
- return `
116
- // Extract pagination from URL parameters
117
- const page = parseInt(context.request.parameters.page as string) || 1;
118
- const limit = parseInt(context.request.parameters.limit as string) || 10;`;
119
- case 'get':
120
- case 'getById':
121
- case 'update':
122
- case 'delete':
123
- return `
124
- const id = parseInt(context.request.parameters.id as string);
125
- if (isNaN(id)) {
126
- throw new Error('Invalid ID parameter');
95
+ // If only 'owner' is specified - just require authentication here
96
+ // (owner check happens post-fetch)
97
+ if (roles.length === 1 && roles[0] === constants_1.AUTH_ROLES.OWNER) {
98
+ return `if (!context.request.user) {
99
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
127
100
  }`;
128
- case 'create':
129
- return ''; // No additional parameter extraction needed for create
130
- default:
131
- return '';
132
101
  }
102
+ // Filter out 'owner' and 'all' for role checks (owner is checked post-fetch)
103
+ const roleChecks = roles.filter(r => r !== constants_1.AUTH_ROLES.OWNER && r !== constants_1.AUTH_ROLES.ALL && r !== constants_1.AUTH_ROLES.AUTHENTICATED);
104
+ const hasOwner = roles.includes(constants_1.AUTH_ROLES.OWNER);
105
+ const hasAuthenticated = roles.includes(constants_1.AUTH_ROLES.AUTHENTICATED);
106
+ // If we have role checks or owner, we need authentication
107
+ if (roleChecks.length > 0 || hasOwner) {
108
+ if (roleChecks.length === 0) {
109
+ // Only owner (and maybe authenticated) - just require auth
110
+ return `if (!context.request.user) {
111
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
112
+ }`;
113
+ }
114
+ if (roleChecks.length === 1 && !hasOwner) {
115
+ // Single role check
116
+ return `if (!context.request.user) {
117
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
133
118
  }
134
- generateMethodImplementation(action, entityName, hasUserParam, actions) {
135
- // Check if we have action handlers
136
- if (actions && actions[action] && actions[action].handlers) {
137
- const handlers = actions[action].handlers;
138
- const entityLower = entityName.toLowerCase();
139
- // Filter handlers that apply to this model
140
- const modelHandlers = handlers.filter(h => h.startsWith(`${entityLower}:`) || h.startsWith(`${entityName}:`));
141
- if (modelHandlers.length === 0) {
142
- return `// TODO: No valid handlers found for action ${action}. Use ${entityName}:default:${action} or ${entityName}:customMethodName format.`;
119
+ if (context.request.user.role !== '${roleChecks[0]}') {
120
+ throw new Error('${constants_1.AUTH_ERRORS.INSUFFICIENT_PERMISSIONS}: ${roleChecks[0]} role required');
121
+ }`;
143
122
  }
144
- const userExtraction = hasUserParam ? controllerTemplates_1.controllerTemplates.userExtraction : '';
145
- const userParam = hasUserParam ? ', user' : '';
146
- // Generate parameter extraction based on action type
147
- const paramExtractions = this.generateParameterExtractions(action);
148
- // Generate step-by-step calls for each handler
149
- const handlerCalls = modelHandlers.map((handler, index) => {
150
- const parts = handler.split(':');
151
- let methodName;
152
- let params;
153
- if (parts.length === 3 && parts[1] === 'default') {
154
- // Format: modelname:default:action
155
- const actionName = parts[2];
156
- methodName = actionName === 'getById' ? 'get' : actionName; // Use 'get' instead of 'getById'
157
- // For default handlers, use extracted parameters
158
- params = this.getMethodCallParams(actionName, entityName) + userParam;
159
- }
160
- else if (parts.length === 2) {
161
- // Format: modelname:custommethod
162
- methodName = parts[1];
163
- // For custom handlers, pass result from previous handler (or null) and context
164
- const prevResult = index === 0 ? 'null' : `result${index}`;
165
- params = `${prevResult}, context${userParam}`;
166
- }
167
- else {
168
- return `// Invalid handler format: ${handler}`;
169
- }
170
- const isLast = index === modelHandlers.length - 1;
171
- const resultVar = isLast ? 'result' : `result${index + 1}`;
172
- return `const ${resultVar} = await this.${entityLower}Service.${methodName}(${params});`;
173
- }).join('\n ');
174
- // For multiple handlers, return the last result
175
- const returnStatement = '\n return result;';
176
- return `${userExtraction}${paramExtractions}
177
- ${handlerCalls}${returnStatement}`;
123
+ // Multiple roles OR owner - use OR logic
124
+ // If owner is included, we can't fully check here (post-fetch), so we just require auth
125
+ // and mark that owner check should happen later
126
+ if (hasOwner) {
127
+ // With owner: require auth, role check will be combined with owner check post-fetch
128
+ return `if (!context.request.user) {
129
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
130
+ }`;
131
+ }
132
+ // Multiple roles without owner - check if user has ANY of the roles
133
+ const roleConditions = roleChecks.map(r => `context.request.user.role === '${r}'`).join(' || ');
134
+ return `if (!context.request.user) {
135
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
136
+ }
137
+ if (!(${roleConditions})) {
138
+ throw new Error('${constants_1.AUTH_ERRORS.INSUFFICIENT_PERMISSIONS}: one of [${roleChecks.join(', ')}] role required');
139
+ }`;
178
140
  }
179
- // Fallback to existing template lookup
180
- const template = controllerTemplates_1.controllerTemplates.methodImplementations[action];
181
- if (!template) {
182
- return ` // TODO: Implement ${action} method\n return {} as any;`;
141
+ // Only 'authenticated' in the mix
142
+ if (hasAuthenticated) {
143
+ return `if (!context.request.user) {
144
+ throw new Error('${constants_1.AUTH_ERRORS.REQUIRED}');
145
+ }`;
183
146
  }
184
- const userExtraction = hasUserParam ? controllerTemplates_1.controllerTemplates.userExtraction : '';
185
- const userParam = hasUserParam ? ', user' : '';
186
- return template
187
- .replace(/{{ENTITY_NAME}}/g, entityName)
188
- .replace(/{{ENTITY_LOWER}}/g, entityName.toLowerCase())
189
- .replace(/{{USER_EXTRACTION}}/g, userExtraction)
190
- .replace(/{{USER_PARAM}}/g, userParam);
191
- }
192
- replaceTemplateVars(template, variables) {
193
- let result = template;
194
- Object.entries(variables).forEach(([key, value]) => {
195
- const regex = new RegExp(`{{${key}}}`, 'g');
196
- result = result.replace(regex, String(value));
197
- });
198
- return result;
199
- }
200
- generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind, actions) {
201
- const methodName = kind === 'web' ? this.getWebMethodName(endpoint) : this.getHttpMethodName(endpoint.method, endpoint.action);
202
- const httpDecorator = kind === 'web' ? 'Get' : this.getHttpDecorator(endpoint.method);
203
- const needsUser = this.needsUserParam(roles);
204
- const methodImplementation = this.generateMethodImplementation(endpoint.action, entityName, needsUser, actions);
205
- const returnType = this.getReturnType(endpoint.action, entityName);
206
- const renderDecorator = kind === 'web' && endpoint.view
207
- ? `\n @Render("${endpoint.view}"${endpoint.layout ? `, "${endpoint.layout}"` : ', "main_view"'})`
208
- : '';
209
- const variables = {
210
- METHOD_NAME: methodName,
211
- HTTP_DECORATOR: httpDecorator,
212
- ENDPOINT_PATH: endpoint.path,
213
- METHOD_IMPLEMENTATION: methodImplementation,
214
- RETURN_TYPE: returnType,
215
- RENDER_DECORATOR: renderDecorator
216
- };
217
- return this.replaceTemplateVars(controllerTemplates_1.controllerTemplates.controllerMethod, variables);
147
+ return '';
218
148
  }
219
- getWebMethodName(endpoint) {
220
- const base = this.getHttpMethodName(endpoint.method, endpoint.action);
221
- const suffix = this.getPathSuffix(endpoint.path);
222
- return `${base}${suffix}`;
149
+ /**
150
+ * Generate post-fetch authorization check for owner validation.
151
+ * This runs after fetching the entity and validates ownership.
152
+ * Used for READ operations (get, list) where we check after fetch.
153
+ * For child entities, uses getResourceOwner() since result has no ownerId.
154
+ */
155
+ generatePostFetchOwnerCheck(auth, resultVar = 'result', useCaseVar, childInfo) {
156
+ const roles = this.normalizeAuth(auth);
157
+ if (!roles.includes(constants_1.AUTH_ROLES.OWNER)) {
158
+ return ''; // No owner check needed
159
+ }
160
+ const bypassRoles = roles.filter(r => r !== constants_1.AUTH_ROLES.OWNER && r !== constants_1.AUTH_ROLES.ALL && r !== constants_1.AUTH_ROLES.AUTHENTICATED);
161
+ // Child entities don't have ownerId on result; resolve via getResourceOwner
162
+ if (childInfo && useCaseVar) {
163
+ if (bypassRoles.length === 0) {
164
+ return `
165
+ // Owner validation (post-fetch for reads, via parent)
166
+ const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(${resultVar}.id);
167
+ if (resourceOwnerId === null) {
168
+ throw new Error('Resource not found');
223
169
  }
224
- getPathSuffix(routePath) {
225
- if (!routePath || routePath === '/')
226
- return 'Index';
227
- const segments = routePath.split('/').filter(Boolean);
228
- const parts = segments.map(seg => {
229
- if (seg.startsWith(':')) {
230
- const name = seg.slice(1);
231
- return 'By' + name.charAt(0).toUpperCase() + name.slice(1);
170
+ if (resourceOwnerId !== context.request.user?.id) {
171
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
172
+ }`;
232
173
  }
233
- const cleaned = seg.replace(/[^a-zA-Z0-9]+/g, ' ')
234
- .split(' ')
235
- .filter(Boolean)
236
- .map(s => s.charAt(0).toUpperCase() + s.slice(1))
237
- .join('');
238
- return cleaned || 'Index';
239
- });
240
- return parts.join('');
174
+ const bypassConditions = bypassRoles.map(r => `context.request.user?.role === '${r}'`).join(' || ');
175
+ return `
176
+ // Owner validation (post-fetch for reads, via parent, bypassed for: ${bypassRoles.join(', ')})
177
+ const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(${resultVar}.id);
178
+ if (resourceOwnerId === null) {
179
+ throw new Error('Resource not found');
241
180
  }
242
- getReturnType(action, entityName) {
243
- switch (action) {
244
- case 'list':
245
- return `${entityName}[]`;
246
- case 'get':
247
- case 'create':
248
- case 'update':
249
- case 'empty':
250
- return entityName;
251
- case 'delete':
252
- return '{ success: boolean; message: string }';
253
- default:
254
- return 'any';
181
+ const isOwner = resourceOwnerId === context.request.user?.id;
182
+ const hasPrivilegedRole = ${bypassConditions};
183
+ if (!isOwner && !hasPrivilegedRole) {
184
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
185
+ }`;
186
+ }
187
+ // Root entity: result has ownerId
188
+ if (bypassRoles.length === 0) {
189
+ return `
190
+ // Owner validation (post-fetch for reads)
191
+ if (${resultVar}.ownerId !== context.request.user?.id) {
192
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
193
+ }`;
255
194
  }
195
+ const bypassConditions = bypassRoles.map(r => `context.request.user?.role === '${r}'`).join(' || ');
196
+ return `
197
+ // Owner validation (post-fetch for reads, bypassed for: ${bypassRoles.join(', ')})
198
+ const isOwner = ${resultVar}.ownerId === context.request.user?.id;
199
+ const hasPrivilegedRole = ${bypassConditions};
200
+ if (!isOwner && !hasPrivilegedRole) {
201
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
202
+ }`;
256
203
  }
257
204
  /**
258
- * Generate controller for a single config (api or routes)
205
+ * Generate pre-mutation authorization check for owner validation.
206
+ * This runs BEFORE the mutation to prevent unauthorized changes.
207
+ * Used for WRITE operations (update, delete).
208
+ * @param auth - The auth requirement
209
+ * @param useCaseVar - The use case variable name
210
+ * @returns Code string for the pre-mutation owner check, or empty string if not needed
259
211
  */
260
- generateControllerForModelWithConfig(model, moduleName, moduleConfig, cfg, hasGlobalPermissions, kind) {
261
- var _a;
262
- const isApi = kind === 'api';
263
- const entityName = model.name;
264
- const entityLower = entityName.toLowerCase();
265
- // Determine if we should generate a controller for this model
266
- const configModel = (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
267
- const topLevelMatches = !configModel || configModel === entityName || configModel.toLowerCase() === entityLower;
268
- // Also check if any endpoints specifically target this model (Option A)
269
- const hasEndpointForThisModel = ((_a = cfg === null || cfg === void 0 ? void 0 : cfg.endpoints) === null || _a === void 0 ? void 0 : _a.some(endpoint => {
270
- const endpointModel = endpoint.model || configModel;
271
- return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
272
- })) || false;
273
- const shouldGenerateForThisModel = topLevelMatches || hasEndpointForThisModel;
274
- if (!shouldGenerateForThisModel) {
275
- return '';
212
+ generatePreMutationOwnerCheck(auth, useCaseVar = 'useCase') {
213
+ const roles = this.normalizeAuth(auth);
214
+ if (!roles.includes(constants_1.AUTH_ROLES.OWNER)) {
215
+ return ''; // No owner check needed
276
216
  }
277
- const controllerBase = (cfg.prefix || `/${isApi ? 'api/' : ''}${entityLower}`).replace(/\/$/, '');
278
- const actionPermissions = this.getActionPermissions(moduleName, moduleConfig);
279
- const hasPermissions = hasGlobalPermissions && !!(moduleConfig.permissions && moduleConfig.permissions.length > 0);
280
- // Filter endpoints that apply to this model (Option A)
281
- const modelEndpoints = (cfg.endpoints || []).filter(endpoint => {
282
- const endpointModel = endpoint.model || (cfg === null || cfg === void 0 ? void 0 : cfg.model) || (moduleConfig.models && moduleConfig.models[0] ? moduleConfig.models[0].name : null);
283
- return endpointModel === entityName || (endpointModel === null || endpointModel === void 0 ? void 0 : endpointModel.toLowerCase()) === entityLower;
284
- });
285
- const controllerMethods = modelEndpoints
286
- .filter(endpoint => {
287
- const roles = actionPermissions[endpoint.action] || [];
288
- return this.shouldGenerateMethod(endpoint.action, roles);
289
- })
290
- .map(endpoint => {
291
- const roles = actionPermissions[endpoint.action] || [];
292
- return this.generateControllerMethod(endpoint, entityName, roles, hasPermissions, kind, moduleConfig.actions);
293
- })
294
- .join('\n\n');
295
- return controllerMethods;
217
+ // Get non-owner roles for the bypass check
218
+ const bypassRoles = roles.filter(r => r !== constants_1.AUTH_ROLES.OWNER && r !== constants_1.AUTH_ROLES.ALL && r !== constants_1.AUTH_ROLES.AUTHENTICATED);
219
+ if (bypassRoles.length === 0) {
220
+ // Only owner - strict ownership check
221
+ return `
222
+ // Pre-mutation owner validation
223
+ const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(input.id);
224
+ if (resourceOwnerId === null) {
225
+ throw new Error('Resource not found');
296
226
  }
297
- /**
298
- * Generate controller for a model (handles both single config and array) - Option D
299
- */
300
- generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, kind) {
301
- const isApi = kind === 'api';
302
- const cfgRaw = isApi ? moduleConfig.api : moduleConfig.routes;
303
- const entityName = model.name;
304
- const entityLower = entityName.toLowerCase();
305
- // Support both single config and array (Option D)
306
- let configs = [];
307
- if (!cfgRaw) {
308
- // No config - generate defaults for web routes only
309
- if (!isApi) {
310
- configs = [{
311
- prefix: `/${entityLower}`,
312
- endpoints: [
313
- { path: '/', action: 'list', method: 'GET', view: `${entityLower}List` },
314
- { path: '/:id', action: 'get', method: 'GET', view: `${entityLower}Detail` },
315
- { path: '/create', action: 'empty', method: 'GET', view: `${entityLower}Create` },
316
- { path: '/:id/edit', action: 'get', method: 'GET', view: `${entityLower}Update` }
317
- ]
318
- }];
227
+ if (resourceOwnerId !== context.request.user?.id) {
228
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
229
+ }
230
+ `;
231
+ }
232
+ // Owner OR other roles - bypass if user has a privileged role
233
+ const bypassConditions = bypassRoles.map(r => `context.request.user?.role === '${r}'`).join(' || ');
234
+ return `
235
+ // Pre-mutation owner validation (bypassed for: ${bypassRoles.join(', ')})
236
+ const resourceOwnerId = await this.${useCaseVar}.getResourceOwner(input.id);
237
+ if (resourceOwnerId === null) {
238
+ throw new Error('Resource not found');
239
+ }
240
+ const isOwner = resourceOwnerId === context.request.user?.id;
241
+ const hasPrivilegedRole = ${bypassConditions};
242
+ if (!isOwner && !hasPrivilegedRole) {
243
+ throw new Error('${constants_1.AUTH_ERRORS.ACCESS_DENIED}');
244
+ }
245
+ `;
246
+ }
247
+ generateApiEndpointMethod(endpoint, resourceName, childInfo) {
248
+ const { model, action } = this.parseUseCase(endpoint.useCase);
249
+ const methodName = action;
250
+ const decorator = this.getHttpDecorator(endpoint.method);
251
+ const useCaseVar = `${model.toLowerCase()}UseCase`;
252
+ const inputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Input`;
253
+ const outputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Output`;
254
+ const dtoImports = new Set();
255
+ dtoImports.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
256
+ // Generate auth check (pre-fetch)
257
+ const authCheck = this.generateAuthCheck(endpoint.auth);
258
+ const authLine = authCheck ? `\n ${authCheck}\n` : '';
259
+ // Build parsing logic
260
+ // For create: root gets ownerId from user, child gets parentId from URL params
261
+ let parseLogic;
262
+ if (action === 'list') {
263
+ parseLogic = `const input = ${inputClass}.parse(context.request.parameters);`;
264
+ }
265
+ else if (action === 'get' || action === 'delete') {
266
+ parseLogic = `const input = ${inputClass}.parse({ id: context.request.parameters.id });`;
267
+ }
268
+ else if (action === 'create') {
269
+ if (childInfo) {
270
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} });`;
319
271
  }
320
272
  else {
321
- return '';
273
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id });`;
322
274
  }
323
275
  }
324
- else if (Array.isArray(cfgRaw)) {
325
- configs = cfgRaw;
276
+ else if (action === 'update') {
277
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, id: context.request.parameters.id });`;
278
+ }
279
+ else {
280
+ parseLogic = `const input = ${inputClass}.parse(context.request.body || {});`;
281
+ }
282
+ // Generate owner checks:
283
+ // - For mutations (update, delete): PRE-mutation check (before operation)
284
+ // - For reads (get): POST-fetch check (after fetching)
285
+ const hasOwner = this.hasOwnerAuth(endpoint.auth);
286
+ const isMutation = action === 'update' || action === 'delete';
287
+ const isRead = action === 'get';
288
+ // Pre-mutation owner check for write operations
289
+ const preMutationOwnerCheck = (hasOwner && isMutation)
290
+ ? this.generatePreMutationOwnerCheck(endpoint.auth, useCaseVar)
291
+ : '';
292
+ // Post-fetch owner check for read operations only
293
+ const postFetchOwnerCheck = (hasOwner && isRead)
294
+ ? this.generatePostFetchOwnerCheck(endpoint.auth, 'result', useCaseVar, childInfo)
295
+ : '';
296
+ // Generate output transformation based on action
297
+ let outputTransform;
298
+ if (action === 'list') {
299
+ outputTransform = `return ${outputClass}.from(result);`;
300
+ }
301
+ else if (action === 'delete') {
302
+ outputTransform = `return result;`;
326
303
  }
327
304
  else {
328
- configs = [cfgRaw];
305
+ outputTransform = `return ${outputClass}.from(result);`;
306
+ }
307
+ const method = ` @${decorator}('${endpoint.path}')
308
+ async ${methodName}(context: IContext): Promise<any> {${authLine}
309
+ ${parseLogic}${preMutationOwnerCheck}
310
+ const result = await this.${useCaseVar}.${action}(input);${postFetchOwnerCheck}
311
+ ${outputTransform}
312
+ }`;
313
+ return { method, dtoImports };
314
+ }
315
+ generateWebPageMethod(page, resourceName, layout, methodIndex, childInfo, withChildChildren) {
316
+ const method = page.method || 'GET';
317
+ const decorator = this.getHttpDecorator(method);
318
+ const dtoImports = new Set();
319
+ // Generate unique method name by appending method type for POST routes
320
+ const pathSegments = page.path.split('/').filter(Boolean);
321
+ let baseMethodName = pathSegments.length === 0
322
+ ? 'index'
323
+ : pathSegments.map((seg, idx) => {
324
+ if (seg.startsWith(':')) {
325
+ return 'By' + (0, typeUtils_1.capitalize)(seg.slice(1));
326
+ }
327
+ return idx === 0 ? seg : (0, typeUtils_1.capitalize)(seg);
328
+ }).join('');
329
+ // Append method suffix for POST to avoid duplicates
330
+ const methodName = method === 'POST' ? `${baseMethodName}Submit` : baseMethodName;
331
+ // Generate auth check (pre-fetch)
332
+ const authCheck = this.generateAuthCheck(page.auth);
333
+ const authLine = authCheck ? `\n ${authCheck}\n` : '';
334
+ // For GET requests with views (display pages)
335
+ if (method === 'GET' && page.view) {
336
+ const renderDecorator = `\n @Render("${page.view}", "${layout}")`;
337
+ if (page.useCase) {
338
+ const { model, action } = this.parseUseCase(page.useCase);
339
+ const useCaseVar = `${model.toLowerCase()}UseCase`;
340
+ const inputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Input`;
341
+ dtoImports.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
342
+ let parseLogic;
343
+ if (page.path.includes(':id')) {
344
+ parseLogic = `const input = ${inputClass}.parse({ id: context.request.parameters.id });`;
345
+ }
346
+ else if (action === 'list') {
347
+ parseLogic = `const input = ${inputClass}.parse(context.request.parameters);`;
348
+ }
349
+ else {
350
+ parseLogic = `const input = ${inputClass}.parse({});`;
351
+ }
352
+ // Generate post-fetch owner check for GET pages (reads only)
353
+ const hasOwner = this.hasOwnerAuth(page.auth);
354
+ const isReadAction = action === 'get' || action === 'list';
355
+ const postFetchOwnerCheck = (hasOwner && isReadAction)
356
+ ? this.generatePostFetchOwnerCheck(page.auth, 'result', useCaseVar, childInfo)
357
+ : '';
358
+ // For get + withChild: load child entities and merge into result for template
359
+ const loadChildBlocks = [];
360
+ let returnExpr;
361
+ if (childInfo) {
362
+ returnExpr = `{ ...result, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} }`;
363
+ }
364
+ else if ((withChildChildren === null || withChildChildren === void 0 ? void 0 : withChildChildren.length) && action === 'get') {
365
+ const childKeys = [];
366
+ for (const child of withChildChildren) {
367
+ const childVar = child.childEntityName.charAt(0).toLowerCase() + child.childEntityName.slice(1);
368
+ const childItemsKey = `${childVar}Items`;
369
+ childKeys.push(childItemsKey);
370
+ loadChildBlocks.push(`const ${childItemsKey} = await this.${childVar}Service.listByParent(result.id);`);
371
+ }
372
+ returnExpr = `{ ...result, ${childKeys.map(k => `${k}: ${k}`).join(', ')} }`;
373
+ }
374
+ else {
375
+ returnExpr = 'result';
376
+ }
377
+ const loadChildCode = loadChildBlocks.length ? '\n ' + loadChildBlocks.join('\n ') + '\n ' : '';
378
+ const methodCode = `${renderDecorator}
379
+ @${decorator}('${page.path}')
380
+ async ${methodName}(context: IContext): Promise<any> {${authLine}
381
+ ${parseLogic}
382
+ const result = await this.${useCaseVar}.${action}(input);${postFetchOwnerCheck}${loadChildCode}
383
+ return ${returnExpr};
384
+ }`;
385
+ return { method: methodCode, dtoImports };
386
+ }
387
+ else {
388
+ // No use case - just render view (e.g. create form)
389
+ // Child entities need the parent ID from URL params for link rendering
390
+ const emptyFormData = childInfo
391
+ ? `{ formData: {}, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} }`
392
+ : '{ formData: {} }';
393
+ const methodCode = `${renderDecorator}
394
+ @${decorator}('${page.path}')
395
+ async ${methodName}(context: IContext): Promise<any> {${authLine}
396
+ return ${emptyFormData};
397
+ }`;
398
+ return { method: methodCode, dtoImports };
399
+ }
400
+ }
401
+ else if (method === 'POST' && page.useCase) {
402
+ // POST request - form submission
403
+ const { model, action } = this.parseUseCase(page.useCase);
404
+ const useCaseVar = `${model.toLowerCase()}UseCase`;
405
+ const inputClass = `${model}${(0, typeUtils_1.capitalize)(action)}Input`;
406
+ dtoImports.add(`${model}${(0, typeUtils_1.capitalize)(action)}`);
407
+ let parseLogic;
408
+ if (page.path.includes(':id')) {
409
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, id: context.request.parameters.id });`;
410
+ }
411
+ else if (action === 'create') {
412
+ if (childInfo) {
413
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ${childInfo.parentIdField}: context.request.parameters.${childInfo.parentIdField} });`;
414
+ }
415
+ else {
416
+ parseLogic = `const input = ${inputClass}.parse({ ...context.request.body, ownerId: context.request.user?.id });`;
417
+ }
418
+ }
419
+ else {
420
+ parseLogic = `const input = ${inputClass}.parse(context.request.body);`;
421
+ }
422
+ // Generate PRE-mutation owner check for update/delete actions (POST requests)
423
+ const hasOwner = this.hasOwnerAuth(page.auth);
424
+ const isMutation = action === 'update' || action === 'delete';
425
+ const preMutationOwnerCheck = (hasOwner && isMutation)
426
+ ? this.generatePreMutationOwnerCheck(page.auth, useCaseVar)
427
+ : '';
428
+ // Handle onSuccess and onError strategies
429
+ const onSuccessHandler = this.generateOnSuccessHandler(page);
430
+ const onErrorHandler = this.generateOnErrorHandler(page);
431
+ const methodCode = ` @${decorator}('${page.path}')
432
+ async ${methodName}(context: IContext): Promise<any> {${authLine}
433
+ try {
434
+ ${parseLogic}${preMutationOwnerCheck}
435
+ const result = await this.${useCaseVar}.${action}(input);
436
+ ${onSuccessHandler}
437
+ return { success: true, data: result };
438
+ } catch (error) {
439
+ ${onErrorHandler}
440
+ throw error;
441
+ }
442
+ }`;
443
+ return { method: methodCode, dtoImports };
444
+ }
445
+ const methodCode = ` @${decorator}('${page.path}')
446
+ async ${methodName}(context: IContext): Promise<any> {
447
+ // TODO: Implement ${methodName}
448
+ return {};
449
+ }`;
450
+ return { method: methodCode, dtoImports };
451
+ }
452
+ generateOnSuccessHandler(page) {
453
+ if (!page.onSuccess)
454
+ return '// Success';
455
+ const handlers = [];
456
+ if (page.onSuccess.toast) {
457
+ handlers.push(`// Toast: ${page.onSuccess.toast}`);
458
+ }
459
+ if (page.onSuccess.redirect) {
460
+ const redirectPath = page.onSuccess.redirect.replace(':id', '${result.id}');
461
+ handlers.push(`// Redirect: ${redirectPath}`);
462
+ }
463
+ if (page.onSuccess.back) {
464
+ handlers.push('// Navigate back');
329
465
  }
330
- // For web routes, force GET method
331
- if (!isApi) {
332
- configs = configs.map(cfg => ({
333
- ...cfg,
334
- endpoints: (cfg.endpoints || []).map(e => ({ ...e, method: 'GET' }))
335
- }));
466
+ return handlers.join('\n ') || '// Success';
467
+ }
468
+ generateOnErrorHandler(page) {
469
+ if (!page.onError)
470
+ return '// Error occurred';
471
+ const handlers = [];
472
+ if (page.onError.stay) {
473
+ handlers.push('// Stay on page');
336
474
  }
337
- // Generate methods from all configs
338
- const allMethods = configs
339
- .map(cfg => this.generateControllerForModelWithConfig(model, moduleName, moduleConfig, cfg, hasGlobalPermissions, kind))
340
- .filter(methods => methods.trim() !== '')
341
- .join('\n\n');
342
- if (!allMethods.trim()) {
343
- return '';
475
+ if (page.onError.toast) {
476
+ handlers.push(`// Error toast: ${page.onError.toast}`);
344
477
  }
345
- // Use the first config for controller base path (or default)
346
- const firstConfig = configs[0];
347
- const controllerBase = ((firstConfig === null || firstConfig === void 0 ? void 0 : firstConfig.prefix) || `/${isApi ? 'api/' : ''}${entityLower}`).replace(/\/$/, '');
348
- const controllerClass = this.replaceTemplateVars(controllerTemplates_1.controllerTemplates.controllerClass, {
349
- CONTROLLER_NAME: `${entityName}${isApi ? 'ApiController' : 'WebController'}`,
350
- ENTITY_NAME: entityName,
351
- ENTITY_LOWER: entityLower,
352
- CONTROLLER_BASE: controllerBase,
353
- CONTROLLER_METHODS: allMethods
478
+ return handlers.join('\n ') || '// Error occurred';
479
+ }
480
+ /**
481
+ * Sort routes so static paths are registered before parameterized ones.
482
+ * This prevents parameterized routes (e.g. /:id) from catching requests
483
+ * meant for static routes (e.g. /create).
484
+ */
485
+ sortRoutesBySpecificity(routes) {
486
+ return [...routes].sort((a, b) => {
487
+ const aSegments = a.path.split('/').filter(Boolean);
488
+ const bSegments = b.path.split('/').filter(Boolean);
489
+ const aParamCount = aSegments.filter(s => s.startsWith(':')).length;
490
+ const bParamCount = bSegments.filter(s => s.startsWith(':')).length;
491
+ return aParamCount - bParamCount;
354
492
  });
355
- return this.replaceTemplateVars(controllerTemplates_1.controllerFileTemplate, {
356
- ENTITY_NAME: entityName,
357
- JWT_IMPORT: '',
358
- CONTROLLER_CLASS: controllerClass
493
+ }
494
+ generateApiController(resourceName, prefix, endpoints, childInfo) {
495
+ const controllerName = `${resourceName}ApiController`;
496
+ // Determine which use cases and DTOs are referenced
497
+ const useCaseModels = new Set();
498
+ const allDtoImports = new Set();
499
+ const methods = [];
500
+ const sortedEndpoints = this.sortRoutesBySpecificity(endpoints);
501
+ sortedEndpoints.forEach(endpoint => {
502
+ const { model } = this.parseUseCase(endpoint.useCase);
503
+ useCaseModels.add(model);
504
+ const { method, dtoImports } = this.generateApiEndpointMethod(endpoint, resourceName, childInfo);
505
+ methods.push(method);
506
+ dtoImports.forEach(d => allDtoImports.add(d));
359
507
  });
508
+ // Generate imports
509
+ const useCaseImports = Array.from(useCaseModels)
510
+ .map(model => `import { ${model}UseCase } from '../../application/useCases/${model}UseCase';`)
511
+ .join('\n');
512
+ const dtoImportStatements = Array.from(allDtoImports)
513
+ .map(dto => `import { ${dto}Input, ${dto}Output } from '../../application/dto/${dto}';`)
514
+ .join('\n');
515
+ // Generate constructor parameters
516
+ const constructorParams = Array.from(useCaseModels)
517
+ .map(model => `private ${model.toLowerCase()}UseCase: ${model}UseCase`)
518
+ .join(',\n ');
519
+ return `import { Controller, Get, Post, Put, Delete, type IContext } from '@currentjs/router';
520
+ ${useCaseImports}
521
+ ${dtoImportStatements}
522
+
523
+ @Controller('${prefix}')
524
+ export class ${controllerName} {
525
+ constructor(
526
+ ${constructorParams}
527
+ ) {}
528
+
529
+ ${methods.join('\n\n')}
530
+ }`;
360
531
  }
361
- generateController(moduleName, moduleConfig, hasGlobalPermissions, kind) {
362
- // Legacy method for backward compatibility - use first model
363
- if (!moduleConfig.models || moduleConfig.models.length === 0) {
364
- return '';
532
+ generateWebController(resourceName, prefix, layout, pages, config, childInfo) {
533
+ const controllerName = `${resourceName}WebController`;
534
+ // Child entities of this resource (for withChild). Only root entities can have withChild.
535
+ const withChildChildren = childInfo ? [] : (0, childEntityUtils_1.getChildrenOfParent)(config, resourceName);
536
+ // Determine which use cases and DTOs are referenced
537
+ const useCaseModels = new Set();
538
+ const allDtoImports = new Set();
539
+ const methods = [];
540
+ const sortedPages = this.sortRoutesBySpecificity(pages);
541
+ sortedPages.forEach((page, index) => {
542
+ var _a, _b;
543
+ if (page.useCase) {
544
+ const { model } = this.parseUseCase(page.useCase);
545
+ useCaseModels.add(model);
546
+ }
547
+ const { model, action } = page.useCase ? this.parseUseCase(page.useCase) : { model: '', action: '' };
548
+ const useCaseWithChild = model && action && ((_b = (_a = config.useCases[model]) === null || _a === void 0 ? void 0 : _a[action]) === null || _b === void 0 ? void 0 : _b.withChild) === true;
549
+ const withChildForThisPage = useCaseWithChild && action === 'get' && withChildChildren.length > 0 ? withChildChildren : undefined;
550
+ const { method, dtoImports } = this.generateWebPageMethod(page, resourceName, layout, index, childInfo, withChildForThisPage);
551
+ methods.push(method);
552
+ dtoImports.forEach(d => allDtoImports.add(d));
553
+ });
554
+ // Determine if any page actually uses withChild (only then inject child services)
555
+ const needsChildServices = withChildChildren.length > 0 && sortedPages.some(page => {
556
+ var _a, _b;
557
+ if (!page.useCase)
558
+ return false;
559
+ const { model: m, action: a } = this.parseUseCase(page.useCase);
560
+ return a === 'get' && ((_b = (_a = config.useCases[m]) === null || _a === void 0 ? void 0 : _a[a]) === null || _b === void 0 ? void 0 : _b.withChild) === true;
561
+ });
562
+ // Constructor: use cases + child entity services when withChild is actually used
563
+ const serviceImports = [];
564
+ const constructorParams = [];
565
+ Array.from(useCaseModels).forEach(model => {
566
+ constructorParams.push(`private ${model.toLowerCase()}UseCase: ${model}UseCase`);
567
+ });
568
+ if (needsChildServices) {
569
+ withChildChildren.forEach(child => {
570
+ const childVar = child.childEntityName.charAt(0).toLowerCase() + child.childEntityName.slice(1);
571
+ serviceImports.push(`import { ${child.childEntityName}Service } from '../../application/services/${child.childEntityName}Service';`);
572
+ constructorParams.push(`private ${childVar}Service: ${child.childEntityName}Service`);
573
+ });
365
574
  }
366
- return this.generateControllerForModel(moduleConfig.models[0], moduleName, moduleConfig, hasGlobalPermissions, kind);
575
+ const useCaseImports = Array.from(useCaseModels)
576
+ .map(model => `import { ${model}UseCase } from '../../application/useCases/${model}UseCase';`)
577
+ .join('\n');
578
+ const dtoImportStatements = Array.from(allDtoImports)
579
+ .map(dto => `import { ${dto}Input } from '../../application/dto/${dto}';`)
580
+ .join('\n');
581
+ const constructorBlock = constructorParams.length > 0
582
+ ? `constructor(
583
+ ${constructorParams.join(',\n ')}
584
+ ) {}`
585
+ : 'constructor() {}';
586
+ return `import { Controller, Get, Post, Render, type IContext } from '@currentjs/router';
587
+ ${useCaseImports}
588
+ ${serviceImports.join('\n')}
589
+ ${dtoImportStatements}
590
+
591
+ @Controller('${prefix}')
592
+ export class ${controllerName} {
593
+ ${constructorBlock}
594
+
595
+ ${methods.join('\n\n')}
596
+ }`;
367
597
  }
368
- generateFromYamlFile(yamlFilePath) {
369
- const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
370
- const config = (0, yaml_1.parse)(yamlContent);
598
+ generateFromConfig(config) {
371
599
  const result = {};
372
- const hasGlobalPermissions = this.hasPermissions(config);
373
- if (config.modules) {
374
- Object.entries(config.modules).forEach(([moduleName, moduleConfig]) => {
375
- if (moduleConfig.models && moduleConfig.models.length > 0) {
376
- // Generate controllers for each model
377
- moduleConfig.models.forEach(model => {
378
- const apiControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'api');
379
- if (apiControllerCode)
380
- result[`${model.name}Api`] = apiControllerCode;
381
- const webControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'web');
382
- if (webControllerCode)
383
- result[`${model.name}Web`] = webControllerCode;
384
- });
385
- }
600
+ const childEntityMap = (0, childEntityUtils_1.buildChildEntityMap)(config);
601
+ // Generate API controllers
602
+ if (config.api) {
603
+ Object.entries(config.api).forEach(([resourceName, resourceConfig]) => {
604
+ const childInfo = childEntityMap.get(resourceName);
605
+ const code = this.generateApiController(resourceName, resourceConfig.prefix, resourceConfig.endpoints, childInfo);
606
+ result[`${resourceName}Api`] = code;
386
607
  });
387
608
  }
388
- else {
389
- const moduleName = 'Module';
390
- const moduleConfig = config;
391
- if (moduleConfig.models && moduleConfig.models.length > 0) {
392
- // Generate controllers for each model
393
- moduleConfig.models.forEach(model => {
394
- const apiControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'api');
395
- if (apiControllerCode)
396
- result[`${model.name}Api`] = apiControllerCode;
397
- const webControllerCode = this.generateControllerForModel(model, moduleName, moduleConfig, hasGlobalPermissions, 'web');
398
- if (webControllerCode)
399
- result[`${model.name}Web`] = webControllerCode;
400
- });
401
- }
609
+ // Generate Web controllers
610
+ if (config.web) {
611
+ Object.entries(config.web).forEach(([resourceName, resourceConfig]) => {
612
+ const childInfo = childEntityMap.get(resourceName);
613
+ const code = this.generateWebController(resourceName, resourceConfig.prefix, resourceConfig.layout || 'main_view', resourceConfig.pages, config, childInfo);
614
+ result[`${resourceName}Web`] = code;
615
+ });
402
616
  }
403
617
  return result;
404
618
  }
405
- async generateAndSaveFiles(yamlFilePath = constants_1.COMMON_FILES.APP_YAML, outputDir = 'infrastructure', opts) {
619
+ generateFromYamlFile(yamlFilePath) {
406
620
  const yamlContent = fs.readFileSync(yamlFilePath, 'utf8');
407
621
  const config = (0, yaml_1.parse)(yamlContent);
408
- const hasGlobalPermissions = this.hasPermissions(config);
409
- const controllers = this.generateFromYamlFile(yamlFilePath);
410
- const generatedControllerPaths = [];
411
- const controllersDir = path.join(outputDir, 'controllers');
622
+ if (!(0, configTypes_1.isValidModuleConfig)(config)) {
623
+ throw new Error('Configuration does not match new module format. Expected domain/useCases/api/web structure.');
624
+ }
625
+ return this.generateFromConfig(config);
626
+ }
627
+ async generateAndSaveFiles(yamlFilePath, moduleDir, opts) {
628
+ const controllersByName = this.generateFromYamlFile(yamlFilePath);
629
+ const controllersDir = path.join(moduleDir, 'infrastructure', 'controllers');
412
630
  fs.mkdirSync(controllersDir, { recursive: true });
413
- for (const [moduleName, controllerCode] of Object.entries(controllers)) {
414
- const fileName = `${moduleName}Controller.ts`;
415
- const filePath = path.join(controllersDir, fileName);
631
+ const generatedPaths = [];
632
+ for (const [name, code] of Object.entries(controllersByName)) {
633
+ const filePath = path.join(controllersDir, `${name}Controller.ts`);
416
634
  // eslint-disable-next-line no-await-in-loop
417
- await (0, generationRegistry_1.writeGeneratedFile)(filePath, controllerCode, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
418
- generatedControllerPaths.push(filePath);
635
+ await (0, generationRegistry_1.writeGeneratedFile)(filePath, code, { force: !!(opts === null || opts === void 0 ? void 0 : opts.force), skipOnConflict: !!(opts === null || opts === void 0 ? void 0 : opts.skipOnConflict) });
636
+ generatedPaths.push(filePath);
419
637
  }
420
638
  // eslint-disable-next-line no-console
421
639
  console.log('\n' + colors_1.colors.green('Controller files generated successfully!') + '\n');
422
- return generatedControllerPaths;
640
+ return generatedPaths;
423
641
  }
424
642
  }
425
643
  exports.ControllerGenerator = ControllerGenerator;