@nestjs-dash/nestjs 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. package/dist/client/nestjs-dash.js +2126 -0
  2. package/dist/client/nestjs-dash.js.map +7 -0
  3. package/dist/controllers/admin-api.controller.d.ts +41 -0
  4. package/dist/controllers/admin-api.controller.d.ts.map +1 -0
  5. package/dist/controllers/admin-api.controller.js +299 -0
  6. package/dist/controllers/admin-api.controller.js.map +1 -0
  7. package/dist/controllers/admin-auth.controller.d.ts +17 -0
  8. package/dist/controllers/admin-auth.controller.d.ts.map +1 -0
  9. package/dist/controllers/admin-auth.controller.js +111 -0
  10. package/dist/controllers/admin-auth.controller.js.map +1 -0
  11. package/dist/controllers/admin-upload.controller.d.ts +50 -0
  12. package/dist/controllers/admin-upload.controller.d.ts.map +1 -0
  13. package/dist/controllers/admin-upload.controller.js +210 -0
  14. package/dist/controllers/admin-upload.controller.js.map +1 -0
  15. package/dist/controllers/admin.controller.d.ts +69 -0
  16. package/dist/controllers/admin.controller.d.ts.map +1 -0
  17. package/dist/controllers/admin.controller.js +1062 -0
  18. package/dist/controllers/admin.controller.js.map +1 -0
  19. package/dist/decorators/admin-action.decorator.d.ts +4 -0
  20. package/dist/decorators/admin-action.decorator.d.ts.map +1 -0
  21. package/dist/decorators/admin-action.decorator.js +6 -0
  22. package/dist/decorators/admin-action.decorator.js.map +1 -0
  23. package/dist/decorators/admin-page.decorator.d.ts +3 -0
  24. package/dist/decorators/admin-page.decorator.d.ts.map +1 -0
  25. package/dist/decorators/admin-page.decorator.js +6 -0
  26. package/dist/decorators/admin-page.decorator.js.map +1 -0
  27. package/dist/decorators/admin-resource.decorator.d.ts +3 -0
  28. package/dist/decorators/admin-resource.decorator.d.ts.map +1 -0
  29. package/dist/decorators/admin-resource.decorator.js +6 -0
  30. package/dist/decorators/admin-resource.decorator.js.map +1 -0
  31. package/dist/features/audit/audit.interceptor.d.ts +17 -0
  32. package/dist/features/audit/audit.interceptor.d.ts.map +1 -0
  33. package/dist/features/audit/audit.interceptor.js +94 -0
  34. package/dist/features/audit/audit.interceptor.js.map +1 -0
  35. package/dist/features/audit/index.d.ts +2 -0
  36. package/dist/features/audit/index.d.ts.map +1 -0
  37. package/dist/features/audit/index.js +2 -0
  38. package/dist/features/audit/index.js.map +1 -0
  39. package/dist/features/auth/auth.types.d.ts +13 -0
  40. package/dist/features/auth/auth.types.d.ts.map +1 -0
  41. package/dist/features/auth/auth.types.js +2 -0
  42. package/dist/features/auth/auth.types.js.map +1 -0
  43. package/dist/features/auth/index.d.ts +12 -0
  44. package/dist/features/auth/index.d.ts.map +1 -0
  45. package/dist/features/auth/index.js +7 -0
  46. package/dist/features/auth/index.js.map +1 -0
  47. package/dist/features/auth/public.decorator.d.ts +3 -0
  48. package/dist/features/auth/public.decorator.d.ts.map +1 -0
  49. package/dist/features/auth/public.decorator.js +4 -0
  50. package/dist/features/auth/public.decorator.js.map +1 -0
  51. package/dist/features/auth/resolvers/credentials.resolver.d.ts +10 -0
  52. package/dist/features/auth/resolvers/credentials.resolver.d.ts.map +1 -0
  53. package/dist/features/auth/resolvers/credentials.resolver.js +35 -0
  54. package/dist/features/auth/resolvers/credentials.resolver.js.map +1 -0
  55. package/dist/features/auth/resolvers/custom.resolver.d.ts +7 -0
  56. package/dist/features/auth/resolvers/custom.resolver.d.ts.map +1 -0
  57. package/dist/features/auth/resolvers/custom.resolver.js +9 -0
  58. package/dist/features/auth/resolvers/custom.resolver.js.map +1 -0
  59. package/dist/features/auth/resolvers/jwt.resolver.d.ts +10 -0
  60. package/dist/features/auth/resolvers/jwt.resolver.d.ts.map +1 -0
  61. package/dist/features/auth/resolvers/jwt.resolver.js +24 -0
  62. package/dist/features/auth/resolvers/jwt.resolver.js.map +1 -0
  63. package/dist/features/auth/resolvers/passport.resolver.d.ts +9 -0
  64. package/dist/features/auth/resolvers/passport.resolver.d.ts.map +1 -0
  65. package/dist/features/auth/resolvers/passport.resolver.js +15 -0
  66. package/dist/features/auth/resolvers/passport.resolver.js.map +1 -0
  67. package/dist/features/auth/session.d.ts +9 -0
  68. package/dist/features/auth/session.d.ts.map +1 -0
  69. package/dist/features/auth/session.js +55 -0
  70. package/dist/features/auth/session.js.map +1 -0
  71. package/dist/features/media/enrich.d.ts +13 -0
  72. package/dist/features/media/enrich.d.ts.map +1 -0
  73. package/dist/features/media/enrich.js +56 -0
  74. package/dist/features/media/enrich.js.map +1 -0
  75. package/dist/features/media/index.d.ts +3 -0
  76. package/dist/features/media/index.d.ts.map +1 -0
  77. package/dist/features/media/index.js +3 -0
  78. package/dist/features/media/index.js.map +1 -0
  79. package/dist/features/media/media.helpers.d.ts +13 -0
  80. package/dist/features/media/media.helpers.d.ts.map +1 -0
  81. package/dist/features/media/media.helpers.js +63 -0
  82. package/dist/features/media/media.helpers.js.map +1 -0
  83. package/dist/guards/admin-auth.guard.d.ts +17 -0
  84. package/dist/guards/admin-auth.guard.d.ts.map +1 -0
  85. package/dist/guards/admin-auth.guard.js +82 -0
  86. package/dist/guards/admin-auth.guard.js.map +1 -0
  87. package/dist/guards/admin-policy.guard.d.ts +10 -0
  88. package/dist/guards/admin-policy.guard.d.ts.map +1 -0
  89. package/dist/guards/admin-policy.guard.js +60 -0
  90. package/dist/guards/admin-policy.guard.js.map +1 -0
  91. package/dist/helpers/action.helpers.d.ts +6 -0
  92. package/dist/helpers/action.helpers.d.ts.map +1 -0
  93. package/dist/helpers/action.helpers.js +14 -0
  94. package/dist/helpers/action.helpers.js.map +1 -0
  95. package/dist/helpers/data-source.helpers.d.ts +4 -0
  96. package/dist/helpers/data-source.helpers.d.ts.map +1 -0
  97. package/dist/helpers/data-source.helpers.js +14 -0
  98. package/dist/helpers/data-source.helpers.js.map +1 -0
  99. package/dist/helpers/icons.d.ts +8 -0
  100. package/dist/helpers/icons.d.ts.map +1 -0
  101. package/dist/helpers/icons.js +50 -0
  102. package/dist/helpers/icons.js.map +1 -0
  103. package/dist/helpers/nav-collapse.helper.d.ts +6 -0
  104. package/dist/helpers/nav-collapse.helper.d.ts.map +1 -0
  105. package/dist/helpers/nav-collapse.helper.js +25 -0
  106. package/dist/helpers/nav-collapse.helper.js.map +1 -0
  107. package/dist/helpers/relation.helpers.d.ts +17 -0
  108. package/dist/helpers/relation.helpers.d.ts.map +1 -0
  109. package/dist/helpers/relation.helpers.js +98 -0
  110. package/dist/helpers/relation.helpers.js.map +1 -0
  111. package/dist/helpers/shell-assets.helper.d.ts +6 -0
  112. package/dist/helpers/shell-assets.helper.d.ts.map +1 -0
  113. package/dist/helpers/shell-assets.helper.js +14 -0
  114. package/dist/helpers/shell-assets.helper.js.map +1 -0
  115. package/dist/index.d.ts +29 -0
  116. package/dist/index.d.ts.map +1 -0
  117. package/dist/index.js +27 -0
  118. package/dist/index.js.map +1 -0
  119. package/dist/modules/admin-api-host.module.d.ts +3 -0
  120. package/dist/modules/admin-api-host.module.d.ts.map +1 -0
  121. package/dist/modules/admin-api-host.module.js +17 -0
  122. package/dist/modules/admin-api-host.module.js.map +1 -0
  123. package/dist/modules/admin-core.module.d.ts +3 -0
  124. package/dist/modules/admin-core.module.d.ts.map +1 -0
  125. package/dist/modules/admin-core.module.js +37 -0
  126. package/dist/modules/admin-core.module.js.map +1 -0
  127. package/dist/modules/admin-feature.module.d.ts +3 -0
  128. package/dist/modules/admin-feature.module.d.ts.map +1 -0
  129. package/dist/modules/admin-feature.module.js +14 -0
  130. package/dist/modules/admin-feature.module.js.map +1 -0
  131. package/dist/modules/admin-ui-host.module.d.ts +3 -0
  132. package/dist/modules/admin-ui-host.module.d.ts.map +1 -0
  133. package/dist/modules/admin-ui-host.module.js +19 -0
  134. package/dist/modules/admin-ui-host.module.js.map +1 -0
  135. package/dist/modules/admin.module.d.ts +18 -0
  136. package/dist/modules/admin.module.d.ts.map +1 -0
  137. package/dist/modules/admin.module.js +197 -0
  138. package/dist/modules/admin.module.js.map +1 -0
  139. package/dist/plugin-contracts.d.ts +88 -0
  140. package/dist/plugin-contracts.d.ts.map +1 -0
  141. package/dist/plugin-contracts.js +4 -0
  142. package/dist/plugin-contracts.js.map +1 -0
  143. package/dist/plugins/openapi/admin-openapi.d.ts +21 -0
  144. package/dist/plugins/openapi/admin-openapi.d.ts.map +1 -0
  145. package/dist/plugins/openapi/admin-openapi.js +237 -0
  146. package/dist/plugins/openapi/admin-openapi.js.map +1 -0
  147. package/dist/providers/admin-page.explorer.d.ts +8 -0
  148. package/dist/providers/admin-page.explorer.d.ts.map +1 -0
  149. package/dist/providers/admin-page.explorer.js +43 -0
  150. package/dist/providers/admin-page.explorer.js.map +1 -0
  151. package/dist/providers/admin-page.registry.d.ts +9 -0
  152. package/dist/providers/admin-page.registry.d.ts.map +1 -0
  153. package/dist/providers/admin-page.registry.js +39 -0
  154. package/dist/providers/admin-page.registry.js.map +1 -0
  155. package/dist/providers/admin-panel.bootstrap.d.ts +23 -0
  156. package/dist/providers/admin-panel.bootstrap.d.ts.map +1 -0
  157. package/dist/providers/admin-panel.bootstrap.js +122 -0
  158. package/dist/providers/admin-panel.bootstrap.js.map +1 -0
  159. package/dist/providers/admin-resource.explorer.d.ts +8 -0
  160. package/dist/providers/admin-resource.explorer.d.ts.map +1 -0
  161. package/dist/providers/admin-resource.explorer.js +43 -0
  162. package/dist/providers/admin-resource.explorer.js.map +1 -0
  163. package/dist/providers/admin-resource.registry.d.ts +9 -0
  164. package/dist/providers/admin-resource.registry.d.ts.map +1 -0
  165. package/dist/providers/admin-resource.registry.js +39 -0
  166. package/dist/providers/admin-resource.registry.js.map +1 -0
  167. package/dist/react/admin-app.d.ts +14 -0
  168. package/dist/react/admin-app.d.ts.map +1 -0
  169. package/dist/react/admin-app.js +421 -0
  170. package/dist/react/admin-app.js.map +1 -0
  171. package/dist/react/document.d.ts +13 -0
  172. package/dist/react/document.d.ts.map +1 -0
  173. package/dist/react/document.js +47 -0
  174. package/dist/react/document.js.map +1 -0
  175. package/dist/react/nav-client.d.ts +10 -0
  176. package/dist/react/nav-client.d.ts.map +1 -0
  177. package/dist/react/nav-client.js +153 -0
  178. package/dist/react/nav-client.js.map +1 -0
  179. package/dist/react/theme.d.ts +6 -0
  180. package/dist/react/theme.d.ts.map +1 -0
  181. package/dist/react/theme.js +51 -0
  182. package/dist/react/theme.js.map +1 -0
  183. package/dist/react/types.d.ts +107 -0
  184. package/dist/react/types.d.ts.map +1 -0
  185. package/dist/react/types.js +2 -0
  186. package/dist/react/types.js.map +1 -0
  187. package/dist/tokens.d.ts +106 -0
  188. package/dist/tokens.d.ts.map +1 -0
  189. package/dist/tokens.js +74 -0
  190. package/dist/tokens.js.map +1 -0
  191. package/dist/views/admin.views.d.ts +114 -0
  192. package/dist/views/admin.views.d.ts.map +1 -0
  193. package/dist/views/admin.views.js +212 -0
  194. package/dist/views/admin.views.js.map +1 -0
  195. package/package.json +98 -0
