@lenne.tech/nest-server 11.16.1 → 11.18.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 (113) hide show
  1. package/dist/config.env.js +8 -2
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/decorators/response-model.decorator.d.ts +3 -0
  4. package/dist/core/common/decorators/response-model.decorator.js +8 -0
  5. package/dist/core/common/decorators/response-model.decorator.js.map +1 -0
  6. package/dist/core/common/helpers/db.helper.js +2 -2
  7. package/dist/core/common/helpers/db.helper.js.map +1 -1
  8. package/dist/core/common/helpers/filter.helper.js +3 -3
  9. package/dist/core/common/helpers/filter.helper.js.map +1 -1
  10. package/dist/core/common/helpers/input.helper.js +2 -2
  11. package/dist/core/common/helpers/input.helper.js.map +1 -1
  12. package/dist/core/common/helpers/interceptor.helper.d.ts +3 -0
  13. package/dist/core/common/helpers/interceptor.helper.js +84 -0
  14. package/dist/core/common/helpers/interceptor.helper.js.map +1 -0
  15. package/dist/core/common/helpers/service.helper.d.ts +1 -0
  16. package/dist/core/common/helpers/service.helper.js +1 -0
  17. package/dist/core/common/helpers/service.helper.js.map +1 -1
  18. package/dist/core/common/interceptors/check-security.interceptor.d.ts +2 -0
  19. package/dist/core/common/interceptors/check-security.interceptor.js +43 -1
  20. package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
  21. package/dist/core/common/interceptors/response-model.interceptor.d.ts +13 -0
  22. package/dist/core/common/interceptors/response-model.interceptor.js +107 -0
  23. package/dist/core/common/interceptors/response-model.interceptor.js.map +1 -0
  24. package/dist/core/common/interceptors/translate-response.interceptor.d.ts +8 -0
  25. package/dist/core/common/interceptors/translate-response.interceptor.js +85 -0
  26. package/dist/core/common/interceptors/translate-response.interceptor.js.map +1 -0
  27. package/dist/core/common/interfaces/server-options.interface.d.ts +16 -0
  28. package/dist/core/common/middleware/request-context.middleware.d.ts +5 -0
  29. package/dist/core/common/middleware/request-context.middleware.js +29 -0
  30. package/dist/core/common/middleware/request-context.middleware.js.map +1 -0
  31. package/dist/core/common/pipes/map-and-validate.pipe.js +2 -2
  32. package/dist/core/common/pipes/map-and-validate.pipe.js.map +1 -1
  33. package/dist/core/common/plugins/complexity.plugin.d.ts +2 -2
  34. package/dist/core/common/plugins/mongoose-audit-fields.plugin.d.ts +1 -0
  35. package/dist/core/common/plugins/mongoose-audit-fields.plugin.js +51 -0
  36. package/dist/core/common/plugins/mongoose-audit-fields.plugin.js.map +1 -0
  37. package/dist/core/common/plugins/mongoose-password.plugin.d.ts +4 -0
  38. package/dist/core/common/plugins/mongoose-password.plugin.js +69 -0
  39. package/dist/core/common/plugins/mongoose-password.plugin.js.map +1 -0
  40. package/dist/core/common/plugins/mongoose-role-guard.plugin.d.ts +1 -0
  41. package/dist/core/common/plugins/mongoose-role-guard.plugin.js +80 -0
  42. package/dist/core/common/plugins/mongoose-role-guard.plugin.js.map +1 -0
  43. package/dist/core/common/services/config.service.js +2 -2
  44. package/dist/core/common/services/config.service.js.map +1 -1
  45. package/dist/core/common/services/model-registry.service.d.ts +8 -0
  46. package/dist/core/common/services/model-registry.service.js +20 -0
  47. package/dist/core/common/services/model-registry.service.js.map +1 -0
  48. package/dist/core/common/services/module.service.d.ts +2 -0
  49. package/dist/core/common/services/module.service.js +36 -1
  50. package/dist/core/common/services/module.service.js.map +1 -1
  51. package/dist/core/common/services/request-context.service.d.ts +18 -0
  52. package/dist/core/common/services/request-context.service.js +32 -0
  53. package/dist/core/common/services/request-context.service.js.map +1 -0
  54. package/dist/core/modules/auth/guards/auth.guard.js +2 -2
  55. package/dist/core/modules/auth/guards/auth.guard.js.map +1 -1
  56. package/dist/core/modules/better-auth/core-better-auth.resolver.js +2 -2
  57. package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
  58. package/dist/core/modules/permissions/core-permissions.controller.d.ts +13 -0
  59. package/dist/core/modules/permissions/core-permissions.controller.js +71 -0
  60. package/dist/core/modules/permissions/core-permissions.controller.js.map +1 -0
  61. package/dist/core/modules/permissions/core-permissions.module.d.ts +5 -0
  62. package/dist/core/modules/permissions/core-permissions.module.js +36 -0
  63. package/dist/core/modules/permissions/core-permissions.module.js.map +1 -0
  64. package/dist/core/modules/permissions/core-permissions.service.d.ts +34 -0
  65. package/dist/core/modules/permissions/core-permissions.service.js +610 -0
  66. package/dist/core/modules/permissions/core-permissions.service.js.map +1 -0
  67. package/dist/core/modules/permissions/interfaces/permissions.interface.d.ts +93 -0
  68. package/dist/core/modules/permissions/interfaces/permissions.interface.js +3 -0
  69. package/dist/core/modules/permissions/interfaces/permissions.interface.js.map +1 -0
  70. package/dist/core/modules/permissions/permissions-scanner.d.ts +25 -0
  71. package/dist/core/modules/permissions/permissions-scanner.js +817 -0
  72. package/dist/core/modules/permissions/permissions-scanner.js.map +1 -0
  73. package/dist/core.module.js +41 -0
  74. package/dist/core.module.js.map +1 -1
  75. package/dist/index.d.ts +15 -0
  76. package/dist/index.js +15 -0
  77. package/dist/index.js.map +1 -1
  78. package/dist/server/modules/file/file-info.model.d.ts +12 -12
  79. package/dist/server/modules/user/user.model.d.ts +33 -33
  80. package/dist/tsconfig.build.tsbuildinfo +1 -1
  81. package/package.json +35 -30
  82. package/src/config.env.ts +8 -2
  83. package/src/core/common/decorators/response-model.decorator.ts +31 -0
  84. package/src/core/common/helpers/db.helper.ts +2 -2
  85. package/src/core/common/helpers/filter.helper.ts +3 -3
  86. package/src/core/common/helpers/input.helper.ts +2 -2
  87. package/src/core/common/helpers/interceptor.helper.ts +132 -0
  88. package/src/core/common/helpers/service.helper.ts +1 -1
  89. package/src/core/common/interceptors/check-security.interceptor.ts +44 -1
  90. package/src/core/common/interceptors/response-model.interceptor.ts +135 -0
  91. package/src/core/common/interceptors/translate-response.interceptor.ts +104 -0
  92. package/src/core/common/interfaces/server-options.interface.ts +186 -0
  93. package/src/core/common/middleware/request-context.middleware.ts +25 -0
  94. package/src/core/common/pipes/map-and-validate.pipe.ts +2 -2
  95. package/src/core/common/plugins/complexity.plugin.ts +2 -2
  96. package/src/core/common/plugins/mongoose-audit-fields.plugin.ts +74 -0
  97. package/src/core/common/plugins/mongoose-password.plugin.ts +100 -0
  98. package/src/core/common/plugins/mongoose-role-guard.plugin.ts +150 -0
  99. package/src/core/common/services/config.service.ts +2 -2
  100. package/src/core/common/services/model-registry.service.ts +25 -0
  101. package/src/core/common/services/module.service.ts +91 -1
  102. package/src/core/common/services/request-context.service.ts +69 -0
  103. package/src/core/modules/auth/guards/auth.guard.ts +2 -2
  104. package/src/core/modules/better-auth/core-better-auth.resolver.ts +2 -2
  105. package/src/core/modules/permissions/INTEGRATION-CHECKLIST.md +56 -0
  106. package/src/core/modules/permissions/README.md +102 -0
  107. package/src/core/modules/permissions/core-permissions.controller.ts +34 -0
  108. package/src/core/modules/permissions/core-permissions.module.ts +36 -0
  109. package/src/core/modules/permissions/core-permissions.service.ts +627 -0
  110. package/src/core/modules/permissions/interfaces/permissions.interface.ts +125 -0
  111. package/src/core/modules/permissions/permissions-scanner.ts +1011 -0
  112. package/src/core.module.ts +62 -4
  113. package/src/index.ts +20 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-permissions.module.js","sourceRoot":"","sources":["../../../../src/core/modules/permissions/core-permissions.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,wDAAyD;AAEzD,4DAAwD;AAExD,+EAA0E;AAC1E,yEAAoE;AAI7D,IAAM,qBAAqB,6BAA3B,MAAM,qBAAqB;IAChC,MAAM,CAAC,OAAO,CAAC,MAA8B;QAC3C,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,oBAAQ,CAAC,KAAK,CAAC;QAEpG,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;QAKrF,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;YACnB,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,uDAAyB,CAAC,CAAC;QACrE,CAAC;QAKD,OAAO,CAAC,cAAc,CAAC,yBAAa,EAAE,IAAI,EAAE,uDAAyB,CAAC,CAAC;QAEvE,OAAO;YACL,WAAW,EAAE,CAAC,uDAAyB,CAAC;YACxC,OAAO,EAAE,CAAC,iDAAsB,CAAC;YACjC,MAAM,EAAE,uBAAqB;YAC7B,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,iDAAsB,CAAC;SACrF,CAAC;IACJ,CAAC;CACF,CAAA;AAzBY,sDAAqB;gCAArB,qBAAqB;IADjC,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,qBAAqB,CAyBjC"}
