@koalarx/nest 4.0.5 → 4.0.8

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 (59) hide show
  1. package/cli/commands/new/configure-test-runner.js +13 -2
  2. package/cli/commands/new/create-ddd-structure.js +2 -4
  3. package/cli/constants/cli-project-checklist.js +27 -3
  4. package/cli/constants/core-packages.js +4 -6
  5. package/cli/utils/install-module.js +15 -7
  6. package/cli/utils/install-workspace-config.js +2 -0
  7. package/cli/utils/parse-new-args.js +0 -1
  8. package/cli/utils/patch-app-test-module.js +29 -0
  9. package/cli/utils/patch-env.js +34 -0
  10. package/cli/utils/patch-main.js +3 -4
  11. package/cli/utils/remove-sample-parts.js +11 -5
  12. package/koala-nest/.env.example +12 -0
  13. package/koala-nest/.vscode/tasks.json +9 -0
  14. package/koala-nest/src/application/common/pagination.request.ts +0 -5
  15. package/koala-nest/src/application/common/request-validator.base.ts +36 -1
  16. package/koala-nest/src/application/mapping/person.mapper.ts +0 -9
  17. package/koala-nest/src/application/person/jobs/cron/delete-inactive.job.ts +34 -17
  18. package/koala-nest/src/application/person/read-many/read-many-person.response.ts +2 -13
  19. package/koala-nest/src/core/background-services/cron-service/cron-job.handler.base.ts +36 -15
  20. package/koala-nest/src/core/background-services/event-service/event-queue.ts +2 -3
  21. package/koala-nest/src/core/common/list-response.base.ts +10 -0
  22. package/koala-nest/src/core/env.ts +5 -0
  23. package/koala-nest/src/core/http/rate-limit.middleware.ts +36 -0
  24. package/koala-nest/src/core/tools/mapping/auto-map.ts +27 -3
  25. package/koala-nest/src/core/tools/mapping/auto-mapper.ts +30 -12
  26. package/koala-nest/src/core/tools/mapping/mapping-store.ts +35 -29
  27. package/koala-nest/src/core/utils/cron-expression-to-boolean.ts +32 -17
  28. package/koala-nest/src/core/utils/hash-password.ts +7 -2
  29. package/koala-nest/src/core/utils/initialize-undefined-array-props.ts +20 -0
  30. package/koala-nest/src/core/utils/resolve-cors-origins.ts +24 -0
  31. package/koala-nest/src/domain/dtos/pagination.dto.ts +87 -2
  32. package/koala-nest/src/domain/dtos/person-query.dto.ts +8 -0
  33. package/koala-nest/src/domain/entities/person/person.ts +0 -2
  34. package/koala-nest/src/host/bootstrap/apply-http-middleware.ts +22 -0
  35. package/koala-nest/src/host/main.ts +14 -14
  36. package/koala-nest/src/infra/common/redis-cache.service.ts +15 -4
  37. package/koala-nest/src/infra/repositories/person.repository.ts +3 -3
  38. package/koala-nest/src/infra/repositories/repository.base.ts +70 -1
  39. package/koala-nest/src/test/application/delete-inactive.job.spec.ts +23 -10
  40. package/koala-nest/src/test/application/read-many-person.handler.spec.ts +42 -2
  41. package/koala-nest/src/test/application/update-person.handler.spec.ts +42 -0
  42. package/koala-nest/src/test/core/cron-expression-to-boolean.spec.ts +20 -1
  43. package/koala-nest/src/test/core/env.spec.ts +19 -0
  44. package/koala-nest/src/test/core/http/rate-limit.middleware.spec.ts +62 -0
  45. package/koala-nest/src/test/core/initialize-undefined-array-props.spec.ts +63 -0
  46. package/koala-nest/src/test/core/mapping.spec.ts +76 -0
  47. package/koala-nest/src/test/core/pagination-typeorm.spec.ts +74 -0
  48. package/koala-nest/src/test/core/resolve-cors-origins.spec.ts +22 -0
  49. package/koala-nest/src/test/core/sync-improvements.spec.ts +39 -0
  50. package/koala-nest/src/test/host/controllers/app/app.e2e.spec.ts +24 -0
  51. package/koala-nest/src/test/host/controllers/person/lazy-loading.e2e.spec.ts +20 -60
  52. package/koala-nest/src/test/infra/redis-cache.service.spec.ts +8 -0
  53. package/koala-nest/src/test/utils/configure-test-app.ts +2 -8
  54. package/koala-nest/src/test/utils/create-e2e-database.ts +17 -1
  55. package/koala-nest/src/test/utils/in-memory-base.repository.ts +80 -0
  56. package/koala-nest/vitest.config.e2e.ts +13 -0
  57. package/koala-nest/vitest.config.ts +12 -0
  58. package/package.json +1 -1
  59. package/koala-nest/src/core/utils/icomparable.ts +0 -1
