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.
Files changed (87) hide show
  1. package/.ai-memory/LOGGER_STRICT_POLICY.md +201 -0
  2. package/.ai-memory/neastore-feature-mapping.md +1114 -0
  3. package/bin/create-vasuzex.js +5 -2
  4. package/examples/runtime-config-examples.js +309 -0
  5. package/framework/Config/DatabaseConfigService.js +348 -0
  6. package/framework/Config/DatabaseConfigServiceProvider.js +69 -0
  7. package/framework/Console/Commands/generate-app.js +97 -4
  8. package/framework/Console/Commands/generate-media-server.js +2 -1
  9. package/framework/Console/Commands/utils/mediaServerTemplates.js +3 -2
  10. package/framework/Console/Commands/utils/webStructure.js +30 -21
  11. package/framework/Console/config/generator.config.js +3 -3
  12. package/framework/Console/plopfile.js +0 -8
  13. package/framework/Console/templates/api/app.js.hbs +5 -4
  14. package/framework/Console/templates/api/server.js.hbs +8 -2
  15. package/framework/Database/DatabaseServiceProvider.js +1 -1
  16. package/framework/Database/Model.js +9 -0
  17. package/framework/Exceptions/index.js +2 -1
  18. package/framework/Foundation/BaseApp.js +19 -0
  19. package/framework/Foundation/BaseService.js +95 -0
  20. package/framework/Foundation/Container.js +18 -3
  21. package/framework/Foundation/Providers/index.js +0 -1
  22. package/framework/Foundation/ServiceProvider.js +42 -0
  23. package/framework/Http/asyncHandler.js +26 -0
  24. package/framework/Http/index.js +1 -0
  25. package/framework/Services/Log/LogManager.js +26 -5
  26. package/framework/Services/Log/LogServiceProvider.js +48 -0
  27. package/framework/Services/Log/index.js +1 -0
  28. package/framework/Services/Mail/MailServiceProvider.js +36 -0
  29. package/framework/Services/Mail/index.js +1 -0
  30. package/framework/Services/Media/MediaServiceProvider.js +2 -2
  31. package/framework/Services/Payment/PaymentServiceProvider.js +35 -0
  32. package/framework/Services/Payment/index.js +1 -0
  33. package/framework/Services/Security/SecurityService.js +253 -0
  34. package/framework/Services/Security/SecurityServiceProvider.js +33 -0
  35. package/framework/Services/Security/index.js +9 -0
  36. package/framework/Services/Storage/StorageManager.js +7 -1
  37. package/framework/Services/Storage/StorageServiceProvider.js +36 -0
  38. package/framework/Services/Storage/index.js +1 -0
  39. package/framework/Services/Upload/UploadManager.js +179 -0
  40. package/framework/Services/index.js +1 -0
  41. package/framework/Support/Facades/Security.js +14 -0
  42. package/framework/Support/Facades/index.js +1 -0
  43. package/framework/Support/Helpers/index.js +1 -0
  44. package/framework/Support/Helpers/utilities.js +348 -0
  45. package/framework/index.js +2 -0
  46. package/frontend/client/Config/ConfigLoader.js +52 -10
  47. package/frontend/client/Http/ApiHelpers.js +99 -0
  48. package/frontend/client/Http/index.js +1 -0
  49. package/frontend/client/index.js +1 -1
  50. package/frontend/client/package.json +14 -66
  51. package/frontend/client/package.json.backup +41 -0
  52. package/frontend/react-ui/components/Avatars/GradientAvatar.jsx +255 -0
  53. package/frontend/react-ui/components/Avatars/index.js +66 -0
  54. package/frontend/react-ui/components/BreadCrumb/BreadCrumb.jsx +69 -0
  55. package/frontend/react-ui/components/BreadCrumb/index.js +2 -0
  56. package/frontend/react-ui/components/DataTable/ActionDefaults.jsx +171 -0
  57. package/frontend/react-ui/components/DataTable/DataTable.jsx +202 -328
  58. package/frontend/react-ui/components/DataTable/Filters.jsx +69 -56
  59. package/frontend/react-ui/components/DataTable/Pagination.jsx +59 -140
  60. package/frontend/react-ui/components/DataTable/TableActions.jsx +11 -20
  61. package/frontend/react-ui/components/DataTable/TableBody.jsx +168 -168
  62. package/frontend/react-ui/components/DataTable/TableHeader.jsx +93 -96
  63. package/frontend/react-ui/components/DataTable/TableState.jsx +33 -0
  64. package/frontend/react-ui/components/DataTable/index.js +10 -8
  65. package/frontend/react-ui/components/ImageLightbox/ImageLightbox.jsx +118 -0
  66. package/frontend/react-ui/components/ImageLightbox/index.js +1 -0
  67. package/frontend/react-ui/components/OrderTimeline/OrderTimeline.jsx +269 -0
  68. package/frontend/react-ui/components/OrderTimeline/index.js +1 -0
  69. package/frontend/react-ui/components/ReadMore/ReadMore.jsx +34 -0
  70. package/frontend/react-ui/components/ReadMore/index.js +1 -0
  71. package/frontend/react-ui/components/Switch/Switch.jsx +34 -0
  72. package/frontend/react-ui/components/Switch/index.js +1 -0
  73. package/frontend/react-ui/hooks/useAppConfig.js +58 -4
  74. package/frontend/react-ui/hooks/useLocalStorage.js +1 -1
  75. package/frontend/react-ui/hooks/useValidationErrors.js +1 -1
  76. package/frontend/react-ui/index.js +10 -0
  77. package/frontend/react-ui/package.json +17 -108
  78. package/frontend/react-ui/providers/ApiClientProvider.jsx +1 -1
  79. package/frontend/react-ui/providers/AppConfigProvider.jsx +212 -20
  80. package/frontend/react-ui/utils/formatters.js +193 -0
  81. package/frontend/react-ui/utils/index.js +30 -0
  82. package/frontend/react-ui/utils/logger.js +62 -0
  83. package/frontend/react-ui/utils/storage.js +90 -0
  84. package/frontend/react-ui/utils/swal.js +134 -0
  85. package/frontend/react-ui/utils/validation.js +207 -0
  86. package/package.json +6 -2
  87. package/framework/Foundation/Providers/LogServiceProvider.js +0 -33
@@ -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;