@@ -0,0 +1,1062 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
+ return function (target, key) { decorator(target, key, paramIndex); }
12
+ };
13
+ var AdminController_1;
14
+ import { isRelationCapable, isSoftDeleteCapable } from '@nestjs-dash/adapter';
15
+ import { collectJsonTextareaFields, parseJsonArrayFields, parseJsonTextareaFields, stringifyJsonTextareaFields, validateFormPayload, } from '@nestjs-dash/core';
16
+ import { Body, Controller, Delete, Get, Header, Inject, Logger, NotFoundException, Optional, Param, Post, Put, Query, Req, Res, UseGuards, UseInterceptors, } from '@nestjs/common';
17
+ import { ModuleRef } from '@nestjs/core';
18
+ import { createRequire } from 'node:module';
19
+ import { dirname, join } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { AdminAction } from '../decorators/admin-action.decorator.js';
22
+ import { AdminAuditInterceptor } from '../features/audit/index.js';
23
+ import { AdminPublic } from '../features/auth/public.decorator.js';
24
+ import { parseCookies } from '../features/auth/session.js';
25
+ import { enrichRecordsWithImageUrls, hydrateFormMediaValues } from '../features/media/enrich.js';
26
+ import { extractMediaFields } from '../features/media/media.helpers.js';
27
+ import { AdminAuthGuard } from '../guards/admin-auth.guard.js';
28
+ import { AdminPolicyGuard } from '../guards/admin-policy.guard.js';
29
+ import { executeActionHandler, findActionNode } from '../helpers/action.helpers.js';
30
+ import { NAV_COLLAPSE_COOKIE_NAME, applyNavCollapseState, parseNavCollapseCookie, sanitizeCollapsedGroups, } from '../helpers/nav-collapse.helper.js';
31
+ import { loadRelationSections, requireRelationAdapter, requireRelationManager, resolveBelongsToOptions, } from '../helpers/relation.helpers.js';
32
+ import { resolveShellAssets } from '../helpers/shell-assets.helper.js';
33
+ import { NESTJS_DASH_MEDIA_SERVICE, NESTJS_DASH_STORAGE_SERVICE, } from '../plugin-contracts.js';
34
+ import { THEME_COOKIE_NAME, isThemeCookieValue } from '../react/theme.js';
35
+ import { NESTJS_DASH_PANEL } from '../tokens.js';
36
+ import { buildCustomPagePayload, buildDashboardPayload, buildFormPayload, buildListPayload, buildRelationPayload, buildViewPayload, renderCustomPageHtml, renderDashboardHtml, renderFormPage, renderListHtml, renderRelationActionPage, renderViewHtml, } from '../views/admin.views.js';
37
+ const SPA_NAV_HEADER = 'x-nestjs-dash-nav';
38
+ const require = createRequire(import.meta.url);
39
+ let AdminController = AdminController_1 = class AdminController {
40
+ constructor(panel, storage, media, moduleRef) {
41
+ this.panel = panel;
42
+ this.storage = storage;
43
+ this.media = media;
44
+ this.moduleRef = moduleRef;
45
+ this.logger = new Logger(AdminController_1.name);
46
+ }
47
+ resolvePageInstance(pageClass) {
48
+ try {
49
+ return this.moduleRef.get(pageClass, { strict: false });
50
+ }
51
+ catch (error) {
52
+ this.logger.warn(`Page "${pageClass.slug ?? pageClass.name}" is not resolvable via ModuleRef ` +
53
+ `(${error instanceof Error ? error.message : String(error)}); constructing it ` +
54
+ 'directly with no injected dependencies. If this page needs DI, make sure its ' +
55
+ "class is listed in a module's `providers: [...]` array.");
56
+ }
57
+ return new pageClass();
58
+ }
59
+ shellAssets(req) {
60
+ return resolveShellAssets(this.panel, req);
61
+ }
62
+ collapsedGroupLabels(req) {
63
+ return parseNavCollapseCookie(parseCookies(req?.headers.cookie)[NAV_COLLAPSE_COOKIE_NAME]);
64
+ }
65
+ wantsJson(req) {
66
+ return req?.header(SPA_NAV_HEADER) === '1';
67
+ }
68
+ policyContext(req, action) {
69
+ const user = req
70
+ ?.nestjsDashUser;
71
+ return { user, request: req, action };
72
+ }
73
+ sendRedirect(req, res, url) {
74
+ if (this.wantsJson(req)) {
75
+ res.status(200).json({ redirect: url });
76
+ return;
77
+ }
78
+ res.redirect(303, url);
79
+ }
80
+ serveStylesheet(res) {
81
+ try {
82
+ const cssPath = require.resolve('@nestjs-dash/design-system/nestjs-dash.css');
83
+ res.sendFile(cssPath);
84
+ }
85
+ catch {
86
+ throw new NotFoundException('Admin stylesheet not found. Build @nestjs-dash/design-system first.');
87
+ }
88
+ }
89
+ serveClientScript(res) {
90
+ const jsPath = join(dirname(fileURLToPath(import.meta.url)), '../client/nestjs-dash.js');
91
+ res.sendFile(jsPath, (err) => {
92
+ if (err && !res.headersSent) {
93
+ res.status(404).json({
94
+ message: 'Admin client script not found. Build @nestjs-dash/core first.',
95
+ error: 'Not Found',
96
+ statusCode: 404,
97
+ });
98
+ }
99
+ });
100
+ }
101
+ setTheme(body, res) {
102
+ if (!isThemeCookieValue(body?.theme)) {
103
+ res.status(400).json({ message: 'Invalid theme value.' });
104
+ return;
105
+ }
106
+ res.cookie(THEME_COOKIE_NAME, body.theme, {
107
+ path: this.panel.getPath(),
108
+ maxAge: 60 * 60 * 24 * 365 * 1000,
109
+ httpOnly: true,
110
+ sameSite: 'lax',
111
+ });
112
+ res.status(204).send();
113
+ }
114
+ setNavCollapse(body, res) {
115
+ const groups = sanitizeCollapsedGroups(body?.collapsedGroups);
116
+ res.cookie(NAV_COLLAPSE_COOKIE_NAME, JSON.stringify(groups), {
117
+ path: this.panel.getPath(),
118
+ maxAge: 60 * 60 * 24 * 365 * 1000,
119
+ httpOnly: true,
120
+ sameSite: 'lax',
121
+ });
122
+ res.status(204).send();
123
+ }
124
+ async renderDashboard(req) {
125
+ const serialized = this.panel.serialize();
126
+ const cards = await Promise.all(serialized.resources.map(async (resource) => {
127
+ const adapter = this.panel.getResourceAdapter(resource.slug);
128
+ const totalItems = adapter
129
+ ? (await adapter.find({ page: 1, limit: 1 }, resource.listQueryConfig ?? undefined)).meta
130
+ .totalItems
131
+ : 0;
132
+ return { resource, totalItems };
133
+ }));
134
+ const policyCtx = this.policyContext(req, 'view');
135
+ const ctx = { panelPath: serialized.path, brandName: serialized.brandName };
136
+ const widgets = await this.renderWidgetList(this.panel.getWidgets(), ctx, policyCtx);
137
+ const model = {
138
+ brandName: serialized.brandName,
139
+ ...this.shellAssets(req),
140
+ path: serialized.path,
141
+ navigation: serialized.navigation,
142
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
143
+ cards,
144
+ widgets,
145
+ };
146
+ return this.wantsJson(req) ? buildDashboardPayload(model) : renderDashboardHtml(model);
147
+ }
148
+ async renderWidgetList(widgetList, ctx, policyCtx) {
149
+ const widgets = { main: [], side: [] };
150
+ for (const widget of widgetList) {
151
+ if (widget.canAccess && !(await widget.canAccess(policyCtx))) {
152
+ continue;
153
+ }
154
+ const slot = widget.slot ?? 'main';
155
+ if (widget.chart) {
156
+ widgets[slot].push({ type: 'chart', chart: await widget.chart(ctx) });
157
+ }
158
+ else if (widget.render) {
159
+ widgets[slot].push({ type: 'html', html: await widget.render(ctx) });
160
+ }
161
+ else {
162
+ this.logger.warn(`Widget in slot "${slot}" implements neither render() nor chart() — skipping.`);
163
+ }
164
+ }
165
+ return widgets;
166
+ }
167
+ getPanelApi() {
168
+ return this.panel.serialize();
169
+ }
170
+ getResource(slug) {
171
+ return this.requireResource(slug);
172
+ }
173
+ async listRecords(slug, query) {
174
+ const resource = this.requireResource(slug);
175
+ const adapter = this.requireAdapter(slug);
176
+ return adapter.find(parseListQuery(query), resource.listQueryConfig ?? undefined);
177
+ }
178
+ async getRecord(slug, id) {
179
+ const adapter = this.requireAdapter(slug);
180
+ const record = await adapter.findOne(id);
181
+ if (!record) {
182
+ throw new NotFoundException(`Record ${id} not found`);
183
+ }
184
+ return record;
185
+ }
186
+ async createRecord(slug, body) {
187
+ return this.requireAdapter(slug).create(normalizeBody(body));
188
+ }
189
+ async updateRecord(slug, id, body) {
190
+ return this.requireAdapter(slug).update(id, normalizeBody(body));
191
+ }
192
+ async deleteRecord(slug, id) {
193
+ await this.requireAdapter(slug).delete(id);
194
+ return { ok: true };
195
+ }
196
+ async listOptions(resourceSlug, query) {
197
+ const adapter = this.requireAdapter(resourceSlug);
198
+ if (!isRelationCapable(adapter)) {
199
+ throw new NotFoundException(`Options loader unavailable for ${resourceSlug}`);
200
+ }
201
+ return adapter.searchRelatedOptions({
202
+ resource: resourceSlug,
203
+ titleAttribute: query.titleAttribute ?? 'name',
204
+ searchBy: query.searchBy?.split(',').filter(Boolean),
205
+ }, { search: query.search, limit: query.limit ? Number(query.limit) : 25 });
206
+ }
207
+ async renderCustomPage(pageSlug, req) {
208
+ const pageClass = this.panel.getPageClass(pageSlug);
209
+ if (!pageClass) {
210
+ throw new NotFoundException(`Page ${pageSlug} not found`);
211
+ }
212
+ const serialized = this.panel.serialize();
213
+ const page = serialized.pages.find((item) => item.slug === pageSlug);
214
+ if (!page) {
215
+ throw new NotFoundException(`Page ${pageSlug} not found`);
216
+ }
217
+ const instance = this.resolvePageInstance(pageClass);
218
+ const ctx = { panelPath: serialized.path, brandName: serialized.brandName };
219
+ const declaredWidgets = (await instance.widgets?.(ctx)) ?? [];
220
+ const content = await (instance.content?.(ctx) ??
221
+ (declaredWidgets.length > 0 ? '' : `<p class="text-afi-muted mt-4">Empty page.</p>`));
222
+ const widgets = declaredWidgets.length > 0
223
+ ? await this.renderWidgetList(declaredWidgets, ctx, this.policyContext(req, 'view'))
224
+ : undefined;
225
+ const pageStyles = await instance.styles?.(ctx);
226
+ const model = {
227
+ brandName: serialized.brandName,
228
+ ...this.shellAssets(req),
229
+ path: serialized.path,
230
+ navigation: serialized.navigation,
231
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
232
+ slug: page.slug,
233
+ title: page.title,
234
+ content,
235
+ widgets,
236
+ pageStyles,
237
+ };
238
+ return this.wantsJson(req) ? buildCustomPagePayload(model) : renderCustomPageHtml(model);
239
+ }
240
+ async submitPageAction(pageSlug, action, body, res, req) {
241
+ const pageClass = this.panel.getPageClass(pageSlug);
242
+ if (!pageClass) {
243
+ throw new NotFoundException(`Page ${pageSlug} not found`);
244
+ }
245
+ const instance = this.resolvePageInstance(pageClass);
246
+ const handler = instance.actions?.[action];
247
+ if (!handler) {
248
+ throw new NotFoundException(`Action ${action} not found on page ${pageSlug}`);
249
+ }
250
+ const result = await handler({
251
+ pageSlug,
252
+ action,
253
+ values: body,
254
+ user: this.policyContext(req, action).user,
255
+ request: req,
256
+ });
257
+ this.sendRedirect(req, res, `${this.panel.getPath()}/pages/${pageSlug}?flash=${encodeURIComponent(result?.message ?? `Ran ${action}`)}`);
258
+ }
259
+ async renderRelationCreate(slug, id, relation, req) {
260
+ const serialized = this.panel.serialize();
261
+ const resource = this.requireResource(slug);
262
+ const manager = requireRelationManager(resource, relation);
263
+ const ownerRecord = (await this.requireAdapter(slug).findOne(id)) ?? undefined;
264
+ const model = {
265
+ brandName: serialized.brandName,
266
+ ...this.shellAssets(req),
267
+ path: serialized.path,
268
+ navigation: serialized.navigation,
269
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
270
+ resource,
271
+ manager,
272
+ ownerId: id,
273
+ ownerRecord,
274
+ title: `Create ${manager.title}`,
275
+ action: `${serialized.path}/${slug}/${id}/relations/${relation}/create`,
276
+ mode: 'create',
277
+ };
278
+ return this.wantsJson(req) ? buildRelationPayload(model) : renderRelationActionPage(model);
279
+ }
280
+ async submitRelationCreate(slug, id, relation, body, req, res) {
281
+ const resource = this.requireResource(slug);
282
+ const manager = requireRelationManager(resource, relation);
283
+ const ownerAdapter = requireRelationAdapter(this.requireAdapter(slug));
284
+ const relatedAdapter = this.requireAdapter(manager.relatedResource);
285
+ const payload = normalizeBody(body);
286
+ if (manager.type === 'has-many' && manager.foreignKey) {
287
+ payload[manager.foreignKey] = id;
288
+ }
289
+ const created = await relatedAdapter.create(payload);
290
+ if (manager.type === 'belongs-to-many') {
291
+ await ownerAdapter.attach(id, manager.definition, String(created.id), pickPivot(body, manager.pivotFields));
292
+ }
293
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
294
+ }
295
+ async renderAssociate(slug, id, relation, req) {
296
+ return this.renderLinkForm(slug, id, relation, 'associate', req);
297
+ }
298
+ async submitAssociate(slug, id, relation, body, req, res) {
299
+ const resource = this.requireResource(slug);
300
+ const manager = requireRelationManager(resource, relation);
301
+ const adapter = requireRelationAdapter(this.requireAdapter(slug));
302
+ await adapter.associate(id, manager.definition, String(body.relatedId));
303
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
304
+ }
305
+ async renderAttach(slug, id, relation, req) {
306
+ return this.renderLinkForm(slug, id, relation, 'attach', req);
307
+ }
308
+ async submitAttach(slug, id, relation, body, req, res) {
309
+ const resource = this.requireResource(slug);
310
+ const manager = requireRelationManager(resource, relation);
311
+ const adapter = requireRelationAdapter(this.requireAdapter(slug));
312
+ await adapter.attach(id, manager.definition, String(body.relatedId), pickPivot(body, manager.pivotFields));
313
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
314
+ }
315
+ async submitDissociate(slug, id, relation, relatedId, req, res) {
316
+ const resource = this.requireResource(slug);
317
+ const manager = requireRelationManager(resource, relation);
318
+ const adapter = requireRelationAdapter(this.requireAdapter(slug));
319
+ await adapter.dissociate(id, manager.definition, relatedId);
320
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
321
+ }
322
+ async submitDetach(slug, id, relation, relatedId, req, res) {
323
+ const resource = this.requireResource(slug);
324
+ const manager = requireRelationManager(resource, relation);
325
+ const adapter = requireRelationAdapter(this.requireAdapter(slug));
326
+ await adapter.detach(id, manager.definition, relatedId);
327
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
328
+ }
329
+ async renderCreate(slug, req) {
330
+ const serialized = this.panel.serialize();
331
+ const resource = this.requireResource(slug);
332
+ const fieldOptions = await resolveBelongsToOptions(this.panel, resource);
333
+ const model = {
334
+ brandName: serialized.brandName,
335
+ ...this.shellAssets(req),
336
+ path: serialized.path,
337
+ navigation: serialized.navigation,
338
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
339
+ resource,
340
+ title: `Create ${resource.label}`,
341
+ action: `${serialized.path}/${slug}/create`,
342
+ submitLabel: 'Create',
343
+ fieldOptions,
344
+ };
345
+ return this.wantsJson(req) ? buildFormPayload(model) : renderFormPage(model);
346
+ }
347
+ async submitCreate(slug, body, res, req) {
348
+ const resource = this.requireResource(slug);
349
+ const { payload: mediaPayload, mediaByField } = extractMediaFields(normalizeBody(body), resource.form);
350
+ let payload = parseJsonArrayFields(mediaPayload, resource.form);
351
+ payload = parseJsonTextareaFields(payload, resource.form);
352
+ const errors = validateFormPayload(resource.form, payload);
353
+ if (errors) {
354
+ await this.rerenderForm(req, res, {
355
+ slug,
356
+ resource,
357
+ title: `Create ${resource.label}`,
358
+ action: `${this.panel.getPath()}/${slug}/create`,
359
+ submitLabel: 'Create',
360
+ values: payload,
361
+ errors,
362
+ });
363
+ return;
364
+ }
365
+ const created = await this.requireAdapter(slug).create(payload);
366
+ await this.syncMediaFields(slug, String(created.id), mediaByField);
367
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${String(created.id)}`);
368
+ }
369
+ async submitBulk(slug, body, req, res) {
370
+ const resource = this.requireResource(slug);
371
+ const adapter = this.requireAdapter(slug);
372
+ const action = String(body.bulkAction ?? '');
373
+ const ids = normalizeIds(body.ids);
374
+ const actionNode = findActionNode(resource.table?.bulkActions, action);
375
+ let message = `Processed ${ids.length} record(s)`;
376
+ if (actionNode) {
377
+ const records = (await Promise.all(ids.map((recordId) => adapter.findOne(recordId)))).filter((record) => record != null);
378
+ const result = await executeActionHandler(actionNode, adapter, {
379
+ resourceSlug: slug,
380
+ records,
381
+ ids,
382
+ values: {},
383
+ });
384
+ if (result?.message)
385
+ message = result.message;
386
+ }
387
+ else if (action === 'bulk-delete' || action === 'delete') {
388
+ for (const recordId of ids) {
389
+ await adapter.delete(recordId);
390
+ }
391
+ }
392
+ const url = `${this.panel.getPath()}/${slug}?flash=${encodeURIComponent(message)}`;
393
+ if (!res) {
394
+ return { url, statusCode: 303 };
395
+ }
396
+ this.sendRedirect(req, res, url);
397
+ }
398
+ async submitToolbarAction(slug, action, body, res, req) {
399
+ const resource = this.requireResource(slug);
400
+ const adapter = this.requireAdapter(slug);
401
+ const actionNode = findActionNode(resource.table?.toolbarActions, action);
402
+ if (!actionNode) {
403
+ throw new NotFoundException(`Action ${action} not found on ${slug}`);
404
+ }
405
+ const result = await executeActionHandler(actionNode, adapter, {
406
+ resourceSlug: slug,
407
+ values: body,
408
+ });
409
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}?flash=${encodeURIComponent(result?.message ?? `Ran ${action}`)}`);
410
+ }
411
+ async submitRecordAction(slug, id, action, body, res, req) {
412
+ const resource = this.requireResource(slug);
413
+ const adapter = this.requireAdapter(slug);
414
+ const actionNode = findActionNode(resource.table?.actions, action);
415
+ if (!actionNode) {
416
+ throw new NotFoundException(`Action ${action} not found on ${slug}`);
417
+ }
418
+ if (actionNode.kind === 'restore-action') {
419
+ if (!isSoftDeleteCapable(adapter)) {
420
+ throw new NotFoundException(`Resource ${slug} does not support soft delete`);
421
+ }
422
+ await adapter.restore(id);
423
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}?flash=${encodeURIComponent('Record restored')}`);
424
+ return;
425
+ }
426
+ const record = await adapter.findOne(id);
427
+ if (!record) {
428
+ throw new NotFoundException(`Record ${id} not found`);
429
+ }
430
+ const result = await executeActionHandler(actionNode, adapter, {
431
+ resourceSlug: slug,
432
+ record,
433
+ values: body,
434
+ });
435
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}?flash=${encodeURIComponent(result?.message ?? `Ran ${action}`)}`);
436
+ }
437
+ async renderEdit(slug, id, req) {
438
+ const serialized = this.panel.serialize();
439
+ const resource = this.requireResource(slug);
440
+ const record = await this.requireAdapter(slug).findOne(id);
441
+ if (!record) {
442
+ throw new NotFoundException(`Record ${id} not found`);
443
+ }
444
+ const fieldOptions = await resolveBelongsToOptions(this.panel, resource);
445
+ const hydratedValues = await hydrateFormMediaValues(record, resource.form, {
446
+ resourceSlug: slug,
447
+ recordId: id,
448
+ media: this.media,
449
+ });
450
+ const values = stringifyJsonTextareaFields(hydratedValues, resource.form);
451
+ const model = {
452
+ brandName: serialized.brandName,
453
+ ...this.shellAssets(req),
454
+ path: serialized.path,
455
+ navigation: serialized.navigation,
456
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
457
+ resource,
458
+ title: `Edit ${resource.label}`,
459
+ action: `${serialized.path}/${slug}/${id}/edit`,
460
+ values,
461
+ submitLabel: 'Save',
462
+ fieldOptions,
463
+ mode: 'edit',
464
+ };
465
+ return this.wantsJson(req) ? buildFormPayload(model) : renderFormPage(model);
466
+ }
467
+ async submitEdit(slug, id, body, res, req) {
468
+ const resource = this.requireResource(slug);
469
+ const { payload: mediaPayload, mediaByField } = extractMediaFields(normalizeBody(body), resource.form);
470
+ let payload = parseJsonArrayFields(mediaPayload, resource.form);
471
+ const needsExistingRecord = collectJsonTextareaFields(resource.form).some((field) => typeof field.props.jsonProperty === 'string');
472
+ const existingRecord = needsExistingRecord
473
+ ? await this.requireAdapter(slug).findOne(id)
474
+ : undefined;
475
+ payload = parseJsonTextareaFields(payload, resource.form, existingRecord);
476
+ const errors = validateFormPayload(resource.form, payload);
477
+ if (errors) {
478
+ await this.rerenderForm(req, res, {
479
+ slug,
480
+ resource,
481
+ title: `Edit ${resource.label}`,
482
+ action: `${this.panel.getPath()}/${slug}/${id}/edit`,
483
+ submitLabel: 'Save',
484
+ values: payload,
485
+ errors,
486
+ mode: 'edit',
487
+ });
488
+ return;
489
+ }
490
+ await this.requireAdapter(slug).update(id, payload);
491
+ await this.syncMediaFields(slug, id, mediaByField);
492
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}/${id}`);
493
+ }
494
+ async rerenderForm(req, res, options) {
495
+ const serialized = this.panel.serialize();
496
+ const fieldOptions = await resolveBelongsToOptions(this.panel, options.resource);
497
+ const model = {
498
+ brandName: serialized.brandName,
499
+ ...this.shellAssets(req),
500
+ path: serialized.path,
501
+ navigation: serialized.navigation,
502
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
503
+ resource: options.resource,
504
+ title: options.title,
505
+ action: options.action,
506
+ submitLabel: options.submitLabel,
507
+ values: stringifyJsonTextareaFields(options.values, options.resource.form),
508
+ fieldOptions,
509
+ errors: options.errors,
510
+ mode: options.mode,
511
+ };
512
+ if (this.wantsJson(req)) {
513
+ res.status(422).json(buildFormPayload(model));
514
+ return;
515
+ }
516
+ res.status(422).type('html').send(renderFormPage(model));
517
+ }
518
+ async submitDelete(slug, id, res, req) {
519
+ await this.requireAdapter(slug).delete(id);
520
+ this.sendRedirect(req, res, `${this.panel.getPath()}/${slug}?flash=${encodeURIComponent('Record deleted')}`);
521
+ }
522
+ async renderView(slug, id, query, req) {
523
+ if (id === 'create' || id === 'bulk' || id === 'relations') {
524
+ throw new NotFoundException();
525
+ }
526
+ const serialized = this.panel.serialize();
527
+ const resource = this.requireResource(slug);
528
+ const record = await this.requireAdapter(slug).findOne(id);
529
+ if (!record) {
530
+ throw new NotFoundException(`Record ${id} not found`);
531
+ }
532
+ const relationSections = await loadRelationSections(this.panel, resource, id, query);
533
+ const panels = await this.renderResourcePanels(slug, record, serialized.path, serialized.brandName);
534
+ const model = {
535
+ brandName: serialized.brandName,
536
+ ...this.shellAssets(req),
537
+ path: serialized.path,
538
+ navigation: serialized.navigation,
539
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
540
+ resource,
541
+ record,
542
+ relationSections,
543
+ panels,
544
+ };
545
+ return this.wantsJson(req) ? buildViewPayload(model) : renderViewHtml(model);
546
+ }
547
+ async renderResourcePanels(slug, record, panelPath, brandName) {
548
+ const resourceClass = this.panel.getResourceClass(slug);
549
+ if (!resourceClass?.panels?.length) {
550
+ return [];
551
+ }
552
+ const ctx = { resourceSlug: slug, record, panelPath, brandName };
553
+ return Promise.all(resourceClass.panels.map((PanelClass) => new PanelClass().render(ctx)));
554
+ }
555
+ async renderList(slug, query, res, req) {
556
+ if (slug === 'api' ||
557
+ slug === 'uploads' ||
558
+ slug === 'files' ||
559
+ slug === 'pages' ||
560
+ slug === 'login' ||
561
+ slug === 'logout') {
562
+ throw new NotFoundException();
563
+ }
564
+ const serialized = this.panel.serialize();
565
+ const resource = this.requireResource(slug);
566
+ const adapter = this.panel.getResourceAdapter(slug);
567
+ const listQuery = parseListQuery(query);
568
+ const page = adapter
569
+ ? await adapter.find(listQuery, resource.listQueryConfig ?? undefined)
570
+ : {
571
+ data: [],
572
+ meta: { totalItems: 0, currentPage: 1, totalPages: 1, itemsPerPage: 20 },
573
+ };
574
+ const records = await enrichRecordsWithImageUrls(page.data, resource.table?.columns, {
575
+ resourceSlug: slug,
576
+ storage: this.storage,
577
+ media: this.media,
578
+ });
579
+ const model = {
580
+ brandName: serialized.brandName,
581
+ ...this.shellAssets(req),
582
+ path: serialized.path,
583
+ navigation: serialized.navigation,
584
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
585
+ resource,
586
+ records,
587
+ meta: page.meta,
588
+ flash: query.flash,
589
+ query: {
590
+ search: listQuery.search,
591
+ filter: listQuery.filter,
592
+ filtersOpen: query.filtersOpen === '1' || query.filtersOpen === 'true',
593
+ limit: listQuery.limit,
594
+ sortBy: listQuery.sortBy,
595
+ withDeleted: listQuery.withDeleted,
596
+ },
597
+ };
598
+ if (this.wantsJson(req)) {
599
+ res.json(buildListPayload(model));
600
+ return;
601
+ }
602
+ res.type('html').send(renderListHtml(model));
603
+ }
604
+ async syncMediaFields(slug, recordId, mediaByField) {
605
+ if (!this.media?.isEnabled())
606
+ return;
607
+ for (const entry of Object.values(mediaByField)) {
608
+ await this.media.syncCollection({ modelType: slug, modelId: recordId }, entry.collection, entry.items, { disk: entry.disk });
609
+ }
610
+ }
611
+ async renderLinkForm(slug, id, relation, mode, req) {
612
+ const serialized = this.panel.serialize();
613
+ const resource = this.requireResource(slug);
614
+ const manager = requireRelationManager(resource, relation);
615
+ const adapter = requireRelationAdapter(this.requireAdapter(slug));
616
+ const ownerRecord = (await this.requireAdapter(slug).findOne(id)) ?? undefined;
617
+ const options = await adapter.searchRelatedOptions({
618
+ resource: manager.relatedResource,
619
+ titleAttribute: 'name',
620
+ searchBy: ['name'],
621
+ });
622
+ const model = {
623
+ brandName: serialized.brandName,
624
+ ...this.shellAssets(req),
625
+ path: serialized.path,
626
+ navigation: serialized.navigation,
627
+ navigationGroups: applyNavCollapseState(serialized.navigationGroups, this.collapsedGroupLabels(req)),
628
+ resource,
629
+ manager,
630
+ ownerId: id,
631
+ ownerRecord,
632
+ title: `${mode === 'attach' ? 'Attach' : 'Associate'} ${manager.title}`,
633
+ action: `${serialized.path}/${slug}/${id}/relations/${relation}/${mode}`,
634
+ mode,
635
+ options,
636
+ };
637
+ return this.wantsJson(req) ? buildRelationPayload(model) : renderRelationActionPage(model);
638
+ }
639
+ requireResource(slug) {
640
+ const resource = this.panel.serialize().resources.find((item) => item.slug === slug);
641
+ if (!resource) {
642
+ throw new NotFoundException(`Resource ${slug} not found`);
643
+ }
644
+ return resource;
645
+ }
646
+ requireAdapter(slug) {
647
+ const adapter = this.panel.getResourceAdapter(slug);
648
+ if (!adapter) {
649
+ throw new NotFoundException(`Adapter for ${slug} not found`);
650
+ }
651
+ return adapter;
652
+ }
653
+ };
654
+ __decorate([
655
+ AdminPublic(),
656
+ Get('_/assets/nestjs-dash.css'),
657
+ Header('Content-Type', 'text/css; charset=utf-8'),
658
+ Header('Cache-Control', 'public, max-age=3600'),
659
+ __param(0, Res()),
660
+ __metadata("design:type", Function),
661
+ __metadata("design:paramtypes", [Object]),
662
+ __metadata("design:returntype", void 0)
663
+ ], AdminController.prototype, "serveStylesheet", null);
664
+ __decorate([
665
+ AdminPublic(),
666
+ Get('_/assets/nestjs-dash.js'),
667
+ Header('Content-Type', 'text/javascript; charset=utf-8'),
668
+ Header('Cache-Control', 'public, max-age=3600'),
669
+ __param(0, Res()),
670
+ __metadata("design:type", Function),
671
+ __metadata("design:paramtypes", [Object]),
672
+ __metadata("design:returntype", void 0)
673
+ ], AdminController.prototype, "serveClientScript", null);
674
+ __decorate([
675
+ Post('_/theme'),
676
+ __param(0, Body()),
677
+ __param(1, Res()),
678
+ __metadata("design:type", Function),
679
+ __metadata("design:paramtypes", [Object, Object]),
680
+ __metadata("design:returntype", void 0)
681
+ ], AdminController.prototype, "setTheme", null);
682
+ __decorate([
683
+ Post('_/nav-collapse'),
684
+ __param(0, Body()),
685
+ __param(1, Res()),
686
+ __metadata("design:type", Function),
687
+ __metadata("design:paramtypes", [Object, Object]),
688
+ __metadata("design:returntype", void 0)
689
+ ], AdminController.prototype, "setNavCollapse", null);
690
+ __decorate([
691
+ Get(),
692
+ __param(0, Req()),
693
+ __metadata("design:type", Function),
694
+ __metadata("design:paramtypes", [Object]),
695
+ __metadata("design:returntype", Promise)
696
+ ], AdminController.prototype, "renderDashboard", null);
697
+ __decorate([
698
+ Get('api/panel'),
699
+ __metadata("design:type", Function),
700
+ __metadata("design:paramtypes", []),
701
+ __metadata("design:returntype", Object)
702
+ ], AdminController.prototype, "getPanelApi", null);
703
+ __decorate([
704
+ Get('api/resources/:slug'),
705
+ __param(0, Param('slug')),
706
+ __metadata("design:type", Function),
707
+ __metadata("design:paramtypes", [String]),
708
+ __metadata("design:returntype", Object)
709
+ ], AdminController.prototype, "getResource", null);
710
+ __decorate([
711
+ Get('api/resources/:slug/records'),
712
+ __param(0, Param('slug')),
713
+ __param(1, Query()),
714
+ __metadata("design:type", Function),
715
+ __metadata("design:paramtypes", [String, Object]),
716
+ __metadata("design:returntype", Promise)
717
+ ], AdminController.prototype, "listRecords", null);
718
+ __decorate([
719
+ Get('api/resources/:slug/records/:id'),
720
+ __param(0, Param('slug')),
721
+ __param(1, Param('id')),
722
+ __metadata("design:type", Function),
723
+ __metadata("design:paramtypes", [String, String]),
724
+ __metadata("design:returntype", Promise)
725
+ ], AdminController.prototype, "getRecord", null);
726
+ __decorate([
727
+ Post('api/resources/:slug/records'),
728
+ AdminAction('create'),
729
+ __param(0, Param('slug')),
730
+ __param(1, Body()),
731
+ __metadata("design:type", Function),
732
+ __metadata("design:paramtypes", [String, Object]),
733
+ __metadata("design:returntype", Promise)
734
+ ], AdminController.prototype, "createRecord", null);
735
+ __decorate([
736
+ Put('api/resources/:slug/records/:id'),
737
+ AdminAction('update'),
738
+ __param(0, Param('slug')),
739
+ __param(1, Param('id')),
740
+ __param(2, Body()),
741
+ __metadata("design:type", Function),
742
+ __metadata("design:paramtypes", [String, String, Object]),
743
+ __metadata("design:returntype", Promise)
744
+ ], AdminController.prototype, "updateRecord", null);
745
+ __decorate([
746
+ Delete('api/resources/:slug/records/:id'),
747
+ AdminAction('delete'),
748
+ __param(0, Param('slug')),
749
+ __param(1, Param('id')),
750
+ __metadata("design:type", Function),
751
+ __metadata("design:paramtypes", [String, String]),
752
+ __metadata("design:returntype", Promise)
753
+ ], AdminController.prototype, "deleteRecord", null);
754
+ __decorate([
755
+ Get('api/options/:resource'),
756
+ __param(0, Param('resource')),
757
+ __param(1, Query()),
758
+ __metadata("design:type", Function),
759
+ __metadata("design:paramtypes", [String, Object]),
760
+ __metadata("design:returntype", Promise)
761
+ ], AdminController.prototype, "listOptions", null);
762
+ __decorate([
763
+ Get('pages/:pageSlug'),
764
+ __param(0, Param('pageSlug')),
765
+ __param(1, Req()),
766
+ __metadata("design:type", Function),
767
+ __metadata("design:paramtypes", [String, Object]),
768
+ __metadata("design:returntype", Promise)
769
+ ], AdminController.prototype, "renderCustomPage", null);
770
+ __decorate([
771
+ Post('pages/:pageSlug/actions/:action'),
772
+ AdminAction('action'),
773
+ __param(0, Param('pageSlug')),
774
+ __param(1, Param('action')),
775
+ __param(2, Body()),
776
+ __param(3, Res()),
777
+ __param(4, Req()),
778
+ __metadata("design:type", Function),
779
+ __metadata("design:paramtypes", [String, String, Object, Object, Object]),
780
+ __metadata("design:returntype", Promise)
781
+ ], AdminController.prototype, "submitPageAction", null);
782
+ __decorate([
783
+ Get(':slug/:id/relations/:relation/create'),
784
+ AdminAction('update'),
785
+ __param(0, Param('slug')),
786
+ __param(1, Param('id')),
787
+ __param(2, Param('relation')),
788
+ __param(3, Req()),
789
+ __metadata("design:type", Function),
790
+ __metadata("design:paramtypes", [String, String, String, Object]),
791
+ __metadata("design:returntype", Promise)
792
+ ], AdminController.prototype, "renderRelationCreate", null);
793
+ __decorate([
794
+ Post(':slug/:id/relations/:relation/create'),
795
+ AdminAction('update'),
796
+ __param(0, Param('slug')),
797
+ __param(1, Param('id')),
798
+ __param(2, Param('relation')),
799
+ __param(3, Body()),
800
+ __param(4, Req()),
801
+ __param(5, Res()),
802
+ __metadata("design:type", Function),
803
+ __metadata("design:paramtypes", [String, String, String, Object, Object, Object]),
804
+ __metadata("design:returntype", Promise)
805
+ ], AdminController.prototype, "submitRelationCreate", null);
806
+ __decorate([
807
+ Get(':slug/:id/relations/:relation/associate'),
808
+ AdminAction('update'),
809
+ __param(0, Param('slug')),
810
+ __param(1, Param('id')),
811
+ __param(2, Param('relation')),
812
+ __param(3, Req()),
813
+ __metadata("design:type", Function),
814
+ __metadata("design:paramtypes", [String, String, String, Object]),
815
+ __metadata("design:returntype", Promise)
816
+ ], AdminController.prototype, "renderAssociate", null);
817
+ __decorate([
818
+ Post(':slug/:id/relations/:relation/associate'),
819
+ AdminAction('update'),
820
+ __param(0, Param('slug')),
821
+ __param(1, Param('id')),
822
+ __param(2, Param('relation')),
823
+ __param(3, Body()),
824
+ __param(4, Req()),
825
+ __param(5, Res()),
826
+ __metadata("design:type", Function),
827
+ __metadata("design:paramtypes", [String, String, String, Object, Object, Object]),
828
+ __metadata("design:returntype", Promise)
829
+ ], AdminController.prototype, "submitAssociate", null);
830
+ __decorate([
831
+ Get(':slug/:id/relations/:relation/attach'),
832
+ AdminAction('update'),
833
+ __param(0, Param('slug')),
834
+ __param(1, Param('id')),
835
+ __param(2, Param('relation')),
836
+ __param(3, Req()),
837
+ __metadata("design:type", Function),
838
+ __metadata("design:paramtypes", [String, String, String, Object]),
839
+ __metadata("design:returntype", Promise)
840
+ ], AdminController.prototype, "renderAttach", null);
841
+ __decorate([
842
+ Post(':slug/:id/relations/:relation/attach'),
843
+ AdminAction('update'),
844
+ __param(0, Param('slug')),
845
+ __param(1, Param('id')),
846
+ __param(2, Param('relation')),
847
+ __param(3, Body()),
848
+ __param(4, Req()),
849
+ __param(5, Res()),
850
+ __metadata("design:type", Function),
851
+ __metadata("design:paramtypes", [String, String, String, Object, Object, Object]),
852
+ __metadata("design:returntype", Promise)
853
+ ], AdminController.prototype, "submitAttach", null);
854
+ __decorate([
855
+ Post(':slug/:id/relations/:relation/dissociate/:relatedId'),
856
+ AdminAction('update'),
857
+ __param(0, Param('slug')),
858
+ __param(1, Param('id')),
859
+ __param(2, Param('relation')),
860
+ __param(3, Param('relatedId')),
861
+ __param(4, Req()),
862
+ __param(5, Res()),
863
+ __metadata("design:type", Function),
864
+ __metadata("design:paramtypes", [String, String, String, String, Object, Object]),
865
+ __metadata("design:returntype", Promise)
866
+ ], AdminController.prototype, "submitDissociate", null);
867
+ __decorate([
868
+ Post(':slug/:id/relations/:relation/detach/:relatedId'),
869
+ AdminAction('update'),
870
+ __param(0, Param('slug')),
871
+ __param(1, Param('id')),
872
+ __param(2, Param('relation')),
873
+ __param(3, Param('relatedId')),
874
+ __param(4, Req()),
875
+ __param(5, Res()),
876
+ __metadata("design:type", Function),
877
+ __metadata("design:paramtypes", [String, String, String, String, Object, Object]),
878
+ __metadata("design:returntype", Promise)
879
+ ], AdminController.prototype, "submitDetach", null);
880
+ __decorate([
881
+ Get(':slug/create'),
882
+ AdminAction('create'),
883
+ __param(0, Param('slug')),
884
+ __param(1, Req()),
885
+ __metadata("design:type", Function),
886
+ __metadata("design:paramtypes", [String, Object]),
887
+ __metadata("design:returntype", Promise)
888
+ ], AdminController.prototype, "renderCreate", null);
889
+ __decorate([
890
+ Post(':slug/create'),
891
+ AdminAction('create'),
892
+ __param(0, Param('slug')),
893
+ __param(1, Body()),
894
+ __param(2, Res()),
895
+ __param(3, Req()),
896
+ __metadata("design:type", Function),
897
+ __metadata("design:paramtypes", [String, Object, Object, Object]),
898
+ __metadata("design:returntype", Promise)
899
+ ], AdminController.prototype, "submitCreate", null);
900
+ __decorate([
901
+ Post(':slug/bulk'),
902
+ AdminAction('bulk-action'),
903
+ __param(0, Param('slug')),
904
+ __param(1, Body()),
905
+ __param(2, Req()),
906
+ __param(3, Res()),
907
+ __metadata("design:type", Function),
908
+ __metadata("design:paramtypes", [String, Object, Object, Object]),
909
+ __metadata("design:returntype", Promise)
910
+ ], AdminController.prototype, "submitBulk", null);
911
+ __decorate([
912
+ Post(':slug/actions/:action'),
913
+ AdminAction('action'),
914
+ __param(0, Param('slug')),
915
+ __param(1, Param('action')),
916
+ __param(2, Body()),
917
+ __param(3, Res()),
918
+ __param(4, Req()),
919
+ __metadata("design:type", Function),
920
+ __metadata("design:paramtypes", [String, String, Object, Object, Object]),
921
+ __metadata("design:returntype", Promise)
922
+ ], AdminController.prototype, "submitToolbarAction", null);
923
+ __decorate([
924
+ Post(':slug/:id/actions/:action'),
925
+ AdminAction('action'),
926
+ __param(0, Param('slug')),
927
+ __param(1, Param('id')),
928
+ __param(2, Param('action')),
929
+ __param(3, Body()),
930
+ __param(4, Res()),
931
+ __param(5, Req()),
932
+ __metadata("design:type", Function),
933
+ __metadata("design:paramtypes", [String, String, String, Object, Object, Object]),
934
+ __metadata("design:returntype", Promise)
935
+ ], AdminController.prototype, "submitRecordAction", null);
936
+ __decorate([
937
+ Get(':slug/:id/edit'),
938
+ AdminAction('update'),
939
+ __param(0, Param('slug')),
940
+ __param(1, Param('id')),
941
+ __param(2, Req()),
942
+ __metadata("design:type", Function),
943
+ __metadata("design:paramtypes", [String, String, Object]),
944
+ __metadata("design:returntype", Promise)
945
+ ], AdminController.prototype, "renderEdit", null);
946
+ __decorate([
947
+ Post(':slug/:id/edit'),
948
+ AdminAction('update'),
949
+ __param(0, Param('slug')),
950
+ __param(1, Param('id')),
951
+ __param(2, Body()),
952
+ __param(3, Res()),
953
+ __param(4, Req()),
954
+ __metadata("design:type", Function),
955
+ __metadata("design:paramtypes", [String, String, Object, Object, Object]),
956
+ __metadata("design:returntype", Promise)
957
+ ], AdminController.prototype, "submitEdit", null);
958
+ __decorate([
959
+ Post(':slug/:id/delete'),
960
+ AdminAction('delete'),
961
+ __param(0, Param('slug')),
962
+ __param(1, Param('id')),
963
+ __param(2, Res()),
964
+ __param(3, Req()),
965
+ __metadata("design:type", Function),
966
+ __metadata("design:paramtypes", [String, String, Object, Object]),
967
+ __metadata("design:returntype", Promise)
968
+ ], AdminController.prototype, "submitDelete", null);
969
+ __decorate([
970
+ Get(':slug/:id'),
971
+ __param(0, Param('slug')),
972
+ __param(1, Param('id')),
973
+ __param(2, Query()),
974
+ __param(3, Req()),
975
+ __metadata("design:type", Function),
976
+ __metadata("design:paramtypes", [String, String, Object, Object]),
977
+ __metadata("design:returntype", Promise)
978
+ ], AdminController.prototype, "renderView", null);
979
+ __decorate([
980
+ Get(':slug'),
981
+ __param(0, Param('slug')),
982
+ __param(1, Query()),
983
+ __param(2, Res()),
984
+ __param(3, Req()),
985
+ __metadata("design:type", Function),
986
+ __metadata("design:paramtypes", [String, Object, Object, Object]),
987
+ __metadata("design:returntype", Promise)
988
+ ], AdminController.prototype, "renderList", null);
989
+ AdminController = AdminController_1 = __decorate([
990
+ Controller(),
991
+ UseGuards(AdminAuthGuard, AdminPolicyGuard),
992
+ UseInterceptors(AdminAuditInterceptor),
993
+ __param(0, Inject(NESTJS_DASH_PANEL)),
994
+ __param(1, Optional()),
995
+ __param(1, Inject(NESTJS_DASH_STORAGE_SERVICE)),
996
+ __param(2, Optional()),
997
+ __param(2, Inject(NESTJS_DASH_MEDIA_SERVICE)),
998
+ __metadata("design:paramtypes", [Function, Object, Object, ModuleRef])
999
+ ], AdminController);
1000
+ export { AdminController };
1001
+ function parseListQuery(query) {
1002
+ const sortBy = query.sortBy
1003
+ ? query.sortBy.split(',').map((part) => {
1004
+ const [column, direction] = part.split(':');
1005
+ return [
1006
+ column ?? '',
1007
+ (direction?.toUpperCase() === 'ASC' ? 'ASC' : 'DESC'),
1008
+ ];
1009
+ })
1010
+ : undefined;
1011
+ const filter = {};
1012
+ for (const [key, value] of Object.entries(query)) {
1013
+ if (key.startsWith('filter.') && value) {
1014
+ filter[key.slice('filter.'.length)] = value;
1015
+ }
1016
+ }
1017
+ return {
1018
+ page: query.page ? Number(query.page) : undefined,
1019
+ limit: query.limit ? Number(query.limit) : undefined,
1020
+ search: query.search,
1021
+ searchBy: query.searchBy?.split(',').filter(Boolean),
1022
+ select: query.select?.split(',').filter(Boolean),
1023
+ sortBy,
1024
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
1025
+ withDeleted: query.withDeleted === '1' || query.withDeleted === 'true',
1026
+ };
1027
+ }
1028
+ function normalizeBody(body) {
1029
+ const result = { ...body };
1030
+ for (const [key, value] of Object.entries(result)) {
1031
+ if (value === 'true')
1032
+ result[key] = true;
1033
+ if (value === 'false')
1034
+ result[key] = false;
1035
+ }
1036
+ if (typeof result.age === 'string' && result.age !== '') {
1037
+ result.age = Number(result.age);
1038
+ }
1039
+ delete result.bulkAction;
1040
+ delete result.ids;
1041
+ delete result.relatedId;
1042
+ return result;
1043
+ }
1044
+ function pickPivot(body, fields) {
1045
+ if (!fields.length)
1046
+ return undefined;
1047
+ const pivot = {};
1048
+ for (const field of fields) {
1049
+ if (body[field] !== undefined) {
1050
+ pivot[field] = body[field] === 'true' ? true : body[field] === 'false' ? false : body[field];
1051
+ }
1052
+ }
1053
+ return Object.keys(pivot).length ? pivot : undefined;
1054
+ }
1055
+ function normalizeIds(ids) {
1056
+ if (ids == null)
1057
+ return [];
1058
+ if (Array.isArray(ids))
1059
+ return ids.map(String).filter(Boolean);
1060
+ return [String(ids)].filter(Boolean);
1061
+ }
1062
+ //# sourceMappingURL=admin.controller.js.map