@@ -6,8 +6,19 @@ export function configureTestRunner(packageJson, packageManager) {
6
6
  scripts["test:watch"] = "bun test --watch";
7
7
  return;
8
8
  }
9
- scripts.test = "vitest run";
10
- scripts["test:watch"] = "vitest";
9
+ scripts.test = "vitest run --config vitest.config.ts";
10
+ scripts["test:watch"] = "vitest --config vitest.config.ts";
11
11
  devDependencies.vitest = "^4.1.8";
12
12
  devDependencies["vite-tsconfig-paths"] = "^5.1.4";
13
13
  }
14
+ export function configureE2ETestRunner(packageJson, packageManager) {
15
+ const scripts = packageJson.scripts;
16
+ const devDependencies = packageJson.devDependencies;
17
+ devDependencies.supertest ??= "^7.1.0";
18
+ devDependencies["@types/supertest"] ??= "^6.0.2";
19
+ if (packageManager === "bun") {
20
+ scripts["test:e2e"] = "bun test --preload ./src/test/setup-e2e.ts src/test/host/controllers/";
21
+ return;
22
+ }
23
+ scripts["test:e2e"] = "vitest run --config vitest.config.e2e.ts";
24
+ }
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { DDD_LAYER_FOLDERS } from "../../constants/domain.js";
4
4
  import { patchGeneratedProjectConfig } from "../../utils/patch-generated-project.js";
5
5
  import { runCommand } from "../../utils/run-command.js";
