@nestledjs/api 0.0.1

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 (132) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +11 -0
  3. package/eslint.config.cjs +28 -0
  4. package/generators.json +69 -0
  5. package/package.json +21 -0
  6. package/project.json +47 -0
  7. package/src/account/files/data-access/src/index.ts__tmpl__ +5 -0
  8. package/src/account/files/data-access/src/lib/api-account-data-access.module.ts__tmpl__ +10 -0
  9. package/src/account/files/data-access/src/lib/api-account-data-access.service.ts__tmpl__ +152 -0
  10. package/src/account/files/data-access/src/lib/dto/account-create-email.input.ts__tmpl__ +9 -0
  11. package/src/account/files/data-access/src/lib/dto/account-update-password.input.ts__tmpl__ +16 -0
  12. package/src/account/files/data-access/src/lib/dto/account-update-profile.input.ts__tmpl__ +25 -0
  13. package/src/account/files/feature/src/index.ts__tmpl__ +1 -0
  14. package/src/account/files/feature/src/lib/api-account-feature.module.ts__tmpl__ +9 -0
  15. package/src/account/files/feature/src/lib/api-account-feature.resolver.ts__tmpl__ +83 -0
  16. package/src/account/generator.spec.ts +71 -0
  17. package/src/account/generator.ts +20 -0
  18. package/src/account/schema.d.ts +3 -0
  19. package/src/account/schema.json +13 -0
  20. package/src/app/files/src/app.config.ts__tmpl__ +66 -0
  21. package/src/app/files/src/app.module.ts__tmpl__ +43 -0
  22. package/src/app/files/src/applogger.middleware.ts__tmpl__ +21 -0
  23. package/src/app/files/src/main.ts__tmpl__ +33 -0
  24. package/src/app/files/webpack.config.js__tmpl__ +54 -0
  25. package/src/app/generator.spec.ts +112 -0
  26. package/src/app/generator.ts +105 -0
  27. package/src/app/schema.d.ts +1 -0
  28. package/src/app/schema.json +9 -0
  29. package/src/config/files/src/index.ts__tmpl__ +3 -0
  30. package/src/config/files/src/lib/config.service.ts__tmpl__ +51 -0
  31. package/src/config/files/src/lib/configuration.ts__tmpl__ +32 -0
  32. package/src/config/files/src/lib/validation.ts__tmpl__ +21 -0
  33. package/src/config/generator.spec.ts +47 -0
  34. package/src/config/generator.ts +16 -0
  35. package/src/config/schema.d.ts +3 -0
  36. package/src/config/schema.json +13 -0
  37. package/src/core/files/data-access/src/index.ts__tmpl__ +5 -0
  38. package/src/core/files/data-access/src/lib/api-core-data-access.module.ts__tmpl__ +9 -0
  39. package/src/core/files/data-access/src/lib/api-core-data-access.service.ts__tmpl__ +97 -0
  40. package/src/core/files/data-access/src/lib/api-core-pub-sub.ts__tmpl__ +37 -0
  41. package/src/core/files/data-access/src/lib/dto/core-paging.input.ts__tmpl__ +26 -0
  42. package/src/core/files/data-access/src/lib/dto/multi-select-input.ts__tmpl__ +7 -0
  43. package/src/core/files/data-access/src/lib/models/core-paging.ts__tmpl__ +19 -0
  44. package/src/core/files/feature/src/index.ts__tmpl__ +2 -0
  45. package/src/core/files/feature/src/lib/api-core-feature.controller.ts__tmpl__ +12 -0
  46. package/src/core/files/feature/src/lib/api-core-feature.module.ts__tmpl__ +86 -0
  47. package/src/core/files/feature/src/lib/api-core-feature.resolver.ts__tmpl__ +12 -0
  48. package/src/core/files/feature/src/lib/api-core-feature.service.ts__tmpl__ +55 -0
  49. package/src/core/files/feature/src/lib/config/configuration.ts__tmpl__ +32 -0
  50. package/src/core/files/feature/src/lib/config/validation.ts__tmpl__ +25 -0
  51. package/src/core/files/feature/src/lib/plugins/complexity.plugin.ts__tmpl__ +51 -0
  52. package/src/core/files/feature/src/lib/plugins/logging.plugin.ts__tmpl__ +17 -0
  53. package/src/core/files/models/src/index.ts__tmpl__ +1 -0
  54. package/src/core/files/models/src/lib/generate-models.ts__tmpl__ +294 -0
  55. package/src/core/files/models/src/lib/models/core-paging.model.ts__tmpl__ +25 -0
  56. package/src/core/generator.spec.ts +85 -0
  57. package/src/core/generator.ts +35 -0
  58. package/src/core/schema.d.ts +3 -0
  59. package/src/core/schema.json +13 -0
  60. package/src/custom/generator.spec.ts +75 -0
  61. package/src/custom/generator.ts +239 -0
  62. package/src/custom/schema.json +21 -0
  63. package/src/custom/schema.ts +5 -0
  64. package/src/extended/generator.spec.ts +95 -0
  65. package/src/extended/generator.ts +161 -0
  66. package/src/extended/index.ts +1 -0
  67. package/src/extended/schema.json +12 -0
  68. package/src/extended/schema.ts +3 -0
  69. package/src/generate-crud/files/data-access/src/index.ts__tmpl__ +3 -0
  70. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.module.ts__tmpl__ +11 -0
  71. package/src/generate-crud/files/data-access/src/lib/api-crud-data-access.service.ts__tmpl__ +72 -0
  72. package/src/generate-crud/files/data-access/src/lib/dto/index.ts__tmpl__ +224 -0
  73. package/src/generate-crud/files/feature/.gitkeep +0 -0
  74. package/src/generate-crud/generator.spec.ts +84 -0
  75. package/src/generate-crud/generator.ts +354 -0
  76. package/src/generate-crud/schema.json +32 -0
  77. package/src/generate-crud/schema.ts +8 -0
  78. package/src/index.ts +13 -0
  79. package/src/plugin/generator.spec.ts +18 -0
  80. package/src/plugin/generator.ts +74 -0
  81. package/src/plugin/schema.json +14 -0
  82. package/src/plugin/schema.ts +4 -0
  83. package/src/prisma/files/src/index.ts__tmpl__ +1 -0
  84. package/src/prisma/files/src/lib/.gitkeep +1 -0
  85. package/src/prisma/files/src/lib/schemas/schema.prisma__tmpl__ +402 -0
  86. package/src/prisma/files/src/lib/seed/seed-data/iso-3166-countries.ts__tmpl__ +3239 -0
  87. package/src/prisma/files/src/lib/seed/seed-data/seed-users.ts__tmpl__ +32 -0
  88. package/src/prisma/files/src/lib/seed/seed.ts__tmpl__ +64 -0
  89. package/src/prisma/generator.spec.ts +60 -0
  90. package/src/prisma/generator.ts +61 -0
  91. package/src/prisma/schema.d.ts +3 -0
  92. package/src/prisma/schema.json +13 -0
  93. package/src/setup/generator.md +49 -0
  94. package/src/setup/generator.spec.ts +18 -0
  95. package/src/setup/generator.ts +106 -0
  96. package/src/setup/schema.json +8 -0
  97. package/src/smtp-mailer/files/data-access/src/index.ts__tmpl__ +2 -0
  98. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.module.ts__tmpl__ +10 -0
  99. package/src/smtp-mailer/files/data-access/src/lib/api-smtp-mailer-data-access.service.ts__tmpl__ +61 -0
  100. package/src/smtp-mailer/generator.spec.ts +41 -0
  101. package/src/smtp-mailer/generator.ts +14 -0
  102. package/src/smtp-mailer/schema.d.ts +0 -0
  103. package/src/smtp-mailer/schema.json +7 -0
  104. package/src/user/files/data-access/src/index.ts__tmpl__ +5 -0
  105. package/src/user/files/data-access/src/lib/api-user-data-access.module.ts__tmpl__ +10 -0
  106. package/src/user/files/data-access/src/lib/api-user-data-access.service.ts__tmpl__ +119 -0
  107. package/src/user/files/data-access/src/lib/dto/admin-create-user.input.ts__tmpl__ +20 -0
  108. package/src/user/files/data-access/src/lib/dto/admin-update-user.input.ts__tmpl__ +29 -0
  109. package/src/user/files/feature/src/index.ts__tmpl__ +1 -0
  110. package/src/user/files/feature/src/lib/api-user-feature-admin.resolver.ts__tmpl__ +57 -0
  111. package/src/user/files/feature/src/lib/api-user-feature.module.ts__tmpl__ +10 -0
  112. package/src/user/files/feature/src/lib/api-user-feature.resolver.ts__tmpl__ +17 -0
  113. package/src/user/generator.spec.ts +41 -0
  114. package/src/user/generator.ts +15 -0
  115. package/src/user/schema.d.ts +0 -0
  116. package/src/user/schema.json +7 -0
  117. package/src/utils/files/src/index.ts__tmpl__ +3 -0
  118. package/src/utils/files/src/lib/decorators/ctx-user.decorator.ts__tmpl__ +6 -0
  119. package/src/utils/files/src/lib/guards/gql-auth-admin.guard.ts__tmpl__ +39 -0
  120. package/src/utils/files/src/lib/guards/gql-auth.guard.ts__tmpl__ +11 -0
  121. package/src/utils/generator.ts +14 -0
  122. package/src/utils/schema.json +8 -0
  123. package/src/workspace-setup/generator.md +39 -0
  124. package/src/workspace-setup/generator.spec.ts +82 -0
  125. package/src/workspace-setup/generator.ts +49 -0
  126. package/src/workspace-setup/lib/helpers.ts +142 -0
  127. package/src/workspace-setup/schema.d.ts +3 -0
  128. package/src/workspace-setup/schema.json +7 -0
  129. package/tsconfig.json +16 -0
  130. package/tsconfig.lib.json +23 -0
  131. package/tsconfig.spec.json +22 -0
  132. package/vite.config.mts +37 -0
