vasuzex 2.1.2 → 2.1.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/.ai-memory/LOGGER_STRICT_POLICY.md +201 -0
- package/.ai-memory/neastore-feature-mapping.md +1114 -0
- package/bin/create-vasuzex.js +5 -2
- package/examples/runtime-config-examples.js +309 -0
- package/framework/Config/DatabaseConfigService.js +348 -0
- package/framework/Config/DatabaseConfigServiceProvider.js +69 -0
- package/framework/Console/Commands/generate-app.js +97 -4
- package/framework/Console/Commands/generate-media-server.js +2 -1
- package/framework/Console/Commands/utils/mediaServerTemplates.js +3 -2
- package/framework/Console/Commands/utils/webStructure.js +30 -21
- package/framework/Console/config/generator.config.js +3 -3
- package/framework/Console/plopfile.js +0 -8
- package/framework/Console/templates/api/app.js.hbs +5 -4
- package/framework/Console/templates/api/server.js.hbs +8 -2
- package/framework/Database/DatabaseServiceProvider.js +1 -1
- package/framework/Database/Model.js +9 -0
- package/framework/Exceptions/index.js +2 -1
- package/framework/Foundation/BaseApp.js +19 -0
- package/framework/Foundation/BaseService.js +95 -0
- package/framework/Foundation/Container.js +18 -3
- package/framework/Foundation/Providers/index.js +0 -1
- package/framework/Foundation/ServiceProvider.js +42 -0
- package/framework/Http/asyncHandler.js +26 -0
- package/framework/Http/index.js +1 -0
- package/framework/Services/Log/LogManager.js +26 -5
- package/framework/Services/Log/LogServiceProvider.js +48 -0
- package/framework/Services/Log/index.js +1 -0
- package/framework/Services/Mail/MailServiceProvider.js +36 -0
- package/framework/Services/Mail/index.js +1 -0
- package/framework/Services/Media/MediaServiceProvider.js +2 -2
- package/framework/Services/Payment/PaymentServiceProvider.js +35 -0
- package/framework/Services/Payment/index.js +1 -0
- package/framework/Services/Security/SecurityService.js +253 -0
- package/framework/Services/Security/SecurityServiceProvider.js +33 -0
- package/framework/Services/Security/index.js +9 -0
- package/framework/Services/Storage/StorageManager.js +7 -1
- package/framework/Services/Storage/StorageServiceProvider.js +36 -0
- package/framework/Services/Storage/index.js +1 -0
- package/framework/Services/Upload/UploadManager.js +179 -0
- package/framework/Services/index.js +1 -0
- package/framework/Support/Facades/Security.js +14 -0
- package/framework/Support/Facades/index.js +1 -0
- package/framework/Support/Helpers/index.js +1 -0
- package/framework/Support/Helpers/utilities.js +348 -0
- package/framework/index.js +2 -0
- package/frontend/client/Config/ConfigLoader.js +52 -10
- package/frontend/client/Http/ApiHelpers.js +99 -0
- package/frontend/client/Http/index.js +1 -0
- package/frontend/client/index.js +1 -1
- package/frontend/client/package.json +14 -66
- package/frontend/client/package.json.backup +41 -0
- package/frontend/react-ui/components/Avatars/GradientAvatar.jsx +255 -0
- package/frontend/react-ui/components/Avatars/index.js +66 -0
- package/frontend/react-ui/components/BreadCrumb/BreadCrumb.jsx +69 -0
- package/frontend/react-ui/components/BreadCrumb/index.js +2 -0
- package/frontend/react-ui/components/DataTable/ActionDefaults.jsx +171 -0
- package/frontend/react-ui/components/DataTable/DataTable.jsx +202 -328
- package/frontend/react-ui/components/DataTable/Filters.jsx +69 -56
- package/frontend/react-ui/components/DataTable/Pagination.jsx +59 -140
- package/frontend/react-ui/components/DataTable/TableActions.jsx +11 -20
- package/frontend/react-ui/components/DataTable/TableBody.jsx +168 -168
- package/frontend/react-ui/components/DataTable/TableHeader.jsx +93 -96
- package/frontend/react-ui/components/DataTable/TableState.jsx +33 -0
- package/frontend/react-ui/components/DataTable/index.js +10 -8
- package/frontend/react-ui/components/ImageLightbox/ImageLightbox.jsx +118 -0
- package/frontend/react-ui/components/ImageLightbox/index.js +1 -0
- package/frontend/react-ui/components/OrderTimeline/OrderTimeline.jsx +269 -0
- package/frontend/react-ui/components/OrderTimeline/index.js +1 -0
- package/frontend/react-ui/components/ReadMore/ReadMore.jsx +34 -0
- package/frontend/react-ui/components/ReadMore/index.js +1 -0
- package/frontend/react-ui/components/Switch/Switch.jsx +34 -0
- package/frontend/react-ui/components/Switch/index.js +1 -0
- package/frontend/react-ui/hooks/useAppConfig.js +58 -4
- package/frontend/react-ui/hooks/useLocalStorage.js +1 -1
- package/frontend/react-ui/hooks/useValidationErrors.js +1 -1
- package/frontend/react-ui/index.js +10 -0
- package/frontend/react-ui/package.json +17 -108
- package/frontend/react-ui/providers/ApiClientProvider.jsx +1 -1
- package/frontend/react-ui/providers/AppConfigProvider.jsx +212 -20
- package/frontend/react-ui/utils/formatters.js +193 -0
- package/frontend/react-ui/utils/index.js +30 -0
- package/frontend/react-ui/utils/logger.js +62 -0
- package/frontend/react-ui/utils/storage.js +90 -0
- package/frontend/react-ui/utils/swal.js +134 -0
- package/frontend/react-ui/utils/validation.js +207 -0
- package/package.json +6 -2
- package/framework/Foundation/Providers/LogServiceProvider.js +0 -33
package/bin/create-vasuzex.js
CHANGED
|
@@ -368,8 +368,6 @@ async function generateEnvFile(projectName, answers, targetDir, spinner) {
|
|
|
368
368
|
let envContent = `# Application
|
|
369
369
|
APP_NAME=${projectName}
|
|
370
370
|
APP_ENV=development
|
|
371
|
-
APP_PORT=3000
|
|
372
|
-
APP_URL=http://localhost:3000
|
|
373
371
|
|
|
374
372
|
# Database
|
|
375
373
|
DB_CONNECTION=${answers.database.toLowerCase() === 'postgresql' ? 'postgresql' : answers.database.toLowerCase()}
|
|
@@ -406,6 +404,11 @@ CACHE_DRIVER=memory
|
|
|
406
404
|
# Session
|
|
407
405
|
SESSION_DRIVER=memory
|
|
408
406
|
|
|
407
|
+
# CORS (will be overridden by app-specific .env files)
|
|
408
|
+
CORS_ORIGIN=*
|
|
409
|
+
CORS_METHODS=GET,HEAD,PUT,PATCH,POST,DELETE
|
|
410
|
+
CORS_CREDENTIALS=true
|
|
411
|
+
|
|
409
412
|
# Logging
|
|
410
413
|
LOG_LEVEL=debug
|
|
411
414
|
LOG_FILE=storage/logs/app.log
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Config Examples (Laravel-Style)
|
|
3
|
+
* On-the-fly config get/set in Vasuzex-v2
|
|
4
|
+
*
|
|
5
|
+
* EXACTLY like Laravel's Config facade!
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Application } from '../framework/Foundation/Application.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Example 1: Basic Get/Set
|
|
12
|
+
*/
|
|
13
|
+
async function example1_basicGetSet() {
|
|
14
|
+
console.log('\n=== Example 1: Basic Get/Set ===\n');
|
|
15
|
+
|
|
16
|
+
const app = new Application(process.cwd());
|
|
17
|
+
await app.bootstrap();
|
|
18
|
+
|
|
19
|
+
// Laravel: Config::get('app.name')
|
|
20
|
+
console.log('Original app name:', app.config('app.name'));
|
|
21
|
+
|
|
22
|
+
// Laravel: Config::set('app.name', 'New Name')
|
|
23
|
+
const config = app.make('config');
|
|
24
|
+
config.set('app.name', 'Neastore Runtime Update!');
|
|
25
|
+
|
|
26
|
+
console.log('Updated app name:', app.config('app.name'));
|
|
27
|
+
|
|
28
|
+
// Set with dot notation
|
|
29
|
+
config.set('app.custom_setting', 'My Custom Value');
|
|
30
|
+
console.log('Custom setting:', app.config('app.custom_setting'));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Example 2: Nested Config Updates
|
|
35
|
+
*/
|
|
36
|
+
async function example2_nestedConfig() {
|
|
37
|
+
console.log('\n=== Example 2: Nested Config Updates ===\n');
|
|
38
|
+
|
|
39
|
+
const app = new Application(process.cwd());
|
|
40
|
+
await app.bootstrap();
|
|
41
|
+
const config = app.make('config');
|
|
42
|
+
|
|
43
|
+
// Original database config
|
|
44
|
+
console.log('Original DB host:', app.config('database.connections.postgresql.host'));
|
|
45
|
+
|
|
46
|
+
// Update nested value
|
|
47
|
+
config.set('database.connections.postgresql.host', 'db.example.com');
|
|
48
|
+
config.set('database.connections.postgresql.port', 5433);
|
|
49
|
+
|
|
50
|
+
console.log('Updated DB host:', app.config('database.connections.postgresql.host'));
|
|
51
|
+
console.log('Updated DB port:', app.config('database.connections.postgresql.port'));
|
|
52
|
+
|
|
53
|
+
// Add new connection on-the-fly
|
|
54
|
+
config.set('database.connections.mongodb', {
|
|
55
|
+
driver: 'mongodb',
|
|
56
|
+
host: 'localhost',
|
|
57
|
+
port: 27017,
|
|
58
|
+
database: 'neastore'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log('New MongoDB config:', app.config('database.connections.mongodb'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Example 3: Bulk Updates
|
|
66
|
+
*/
|
|
67
|
+
async function example3_bulkUpdates() {
|
|
68
|
+
console.log('\n=== Example 3: Bulk Config Updates ===\n');
|
|
69
|
+
|
|
70
|
+
const app = new Application(process.cwd());
|
|
71
|
+
await app.bootstrap();
|
|
72
|
+
const config = app.make('config');
|
|
73
|
+
|
|
74
|
+
// Laravel: Config::set(['key1' => 'val1', 'key2' => 'val2'])
|
|
75
|
+
config.set({
|
|
76
|
+
'app.debug': false,
|
|
77
|
+
'app.timezone': 'Asia/Kolkata',
|
|
78
|
+
'app.locale': 'hi',
|
|
79
|
+
'cache.default': 'redis'
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log('Debug mode:', app.config('app.debug'));
|
|
83
|
+
console.log('Timezone:', app.config('app.timezone'));
|
|
84
|
+
console.log('Locale:', app.config('app.locale'));
|
|
85
|
+
console.log('Cache driver:', app.config('cache.default'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Example 4: Array Operations
|
|
90
|
+
*/
|
|
91
|
+
async function example4_arrayOperations() {
|
|
92
|
+
console.log('\n=== Example 4: Array Operations ===\n');
|
|
93
|
+
|
|
94
|
+
const app = new Application(process.cwd());
|
|
95
|
+
await app.bootstrap();
|
|
96
|
+
const config = app.make('config');
|
|
97
|
+
|
|
98
|
+
// Create array config
|
|
99
|
+
config.set('app.allowed_hosts', ['localhost', '127.0.0.1']);
|
|
100
|
+
console.log('Initial hosts:', app.config('app.allowed_hosts'));
|
|
101
|
+
|
|
102
|
+
// Push new value (Laravel: Config::push())
|
|
103
|
+
config.push('app.allowed_hosts', 'example.com');
|
|
104
|
+
console.log('After push:', app.config('app.allowed_hosts'));
|
|
105
|
+
|
|
106
|
+
// Prepend value (Laravel: Config::prepend())
|
|
107
|
+
config.prepend('app.allowed_hosts', 'app.local');
|
|
108
|
+
console.log('After prepend:', app.config('app.allowed_hosts'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Example 5: Runtime Feature Flags
|
|
113
|
+
*/
|
|
114
|
+
async function example5_featureFlags() {
|
|
115
|
+
console.log('\n=== Example 5: Runtime Feature Flags ===\n');
|
|
116
|
+
|
|
117
|
+
const app = new Application(process.cwd());
|
|
118
|
+
await app.bootstrap();
|
|
119
|
+
const config = app.make('config');
|
|
120
|
+
|
|
121
|
+
// Enable/disable features at runtime
|
|
122
|
+
config.set('features.new_checkout', true);
|
|
123
|
+
config.set('features.beta_ui', false);
|
|
124
|
+
config.set('features.payment_gateway_v2', true);
|
|
125
|
+
|
|
126
|
+
// Use in code
|
|
127
|
+
if (app.config('features.new_checkout')) {
|
|
128
|
+
console.log('✅ New checkout enabled');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!app.config('features.beta_ui')) {
|
|
132
|
+
console.log('❌ Beta UI disabled');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log('All features:', app.config('features'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Example 6: API/Controller Usage
|
|
140
|
+
*/
|
|
141
|
+
async function example6_controllerUsage() {
|
|
142
|
+
console.log('\n=== Example 6: Controller Usage ===\n');
|
|
143
|
+
|
|
144
|
+
const app = new Application(process.cwd());
|
|
145
|
+
await app.bootstrap();
|
|
146
|
+
|
|
147
|
+
// Simulate controller/service accessing config
|
|
148
|
+
class UserService {
|
|
149
|
+
constructor(app) {
|
|
150
|
+
this.app = app;
|
|
151
|
+
this.config = app.make('config');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
getMaxLoginAttempts() {
|
|
155
|
+
// Get with default
|
|
156
|
+
return this.app.config('auth.max_login_attempts', 5);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
setMaxLoginAttempts(attempts) {
|
|
160
|
+
this.config.set('auth.max_login_attempts', attempts);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
enableTwoFactor() {
|
|
164
|
+
this.config.set('auth.two_factor_enabled', true);
|
|
165
|
+
console.log('✅ Two-factor authentication enabled');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const userService = new UserService(app);
|
|
170
|
+
|
|
171
|
+
console.log('Max login attempts:', userService.getMaxLoginAttempts());
|
|
172
|
+
|
|
173
|
+
userService.setMaxLoginAttempts(3);
|
|
174
|
+
console.log('Updated max attempts:', userService.getMaxLoginAttempts());
|
|
175
|
+
|
|
176
|
+
userService.enableTwoFactor();
|
|
177
|
+
console.log('2FA enabled:', app.config('auth.two_factor_enabled'));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Example 7: Check if Config Exists
|
|
182
|
+
*/
|
|
183
|
+
async function example7_checkExists() {
|
|
184
|
+
console.log('\n=== Example 7: Check Config Exists ===\n');
|
|
185
|
+
|
|
186
|
+
const app = new Application(process.cwd());
|
|
187
|
+
await app.bootstrap();
|
|
188
|
+
const config = app.make('config');
|
|
189
|
+
|
|
190
|
+
// Laravel: Config::has('key')
|
|
191
|
+
console.log('Has app.name?', config.has('app.name'));
|
|
192
|
+
console.log('Has app.nonexistent?', config.has('app.nonexistent'));
|
|
193
|
+
|
|
194
|
+
// Set and check
|
|
195
|
+
config.set('temp.test', 'value');
|
|
196
|
+
console.log('Has temp.test?', config.has('temp.test'));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Example 8: Get All Config
|
|
201
|
+
*/
|
|
202
|
+
async function example8_getAllConfig() {
|
|
203
|
+
console.log('\n=== Example 8: Get All Config ===\n');
|
|
204
|
+
|
|
205
|
+
const app = new Application(process.cwd());
|
|
206
|
+
await app.bootstrap();
|
|
207
|
+
const config = app.make('config');
|
|
208
|
+
|
|
209
|
+
// Laravel: Config::all()
|
|
210
|
+
const allConfig = config.all();
|
|
211
|
+
console.log('All config keys:', Object.keys(allConfig));
|
|
212
|
+
|
|
213
|
+
// Get multiple values
|
|
214
|
+
const values = config.get(['app.name', 'app.env', 'app.debug']);
|
|
215
|
+
console.log('Multiple values:', values);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Example 9: Merge Config
|
|
220
|
+
*/
|
|
221
|
+
async function example9_mergeConfig() {
|
|
222
|
+
console.log('\n=== Example 9: Merge Config ===\n');
|
|
223
|
+
|
|
224
|
+
const app = new Application(process.cwd());
|
|
225
|
+
await app.bootstrap();
|
|
226
|
+
const config = app.make('config');
|
|
227
|
+
|
|
228
|
+
// Merge new config section
|
|
229
|
+
config.merge({
|
|
230
|
+
payment: {
|
|
231
|
+
gateway: 'razorpay',
|
|
232
|
+
key: 'rzp_test_xxx',
|
|
233
|
+
webhook_secret: 'whsec_xxx'
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
console.log('Payment config:', app.config('payment'));
|
|
238
|
+
console.log('Payment gateway:', app.config('payment.gateway'));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Example 10: Real-World - Dynamic CORS Update
|
|
243
|
+
*/
|
|
244
|
+
async function example10_dynamicCORS() {
|
|
245
|
+
console.log('\n=== Example 10: Dynamic CORS Update ===\n');
|
|
246
|
+
|
|
247
|
+
const app = new Application(process.cwd());
|
|
248
|
+
await app.bootstrap();
|
|
249
|
+
const config = app.make('config');
|
|
250
|
+
|
|
251
|
+
// Original CORS origin
|
|
252
|
+
console.log('Original CORS:', app.config('cors.origin'));
|
|
253
|
+
|
|
254
|
+
// Add new allowed origin at runtime
|
|
255
|
+
const currentOrigins = app.config('cors.origin', []);
|
|
256
|
+
const newOrigins = Array.isArray(currentOrigins)
|
|
257
|
+
? currentOrigins
|
|
258
|
+
: [currentOrigins];
|
|
259
|
+
|
|
260
|
+
newOrigins.push('https://admin.example.com');
|
|
261
|
+
config.set('cors.origin', newOrigins);
|
|
262
|
+
|
|
263
|
+
console.log('Updated CORS origins:', app.config('cors.origin'));
|
|
264
|
+
|
|
265
|
+
// Update methods
|
|
266
|
+
config.set('cors.methods', 'GET,POST,PUT,DELETE,OPTIONS,PATCH');
|
|
267
|
+
console.log('Updated methods:', app.config('cors.methods'));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Run all examples
|
|
272
|
+
*/
|
|
273
|
+
async function runAllExamples() {
|
|
274
|
+
try {
|
|
275
|
+
await example1_basicGetSet();
|
|
276
|
+
await example2_nestedConfig();
|
|
277
|
+
await example3_bulkUpdates();
|
|
278
|
+
await example4_arrayOperations();
|
|
279
|
+
await example5_featureFlags();
|
|
280
|
+
await example6_controllerUsage();
|
|
281
|
+
await example7_checkExists();
|
|
282
|
+
await example8_getAllConfig();
|
|
283
|
+
await example9_mergeConfig();
|
|
284
|
+
await example10_dynamicCORS();
|
|
285
|
+
|
|
286
|
+
console.log('\n✅ All examples completed successfully!\n');
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('❌ Error:', error.message);
|
|
289
|
+
console.error(error.stack);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Run if executed directly
|
|
294
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
295
|
+
runAllExamples();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export {
|
|
299
|
+
example1_basicGetSet,
|
|
300
|
+
example2_nestedConfig,
|
|
301
|
+
example3_bulkUpdates,
|
|
302
|
+
example4_arrayOperations,
|
|
303
|
+
example5_featureFlags,
|
|
304
|
+
example6_controllerUsage,
|
|
305
|
+
example7_checkExists,
|
|
306
|
+
example8_getAllConfig,
|
|
307
|
+
example9_mergeConfig,
|
|
308
|
+
example10_dynamicCORS
|
|
309
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatabaseConfigService
|
|
3
|
+
* Loads runtime configurations from database (app_configs and system_configs tables)
|
|
4
|
+
* and merges them into the ConfigRepository
|
|
5
|
+
*
|
|
6
|
+
* Similar to Laravel's database-driven config but integrated with Vasuzex's ConfigRepository
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { DatabaseConfigService } from '#framework/Config/DatabaseConfigService.js';
|
|
10
|
+
*
|
|
11
|
+
* const dbConfigService = new DatabaseConfigService(app);
|
|
12
|
+
* await dbConfigService.load();
|
|
13
|
+
*
|
|
14
|
+
* // Now access via config()
|
|
15
|
+
* app.config('phonepe.merchantId'); // From database
|
|
16
|
+
* app.config('app.name'); // From file-based config
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export class DatabaseConfigService {
|
|
20
|
+
/**
|
|
21
|
+
* Application instance
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
#app = null;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Cache for database configs
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
#cache = {
|
|
31
|
+
appConfigs: null,
|
|
32
|
+
systemConfigs: null,
|
|
33
|
+
lastLoadTime: null,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cache duration in milliseconds (5 minutes)
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
#cacheDuration = 5 * 60 * 1000;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Current environment
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
#environment = 'all';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create a new database config service
|
|
50
|
+
* @param {Application} app - Application instance
|
|
51
|
+
* @param {Object} options - Service options
|
|
52
|
+
*/
|
|
53
|
+
constructor(app, options = {}) {
|
|
54
|
+
this.#app = app;
|
|
55
|
+
this.#environment = options.environment || process.env.NODE_ENV || 'development';
|
|
56
|
+
this.#cacheDuration = options.cacheDuration || this.#cacheDuration;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Load database configs and merge into ConfigRepository
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
async load() {
|
|
64
|
+
try {
|
|
65
|
+
// Check if cache is valid
|
|
66
|
+
if (this.#isCacheValid()) {
|
|
67
|
+
console.log('[DatabaseConfigService] Using cached configs');
|
|
68
|
+
this.#mergeIntoConfigRepository();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('[DatabaseConfigService] Loading configs from database...');
|
|
73
|
+
|
|
74
|
+
// Load configs from database
|
|
75
|
+
await this.#loadAppConfigs();
|
|
76
|
+
await this.#loadSystemConfigs();
|
|
77
|
+
|
|
78
|
+
// Update cache timestamp
|
|
79
|
+
this.#cache.lastLoadTime = Date.now();
|
|
80
|
+
|
|
81
|
+
// Merge into ConfigRepository
|
|
82
|
+
this.#mergeIntoConfigRepository();
|
|
83
|
+
|
|
84
|
+
console.log('[DatabaseConfigService] Configs loaded successfully');
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('[DatabaseConfigService] Failed to load configs:', error.message);
|
|
87
|
+
// Don't throw - app should still work with file-based configs
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Reload configs from database (bypass cache)
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
async reload() {
|
|
96
|
+
this.#cache.lastLoadTime = null;
|
|
97
|
+
await this.load();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get config value directly from database (bypass ConfigRepository)
|
|
102
|
+
* @param {string} key - Config key
|
|
103
|
+
* @param {*} defaultValue - Default value
|
|
104
|
+
* @returns {Promise<*>}
|
|
105
|
+
*/
|
|
106
|
+
async getDirect(key, defaultValue = null) {
|
|
107
|
+
// Try app_configs first
|
|
108
|
+
const AppConfig = await this.#getModel('AppConfig');
|
|
109
|
+
if (AppConfig) {
|
|
110
|
+
const value = await AppConfig.getValue(key, this.#environment, null);
|
|
111
|
+
if (value !== null) {
|
|
112
|
+
return value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Try system_configs
|
|
117
|
+
const SystemConfig = await this.#getModel('SystemConfig');
|
|
118
|
+
if (SystemConfig) {
|
|
119
|
+
const value = await SystemConfig.getValue(key, this.#environment, null);
|
|
120
|
+
if (value !== null) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return defaultValue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Set config value in database
|
|
130
|
+
* @param {string} key - Config key
|
|
131
|
+
* @param {*} value - Config value
|
|
132
|
+
* @param {Object} options - Additional options (category, description, etc.)
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
async set(key, value, options = {}) {
|
|
136
|
+
const {
|
|
137
|
+
type = 'app', // 'app' or 'system'
|
|
138
|
+
category = 'app',
|
|
139
|
+
description = '',
|
|
140
|
+
is_public = false,
|
|
141
|
+
is_active = true,
|
|
142
|
+
environment = this.#environment,
|
|
143
|
+
} = options;
|
|
144
|
+
|
|
145
|
+
const Model = await this.#getModel(type === 'app' ? 'AppConfig' : 'SystemConfig');
|
|
146
|
+
|
|
147
|
+
if (!Model) {
|
|
148
|
+
throw new Error(`Model not found for type: ${type}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
await Model.setValue(key, value, {
|
|
152
|
+
category,
|
|
153
|
+
description,
|
|
154
|
+
is_public,
|
|
155
|
+
is_active,
|
|
156
|
+
environment,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Invalidate cache
|
|
160
|
+
this.#cache.lastLoadTime = null;
|
|
161
|
+
|
|
162
|
+
// Reload configs
|
|
163
|
+
await this.load();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Delete config from database
|
|
168
|
+
* @param {string} key - Config key
|
|
169
|
+
* @param {string} type - Config type ('app' or 'system')
|
|
170
|
+
* @returns {Promise<boolean>}
|
|
171
|
+
*/
|
|
172
|
+
async delete(key, type = 'app') {
|
|
173
|
+
const Model = await this.#getModel(type === 'app' ? 'AppConfig' : 'SystemConfig');
|
|
174
|
+
|
|
175
|
+
if (!Model) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const result = await Model.deleteByKey(key, this.#environment);
|
|
180
|
+
|
|
181
|
+
// Invalidate cache
|
|
182
|
+
this.#cache.lastLoadTime = null;
|
|
183
|
+
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get all database configs as object
|
|
189
|
+
* @returns {Object}
|
|
190
|
+
*/
|
|
191
|
+
getAllDatabaseConfigs() {
|
|
192
|
+
return {
|
|
193
|
+
app: this.#cache.appConfigs || {},
|
|
194
|
+
system: this.#cache.systemConfigs || {},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Load app_configs from database
|
|
200
|
+
* @private
|
|
201
|
+
*/
|
|
202
|
+
async #loadAppConfigs() {
|
|
203
|
+
const AppConfig = await this.#getModel('AppConfig');
|
|
204
|
+
|
|
205
|
+
if (!AppConfig) {
|
|
206
|
+
console.warn('[DatabaseConfigService] AppConfig model not found');
|
|
207
|
+
this.#cache.appConfigs = {};
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const configs = await AppConfig.getAllAsObject(this.#environment);
|
|
212
|
+
this.#cache.appConfigs = configs;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Load system_configs from database
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
async #loadSystemConfigs() {
|
|
220
|
+
const SystemConfig = await this.#getModel('SystemConfig');
|
|
221
|
+
|
|
222
|
+
if (!SystemConfig) {
|
|
223
|
+
console.warn('[DatabaseConfigService] SystemConfig model not found');
|
|
224
|
+
this.#cache.systemConfigs = {};
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const configs = await SystemConfig.getAllAsObject(this.#environment);
|
|
229
|
+
this.#cache.systemConfigs = configs;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Merge database configs into ConfigRepository
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
#mergeIntoConfigRepository() {
|
|
237
|
+
const config = this.#app.make('config');
|
|
238
|
+
|
|
239
|
+
if (!config) {
|
|
240
|
+
throw new Error('[DatabaseConfigService] ConfigRepository not found in container');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Merge app_configs
|
|
244
|
+
if (this.#cache.appConfigs) {
|
|
245
|
+
for (const [key, value] of Object.entries(this.#cache.appConfigs)) {
|
|
246
|
+
config.set(key, value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Merge system_configs
|
|
251
|
+
if (this.#cache.systemConfigs) {
|
|
252
|
+
for (const [key, value] of Object.entries(this.#cache.systemConfigs)) {
|
|
253
|
+
config.set(key, value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if cache is still valid
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
#isCacheValid() {
|
|
263
|
+
if (!this.#cache.lastLoadTime) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const elapsed = Date.now() - this.#cache.lastLoadTime;
|
|
268
|
+
return elapsed < this.#cacheDuration;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Get model from container or require it
|
|
273
|
+
* @private
|
|
274
|
+
*/
|
|
275
|
+
async #getModel(modelName) {
|
|
276
|
+
try {
|
|
277
|
+
// Try to get from container first
|
|
278
|
+
if (this.#app.has(modelName)) {
|
|
279
|
+
return this.#app.make(modelName);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Dynamic import needs path module
|
|
283
|
+
const path = await import('path');
|
|
284
|
+
const { fileURLToPath } = await import('url');
|
|
285
|
+
|
|
286
|
+
// Try to require dynamically from project's database/models/
|
|
287
|
+
const cwd = process.cwd();
|
|
288
|
+
|
|
289
|
+
// Try multiple paths to find models
|
|
290
|
+
const possiblePaths = [
|
|
291
|
+
path.default.join(cwd, 'database', 'models', `${modelName}.js`), // If cwd is monorepo root
|
|
292
|
+
path.default.join(cwd, '..', '..', '..', 'database', 'models', `${modelName}.js`), // If cwd is apps/*/api
|
|
293
|
+
path.default.join(cwd, '..', '..', 'database', 'models', `${modelName}.js`), // If cwd is apps/*
|
|
294
|
+
];
|
|
295
|
+
|
|
296
|
+
let Model = null;
|
|
297
|
+
for (const modelPath of possiblePaths) {
|
|
298
|
+
try {
|
|
299
|
+
const module = await import(modelPath);
|
|
300
|
+
Model = module.default || module[modelName];
|
|
301
|
+
if (Model) {
|
|
302
|
+
console.log(`[DatabaseConfigService] Loaded ${modelName} from ${modelPath}`);
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
// Try next path
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!Model) {
|
|
311
|
+
throw new Error(`Model ${modelName} not found in paths: ${possiblePaths.join(', ')}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return Model;
|
|
315
|
+
} catch (error) {
|
|
316
|
+
console.warn(`[DatabaseConfigService] Could not load model ${modelName}:`, error.message);
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Clear cache
|
|
323
|
+
*/
|
|
324
|
+
clearCache() {
|
|
325
|
+
this.#cache = {
|
|
326
|
+
appConfigs: null,
|
|
327
|
+
systemConfigs: null,
|
|
328
|
+
lastLoadTime: null,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get cache statistics
|
|
334
|
+
* @returns {Object}
|
|
335
|
+
*/
|
|
336
|
+
getCacheStats() {
|
|
337
|
+
return {
|
|
338
|
+
isValid: this.#isCacheValid(),
|
|
339
|
+
lastLoadTime: this.#cache.lastLoadTime,
|
|
340
|
+
appConfigsCount: Object.keys(this.#cache.appConfigs || {}).length,
|
|
341
|
+
systemConfigsCount: Object.keys(this.#cache.systemConfigs || {}).length,
|
|
342
|
+
cacheAge: this.#cache.lastLoadTime ? Date.now() - this.#cache.lastLoadTime : null,
|
|
343
|
+
cacheDuration: this.#cacheDuration,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export default DatabaseConfigService;
|