@intranefr/superbackend 1.5.3 → 1.6.3

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 (106) hide show
  1. package/cookies.txt +6 -0
  2. package/cookies1.txt +6 -0
  3. package/cookies2.txt +6 -0
  4. package/cookies3.txt +6 -0
  5. package/cookies4.txt +5 -0
  6. package/cookies_old.txt +5 -0
  7. package/cookies_old_test.txt +6 -0
  8. package/cookies_super.txt +5 -0
  9. package/cookies_super_test.txt +6 -0
  10. package/cookies_test.txt +6 -0
  11. package/index.js +7 -0
  12. package/package.json +3 -1
  13. package/plugins/core-waiting-list-migration/README.md +118 -0
  14. package/plugins/core-waiting-list-migration/index.js +438 -0
  15. package/plugins/global-settings-presets/index.js +20 -0
  16. package/plugins/hello-cli/index.js +17 -0
  17. package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
  18. package/plugins/ui-components-seeder/components/suiToast.js +186 -0
  19. package/plugins/ui-components-seeder/index.js +31 -0
  20. package/public/js/admin-ui-components-preview.js +281 -0
  21. package/public/js/admin-ui-components.js +408 -0
  22. package/public/js/llm-provider-model-picker.js +193 -0
  23. package/public/test-iframe-fix.html +63 -0
  24. package/public/test-iframe.html +14 -0
  25. package/src/admin/endpointRegistry.js +68 -0
  26. package/src/controllers/admin.controller.js +25 -5
  27. package/src/controllers/adminDataCleanup.controller.js +45 -0
  28. package/src/controllers/adminLlm.controller.js +0 -8
  29. package/src/controllers/adminLogin.controller.js +269 -0
  30. package/src/controllers/adminPlugins.controller.js +55 -0
  31. package/src/controllers/adminRegistry.controller.js +106 -0
  32. package/src/controllers/adminStats.controller.js +4 -4
  33. package/src/controllers/registry.controller.js +32 -0
  34. package/src/controllers/waitingList.controller.js +52 -74
  35. package/src/middleware/auth.js +71 -1
  36. package/src/middleware/rbac.js +62 -0
  37. package/src/middleware.js +454 -153
  38. package/src/models/GlobalSetting.js +11 -1
  39. package/src/models/UiComponent.js +2 -0
  40. package/src/models/User.js +1 -1
  41. package/src/routes/admin.routes.js +3 -3
  42. package/src/routes/adminAgents.routes.js +2 -2
  43. package/src/routes/adminAssets.routes.js +11 -11
  44. package/src/routes/adminBlog.routes.js +2 -2
  45. package/src/routes/adminBlogAi.routes.js +2 -2
  46. package/src/routes/adminBlogAutomation.routes.js +2 -2
  47. package/src/routes/adminCache.routes.js +2 -2
  48. package/src/routes/adminConsoleManager.routes.js +2 -2
  49. package/src/routes/adminCrons.routes.js +2 -2
  50. package/src/routes/adminDataCleanup.routes.js +26 -0
  51. package/src/routes/adminDbBrowser.routes.js +2 -2
  52. package/src/routes/adminEjsVirtual.routes.js +2 -2
  53. package/src/routes/adminFeatureFlags.routes.js +6 -6
  54. package/src/routes/adminHeadless.routes.js +2 -2
  55. package/src/routes/adminHealthChecks.routes.js +2 -2
  56. package/src/routes/adminI18n.routes.js +2 -2
  57. package/src/routes/adminJsonConfigs.routes.js +8 -8
  58. package/src/routes/adminLlm.routes.js +8 -8
  59. package/src/routes/adminLogin.routes.js +23 -0
  60. package/src/routes/adminMarkdowns.routes.js +3 -9
  61. package/src/routes/adminMigration.routes.js +12 -12
  62. package/src/routes/adminPages.routes.js +2 -2
  63. package/src/routes/adminPlugins.routes.js +15 -0
  64. package/src/routes/adminProxy.routes.js +2 -2
  65. package/src/routes/adminRateLimits.routes.js +8 -8
  66. package/src/routes/adminRbac.routes.js +2 -2
  67. package/src/routes/adminRegistry.routes.js +24 -0
  68. package/src/routes/adminScripts.routes.js +2 -2
  69. package/src/routes/adminSeoConfig.routes.js +10 -10
  70. package/src/routes/adminTelegram.routes.js +2 -2
  71. package/src/routes/adminTerminals.routes.js +2 -2
  72. package/src/routes/adminUiComponents.routes.js +2 -2
  73. package/src/routes/adminUploadNamespaces.routes.js +7 -7
  74. package/src/routes/blogInternal.routes.js +2 -2
  75. package/src/routes/experiments.routes.js +2 -2
  76. package/src/routes/formsAdmin.routes.js +6 -6
  77. package/src/routes/globalSettings.routes.js +8 -8
  78. package/src/routes/internalExperiments.routes.js +2 -2
  79. package/src/routes/notificationAdmin.routes.js +7 -7
  80. package/src/routes/orgAdmin.routes.js +16 -16
  81. package/src/routes/pages.routes.js +3 -3
  82. package/src/routes/registry.routes.js +11 -0
  83. package/src/routes/stripeAdmin.routes.js +12 -12
  84. package/src/routes/userAdmin.routes.js +7 -7
  85. package/src/routes/waitingListAdmin.routes.js +2 -2
  86. package/src/routes/workflows.routes.js +3 -3
  87. package/src/services/dataCleanup.service.js +286 -0
  88. package/src/services/jsonConfigs.service.js +262 -0
  89. package/src/services/plugins.service.js +348 -0
  90. package/src/services/registry.service.js +452 -0
  91. package/src/services/uiComponents.service.js +180 -0
  92. package/src/services/waitingListJson.service.js +401 -0
  93. package/src/utils/rbac/rightsRegistry.js +118 -0
  94. package/test-access.js +63 -0
  95. package/test-iframe-fix.html +63 -0
  96. package/test-iframe.html +14 -0
  97. package/views/admin-403.ejs +92 -0
  98. package/views/admin-dashboard-home.ejs +52 -2
  99. package/views/admin-dashboard.ejs +143 -2
  100. package/views/admin-data-cleanup.ejs +357 -0
  101. package/views/admin-login.ejs +286 -0
  102. package/views/admin-plugins-system.ejs +223 -0
  103. package/views/admin-ui-components.ejs +82 -402
  104. package/views/admin-users.ejs +207 -11
  105. package/views/partials/dashboard/nav-items.ejs +2 -0
  106. package/views/partials/llm-provider-model-picker.ejs +0 -161
