webspresso 0.0.89 → 0.0.91

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 (102) hide show
  1. package/README.md +450 -53
  2. package/bin/commands/service.js +193 -0
  3. package/bin/webspresso.js +2 -0
  4. package/core/auth/manager.js +36 -26
  5. package/core/auth/middleware.js +21 -2
  6. package/core/compression/compressible.js +51 -0
  7. package/core/compression/compression-stream.js +59 -0
  8. package/core/compression/index.js +25 -0
  9. package/core/compression/middleware.js +301 -0
  10. package/core/compression/negotiate.js +153 -0
  11. package/core/errors/base.js +31 -0
  12. package/core/errors/domain.js +193 -0
  13. package/core/errors/http.js +133 -0
  14. package/core/errors/index.js +77 -0
  15. package/core/errors/middleware.js +162 -0
  16. package/core/errors/normalize.js +171 -0
  17. package/core/orm/complexity.js +120 -0
  18. package/core/orm/events.js +64 -14
  19. package/core/orm/index.js +45 -10
  20. package/core/orm/model.js +10 -2
  21. package/core/orm/query-builder.js +30 -4
  22. package/core/orm/repository.js +57 -25
  23. package/core/orm/scopes.js +2 -1
  24. package/core/orm/transaction.js +39 -5
  25. package/core/orm/utils.js +6 -3
  26. package/core/queue/adapters/database.js +284 -0
  27. package/core/queue/adapters/memory.js +155 -0
  28. package/core/queue/adapters/redis.js +336 -0
  29. package/core/queue/index.js +21 -0
  30. package/core/queue/job.js +103 -0
  31. package/core/queue/manager.js +387 -0
  32. package/core/realtime/adapters/redis.js +431 -0
  33. package/core/realtime/adapters/socket-io.js +203 -0
  34. package/core/realtime/adapters/sse.js +210 -0
  35. package/core/realtime/adapters/websocket.js +337 -0
  36. package/core/realtime/auth-manager.js +140 -0
  37. package/core/realtime/core.js +359 -0
  38. package/core/realtime/emitter.js +98 -0
  39. package/core/realtime/errors.js +71 -0
  40. package/core/realtime/index.js +79 -0
  41. package/core/realtime/reconnect-manager.js +118 -0
  42. package/core/realtime/registry.js +243 -0
  43. package/core/shutdown/http-adapter.js +134 -0
  44. package/core/shutdown/index.js +12 -0
  45. package/core/shutdown/shutdown-manager.js +312 -0
  46. package/core/ssr/index.js +13 -0
  47. package/core/ssr/stream.js +174 -0
  48. package/index.d.ts +829 -52
  49. package/index.js +103 -0
  50. package/package.json +22 -2
  51. package/plugins/admin-panel/api.js +37 -4
  52. package/plugins/admin-panel/app.js +67 -17
  53. package/plugins/admin-panel/client/parts/01-state-api-breadcrumb.js +4 -1
  54. package/plugins/admin-panel/client/parts/03-pagination-intro.js +25 -18
  55. package/plugins/admin-panel/client/parts/04-field-renderers.js +102 -58
  56. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +37 -23
  57. package/plugins/admin-panel/client/parts/08-record-list.js +27 -34
  58. package/plugins/admin-panel/client/parts/09-record-form.js +168 -19
  59. package/plugins/admin-panel/core/admin-module.js +2 -2
  60. package/plugins/admin-panel/core/api-extensions.js +149 -0
  61. package/plugins/admin-panel/field-renderers/rich-text.js +3 -2
  62. package/plugins/admin-panel/index.js +131 -18
  63. package/plugins/admin-panel/modules/admin-users.js +770 -0
  64. package/plugins/admin-panel/modules/custom-pages.js +144 -45
  65. package/plugins/admin-panel/modules/menu.js +85 -0
  66. package/plugins/admin-panel/modules/user-management.js +5 -2
  67. package/plugins/admin-panel/vendor/mithril.min.js +1 -0
  68. package/plugins/admin-panel/vendor/quill.min.js +7 -0
  69. package/plugins/admin-panel/vendor/quill.snow.min.css +945 -0
  70. package/plugins/admin-panel/vendor/tailwind.min.js +65 -0
  71. package/plugins/basic-auth/index.js +293 -0
  72. package/plugins/content/client/inline-edit.js +41 -17
  73. package/plugins/cors.js +12 -8
  74. package/plugins/csrf/index.js +8 -8
  75. package/plugins/data-exchange/export-xlsx.js +5 -1
  76. package/plugins/data-exchange/import.js +16 -2
  77. package/plugins/data-exchange/index.js +14 -0
  78. package/plugins/email/index.js +14 -1
  79. package/plugins/index.js +29 -0
  80. package/plugins/queue/index.js +108 -0
  81. package/plugins/realtime/index.js +150 -0
  82. package/plugins/recaptcha/index.js +11 -0
  83. package/plugins/redirect/index.js +9 -1
  84. package/plugins/rest-resources/index.js +280 -11
  85. package/plugins/upload/index.js +16 -0
  86. package/src/app-context.js +46 -6
  87. package/src/file-router.js +220 -28
  88. package/src/helpers.js +24 -5
  89. package/src/plugin-manager.js +134 -7
  90. package/src/server.js +470 -44
  91. package/src/services/builtins/auth.js +273 -0
  92. package/src/services/builtins/exchange.js +229 -0
  93. package/src/services/builtins/mail.js +134 -0
  94. package/src/services/builtins/media.js +196 -0
  95. package/src/services/builtins/system.js +221 -0
  96. package/src/services/discovery.js +140 -0
  97. package/src/services/executor.js +292 -0
  98. package/src/services/index.js +70 -0
  99. package/src/services/memoize.js +256 -0
  100. package/src/services/registry.js +288 -0
  101. package/src/services/validator.js +281 -0
  102. package/templates/skills/webspresso-usage/REFERENCE-framework.md +2 -2
