@intranefr/superbackend 1.5.3 → 1.6.4
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/cookies.txt +6 -0
- package/cookies1.txt +6 -0
- package/cookies2.txt +6 -0
- package/cookies3.txt +6 -0
- package/cookies4.txt +5 -0
- package/cookies_old.txt +5 -0
- package/cookies_old_test.txt +6 -0
- package/cookies_super.txt +5 -0
- package/cookies_super_test.txt +6 -0
- package/cookies_test.txt +6 -0
- package/index.js +7 -0
- package/package.json +3 -1
- package/plugins/core-waiting-list-migration/README.md +118 -0
- package/plugins/core-waiting-list-migration/index.js +438 -0
- package/plugins/global-settings-presets/index.js +20 -0
- package/plugins/hello-cli/index.js +17 -0
- package/plugins/ui-components-seeder/components/suiAlert.js +212 -0
- package/plugins/ui-components-seeder/components/suiToast.js +186 -0
- package/plugins/ui-components-seeder/index.js +31 -0
- package/public/js/admin-ui-components-preview.js +281 -0
- package/public/js/admin-ui-components.js +408 -0
- package/public/js/llm-provider-model-picker.js +193 -0
- package/public/test-iframe-fix.html +63 -0
- package/public/test-iframe.html +14 -0
- package/src/admin/endpointRegistry.js +68 -0
- package/src/controllers/admin.controller.js +25 -5
- package/src/controllers/adminDataCleanup.controller.js +45 -0
- package/src/controllers/adminLlm.controller.js +0 -8
- package/src/controllers/adminLogin.controller.js +269 -0
- package/src/controllers/adminPlugins.controller.js +55 -0
- package/src/controllers/adminRegistry.controller.js +106 -0
- package/src/controllers/adminStats.controller.js +4 -4
- package/src/controllers/registry.controller.js +32 -0
- package/src/controllers/waitingList.controller.js +52 -74
- package/src/middleware/auth.js +71 -1
- package/src/middleware/rbac.js +62 -0
- package/src/middleware.js +480 -156
- package/src/models/GlobalSetting.js +11 -1
- package/src/models/UiComponent.js +2 -0
- package/src/models/User.js +1 -1
- package/src/routes/admin.routes.js +3 -3
- package/src/routes/adminAgents.routes.js +2 -2
- package/src/routes/adminAssets.routes.js +11 -11
- package/src/routes/adminBlog.routes.js +2 -2
- package/src/routes/adminBlogAi.routes.js +2 -2
- package/src/routes/adminBlogAutomation.routes.js +2 -2
- package/src/routes/adminCache.routes.js +2 -2
- package/src/routes/adminConsoleManager.routes.js +2 -2
- package/src/routes/adminCrons.routes.js +2 -2
- package/src/routes/adminDataCleanup.routes.js +26 -0
- package/src/routes/adminDbBrowser.routes.js +2 -2
- package/src/routes/adminEjsVirtual.routes.js +2 -2
- package/src/routes/adminFeatureFlags.routes.js +6 -6
- package/src/routes/adminHeadless.routes.js +2 -2
- package/src/routes/adminHealthChecks.routes.js +2 -2
- package/src/routes/adminI18n.routes.js +2 -2
- package/src/routes/adminJsonConfigs.routes.js +8 -8
- package/src/routes/adminLlm.routes.js +8 -8
- package/src/routes/adminLogin.routes.js +23 -0
- package/src/routes/adminMarkdowns.routes.js +3 -9
- package/src/routes/adminMigration.routes.js +12 -12
- package/src/routes/adminPages.routes.js +2 -2
- package/src/routes/adminPlugins.routes.js +15 -0
- package/src/routes/adminProxy.routes.js +2 -2
- package/src/routes/adminRateLimits.routes.js +8 -8
- package/src/routes/adminRbac.routes.js +2 -2
- package/src/routes/adminRegistry.routes.js +24 -0
- package/src/routes/adminScripts.routes.js +2 -2
- package/src/routes/adminSeoConfig.routes.js +10 -10
- package/src/routes/adminTelegram.routes.js +2 -2
- package/src/routes/adminTerminals.routes.js +2 -2
- package/src/routes/adminUiComponents.routes.js +2 -2
- package/src/routes/adminUploadNamespaces.routes.js +7 -7
- package/src/routes/blogInternal.routes.js +2 -2
- package/src/routes/experiments.routes.js +2 -2
- package/src/routes/formsAdmin.routes.js +6 -6
- package/src/routes/globalSettings.routes.js +8 -8
- package/src/routes/internalExperiments.routes.js +2 -2
- package/src/routes/notificationAdmin.routes.js +7 -7
- package/src/routes/orgAdmin.routes.js +16 -16
- package/src/routes/pages.routes.js +3 -3
- package/src/routes/registry.routes.js +11 -0
- package/src/routes/stripeAdmin.routes.js +12 -12
- package/src/routes/userAdmin.routes.js +7 -7
- package/src/routes/waitingListAdmin.routes.js +2 -2
- package/src/routes/workflows.routes.js +3 -3
- package/src/services/dataCleanup.service.js +286 -0
- package/src/services/jsonConfigs.service.js +262 -0
- package/src/services/plugins.service.js +348 -0
- package/src/services/registry.service.js +452 -0
- package/src/services/uiComponents.service.js +180 -0
- package/src/services/waitingListJson.service.js +401 -0
- package/src/utils/rbac/rightsRegistry.js +118 -0
- package/test-access.js +63 -0
- package/test-iframe-fix.html +63 -0
- package/test-iframe.html +14 -0
- package/views/admin-403.ejs +92 -0
- package/views/admin-dashboard-home.ejs +52 -2
- package/views/admin-dashboard.ejs +143 -2
- package/views/admin-data-cleanup.ejs +357 -0
- package/views/admin-login.ejs +286 -0
- package/views/admin-plugins-system.ejs +223 -0
- package/views/admin-ui-components.ejs +82 -402
- package/views/admin-users.ejs +207 -11
- package/views/partials/dashboard/nav-items.ejs +2 -0
- 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
package/cookies_old.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 1771629556 superbackend.admin.session s%3ABoRFJAcrePSTzSsN1EbrhiflhjfX1d69.H0fWiFrS1g9rqZARagoBd5csk387jkobU7e4KFIZd%2Fk
|
|
6
|
+
localhost FALSE / FALSE 1803079156 saas_anon_id cdadcc15da1d363683ebf9267b79f8a8
|
|
@@ -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
|
package/cookies_test.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 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.
|
|
3
|
+
"version": "1.6.4",
|
|
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
|
+
}
|