package/cookies.txt ADDED
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771637659 superbackend.admin.session s%3AqRrB9xwjQm68DAgSOlyfgBfyq8BqnDCp.q1NiKyQlwZw2ZllDyHbP6v0%2F%2FMrE%2FU%2FlAa7K2vUleBE
6
+ localhost FALSE / FALSE 1803087259 saas_anon_id b1095bdfcae46d10f0428aa70ee83de2
package/cookies1.txt ADDED
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771629571 superbackend.admin.session s%3AsEcSn6xXkyeOqHm9mRm23J5Dc1nxuz1k.OqghsIsnLrw4SKxxIYx8lmmAjWt9IXCMxn%2FcYeAvUBc
6
+ localhost FALSE / FALSE 1803079171 saas_anon_id 51dc6dfc39d5ea7a7012b848631cae6a
package/cookies2.txt ADDED
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771637427 superbackend.admin.session s%3AvhBh0by7y7DrXprXCYSEi1UFlhT1fuR8.t1FufjMGUS%2F4PPxxQa%2B3En7QjEGB26clmZ3L6yf4%2F0o
6
+ localhost FALSE / FALSE 1803087027 saas_anon_id 711a36c4d41763f6964ae744e69164c3
package/cookies3.txt ADDED
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771629581 superbackend.admin.session s%3AiN0D-96QhVJhqcAUYtzx6qy01CgQ3Dgo.VyE%2F7D1wbr5FqK3UMQd1lda%2FC7Y8SQ9dRbwkxGMzuBM
6
+ localhost FALSE / FALSE 1803079181 saas_anon_id c8eeb3f7879f705c085ca27b89f7ff42
package/cookies4.txt ADDED
@@ -0,0 +1,5 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ localhost FALSE / FALSE 1803079186 saas_anon_id f679820563d5f343f793c6ed7d5b4f87
@@ -0,0 +1,5 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ localhost FALSE / FALSE 1803079090 saas_anon_id 0ad79ed83e8b6933dd2469b2af3065d8
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771629556 superbackend.admin.session s%3ABoRFJAcrePSTzSsN1EbrhiflhjfX1d69.H0fWiFrS1g9rqZARagoBd5csk387jkobU7e4KFIZd%2Fk
6
+ localhost FALSE / FALSE 1803079156 saas_anon_id cdadcc15da1d363683ebf9267b79f8a8
@@ -0,0 +1,5 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ localhost FALSE / FALSE 1803079106 saas_anon_id 93e0f60b466aca5cd9974d947c6bf78f
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771629526 superbackend.admin.session s%3ATUoGC9bTPVpHPYS6GtIdeJNMcLfcD98e.utwPy950VenpwybMXuLeVNxi2XZEv%2FiCs2LN1FZumGs
6
+ localhost FALSE / FALSE 1803079126 saas_anon_id 229f3817edba8d9274702f3b7fa6223c
@@ -0,0 +1,6 @@
1
+ # Netscape HTTP Cookie File
2
+ # https://curl.se/docs/http-cookies.html
3
+ # This file was generated by libcurl! Edit at your own risk.
4
+
5
+ #HttpOnly_localhost FALSE / FALSE 1771629501 superbackend.admin.session s%3AgzJQktAUd-6uZRBWJl2TgZL_iEKx9FME.UwOmJJS6XL9wuK7nQtMpVs6qSktGrgdgGVMZWW0eTmE
6
+ localhost FALSE / FALSE 1803079101 saas_anon_id 277a3d95195da9261595cb40995a4bf0
package/index.js CHANGED
@@ -76,7 +76,11 @@ const saasbackend = {
76
76
  workflow: require("./src/services/workflow.service"),
77
77
  healthChecks: require("./src/services/healthChecks.service"),
78
78
  dbBrowser: require("./src/services/dbBrowser.service"),
79
+ dataCleanup: require("./src/services/dataCleanup.service"),
79
80
  rateLimiter: require("./src/services/rateLimiter.service"),
81
+ registry: require("./src/services/registry.service"),
82
+ plugins: require("./src/services/plugins.service"),
83
+ uiComponents: require("./src/services/uiComponents.service"),
80
84
  },