6
- import { configureTestRunner } from "./configure-test-runner.js";
6
+ import { configureE2ETestRunner, configureTestRunner } from "./configure-test-runner.js";
7
7
  export async function createDDDStructure(projectName, packageManager) {
8
8
  const folders = [...DDD_LAYER_FOLDERS];
9
9
  for (const folder of folders) {
@@ -38,14 +38,12 @@ export async function createDDDStructure(projectName, packageManager) {
38
38
  packageJson.scripts["migration:revert"] = `${migrationRunner} ${typeormCli} migration:revert ${migrationDatasource}`;
39
39
  packageJson.devDependencies ??= {};
40
40
  configureTestRunner(packageJson, packageManager);
41
+ configureE2ETestRunner(packageJson, packageManager);
41
42
  delete packageJson.scripts["test:cov"];
42
43
  delete packageJson.scripts["test:debug"];
43
- delete packageJson.scripts["test:e2e"];
44
44
  delete packageJson.jest;
45
45
  delete packageJson.devDependencies["@types/jest"];
46
- delete packageJson.devDependencies["@types/supertest"];
47
46
  delete packageJson.devDependencies["ts-jest"];
48
- delete packageJson.devDependencies["supertest"];
49
47
  writeFileSync(path.join(process.cwd(), projectName, "package.json"), JSON.stringify(packageJson, null, 2));
50
48
  patchGeneratedProjectConfig(path.join(process.cwd(), projectName));
51
49
  await runCommand([packageManager, "install"], path.join(process.cwd(), projectName));
@@ -5,6 +5,21 @@ import {
5
5
  resolveNewProjectOptions
6
6
  } from "./domain.js";
7
7
  import { resolveProjectFeatures } from "../utils/install-module.js";
8
+ export const E2E_INFRA_PATHS = [
9
+ "src/test/setup-e2e.ts",
10
+ "src/test/create-e2e-test-app.ts",
11
+ "src/test/e2e-context.ts",
12
+ "src/test/utils/create-e2e-database.ts",
13
+ "src/test/utils/e2e-database-client.ts",
14
+ "src/test/host/controllers/app/app.e2e.spec.ts"
15
+ ];
16
+ export const CRUD_E2E_EXAMPLE_PATHS = [
17
+ "src/test/host/controllers/person/person.controller.e2e.spec.ts",
18
+ "src/test/host/controllers/person/lazy-loading.e2e.spec.ts",
19
+ "src/test/host/controllers/auth/auth.controller.e2e.spec.ts",
20
+ "src/test/app-auth-test.module.ts",
21
+ "src/test/create-auth-e2e-test-app.ts"
22
+ ];
8
23
  export const WORKSPACE_SETUP_PATHS = [
9
24
  ".vscode/launch.json",
10
25
  ".vscode/settings.json",
@@ -14,6 +29,11 @@ export const WORKSPACE_SETUP_PATHS = [
14
29
  ];
15
30
  export const CORE_REQUIRED_PATHS = [
16
31
  "src/core/env.ts",
32
+ "src/core/common/list-response.base.ts",
33
+ "src/core/utils/initialize-undefined-array-props.ts",
34
+ "src/core/http/rate-limit.middleware.ts",
35
+ "src/core/utils/resolve-cors-origins.ts",
36
+ "src/host/bootstrap/apply-http-middleware.ts",
17
37
  "src/host/main.ts",
18
38
  "src/host/app.module.ts",
19
39
  "src/host/jobs/jobs.module.ts",
@@ -172,9 +192,13 @@ export function buildProjectExpectation(template, auth, features) {
172
192
  };
173
193
  }
174
194
  export function requiredPathsForExpectation(expectation) {
175
- const paths = [...CORE_REQUIRED_PATHS, ...WORKSPACE_SETUP_PATHS];
195
+ const paths = [
196
+ ...CORE_REQUIRED_PATHS,
197
+ ...WORKSPACE_SETUP_PATHS,
198
+ ...E2E_INFRA_PATHS
199
+ ];
176
200
  if (expectation.template === Template.CRUD_SAMPLE) {
177
- paths.push(...CRUD_TEMPLATE_REQUIRED_PATHS);
201
+ paths.push(...CRUD_TEMPLATE_REQUIRED_PATHS, ...CRUD_E2E_EXAMPLE_PATHS);
178
202
  }
179
203
  if (expectation.cache === "redis") {
180
204
  paths.push(...CACHE_REDIS_REQUIRED_PATHS);
@@ -196,7 +220,7 @@ export function requiredPathsForExpectation(expectation) {
196
220
  export function forbiddenPathsForExpectation(expectation) {
197
221
  const paths = [];
198
222
  if (expectation.template === Template.DEFAULT) {
199
- paths.push(...DEFAULT_TEMPLATE_FORBIDDEN_PATHS);
223
+ paths.push(...DEFAULT_TEMPLATE_FORBIDDEN_PATHS, ...CRUD_E2E_EXAMPLE_PATHS);
200
224
  } else {
201
225
  paths.push(...CRUD_TEMPLATE_FORBIDDEN_PATHS);
202
226
  }
@@ -5,21 +5,19 @@ export const CORE_PACKAGES = [
5
5
  "typeorm",
6
6
  "pg",
7
7
  "zod",
8
- "@scalar/nestjs-api-reference"
8
+ "@scalar/nestjs-api-reference",
9
+ "cookie-parser"
9
10
  ];
11
+ export const CORE_DEV_PACKAGES = ["@types/cookie-parser"];
10
12
  export const CACHE_PACKAGES = ["ioredis"];
11
13
  export const AUTH_PACKAGES = [
12
14
  "@nestjs/jwt",
13
15
  "@nestjs/passport",
14
16
  "passport",
15
17
  "passport-jwt",
16
- "cookie-parser",
17
18
  "bcrypt"
18
19
  ];
19
- export const AUTH_DEV_PACKAGES = [
20
- "@types/cookie-parser",
21
- "@types/bcrypt"
22
- ];
20
+ export const AUTH_DEV_PACKAGES = ["@types/bcrypt"];
23
21
  export const CRON_PACKAGES = ["cron-parser"];
24
22
  export const HEALTH_PACKAGES = ["@nestjs/terminus", "@nestjs/axios"];
25
23
  export function devAddFlag(packageManager) {
@@ -11,6 +11,7 @@ import {
11
11
  AUTH_DEV_PACKAGES,
12
12
  AUTH_PACKAGES,
13
13
  CACHE_PACKAGES,
14
+ CORE_DEV_PACKAGES,
14
15
  CORE_PACKAGES,
15
16
  CRON_PACKAGES,
16
17
  devAddFlag,
@@ -34,7 +35,9 @@ import {
34
35
  stripInfraModuleCache
35
36
  } from "./patch-infra-module.js";
36
37
  import { patchAuthInstall } from "./patch-auth-install.js";
37
- import { patchMainForAuth, stripMainOptionalFeatures } from "./patch-main.js";
38
+ import {
39
+ patchMainForAuth
40
+ } from "./patch-main.js";
38
41
  import { restoreDefineDocumentationWithAuth } from "./patch-define-documentation.js";
39
42
  import { pruneCoreAuthForSlimTemplate } from "./prune-core-auth.js";
40
43
  import { removeSampleParts } from "./remove-sample-parts.js";
@@ -127,15 +130,16 @@ export async function installModule(module, template, projectName = "", options
127
130
  install("src/infra/repositories/repository.base.ts", projectName);
128
131
  install("src/infra/repositories/repository.module.ts", projectName);
129
132
  install("src/infra/infra.module.ts", projectName);
133
+ install("src/core/http/rate-limit.middleware.ts", projectName);
134
+ install("src/core/utils/resolve-cors-origins.ts", projectName);
135
+ install("src/host/bootstrap/apply-http-middleware.ts", projectName);
136
+ install("src/test/core/http/rate-limit.middleware.spec.ts", projectName);
137
+ install("src/test/core/resolve-cors-origins.spec.ts", projectName);
130
138
  install("src/test", projectName);
131
139
  rmSync(path.join(resolveProjectPath(projectName), "src/core/background-services"), { recursive: true, force: true });
132
140
  rmSync(path.join(resolveProjectPath(projectName), "src/core/utils/cron-expression-to-boolean.ts"), { force: true });
133
141
  rmSync(path.join(resolveProjectPath(projectName), "src/core/utils/person-list-cache.ts"), { force: true });
134
142
  rmSync(path.join(resolveProjectPath(projectName), "src/core/utils/build-list-cache-key.ts"), { force: true });
135
- rmSync(path.join(resolveProjectPath(projectName), "src/host/bootstrap"), {
136
- recursive: true,
137
- force: true
138
- });
139
143
  rmSync(path.join(resolveProjectPath(projectName), "src/host/controllers/health-check"), { recursive: true, force: true });
140
144
  rmSync(path.join(resolveProjectPath(projectName), "src/infra/services/database.indicator.service.ts"), { force: true });
141
145
  rmSync(path.join(resolveProjectPath(projectName), "src/infra/services/redis.indicator.service.ts"), { force: true });
@@ -153,10 +157,14 @@ export async function installModule(module, template, projectName = "", options
153
157
  for (const configFile of ["tsconfig.build.json", ".env.example"]) {
154
158
  cpSync(path.join(getSourceCodePath(), configFile), path.join(projectPath, configFile));
155
159
  }
160
+ if (packageManager !== "bun") {
161
+ for (const configFile of ["vitest.config.ts", "vitest.config.e2e.ts"]) {
162
+ cpSync(path.join(getSourceCodePath(), configFile), path.join(projectPath, configFile));
163
+ }
164
+ }
156
165
  patchInfraModuleFile(projectName, false);
157
- patchMainFile(projectName, stripMainOptionalFeatures);
158
166
  if (!options.skipPackages) {
159
- await installPackages(projectName, CORE_PACKAGES);
167
+ await installPackages(projectName, CORE_PACKAGES, CORE_DEV_PACKAGES);
160
168
  }
161
169
  if (template === Template.DEFAULT) {
162
170
  await removeSampleParts(projectName);
@@ -45,6 +45,8 @@ export function installWorkspaceConfig(projectName, packageManager) {
45
45
  task.command = runScriptCommand(packageManager, "start:dev");
46
46
  } else if (task.command.includes("test")) {
47
47
  task.command = runScriptCommand(packageManager, "test");
48
+ } else if (task.command.includes("test:e2e")) {
49
+ task.command = runScriptCommand(packageManager, "test:e2e");
48
50
  } else if (task.command.includes("migration:run")) {
49
51
  task.command = runScriptCommand(packageManager, "migration:run");
50
52
  }
@@ -1,5 +1,4 @@
1
1
  import {
2
- AuthStrategy,
3
2
  FEATURE_ALIASES,
4
3
  parseAuthStrategies,
5
4
  Template,
@@ -0,0 +1,29 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveProjectPath } from "./resolve-project-path.js";
4
+ const defaultAppTestModule = `import { envSchema } from '@/core/env';
5
+ import { Module } from '@nestjs/common';
6
+ import { ConfigModule } from '@nestjs/config';
7
+ import { InfraModule } from '@/infra/infra.module';
8
+ import { e2eDatabaseUrl } from '@/test/e2e-context';
9
+
10
+ @Module({
11
+ imports: [
12
+ ConfigModule.forRoot({
13
+ isGlobal: true,
14
+ ignoreEnvFile: true,
15
+ validate: () =>
16
+ envSchema.parse({
17
+ PORT: 3000,
18
+ NODE_ENV: 'test',
19
+ DATABASE_URL: e2eDatabaseUrl,
20
+ }),
21
+ }),
22
+ InfraModule,
23
+ ],
24
+ })
25
+ export class AppTestModule {}
26
+ `;
27
+ export function patchAppTestModuleForDefault(projectName) {
28
+ writeFileSync(path.join(resolveProjectPath(projectName), "src/test/app-test.module.ts"), defaultAppTestModule);
29
+ }
@@ -8,12 +8,17 @@ import { z } from 'zod';
8
8
 
9
9
  export const envSchema = z.object({
10
10
  PORT: z.coerce.number().default(3000),
11
+ HOST: z.string().default('0.0.0.0'),
11
12
  NODE_ENV: z.enum(['test', 'develop', 'staging', 'production']),
12
13
  DATABASE_URL: z.string(),
13
14
  REDIS_CONNECTION_STRING: z.string().optional(),
14
15
  CACHE_KEY_PREFIX: z.string().optional(),
15
16
  CRON_JOBS_ENABLED: envBooleanSchema(false),
16
17
  BOOTSTRAP_DELAY_MS: z.coerce.number().default(0),
18
+ RATE_LIMIT_MAX: z.coerce.number().default(0),
19
+ RATE_LIMIT_WINDOW_MS: z.coerce.number().default(60_000),
20
+ CORS_ORIGINS: z.string().optional(),
21
+ BCRYPT_ROUNDS: z.coerce.number().min(4).max(15).default(10),
17
22
  });
18
23
 
19
24
  export type Env = z.infer<typeof envSchema>;
@@ -23,6 +28,8 @@ export function validateEnvConfig(config: Record<string, unknown>): Env {
23
28
  }
24
29
  `;
25
30
  const envExampleWithoutAuth = `PORT=3000
31
+ # Endereço de bind do servidor (Docker/K8s). URLs públicas usam API_HOST.
32
+ HOST=0.0.0.0
26
33
  NODE_ENV=develop
27
34
  DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
28
35
 
@@ -35,6 +42,16 @@ DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
35
42
  # Cron jobs internos. Ative com \`kl-nest add cron\`.
36
43
  CRON_JOBS_ENABLED=false
37
44
  BOOTSTRAP_DELAY_MS=0
45
+
46
+ # Rate limit (0 = desabilitado)
47
+ # RATE_LIMIT_MAX=300
48
+ # RATE_LIMIT_WINDOW_MS=60000
49
+
50
+ # CORS — aberto por padrão; restrinja com origens separadas por vírgula se necessário
51
+ # CORS_ORIGINS=http://localhost:4200,https://app.example.com
52
+
53
+ # Custo do bcrypt (padrão 10)
54
+ # BCRYPT_ROUNDS=10
38
55
  `;
39
56
  export function stripEnvAuth(projectName) {
40
57
  const projectRoot = resolveProjectPath(projectName);
@@ -53,12 +70,17 @@ import { z } from 'zod';
53
70
 
54
71
  export const envSchema = z.object({
55
72
  PORT: z.coerce.number().default(3000),
73
+ HOST: z.string().default('0.0.0.0'),
56
74
  NODE_ENV: z.enum(['test', 'develop', 'staging', 'production']),
57
75
  DATABASE_URL: z.string(),
58
76
  REDIS_CONNECTION_STRING: z.string().optional(),
59
77
  CACHE_KEY_PREFIX: z.string().optional(),
60
78
  CRON_JOBS_ENABLED: envBooleanSchema(false),
61
79
  BOOTSTRAP_DELAY_MS: z.coerce.number().default(0),
80
+ RATE_LIMIT_MAX: z.coerce.number().default(0),
81
+ RATE_LIMIT_WINDOW_MS: z.coerce.number().default(60_000),
82
+ CORS_ORIGINS: z.string().optional(),
83
+ BCRYPT_ROUNDS: z.coerce.number().min(4).max(15).default(10),
62
84
  JWT_PRIVATE_KEY: z.string().optional(),
63
85
  JWT_PUBLIC_KEY: z.string().optional(),
64
86
  JWT_ACCESS_TOKEN_EXPIRES_IN: z.string().default('15m'),
@@ -73,6 +95,8 @@ export function validateEnvConfig(config: Record<string, unknown>): Env {
73
95
  }
74
96
  `;
75
97
  const envExampleJwtOnly = `PORT=3000
98
+ # Endereço de bind do servidor (Docker/K8s). URLs públicas usam API_HOST.
99
+ HOST=0.0.0.0
76
100
  NODE_ENV=develop
77
101
  DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
78
102
 
@@ -83,6 +107,16 @@ DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
83
107
  CRON_JOBS_ENABLED=false
84
108
  BOOTSTRAP_DELAY_MS=0
85
109
 
110
+ # Rate limit (0 = desabilitado)
111
+ # RATE_LIMIT_MAX=300
112
+ # RATE_LIMIT_WINDOW_MS=60000
113
+
114
+ # CORS — aberto por padrão; restrinja com origens separadas por vírgula se necessário
115
+ # CORS_ORIGINS=http://localhost:4200,https://app.example.com
116
+
117
+ # Custo do bcrypt (padrão 10)
118
+ # BCRYPT_ROUNDS=10
119
+
86
120
  # JWT (RS256 — chaves em base64)
87
121
  JWT_PRIVATE_KEY=
88
122
  JWT_PUBLIC_KEY=
@@ -1,8 +1,7 @@
1
- export function stripMainOptionalFeatures(content) {
2
- return content.replace(/import cookieParser from 'cookie-parser';\n/, "").replace(/\n {2}app\.use\(cookieParser\(\)\);\n/, `
3
- `);
4
- }
5
1
  export function patchMainForAuth(content) {
2
+ if (content.includes("applyHttpMiddleware")) {
3
+ return content;
4
+ }
6
5
  if (content.includes("cookieParser()")) {
7
6
  return content;
8
7
  }
@@ -2,8 +2,8 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { removeImportLines } from "./project-files.js";
4
4
  import { patchAppModuleJobs } from "./patch-jobs-module.js";
5
+ import { patchAppTestModuleForDefault } from "./patch-app-test-module.js";
5
6
  import { resolveProjectPath } from "./resolve-project-path.js";
6
- import { stripMainOptionalFeatures } from "./patch-main.js";
7
7
  import { stripDefineDocumentationAuth } from "./patch-define-documentation.js";
8
8
  import { stripEnvAuth } from "./patch-env.js";
9
9
  import { pruneCoreAuthForSlimTemplate } from "./prune-core-auth.js";
@@ -85,11 +85,13 @@ const partsToRemove = [
85
85
  replace: [{ from: "PersonMapper.createMap();", to: "" }]
86
86
  }
87
87
  ];
88
- const defaultTemplatePathsToRemove = [
88
+ const defaultSampleTestPathsToRemove = [
89
89
  "src/test/application",
90
90
  "src/test/mockup/person",
91
91
  "src/test/host/controllers/person/person.controller.e2e.spec.ts",
92
- "src/test/host/controllers/person/lazy-loading.e2e.spec.ts",
92
+ "src/test/host/controllers/person/lazy-loading.e2e.spec.ts"
93
+ ];
94
+ const defaultAuthE2ePathsToRemove = [
93
95
  "src/test/host/controllers/auth/auth.controller.e2e.spec.ts",
94
96
  "src/test/app-auth-test.module.ts",
95
97
  "src/test/create-auth-e2e-test-app.ts"
@@ -154,7 +156,11 @@ export async function removeSampleParts(projectName) {
154
156
  writeFileSync(partPath, content, "utf8");
155
157
  }
156
158
  patchAppModuleJobs(projectName, { eventHandlers: [], cronJobs: [] });
157
- removePaths(projectName, defaultTemplatePathsToRemove);
159
+ patchAppTestModuleForDefault(projectName);
160
+ removePaths(projectName, [
161
+ ...defaultSampleTestPathsToRemove,
162
+ ...defaultAuthE2ePathsToRemove
163
+ ]);
158
164
  }
159
165
  export async function cleanDefaultTemplateWithoutAuth(projectName) {
160
166
  stripDefaultProjectAuth(projectName);
@@ -198,6 +204,6 @@ function stripDefaultProjectAuth(projectName) {
198
204
  ]);
199
205
  main = main.replace(/\n {2}app\.useGlobalGuards\([\s\S]*?\);\n/, `
200
206
  `);
201
- writeFileSync(mainPath, stripMainOptionalFeatures(main), "utf8");
207
+ writeFileSync(mainPath, main, "utf8");
202
208
  }
203
209
  }
@@ -1,4 +1,6 @@
1
1
  PORT=3000
2
+ # Endereço de bind do servidor (Docker/K8s). URLs públicas usam API_HOST.
3
+ HOST=0.0.0.0
2
4
  NODE_ENV=develop
3
5
  DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
4
6
 
@@ -12,6 +14,16 @@ DATABASE_URL=postgresql://postgres:root@localhost:5432/koala_nest
12
14
  CRON_JOBS_ENABLED=true
13
15
  BOOTSTRAP_DELAY_MS=0
14
16
 
17
+ # Rate limit (0 = desabilitado)
18
+ # RATE_LIMIT_MAX=300
19
+ # RATE_LIMIT_WINDOW_MS=60000
20
+
21
+ # CORS — aberto por padrão; restrinja com origens separadas por vírgula se necessário
22
+ # CORS_ORIGINS=http://localhost:4200,https://app.example.com
23
+
24
+ # Custo do bcrypt (padrão 10)
25
+ # BCRYPT_ROUNDS=10
26
+
15
27
  # JWT (RS256 — chaves em base64) — ative com `kl-nest new --auth jwt` ou `kl-nest add auth jwt`
16
28
  JWT_PRIVATE_KEY=
17
29
  JWT_PUBLIC_KEY=
@@ -25,6 +25,15 @@
25
25
  "group": "test",
26
26
  "label": "Run tests"
27
27
  },
28
+ {
29
+ "type": "shell",
30
+ "command": "bun run test:e2e",
31
+ "options": {
32
+ "cwd": "${workspaceFolder}"
33
+ },
34
+ "group": "test",
35
+ "label": "Run E2E tests"
36
+ },
28
37
  {
29
38
  "type": "shell",
30
39
  "command": "bun run migration:run",
@@ -3,11 +3,6 @@ import { AutoMap } from '@/core/tools/mapping';
3
3
  import type { QueryDirectionType } from '@/core/types';
4
4
  import { ApiProperty } from '@nestjs/swagger';
5
5
 
6
- export type PaginatedRequestProps<T extends PaginationRequest> = Omit<
7
- { [K in keyof T as T[K] extends Function ? never : K]: T[K] },
8
- '_id'
9
- >;
10
-
11
6
  export class PaginationRequest {
12
7
  @ApiProperty({
13
8
  required: false,
@@ -6,7 +6,7 @@ export abstract class RequestValidatorBase<
6
6
  protected _request: Record<string, any>;
7
7
 
8
8
  constructor(request: TRequest) {
9
- this._request = { ...request };
9
+ this._request = RequestValidatorBase.toValidationInput(request);
10
10
  }
11
11
 
12
12
  validate(): TRequest {
@@ -30,4 +30,39 @@ export abstract class RequestValidatorBase<
30
30
  }
31
31
 
32
32
  protected abstract get schema(): ZodType;
33
+
34
+ /**
35
+ * Objetos plain (query HTTP) passam direto. Instâncias de classe carregam
36
+ * defaults nos fields — removemos os que não foram alterados para o Zod
37
+ * aplicar defaults/transforms sem sobrescrever query params explícitos.
38
+ */
39
+ private static toValidationInput<T extends Record<string, unknown>>(
40
+ request: T,
41
+ ): Record<string, unknown> {
42
+ if (request === null || typeof request !== 'object') {
43
+ return {};
44
+ }
45
+
46
+ const prototype = Object.getPrototypeOf(request);
47
+ const isClassInstance =
48
+ prototype !== null &&
49
+ prototype !== Object.prototype &&
50
+ typeof prototype.constructor === 'function';
51
+
52
+ if (!isClassInstance) {
53
+ return { ...request };
54
+ }
55
+
56
+ const Constructor = prototype.constructor as new () => T;
57
+ const defaultValues = new Constructor() as Record<string, unknown>;
58
+ const input: Record<string, unknown> = { ...request };
59
+
60
+ for (const key of Object.keys(defaultValues)) {
61
+ if (Object.is(input[key], defaultValues[key])) {
62
+ delete input[key];
63
+ }
64
+ }
65
+
66
+ return input;
67
+ }
33
68
  }
@@ -15,11 +15,6 @@ import {
15
15
  ReadPersonContactResponse,
16
16
  ReadPersonResponse,
17
17
  } from '@/application/person/read/read-person.response';
18
- import {
19
- UpdatePersonAddressRequest,
20
- UpdatePersonContactRequest,
21
- UpdatePersonRequest,
22
- } from '@/application/person/update/update-person.request';
23
18
  import { ReadManyPersonResponseItem } from '@/application/person/read-many/read-many-person.response';
24
19
 
25
20
  export class PersonMapper {
@@ -34,10 +29,6 @@ export class PersonMapper {
34
29
  createMap(CreatePersonAddressRequest, PersonAddress);
35
30
  createMap(CreatePersonContactRequest, PersonContact);
36
31
 
37
- createMap(UpdatePersonRequest, Person);
38
- createMap(UpdatePersonAddressRequest, PersonAddress);
39
- createMap(UpdatePersonContactRequest, PersonContact);
40
-
41
32
  createMap(ReadManyPersonRequest, PersonQueryDto);
42
33
  createMap(Person, ReadManyPersonResponseItem);
43
34
  }
@@ -1,16 +1,18 @@
1
1
  import { CronExpression } from '@/core/constants/cron.constants';
2
2
  import { DeletePersonHandler } from '@/application/person/delete/delete-person.handler';
3
- import { ReadManyPersonHandler } from '@/application/person/read-many/read-many-person.handler';
4
- import { ReadManyPersonRequest } from '@/application/person/read-many/read-many-person.request';
5
3
  import {
6
4
  CronJobHandlerBase,
7
5
  CronJobSettings,
8
6
  cronJobSettings,
9
7
  } from '@/core/background-services/cron-service/cron-job.handler.base';
8
+ import { PersonQueryDto } from '@/domain/dtos/person-query.dto';
10
9
  import { ILoggingService } from '@/domain/common/ilogging.service';
11
10
  import { IRedLockService } from '@/domain/common/ired-lock.service';
11
+ import { IPersonRepository } from '@/domain/repositories/iperson.repository';
12
12
  import { Injectable, Logger } from '@nestjs/common';
13
13
 
14
+ const DELETE_INACTIVE_BATCH_SIZE = 100;
15
+
14
16
  @Injectable()
15
17
  export class DeleteInactiveJob extends CronJobHandlerBase {
16
18
  private readonly logger = new Logger(DeleteInactiveJob.name);
@@ -18,7 +20,7 @@ export class DeleteInactiveJob extends CronJobHandlerBase {
18
20
  constructor(
19
21
  redlockService: IRedLockService,
20
22
  loggingService: ILoggingService,
21
- private readonly readManyPerson: ReadManyPersonHandler,
23
+ private readonly repository: IPersonRepository,
22
24
  private readonly deletePerson: DeletePersonHandler,
23
25
  ) {
24
26
  super(redlockService, loggingService);
@@ -31,27 +33,42 @@ export class DeleteInactiveJob extends CronJobHandlerBase {
31
33
  protected async run(): Promise<void> {
32
34
  this.logger.debug('Iniciando remoção de pessoas inativas...');
33
35
 
34
- const request = new ReadManyPersonRequest();
35
- request.active = false;
36
+ let page = 0;
37
+ let totalDeleted = 0;
38
+
39
+ while (true) {
40
+ const query = PersonQueryDto.from({
41
+ active: false,
42
+ page,
43
+ limit: DELETE_INACTIVE_BATCH_SIZE,
44
+ });
45
+ const { items, count } = await this.repository.findMany(query);
46
+
47
+ if (items.length === 0) {
48
+ break;
49
+ }
50
+
51
+ this.logger.debug('Removendo pessoas inativas...', items.length);
52
+
53
+ for (const person of items) {
54
+ await this.deletePerson.handle(person.id);
55
+ }
36
56
 
37
- const result = await this.readManyPerson.handle(request);
57
+ totalDeleted += items.length;
58
+ page += 1;
38
59
 
39
- if (result.items.length === 0) {
60
+ if (page * DELETE_INACTIVE_BATCH_SIZE >= count) {
61
+ break;
62
+ }
63
+ }
64
+
65
+ if (totalDeleted === 0) {
40
66
  this.logger.log('Nenhuma pessoa inativa encontrada.');
41
67
  this.logger.log('Job concluído sem ação.');
42
68
  return;
43
69
  }
44
70
 
45
- this.logger.debug('Removendo pessoas inativas...', result.items.length);
46
-
47
- for (const person of result.items) {
48
- await this.deletePerson.handle(person.id);
49
- }
50
-
51
- this.logger.log(
52
- 'Pessoas inativas removidas com sucesso.',
53
- result.items.length,
54
- );
71
+ this.logger.log('Pessoas inativas removidas com sucesso.', totalDeleted);
55
72
  this.logger.log('Job concluído.');
56
73
  }
57
74
  }
@@ -1,6 +1,5 @@
1
- import { ObjectClass } from '@/core/base/object-class';
1
+ import { ListResponseBase } from '@/core/common/list-response.base';
2
2
  import { AutoMap } from '@/core/tools/mapping';
3
- import { ListResponse } from '@/core/types';
4
3
  import { ApiProperty } from '@nestjs/swagger';
5
4
 
6
5
  export class ReadManyPersonResponseItem {
@@ -17,14 +16,4 @@ export class ReadManyPersonResponseItem {
17
16
  active: boolean;
18
17
  }
19
18
 
20
- export class ReadManyPersonResponse extends ObjectClass<
21
- ListResponse<ReadManyPersonResponseItem>
22
- > {
23
- @ApiProperty({ type: [ReadManyPersonResponseItem] })
24
- @AutoMap({ type: () => ReadManyPersonResponseItem })
25
- items: ReadManyPersonResponseItem[];
26
-
27
- @ApiProperty()
28
- @AutoMap()
29
- count: number;
30
- }
19
+ export class ReadManyPersonResponse extends ListResponseBase<ReadManyPersonResponseItem> {}