@mikemajesty/microservice-crud 7.1.0 → 7.1.2

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 CHANGED
@@ -81,11 +81,18 @@ src/
81
81
  │ ├── controller.ts # REST controller with decorators
82
82
  │ ├── module.ts # NestJS module
83
83
  │ └── repository.ts # Repository implementation
84
- └── infra/
85
- └── database/
86
- └── [postgres|mongo]/
87
- └── schemas/
88
- └── [name].ts # Database schema
84
+ ├── infra/
85
+ └── database/
86
+ └── [postgres|mongo]/
87
+ └── schemas/
88
+ └── [name].ts # Database schema
89
+ └── docs/
90
+ └── src/
91
+ └── modules/
92
+ └── [name]/
93
+ ├── controller.tsp # TypeSpec API routes
94
+ ├── model.tsp # TypeSpec models/DTOs
95
+ └── exception.tsp # TypeSpec error definitions
89
96
  ```
90
97
 
91
98
  **Features:**
@@ -97,6 +104,7 @@ src/
97
104
  - ✅ Permission-based access control
98
105
  - ✅ Swagger documentation ready
99
106
  - ✅ Full test coverage
107
+ - ✅ **TypeSpec API documentation** (auto-generated!)
100
108
 
101
109
  ### LIB (Library Module)
102
110
 
@@ -161,9 +169,36 @@ The CLI automatically registers generated modules:
161
169
  - **CRUD/MODULE**: Registered in `src/app.module.ts`
162
170
  - **LIB**: Registered in `src/libs/module.ts` with `LibModule` suffix
163
171
  - **INFRA**: Registered in `src/infra/module.ts`
172
+ - **TypeSpec**: Auto-imports in `docs/src/main.tsp` (for CRUD templates)
164
173
 
165
174
  No manual imports needed! 🎉
166
175
 
176
+ ## TypeSpec Documentation
177
+
178
+ When generating CRUD modules (Postgres or Mongo), the CLI automatically creates TypeSpec documentation:
179
+
180
+ ```
181
+ docs/src/modules/[name]/
182
+ ├── controller.tsp # API routes with all endpoints
183
+ ├── model.tsp # Entity models and DTOs
184
+ └── exception.tsp # Error definitions (400, 404)
185
+ ```
186
+
187
+ The import is automatically added to `docs/src/main.tsp`:
188
+
189
+ ```typespec
190
+ import "./modules/[name]/controller.tsp";
191
+ ```
192
+
193
+ **Generated TypeSpec includes:**
194
+ - ✅ All CRUD endpoints (POST, PUT, GET, DELETE)
195
+ - ✅ Input/Output models
196
+ - ✅ Pagination models
197
+ - ✅ Validation error definitions
198
+ - ✅ Not found error definitions
199
+ - ✅ Bearer authentication
200
+ - ✅ API versioning support
201
+
167
202
  ## Name Validation
168
203
 
169
204
  Module names are automatically sanitized:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikemajesty/microservice-crud",
3
- "version": "7.1.0",
3
+ "version": "7.1.2",
4
4
  "description": "Monorepo CLI",
5
5
  "main": "src/cli.js",
6
6
  "type": "module",
package/src/cli.mjs CHANGED
@@ -12,9 +12,12 @@ import { getModuleLib } from './templates/libs/module.mjs';
12
12
  import { getServiceLib } from './templates/libs/service.mjs';
13
13
  import { getModuleControllerModule } from './templates/module/controller.mjs';
14
14
  import { getModuleModule } from './templates/module/module.mjs';
15
+ import { getTypeSpecController } from './templates/typespec/controller.mjs';
16
+ import { getTypeSpecModel } from './templates/typespec/model.mjs';
17
+ import { getTypeSpecException } from './templates/typespec/exception.mjs';
15
18
 
16
19
  import fs from 'fs';
17
- import { bold, green, red } from 'colorette';
20
+ import { bold, green, red, cyan, yellow, magenta, gray, blue, white } from 'colorette';
18
21
  import fse from 'fs-extra';
19
22
  import path from 'path';
20
23
  import cliSelect from 'cli-select';
@@ -27,6 +30,99 @@ const __dirname = dirname(__filename);
27
30
 
28
31
  const prompt = promptSync();
29
32
 
33
+ // Created files tracker
34
+ const createdFiles = [];
35
+
36
+ const trackFile = (filePath, category) => {
37
+ createdFiles.push({ path: filePath, category });
38
+ };
39
+
40
+ const getFileIcon = (fileName) => {
41
+ if (fileName.endsWith('.spec.ts')) return '🧪';
42
+ if (fileName.includes('entity')) return '📦';
43
+ if (fileName.includes('repository')) return '🗄️';
44
+ if (fileName.includes('use-cases') || fileName.includes('usecase')) return '⚡';
45
+ if (fileName.includes('controller')) return '🎮';
46
+ if (fileName.includes('module')) return '📦';
47
+ if (fileName.includes('adapter')) return '🔌';
48
+ if (fileName.includes('schema')) return '📋';
49
+ if (fileName.includes('service')) return '⚙️';
50
+ if (fileName.includes('.tsp')) return '📝';
51
+ if (fileName.includes('index')) return '📤';
52
+ return '📄';
53
+ };
54
+
55
+ const printCreatedFiles = (moduleName, moduleType) => {
56
+ console.log('');
57
+ console.log('');
58
+ console.log(bold(cyan(' ╭───────────────────────────────────────────────────────────╮')));
59
+ console.log(bold(cyan(' │ │')));
60
+ console.log(bold(cyan(' │')) + bold(green(' 🚀 ')) + bold(white(moduleType.toUpperCase() + ' CREATED SUCCESSFULLY!')) + bold(cyan(' │')));
61
+ console.log(bold(cyan(' │ │')));
62
+ console.log(bold(cyan(' ╰───────────────────────────────────────────────────────────╯')));
63
+ console.log('');
64
+ console.log(bold(white(' 📋 Module: ')) + bold(yellow(moduleName)));
65
+ console.log('');
66
+
67
+ const categories = {};
68
+ createdFiles.forEach(file => {
69
+ if (!categories[file.category]) categories[file.category] = [];
70
+ categories[file.category].push(file.path);
71
+ });
72
+
73
+ const categoryConfig = {
74
+ 'core/entity': { icon: '📦', label: 'Entity', color: magenta },
75
+ 'core/repository': { icon: '🗄️ ', label: 'Repository', color: blue },
76
+ 'core/use-cases': { icon: '⚡', label: 'Use Cases', color: yellow },
77
+ 'core/tests': { icon: '🧪', label: 'Tests', color: green },
78
+ 'modules': { icon: '🎮', label: 'Module Files', color: cyan },
79
+ 'schemas': { icon: '📋', label: 'Database Schema', color: magenta },
80
+ 'typespec': { icon: '📝', label: 'TypeSpec Docs', color: blue },
81
+ 'libs': { icon: '📚', label: 'Library Files', color: yellow },
82
+ 'infra': { icon: '🔧', label: 'Infrastructure', color: cyan },
83
+ };
84
+
85
+ // Define display order
86
+ const categoryOrder = [
87
+ 'core/entity', 'core/repository', 'core/use-cases', 'core/tests',
88
+ 'modules', 'schemas', 'typespec', 'libs', 'infra'
89
+ ];
90
+
91
+ const sortedCategories = categoryOrder.filter(cat => categories[cat]);
92
+
93
+ sortedCategories.forEach((category, catIndex) => {
94
+ const config = categoryConfig[category] || { icon: '📁', label: category, color: white };
95
+ const isLastCategory = catIndex === sortedCategories.length - 1;
96
+ const catPrefix = isLastCategory ? ' └─' : ' ├─';
97
+
98
+ console.log(gray(catPrefix) + ' ' + bold(config.color(`${config.icon} ${config.label}`)));
99
+
100
+ categories[category].forEach((file, index) => {
101
+ const isLast = index === categories[category].length - 1;
102
+ const linePrefix = isLastCategory ? ' ' : ' │ ';
103
+ const filePrefix = isLast ? '└──' : '├──';
104
+ const fileName = file.split('/').pop();
105
+ const icon = getFileIcon(fileName);
106
+ console.log(gray(`${linePrefix}${filePrefix}`) + ` ${icon} ` + white(fileName));
107
+ });
108
+ });
109
+
110
+ const totalFiles = createdFiles.length;
111
+ console.log('');
112
+ console.log(bold(cyan(' ─────────────────────────────────────────────────────────────')));
113
+ console.log('');
114
+ console.log(bold(green(` ✨ ${totalFiles} file${totalFiles > 1 ? 's' : ''} created successfully!`)));
115
+ console.log('');
116
+ console.log(gray(' 💡 Next steps:'));
117
+ console.log(gray(' 1. ') + cyan('npm run build') + gray(' - Compile the project'));
118
+ console.log(gray(' 2. ') + cyan('npm run lint') + gray(' - Check code style'));
119
+ console.log(gray(' 3. ') + cyan('npm test') + gray(' - Run tests'));
120
+ console.log('');
121
+
122
+ // Clear for next run
123
+ createdFiles.length = 0;
124
+ };
125
+
30
126
  // Function to add module to app.module.ts, libs/module.ts or infra/module.ts
31
127
  const addModuleToAppModule = (dest, moduleName, importPath, targetFile = 'app.module.ts', moduleSuffix = 'Module') => {
32
128
  try {
@@ -92,6 +188,50 @@ const addModuleToAppModule = (dest, moduleName, importPath, targetFile = 'app.mo
92
188
  }
93
189
  };
94
190
 
191
+ const createTypeSpecDocs = (dest, moduleName) => {
192
+ try {
193
+ const docsPath = `${dest}/docs/src/modules/${moduleName}`;
194
+ const mainTspPath = `${dest}/docs/src/main.tsp`;
195
+
196
+ if (!fs.existsSync(`${dest}/docs/src`)) {
197
+ console.log(bold(green(`TypeSpec docs folder not found, skipping...`)));
198
+ return;
199
+ }
200
+
201
+ if (!fs.existsSync(docsPath)) {
202
+ fs.mkdirSync(docsPath, { recursive: true });
203
+ }
204
+
205
+ fs.writeFileSync(`${docsPath}/controller.tsp`, getTypeSpecController(moduleName));
206
+ trackFile(`docs/src/modules/${moduleName}/controller.tsp`, 'typespec');
207
+ fs.writeFileSync(`${docsPath}/model.tsp`, getTypeSpecModel(moduleName));
208
+ trackFile(`docs/src/modules/${moduleName}/model.tsp`, 'typespec');
209
+ fs.writeFileSync(`${docsPath}/exception.tsp`, getTypeSpecException(moduleName));
210
+ trackFile(`docs/src/modules/${moduleName}/exception.tsp`, 'typespec');
211
+
212
+ if (fs.existsSync(mainTspPath)) {
213
+ let mainContent = fs.readFileSync(mainTspPath, 'utf-8');
214
+ const importStatement = `import "./modules/${moduleName}/controller.tsp";`;
215
+
216
+ if (!mainContent.includes(importStatement)) {
217
+ const lastImportRegex = /import "\.\/modules\/[^"]+\/controller\.tsp";/g;
218
+ const matches = [...mainContent.matchAll(lastImportRegex)];
219
+
220
+ if (matches.length > 0) {
221
+ const lastMatch = matches[matches.length - 1];
222
+ const insertPosition = lastMatch.index + lastMatch[0].length;
223
+ mainContent = mainContent.slice(0, insertPosition) + '\n' + importStatement + mainContent.slice(insertPosition);
224
+ fs.writeFileSync(mainTspPath, mainContent, 'utf-8');
225
+ }
226
+ }
227
+ }
228
+
229
+ console.log(bold(green(`✓ TypeSpec documentation created at docs/src/modules/${moduleName}/`)));
230
+ } catch (error) {
231
+ console.log(bold(red(`Error creating TypeSpec docs: ${error.message}`)));
232
+ }
233
+ };
234
+
95
235
  import { getCoreUsecaseCreateTest } from './templates/core/use-cases/__tests__/create.spec.mjs';
96
236
  import { getCoreUsecaseUpdateTest } from './templates/core/use-cases/__tests__/update.spec.mjs';
97
237
  import { getCoreUsecaseDeleteTest } from './templates/core/use-cases/__tests__/delete.spec.mjs';
@@ -122,7 +262,6 @@ const createModule = async (name) => {
122
262
  name = getName(name)
123
263
  const dirRoot = `${__dirname}/scafold/module/${name}`
124
264
 
125
- console.log("dirRoot", dirRoot)
126
265
  try {
127
266
  if (fs.existsSync(dirRoot)) {
128
267
  fs.rmSync(dirRoot, { recursive: true });
@@ -131,7 +270,9 @@ const createModule = async (name) => {
131
270
  fs.mkdirSync(dirRoot)
132
271
 
133
272
  fs.writeFileSync(`${dirRoot}/controller.ts`, getModuleControllerModule(name))
273
+ trackFile(`modules/${name}/controller.ts`, 'modules');
134
274
  fs.writeFileSync(`${dirRoot}/module.ts`, getModuleModule(name))
275
+ trackFile(`modules/${name}/module.ts`, 'modules');
135
276
 
136
277
  return `${name}`
137
278
  } catch (error) {
@@ -148,7 +289,6 @@ const createInfra = async (name) => {
148
289
  name = getName(name)
149
290
  const dirRoot = `${__dirname}/scafold/infra/${name}`
150
291
 
151
- console.log("dirRoot", dirRoot)
152
292
  try {
153
293
  if (fs.existsSync(dirRoot)) {
154
294
  fs.rmSync(dirRoot, { recursive: true });
@@ -157,9 +297,13 @@ const createInfra = async (name) => {
157
297
  fs.mkdirSync(dirRoot)
158
298
 
159
299
  fs.writeFileSync(`${dirRoot}/adapter.ts`, getAdapterInfra(name))
300
+ trackFile(`infra/${name}/adapter.ts`, 'infra');
160
301
  fs.writeFileSync(`${dirRoot}/index.ts`, getIndexInfra(name))
302
+ trackFile(`infra/${name}/index.ts`, 'infra');
161
303
  fs.writeFileSync(`${dirRoot}/module.ts`, getModuleInfa(name))
304
+ trackFile(`infra/${name}/module.ts`, 'infra');
162
305
  fs.writeFileSync(`${dirRoot}/service.ts`, getServiceInfra(name))
306
+ trackFile(`infra/${name}/service.ts`, 'infra');
163
307
 
164
308
  return `${name}`
165
309
  } catch (error) {
@@ -184,9 +328,13 @@ const createLib = async (name) => {
184
328
  fs.mkdirSync(dirRoot)
185
329
 
186
330
  fs.writeFileSync(`${dirRoot}/adapter.ts`, getAdapterLib(name))
331
+ trackFile(`libs/${name}/adapter.ts`, 'libs');
187
332
  fs.writeFileSync(`${dirRoot}/index.ts`, getIndexLib(name))
333
+ trackFile(`libs/${name}/index.ts`, 'libs');
188
334
  fs.writeFileSync(`${dirRoot}/module.ts`, getModuleLib(name))
335
+ trackFile(`libs/${name}/module.ts`, 'libs');
189
336
  fs.writeFileSync(`${dirRoot}/service.ts`, getServiceLib(name))
337
+ trackFile(`libs/${name}/service.ts`, 'libs');
190
338
 
191
339
  return `${name}`
192
340
  } catch (error) {
@@ -221,22 +369,34 @@ const createCore = async (name) => {
221
369
  fs.mkdirSync(useCasesPath)
222
370
 
223
371
  fs.writeFileSync(`${entityPath}/${name}.ts`, getCoreEntity(name))
372
+ trackFile(`${entityPath}/${name}.ts`, 'core/entity');
373
+
224
374
  fs.writeFileSync(`${repositoryPath}/${name}.ts`, getCoreRepository(name))
225
-
375
+ trackFile(`${repositoryPath}/${name}.ts`, 'core/repository');
226
376
 
227
377
  fs.writeFileSync(`${useCasesPath}/${name}-create.ts`, getCoreUsecaseCreate(name))
378
+ trackFile(`${useCasesPath}/${name}-create.ts`, 'core/use-cases');
228
379
  fs.writeFileSync(`${useCasesPath}/${name}-delete.ts`, getCoreUsecaseDelete(name))
380
+ trackFile(`${useCasesPath}/${name}-delete.ts`, 'core/use-cases');
229
381
  fs.writeFileSync(`${useCasesPath}/${name}-get-by-id.ts`, getCoreUsecaseGetById(name))
382
+ trackFile(`${useCasesPath}/${name}-get-by-id.ts`, 'core/use-cases');
230
383
  fs.writeFileSync(`${useCasesPath}/${name}-list.ts`, getCoreUsecaseList(name))
384
+ trackFile(`${useCasesPath}/${name}-list.ts`, 'core/use-cases');
231
385
  fs.writeFileSync(`${useCasesPath}/${name}-update.ts`, getCoreUsecaseUpdate(name))
386
+ trackFile(`${useCasesPath}/${name}-update.ts`, 'core/use-cases');
232
387
 
233
388
  const useCasesPathTest = `${useCasesPath}/__tests__`
234
389
  fs.mkdirSync(useCasesPathTest)
235
390
  fs.writeFileSync(`${useCasesPathTest}/${name}-create.spec.ts`, getCoreUsecaseCreateTest(name))
391
+ trackFile(`${useCasesPathTest}/${name}-create.spec.ts`, 'core/tests');
236
392
  fs.writeFileSync(`${useCasesPathTest}/${name}-update.spec.ts`, getCoreUsecaseUpdateTest(name))
393
+ trackFile(`${useCasesPathTest}/${name}-update.spec.ts`, 'core/tests');
237
394
  fs.writeFileSync(`${useCasesPathTest}/${name}-delete.spec.ts`, getCoreUsecaseDeleteTest(name))
395
+ trackFile(`${useCasesPathTest}/${name}-delete.spec.ts`, 'core/tests');
238
396
  fs.writeFileSync(`${useCasesPathTest}/${name}-list.spec.ts`, getCoreUsecaseListTest(name))
397
+ trackFile(`${useCasesPathTest}/${name}-list.spec.ts`, 'core/tests');
239
398
  fs.writeFileSync(`${useCasesPathTest}/${name}-get-by-id.spec.ts`, getCoreUsecaseGetByIdTest(name))
399
+ trackFile(`${useCasesPathTest}/${name}-get-by-id.spec.ts`, 'core/tests');
240
400
  } catch (error) {
241
401
  console.log('error', error)
242
402
  if (fs.existsSync(dirRoot)) {
@@ -305,13 +465,18 @@ const createPostgresCrud = async (name) => {
305
465
  }
306
466
  fs.mkdirSync(schemasPath)
307
467
  fs.writeFileSync(`${schemasPath}/${name}.ts`, getModuleSchema(name))
468
+ trackFile(`schemas/${name}.ts`, 'schemas');
308
469
 
309
470
  const modulesPath = `${dirRoot}/modules/${name}`;
310
471
  fs.mkdirSync(modulesPath)
311
472
  fs.writeFileSync(`${modulesPath}/adapter.ts`, getModuleAdapter(name))
473
+ trackFile(`modules/${name}/adapter.ts`, 'modules');
312
474
  fs.writeFileSync(`${modulesPath}/controller.ts`, getModuleController(name))
475
+ trackFile(`modules/${name}/controller.ts`, 'modules');
313
476
  fs.writeFileSync(`${modulesPath}/module.ts`, getModule(name))
477
+ trackFile(`modules/${name}/module.ts`, 'modules');
314
478
  fs.writeFileSync(`${modulesPath}/repository.ts`, getModuleRepository(name))
479
+ trackFile(`modules/${name}/repository.ts`, 'modules');
315
480
 
316
481
  await createCore(name)
317
482
 
@@ -350,14 +515,19 @@ const createMongoCrud = async (name) => {
350
515
 
351
516
  fs.mkdirSync(schemasPath)
352
517
  fs.writeFileSync(`${schemasPath}/${name}.ts`, getModuleSchemaMongo(name))
518
+ trackFile(`schemas/${name}.ts`, 'schemas');
353
519
 
354
520
  const modulesPath = `${dirRoot}/modules/${name}`;
355
521
  fs.mkdirSync(modulesPath)
356
522
 
357
523
  fs.writeFileSync(`${modulesPath}/adapter.ts`, getModuleAdapterMongo(name))
524
+ trackFile(`modules/${name}/adapter.ts`, 'modules');
358
525
  fs.writeFileSync(`${modulesPath}/controller.ts`, getModuleControllerMongo(name))
526
+ trackFile(`modules/${name}/controller.ts`, 'modules');
359
527
  fs.writeFileSync(`${modulesPath}/module.ts`, getModuleMongo(name))
528
+ trackFile(`modules/${name}/module.ts`, 'modules');
360
529
  fs.writeFileSync(`${modulesPath}/repository.ts`, getModuleRepositoryMongo(name))
530
+ trackFile(`modules/${name}/repository.ts`, 'modules');
361
531
 
362
532
  await createCore(name)
363
533
 
@@ -450,6 +620,7 @@ export async function cli(args) {
450
620
 
451
621
  try {
452
622
 
623
+ // DESTINATION PATH
453
624
  const dest = path.resolve(`${__dirname}/../../../../`)
454
625
  const src = paths[0]
455
626
 
@@ -566,6 +737,7 @@ export async function cli(args) {
566
737
  // Add module to the appropriate module file
567
738
  if (userInput.type === 'postgres:crud' || userInput.type === 'mongo:crud') {
568
739
  addModuleToAppModule(dest, name, `@/modules/${name}/module`, 'app.module.ts', 'Module');
740
+ createTypeSpecDocs(dest, name);
569
741
  } else if (userInput.type === 'module') {
570
742
  addModuleToAppModule(dest, name, `@/modules/${name}/module`, 'app.module.ts', 'Module');
571
743
  } else if (userInput.type === 'lib') {
@@ -574,7 +746,7 @@ export async function cli(args) {
574
746
  addModuleToAppModule(dest, name, `./${name}`, 'infra/module.ts', 'Module');
575
747
  }
576
748
 
577
- console.log(bold(green('✓ Module created successfully!')))
749
+ printCreatedFiles(name, userInput.type);
578
750
  });
579
751
 
580
752
  } catch (error) {
@@ -1,5 +1,5 @@
1
1
  import pluralize from 'pluralize'
2
- import { dashToPascal, snakeToCamel } from '../../../textUtils.mjs'
2
+ import { dashToPascal } from '../../../textUtils.mjs'
3
3
 
4
4
  const getModuleController = (name) => `import { Controller, Delete, Get, HttpCode, Post, Put, Req, Version } from '@nestjs/common';