@@ -4,6 +4,8 @@
4
4
  * @module plugins/admin-panel
5
5
  */
6
6
 
7
+ const path = require('path');
8
+ const fs = require('fs');
7
9
  const { createAdminUserModel } = require('./admin-user-model');
8
10
  const { generateAdminUsersMigration } = require('./migration-template');
9
11
  const { createApiHandlers } = require('./api');
@@ -16,6 +18,7 @@ const { registerDefaultBulkActions, generateBulkActionsComponent } = require('./
16
18
  const { registerDefaultPages, createCustomPageApiHandlers, generateCustomPageComponent } = require('./modules/custom-pages');
17
19
  const { registerModelMenuItems, registerSystemMenuItems, generateMenuComponent } = require('./modules/menu');
18
20
  const { generateProfileComponent } = require('./modules/profile');
21
+ const { registerAdminUsersManagement, createAdminUsersApiHandlers, generateAdminUsersComponent } = require('./modules/admin-users');
19
22
  const { registerModule, registerPageDir } = require('./core/admin-module');
20
23
 
21
24
 
@@ -44,11 +47,14 @@ function adminPanelPlugin(options = {}) {
44
47
  db,
45
48
  auth,
46
49
  userManagement: userMgmtConfig,
50
+ adminUsers: adminUsersOption = true,
47
51
  configure,
48
52
  uploadUrl: uploadUrlOption,
49
53
  richTextSanitize = true,
50
54
  } = options;
51
55
 
56
+ const adminUsersConfig = typeof adminUsersOption === 'object' ? adminUsersOption : { enabled: adminUsersOption !== false };
57
+
52
58
  // Validate required options
53
59
  if (!db) {
54
60
  throw new Error('Admin panel plugin requires a database instance. Pass `db` in options.');
@@ -71,22 +77,26 @@ function adminPanelPlugin(options = {}) {
71
77
  version: '2.0.0',
72
78
  description: 'Modular admin panel for Webspresso with extensions support',
73
79
 
74
- // CSP requirements for admin panel scripts, styles, and custom page iframe sources
75
- // Note: cdn.quilljs.com 301-redirects to cdn.jsdelivr.net; CSP is enforced on the final URL.
80
+ // CSP requirements for admin panel scripts, styles, and custom page iframe sources (Offline Zero-CDN)
76
81
  csp: {
77
82
  styleSrc: [
83
+ "'self'",
84
+ "'unsafe-inline'",
78
85
  'https://cdn.quilljs.com',
79
86
  'https://cdn.jsdelivr.net',
80
87
  'https://unpkg.com',
81
88
  'https://cdn.tailwindcss.com',
82
89
  ],
83
90
  scriptSrc: [
91
+ "'self'",
92
+ "'unsafe-inline'",
93
+ "'unsafe-eval'",
84
94
  'https://cdn.quilljs.com',
85
95
  'https://cdn.jsdelivr.net',
86
96
  'https://unpkg.com',
87
97
  'https://cdn.tailwindcss.com',
88
98
  ],
89
- connectSrc: ['https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
99
+ connectSrc: ["'self'", 'https://unpkg.com', 'https://cdn.tailwindcss.com', 'https://cdn.jsdelivr.net'],
90
100
  frameSrc: ["'self'", 'data:', 'blob:', 'https:'],
91
101
  },
92
102
  enabled,
@@ -152,13 +162,29 @@ function adminPanelPlugin(options = {}) {
152
162
  // Session must be registered before file routes so SSR pages can read adminUser.
153
163
  if (enabled && ctx.app && !ctx.app._webspressoSessionInitialized) {
154
164
  const secret = sessionSecret || process.env.SESSION_SECRET || 'webspresso-admin-secret-change-in-production';
165
+ const secureMode =
166
+ process.env.NODE_ENV === 'production' ||
167
+ process.env.COOKIE_SECURE === 'true' ||
168
+ /^https:/i.test(String(process.env.BASE_URL || '').trim())
169
+ ? true
170
+ : 'auto';
171
+ if (process.env.NODE_ENV === 'production') {
172
+ const warnMsg = '[webspresso:admin] Warning: Admin panel is using default MemoryStore in production. Please configure a persistent session store to prevent memory leaks.';
173
+ if (typeof process !== 'undefined' && typeof process.emitWarning === 'function') {
174
+ process.emitWarning(warnMsg, 'SecurityWarning');
175
+ } else {
176
+ console.warn(warnMsg);
177
+ }
178
+ }
179
+
155
180
  ctx.app.use(session({
156
181
  secret,
157
182
  resave: false,
158
183
  saveUninitialized: false,
159
184
  cookie: {
160
- secure: process.env.NODE_ENV === 'production',
185
+ secure: secureMode,
161
186
  httpOnly: true,
187
+ sameSite: 'lax',
162
188
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
163
189
  },
164
190
  }));
@@ -178,22 +204,24 @@ function adminPanelPlugin(options = {}) {
178
204
  const { app } = ctx;
179
205
 
180
206
  const uploadUrlResolved =
181
- uploadUrlOption || app.get('webspresso.uploadPath') || null;
207
+ uploadUrlOption || (app && typeof app.get === 'function' ? app.get('webspresso.uploadPath') : null);
182
208
  if (uploadUrlResolved) {
183
209
  registry.configure({ uploadUrl: uploadUrlResolved });
184
210
  }
185
211
 
186
212
  // Check if admin_users table exists (migration run)
187
- db.knex.schema.hasTable('admin_users').then((hasAdminTable) => {
188
- if (!hasAdminTable) {
189
- console.warn(
190
- '\n⚠️ [admin-panel] admin_users table not found. Run: webspresso admin:setup\n' +
191
- ' Or create the table manually via migration. See: AdminUser model / getMigrationTemplate()\n'
192
- );
193
- }
194
- }).catch((err) => {
195
- console.warn('[admin-panel] Could not check admin_users table:', err.message);
196
- });
213
+ if (db?.knex?.schema?.hasTable) {
214
+ db.knex.schema.hasTable('admin_users').then((hasAdminTable) => {
215
+ if (!hasAdminTable) {
216
+ console.warn(
217
+ '\n⚠️ [admin-panel] admin_users table not found. Run: webspresso admin:setup\n' +
218
+ ' Or create the table manually via migration. See: AdminUser model / getMigrationTemplate()\n'
219
+ );
220
+ }
221
+ }).catch((err) => {
222
+ console.warn('[admin-panel] Could not check admin_users table:', err.message);
223
+ });
224
+ }
197
225
 
198
226
  // Create and register AdminUser model
199
227
  const { hasModel: hasGlobalModel, getModel: getGlobalModel } = require('../../core/orm/model');
@@ -227,6 +255,14 @@ function adminPanelPlugin(options = {}) {
227
255
  });
228
256
  }
229
257
 
258
+ // Register admin user management if enabled
259
+ if (adminUsersConfig.enabled !== false) {
260
+ registerAdminUsersManagement({
261
+ registry,
262
+ config: adminUsersConfig,
263
+ });
264
+ }
265
+
230
266
  // Create API handlers
231
267
  const apiHandlers = createApiHandlers({
232
268
  path: adminPath,
@@ -245,6 +281,16 @@ function adminPanelPlugin(options = {}) {
245
281
 
246
282
  const pageHandlers = createCustomPageApiHandlers({ registry, db });
247
283
 
284
+ // Admin users management API handlers
285
+ let adminUsersHandlers = null;
286
+ if (adminUsersConfig.enabled !== false) {
287
+ adminUsersHandlers = createAdminUsersApiHandlers({
288
+ db,
289
+ AdminUser,
290
+ hashPassword: (password, rounds) => bcrypt.hash(password, rounds),
291
+ });
292
+ }
293
+
248
294
  // User management API handlers
249
295
  let userHandlers = null;
250
296
  if (userMgmtConfig?.enabled) {
@@ -284,11 +330,22 @@ function adminPanelPlugin(options = {}) {
284
330
  // Settings API routes
285
331
  ctx.addRoute('get', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsGetHandler);
286
332
  ctx.addRoute('post', `${adminPath}/api/extensions/settings`, requireAuth, extensionHandlers.settingsUpdateHandler);
333
+ ctx.addRoute('get', `${adminPath}/api/extensions/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
334
+ ctx.addRoute('get', `${adminPath}/api/system-info`, requireAuth, extensionHandlers.systemInfoHandler);
287
335
 
288
336
  // Custom pages API routes
289
337
  ctx.addRoute('get', `${adminPath}/api/extensions/pages/:pageId/data`, requireAuth, pageHandlers.getPageData);
290
338
  ctx.addRoute('all', `${adminPath}/api/extensions/pages/:pageId/actions/:actionId`, requireAuth, pageHandlers.executePageAction);
291
339
 
340
+ // Admin users management API routes
341
+ if (adminUsersHandlers) {
342
+ ctx.addRoute('get', `${adminPath}/api/admins`, requireAuth, adminUsersHandlers.listAdmins);
343
+ ctx.addRoute('get', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.getAdmin);
344
+ ctx.addRoute('post', `${adminPath}/api/admins`, requireAuth, adminUsersHandlers.createAdmin);
345
+ ctx.addRoute('put', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.updateAdmin);
346
+ ctx.addRoute('delete', `${adminPath}/api/admins/:id`, requireAuth, adminUsersHandlers.deleteAdmin);
347
+ }
348
+
292
349
  // User management API routes
293
350
  if (userHandlers) {
294
351
  ctx.addRoute('get', `${adminPath}/api/users`, requireAuth, userHandlers.listUsers);
@@ -322,6 +379,30 @@ function adminPanelPlugin(options = {}) {
322
379
  // Query API routes (auth required)
323
380
  ctx.addRoute('get', `${adminPath}/api/models/:model/queries/:query`, requireAuth, apiHandlers.queryHandler);
324
381
 
382
+ // ==========================================
383
+ // Static Vendor Assets (Offline Zero-CDN)
384
+ // ==========================================
385
+ const vendorDir = path.join(__dirname, 'vendor');
386
+ ctx.addRoute('get', `${adminPath}/vendor/:file`, (req, res) => {
387
+ const fileName = path.basename(req.params.file);
388
+ const filePath = path.join(vendorDir, fileName);
389
+
390
+ if (!fs.existsSync(filePath)) {
391
+ return res.status(404).send('Vendor asset not found');
392
+ }
393
+
394
+ if (fileName.endsWith('.js')) {
395
+ res.type('application/javascript');
396
+ } else if (fileName.endsWith('.css')) {
397
+ res.type('text/css');
398
+ } else {
399
+ res.type('application/octet-stream');
400
+ }
401
+
402
+ res.setHeader('Cache-Control', 'public, max-age=86400');
403
+ res.sendFile(filePath);
404
+ });
405
+
325
406
  // ==========================================
326
407
  // HTML Endpoints
327
408
  // ==========================================
@@ -391,6 +472,7 @@ function generateAdminPanelHtml(adminPath, registry) {
391
472
  const bulkActionsComponent = generateBulkActionsComponent();
392
473
  const customPageComponent = generateCustomPageComponent();
393
474
  const profileComponent = generateProfileComponent();
475
+ const adminUsersComponent = generateAdminUsersComponent();
394
476
 
395
477
  const settings = registry.settings;
396
478
  const fieldRenderersCode = Array.from(registry.fieldRenderers.entries()).map(([type, r]) => {
@@ -426,9 +508,9 @@ function generateAdminPanelHtml(adminPath, registry) {
426
508
  });
427
509
  } catch (e) {}
428
510
  })();
429
- </script>
430
- <script src="https://unpkg.com/mithril/mithril.js"></script>
431
- <script src="https://cdn.tailwindcss.com"></script>
511
+ <script>window.__ADMIN_PATH__ = ${JSON.stringify(adminPath)};</script>
512
+ <script src="${adminPath}/vendor/mithril.min.js"></script>
513
+ <script src="${adminPath}/vendor/tailwind.min.js"></script>
432
514
  <script>tailwind.config = { darkMode: 'class' };</script>
433
515
  <style>
434
516
  body { margin: 0; font-family: system-ui, -apple-system, sans-serif; }
@@ -445,6 +527,35 @@ function generateAdminPanelHtml(adminPath, registry) {
445
527
  .dark .ql-snow .ql-stroke { stroke: #94a3b8; }
446
528
  .dark .ql-snow .ql-fill { fill: #94a3b8; }
447
529
  .dark .ql-picker { color: #cbd5e1; }
530
+ @media (min-width: 768px) {
531
+ .admin-table-sticky-check {
532
+ position: sticky;
533
+ left: 0;
534
+ z-index: 5;
535
+ box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
536
+ }
537
+ th.admin-table-sticky-check {
538
+ z-index: 15;
539
+ }
540
+ .admin-table-sticky-first {
541
+ position: sticky;
542
+ left: 40px;
543
+ z-index: 5;
544
+ box-shadow: 4px 0 8px -4px rgba(0, 0, 0, 0.08);
545
+ }
546
+ th.admin-table-sticky-first {
547
+ z-index: 15;
548
+ }
549
+ .admin-table-sticky-actions {
550
+ position: sticky;
551
+ right: 0;
552
+ z-index: 5;
553
+ box-shadow: -4px 0 8px -4px rgba(0, 0, 0, 0.08);
554
+ }
555
+ th.admin-table-sticky-actions {
556
+ z-index: 15;
557
+ }
558
+ }
448
559
  </style>
449
560
  ${registry.getStylesHtml()}
450
561
  ${registry.getScriptsHtml('head')}
@@ -473,6 +584,7 @@ function generateAdminPanelHtml(adminPath, registry) {
473
584
  ${bulkActionsComponent}
474
585
  ${customPageComponent}
475
586
  ${profileComponent}
587
+ ${adminUsersComponent}
476
588
 
477
589
  // Custom page components container
478
590
  window.__customPages = window.__customPages || {};
@@ -490,5 +602,6 @@ function generateAdminPanelHtml(adminPath, registry) {
490
602
 
491
603
  // Export registry and utilities for external use
492
604
  module.exports = adminPanelPlugin;
605
+ module.exports.adminPanelPlugin = adminPanelPlugin;
493
606
  module.exports.AdminRegistry = AdminRegistry;
494
607
  module.exports.defaultRegistry = defaultRegistry;