81
85
  models: {
82
86
  ActionEvent: require("./src/models/ActionEvent"),
@@ -131,6 +135,9 @@ const saasbackend = {
131
135
  jsonConfigs: require("./src/services/jsonConfigs.service"),
132
136
  terminals: require("./src/services/terminalsWs.service"),
133
137
  rateLimiter: require("./src/services/rateLimiter.service"),
138
+ registry: require("./src/services/registry.service"),
139
+ dataCleanup: require("./src/services/dataCleanup.service"),
140
+ plugins: require("./src/services/plugins.service"),
134
141
  },
135
142
  };
136
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.5.3",
3
+ "version": "1.6.3",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,11 +35,13 @@
35
35
  "axios": "^1.13.2",
36
36
  "bcryptjs": "^2.4.3",
37
37
  "cheerio": "^1.0.0-rc.12",
38
+ "connect-mongo": "^5.1.0",
38
39
  "cors": "^2.8.5",
39
40
  "cron-parser": "3.5.0",
40
41
  "dotenv": "^16.3.1",
41
42
  "ejs": "^3.1.9",
42
43
  "express": "^4.18.2",
44
+ "express-session": "^1.17.3",
43
45
  "jsonwebtoken": "^9.0.2",
44
46
  "marked": "^4.3.0",
45
47
  "mongoose": "^8.0.0",
