webspresso 0.0.97 → 0.0.99
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.
- package/.agents/ADMIN.md +163 -5
- package/.agents/AGENTS.md +1 -1
- package/.agents/ARCHITECTURE.md +10 -0
- package/.agents/AUTH.md +169 -0
- package/.agents/CLI.md +195 -1
- package/.agents/ORM.md +88 -4
- package/.agents/PLUGINS.md +221 -9
- package/.agents/QUEUE.md +127 -0
- package/.agents/ROUTING.md +127 -7
- package/AGENTS.md +1 -0
- package/bin/commands/add-esbuild.js +80 -0
- package/bin/commands/add-tailwind.js +76 -31
- package/bin/commands/analytics-prune.js +57 -0
- package/bin/commands/api.js +207 -15
- package/bin/commands/build.js +99 -0
- package/bin/commands/dev.js +64 -35
- package/bin/commands/doctor.js +26 -0
- package/bin/commands/email-prune.js +47 -0
- package/bin/commands/make-job.js +86 -0
- package/bin/commands/make-model.js +250 -0
- package/bin/commands/new.js +45 -28
- package/bin/commands/openapi.js +102 -0
- package/bin/commands/page.js +6 -3
- package/bin/commands/queue-prune.js +59 -0
- package/bin/commands/routes.js +52 -0
- package/bin/commands/service.js +6 -3
- package/bin/commands/typegen.js +106 -0
- package/bin/commands/upload-prune.js +79 -0
- package/bin/webspresso.js +22 -0
- package/core/applySchema.js +13 -6
- package/core/auth/hash.js +21 -12
- package/core/auth/index.js +60 -0
- package/core/auth/manager.js +134 -0
- package/core/auth/middleware.js +88 -0
- package/core/auth/oauth.js +517 -0
- package/core/auth/qrcode.js +499 -0
- package/core/auth/totp.js +504 -0
- package/core/compression/middleware.js +23 -17
- package/core/openapi/build-from-api-routes.js +160 -29
- package/core/orm/cache/fingerprint.js +15 -1
- package/core/orm/cache/memory-provider.js +27 -2
- package/core/orm/eager-loader.js +144 -10
- package/core/orm/index.js +20 -7
- package/core/orm/model.js +53 -7
- package/core/orm/query-builder.js +90 -4
- package/core/orm/repository.js +44 -3
- package/core/orm/search.js +213 -0
- package/core/queue/adapters/database.js +94 -0
- package/core/queue/adapters/memory.js +62 -0
- package/core/queue/adapters/redis.js +80 -0
- package/core/queue/cron.js +331 -0
- package/core/queue/index.js +8 -1
- package/core/queue/manager.js +205 -1
- package/core/queue/scheduler.js +359 -0
- package/core/sse/index.js +19 -0
- package/core/sse/stream.js +430 -0
- package/core/ssr/stream.js +11 -0
- package/index.d.ts +1188 -10
- package/index.js +88 -96
- package/package.json +1 -1
- package/plugins/admin-panel/api.js +279 -17
- package/plugins/admin-panel/app.js +114 -22
- package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +314 -9
- package/plugins/admin-panel/client/parts/04-field-renderers.js +264 -67
- package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +333 -36
- package/plugins/admin-panel/client/parts/06-login-setup-forms.js +119 -28
- package/plugins/admin-panel/client/parts/07-model-list.js +49 -8
- package/plugins/admin-panel/client/parts/08-record-list.js +248 -23
- package/plugins/admin-panel/client/parts/09-record-form.js +59 -12
- package/plugins/admin-panel/client/parts/10-export-registry.js +23 -0
- package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
- package/plugins/admin-panel/core/admin-module.js +13 -7
- package/plugins/admin-panel/core/api-extensions.js +30 -2
- package/plugins/admin-panel/core/registry.js +96 -16
- package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
- package/plugins/admin-panel/field-renderers/index.js +2 -0
- package/plugins/admin-panel/index.js +96 -0
- package/plugins/admin-panel/lib/login-rate-limiter.js +119 -0
- package/plugins/admin-panel/modules/admin-users.js +16 -1
- package/plugins/admin-panel/modules/bulk-actions.js +13 -5
- package/plugins/admin-panel/modules/custom-pages.js +492 -3
- package/plugins/admin-panel/modules/dashboard.js +283 -0
- package/plugins/admin-panel/modules/menu.js +56 -18
- package/plugins/admin-panel/modules/user-management.js +24 -6
- package/plugins/api-key/crypto.js +101 -0
- package/plugins/api-key/index.js +288 -0
- package/plugins/api-key/middleware.js +461 -0
- package/plugins/api-key/migration-template.js +48 -0
- package/plugins/api-key/model.js +50 -0
- package/plugins/api-key/scopes.js +125 -0
- package/plugins/audit-log/admin-component.js +487 -59
- package/plugins/audit-log/api-handlers.js +148 -0
- package/plugins/audit-log/index.js +153 -3
- package/plugins/audit-log/purge.js +60 -10
- package/plugins/dashboard/app.js +152 -27
- package/plugins/dashboard/index.js +143 -39
- package/plugins/dashboard/styles.js +78 -0
- package/plugins/data-exchange/export-xlsx.js +9 -2
- package/plugins/data-exchange/import.js +9 -2
- package/plugins/data-exchange/index.js +6 -3
- package/plugins/email/index.js +132 -2
- package/plugins/email/purge.js +60 -10
- package/plugins/file-manager/admin-component.js +20 -4
- package/plugins/file-manager/index.js +21 -4
- package/plugins/index.js +10 -0
- package/plugins/oauth/index.js +338 -0
- package/plugins/orm-cache-admin/index.js +4 -2
- package/plugins/queue/index.js +15 -0
- package/plugins/schema-explorer/admin-component.js +714 -0
- package/plugins/schema-explorer/relations-graph.js +205 -0
- package/plugins/schema-explorer.js +248 -69
- package/plugins/site-analytics/admin-component.js +23 -0
- package/plugins/site-analytics/api-handlers.js +42 -0
- package/plugins/site-analytics/index.js +147 -2
- package/plugins/site-analytics/purge.js +168 -0
- package/plugins/swagger.js +261 -13
- package/plugins/upload/index.js +26 -0
- package/plugins/upload/orphan-pruner.js +292 -0
- package/src/api/api-loader.js +4 -1
- package/src/bundle/index.js +517 -0
- package/src/bundle/manifest.js +165 -0
- package/src/dev/banner.js +296 -0
- package/src/dev/colors.js +70 -0
- package/src/dev/index.js +62 -0
- package/src/dev/profiler.js +354 -0
- package/src/dev/typegen.js +497 -0
- package/src/dev-reload/index.js +611 -0
- package/src/discovery/file-route-parser.js +44 -1
- package/src/discovery/scan-routes.js +2 -2
- package/src/file-router.js +347 -162
- package/src/helpers.js +967 -98
- package/src/njk-frontmatter.js +12 -0
- package/src/pages/page-loader.js +61 -11
- package/src/routing/cascading.js +378 -0
- package/src/routing/middleware-resolver.js +92 -0
- package/src/server.js +635 -63
|
@@ -43,6 +43,8 @@ function adminPanelPlugin(options = {}) {
|
|
|
43
43
|
const {
|
|
44
44
|
path: adminPath = '/_admin',
|
|
45
45
|
enabled = true,
|
|
46
|
+
title,
|
|
47
|
+
titleTemplate,
|
|
46
48
|
sessionSecret,
|
|
47
49
|
db,
|
|
48
50
|
auth,
|
|
@@ -52,6 +54,7 @@ function adminPanelPlugin(options = {}) {
|
|
|
52
54
|
uploadUrl: uploadUrlOption,
|
|
53
55
|
richTextSanitize = true,
|
|
54
56
|
dataExchange: dataExchangeOption = true,
|
|
57
|
+
loginRateLimit,
|
|
55
58
|
} = options;
|
|
56
59
|
|
|
57
60
|
const adminUsersConfig = typeof adminUsersOption === 'object' ? adminUsersOption : { enabled: adminUsersOption !== false };
|
|
@@ -63,6 +66,12 @@ function adminPanelPlugin(options = {}) {
|
|
|
63
66
|
|
|
64
67
|
// Create fresh registry for this plugin instance
|
|
65
68
|
const registry = new AdminRegistry();
|
|
69
|
+
if (title) {
|
|
70
|
+
registry.setTitle(title);
|
|
71
|
+
}
|
|
72
|
+
if (titleTemplate) {
|
|
73
|
+
registry.setTitleTemplate(titleTemplate);
|
|
74
|
+
}
|
|
66
75
|
|
|
67
76
|
// Dependencies are now in main package
|
|
68
77
|
const session = require('express-session');
|
|
@@ -144,8 +153,40 @@ function adminPanelPlugin(options = {}) {
|
|
|
144
153
|
registerStyle(style) {
|
|
145
154
|
return registry.registerStyle(style);
|
|
146
155
|
},
|
|
156
|
+
registerHook(hookName, callback) {
|
|
157
|
+
return registry.registerHook(hookName, callback);
|
|
158
|
+
},
|
|
159
|
+
runHooks(hookName, context) {
|
|
160
|
+
return registry.runHooks(hookName, context);
|
|
161
|
+
},
|
|
162
|
+
registerAction(id, config) {
|
|
163
|
+
return registry.registerAction(id, config);
|
|
164
|
+
},
|
|
165
|
+
registerBulkAction(id, config) {
|
|
166
|
+
return registry.registerBulkAction(id, config);
|
|
167
|
+
},
|
|
168
|
+
setTitle(title) {
|
|
169
|
+
registry.setTitle(title);
|
|
170
|
+
return this;
|
|
171
|
+
},
|
|
172
|
+
getTitle() {
|
|
173
|
+
return registry.getTitle();
|
|
174
|
+
},
|
|
175
|
+
setTitleTemplate(template) {
|
|
176
|
+
registry.setTitleTemplate(template);
|
|
177
|
+
return this;
|
|
178
|
+
},
|
|
179
|
+
getTitleTemplate() {
|
|
180
|
+
return registry.getTitleTemplate();
|
|
181
|
+
},
|
|
182
|
+
resetLoginAttempts(ip) {
|
|
183
|
+
if (this._apiHandlers && typeof this._apiHandlers.resetLoginAttempts === 'function') {
|
|
184
|
+
return this._apiHandlers.resetLoginAttempts(ip);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
147
187
|
|
|
148
188
|
_ctx: null,
|
|
189
|
+
_apiHandlers: null,
|
|
149
190
|
},
|
|
150
191
|
|
|
151
192
|
/**
|
|
@@ -275,15 +316,68 @@ function adminPanelPlugin(options = {}) {
|
|
|
275
316
|
});
|
|
276
317
|
}
|
|
277
318
|
|
|
319
|
+
// Auto-register model-level declared actions and bulkActions
|
|
320
|
+
const { getAllModels: getFrameworkModels } = require('../../core/orm/model');
|
|
321
|
+
const modelMap = new Map();
|
|
322
|
+
if (getFrameworkModels) {
|
|
323
|
+
for (const [name, model] of getFrameworkModels()) {
|
|
324
|
+
modelMap.set(name, model);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (db.getAllModels) {
|
|
328
|
+
const dbModels = db.getAllModels();
|
|
329
|
+
const list = Array.isArray(dbModels) ? dbModels : Array.from(dbModels.values());
|
|
330
|
+
for (const model of list) {
|
|
331
|
+
modelMap.set(model.name, model);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const appModelList = Array.from(modelMap.values());
|
|
335
|
+
for (const model of appModelList) {
|
|
336
|
+
if (model.admin?.actions && Array.isArray(model.admin.actions)) {
|
|
337
|
+
for (const act of model.admin.actions) {
|
|
338
|
+
if (act.id && act.label && act.handler && !registry.actions.has(act.id)) {
|
|
339
|
+
registry.registerAction(act.id, {
|
|
340
|
+
models: [model.name],
|
|
341
|
+
...act,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (model.admin?.bulkActions && Array.isArray(model.admin.bulkActions)) {
|
|
347
|
+
for (const bAct of model.admin.bulkActions) {
|
|
348
|
+
if (bAct.id && bAct.label && bAct.handler && !registry.bulkActions.has(bAct.id)) {
|
|
349
|
+
registry.registerBulkAction(bAct.id, {
|
|
350
|
+
models: [model.name],
|
|
351
|
+
...bAct,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (model.admin?.hooks && typeof model.admin.hooks === 'object') {
|
|
357
|
+
for (const [hookName, fn] of Object.entries(model.admin.hooks)) {
|
|
358
|
+
if (typeof fn === 'function') {
|
|
359
|
+
registry.registerHook(hookName, async (ctx) => {
|
|
360
|
+
if (!ctx.model || ctx.model === model.name) {
|
|
361
|
+
await fn(ctx);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
278
369
|
// Create API handlers
|
|
279
370
|
const apiHandlers = createApiHandlers({
|
|
280
371
|
path: adminPath,
|
|
281
372
|
db,
|
|
373
|
+
registry,
|
|
282
374
|
AdminUser,
|
|
283
375
|
hashPassword: (password, rounds) => bcrypt.hash(password, process.env.NODE_ENV === 'test' ? 4 : (rounds || 10)),
|
|
284
376
|
comparePassword: (password, hash) => bcrypt.compare(password, hash),
|
|
285
377
|
richTextSanitize,
|
|
378
|
+
loginRateLimit,
|
|
286
379
|
});
|
|
380
|
+
this.api._apiHandlers = apiHandlers;
|
|
287
381
|
|
|
288
382
|
const extensionHandlers = createExtensionApiHandlers({
|
|
289
383
|
registry,
|
|
@@ -330,7 +424,9 @@ function adminPanelPlugin(options = {}) {
|
|
|
330
424
|
ctx.addRoute('get', `${adminPath}/api/extensions/widgets/:widgetId/data`, requireAuth, extensionHandlers.widgetDataHandler);
|
|
331
425
|
ctx.addRoute('get', `${adminPath}/api/extensions/dashboard/stats`, requireAuth, extensionHandlers.dashboardStatsHandler);
|
|
332
426
|
ctx.addRoute('post', `${adminPath}/api/extensions/actions/:actionId/:model/:id`, requireAuth, extensionHandlers.actionHandler);
|
|
427
|
+
ctx.addRoute('post', `${adminPath}/api/actions/:actionId/:model/:id`, requireAuth, extensionHandlers.actionHandler);
|
|
333
428
|
ctx.addRoute('post', `${adminPath}/api/extensions/bulk-actions/:actionId/:model`, requireAuth, extensionHandlers.bulkActionHandler);
|
|
429
|
+
ctx.addRoute('post', `${adminPath}/api/bulk-actions/:actionId/:model`, requireAuth, extensionHandlers.bulkActionHandler);
|
|
334
430
|
ctx.addRoute('get', `${adminPath}/api/extensions/bulk-fields/:model`, requireAuth, extensionHandlers.bulkFieldsHandler);
|
|
335
431
|
ctx.addRoute('post', `${adminPath}/api/extensions/bulk-update/:model`, requireAuth, extensionHandlers.bulkUpdateFieldHandler);
|
|
336
432
|
ctx.addRoute('get', `${adminPath}/api/extensions/export/:model`, requireAuth, extensionHandlers.exportHandler);
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* In-memory brute-force rate limiter for admin login.
|
|
5
|
+
* Zero external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
class LoginRateLimiter {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
if (options === false) {
|
|
10
|
+
this.enabled = false;
|
|
11
|
+
this.maxAttempts = 5;
|
|
12
|
+
this.windowMs = 15 * 60 * 1000;
|
|
13
|
+
} else {
|
|
14
|
+
const opts = typeof options === 'object' && options !== null ? options : {};
|
|
15
|
+
this.enabled = opts.enabled !== false;
|
|
16
|
+
this.maxAttempts = typeof opts.maxAttempts === 'number' && opts.maxAttempts > 0 ? opts.maxAttempts : 5;
|
|
17
|
+
this.windowMs = typeof opts.windowMs === 'number' && opts.windowMs > 0 ? opts.windowMs : (15 * 60 * 1000);
|
|
18
|
+
}
|
|
19
|
+
this.attempts = new Map();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getClientIp(req) {
|
|
23
|
+
if (!req) return '127.0.0.1';
|
|
24
|
+
if (req.ip) return String(req.ip);
|
|
25
|
+
if (req.headers && req.headers['x-forwarded-for']) {
|
|
26
|
+
const forwarded = String(req.headers['x-forwarded-for']).split(',')[0].trim();
|
|
27
|
+
if (forwarded) return forwarded;
|
|
28
|
+
}
|
|
29
|
+
if (req.connection && req.connection.remoteAddress) {
|
|
30
|
+
return String(req.connection.remoteAddress);
|
|
31
|
+
}
|
|
32
|
+
if (req.socket && req.socket.remoteAddress) {
|
|
33
|
+
return String(req.socket.remoteAddress);
|
|
34
|
+
}
|
|
35
|
+
return '127.0.0.1';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isBlocked(req) {
|
|
39
|
+
if (!this.enabled) return { blocked: false, remaining: Infinity, count: 0 };
|
|
40
|
+
const ip = this.getClientIp(req);
|
|
41
|
+
const now = Date.now();
|
|
42
|
+
const entry = this.attempts.get(ip);
|
|
43
|
+
if (!entry) return { blocked: false, remaining: this.maxAttempts, count: 0 };
|
|
44
|
+
|
|
45
|
+
if (now >= entry.resetAt) {
|
|
46
|
+
this.attempts.delete(ip);
|
|
47
|
+
return { blocked: false, remaining: this.maxAttempts, count: 0 };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (entry.count >= this.maxAttempts) {
|
|
51
|
+
const remainingMs = Math.max(0, entry.resetAt - now);
|
|
52
|
+
const waitMinutes = Math.max(1, Math.ceil(remainingMs / 60000));
|
|
53
|
+
const retryAfter = Math.max(1, Math.ceil(remainingMs / 1000));
|
|
54
|
+
return {
|
|
55
|
+
blocked: true,
|
|
56
|
+
remaining: 0,
|
|
57
|
+
count: entry.count,
|
|
58
|
+
waitMinutes,
|
|
59
|
+
retryAfter,
|
|
60
|
+
message: `Too many failed login attempts. Please try again in ${waitMinutes} ${waitMinutes === 1 ? 'minute' : 'minutes'}.`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
blocked: false,
|
|
66
|
+
remaining: Math.max(0, this.maxAttempts - entry.count),
|
|
67
|
+
count: entry.count,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
recordFailure(req) {
|
|
72
|
+
if (!this.enabled) return { blocked: false, remaining: Infinity, count: 0 };
|
|
73
|
+
const ip = this.getClientIp(req);
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
const entry = this.attempts.get(ip);
|
|
76
|
+
|
|
77
|
+
if (!entry || now >= entry.resetAt) {
|
|
78
|
+
this.attempts.set(ip, {
|
|
79
|
+
count: 1,
|
|
80
|
+
resetAt: now + this.windowMs,
|
|
81
|
+
});
|
|
82
|
+
} else {
|
|
83
|
+
entry.count += 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (this.attempts.size > 500) {
|
|
87
|
+
this.cleanup();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return this.isBlocked(req);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
recordSuccess(req) {
|
|
94
|
+
if (!this.enabled) return;
|
|
95
|
+
const ip = this.getClientIp(req);
|
|
96
|
+
this.attempts.delete(ip);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
reset(ip) {
|
|
100
|
+
if (ip) {
|
|
101
|
+
this.attempts.delete(ip);
|
|
102
|
+
} else {
|
|
103
|
+
this.attempts.clear();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cleanup() {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
for (const [ip, entry] of this.attempts.entries()) {
|
|
110
|
+
if (now >= entry.resetAt) {
|
|
111
|
+
this.attempts.delete(ip);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
LoginRateLimiter,
|
|
119
|
+
};
|
|
@@ -497,7 +497,16 @@ const AdminUsersPage = {
|
|
|
497
497
|
};
|
|
498
498
|
|
|
499
499
|
vnode.state.deleteAdmin = async function(admin) {
|
|
500
|
-
|
|
500
|
+
var confirmed = typeof confirmAction === 'function'
|
|
501
|
+
? await confirmAction({
|
|
502
|
+
title: 'Delete Administrator',
|
|
503
|
+
message: 'Are you sure you want to delete administrator "' + (admin.name || admin.email) + '"? This action cannot be undone.',
|
|
504
|
+
danger: true,
|
|
505
|
+
confirmText: 'Delete'
|
|
506
|
+
})
|
|
507
|
+
: confirm('Are you sure you want to delete administrator "' + (admin.name || admin.email) + '"? This action cannot be undone.');
|
|
508
|
+
|
|
509
|
+
if (!confirmed) {
|
|
501
510
|
return;
|
|
502
511
|
}
|
|
503
512
|
vnode.state.loading = true;
|
|
@@ -506,9 +515,15 @@ const AdminUsersPage = {
|
|
|
506
515
|
try {
|
|
507
516
|
await api.delete('/admins/' + encodeURIComponent(admin.id));
|
|
508
517
|
vnode.state.success = 'Administrator deleted successfully';
|
|
518
|
+
if (typeof notifySuccess === 'function') {
|
|
519
|
+
notifySuccess('Administrator deleted successfully');
|
|
520
|
+
}
|
|
509
521
|
await vnode.state.loadAdmins();
|
|
510
522
|
} catch (err) {
|
|
511
523
|
vnode.state.error = err.message || 'Failed to delete administrator';
|
|
524
|
+
if (typeof notifyError === 'function') {
|
|
525
|
+
notifyError(vnode.state.error);
|
|
526
|
+
}
|
|
512
527
|
vnode.state.loading = false;
|
|
513
528
|
m.redraw();
|
|
514
529
|
}
|
|
@@ -22,12 +22,15 @@ function registerDefaultBulkActions(options) {
|
|
|
22
22
|
confirm: true,
|
|
23
23
|
confirmMessage: 'Restore the selected records?',
|
|
24
24
|
handler: async (records, modelName, { db }) => {
|
|
25
|
+
const targetModel = typeof modelName === 'object' && modelName !== null
|
|
26
|
+
? (modelName.name || modelName.modelName || modelName.model || String(modelName))
|
|
27
|
+
: String(modelName);
|
|
25
28
|
const { getModel } = require('../../../core/orm/model');
|
|
26
|
-
const model = db.getModel ? db.getModel(
|
|
29
|
+
const model = db.getModel ? db.getModel(targetModel) : getModel(targetModel);
|
|
27
30
|
if (!model?.scopes?.softDelete) {
|
|
28
31
|
return { message: 'Model does not support restore', error: true };
|
|
29
32
|
}
|
|
30
|
-
const repo = db.getRepository(
|
|
33
|
+
const repo = db.getRepository(targetModel);
|
|
31
34
|
let restored = 0;
|
|
32
35
|
for (const record of records) {
|
|
33
36
|
try {
|
|
@@ -48,9 +51,11 @@ function registerDefaultBulkActions(options) {
|
|
|
48
51
|
color: 'red',
|
|
49
52
|
models: '*',
|
|
50
53
|
confirm: true,
|
|
51
|
-
confirmMessage: 'Are you sure you want to delete the selected records? This action cannot be undone.',
|
|
52
54
|
handler: async (records, model, { db }) => {
|
|
53
|
-
const
|
|
55
|
+
const targetModel = typeof model === 'object' && model !== null
|
|
56
|
+
? (model.name || model.modelName || model.model || String(model))
|
|
57
|
+
: String(model);
|
|
58
|
+
const repo = db.getRepository(targetModel);
|
|
54
59
|
let deleted = 0;
|
|
55
60
|
|
|
56
61
|
for (const record of records) {
|
|
@@ -131,7 +136,10 @@ function createFieldUpdateBulkAction(modelName, field, value, options = {}) {
|
|
|
131
136
|
confirm,
|
|
132
137
|
confirmMessage,
|
|
133
138
|
handler: async (records, model, { db }) => {
|
|
134
|
-
const
|
|
139
|
+
const targetModel = typeof model === 'object' && model !== null
|
|
140
|
+
? (model.name || model.modelName || model.model || String(model))
|
|
141
|
+
: String(model);
|
|
142
|
+
const repo = db.getRepository(targetModel);
|
|
135
143
|
let updated = 0;
|
|
136
144
|
|
|
137
145
|
for (const record of records) {
|