@@ -0,0 +1,66 @@
1
+ import { registerAs } from '@nestjs/config'
2
+ import Joi from 'joi'
3
+
4
+ export const appConfig = registerAs('app', () => ({
5
+ prefix: 'api',
6
+ environment: process.env.NODE_ENV,
7
+ host: process.env.HOST,
8
+ port: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
9
+ apiUrl: process.env.API_URL,
10
+ webPort: process.env.WEB_PORT ? parseInt(process.env.WEB_PORT, 10) : 4200,
11
+ api: {
12
+ cookie: {
13
+ name: process.env.API_COOKIE_NAME,
14
+ secret: process.env.API_COOKIE_SECRET,
15
+ options: {
16
+ domain: process.env.API_COOKIE_DOMAIN,
17
+ httpOnly: true,
18
+ secure: true,
19
+ sameSite: 'none',
20
+ path: '/',
21
+ },
22
+ },
23
+ cors: {
24
+ origin: [process.env.SITE_URL],
25
+ },
26
+ },
27
+ siteUrl: process.env.SITE_URL || (process.env.API_URL ? process.env.API_URL.replace('/api', '') : ''),
28
+ app: {
29
+ email: process.env.APP_EMAIL,
30
+ supportEmail: process.env.APP_SUPPORT_EMAIL,
31
+ adminEmails: process.env.APP_ADMIN_EMAILS,
32
+ name: process.env.APP_NAME,
33
+ },
34
+ smtp: {
35
+ host: process.env.SMTP_HOST,
36
+ port: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587,
37
+ user: process.env.SMTP_USER,
38
+ pass: process.env.SMTP_PASS,
39
+ ignoreTLS: true,
40
+ },
41
+ twilio: {
42
+ accountSid: process.env.TWILIO_ACCOUNT_SID,
43
+ authToken: process.env.TWILIO_AUTH_TOKEN,
44
+ fromNumber: process.env.TWILIO_FROM_NUMBER,
45
+ },
46
+ }))
47
+
48
+ export const validationSchema = Joi.object({
49
+ NODE_ENV: Joi.string().valid('development', 'production', 'test'),
50
+ HOST: Joi.string().alphanum().default('localhost'),
51
+ PORT: Joi.number().default(3000),
52
+ WEB_PORT: Joi.number().default(4200),
53
+ WEB_URL: Joi.string().default(`http://${process.env.HOST || 'localhost'}:${process.env.WEB_PORT}`),
54
+ API_COOKIE_DOMAIN: Joi.string().default('localhost'),
55
+ API_COOKIE_NAME: Joi.string().default('__session'),
56
+ API_URL: Joi.string().default(`http://${process.env.HOST || 'localhost'}:${process.env.PORT}/api`),
57
+ APP_NAME: Joi.string().required(),
58
+ APP_EMAIL: Joi.string().email().required(),
59
+ APP_SUPPORT_EMAIL: Joi.string().email().required(),
60
+ APP_ADMIN_EMAILS: Joi.string().required(),
61
+ SITE_URL: Joi.string().uri().required(),
62
+ SMTP_HOST: Joi.string().required(),
63
+ SMTP_PORT: Joi.string().required(),
64
+ SMTP_USER: Joi.string().required(),
65
+ SMTP_PASS: Joi.string().required(),
66
+ })
@@ -0,0 +1,43 @@
1
+ import { MiddlewareConsumer, Module, NestModule, RequestMethod } from '@nestjs/common'
2
+ import { LoggerMiddleware } from './applogger.middleware'
3
+ import { ConfigModule } from '@nestjs/config'
4
+ import { appConfig, validationSchema } from './app.config'
5
+
6
+ // Auto-generated modules with special functions
7
+ export const coreModules = [
8
+ // Auto-generated modules with special functions
9
+ ]
10
+ // Auto-generated modules for each data type/model
11
+ export const defaultModules = [
12
+ // Auto-generated modules for each data type/model
13
+ ];
14
+ // Manually maintained plugin modules (never overwritten by generator)
15
+ export const pluginModules = [
16
+ // Manually maintained plugin modules (never overwritten by generator)
17
+ ];
18
+ // Combined modules used in the app
19
+ export const appModules = [
20
+ ...coreModules,
21
+ ...defaultModules,
22
+ ...pluginModules,
23
+ ];
24
+
25
+ @Module({
26
+ imports: [
27
+ ConfigModule.forRoot({
28
+ load: [appConfig],
29
+ validationSchema: validationSchema,
30
+ validationOptions: {
31
+ allowUnknown: true,
32
+ abortEarly: false,
33
+ },
34
+ isGlobal: true,
35
+ }),
36
+ ...appModules,
37
+ ],
38
+ })
39
+ export class AppModule implements NestModule {
40
+ configure(consumer: MiddlewareConsumer): void {
41
+ consumer.apply(LoggerMiddleware) .forRoutes({ path: '*path', method: RequestMethod.ALL })
42
+ }
43
+ }
@@ -0,0 +1,21 @@
1
+ import { NextFunction, Request, Response } from 'express'
2
+ import { Injectable, Logger, NestMiddleware } from '@nestjs/common'
3
+
4
+ @Injectable()
5
+ export class LoggerMiddleware implements NestMiddleware {
6
+ private readonly logger = new Logger('HTTP')
7
+
8
+ use(request: Request, response: Response, next: NextFunction): void {
9
+ const { ip, method, originalUrl } = request
10
+ const userAgent = request.get('user-agent') || ''
11
+
12
+ response.on('finish', () => {
13
+ const { statusCode } = response
14
+ const contentLength = response.get('content-length')
15
+
16
+ this.logger.log(`${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`)
17
+ })
18
+
19
+ next()
20
+ }
21
+ }
@@ -0,0 +1,33 @@
1
+ import { Logger } from '@nestjs/common'
2
+ import { NestFactory } from '@nestjs/core'
3
+ import { ConfigService } from '@nestjs/config'
4
+ import cookieParser from 'cookie-parser'
5
+
6
+ import { AppModule } from './app.module'
7
+
8
+ async function bootstrap() {
9
+ const app = await NestFactory.create(AppModule)
10
+ const configService = app.get(ConfigService)
11
+
12
+ // Get individual properties with fallbacks
13
+ const prefix = configService.get('app.prefix') || 'api';
14
+ const port = configService.get('app.port') || 3000;
15
+ const host = configService.get('app.host') || 'localhost';
16
+
17
+ app.setGlobalPrefix(prefix)
18
+ app.enableCors({
19
+ credentials: true,
20
+ origin: configService.get('app.api.cors.origin') || '*',
21
+ })
22
+ app.use(cookieParser(configService.get('app.api.cookie.secret') || 'secret'));
23
+
24
+ await app.listen(port, host, () => {
25
+ Logger.log(`Listening at http://${host}:${port}/${prefix}`)
26
+ Logger.log(`Listening at http://${host}:${port}/graphql`)
27
+ })
28
+ }
29
+
30
+ bootstrap().catch((error) => {
31
+ Logger.error('Failed to start the application', error);
32
+ process.exit(1);
33
+ });
@@ -0,0 +1,54 @@
1
+ const path = require('path');
2
+ const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
3
+ const nodeExternals = require('webpack-node-externals');
4
+
5
+ module.exports = {
6
+ target: 'node',
7
+ mode: 'development',
8
+ entry: path.resolve(__dirname, 'src/main.ts'),
9
+ output: {
10
+ path: path.resolve(__dirname, '../../dist/apps/api'),
11
+ filename: 'main.js',
12
+ },
13
+ resolve: {
14
+ extensions: ['.ts', '.js'],
15
+ plugins: [
16
+ new TsconfigPathsPlugin({
17
+ configFile: path.resolve(__dirname, '../../tsconfig.base.json'),
18
+ }),
19
+ ],
20
+ },
21
+ module: {
22
+ rules: [
23
+ {
24
+ test: /\.ts$/,
25
+ use: 'ts-loader',
26
+ exclude: [
27
+ /node_modules/,
28
+ path.resolve(__dirname, '../../libs/api/prisma/src/lib/prisma-generated')
29
+ ],
30
+ },
31
+
32
+ ],
33
+ },
34
+ externals: [
35
+ nodeExternals({
36
+ allowlist: [
37
+ /^@<%= npmScope %>\/api/, // allow your own libs
38
+ ],
39
+ // Manually exclude Prisma internals if needed
40
+ function ({ request }, callback) {
41
+ if (request && request.includes('@prisma/client')) {
42
+ return callback(null, 'commonjs @prisma/client')
43
+ }
44
+ if (request && request.includes('.prisma/client')) {
45
+ return callback(null, 'commonjs .prisma/client')
46
+ }
47
+ if (request && request === '@<%= npmScope %>/api/prisma') {
48
+ return callback(null, 'commonjs @<%= npmScope %>/api/prisma')
49
+ }
50
+ callback()
51
+ }
52
+ })
53
+ ]
54
+ };
@@ -0,0 +1,112 @@
1
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
2
+ import { Tree } from '@nx/devkit'
3
+ import { execSync } from 'child_process'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import generator from './generator'
6
+
7
+ vi.mock('child_process', async (importOriginal) => {
8
+ const originalModule = await importOriginal()
9
+ return {
10
+ ...(originalModule as object),
11
+ execSync: vi.fn(),
12
+ }
13
+ })
14
+
15
+ describe('api generator', () => {
16
+ let tree: Tree
17
+ const mockedExecSync = execSync as ReturnType<(typeof vi)['fn']>
18
+
19
+ beforeEach(() => {
20
+ tree = createTreeWithEmptyWorkspace()
21
+ tree.write('package.json', JSON.stringify({ name: 'test-repo', scripts: {} }))
22
+
23
+ mockedExecSync.mockImplementation((command: string) => {
24
+ if (command.startsWith('nx g @nx/nest:application')) {
25
+ tree.write(
26
+ 'apps/api/webpack.config.js',
27
+ `
28
+ const path = require('path');
29
+ const { composePlugins, withNx } = require('@nx/webpack');
30
+
31
+ module.exports = composePlugins(withNx(), (config) => {
32
+ return {
33
+ ...config,
34
+ output: {
35
+ ...config.output,
36
+ devtoolModuleFilenameTemplate: (info) => {
37
+ const rel = path.relative(process.cwd(), info.absoluteResourcePath);
38
+ return \`webpack://\${rel}\`;
39
+ },
40
+ },
41
+ assets: ['./src/assets'],
42
+ generatePackageJson: true,
43
+ };
44
+ });`
45
+ )
46
+ // Add a mock tsconfig.app.json file with the compiler options we want to remove
47
+ tree.write('apps/api/tsconfig.app.json', JSON.stringify({
48
+ extends: '../../tsconfig.base.json',
49
+ compilerOptions: {
50
+ module: 'commonjs',
51
+ moduleResolution: 'node',
52
+ emitDecoratorMetadata: true,
53
+ experimentalDecorators: true,
54
+ outDir: '../../dist/out-tsc',
55
+ declaration: true,
56
+ types: ['node']
57
+ },
58
+ exclude: ['jest.config.ts', '**/*.spec.ts', '**/*.test.ts'],
59
+ include: ['**/*.ts']
60
+ }, null, 2))
61
+ tree.write('apps/api/src/app/.gitkeep', '')
62
+ tree.write('apps/api/src/assets/.gitkeep', '')
63
+ }
64
+ return ''
65
+ })
66
+
67
+ vi.spyOn(console, 'log').mockImplementation(() => { /* empty */ })
68
+ vi.spyOn(console, 'warn').mockImplementation(() => { /* empty */ })
69
+ vi.spyOn(console, 'error').mockImplementation(() => { /* empty */ })
70
+ })
71
+
72
+ afterEach(() => {
73
+ vi.restoreAllMocks()
74
+ mockedExecSync.mockClear()
75
+ })
76
+
77
+ it('should run successfully', async () => {
78
+ await generator(tree, {})
79
+
80
+ expect(mockedExecSync).toHaveBeenCalledWith(
81
+ 'nx g @nx/nest:application --name api --directory apps/api --no-interactive',
82
+ {
83
+ stdio: 'inherit',
84
+ cwd: tree.root,
85
+ }
86
+ )
87
+
88
+ const webpackConfig = tree.read('apps/api/webpack.config.js', 'utf-8')
89
+ expect(webpackConfig).not.toContain("assets: ['./src/assets']")
90
+
91
+ const packageJson = JSON.parse(tree.read('package.json', 'utf-8'))
92
+ expect(packageJson.scripts['dev:api']).toBe('nx serve api --skip-nx-cache')
93
+
94
+ expect(tree.exists('apps/api/src/app')).toBe(false)
95
+ expect(tree.exists('apps/api/src/assets')).toBe(false)
96
+
97
+ expect(tree.exists('apps/api/src/app.config.ts')).toBe(true)
98
+ expect(tree.exists('apps/api/src/app.module.ts')).toBe(true)
99
+ expect(tree.exists('apps/api/src/applogger.middleware.ts')).toBe(true)
100
+ expect(tree.exists('apps/api/src/main.ts')).toBe(true)
101
+
102
+ // Verify that the compiler options are removed from tsconfig.app.json
103
+ const tsConfig = JSON.parse(tree.read('apps/api/tsconfig.app.json', 'utf-8'))
104
+ expect(tsConfig.compilerOptions.module).toBeUndefined()
105
+ expect(tsConfig.compilerOptions.moduleResolution).toBeUndefined()
106
+ expect(tsConfig.compilerOptions.emitDecoratorMetadata).toBeUndefined()
107
+ expect(tsConfig.compilerOptions.experimentalDecorators).toBeUndefined()
108
+ // Verify that other compiler options are preserved
109
+ expect(tsConfig.compilerOptions.outDir).toBe('../../dist/out-tsc')
110
+ expect(tsConfig.compilerOptions.declaration).toBe(true)
111
+ })
112
+ })
@@ -0,0 +1,105 @@
1
+ import { generateFiles, joinPathFragments, Tree, updateJson } from '@nx/devkit'
2
+ import { execSync } from 'child_process'
3
+ import * as path from 'path'
4
+ import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope'
5
+
6
+ /**
7
+ * Removes specific compiler options from tsconfig.app.json to use the settings from tsconfig.base.json
8
+ * @param tree The file system tree
9
+ */
10
+ function updateAppTsConfig(tree: Tree): void {
11
+ const tsConfigPath = 'apps/api/tsconfig.app.json'
12
+ if (tree.exists(tsConfigPath)) {
13
+ updateJson(tree, tsConfigPath, (json) => {
14
+ if (json.compilerOptions) {
15
+ // Remove specific options to use the ones from tsconfig.base.json
16
+ delete json.compilerOptions.module
17
+ delete json.compilerOptions.moduleResolution
18
+ delete json.compilerOptions.emitDecoratorMetadata
19
+ delete json.compilerOptions.experimentalDecorators
20
+ }
21
+ return json
22
+ })
23
+ } else {
24
+ console.warn(`tsconfig.app.json not found at: ${tsConfigPath}`)
25
+ }
26
+ }
27
+
28
+ interface Schema {
29
+ [key: string]: unknown
30
+ }
31
+
32
+ export default async function (tree: Tree, schema: Schema) {
33
+ try {
34
+ // Get the workspace root directory
35
+ const workspaceRoot = tree.root
36
+
37
+ // Create apps directory if it doesn't exist
38
+ if (!tree.exists('apps')) {
39
+ tree.write('apps/.gitkeep', '')
40
+ }
41
+
42
+ // Run the Nx generator command directly from the workspace root with proper workspace layout
43
+ execSync('nx g @nx/nest:application --name api --directory apps/api --no-interactive', {
44
+ stdio: 'inherit',
45
+ cwd: workspaceRoot,
46
+ })
47
+
48
+ // Wait a bit for files to be created
49
+ await new Promise((resolve) => setTimeout(resolve, 2000))
50
+
51
+ // Update tsconfig.app.json to remove specific compiler options
52
+ updateAppTsConfig(tree)
53
+
54
+ // Generate all files according to the template folder structure
55
+ generateFiles(
56
+ tree,
57
+ joinPathFragments(__dirname, './files'),
58
+ path.join('apps', 'api'),
59
+ { ...schema, tmpl: '', npmScope: getNpmScope(tree) }
60
+ );
61
+
62
+ // Add dev:api script to package.json
63
+ updateJson(tree, 'package.json', (json) => {
64
+ if (!json.scripts) {
65
+ json.scripts = {}
66
+ }
67
+ json.scripts['dev:api'] = 'nx serve api --skip-nx-cache'
68
+ return json
69
+ })
70
+
71
+ // Update the build target in apps/api/project.json to use custom webpack command
72
+ const projectJsonPath = path.join('apps', 'api', 'project.json')
73
+ if (tree.exists(projectJsonPath)) {
74
+ updateJson(tree, projectJsonPath, (json) => {
75
+ json.targets = json.targets || {}
76
+ json.targets.build = {
77
+ executor: 'nx:run-commands',
78
+ options: {
79
+ command: 'NODE_ENV=production webpack-cli --config apps/api/webpack.config.js',
80
+ },
81
+ configurations: {
82
+ development: {
83
+ command: 'NODE_ENV=development webpack-cli --config apps/api/webpack.config.js',
84
+ },
85
+ },
86
+ }
87
+ return json
88
+ })
89
+ } else {
90
+ console.warn(`project.json not found at: ${projectJsonPath}`)
91
+ }
92
+
93
+ // Optionally, delete the unused default app files if they exist
94
+ const targetPath = path.join('apps', 'api', 'src')
95
+ const filesToDelete = [path.join(targetPath, 'assets'), path.join(targetPath, 'app')]
96
+ filesToDelete.forEach((filePath) => {
97
+ if (tree.exists(filePath)) {
98
+ tree.delete(filePath)
99
+ }
100
+ })
101
+ } catch (error) {
102
+ console.error('Error generating API app:', error)
103
+ throw error
104
+ }
105
+ }
@@ -0,0 +1 @@
1
+ export type AppSetupGeneratorSchema = Record<string, unknown>
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "cli": "nx",
4
+ "$id": "ApiGenerator",
5
+ "title": "API Application Generator",
6
+ "type": "object",
7
+ "properties": {},
8
+ "required": []
9
+ }
@@ -0,0 +1,3 @@
1
+ export * from './lib/config.service'
2
+ export * from './lib/configuration'
3
+ export * from './lib/validation'
@@ -0,0 +1,51 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import { ConfigService } from '@nestjs/config'
3
+ import { CookieOptions } from 'express'
4
+
5
+ @Injectable()
6
+ export class ApiConfigService {
7
+ constructor(public readonly config: ConfigService) {}
8
+
9
+ get apiUrl(): string {
10
+ return this.config.getOrThrow<string>('apiUrl')
11
+ }
12
+
13
+ get apiCorsOrigins(): string[] {
14
+ return this.config.get<string[]>('api.cors.origin') ?? []
15
+ }
16
+
17
+ get cookie(): { name: string; options: CookieOptions } {
18
+ return this.config.getOrThrow<{ name: string; options: CookieOptions }>('api.cookie')
19
+ }
20
+
21
+ get appEmail(): string {
22
+ return this.config.getOrThrow<string>('app.email')
23
+ }
24
+
25
+ get appSupportEmail(): string {
26
+ return this.config.getOrThrow<string>('app.supportEmail')
27
+ }
28
+
29
+ get appAdminEmails(): string {
30
+ return this.config.getOrThrow<string>('app.adminEmails')
31
+ }
32
+
33
+ get appName(): string {
34
+ return this.config.getOrThrow<string>('app.name')
35
+ }
36
+
37
+ get siteUrl(): string {
38
+ return this.config.getOrThrow<string>('siteUrl')
39
+ }
40
+
41
+ get mailerConfig() {
42
+ return {
43
+ host: this.config.getOrThrow<string>('smtp.host'),
44
+ port: this.config.getOrThrow<string>('smtp.port'),
45
+ auth: {
46
+ user: this.config.getOrThrow<string>('smtp.user'),
47
+ pass: this.config.getOrThrow<string>('smtp.pass'),
48
+ },
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,32 @@
1
+ export const configuration = () => ({
2
+ prefix: 'api',
3
+ environment: process.env['NODE_ENV'],
4
+ host: process.env['HOST'],
5
+ port: parseInt(process.env['PORT'] ?? '3000', 10),
6
+ apiUrl: process.env['API_URL'],
7
+ api: {
8
+ cookie: {
9
+ name: process.env['API_COOKIE_NAME'],
10
+ options: {
11
+ domain: process.env['API_COOKIE_DOMAIN'],
12
+ httpOnly: true,
13
+ },
14
+ },
15
+ cors: {
16
+ origin: [process.env['WEB_URL']],
17
+ },
18
+ },
19
+ siteUrl: process.env['SITE_URL'] || process.env['API_URL']?.replace('/api', ''),
20
+ app: {
21
+ email: process.env['APP_EMAIL'],
22
+ supportEmail: process.env['APP_SUPPORT_EMAIL'],
23
+ adminEmails: process.env['APP_ADMIN_EMAILS'],
24
+ name: process.env['APP_NAME'],
25
+ },
26
+ smtp: {
27
+ host: process.env['SMTP_HOST'],
28
+ port: process.env['SMTP_PORT'],
29
+ user: process.env['SMTP_USER'],
30
+ pass: process.env['SMTP_PASS'],
31
+ },
32
+ })
@@ -0,0 +1,21 @@
1
+ import * as Joi from 'joi'
2
+
3
+ export const validationSchema = Joi.object({
4
+ NODE_ENV: Joi.string().valid('development', 'production', 'test'),
5
+ HOST: Joi.string().alphanum().default('localhost'),
6
+ PORT: Joi.number().default(3000),
7
+ WEB_PORT: Joi.number().default(4200),
8
+ WEB_URL: Joi.string().default(`http://${process.env['HOST'] || 'localhost'}:${process.env['WEB_PORT']}`),
9
+ API_COOKIE_DOMAIN: Joi.string().default('localhost'),
10
+ API_COOKIE_NAME: Joi.string().default('__session'),
11
+ API_URL: Joi.string().default(`http://${process.env['HOST'] || 'localhost'}:${process.env['PORT']}/api`),
12
+ APP_NAME: Joi.string().required(),
13
+ APP_EMAIL: Joi.string().email().required(),
14
+ APP_SUPPORT_EMAIL: Joi.string().email().required(),
15
+ APP_ADMIN_EMAILS: Joi.string().required(),
16
+ SITE_URL: Joi.string().uri().required(),
17
+ SMTP_HOST: Joi.string().required(),
18
+ SMTP_PORT: Joi.string().required(),
19
+ SMTP_USER: Joi.string().required(),
20
+ SMTP_PASS: Joi.string().required(),
21
+ })
@@ -0,0 +1,47 @@
1
+ import { formatFiles, installPackagesTask, Tree } from '@nx/devkit'
2
+ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import { apiLibraryGenerator } from '@nestledjs/utils'
5
+
6
+ import generator from './generator'
7
+
8
+ vi.mock('@nestledjs/utils', async () => {
9
+ const actual = await vi.importActual('@nestledjs/utils')
10
+ return {
11
+ ...actual,
12
+ apiLibraryGenerator: vi.fn(),
13
+ }
14
+ })
15
+
16
+ vi.mock('@nx/devkit', async () => {
17
+ const actual = await vi.importActual('@nx/devkit')
18
+ return {
19
+ ...actual,
20
+ formatFiles: vi.fn(),
21
+ installPackagesTask: vi.fn(),
22
+ }
23
+ })
24
+
25
+ describe('config generator', () => {
26
+ let tree: Tree
27
+
28
+ beforeEach(() => {
29
+ tree = createTreeWithEmptyWorkspace()
30
+ })
31
+
32
+ it('should run successfully', async () => {
33
+ const callback = await generator(tree)
34
+ callback()
35
+
36
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'config', overwrite: false }, expect.any(String))
37
+ expect(formatFiles).toHaveBeenCalledWith(tree)
38
+ expect(installPackagesTask).toHaveBeenCalledWith(tree)
39
+ })
40
+
41
+ it('should pass overwrite flag to apiLibraryGenerator when overwrite is true', async () => {
42
+ const callback = await generator(tree, { overwrite: true })
43
+ callback()
44
+
45
+ expect(apiLibraryGenerator).toHaveBeenCalledWith(tree, { name: 'config', overwrite: true }, expect.any(String))
46
+ })
47
+ })
@@ -0,0 +1,16 @@
1
+ import { formatFiles, installPackagesTask, joinPathFragments, Tree } from '@nx/devkit'
2
+ import { apiLibraryGenerator } from '@nestledjs/utils'
3
+ import { ApiConfigGeneratorSchema } from './schema'
4
+
5
+ export default async function generateLibraries(tree: Tree, options: ApiConfigGeneratorSchema = {}) {
6
+ const templateRootPath = joinPathFragments(__dirname, './files')
7
+ const overwrite = options.overwrite === true
8
+
9
+ await apiLibraryGenerator(tree, { name: 'config', overwrite }, templateRootPath)
10
+
11
+ await formatFiles(tree)
12
+
13
+ return () => {
14
+ installPackagesTask(tree)
15
+ }
16
+ }
@@ -0,0 +1,3 @@
1
+ export interface ApiConfigGeneratorSchema {
2
+ overwrite?: boolean
3
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/schema",
3
+ "$id": "ApiConfig",
4
+ "title": "Create the API Config library",
5
+ "type": "object",
6
+ "properties": {
7
+ "overwrite": {
8
+ "type": "boolean",
9
+ "description": "Whether to overwrite the existing config library if it exists.",
10
+ "default": false
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ export * from './lib/api-core-data-access.service'
2
+ export * from './lib/api-core-data-access.module'
3
+ export * from './lib/api-core-pub-sub'
4
+ export * from './lib/models/core-paging'
5
+ export * from './lib/dto/core-paging.input'
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common'
2
+
3
+ import { ApiCoreDataAccessService } from './api-core-data-access.service'
4
+
5
+ @Module({
6
+ providers: [ApiCoreDataAccessService],
7
+ exports: [ApiCoreDataAccessService],
8
+ })
9
+ export class ApiCoreDataAccessModule {}