@@ -0,0 +1,34 @@
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import type { PermissionsReport } from './interfaces/permissions.interface';
3
+ export declare class CorePermissionsService implements OnModuleDestroy {
4
+ private htmlCache;
5
+ private lastScanTime;
6
+ private readonly logger;
7
+ private markdownCache;
8
+ private readonly basePath;
9
+ private report;
10
+ private scanPromise;
11
+ private watcher;
12
+ private readonly SCAN_COOLDOWN_MS;
13
+ constructor(basePath?: string);
14
+ generateHtml(authToken?: string): string;
15
+ generateMarkdown(): string;
16
+ getReport(): PermissionsReport | null;
17
+ getOrScan(): Promise<PermissionsReport>;
18
+ onModuleDestroy(): void;
19
+ scan(): Promise<PermissionsReport>;
20
+ private collectModuleRoles;
21
+ private badge;
22
+ private badgeList;
23
+ private escapeHtml;
24
+ private getBadgeClass;
25
+ private buildClientJs;
26
+ private buildCss;
27
+ private buildHtml;
28
+ private buildModuleSectionsHtml;
29
+ private buildObjectsSectionsHtml;
30
+ private buildRoleIndexSection;
31
+ private buildSidebarHtml;
32
+ private buildWarningsSection;
33
+ private setupWatcher;
34
+ }
@@ -0,0 +1,610 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var CorePermissionsService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.CorePermissionsService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const fs = require("fs");
19
+ const fs_1 = require("fs");
20
+ const path_1 = require("path");
21
+ const permissions_scanner_1 = require("./permissions-scanner");
22
+ let CorePermissionsService = CorePermissionsService_1 = class CorePermissionsService {
23
+ htmlCache = null;
24
+ lastScanTime = 0;
25
+ logger = new common_1.Logger(CorePermissionsService_1.name);
26
+ markdownCache = null;
27
+ basePath;
28
+ report = null;
29
+ scanPromise = null;
30
+ watcher = null;
31
+ SCAN_COOLDOWN_MS = 10_000;
32
+ constructor(basePath) {
33
+ this.basePath = basePath || 'permissions';
34
+ this.setupWatcher();
35
+ }
36
+ generateHtml(authToken) {
37
+ if (!this.htmlCache) {
38
+ if (!this.report)
39
+ return `<p>No report available. Access /${this.basePath} to trigger scan.</p>`;
40
+ this.htmlCache = this.buildHtml(this.report);
41
+ }
42
+ if (authToken) {
43
+ const escaped = authToken.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/</g, '\\u003c');
44
+ return this.htmlCache.replace('</body>', `<script>var AUTH_TOKEN='${escaped}';</script></body>`);
45
+ }
46
+ return this.htmlCache;
47
+ }
48
+ generateMarkdown() {
49
+ if (this.markdownCache)
50
+ return this.markdownCache;
51
+ if (!this.report)
52
+ return `# Permissions Report\n\nNo report available. Access /${this.basePath} to trigger scan.`;
53
+ this.markdownCache = (0, permissions_scanner_1.generateMarkdownReport)(this.report);
54
+ return this.markdownCache;
55
+ }
56
+ getReport() {
57
+ return this.report;
58
+ }
59
+ async getOrScan() {
60
+ if (this.report)
61
+ return this.report;
62
+ if (this.scanPromise)
63
+ return this.scanPromise;
64
+ this.scanPromise = this.scan();
65
+ return this.scanPromise;
66
+ }
67
+ onModuleDestroy() {
68
+ this.watcher?.close();
69
+ }
70
+ async scan() {
71
+ const now = Date.now();
72
+ if (now - this.lastScanTime < this.SCAN_COOLDOWN_MS) {
73
+ if (this.report)
74
+ return this.report;
75
+ }
76
+ try {
77
+ this.lastScanTime = now;
78
+ const projectRoot = (0, permissions_scanner_1.findProjectRoot)();
79
+ if (!projectRoot) {
80
+ throw new Error('Could not find project root (src/server/modules/ not found)');
81
+ }
82
+ this.report = (0, permissions_scanner_1.scanPermissions)(projectRoot, {
83
+ log: (msg) => this.logger.log(msg),
84
+ warn: (msg) => this.logger.warn(msg),
85
+ });
86
+ this.htmlCache = null;
87
+ this.markdownCache = null;
88
+ return this.report;
89
+ }
90
+ catch (error) {
91
+ this.logger.error('Permissions scan failed', error);
92
+ throw error;
93
+ }
94
+ finally {
95
+ this.scanPromise = null;
96
+ }
97
+ }
98
+ collectModuleRoles(mod) {
99
+ const roles = new Set();
100
+ for (const model of mod.models) {
101
+ for (const r of model.classRestriction)
102
+ roles.add(r);
103
+ for (const f of model.fields) {
104
+ const matches = f.roles.match(/`([^`]+)`/g);
105
+ if (matches)
106
+ matches.forEach((m) => roles.add(m.replace(/`/g, '')));
107
+ }
108
+ }
109
+ for (const ctrl of mod.controllers) {
110
+ for (const r of ctrl.classRoles)
111
+ roles.add(r);
112
+ for (const m of ctrl.methods) {
113
+ for (const r of m.roles)
114
+ roles.add(r);
115
+ }
116
+ }
117
+ for (const res of mod.resolvers) {
118
+ for (const r of res.classRoles)
119
+ roles.add(r);
120
+ for (const m of res.methods) {
121
+ for (const r of m.roles)
122
+ roles.add(r);
123
+ }
124
+ }
125
+ return [...roles].sort();
126
+ }
127
+ badge(role) {
128
+ return `<span class="badge ${this.getBadgeClass(role)}">${this.escapeHtml(role)}</span>`;
129
+ }
130
+ badgeList(roles) {
131
+ return roles.length > 0 ? roles.map((r) => this.badge(r)).join(' ') : '<em>(none)</em>';
132
+ }
133
+ escapeHtml(str) {
134
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
135
+ }
136
+ getBadgeClass(role) {
137
+ if (!role)
138
+ return 'badge-custom';
139
+ const r = role.toUpperCase();
140
+ if (r === 'S_EVERYONE')
141
+ return 'badge-everyone';
142
+ if (r === 'S_NO_ONE')
143
+ return 'badge-noone';
144
+ if (r === 'ADMIN')
145
+ return 'badge-admin';
146
+ if (r === 'S_USER')
147
+ return 'badge-user';
148
+ if (r === 'S_SELF')
149
+ return 'badge-self';
150
+ if (r === 'S_CREATOR')
151
+ return 'badge-creator';
152
+ return 'badge-custom';
153
+ }
154
+ buildClientJs(data) {
155
+ const basePath = JSON.stringify('/' + this.basePath);
156
+ return `<script>
157
+ var DATA = ${data};
158
+ var BASE_PATH = ${basePath};
159
+
160
+ (function() {
161
+ var sel = document.getElementById('roleFilter');
162
+ var roles = {};
163
+
164
+ // Collect roles from roleEnums
165
+ DATA.roleEnums.forEach(function(e) { e.values.forEach(function(v) { roles[v.key] = true; }); });
166
+
167
+ // Collect roles from actual scan data
168
+ DATA.modules.forEach(function(mod) {
169
+ mod.models.forEach(function(model) {
170
+ model.classRestriction.forEach(function(r) { roles[r] = true; });
171
+ model.fields.forEach(function(f) {
172
+ var m = f.roles.match(/\x60([^\x60]+)\x60/g);
173
+ if (m) m.forEach(function(r) { roles[r.replace(/\x60/g, '')] = true; });
174
+ });
175
+ });
176
+ mod.controllers.forEach(function(ep) {
177
+ ep.classRoles.forEach(function(r) { roles[r] = true; });
178
+ ep.methods.forEach(function(m) { m.roles.forEach(function(r) { roles[r] = true; }); });
179
+ });
180
+ mod.resolvers.forEach(function(ep) {
181
+ ep.classRoles.forEach(function(r) { roles[r] = true; });
182
+ ep.methods.forEach(function(m) { m.roles.forEach(function(r) { roles[r] = true; }); });
183
+ });
184
+ });
185
+
186
+ Object.keys(roles).sort().forEach(function(r) {
187
+ var o = document.createElement('option');
188
+ o.value = r; o.textContent = r;
189
+ sel.appendChild(o);
190
+ });
191
+ })();
192
+
193
+ function toggle(el) {
194
+ el.classList.toggle('open');
195
+ var content = el.nextElementSibling;
196
+ if (content) content.classList.toggle('open');
197
+ }
198
+
199
+ function filterAll() {
200
+ var q = document.getElementById('search').value.toLowerCase();
201
+ var role = document.getElementById('roleFilter').value;
202
+ var warnOnly = document.getElementById('warnOnly').checked;
203
+
204
+ // Filter module sections
205
+ document.querySelectorAll('.module-section').forEach(function(section) {
206
+ var text = section.textContent.toLowerCase();
207
+ var hasWarnings = section.dataset.hasWarnings === 'true';
208
+ var sectionRoles = (section.dataset.roles || '').split(',');
209
+ var show = true;
210
+ if (q && !text.includes(q)) show = false;
211
+ if (warnOnly && !hasWarnings) show = false;
212
+ if (role && sectionRoles.indexOf(role) === -1) show = false;
213
+ section.style.display = show ? '' : 'none';
214
+ });
215
+
216
+ // Filter warnings table rows
217
+ var warningsSection = document.getElementById('warnings');
218
+ if (warningsSection) {
219
+ var rows = warningsSection.querySelectorAll('tbody tr');
220
+ rows.forEach(function(row) {
221
+ var text = row.textContent.toLowerCase();
222
+ var show = true;
223
+ if (q && !text.includes(q)) show = false;
224
+ row.style.display = show ? '' : 'none';
225
+ });
226
+ }
227
+
228
+ // Filter subobjects section
229
+ var subObjSection = document.getElementById('subobjects');
230
+ if (subObjSection) {
231
+ subObjSection.querySelectorAll('div > h3').forEach(function(h3) {
232
+ var container = h3.parentElement;
233
+ if (!container) return;
234
+ var text = container.textContent.toLowerCase();
235
+ var show = true;
236
+ if (q && !text.includes(q)) show = false;
237
+ container.style.display = show ? '' : 'none';
238
+ });
239
+ }
240
+ }
241
+
242
+ document.querySelectorAll('th').forEach(function(th) {
243
+ th.addEventListener('click', function() {
244
+ var table = this.closest('table');
245
+ var tbody = table.querySelector('tbody');
246
+ if (!tbody) return;
247
+ var idx = Array.from(this.parentNode.children).indexOf(this);
248
+ var rows = Array.from(tbody.querySelectorAll('tr'));
249
+ var asc = this.dataset.sort !== 'asc';
250
+ rows.sort(function(a, b) {
251
+ var at = (a.children[idx] || {}).textContent || '';
252
+ var bt = (b.children[idx] || {}).textContent || '';
253
+ return asc ? at.localeCompare(bt) : bt.localeCompare(at);
254
+ });
255
+ this.dataset.sort = asc ? 'asc' : 'desc';
256
+ rows.forEach(function(r) { tbody.appendChild(r); });
257
+ });
258
+ });
259
+
260
+ function exportAs(fmt) {
261
+ var blob = new Blob([JSON.stringify(DATA, null, 2)], { type: 'application/json' });
262
+ var a = document.createElement('a');
263
+ a.href = URL.createObjectURL(blob);
264
+ a.download = 'permissions.' + fmt;
265
+ a.click();
266
+ }
267
+
268
+ function exportMarkdown() {
269
+ var opts = { credentials: 'same-origin' };
270
+ if (typeof AUTH_TOKEN !== 'undefined' && AUTH_TOKEN) {
271
+ opts.headers = { 'Authorization': AUTH_TOKEN };
272
+ }
273
+ fetch(BASE_PATH + '/markdown', opts)
274
+ .then(function(res) { return res.text(); })
275
+ .then(function(text) {
276
+ var blob = new Blob([text], { type: 'text/plain' });
277
+ var a = document.createElement('a');
278
+ a.href = URL.createObjectURL(blob);
279
+ a.download = 'permissions.md';
280
+ a.click();
281
+ });
282
+ }
283
+
284
+ function rescan(e) {
285
+ var btn = e ? e.target : this;
286
+ btn.disabled = true;
287
+ btn.textContent = 'Scanning...';
288
+ var opts = { method: 'POST', credentials: 'same-origin' };
289
+ if (typeof AUTH_TOKEN !== 'undefined' && AUTH_TOKEN) {
290
+ opts.headers = { 'Authorization': AUTH_TOKEN };
291
+ }
292
+ fetch(BASE_PATH + '/rescan', opts)
293
+ .then(function(res) { if (!res.ok) throw new Error(res.status); window.location.reload(); })
294
+ .catch(function(err) { alert('Rescan failed: ' + err); btn.disabled = false; btn.textContent = 'Rescan'; });
295
+ }
296
+ </script>`;
297
+ }
298
+ buildCss() {
299
+ return `<style>
300
+ :root {
301
+ --bg: #ffffff; --fg: #1a1a2e; --bg-card: #f8f9fa; --border: #dee2e6;
302
+ --primary: #0d6efd; --success: #198754; --warning: #ffc107; --danger: #dc3545;
303
+ --role-everyone: #198754; --role-noone: #dc3545; --role-admin: #0d6efd;
304
+ --role-user: #e6a817; --role-self: #6c757d; --role-custom: #fd7e14;
305
+ --shadow: 0 1px 3px rgba(0,0,0,0.12);
306
+ }
307
+ @media (prefers-color-scheme: dark) {
308
+ :root {
309
+ --bg: #1a1a2e; --fg: #e0e0e0; --bg-card: #16213e; --border: #3a3a5c;
310
+ --shadow: 0 1px 3px rgba(0,0,0,0.4);
311
+ }
312
+ }
313
+ * { margin: 0; padding: 0; box-sizing: border-box; }
314
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--fg); line-height: 1.6; }
315
+ .layout { display: flex; min-height: 100vh; }
316
+ .sidebar { width: 260px; position: sticky; top: 0; height: 100vh; overflow-y: auto; background: var(--bg-card); border-right: 1px solid var(--border); padding: 1rem; flex-shrink: 0; }
317
+ .sidebar h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--primary); margin-bottom: 0.5rem; }
318
+ .sidebar a { display: block; padding: 0.25rem 0.5rem; color: var(--fg); text-decoration: none; font-size: 0.85rem; border-radius: 4px; }
319
+ .sidebar a:hover { background: var(--border); }
320
+ .sidebar a.indent { padding-left: 1.5rem; font-size: 0.8rem; opacity: 0.8; }
321
+ .main { flex: 1; padding: 2rem; max-width: 1200px; }
322
+ .dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
323
+ .stat { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; text-align: center; box-shadow: var(--shadow); }
324
+ .stat .num { font-size: 2rem; font-weight: 700; color: var(--primary); }
325
+ .stat .label { font-size: 0.8rem; opacity: 0.7; }
326
+ .stat.warn .num { color: var(--warning); }
327
+ .stat.danger .num { color: var(--danger); }
328
+ .controls { display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.5rem; align-items: center; }
329
+ .controls input, .controls select { padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--fg); font-size: 0.9rem; }
330
+ .controls input { flex: 1; min-width: 200px; }
331
+ .controls label { font-size: 0.85rem; display: flex; align-items: center; gap: 0.3rem; }
332
+ .badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 12px; font-size: 0.75rem; font-weight: 600; color: #fff; }
333
+ .badge-everyone { background: var(--role-everyone); }
334
+ .badge-noone { background: var(--role-noone); }
335
+ .badge-admin { background: var(--role-admin); }
336
+ .badge-user { background: var(--role-user); color: #000; }
337
+ .badge-self, .badge-creator { background: var(--role-self); }
338
+ .badge-custom { background: var(--role-custom); }
339
+ .badge-warn { background: var(--warning); color: #000; }
340
+ section { margin-bottom: 2rem; }
341
+ h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
342
+ h2 { font-size: 1.4rem; margin: 1.5rem 0 0.75rem; padding-bottom: 0.3rem; border-bottom: 2px solid var(--primary); }
343
+ h3 { font-size: 1.1rem; margin: 1rem 0 0.5rem; }
344
+ .meta { font-size: 0.85rem; opacity: 0.7; margin-bottom: 0.25rem; }
345
+ table { width: 100%; border-collapse: collapse; margin: 0.5rem 0 1rem; font-size: 0.85rem; }
346
+ th, td { padding: 0.4rem 0.6rem; text-align: left; border: 1px solid var(--border); }
347
+ th { background: var(--bg-card); cursor: pointer; user-select: none; white-space: nowrap; }
348
+ th:hover { background: var(--border); }
349
+ tr:nth-child(even) { background: var(--bg-card); }
350
+ .collapsible { cursor: pointer; }
351
+ .collapsible::before { content: '\\25B6'; display: inline-block; margin-right: 0.5rem; transition: transform 0.2s; font-size: 0.8rem; }
352
+ .collapsible.open::before { transform: rotate(90deg); }
353
+ .collapse-content { display: none; }
354
+ .collapse-content.open { display: block; }
355
+ .warning-row { background: rgba(255, 193, 7, 0.1) !important; }
356
+ .btn { padding: 0.4rem 0.8rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg-card); color: var(--fg); cursor: pointer; font-size: 0.8rem; margin: 0.25rem 0; }
357
+ .btn:hover { background: var(--border); }
358
+ .module-header { position: sticky; top: 0; background: var(--bg); z-index: 10; padding: 0.5rem 0; border-bottom: 1px solid var(--border); }
359
+ @media (max-width: 768px) {
360
+ .sidebar { display: none; }
361
+ .main { padding: 1rem; }
362
+ .dashboard { grid-template-columns: repeat(2, 1fr); }
363
+ }
364
+ </style>`;
365
+ }
366
+ buildHtml(report) {
367
+ const data = JSON.stringify(report).replace(/</g, '\\u003c');
368
+ const s = report.stats;
369
+ const coverageClass = (pct) => (pct >= 90 ? '' : pct >= 70 ? 'warn' : 'danger');
370
+ return `<!DOCTYPE html>
371
+ <html lang="en">
372
+ <head>
373
+ <meta charset="UTF-8">
374
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
375
+ <title>Permissions Report</title>
376
+ ${this.buildCss()}
377
+ </head>
378
+ <body>
379
+ <div class="layout">
380
+ ${this.buildSidebarHtml(report)}
381
+ <div class="main">
382
+ <h1>Permissions Report</h1>
383
+ <p class="meta">Generated: ${this.escapeHtml(report.generated)}</p>
384
+
385
+ <section id="dashboard">
386
+ <div class="dashboard">
387
+ <div class="stat"><div class="num">${s.totalModules}</div><div class="label">Modules</div></div>
388
+ <div class="stat"><div class="num">${s.totalModels}</div><div class="label">Models</div></div>
389
+ <div class="stat"><div class="num">${s.totalEndpoints}</div><div class="label">Endpoints</div></div>
390
+ <div class="stat"><div class="num">${s.totalSubObjects}</div><div class="label">SubObjects</div></div>
391
+ <div class="stat ${s.totalWarnings > 0 ? 'danger' : ''}"><div class="num">${s.totalWarnings}</div><div class="label">Warnings</div></div>
392
+ <div class="stat ${coverageClass(s.endpointCoverage)}"><div class="num">${s.endpointCoverage}%</div><div class="label">Endpoint Coverage</div></div>
393
+ <div class="stat ${coverageClass(s.securityCoverage)}"><div class="num">${s.securityCoverage}%</div><div class="label">Security Coverage</div></div>
394
+ </div>
395
+ </section>
396
+
397
+ <section>
398
+ <div class="controls">
399
+ <input type="text" id="search" placeholder="Search modules, fields, roles..." oninput="filterAll()">
400
+ <select id="roleFilter" onchange="filterAll()">
401
+ <option value="">All Roles</option>
402
+ </select>
403
+ <label><input type="checkbox" id="warnOnly" onchange="filterAll()"> Warnings only</label>
404
+ </div>
405
+ </section>
406
+
407
+ ${this.buildRoleIndexSection(report)}
408
+ ${this.buildWarningsSection(report)}
409
+ ${this.buildModuleSectionsHtml(report)}
410
+ ${this.buildObjectsSectionsHtml(report)}
411
+
412
+ </div>
413
+ </div>
414
+
415
+ ${this.buildClientJs(data)}
416
+ </body>
417
+ </html>`;
418
+ }
419
+ buildModuleSectionsHtml(report) {
420
+ let html = '';
421
+ for (const mod of report.modules) {
422
+ const hasWarnings = report.warnings.some((w) => w.module === mod.name);
423
+ const allRoles = this.collectModuleRoles(mod);
424
+ html += `<section class="module-section" id="mod-${this.escapeHtml(mod.name)}" data-module="${this.escapeHtml(mod.name)}" data-has-warnings="${hasWarnings}" data-roles="${this.escapeHtml(allRoles.join(','))}">\n`;
425
+ html += `<div class="module-header"><h2 class="collapsible open" onclick="toggle(this)">Module: ${this.escapeHtml(mod.name)}</h2></div>\n<div class="collapse-content open">\n`;
426
+ for (const model of mod.models) {
427
+ html += `<div id="model-${this.escapeHtml(mod.name)}-${this.escapeHtml(model.className)}">`;
428
+ html += `<h3>Model: ${this.escapeHtml(model.className)}</h3>`;
429
+ html += `<p class="meta">File: ${this.escapeHtml(model.filePath)}</p>`;
430
+ if (model.extendsClass)
431
+ html += `<p class="meta">Extends: ${this.escapeHtml(model.extendsClass)}</p>`;
432
+ html += `<p class="meta">Class Restriction: ${model.classRestriction.length > 0 ? model.classRestriction.map((r) => this.badge(r)).join(' ') : '<em>(none)</em>'}</p>`;
433
+ html += `<p class="meta">securityCheck: ${model.securityCheck ? this.escapeHtml(model.securityCheck.summary) : '<em>Not present</em>'}</p>`;
434
+ if (model.fields.length > 0) {
435
+ html += '<table><thead><tr><th>Field</th><th>Roles</th><th>Source</th></tr></thead><tbody>';
436
+ for (const f of model.fields) {
437
+ html += `<tr><td>${this.escapeHtml(f.name)}</td><td>${this.escapeHtml(f.roles)}</td><td>${f.inherited ? 'inherited' : 'local'}</td></tr>`;
438
+ }
439
+ html += '</tbody></table>';
440
+ }
441
+ html += '</div>\n';
442
+ }
443
+ for (const input of mod.inputs) {
444
+ html += `<div><h3>Input: ${this.escapeHtml(input.className)}</h3>`;
445
+ html += `<p class="meta">File: ${this.escapeHtml(input.filePath)}</p>`;
446
+ if (input.extendsClass)
447
+ html += `<p class="meta">Extends: ${this.escapeHtml(input.extendsClass)}</p>`;
448
+ if (input.fields.length > 0) {
449
+ html += '<table><thead><tr><th>Field</th><th>Roles</th></tr></thead><tbody>';
450
+ for (const f of input.fields) {
451
+ html += `<tr><td>${this.escapeHtml(f.name)}</td><td>${this.escapeHtml(f.roles)}</td></tr>`;
452
+ }
453
+ html += '</tbody></table>';
454
+ }
455
+ html += '</div>\n';
456
+ }
457
+ for (const ctrl of mod.controllers) {
458
+ html += `<div id="ctrl-${this.escapeHtml(mod.name)}-${this.escapeHtml(ctrl.className)}">`;
459
+ html += `<h3>Controller: ${this.escapeHtml(ctrl.className)}</h3>`;
460
+ html += `<p class="meta">File: ${this.escapeHtml(ctrl.filePath)}</p>`;
461
+ html += `<p class="meta">Class Roles: ${this.badgeList(ctrl.classRoles)}</p>`;
462
+ if (ctrl.methods.length > 0) {
463
+ html +=
464
+ '<table><thead><tr><th>Method</th><th>HTTP</th><th>Route</th><th>Roles</th><th>Effective</th></tr></thead><tbody>';
465
+ for (const m of ctrl.methods) {
466
+ const eff = m.roles.length > 0 ? m.roles : ctrl.classRoles;
467
+ html += `<tr><td>${this.escapeHtml(m.name)}</td><td>${this.escapeHtml(m.httpMethod)}</td><td>${this.escapeHtml(m.route || '/')}</td><td>${this.badgeList(m.roles)}</td><td>${this.badgeList(eff)}${m.roles.length === 0 && ctrl.classRoles.length > 0 ? ' (class)' : ''}</td></tr>`;
468
+ }
469
+ html += '</tbody></table>';
470
+ }
471
+ html += '</div>\n';
472
+ }
473
+ for (const res of mod.resolvers) {
474
+ html += `<div id="res-${this.escapeHtml(mod.name)}-${this.escapeHtml(res.className)}">`;
475
+ html += `<h3>Resolver: ${this.escapeHtml(res.className)}</h3>`;
476
+ html += `<p class="meta">File: ${this.escapeHtml(res.filePath)}</p>`;
477
+ html += `<p class="meta">Class Roles: ${this.badgeList(res.classRoles)}</p>`;
478
+ if (res.methods.length > 0) {
479
+ html += '<table><thead><tr><th>Method</th><th>Type</th><th>Roles</th><th>Effective</th></tr></thead><tbody>';
480
+ for (const m of res.methods) {
481
+ const eff = m.roles.length > 0 ? m.roles : res.classRoles;
482
+ html += `<tr><td>${this.escapeHtml(m.name)}</td><td>${this.escapeHtml(m.httpMethod)}</td><td>${this.badgeList(m.roles)}</td><td>${this.badgeList(eff)}${m.roles.length === 0 && res.classRoles.length > 0 ? ' (class)' : ''}</td></tr>`;
483
+ }
484
+ html += '</tbody></table>';
485
+ }
486
+ html += '</div>\n';
487
+ }
488
+ html += '</div></section>\n';
489
+ }
490
+ return html;
491
+ }
492
+ buildObjectsSectionsHtml(report) {
493
+ if (report.objects.length === 0)
494
+ return '';
495
+ let html = '<section id="subobjects"><h2>SubObjects</h2>\n';
496
+ for (const obj of report.objects) {
497
+ html += `<div><h3>${this.escapeHtml(obj.className)}</h3>`;
498
+ html += `<p class="meta">File: ${this.escapeHtml(obj.filePath)}</p>`;
499
+ if (obj.extendsClass)
500
+ html += `<p class="meta">Extends: ${this.escapeHtml(obj.extendsClass)}</p>`;
501
+ if (obj.fields.length > 0) {
502
+ html += '<table><thead><tr><th>Field</th><th>Roles</th><th>Source</th></tr></thead><tbody>';
503
+ for (const f of obj.fields) {
504
+ html += `<tr><td>${this.escapeHtml(f.name)}</td><td>${this.escapeHtml(f.roles)}</td><td>${f.inherited ? 'inherited' : 'local'}</td></tr>`;
505
+ }
506
+ html += '</tbody></table>';
507
+ }
508
+ html += '</div>\n';
509
+ }
510
+ html += '</section>\n';
511
+ return html;
512
+ }
513
+ buildRoleIndexSection(report) {
514
+ let rows = '';
515
+ if (report.roleEnums.length > 0) {
516
+ for (const e of report.roleEnums) {
517
+ for (const v of e.values) {
518
+ const isSystem = v.key.startsWith('S_');
519
+ rows += `<tr><td>${this.escapeHtml(e.name)}.${this.escapeHtml(v.key)}</td><td>${isSystem ? '(system)' : this.escapeHtml(v.value)}</td><td>${this.badge(isSystem ? 'System' : 'Real')}</td></tr>\n`;
520
+ }
521
+ }
522
+ }
523
+ const content = report.roleEnums.length > 0
524
+ ? `<table><thead><tr><th>Enum</th><th>Value</th><th>Type</th></tr></thead><tbody>${rows}</tbody></table>`
525
+ : '<p><em>No role enums found.</em></p>';
526
+ return `<section id="role-index">
527
+ <h2 class="collapsible open" onclick="toggle(this)">Role Index</h2>
528
+ <div class="collapse-content open">
529
+ ${content}
530
+ </div>
531
+ </section>`;
532
+ }
533
+ buildSidebarHtml(report) {
534
+ let links = '';
535
+ for (const mod of report.modules) {
536
+ links += `<a href="#mod-${this.escapeHtml(mod.name)}">${this.escapeHtml(mod.name)}</a>\n`;
537
+ for (const model of mod.models) {
538
+ links += `<a href="#model-${this.escapeHtml(mod.name)}-${this.escapeHtml(model.className)}" class="indent">Model: ${this.escapeHtml(model.className)}</a>\n`;
539
+ }
540
+ for (const ctrl of mod.controllers) {
541
+ links += `<a href="#ctrl-${this.escapeHtml(mod.name)}-${this.escapeHtml(ctrl.className)}" class="indent">Ctrl: ${this.escapeHtml(ctrl.className)}</a>\n`;
542
+ }
543
+ for (const res of mod.resolvers) {
544
+ links += `<a href="#res-${this.escapeHtml(mod.name)}-${this.escapeHtml(res.className)}" class="indent">Resolver: ${this.escapeHtml(res.className)}</a>\n`;
545
+ }
546
+ }
547
+ return `<nav class="sidebar">
548
+ <h3>Permissions Report</h3>
549
+ <a href="#dashboard">Dashboard</a>
550
+ <a href="#role-index">Role Index</a>
551
+ <a href="#warnings">Warnings (${report.warnings.length})</a>
552
+ <hr style="margin:0.5rem 0;border-color:var(--border)">
553
+ ${links}
554
+ ${report.objects.length > 0 ? '<hr style="margin:0.5rem 0;border-color:var(--border)"><a href="#subobjects">SubObjects</a>' : ''}
555
+ <hr style="margin:0.5rem 0;border-color:var(--border)">
556
+ <button class="btn" onclick="exportAs('json')">Export JSON</button>
557
+ <button class="btn" onclick="exportMarkdown()">Export Markdown</button>
558
+ <button class="btn" onclick="rescan(event)">Rescan</button>
559
+ </nav>`;
560
+ }
561
+ buildWarningsSection(report) {
562
+ let rows = '';
563
+ for (let i = 0; i < report.warnings.length; i++) {
564
+ const w = report.warnings[i];
565
+ const fileName = w.file.split('/').pop() || w.file;
566
+ rows += `<tr class="warning-row"><td>${i + 1}</td><td>${this.escapeHtml(w.module)}</td><td>${this.escapeHtml(fileName)}</td><td><span class="badge badge-warn">${this.escapeHtml(w.type)}</span></td><td>${this.escapeHtml(w.details)}</td></tr>\n`;
567
+ }
568
+ const content = report.warnings.length > 0
569
+ ? `<table><thead><tr><th>#</th><th>Module</th><th>File</th><th>Type</th><th>Details</th></tr></thead><tbody>${rows}</tbody></table>`
570
+ : '<p><em>No warnings found.</em></p>';
571
+ return `<section id="warnings">
572
+ <h2 class="collapsible open" onclick="toggle(this)">Warnings (${report.warnings.length})</h2>
573
+ <div class="collapse-content open">
574
+ ${content}
575
+ </div>
576
+ </section>`;
577
+ }
578
+ setupWatcher() {
579
+ try {
580
+ const root = (0, permissions_scanner_1.findProjectRoot)();
581
+ if (!root)
582
+ return;
583
+ const watchPath = (0, path_1.join)(root, 'src', 'server');
584
+ if (!(0, fs_1.existsSync)(watchPath))
585
+ return;
586
+ this.watcher = fs.watch(watchPath, { recursive: true }, (_eventType, filename) => {
587
+ if (filename?.endsWith('.ts')) {
588
+ this.logger.debug(`File changed: ${filename}, invalidating cache`);
589
+ this.report = null;
590
+ this.htmlCache = null;
591
+ this.markdownCache = null;
592
+ }
593
+ });
594
+ this.watcher.on('error', (err) => {
595
+ this.logger.warn(`File watcher error: ${err.message}, manual rescan needed`);
596
+ });
597
+ }
598
+ catch (err) {
599
+ this.logger.warn(`File watcher setup failed: ${err}, manual rescan needed`);
600
+ }
601
+ }
602
+ };
603
+ exports.CorePermissionsService = CorePermissionsService;
604
+ exports.CorePermissionsService = CorePermissionsService = CorePermissionsService_1 = __decorate([
605
+ (0, common_1.Injectable)(),
606
+ __param(0, (0, common_1.Optional)()),
607
+ __param(0, (0, common_1.Inject)('PERMISSIONS_PATH')),
608
+ __metadata("design:paramtypes", [String])
609
+ ], CorePermissionsService);
610
+ //# sourceMappingURL=core-permissions.service.js.map