5
5
 
@@ -8,7 +8,6 @@ import { ${dashToPascal(name)}DeleteInput, ${dashToPascal(name)}DeleteOutput } f
8
8
  import { ${dashToPascal(name)}GetByIdInput, ${dashToPascal(name)}GetByIdOutput } from '@/core/${name}/use-cases/${name}-get-by-id';
9
9
  import { ${dashToPascal(name)}ListInput, ${dashToPascal(name)}ListOutput } from '@/core/${name}/use-cases/${name}-list';
10
10
  import { ${dashToPascal(name)}UpdateInput, ${dashToPascal(name)}UpdateOutput } from '@/core/${name}/use-cases/${name}-update';
11
- import { Permission } from '@/utils/decorators';
12
11
  import { ApiRequest } from '@/utils/request';
13
12
  import { SearchHttpSchema } from '@/utils/search';
14
13
  import { SortHttpSchema } from '@/utils/sort';
@@ -27,7 +26,6 @@ export class ${dashToPascal(name)}Controller {
27
26
 
28
27
  @Post()
29
28
  @Version('1')
30
- @Permission('${snakeToCamel(name)}:create')
31
29
  @HttpCode(201)
32
30
  async create(@Req() { body }: ApiRequest): Promise<${dashToPascal(name)}CreateOutput> {
33
31
  return await this.createUsecase.execute(body as ${dashToPascal(name)}CreateInput);
@@ -35,21 +33,18 @@ export class ${dashToPascal(name)}Controller {
35
33
 
36
34
  @Put(':id')
37
35
  @Version('1')
38
- @Permission('${snakeToCamel(name)}:update')
39
36
  async update(@Req() { body, params }: ApiRequest): Promise<${dashToPascal(name)}UpdateOutput> {
40
37
  return await this.updateUsecase.execute({ ...body, id: params.id } as ${dashToPascal(name)}UpdateInput);
41
38
  }
42
39
 
43
40
  @Get(':id')
44
41
  @Version('1')
45
- @Permission('${snakeToCamel(name)}:getbyid')
46
42
  async getById(@Req() { params }: ApiRequest): Promise<${dashToPascal(name)}GetByIdOutput> {
47
43
  return await this.getByIdUsecase.execute(params as ${dashToPascal(name)}GetByIdInput);
48
44
  }
49
45
 
50
46
  @Get()
51
47
  @Version('1')
52
- @Permission('${snakeToCamel(name)}:list')
53
48
  async list(@Req() { query }: ApiRequest): Promise<${dashToPascal(name)}ListOutput> {
54
49
  const input: ${dashToPascal(name)}ListInput = {
55
50
  sort: SortHttpSchema.parse(query.sort),
@@ -63,7 +58,6 @@ export class ${dashToPascal(name)}Controller {
63
58
 
64
59
  @Delete(':id')
65
60
  @Version('1')
66
- @Permission('${snakeToCamel(name)}:delete')
67
61
  async delete(@Req() { params }: ApiRequest): Promise<${dashToPascal(name)}DeleteOutput> {
68
62
  return await this.deleteUsecase.execute(params as ${dashToPascal(name)}DeleteInput);
69
63
  }
@@ -1,5 +1,5 @@
1
1
  import pluralize from 'pluralize'
2
- import { dashToPascal, snakeToCamel } from '../../../textUtils.mjs'
2
+ import { dashToPascal } from '../../../textUtils.mjs'
3
3
 
4
4
  const getModuleController = (name) => `import { Controller, Delete, Get, HttpCode, Post, Put, Req, Version } from '@nestjs/common';
5
5
 
@@ -8,7 +8,6 @@ import { ${dashToPascal(name)}DeleteInput, ${dashToPascal(name)}DeleteOutput } f
8
8
  import { ${dashToPascal(name)}GetByIdInput, ${dashToPascal(name)}GetByIdOutput } from '@/core/${name}/use-cases/${name}-get-by-id';
9
9
  import { ${dashToPascal(name)}ListInput, ${dashToPascal(name)}ListOutput } from '@/core/${name}/use-cases/${name}-list';
10
10
  import { ${dashToPascal(name)}UpdateInput, ${dashToPascal(name)}UpdateOutput } from '@/core/${name}/use-cases/${name}-update';
11
- import { Permission } from '@/utils/decorators';
12
11
  import { ApiRequest } from '@/utils/request';
13
12
  import { SearchHttpSchema } from '@/utils/search';
14
13
  import { SortHttpSchema } from '@/utils/sort';
@@ -27,7 +26,6 @@ export class ${dashToPascal(name)}Controller {
27
26
 
28
27
  @Post()
29
28
  @Version('1')
30
- @Permission('${snakeToCamel(name)}:create')
31
29
  @HttpCode(201)
32
30
  async create(@Req() { body }: ApiRequest): Promise<${dashToPascal(name)}CreateOutput> {
33
31
  return await this.createUsecase.execute(body as ${dashToPascal(name)}CreateInput);
@@ -35,21 +33,18 @@ export class ${dashToPascal(name)}Controller {
35
33
 
36
34
  @Put(':id')
37
35
  @Version('1')
38
- @Permission('${snakeToCamel(name)}:update')
39
36
  async update(@Req() { body, params }: ApiRequest): Promise<${dashToPascal(name)}UpdateOutput> {
40
37
  return await this.updateUsecase.execute({ ...body, id: params.id } as ${dashToPascal(name)}UpdateInput);
41
38
  }
42
39
 
43
40
  @Get(':id')
44
41
  @Version('1')
45
- @Permission('${snakeToCamel(name)}:getbyid')
46
42
  async getById(@Req() { params }: ApiRequest): Promise<${dashToPascal(name)}GetByIdOutput> {
47
43
  return await this.getByIdUsecase.execute(params as ${dashToPascal(name)}GetByIdInput);
48
44
  }
49
45
 
50
46
  @Get()
51
47
  @Version('1')
52
- @Permission('${snakeToCamel(name)}:list')
53
48
  async list(@Req() { query }: ApiRequest): Promise<${dashToPascal(name)}ListOutput> {
54
49
  const input: ${dashToPascal(name)}ListInput = {
55
50
  sort: SortHttpSchema.parse(query.sort),
@@ -63,7 +58,6 @@ export class ${dashToPascal(name)}Controller {
63
58
 
64
59
  @Delete(':id')
65
60
  @Version('1')
66
- @Permission('${snakeToCamel(name)}:delete')
67
61
  async delete(@Req() { params }: ApiRequest): Promise<${dashToPascal(name)}DeleteOutput> {
68
62
  return await this.deleteUsecase.execute(params as ${dashToPascal(name)}DeleteInput);
69
63
  }
@@ -0,0 +1,68 @@
1
+ import pluralize from 'pluralize';
2
+
3
+ const dashToPascal = (string) =>
4
+ string
5
+ .split("-")
6
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
7
+ .join("");
8
+
9
+ export const getTypeSpecController = (name) => {
10
+ const pascalName = dashToPascal(name);
11
+
12
+ return `import "@typespec/http";
13
+ import "@typespec/rest";
14
+ import "@typespec/openapi3";
15
+ import "@typespec/versioning";
16
+ import "../../utils/model.tsp";
17
+ import "./model.tsp";
18
+ import "./exception.tsp";
19
+
20
+ using TypeSpec.Http;
21
+ using TypeSpec.Versioning;
22
+ using OpenAPI;
23
+ using Utils.Model;
24
+ using Utils.Versioning;
25
+
26
+ @service(#{
27
+ title: "${pascalName}",
28
+ })
29
+ namespace api.${pascalName};
30
+
31
+ @tag("${pascalName}")
32
+ @route("api/{version}/${pluralize(name)}")
33
+ @useAuth(BearerAuth)
34
+ interface ${pascalName}Controller {
35
+ @post
36
+ @doc("create ${name}")
37
+ @returnsDoc("${name} created successfully")
38
+ create(...VersionParams, @body body: CreateInput): CreateOutput | CreateValidationException;
39
+
40
+ @put
41
+ @doc("update ${name}")
42
+ @returnsDoc("${name} updated successfully")
43
+ update(
44
+ ...VersionParams,
45
+ ...UpdateParamInput,
46
+ @body body: UpdateInput,
47
+ ): UpdateOutput | UpdateValidationException | UpdateNotFoundException;
48
+
49
+ @get
50
+ @doc("get ${name} by id")
51
+ @returnsDoc("get ${name} by id successfully")
52
+ getById(
53
+ ...VersionParams,
54
+ ...GetByIdParamInput,
55
+ ): GetByIdOutput | GetByIdValidationException | GetByIdNotFoundException;
56
+
57
+ @get
58
+ @doc("list ${name}")
59
+ @returnsDoc("list ${name} successfully")
60
+ list(...VersionParams, ...ListQueryInput): ListOutput;
61
+
62
+ @delete
63
+ @doc("delete ${name}")
64
+ @returnsDoc("${name} deleted successfully")
65
+ delete(...VersionParams, ...DeleteParamInput): DeleteOutput | DeleteValidationException | DeleteNotFoundException;
66
+ }
67
+ `;
68
+ };
@@ -0,0 +1,84 @@
1
+ const dashToPascal = (string) =>
2
+ string
3
+ .split("-")
4
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
5
+ .join("");
6
+
7
+ export const getTypeSpecException = (name) => {
8
+ const pascalName = dashToPascal(name);
9
+ const camelName = name.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
10
+
11
+ return `import "../../utils/model.tsp";
12
+ import "../../utils/exceptions.tsp";
13
+ import "@typespec/http";
14
+ import "@typespec/versioning";
15
+
16
+ using TypeSpec.Http;
17
+ using TypeSpec.Versioning;
18
+ using Utils.Model;
19
+ using Utils.Exception;
20
+ using Utils.Versioning;
21
+
22
+ namespace api.${pascalName};
23
+
24
+ // CREATE //
25
+ @doc("When input is invalid")
26
+ @error
27
+ model CreateValidationError extends ApiBadRequestException<"name: Required"> {
28
+ @statusCode statusCode: 400;
29
+ }
30
+
31
+ alias CreateValidationException = CreateValidationError;
32
+
33
+ // UPDATE //
34
+ @doc("When input is invalid")
35
+ @error
36
+ model UpdateValidationError extends ApiBadRequestException<"id: Required"> {
37
+ @statusCode statusCode: 400;
38
+ }
39
+
40
+ alias UpdateValidationException = UpdateValidationError;
41
+
42
+ @doc("When ${name} not found")
43
+ @error
44
+ model Update${pascalName}NotFoundError extends ApiNotFoundException<"${camelName}NotFound"> {
45
+ @statusCode statusCode: 404;
46
+ }
47
+
48
+ alias UpdateNotFoundException = Update${pascalName}NotFoundError;
49
+
50
+ // GET BY ID //
51
+ @doc("When input is invalid")
52
+ @error
53
+ model GetByIdValidationError extends ApiBadRequestException<"id: Required"> {
54
+ @statusCode statusCode: 400;
55
+ }
56
+
57
+ alias GetByIdValidationException = GetByIdValidationError;
58
+
59
+ @doc("When ${name} not found")
60
+ @error
61
+ model GetById${pascalName}NotFoundError extends ApiNotFoundException<"${camelName}NotFound"> {
62
+ @statusCode statusCode: 404;
63
+ }
64
+
65
+ alias GetByIdNotFoundException = GetById${pascalName}NotFoundError;
66
+
67
+ // DELETE //
68
+ @doc("When input is invalid")
69
+ @error
70
+ model DeleteValidationError extends ApiBadRequestException<"id: Required"> {
71
+ @statusCode statusCode: 400;
72
+ }
73
+
74
+ alias DeleteValidationException = DeleteValidationError;
75
+
76
+ @doc("When ${name} not found")
77
+ @error
78
+ model Delete${pascalName}NotFoundError extends ApiNotFoundException<"${camelName}NotFound"> {
79
+ @statusCode statusCode: 404;
80
+ }
81
+
82
+ alias DeleteNotFoundException = Delete${pascalName}NotFoundError;
83
+ `;
84
+ };
@@ -0,0 +1,71 @@
1
+ const dashToPascal = (string) =>
2
+ string
3
+ .split("-")
4
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
5
+ .join("");
6
+
7
+ export const getTypeSpecModel = (name) => {
8
+ const pascalName = dashToPascal(name);
9
+
10
+ return `import "../../utils/model.tsp";
11
+ import "@typespec/http";
12
+ import "@typespec/versioning";
13
+
14
+ using TypeSpec.Http;
15
+ using TypeSpec.Versioning;
16
+ using Utils.Model;
17
+ using Utils.Versioning;
18
+
19
+ namespace api.${pascalName};
20
+
21
+ @doc("${name} base entity")
22
+ model ${pascalName}Entity {
23
+ name: string;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ deletedAt: string | null = null;
27
+ }
28
+
29
+ // CREATE //
30
+ @doc("${name} create input")
31
+ model CreateInput extends PickProperties<${pascalName}Entity, "name"> {}
32
+ @doc("${name} create output")
33
+ model CreateOutput extends ${pascalName}Entity {}
34
+
35
+ // UPDATE //
36
+ model UpdateParamInput {
37
+ @doc("${name} id")
38
+ @path
39
+ id: string;
40
+ }
41
+ @doc("${name} update input")
42
+ model UpdateInput extends PickProperties<${pascalName}Entity, "name"> {}
43
+ @doc("${name} update output")
44
+ model UpdateOutput extends ${pascalName}Entity {}
45
+
46
+ // GET BY ID //
47
+ model GetByIdParamInput {
48
+ @doc("${name} id")
49
+ @path
50
+ id: string;
51
+ }
52
+ @doc("${name} get by id input")
53
+ model GetByIdOutput extends ${pascalName}Entity {}
54
+
55
+ // LIST //
56
+ model ListQueryInput extends ApiPaginationInput {}
57
+ @doc("${name} list output")
58
+ model ListOutput extends ApiPaginationOutput<${pascalName}Entity> {}
59
+
60
+ // DELETE //
61
+ model DeleteParamInput {
62
+ @doc("${name} id")
63
+ @path
64
+ id: string;
65
+ }
66
+ @doc("${name} delete output")
67
+ model DeleteOutput extends OmitProperties<${pascalName}Entity, "deletedAt"> {
68
+ deletedAt: utcDateTime;
69
+ }
70
+ `;
71
+ };