@@ -0,0 +1,118 @@
1
+ # Core Waiting List Migration Plugin
2
+
3
+ This core plugin creates migration scripts to transfer waiting list data from the legacy MongoDB collection to the new JSON Configs persistence system.
4
+
5
+ ## Installation
6
+
7
+ 1. Enable the plugin via the admin interface at `/admin/plugins-system`
8
+ 2. The plugin will automatically create two scripts:
9
+ - **Migration Script**: `waiting-list-migration-mongo-to-json`
10
+ - **Rollback Script**: `waiting-list-migration-rollback-json-to-mongo`
11
+
12
+ ## Usage
13
+
14
+ ### Migration Script
15
+
16
+ Access the migration script at `/admin/scripts`:
17
+
18
+ 1. Find "Waiting List Migration - MongoDB to JSON Configs" in the script list
19
+ 2. Configure environment variables as needed:
20
+ - `BATCH_SIZE`: Number of entries to process per batch (default: 100)
21
+ - `DRY_RUN`: Set to 'true' to preview migration without changes (default: false)
22
+ - `FORCE_MIGRATION`: Set to 'true' to bypass existing data checks (default: false)
23
+ 3. Click "Run" to start the migration
24
+ 4. Monitor the live output for progress and any errors
25
+
26
+ ### Rollback Script
27
+
28
+ If you need to restore data back to MongoDB:
29
+
30
+ 1. Find "Waiting List Migration Rollback - JSON Configs to MongoDB"
31
+ 2. Set `CONFIRM_ROLLBACK=true` to enable the rollback
32
+ 3. Run the script to restore data from JSON Configs to MongoDB
33
+
34
+ ## Features
35
+
36
+ ### Migration Script Features
37
+ - **Batch Processing**: Processes entries in configurable batches to avoid memory issues
38
+ - **Progress Tracking**: Shows real-time progress, processing rate, and estimated time remaining
39
+ - **Dry Run Mode**: Preview migration without making actual changes
40
+ - **Duplicate Detection**: Identifies and handles duplicate email addresses
41
+ - **Error Handling**: Continues processing even if individual entries fail
42
+ - **Data Validation**: Validates source and destination data before migration
43
+ - **Rollback Data**: Generates rollback data file for emergency recovery
44
+
45
+ ### Safety Features
46
+ - **Prerequisite Checks**: Verifies both MongoDB and JSON Configs systems are available
47
+ - **Data Validation**: Checks for existing data before migration
48
+ - **Backup Recommendations**: Advises on data backup before starting
49
+ - **Final Verification**: Compares source and destination counts after migration
50
+ - **Clear Logging**: Detailed output for troubleshooting
51
+
52
+ ## Data Mapping
53
+
54
+ The migration transforms MongoDB schema to JSON Configs format:
55
+
56
+ | MongoDB Field | JSON Configs Field | Notes |
57
+ |---------------|-------------------|-------|
58
+ | `_id` | `id` | Converts ObjectId to string or generates UUID |
59
+ | `email` | `email` | Preserves email address |
60
+ | `type` | `type` | Preserves type (buyer/seller/both) |
61
+ | `status` | `status` | Preserves status (active/subscribed/launched) |
62
+ | `referralSource` | `referralSource` | Preserves referral source |
63
+ | `createdAt` | `createdAt` | Preserves creation timestamp |
64
+ | `updatedAt` | `updatedAt` | Preserves update timestamp |
65
+
66
+ ## Post-Migration Steps
67
+
68
+ After successful migration:
69
+
70
+ 1. **Test Functionality**: Verify waiting list features work with new system
71
+ 2. **Data Integrity**: Check counts and random entries in admin interface
72
+ 3. **Grace Period**: Keep MongoDB collection for a reasonable period
73
+ 4. **Cleanup**: Once satisfied, consider archiving or removing the old collection
74
+
75
+ ## Troubleshooting
76
+
77
+ ### Common Issues
78
+
79
+ **Migration fails with "Destination not empty"**
80
+ - Use `FORCE_MIGRATION=true` if you want to overwrite existing data
81
+ - Or clear the JSON Configs data first
82
+
83
+ **Script times out**
84
+ - Increase timeout in script configuration
85
+ - Reduce `BATCH_SIZE` to process smaller batches
86
+
87
+ **Individual entries fail**
88
+ - Check the error logs for specific failure reasons
89
+ - Migration continues even if some entries fail
90
+ - Review and fix problematic entries manually
91
+
92
+ ### Performance Tips
93
+
94
+ - For large datasets (>10,000 entries), consider:
95
+ - Setting `BATCH_SIZE=50` to reduce memory usage
96
+ - Running during off-peak hours
97
+ - Monitoring server resources during migration
98
+
99
+ ## Plugin Structure
100
+
101
+ ```
102
+ plugins/core-waiting-list-migration/
103
+ ├── index.js # Main plugin file
104
+ └── README.md # This documentation
105
+ ```
106
+
107
+ The plugin follows the SuperBackend plugin system conventions:
108
+ - `meta`: Plugin metadata and description
109
+ - `hooks.install`: Creates migration scripts when plugin is enabled
110
+ - `hooks.bootstrap`: Verifies scripts exist on server startup
111
+
112
+ ## Support
113
+
114
+ For issues or questions:
115
+ 1. Check the script output logs for error details
116
+ 2. Verify both MongoDB and JSON Configs systems are operational
117
+ 3. Review plugin logs in server console
118
+ 4. Consider using the rollback script if needed
@@ -0,0 +1,438 @@
1
+ const crypto = require('crypto');
2
+
3
+ module.exports = {
4
+ meta: {
5
+ id: 'core-waiting-list-migration',
6
+ name: 'Core Waiting List Migration Plugin',
7
+ version: '1.0.0',
8
+ description: 'Creates migration script for waiting list data from MongoDB to JSON Configs',
9
+ tags: ['migration', 'waiting-list', 'json-configs', 'core']
10
+ },
11
+ hooks: {
12
+ async install(ctx) {
13
+ console.log('[core-waiting-list-migration] Installing migration script...');
14
+
15
+ const ScriptDefinition = ctx?.services?.mongoose?.models?.ScriptDefinition || null;
16
+ if (!ScriptDefinition) {
17
+ console.log('[core-waiting-list-migration] ScriptDefinition model not found, skipping script creation');
18
+ return;
19
+ }
20
+
21
+ try {
22
+ // Check if script already exists
23
+ const existingScript = await ScriptDefinition.findOne({
24
+ codeIdentifier: 'waiting-list-migration-mongo-to-json'
25
+ });
26
+
27
+ if (existingScript) {
28
+ console.log('[core-waiting-list-migration] Migration script already exists, skipping creation');
29
+ return;
30
+ }
31
+
32
+ // Create the migration script
33
+ const script = await ScriptDefinition.create({
34
+ name: 'Waiting List Migration - MongoDB to JSON Configs',
35
+ codeIdentifier: 'waiting-list-migration-mongo-to-json',
36
+ description: 'Migrates waiting list entries from MongoDB collection to JSON Configs system with batch processing, progress tracking, and rollback capability.',
37
+ type: 'node',
38
+ runner: 'host',
39
+ script: getMigrationScriptContent(),
40
+ defaultWorkingDirectory: process.cwd(),
41
+ env: [
42
+ { key: 'BATCH_SIZE', value: '100' },
43
+ { key: 'DRY_RUN', value: 'false' },
44
+ { key: 'FORCE_MIGRATION', value: 'false' }
45
+ ],
46
+ timeoutMs: 1800000, // 30 minutes
47
+ enabled: true
48
+ });
49
+
50
+ console.log('[core-waiting-list-migration] Successfully created migration script:', script._id);
51
+
52
+ // Also create rollback script
53
+ const rollbackScript = await ScriptDefinition.create({
54
+ name: 'Waiting List Migration Rollback - JSON Configs to MongoDB',
55
+ codeIdentifier: 'waiting-list-migration-rollback-json-to-mongo',
56
+ description: 'Rollback script to restore waiting list entries from JSON Configs back to MongoDB collection (generated after migration).',
57
+ type: 'node',
58
+ runner: 'host',
59
+ script: getRollbackScriptContent(),
60
+ defaultWorkingDirectory: process.cwd(),
61
+ env: [
62
+ { key: 'BATCH_SIZE', value: '100' },
63
+ { key: 'CONFIRM_ROLLBACK', value: 'false' }
64
+ ],
65
+ timeoutMs: 1800000, // 30 minutes
66
+ enabled: true
67
+ });
68
+
69
+ console.log('[core-waiting-list-migration] Successfully created rollback script:', rollbackScript._id);
70
+
71
+ } catch (error) {
72
+ console.error('[core-waiting-list-migration] Failed to create migration script:', error);
73
+ }
74
+ },
75
+
76
+ async bootstrap(ctx) {
77
+ console.log('[core-waiting-list-migration] Bootstrap - verifying migration scripts...');
78
+
79
+ const ScriptDefinition = ctx?.services?.mongoose?.models?.ScriptDefinition || null;
80
+ if (!ScriptDefinition) {
81
+ console.log('[core-waiting-list-migration] ScriptDefinition model not found');
82
+ return;
83
+ }
84
+
85
+ try {
86
+ const migrationScript = await ScriptDefinition.findOne({
87
+ codeIdentifier: 'waiting-list-migration-mongo-to-json'
88
+ });
89
+
90
+ if (!migrationScript) {
91
+ console.log('[core-waiting-list-migration] Migration script not found, please run install');
92
+ } else {
93
+ console.log('[core-waiting-list-migration] Migration script is ready');
94
+ }
95
+ } catch (error) {
96
+ console.error('[core-waiting-list-migration] Error verifying scripts:', error);
97
+ }
98
+ }
99
+ }
100
+ };
101
+
102
+ function getMigrationScriptContent() {
103
+ return `
104
+ // Waiting List Migration Script
105
+ // Migrates entries from MongoDB WaitingList collection to JSON Configs system
106
+
107
+ const mongoose = require('mongoose');
108
+ const { performance } = require('perf_hooks');
109
+
110
+ // Configuration
111
+ const BATCH_SIZE = parseInt(process.env.BATCH_SIZE || '100');
112
+ const DRY_RUN = process.env.DRY_RUN === 'true';
113
+ const FORCE_MIGRATION = process.env.FORCE_MIGRATION === 'true';
114
+
115
+ // Load models and services
116
+ const WaitingList = require('./src/models/WaitingList');
117
+ const waitingListService = require('./src/services/waitingListJson.service');
118
+
119
+ async function migrate() {
120
+ console.log('=== Waiting List Migration: MongoDB → JSON Configs ===');
121
+ console.log('Configuration:');
122
+ console.log(' - Batch size:', BATCH_SIZE);
123
+ console.log(' - Dry run:', DRY_RUN);
124
+ console.log(' - Force migration:', FORCE_MIGRATION);
125
+ console.log('');
126
+
127
+ try {
128
+ // Ensure MongoDB connection
129
+ if (mongoose.connection.readyState !== 1) {
130
+ throw new Error('MongoDB not connected');
131
+ }
132
+
133
+ // Check prerequisites
134
+ await checkPrerequisites();
135
+
136
+ // Get source data count
137
+ const totalCount = await WaitingList.countDocuments();
138
+ console.log('Found', totalCount, 'entries in MongoDB WaitingList collection');
139
+
140
+ if (totalCount === 0) {
141
+ console.log('No entries to migrate. Exiting.');
142
+ return;
143
+ }
144
+
145
+ // Check destination
146
+ const { entries: existingEntries } = await waitingListService.getWaitingListEntries();
147
+ if (existingEntries.length > 0 && !FORCE_MIGRATION) {
148
+ console.log('\\nWARNING: Found', existingEntries.length, 'entries already in JSON Configs!');
149
+ console.log('Use FORCE_MIGRATION=true to override or clear the JSON Configs first.');
150
+ throw new Error('Destination not empty');
151
+ }
152
+
153
+ if (DRY_RUN) {
154
+ console.log('\\n=== DRY RUN MODE - No changes will be made ===');
155
+ }
156
+
157
+ // Initialize counters
158
+ let processed = 0;
159
+ let success = 0;
160
+ let skipped = 0;
161
+ let errors = 0;
162
+ const startTime = performance.now();
163
+
164
+ // Process in batches
165
+ console.log('\\nStarting migration...');
166
+
167
+ for (let skip = 0; skip < totalCount; skip += BATCH_SIZE) {
168
+ const batch = await WaitingList.find({})
169
+ .sort({ createdAt: 1 })
170
+ .skip(skip)
171
+ .limit(BATCH_SIZE)
172
+ .lean();
173
+
174
+ console.log(\`\\nProcessing batch \${Math.floor(skip / BATCH_SIZE) + 1} (\${batch.length} entries)...\`);
175
+
176
+ for (const entry of batch) {
177
+ try {
178
+ // Transform data
179
+ const transformedEntry = transformEntry(entry);
180
+
181
+ // Check for duplicates in this batch
182
+ const isDuplicate = existingEntries.some(e =>
183
+ e.email.toLowerCase() === transformedEntry.email.toLowerCase()
184
+ );
185
+
186
+ if (isDuplicate && !FORCE_MIGRATION) {
187
+ console.log(\` ⚠️ Skipping duplicate: \${transformedEntry.email}\`);
188
+ skipped++;
189
+ continue;
190
+ }
191
+
192
+ if (!DRY_RUN) {
193
+ // Add to JSON Configs
194
+ await waitingListService.addWaitingListEntry(transformedEntry);
195
+ }
196
+
197
+ console.log(\` ✓ Migrated: \${transformedEntry.email}\`);
198
+ success++;
199
+ } catch (error) {
200
+ console.error(\` ✗ Failed to migrate \${entry.email}:\`, error.message);
201
+ errors++;
202
+ }
203
+ processed++;
204
+ }
205
+
206
+ // Show progress
207
+ const progress = ((processed / totalCount) * 100).toFixed(1);
208
+ const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
209
+ const rate = (processed / elapsed).toFixed(1);
210
+ const eta = processed > 0 ? ((totalCount - processed) / rate).toFixed(0) : 'N/A';
211
+
212
+ console.log(\` Progress: \${progress}% (\${processed}/\${totalCount}) | Rate: \${rate}/sec | ETA: \${eta}s\`);
213
+ }
214
+
215
+ // Final verification
216
+ console.log('\\n=== Migration Summary ===');
217
+ console.log('Total processed:', processed);
218
+ console.log('Successful:', success);
219
+ console.log('Skipped:', skipped);
220
+ console.log('Errors:', errors);
221
+ console.log('Elapsed time:', ((performance.now() - startTime) / 1000).toFixed(2), 'seconds');
222
+
223
+ if (!DRY_RUN) {
224
+ // Verify final count
225
+ const { entries: finalEntries } = await waitingListService.getWaitingListEntries();
226
+ console.log('\\nFinal verification:');
227
+ console.log(' Source count (MongoDB):', totalCount);
228
+ console.log(' Destination count (JSON Configs):', finalEntries.length);
229
+
230
+ if (finalEntries.length === totalCount - skipped) {
231
+ console.log(' ✅ Migration successful!');
232
+ } else {
233
+ console.log(' ⚠️ Count mismatch - please review');
234
+ }
235
+
236
+ // Generate rollback data
237
+ console.log('\\nRollback data saved to: ./waiting-list-rollback-data.json');
238
+ require('fs').writeFileSync(
239
+ './waiting-list-rollback-data.json',
240
+ JSON.stringify({ migrated: await WaitingList.find().lean(), timestamp: new Date().toISOString() }, null, 2)
241
+ );
242
+ }
243
+
244
+ console.log('\\nNext steps:');
245
+ if (DRY_RUN) {
246
+ console.log('1. Review the output above');
247
+ console.log('2. Run again with DRY_RUN=false to perform actual migration');
248
+ } else {
249
+ console.log('1. Test the waiting list functionality');
250
+ console.log('2. Verify data integrity in admin interface');
251
+ console.log('3. Keep the MongoDB collection for a grace period');
252
+ console.log('4. Use rollback script if needed: waiting-list-migration-rollback-json-to-mongo');
253
+ }
254
+
255
+ } catch (error) {
256
+ console.error('\\nMigration failed:', error);
257
+ process.exit(1);
258
+ }
259
+ }
260
+
261
+ function transformEntry(entry) {
262
+ return {
263
+ id: entry._id.toString() || generateId(),
264
+ email: entry.email,
265
+ type: entry.type || 'both',
266
+ status: entry.status || 'active',
267
+ referralSource: entry.referralSource || '',
268
+ createdAt: entry.createdAt || new Date(),
269
+ updatedAt: entry.updatedAt || new Date()
270
+ };
271
+ }
272
+
273
+ function generateId() {
274
+ return crypto.randomBytes(16).toString('hex');
275
+ }
276
+
277
+ async function checkPrerequisites() {
278
+ console.log('Checking prerequisites...');
279
+
280
+ // Check JSON Configs service
281
+ try {
282
+ await waitingListService.getWaitingListEntries();
283
+ console.log(' ✅ JSON Configs service available');
284
+ } catch (error) {
285
+ throw new Error('JSON Configs service not available: ' + error.message);
286
+ }
287
+
288
+ // Check MongoDB collection
289
+ try {
290
+ await WaitingList.findOne().limit(1);
291
+ console.log(' ✅ MongoDB WaitingList collection accessible');
292
+ } catch (error) {
293
+ throw new Error('MongoDB WaitingList collection not accessible: ' + error.message);
294
+ }
295
+ }
296
+
297
+ // Execute migration
298
+ migrate().catch(error => {
299
+ console.error('Fatal error:', error);
300
+ process.exit(1);
301
+ });
302
+ `;
303
+ }
304
+
305
+ function getRollbackScriptContent() {
306
+ return `
307
+ // Waiting List Migration Rollback Script
308
+ // Restores entries from JSON Configs back to MongoDB collection
309
+
310
+ const mongoose = require('mongoose');
311
+ const { performance } = require('perf_hooks');
312
+ const crypto = require('crypto');
313
+
314
+ // Configuration
315
+ const BATCH_SIZE = parseInt(process.env.BATCH_SIZE || '100');
316
+ const CONFIRM_ROLLBACK = process.env.CONFIRM_ROLLBACK === 'true';
317
+
318
+ // Load models and services
319
+ const WaitingList = require('./src/models/WaitingList');
320
+ const waitingListService = require('./src/services/waitingListJson.service');
321
+
322
+ async function rollback() {
323
+ console.log('=== Waiting List Rollback: JSON Configs → MongoDB ===');
324
+ console.log('Configuration:');
325
+ console.log(' - Batch size:', BATCH_SIZE);
326
+ console.log(' - Confirm rollback:', CONFIRM_ROLLBACK);
327
+ console.log('');
328
+
329
+ if (!CONFIRM_ROLLBACK) {
330
+ console.log('⚠️ DANGER: This will overwrite MongoDB data!');
331
+ console.log('Set CONFIRM_ROLLBACK=true to proceed.');
332
+ return;
333
+ }
334
+
335
+ try {
336
+ // Ensure MongoDB connection
337
+ if (mongoose.connection.readyState !== 1) {
338
+ throw new Error('MongoDB not connected');
339
+ }
340
+
341
+ // Get source data from JSON Configs
342
+ const { entries } = await waitingListService.getWaitingListEntries();
343
+ console.log('Found', entries.length, 'entries in JSON Configs');
344
+
345
+ if (entries.length === 0) {
346
+ console.log('No entries to rollback. Exiting.');
347
+ return;
348
+ }
349
+
350
+ // Clear existing MongoDB data (with confirmation)
351
+ const existingCount = await WaitingList.countDocuments();
352
+ if (existingCount > 0) {
353
+ console.log('\\nClearing existing MongoDB entries...');
354
+ await WaitingList.deleteMany({});
355
+ console.log('Deleted', existingCount, 'existing entries');
356
+ }
357
+
358
+ // Initialize counters
359
+ let processed = 0;
360
+ let success = 0;
361
+ let errors = 0;
362
+ const startTime = performance.now();
363
+
364
+ // Process in batches
365
+ console.log('\\nStarting rollback...');
366
+
367
+ for (let i = 0; i < entries.length; i += BATCH_SIZE) {
368
+ const batch = entries.slice(i, i + BATCH_SIZE);
369
+ console.log(\`\\nProcessing batch \${Math.floor(i / BATCH_SIZE) + 1} (\${batch.length} entries)...\`);
370
+
371
+ for (const entry of batch) {
372
+ try {
373
+ // Transform data for MongoDB
374
+ const mongoEntry = {
375
+ _id: entry.id || generateObjectId(),
376
+ email: entry.email,
377
+ type: entry.type || 'both',
378
+ status: entry.status || 'active',
379
+ referralSource: entry.referralSource || '',
380
+ createdAt: new Date(entry.createdAt),
381
+ updatedAt: new Date(entry.updatedAt)
382
+ };
383
+
384
+ // Insert into MongoDB
385
+ await WaitingList.create(mongoEntry);
386
+ console.log(\` ✓ Restored: \${mongoEntry.email}\`);
387
+ success++;
388
+ } catch (error) {
389
+ console.error(\` ✗ Failed to restore \${entry.email}:\`, error.message);
390
+ errors++;
391
+ }
392
+ processed++;
393
+ }
394
+
395
+ // Show progress
396
+ const progress = ((processed / entries.length) * 100).toFixed(1);
397
+ const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
398
+ const rate = (processed / elapsed).toFixed(1);
399
+
400
+ console.log(\` Progress: \${progress}% (\${processed}/\${entries.length}) | Rate: \${rate}/sec\`);
401
+ }
402
+
403
+ // Final verification
404
+ console.log('\\n=== Rollback Summary ===');
405
+ console.log('Total processed:', processed);
406
+ console.log('Successful:', success);
407
+ console.log('Errors:', errors);
408
+ console.log('Elapsed time:', ((performance.now() - startTime) / 1000).toFixed(2), 'seconds');
409
+
410
+ // Verify final count
411
+ const finalCount = await WaitingList.countDocuments();
412
+ console.log('\\nFinal verification:');
413
+ console.log(' Source count (JSON Configs):', entries.length);
414
+ console.log(' Destination count (MongoDB):', finalCount);
415
+
416
+ if (finalCount === success) {
417
+ console.log(' ✅ Rollback successful!');
418
+ } else {
419
+ console.log(' ⚠️ Count mismatch - please review');
420
+ }
421
+
422
+ } catch (error) {
423
+ console.error('\\nRollback failed:', error);
424
+ process.exit(1);
425
+ }
426
+ }
427
+
428
+ function generateObjectId() {
429
+ return new mongoose.Types.ObjectId();
430
+ }
431
+
432
+ // Execute rollback
433
+ rollback().catch(error => {
434
+ console.error('Fatal error:', error);
435
+ process.exit(1);
436
+ });
437
+ `;
438
+ }