@lenne.tech/nest-server 11.31.2 → 11.32.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 (181) hide show
  1. package/.claude/rules/architecture.md +2 -0
  2. package/.claude/rules/configurable-features.md +2 -0
  3. package/.claude/rules/versioning.md +5 -8
  4. package/CLAUDE.md +31 -4
  5. package/FRAMEWORK-API.md +7 -2
  6. package/README.md +1 -0
  7. package/dist/config.env.d.ts +4 -0
  8. package/dist/config.env.js +32 -2
  9. package/dist/config.env.js.map +1 -1
  10. package/dist/core/common/helpers/logging.helper.d.ts +1 -0
  11. package/dist/core/common/helpers/logging.helper.js +12 -0
  12. package/dist/core/common/helpers/logging.helper.js.map +1 -1
  13. package/dist/core/common/helpers/meta.helper.d.ts +1 -0
  14. package/dist/core/common/helpers/meta.helper.js +32 -1
  15. package/dist/core/common/helpers/meta.helper.js.map +1 -1
  16. package/dist/core/common/helpers/process-diagnostics.helper.d.ts +18 -0
  17. package/dist/core/common/helpers/process-diagnostics.helper.js +88 -0
  18. package/dist/core/common/helpers/process-diagnostics.helper.js.map +1 -0
  19. package/dist/core/common/interfaces/server-options.interface.d.ts +12 -0
  20. package/dist/core/common/services/brevo.service.d.ts +7 -1
  21. package/dist/core/common/services/brevo.service.js +37 -16
  22. package/dist/core/common/services/brevo.service.js.map +1 -1
  23. package/dist/core/common/services/email.service.d.ts +3 -1
  24. package/dist/core/common/services/email.service.js +33 -2
  25. package/dist/core/common/services/email.service.js.map +1 -1
  26. package/dist/core/common/services/template.service.js +9 -4
  27. package/dist/core/common/services/template.service.js.map +1 -1
  28. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +9 -4
  29. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
  30. package/dist/core/modules/hub/core-hub-actions.controller.d.ts +22 -0
  31. package/dist/core/modules/hub/core-hub-actions.controller.js +141 -0
  32. package/dist/core/modules/hub/core-hub-actions.controller.js.map +1 -0
  33. package/dist/core/modules/hub/core-hub-html.service.d.ts +8 -0
  34. package/dist/core/modules/hub/core-hub-html.service.js +123 -0
  35. package/dist/core/modules/hub/core-hub-html.service.js.map +1 -0
  36. package/dist/core/modules/hub/core-hub.controller.d.ts +56 -0
  37. package/dist/core/modules/hub/core-hub.controller.js +398 -0
  38. package/dist/core/modules/hub/core-hub.controller.js.map +1 -0
  39. package/dist/core/modules/hub/core-hub.module.d.ts +17 -0
  40. package/dist/core/modules/hub/core-hub.module.js +109 -0
  41. package/dist/core/modules/hub/core-hub.module.js.map +1 -0
  42. package/dist/core/modules/hub/core-hub.service.d.ts +18 -0
  43. package/dist/core/modules/hub/core-hub.service.js +153 -0
  44. package/dist/core/modules/hub/core-hub.service.js.map +1 -0
  45. package/dist/core/modules/hub/helpers/hub-client-js.helper.d.ts +1 -0
  46. package/dist/core/modules/hub/helpers/hub-client-js.helper.js +755 -0
  47. package/dist/core/modules/hub/helpers/hub-client-js.helper.js.map +1 -0
  48. package/dist/core/modules/hub/helpers/hub-command-shape.helper.d.ts +1 -0
  49. package/dist/core/modules/hub/helpers/hub-command-shape.helper.js +33 -0
  50. package/dist/core/modules/hub/helpers/hub-command-shape.helper.js.map +1 -0
  51. package/dist/core/modules/hub/helpers/hub-mask.helper.d.ts +1 -0
  52. package/dist/core/modules/hub/helpers/hub-mask.helper.js +46 -0
  53. package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -0
  54. package/dist/core/modules/hub/helpers/hub-mermaid.helper.d.ts +10 -0
  55. package/dist/core/modules/hub/helpers/hub-mermaid.helper.js +35 -0
  56. package/dist/core/modules/hub/helpers/hub-mermaid.helper.js.map +1 -0
  57. package/dist/core/modules/hub/helpers/hub-shell.helper.d.ts +9 -0
  58. package/dist/core/modules/hub/helpers/hub-shell.helper.js +62 -0
  59. package/dist/core/modules/hub/helpers/hub-shell.helper.js.map +1 -0
  60. package/dist/core/modules/hub/hub-action-messages.d.ts +17 -0
  61. package/dist/core/modules/hub/hub-action-messages.js +21 -0
  62. package/dist/core/modules/hub/hub-action-messages.js.map +1 -0
  63. package/dist/core/modules/hub/hub-config.helper.d.ts +11 -0
  64. package/dist/core/modules/hub/hub-config.helper.js +164 -0
  65. package/dist/core/modules/hub/hub-config.helper.js.map +1 -0
  66. package/dist/core/modules/hub/hub-nav.d.ts +12 -0
  67. package/dist/core/modules/hub/hub-nav.js +43 -0
  68. package/dist/core/modules/hub/hub-nav.js.map +1 -0
  69. package/dist/core/modules/hub/hub-ring-buffer.d.ts +22 -0
  70. package/dist/core/modules/hub/hub-ring-buffer.js +59 -0
  71. package/dist/core/modules/hub/hub-ring-buffer.js.map +1 -0
  72. package/dist/core/modules/hub/hub.constants.d.ts +18 -0
  73. package/dist/core/modules/hub/hub.constants.js +22 -0
  74. package/dist/core/modules/hub/hub.constants.js.map +1 -0
  75. package/dist/core/modules/hub/index.d.ts +25 -0
  76. package/dist/core/modules/hub/index.js +42 -0
  77. package/dist/core/modules/hub/index.js.map +1 -0
  78. package/dist/core/modules/hub/interfaces/hub-config.interface.d.ts +115 -0
  79. package/dist/core/modules/hub/interfaces/hub-config.interface.js +3 -0
  80. package/dist/core/modules/hub/interfaces/hub-config.interface.js.map +1 -0
  81. package/dist/core/modules/hub/interfaces/hub-panels.interface.d.ts +231 -0
  82. package/dist/core/modules/hub/interfaces/hub-panels.interface.js +3 -0
  83. package/dist/core/modules/hub/interfaces/hub-panels.interface.js.map +1 -0
  84. package/dist/core/modules/hub/middleware/hub-trace.middleware.d.ts +8 -0
  85. package/dist/core/modules/hub/middleware/hub-trace.middleware.js +50 -0
  86. package/dist/core/modules/hub/middleware/hub-trace.middleware.js.map +1 -0
  87. package/dist/core/modules/hub/services/core-hub-actions.service.d.ts +48 -0
  88. package/dist/core/modules/hub/services/core-hub-actions.service.js +136 -0
  89. package/dist/core/modules/hub/services/core-hub-actions.service.js.map +1 -0
  90. package/dist/core/modules/hub/services/core-hub-db.service.d.ts +19 -0
  91. package/dist/core/modules/hub/services/core-hub-db.service.js +180 -0
  92. package/dist/core/modules/hub/services/core-hub-db.service.js.map +1 -0
  93. package/dist/core/modules/hub/services/core-hub-email.service.d.ts +24 -0
  94. package/dist/core/modules/hub/services/core-hub-email.service.js +148 -0
  95. package/dist/core/modules/hub/services/core-hub-email.service.js.map +1 -0
  96. package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +20 -0
  97. package/dist/core/modules/hub/services/core-hub-mailbox.service.js +103 -0
  98. package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -0
  99. package/dist/core/modules/hub/services/core-hub-migrations.service.d.ts +25 -0
  100. package/dist/core/modules/hub/services/core-hub-migrations.service.js +115 -0
  101. package/dist/core/modules/hub/services/core-hub-migrations.service.js.map +1 -0
  102. package/dist/core/modules/hub/services/core-hub-sources.service.d.ts +28 -0
  103. package/dist/core/modules/hub/services/core-hub-sources.service.js +187 -0
  104. package/dist/core/modules/hub/services/core-hub-sources.service.js.map +1 -0
  105. package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +24 -0
  106. package/dist/core/modules/hub/services/hub-log-buffer.service.js +210 -0
  107. package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -0
  108. package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +38 -0
  109. package/dist/core/modules/hub/services/hub-query-profiler.service.js +235 -0
  110. package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -0
  111. package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +18 -0
  112. package/dist/core/modules/hub/services/hub-trace-buffer.service.js +123 -0
  113. package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -0
  114. package/dist/core.module.js +42 -1
  115. package/dist/core.module.js.map +1 -1
  116. package/dist/index.d.ts +2 -0
  117. package/dist/index.js +2 -0
  118. package/dist/index.js.map +1 -1
  119. package/dist/main.js +5 -2
  120. package/dist/main.js.map +1 -1
  121. package/dist/tsconfig.build.tsbuildinfo +1 -1
  122. package/docs/REQUEST-LIFECYCLE.md +2 -0
  123. package/docs/brevo-manual-test.md +166 -0
  124. package/docs/security-overrides.md +90 -0
  125. package/migration-guides/11.31.2-to-11.31.3.md +135 -0
  126. package/migration-guides/11.31.3-to-11.32.0.md +254 -0
  127. package/package.json +16 -14
  128. package/src/config.env.ts +75 -2
  129. package/src/core/common/helpers/logging.helper.spec.ts +61 -0
  130. package/src/core/common/helpers/logging.helper.ts +48 -0
  131. package/src/core/common/helpers/meta.helper.ts +46 -1
  132. package/src/core/common/helpers/process-diagnostics.helper.spec.ts +310 -0
  133. package/src/core/common/helpers/process-diagnostics.helper.ts +321 -0
  134. package/src/core/common/interfaces/server-options.interface.ts +78 -0
  135. package/src/core/common/services/brevo.service.spec.ts +266 -0
  136. package/src/core/common/services/brevo.service.ts +100 -17
  137. package/src/core/common/services/email.service.ts +33 -1
  138. package/src/core/common/services/template.service.ts +21 -16
  139. package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +14 -4
  140. package/src/core/modules/hub/INTEGRATION-CHECKLIST.md +64 -0
  141. package/src/core/modules/hub/README.md +159 -0
  142. package/src/core/modules/hub/core-hub-actions.controller.ts +137 -0
  143. package/src/core/modules/hub/core-hub-html.service.ts +135 -0
  144. package/src/core/modules/hub/core-hub.controller.ts +286 -0
  145. package/src/core/modules/hub/core-hub.module.spec.ts +108 -0
  146. package/src/core/modules/hub/core-hub.module.ts +159 -0
  147. package/src/core/modules/hub/core-hub.service.ts +169 -0
  148. package/src/core/modules/hub/helpers/hub-client-js.helper.ts +768 -0
  149. package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +48 -0
  150. package/src/core/modules/hub/helpers/hub-command-shape.helper.ts +47 -0
  151. package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +67 -0
  152. package/src/core/modules/hub/helpers/hub-mask.helper.ts +78 -0
  153. package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +54 -0
  154. package/src/core/modules/hub/helpers/hub-mermaid.helper.ts +62 -0
  155. package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +106 -0
  156. package/src/core/modules/hub/helpers/hub-shell.helper.ts +90 -0
  157. package/src/core/modules/hub/hub-action-messages.ts +47 -0
  158. package/src/core/modules/hub/hub-config.helper.spec.ts +108 -0
  159. package/src/core/modules/hub/hub-config.helper.ts +233 -0
  160. package/src/core/modules/hub/hub-nav.ts +66 -0
  161. package/src/core/modules/hub/hub-ring-buffer.spec.ts +95 -0
  162. package/src/core/modules/hub/hub-ring-buffer.ts +101 -0
  163. package/src/core/modules/hub/hub.constants.ts +84 -0
  164. package/src/core/modules/hub/index.ts +25 -0
  165. package/src/core/modules/hub/interfaces/hub-config.interface.ts +265 -0
  166. package/src/core/modules/hub/interfaces/hub-panels.interface.ts +186 -0
  167. package/src/core/modules/hub/middleware/hub-trace.middleware.ts +45 -0
  168. package/src/core/modules/hub/services/core-hub-actions.service.ts +133 -0
  169. package/src/core/modules/hub/services/core-hub-db.service.ts +185 -0
  170. package/src/core/modules/hub/services/core-hub-email.service.ts +158 -0
  171. package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +116 -0
  172. package/src/core/modules/hub/services/core-hub-mailbox.service.ts +121 -0
  173. package/src/core/modules/hub/services/core-hub-migrations.service.ts +112 -0
  174. package/src/core/modules/hub/services/core-hub-sources.service.ts +194 -0
  175. package/src/core/modules/hub/services/hub-log-buffer.service.ts +252 -0
  176. package/src/core/modules/hub/services/hub-query-profiler.service.ts +274 -0
  177. package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +112 -0
  178. package/src/core/modules/hub/services/hub-trace-buffer.service.ts +134 -0
  179. package/src/core.module.ts +61 -1
  180. package/src/index.ts +7 -0
  181. package/src/main.ts +22 -3
@@ -0,0 +1,25 @@
1
+ export * from './core-hub-actions.controller';
2
+ export * from './core-hub-html.service';
3
+ export * from './core-hub.controller';
4
+ export * from './core-hub.module';
5
+ export * from './core-hub.service';
6
+ export * from './services/core-hub-actions.service';
7
+ export * from './services/core-hub-db.service';
8
+ export * from './services/core-hub-email.service';
9
+ export * from './services/core-hub-mailbox.service';
10
+ export * from './services/core-hub-migrations.service';
11
+ export * from './services/core-hub-sources.service';
12
+ export * from './services/hub-log-buffer.service';
13
+ export * from './services/hub-query-profiler.service';
14
+ export * from './services/hub-trace-buffer.service';
15
+ export * from './middleware/hub-trace.middleware';
16
+ export * from './helpers/hub-command-shape.helper';
17
+ export * from './helpers/hub-mask.helper';
18
+ export * from './helpers/hub-mermaid.helper';
19
+ export * from './helpers/hub-shell.helper';
20
+ export * from './hub-config.helper';
21
+ export * from './hub-nav';
22
+ export * from './hub-ring-buffer';
23
+ export * from './hub.constants';
24
+ export * from './interfaces/hub-config.interface';
25
+ export * from './interfaces/hub-panels.interface';
@@ -0,0 +1,265 @@
1
+ import type { LogLevel } from '@nestjs/common';
2
+
3
+ /**
4
+ * Configuration for the Hub admin area (operator cockpit).
5
+ *
6
+ * Follows the "presence implies enabled" pattern (see .claude/rules/configurable-features.md):
7
+ * - `true`: enabled with all defaults (admin-only, served at `/hub`)
8
+ * - `{}`: same as `true`
9
+ * - `{ path: 'admin/hub' }`: enabled with a custom base path
10
+ * - `{ enabled: false, ... }`: pre-configured but disabled
11
+ * - `undefined`: disabled — every Hub route answers 404
12
+ *
13
+ * The Hub is NEVER enabled implicitly. It must be switched on per environment (in `config.env.ts`
14
+ * or via `NSC__HUB__*` env vars). No environment inherits it.
15
+ */
16
+ export interface IHubConfig {
17
+ /**
18
+ * Master switch for every mutating endpoint (migrations run/down, cron control, buffer clears,
19
+ * file delete, test mail). When `false`, the actions controller is not registered at all, so
20
+ * those routes answer 404.
21
+ *
22
+ * @default true
23
+ */
24
+ actions?: boolean;
25
+
26
+ /**
27
+ * Runtime collectors feeding the Logs, Traces and Queries panels.
28
+ * When the Hub is enabled: `logs` and `traces` default on, `queries` defaults off
29
+ * (query profiling opts the MongoDB driver into command monitoring, so it is opt-in).
30
+ */
31
+ collectors?: IHubCollectorsConfig;
32
+
33
+ /**
34
+ * MongoDB stats panel (dbStats, per-collection collStats).
35
+ *
36
+ * @default true
37
+ */
38
+ db?: boolean | IHubDbConfig;
39
+
40
+ /**
41
+ * Whether the Hub is enabled. Presence of the config object already implies `true`; set to
42
+ * `false` to keep a pre-configured block dormant.
43
+ */
44
+ enabled?: boolean;
45
+
46
+ /**
47
+ * Email preview panel (renders EJS templates with sample data).
48
+ *
49
+ * @default true
50
+ */
51
+ emailPreview?: boolean;
52
+
53
+ /** External links surfaced on the dashboard and in the navigation. Set a value to `false` to hide it. */
54
+ links?: IHubLinksConfig;
55
+
56
+ /**
57
+ * Endpoint the built-in login form POSTs `{ email, password }` to. It must set a session cookie on
58
+ * success (BetterAuth IAM does). Lets an admin log into the Hub directly, without the frontend.
59
+ *
60
+ * @default '/iam/sign-in/email'
61
+ */
62
+ loginEndpoint?: string;
63
+
64
+ /**
65
+ * Endpoint the built-in "Sign out" button POSTs to. It must clear the session cookie (BetterAuth
66
+ * IAM's `/iam/sign-out` does). Only used after authentication, so it is delivered in the ADMIN-gated
67
+ * session payload, never in the public shell.
68
+ *
69
+ * @default '/iam/sign-out'
70
+ */
71
+ logoutEndpoint?: string;
72
+
73
+ /**
74
+ * Built-in mailbox (a Mailpit-style capture of outgoing mail for local/test use).
75
+ *
76
+ * @default disabled
77
+ */
78
+ mailbox?: boolean | IHubMailboxConfig;
79
+
80
+ /**
81
+ * Migrations panel + actions.
82
+ *
83
+ * @default enabled with dir `./migrations`, collection `migrations`
84
+ */
85
+ migrations?: false | IHubMigrationsConfig;
86
+
87
+ /**
88
+ * Base path for all Hub routes.
89
+ *
90
+ * @default 'hub'
91
+ */
92
+ path?: string;
93
+
94
+ /**
95
+ * Default client poll interval (ms) for JSON sidecars. Clamped to a minimum of 1000 ms.
96
+ *
97
+ * @default 5000
98
+ */
99
+ pollIntervalMs?: number;
100
+
101
+ /**
102
+ * Role(s) required to access the Hub.
103
+ * - `undefined`: defaults to `RoleEnum.ADMIN`
104
+ * - `string | string[]`: required role(s), OR semantics
105
+ * - `false`: NO auth check at all — every route becomes public. Only ever use behind a network
106
+ * boundary you fully control; a public Hub exposes config, logs and destructive actions.
107
+ * **Forbidden in `production`/`staging` unless {@link allowPublicAccessInProduction} is set** —
108
+ * see that flag.
109
+ *
110
+ * @default RoleEnum.ADMIN
111
+ */
112
+ roles?: false | string | string[];
113
+
114
+ /**
115
+ * Acknowledge, explicitly, that a PUBLIC Hub (`roles: false`, no auth check) may run in a reachable
116
+ * environment. Without this, `roles: false` throws at startup in `production`/`staging`, because it
117
+ * exposes the config viewer, logs and DESTRUCTIVE admin actions (migrations, file delete, cron) to
118
+ * unauthenticated requests. Set to `true` ONLY when the Hub sits behind a fully-controlled network
119
+ * boundary (VPN / IP allow-list / authenticating reverse proxy). Has no effect unless `roles: false`.
120
+ *
121
+ * @default false
122
+ */
123
+ allowPublicAccessInProduction?: boolean;
124
+ }
125
+
126
+ /** Collector toggles (each: boolean shorthand or an options object). */
127
+ export interface IHubCollectorsConfig {
128
+ /** Log ring buffer. @default on (capacity 500). */
129
+ logs?: boolean | IHubLogsConfig;
130
+ /** MongoDB query profiler (driver command monitoring). @default off. */
131
+ queries?: boolean | IHubQueriesConfig;
132
+ /** HTTP request traces. @default on (capacity 200). */
133
+ traces?: boolean | IHubTracesConfig;
134
+ }
135
+
136
+ export interface IHubCollectorBaseConfig {
137
+ /** Ring-buffer capacity (entries). */
138
+ capacity?: number;
139
+ /** @default true when the parent object is present. */
140
+ enabled?: boolean;
141
+ }
142
+
143
+ export interface IHubDbConfig {
144
+ enabled?: boolean;
145
+ /** Include index size/definitions per collection (extra collStats work). @default false. */
146
+ includeIndexes?: boolean;
147
+ }
148
+
149
+ export interface IHubLinksConfig {
150
+ /** GraphQL sandbox link. @default '/graphql' when GraphQL is enabled. */
151
+ graphql?: false | string;
152
+ /** Externally hosted Mailpit URL. @default undefined. */
153
+ mailpit?: string;
154
+ /** Permissions cockpit link. @default derived from the permissions module path when enabled. */
155
+ permissions?: false | string;
156
+ /** Swagger UI link. @default '/swagger'. */
157
+ swagger?: false | string;
158
+ }
159
+
160
+ export interface IHubLogsConfig extends IHubCollectorBaseConfig {
161
+ /** Context names never captured (e.g. noisy framework contexts). @default []. */
162
+ excludeContexts?: string[];
163
+ /** Levels captured into the buffer (independent of console output level). @default ['log','warn','error','fatal']. */
164
+ levels?: LogLevel[];
165
+ /** Per-record message cap (characters). @default 2048. */
166
+ maxMessageLength?: number;
167
+ }
168
+
169
+ export interface IHubMailboxConfig {
170
+ /** Number of captured mails retained. @default 100. */
171
+ capacity?: number;
172
+ enabled?: boolean;
173
+ /** Per-field body cap (bytes); the html and text bodies are EACH truncated to this. @default 262144. */
174
+ maxMailSize?: number;
175
+ /**
176
+ * - `capture`: intercept mail and DO NOT send it — for local/test use (a Mailpit replacement).
177
+ * Throws at startup in `production`/`staging` to prevent silently swallowing real mail.
178
+ * - `copy`: send mail normally AND record a copy in the mailbox.
179
+ *
180
+ * @default 'capture'
181
+ */
182
+ mode?: 'capture' | 'copy';
183
+ }
184
+
185
+ export interface IHubMigrationsConfig {
186
+ /** MongoDB collection holding migration state. @default 'migrations'. */
187
+ collectionName?: string;
188
+ /** Filesystem directory of migration files. @default './migrations'. */
189
+ dir?: string;
190
+ enabled?: boolean;
191
+ /** Collection used for cluster-safe locking during migration actions. @default undefined. */
192
+ lockCollectionName?: string;
193
+ }
194
+
195
+ export interface IHubQueriesConfig extends IHubCollectorBaseConfig {
196
+ /** Duration (ms) above which a query is classified "critical". @default 200. */
197
+ criticalMs?: number;
198
+ /** Command names never recorded (replaces the default admin/heartbeat ignore set when provided). */
199
+ ignoreCommands?: string[];
200
+ /** Command-summary length cap (characters). @default 512. */
201
+ maxShapeLength?: number;
202
+ /** Duration (ms) above which a query is classified "warn". @default 50. */
203
+ warnMs?: number;
204
+ }
205
+
206
+ export interface IHubTracesConfig extends IHubCollectorBaseConfig {
207
+ /** Capture the GraphQL operationName for POST /graphql. @default true. */
208
+ captureGraphQlOperation?: boolean;
209
+ /** Path prefixes never traced. The Hub base path is always added on top. @default ['/hub']. */
210
+ excludePaths?: string[];
211
+ /** Duration (ms) above which a trace is flagged slow. @default 1000. */
212
+ slowMs?: number;
213
+ }
214
+
215
+ /**
216
+ * Fully-resolved Hub configuration (all defaults applied, booleans normalized to objects).
217
+ * This is the shape bound to the `HUB_CONFIG` token and injected everywhere in the module.
218
+ */
219
+ export interface ResolvedHubConfig {
220
+ actions: boolean;
221
+ /** Explicit acknowledgment that lets a public (`roles: false`) Hub run in production/staging. */
222
+ allowPublicAccessInProduction: boolean;
223
+ collectors: {
224
+ logs: false | Required<Omit<IHubLogsConfig, 'enabled'>>;
225
+ queries: false | (Required<Omit<IHubQueriesConfig, 'enabled' | 'ignoreCommands'>> & { ignoreCommands?: string[] });
226
+ traces: false | Required<Omit<IHubTracesConfig, 'enabled'>>;
227
+ };
228
+ db: false | { includeIndexes: boolean };
229
+ emailPreview: boolean;
230
+ /** The environment name (from `IServerOptions.env`), used for the production mailbox guard + header badge. */
231
+ env: string;
232
+ links: { graphql?: string; mailpit?: string; permissions?: string; swagger?: string };
233
+ loginEndpoint: string;
234
+ logoutEndpoint: string;
235
+ mailbox: false | { capacity: number; maxMailSize: number; mode: 'capture' | 'copy' };
236
+ migrations: false | { collectionName: string; dir: string; lockCollectionName?: string };
237
+ path: string;
238
+ pollIntervalMs: number;
239
+ roles: false | string[];
240
+ /** The framework/app version (from `IServerOptions.version`), used for the header badge + hub.js cache-busting. */
241
+ version: string;
242
+ }
243
+
244
+ /**
245
+ * The capture hook injected into `EmailService`. Implemented by the mailbox service.
246
+ * Kept minimal so `EmailService` never depends on the Hub module type graph.
247
+ */
248
+ export interface IHubEmailCapture {
249
+ /**
250
+ * Record an outgoing mail. Returns `true` when the caller should SKIP the actual transport
251
+ * (mailbox `mode: 'capture'`), `false` when it should send normally (`mode: 'copy'`).
252
+ */
253
+ capture(mail: IHubCapturedMailInput): boolean;
254
+ }
255
+
256
+ export interface IHubCapturedMailInput {
257
+ bcc?: string;
258
+ cc?: string;
259
+ from?: string;
260
+ html?: string;
261
+ subject?: string;
262
+ templateName?: string;
263
+ text?: string;
264
+ to?: string;
265
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Response DTOs for the Hub JSON sidecars — the stable contract between the server data providers
3
+ * and the client runtime (and any future richer UI). Every panel that depends on an optional source
4
+ * returns `{ available: false, hint }` instead of erroring when that source is absent.
5
+ */
6
+
7
+ export interface HubUnavailable {
8
+ available: false;
9
+ hint: string;
10
+ }
11
+
12
+ /**
13
+ * Cockpit-chrome data served (ADMIN-gated) by `GET /{hub}/session.json`. The public shell HTML
14
+ * carries none of this — the client builds the navigation, environment badge, version and external
15
+ * links from this payload only AFTER the auth probe succeeds, so an unauthenticated request reveals
16
+ * nothing of the Hub's structure.
17
+ */
18
+ export interface HubSessionData {
19
+ authenticated: true;
20
+ env: string;
21
+ links: Record<string, string | undefined>;
22
+ logoutEndpoint: string;
23
+ panelGroups: string[];
24
+ panels: { available: boolean; group: string; id: string; optional: boolean; path: string; title: string }[];
25
+ version: string;
26
+ }
27
+
28
+ export interface HubDashboardData {
29
+ build: { commit?: string; env: string; version: string };
30
+ features: Record<string, boolean>;
31
+ health?: { details?: Record<string, unknown>; status: string };
32
+ links: Record<string, string>;
33
+ memory: { heapTotal: number; heapUsed: number; rss: number };
34
+ mongo: { readyState: number; state: string };
35
+ time: string;
36
+ uptimeSeconds: number;
37
+ }
38
+
39
+ export interface HubDiagnosticsData {
40
+ arch: string;
41
+ buffers: Record<string, { capacity: number; enabled: boolean; size: number }>;
42
+ cpuUsage: { system: number; user: number };
43
+ env: string;
44
+ memory: { arrayBuffers: number; external: number; heapTotal: number; heapUsed: number; rss: number };
45
+ nodeVersion: string;
46
+ pid: number;
47
+ platform: string;
48
+ time: string;
49
+ uptimeSeconds: number;
50
+ }
51
+
52
+ export interface HubDbData {
53
+ collections: HubDbCollection[];
54
+ stats: { collections: number; dataSize: number; indexSize: number; objects: number; storageSize: number };
55
+ }
56
+
57
+ export interface HubDbCollection {
58
+ avgObjSize?: number;
59
+ count: number;
60
+ indexCount?: number;
61
+ indexSize?: number;
62
+ name: string;
63
+ size: number;
64
+ storageSize: number;
65
+ }
66
+
67
+ export interface HubModelsData {
68
+ entities: { fields: { name: string; ref?: string; type: string }[]; name: string }[];
69
+ mermaid: string;
70
+ modelCount: number;
71
+ relationCount: number;
72
+ }
73
+
74
+ export interface HubMigrationsData {
75
+ completed: string[];
76
+ dirAvailable: boolean;
77
+ lastRun?: string;
78
+ pending: string[];
79
+ source: 'collection' | 'runner';
80
+ }
81
+
82
+ export interface HubFilesData {
83
+ bucket: string;
84
+ files: { contentType?: string; filename: string; id: string; length: number; uploadDate?: string }[];
85
+ total: number;
86
+ }
87
+
88
+ export interface HubCronData {
89
+ intervals: string[];
90
+ jobs: { lastDate?: string; name: string; nextDate?: string; running: boolean }[];
91
+ timeouts: string[];
92
+ }
93
+
94
+ export interface HubErrorCodesData {
95
+ codes: { code: string; de?: string; en?: string }[];
96
+ locale: string;
97
+ }
98
+
99
+ export interface HubEmailsData {
100
+ templates: { locales: string[]; name: string; source: 'framework' | 'project' }[];
101
+ }
102
+
103
+ export interface HubMailboxData {
104
+ cursor: number;
105
+ dropped: number;
106
+ mails: HubMailboxEntry[];
107
+ mode: 'capture' | 'copy';
108
+ }
109
+
110
+ export interface HubLogRecord {
111
+ context?: string;
112
+ level: string;
113
+ message: string;
114
+ seq: number;
115
+ stack?: string;
116
+ timestamp: number;
117
+ }
118
+
119
+ export interface HubLogsData {
120
+ cursor: number;
121
+ dropped: number;
122
+ records: HubLogRecord[];
123
+ }
124
+
125
+ export interface HubTraceRecord {
126
+ aborted?: boolean;
127
+ contentLength?: number;
128
+ durationMs: number;
129
+ error?: boolean;
130
+ graphqlOperation?: string;
131
+ method: string;
132
+ path: string;
133
+ seq: number;
134
+ slow?: boolean;
135
+ statusCode: number;
136
+ timestamp: number;
137
+ userId?: string;
138
+ }
139
+
140
+ export interface HubTracesData {
141
+ cursor: number;
142
+ dropped: number;
143
+ summary: { avgMs: number; errorCount: number; slowCount: number; total: number };
144
+ traces: HubTraceRecord[];
145
+ }
146
+
147
+ export interface HubQueryRecord {
148
+ classification: 'critical' | 'ok' | 'warn';
149
+ collection: string;
150
+ commandSummary: string;
151
+ durationMs: number;
152
+ errorMessage?: string;
153
+ failed?: boolean;
154
+ operation: string;
155
+ requestId: number;
156
+ seq: number;
157
+ timestamp: number;
158
+ }
159
+
160
+ export interface HubQueryTemplate {
161
+ avgMs: number;
162
+ count: number;
163
+ maxMs: number;
164
+ template: string;
165
+ }
166
+
167
+ export interface HubQueriesData {
168
+ cursor: number;
169
+ recent: HubQueryRecord[];
170
+ slowest: HubQueryRecord[];
171
+ summary: { avgMs: number; criticalCount: number; failedCount: number; total: number; warnCount: number };
172
+ topTemplates: HubQueryTemplate[];
173
+ }
174
+
175
+ export interface HubMailboxEntry {
176
+ bcc?: string;
177
+ cc?: string;
178
+ from?: string;
179
+ hasHtml: boolean;
180
+ hasText: boolean;
181
+ seq: number;
182
+ subject?: string;
183
+ templateName?: string;
184
+ timestamp: number;
185
+ to?: string;
186
+ }
@@ -0,0 +1,45 @@
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { NextFunction, Request, Response } from 'express';
3
+
4
+ import { HubTraceBufferService } from '../services/hub-trace-buffer.service';
5
+
6
+ /**
7
+ * Measures request duration and records a trace at response completion.
8
+ *
9
+ * Registered by `CoreHubModule.configure()` ONLY when the traces collector is enabled (zero cost
10
+ * otherwise — not even a no-op layer). Uses `hrtime` for timing and a latch so `finish` and `close`
11
+ * (Express 5 / Node http.ServerResponse) never double-record; `close` without `writableFinished`
12
+ * marks a client-aborted request.
13
+ */
14
+ @Injectable()
15
+ export class HubTraceMiddleware implements NestMiddleware {
16
+ constructor(private readonly traceBuffer: HubTraceBufferService) {}
17
+
18
+ use(req: Request, res: Response, next: NextFunction): void {
19
+ // NestJS mounts `forRoutes('*')` middleware at a level where `req.path` is stripped to '/'; the
20
+ // full request path lives in `req.originalUrl`. Use that for the exclusion check (and pass it to
21
+ // the recorder as a fallback), otherwise the Hub's own routes are never excluded.
22
+ const fullPath = (req.originalUrl || req.url || '').split('?')[0] || '/';
23
+ if (!this.traceBuffer.enabled || this.traceBuffer.isExcluded(fullPath)) {
24
+ next();
25
+ return;
26
+ }
27
+ const start = process.hrtime.bigint();
28
+ let recorded = false;
29
+ const record = (aborted: boolean): void => {
30
+ if (recorded) {
31
+ return;
32
+ }
33
+ recorded = true;
34
+ const durationMs = Number(process.hrtime.bigint() - start) / 1e6;
35
+ try {
36
+ this.traceBuffer.record(req, res, durationMs, aborted, fullPath);
37
+ } catch {
38
+ /* never let trace recording break the response path */
39
+ }
40
+ };
41
+ res.on('finish', () => record(false));
42
+ res.on('close', () => record(!res.writableFinished));
43
+ next();
44
+ }
45
+ }
@@ -0,0 +1,133 @@
1
+ import { Injectable, Logger, Optional } from '@nestjs/common';
2
+
3
+ import { EmailService } from '../../../common/services/email.service';
4
+ import { RequestContext } from '../../../common/services/request-context.service';
5
+ import { HubActionMessage } from '../hub-action-messages';
6
+ import { CoreHubDbService } from './core-hub-db.service';
7
+ import { CoreHubEmailService } from './core-hub-email.service';
8
+ import { CoreHubMailboxService } from './core-hub-mailbox.service';
9
+ import { CoreHubMigrationsService } from './core-hub-migrations.service';
10
+ import { CoreHubSourcesService } from './core-hub-sources.service';
11
+ import { HubLogBufferService } from './hub-log-buffer.service';
12
+ import { HubQueryProfilerService } from './hub-query-profiler.service';
13
+ import { HubTraceBufferService } from './hub-trace-buffer.service';
14
+
15
+ /** Illustrative sample data for the test-mail action. */
16
+ const TEST_MAIL_DATA = {
17
+ appName: 'Nest Server',
18
+ firstName: 'Hub',
19
+ link: 'https://example.com/verify?token=SAMPLE',
20
+ name: 'Hub Admin',
21
+ url: 'https://example.com/verify?token=SAMPLE',
22
+ };
23
+
24
+ /**
25
+ * Executes the Hub's mutating actions, each behind an audit log line recording the acting admin.
26
+ *
27
+ * Read/mutate separation: this service holds all state-changing operations, so a project can override
28
+ * it (`overrides.hub.actionsService`) to veto or extend actions in one place.
29
+ */
30
+ @Injectable()
31
+ export class CoreHubActionsService {
32
+ protected readonly logger = new Logger('HubAction');
33
+
34
+ constructor(
35
+ protected readonly migrationsService: CoreHubMigrationsService,
36
+ protected readonly dbService: CoreHubDbService,
37
+ protected readonly sourcesService: CoreHubSourcesService,
38
+ protected readonly hubEmailService: CoreHubEmailService,
39
+ protected readonly logBuffer: HubLogBufferService,
40
+ protected readonly traceBuffer: HubTraceBufferService,
41
+ protected readonly queryProfiler: HubQueryProfilerService,
42
+ @Optional() protected readonly emailService?: EmailService,
43
+ @Optional() protected readonly mailboxService?: CoreHubMailboxService,
44
+ ) {}
45
+
46
+ /** Clear a collector's ring buffer. */
47
+ clearBuffer(name: 'logs' | 'mailbox' | 'queries' | 'traces'): { cleared: string } {
48
+ this.audit(`clear ${name} buffer`);
49
+ switch (name) {
50
+ case 'logs':
51
+ this.logBuffer.clear();
52
+ break;
53
+ case 'mailbox':
54
+ this.mailboxService?.clear();
55
+ break;
56
+ case 'queries':
57
+ this.queryProfiler.clear();
58
+ break;
59
+ case 'traces':
60
+ this.traceBuffer.clear();
61
+ break;
62
+ }
63
+ return { cleared: name };
64
+ }
65
+
66
+ /** Fire/stop/start a cron job. */
67
+ controlCron(name: string, action: 'start' | 'stop' | 'trigger'): { action: string; name: string } {
68
+ this.audit(`cron ${action} ${name}`);
69
+ this.sourcesService.controlCron(name, action);
70
+ return { action, name };
71
+ }
72
+
73
+ /** Delete a GridFS file (the confirm keyword must equal its filename). */
74
+ async deleteFile(id: string, expectedFilename: string): Promise<{ deleted: { filename: string; id: string } }> {
75
+ this.audit(`delete file ${id}`);
76
+ return { deleted: await this.dbService.deleteFile(id, expectedFilename) };
77
+ }
78
+
79
+ /** Roll back the last migration. */
80
+ async rollbackMigration(): Promise<{ rolledBack?: string }> {
81
+ this.audit('rollback last migration');
82
+ return this.migrationsService.rollbackLast();
83
+ }
84
+
85
+ /** Run all pending migrations. */
86
+ async runMigrations(): Promise<{ ran: string[] }> {
87
+ this.audit('run pending migrations');
88
+ return this.migrationsService.runPending();
89
+ }
90
+
91
+ /** Send a test mail (lands in the mailbox when capture mode is active). */
92
+ async sendTestEmail(to: string, template?: string, locale?: string): Promise<{ sent: boolean; to: string }> {
93
+ if (!this.emailService) {
94
+ throw new Error(HubActionMessage.emailServiceUnavailable);
95
+ }
96
+ const htmlTemplate = this.resolveTestTemplate(template, locale);
97
+ this.audit(`send test mail to ${to}`);
98
+ await this.emailService.sendMail(to, 'Hub test email', {
99
+ htmlTemplate,
100
+ templateData: { ...TEST_MAIL_DATA, email: to },
101
+ });
102
+ return { sent: true, to };
103
+ }
104
+
105
+ /**
106
+ * Resolve the template file name for a test mail, validated against the live template inventory.
107
+ *
108
+ * Security: a caller-supplied `template`/`locale` is only accepted when it maps to a real entry in
109
+ * `CoreHubEmailService.getTemplates()`. This mirrors `renderPreview`'s allowlist so the test-mail
110
+ * action can never point the renderer at a path outside the templates directory (`..` traversal).
111
+ */
112
+ protected resolveTestTemplate(template?: string, locale?: string): string {
113
+ const inventory = this.hubEmailService.getTemplates().templates;
114
+ // Preserve the historical defaults ('welcome', or 'email-verification' when a locale is given),
115
+ // but bound every path to the inventory.
116
+ const requestedBase = template || (locale ? 'email-verification' : 'welcome');
117
+ const match = inventory.find((entry) => entry.name === requestedBase);
118
+ if (!match) {
119
+ throw new Error(HubActionMessage.unknownEmailTemplate(requestedBase));
120
+ }
121
+ // Append the locale suffix only when it is a known variant of this template.
122
+ if (locale && match.locales.includes(locale)) {
123
+ return `${requestedBase}-${locale}`;
124
+ }
125
+ return requestedBase;
126
+ }
127
+
128
+ /** Write an audit line with the acting admin's id (from the async request context). */
129
+ protected audit(action: string): void {
130
+ const userId = RequestContext.getCurrentUser()?.id ?? 'unknown';
131
+ this.logger.warn(`[HUB-ACTION] ${action} by user ${userId}`);
132
+ }
133
+ }