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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DatabaseConfigServiceProvider
|
|
3
|
+
* Service provider for database-driven configuration
|
|
4
|
+
*
|
|
5
|
+
* Loads configurations from database (app_configs and system_configs)
|
|
6
|
+
* and merges them into the ConfigRepository during boot phase
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // In app.js
|
|
10
|
+
* import { DatabaseConfigServiceProvider } from '#framework/Config/DatabaseConfigServiceProvider.js';
|
|
11
|
+
*
|
|
12
|
+
* const providers = [
|
|
13
|
+
* DatabaseConfigServiceProvider,
|
|
14
|
+
* // ... other providers
|
|
15
|
+
* ];
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { ServiceProvider } from '../Foundation/ServiceProvider.js';
|
|
19
|
+
import { DatabaseConfigService } from './DatabaseConfigService.js';
|
|
20
|
+
|
|
21
|
+
export class DatabaseConfigServiceProvider extends ServiceProvider {
|
|
22
|
+
/**
|
|
23
|
+
* Register services in the container
|
|
24
|
+
*/
|
|
25
|
+
register() {
|
|
26
|
+
// Register DatabaseConfigService as singleton
|
|
27
|
+
this.singleton('db.config', (app) => {
|
|
28
|
+
const environment = process.env.NODE_ENV || 'development';
|
|
29
|
+
|
|
30
|
+
return new DatabaseConfigService(app, {
|
|
31
|
+
environment,
|
|
32
|
+
cacheDuration: 5 * 60 * 1000, // 5 minutes
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Create alias for convenience
|
|
37
|
+
this.alias('database.config', 'db.config');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Bootstrap services
|
|
42
|
+
* Load database configs and merge into ConfigRepository
|
|
43
|
+
*/
|
|
44
|
+
async boot() {
|
|
45
|
+
try {
|
|
46
|
+
const dbConfigService = this.make('db.config');
|
|
47
|
+
|
|
48
|
+
// Load configs from database
|
|
49
|
+
await dbConfigService.load();
|
|
50
|
+
|
|
51
|
+
console.log('[DatabaseConfigServiceProvider] Database configs loaded');
|
|
52
|
+
|
|
53
|
+
// Log cache stats in debug mode
|
|
54
|
+
if (this.config('app.debug', false)) {
|
|
55
|
+
const stats = dbConfigService.getCacheStats();
|
|
56
|
+
console.log('[DatabaseConfigServiceProvider] Cache stats:', {
|
|
57
|
+
appConfigs: stats.appConfigsCount,
|
|
58
|
+
systemConfigs: stats.systemConfigsCount,
|
|
59
|
+
cacheValid: stats.isValid,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('[DatabaseConfigServiceProvider] Failed to load database configs:', error.message);
|
|
64
|
+
// Don't throw - app should continue with file-based configs
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default DatabaseConfigServiceProvider;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { join } from 'path';
|
|
8
|
+
import { readdirSync, existsSync, readFileSync } from 'fs';
|
|
9
|
+
import { GENERATOR_CONFIG } from '../config/generator.config.js';
|
|
8
10
|
import {
|
|
9
11
|
// File Operations
|
|
10
12
|
getAppPath,
|
|
@@ -38,6 +40,62 @@ import {
|
|
|
38
40
|
displayWebNextSteps,
|
|
39
41
|
} from './utils/index.js';
|
|
40
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Get next available port for app type by checking existing apps
|
|
45
|
+
* API apps: start from 3000 and increment (3000, 3001, 3002...)
|
|
46
|
+
* Web apps: start from 4000 and increment (4000, 4001, 4002...)
|
|
47
|
+
*/
|
|
48
|
+
function getNextAvailablePort(type) {
|
|
49
|
+
const projectRoot = process.cwd();
|
|
50
|
+
const appsDir = join(projectRoot, 'apps');
|
|
51
|
+
|
|
52
|
+
// Starting ports based on type
|
|
53
|
+
const startPort = type === 'api' ? GENERATOR_CONFIG.ports.apiStart : GENERATOR_CONFIG.ports.webStart;
|
|
54
|
+
|
|
55
|
+
// If apps directory doesn't exist, return start port
|
|
56
|
+
if (!existsSync(appsDir)) {
|
|
57
|
+
return startPort;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Get all existing apps and their ports
|
|
61
|
+
const usedPorts = new Set();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const appFolders = readdirSync(appsDir, { withFileTypes: true })
|
|
65
|
+
.filter(dirent => dirent.isDirectory())
|
|
66
|
+
.map(dirent => dirent.name);
|
|
67
|
+
|
|
68
|
+
// Check each app folder for app type subdirectories
|
|
69
|
+
for (const appFolder of appFolders) {
|
|
70
|
+
const appTypePath = join(appsDir, appFolder, type);
|
|
71
|
+
const envPath = join(appTypePath, '.env');
|
|
72
|
+
|
|
73
|
+
if (existsSync(envPath)) {
|
|
74
|
+
try {
|
|
75
|
+
const envContent = readFileSync(envPath, 'utf8');
|
|
76
|
+
const portMatch = envContent.match(/APP_PORT=(\d+)/);
|
|
77
|
+
if (portMatch) {
|
|
78
|
+
usedPorts.add(parseInt(portMatch[1]));
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// Skip if can't read file
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// If can't read apps directory, return start port
|
|
87
|
+
return startPort;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find next available port starting from startPort
|
|
91
|
+
let nextPort = startPort;
|
|
92
|
+
while (usedPorts.has(nextPort)) {
|
|
93
|
+
nextPort++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return nextPort;
|
|
97
|
+
}
|
|
98
|
+
|
|
41
99
|
/**
|
|
42
100
|
* Main generate app function
|
|
43
101
|
*/
|
|
@@ -163,8 +221,8 @@ async function generateWebApp(name, targetDir, framework) {
|
|
|
163
221
|
}
|
|
164
222
|
|
|
165
223
|
/**
|
|
166
|
-
* Generate common files (package.json, README, .gitignore)
|
|
167
|
-
*
|
|
224
|
+
* Generate common files (package.json, README, .gitignore, .env)
|
|
225
|
+
* App-specific .env with APP_PORT/APP_URL based on app type
|
|
168
226
|
*/
|
|
169
227
|
async function generateCommonFiles(name, type, targetDir, framework) {
|
|
170
228
|
// package.json
|
|
@@ -184,6 +242,41 @@ async function generateCommonFiles(name, type, targetDir, framework) {
|
|
|
184
242
|
generateGitignoreTemplate()
|
|
185
243
|
);
|
|
186
244
|
|
|
187
|
-
//
|
|
188
|
-
|
|
245
|
+
// App-specific .env file with correct port from GENERATOR_CONFIG
|
|
246
|
+
await generateAppEnvFile(name, type, targetDir);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Generate app-specific .env file with APP_PORT and APP_URL
|
|
251
|
+
* Automatically finds next available port for the app type
|
|
252
|
+
*/
|
|
253
|
+
async function generateAppEnvFile(name, type, targetDir) {
|
|
254
|
+
// Get next available port based on app type (auto-increment)
|
|
255
|
+
const port = getNextAvailablePort(type);
|
|
256
|
+
|
|
257
|
+
console.log(`📌 Assigning port ${port} to ${name} ${type}`);
|
|
258
|
+
|
|
259
|
+
let envContent = `# ${name.toUpperCase()} ${type.toUpperCase()} Configuration
|
|
260
|
+
APP_PORT=${port}
|
|
261
|
+
APP_URL=http://localhost:${port}
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
// For API apps, add CORS_ORIGIN pointing to corresponding web app port
|
|
265
|
+
if (type === 'api') {
|
|
266
|
+
const webPort = getNextAvailablePort('web');
|
|
267
|
+
envContent += `
|
|
268
|
+
# CORS Configuration
|
|
269
|
+
CORS_ORIGIN=http://localhost:${webPort}
|
|
270
|
+
`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await writeFileContent(
|
|
274
|
+
join(targetDir, '.env'),
|
|
275
|
+
envContent
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
await writeFileContent(
|
|
279
|
+
join(targetDir, '.env.example'),
|
|
280
|
+
envContent
|
|
281
|
+
);
|
|
189
282
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { join } from 'path';
|
|
7
|
+
import { GENERATOR_CONFIG } from '../config/generator.config.js';
|
|
7
8
|
import {
|
|
8
9
|
// File Operations
|
|
9
10
|
getProjectRoot,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
* Main generate media server function
|
|
37
38
|
*/
|
|
38
39
|
export async function generateMediaServer(options) {
|
|
39
|
-
const port = options.port || '
|
|
40
|
+
const port = options.port || '5000'; // Media server is static service, hard-coded port
|
|
40
41
|
const targetDir = join(getProjectRoot(), 'apps', 'media-server');
|
|
41
42
|
|
|
42
43
|
console.log('🖼️ Generating Media Server...\n');
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export function generateMediaServerConfigTemplate(port) {
|
|
10
10
|
return `module.exports = {
|
|
11
11
|
name: 'media-server',
|
|
12
|
-
port: process.env.
|
|
12
|
+
port: process.env.APP_PORT || ${port},
|
|
13
13
|
env: process.env.NODE_ENV || 'development',
|
|
14
14
|
debug: process.env.NODE_ENV !== 'production',
|
|
15
15
|
};
|
|
@@ -21,7 +21,8 @@ export function generateMediaServerConfigTemplate(port) {
|
|
|
21
21
|
*/
|
|
22
22
|
export function generateMediaServerEnvTemplate(port) {
|
|
23
23
|
return `# Media Server Configuration
|
|
24
|
-
|
|
24
|
+
APP_PORT=${port}
|
|
25
|
+
APP_URL=http://localhost:${port}
|
|
25
26
|
|
|
26
27
|
# Storage Configuration (uses framework's storage config)
|
|
27
28
|
STORAGE_DRIVER=local
|
|
@@ -135,44 +135,53 @@ export async function generateSvelteApp(targetDir, appName) {
|
|
|
135
135
|
*/
|
|
136
136
|
function generateViteConfig(framework) {
|
|
137
137
|
if (framework === 'react') {
|
|
138
|
-
return `import { defineConfig } from 'vite';
|
|
138
|
+
return `import { defineConfig, loadEnv } from 'vite';
|
|
139
139
|
import react from '@vitejs/plugin-react';
|
|
140
140
|
import path from 'path';
|
|
141
141
|
|
|
142
|
-
export default defineConfig({
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
142
|
+
export default defineConfig(({ mode }) => {
|
|
143
|
+
const env = loadEnv(mode, process.cwd(), '');
|
|
144
|
+
return {
|
|
145
|
+
plugins: [react()],
|
|
146
|
+
cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
|
|
147
|
+
server: {
|
|
148
|
+
port: parseInt(env.APP_PORT) || 4000,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
148
151
|
});`;
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
if (framework === 'vue') {
|
|
152
|
-
return `import { defineConfig } from 'vite';
|
|
155
|
+
return `import { defineConfig, loadEnv } from 'vite';
|
|
153
156
|
import vue from '@vitejs/plugin-vue';
|
|
154
157
|
import path from 'path';
|
|
155
158
|
|
|
156
|
-
export default defineConfig({
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
export default defineConfig(({ mode }) => {
|
|
160
|
+
const env = loadEnv(mode, process.cwd(), '');
|
|
161
|
+
return {
|
|
162
|
+
plugins: [vue()],
|
|
163
|
+
cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
|
|
164
|
+
server: {
|
|
165
|
+
port: parseInt(env.APP_PORT) || 4000,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
162
168
|
});`;
|
|
163
169
|
}
|
|
164
170
|
|
|
165
171
|
if (framework === 'svelte') {
|
|
166
|
-
return `import { defineConfig } from 'vite';
|
|
172
|
+
return `import { defineConfig, loadEnv } from 'vite';
|
|
167
173
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
168
174
|
import path from 'path';
|
|
169
175
|
|
|
170
|
-
export default defineConfig({
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
export default defineConfig(({ mode }) => {
|
|
177
|
+
const env = loadEnv(mode, process.cwd(), '');
|
|
178
|
+
return {
|
|
179
|
+
plugins: [svelte()],
|
|
180
|
+
cacheDir: path.resolve(__dirname, '../../../node_modules/.vite'),
|
|
181
|
+
server: {
|
|
182
|
+
port: parseInt(env.APP_PORT) || 4000,
|
|
183
|
+
},
|
|
184
|
+
};
|
|
176
185
|
});`;
|
|
177
186
|
}
|
|
178
187
|
|
|
@@ -19,9 +19,9 @@ export const GENERATOR_CONFIG = {
|
|
|
19
19
|
|
|
20
20
|
// Default Ports
|
|
21
21
|
ports: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
mediaServer:
|
|
22
|
+
apiStart: 3000, // API apps start from 3000 and auto-increment
|
|
23
|
+
webStart: 4000, // Web apps start from 4000 and auto-increment
|
|
24
|
+
mediaServer: 5000, // Media server is static service (hard-coded)
|
|
25
25
|
},
|
|
26
26
|
|
|
27
27
|
// Database Defaults
|
|
@@ -113,14 +113,6 @@ export default function (plop) {
|
|
|
113
113
|
skipIfExists: true
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
// Config
|
|
117
|
-
actions.push({
|
|
118
|
-
type: 'add',
|
|
119
|
-
path: `${basePath}/src/config/database.js`,
|
|
120
|
-
templateFile: 'templates/api/config/database.js.hbs',
|
|
121
|
-
skipIfExists: true
|
|
122
|
-
});
|
|
123
|
-
|
|
124
116
|
// Main app files
|
|
125
117
|
actions.push({
|
|
126
118
|
type: 'add',
|
|
@@ -14,10 +14,11 @@ import { getAllRoutes } from './routes/index.js';
|
|
|
14
14
|
* Organized and maintainable Express app configuration
|
|
15
15
|
*/
|
|
16
16
|
class {{pascalCase appName}}App extends BaseApp {
|
|
17
|
-
constructor() {
|
|
17
|
+
constructor(projectRoot) {
|
|
18
18
|
super({
|
|
19
19
|
serviceName: process.env.APP_NAME || '{{appName}}-api',
|
|
20
|
-
corsOrigin: env('CORS_ORIGIN', '
|
|
20
|
+
corsOrigin: env('CORS_ORIGIN', '*'),
|
|
21
|
+
projectRoot: projectRoot
|
|
21
22
|
});
|
|
22
23
|
|
|
23
24
|
// Register service providers
|
|
@@ -65,7 +66,7 @@ class {{pascalCase appName}}App extends BaseApp {
|
|
|
65
66
|
/**
|
|
66
67
|
* Create and configure the Express app
|
|
67
68
|
*/
|
|
68
|
-
export function createApp() {
|
|
69
|
-
const app = new {{pascalCase appName}}App();
|
|
69
|
+
export function createApp(projectRoot) {
|
|
70
|
+
const app = new {{pascalCase appName}}App(projectRoot);
|
|
70
71
|
return app;
|
|
71
72
|
}
|
|
@@ -9,9 +9,15 @@ import { createApp as buildApp } from './app.js';
|
|
|
9
9
|
|
|
10
10
|
// Import centralized database (triggers connection automatically)
|
|
11
11
|
import '@{{projectName}}/database';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
|
|
15
|
+
// Get monorepo root (3 levels up from apps/{{appName}}/api)
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const projectRoot = path.resolve(__dirname, '..', '..', '..', '..');
|
|
12
19
|
|
|
13
20
|
// Load root .env file
|
|
14
|
-
const projectRoot = process.cwd();
|
|
15
21
|
dotenv.config({ path: `${projectRoot}/.env` });
|
|
16
22
|
|
|
17
23
|
/**
|
|
@@ -59,7 +65,7 @@ class {{pascalCase appName}}Server extends BaseServer {
|
|
|
59
65
|
* Create Express app
|
|
60
66
|
*/
|
|
61
67
|
createApp() {
|
|
62
|
-
return buildApp();
|
|
68
|
+
return buildApp(this.projectRoot);
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Registers database services and model components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import ServiceProvider from '../
|
|
6
|
+
import ServiceProvider from '../Foundation/ServiceProvider.js';
|
|
7
7
|
import Model from './Model.js';
|
|
8
8
|
|
|
9
9
|
export class DatabaseServiceProvider extends ServiceProvider {
|
|
@@ -410,6 +410,15 @@ export class Model extends GuruORMModel {
|
|
|
410
410
|
dirty[this.constructor.updatedAt] = this.attributes[this.constructor.updatedAt];
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
// Filter out internal tracking properties before update
|
|
414
|
+
delete dirty.isDirtyFlag; // Remove internal tracking fields
|
|
415
|
+
delete dirty.pendingMutators; // Remove async mutator tracking
|
|
416
|
+
delete dirty.isHydrating; // Remove hydration flag
|
|
417
|
+
delete dirty.exists; // Remove exists flag
|
|
418
|
+
delete dirty.wasRecentlyCreated; // Remove recently created flag
|
|
419
|
+
delete dirty.original; // Remove original tracking
|
|
420
|
+
delete dirty.relations; // Remove relations (handled separately)
|
|
421
|
+
|
|
413
422
|
const pk = this.constructor.primaryKey || 'id';
|
|
414
423
|
|
|
415
424
|
// Use GuruORM's update
|
|
@@ -20,4 +20,5 @@ export {
|
|
|
20
20
|
AuthorizationError,
|
|
21
21
|
} from './ErrorTypes.js';
|
|
22
22
|
export { ExceptionHandler, createExceptionHandler } from './Handler.js';
|
|
23
|
-
|
|
23
|
+
// asyncHandler moved to Http module - import from 'vasuzex/Http' instead
|
|
24
|
+
export { asyncMiddleware, catchAsync } from './asyncHandler.js';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { Application } from './Application.js';
|
|
3
3
|
import { applySecurityMiddleware } from '../Http/Middleware/SecurityMiddleware.js';
|
|
4
|
+
import { LogServiceProvider } from '../Services/Log/LogServiceProvider.js';
|
|
5
|
+
import { HashServiceProvider } from './Providers/HashServiceProvider.js';
|
|
6
|
+
import { ValidationServiceProvider } from './Providers/ValidationServiceProvider.js';
|
|
7
|
+
import { EncryptionServiceProvider } from './Providers/EncryptionServiceProvider.js';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* BaseApp - Base class for application-level apps (Express apps)
|
|
@@ -19,6 +23,21 @@ export class BaseApp extends Application {
|
|
|
19
23
|
this.middlewareSetup = false;
|
|
20
24
|
this.routesSetup = false;
|
|
21
25
|
this.securitySetup = false;
|
|
26
|
+
|
|
27
|
+
// Register core service providers automatically
|
|
28
|
+
this.registerCoreServiceProviders();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Register core framework service providers
|
|
33
|
+
* These are registered automatically for all apps
|
|
34
|
+
*/
|
|
35
|
+
registerCoreServiceProviders() {
|
|
36
|
+
// Core services that every app needs
|
|
37
|
+
this.register(LogServiceProvider);
|
|
38
|
+
this.register(HashServiceProvider);
|
|
39
|
+
this.register(ValidationServiceProvider);
|
|
40
|
+
this.register(EncryptionServiceProvider);
|
|
22
41
|
}
|
|
23
42
|
|
|
24
43
|
/**
|
|
@@ -19,6 +19,101 @@ export class BaseService {
|
|
|
19
19
|
this.sortableFields = ['id', 'created_at', 'updated_at'];
|
|
20
20
|
this.defaultPerPage = 15;
|
|
21
21
|
this.maxPerPage = 100;
|
|
22
|
+
this.request = null; // Will be set from controller
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert camelCase to snake_case for PostgreSQL column names
|
|
27
|
+
* PostgreSQL convention uses snake_case
|
|
28
|
+
* @param {string} str - camelCase string
|
|
29
|
+
* @returns {string} snake_case string
|
|
30
|
+
*/
|
|
31
|
+
toSnakeCase(str) {
|
|
32
|
+
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Set request context (call from controller before service methods)
|
|
37
|
+
* @param {Object} req - Express request object
|
|
38
|
+
* @returns {this} For chaining
|
|
39
|
+
*/
|
|
40
|
+
setContext(req) {
|
|
41
|
+
this.request = req;
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get query options from request
|
|
47
|
+
* Standardizes pagination, search, sort, filters from req.query
|
|
48
|
+
* @returns {Object} Standardized query options
|
|
49
|
+
*/
|
|
50
|
+
getQueryOptions() {
|
|
51
|
+
if (!this.request) {
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { query } = this.request;
|
|
56
|
+
const columnSearch = this.extractColumnSearch(query);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
page: parseInt(query.page) || 1,
|
|
60
|
+
limit: parseInt(query.limit) || 10,
|
|
61
|
+
search: query.search || '',
|
|
62
|
+
sortBy: query.sortBy || query.sort_by || 'created_at',
|
|
63
|
+
sortOrder: query.sortOrder || query.sort_order || 'desc',
|
|
64
|
+
filters: this.extractFilters(query),
|
|
65
|
+
columnSearch: columnSearch,
|
|
66
|
+
relations: query.with ? query.with.split(',') : []
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract column-specific search from query params
|
|
72
|
+
* Frontend sends: columnSearch[name]=john&columnSearch[email]=test
|
|
73
|
+
* Backend receives as: req.query['columnSearch[name]'] = 'john'
|
|
74
|
+
* This method parses them into: {name: 'john', email: 'test'}
|
|
75
|
+
* @param {Object} query - Request query params
|
|
76
|
+
* @returns {Object} Column search object
|
|
77
|
+
*/
|
|
78
|
+
extractColumnSearch(query) {
|
|
79
|
+
const columnSearch = {};
|
|
80
|
+
const columnSearchPattern = /^columnSearch\[(.+)\]$/;
|
|
81
|
+
|
|
82
|
+
Object.keys(query).forEach(key => {
|
|
83
|
+
const match = key.match(columnSearchPattern);
|
|
84
|
+
if (match && query[key]) {
|
|
85
|
+
const fieldName = match[1];
|
|
86
|
+
columnSearch[fieldName] = query[key];
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return columnSearch;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extract filters from query params
|
|
95
|
+
* Override in child services for custom filter extraction
|
|
96
|
+
* @param {Object} query - Request query params
|
|
97
|
+
* @returns {Object} Filters object
|
|
98
|
+
*/
|
|
99
|
+
extractFilters(query) {
|
|
100
|
+
const filters = {};
|
|
101
|
+
const excludedKeys = ['page', 'limit', 'search', 'sortBy', 'sort_by', 'sortOrder', 'sort_order', 'with'];
|
|
102
|
+
const columnSearchPattern = /^columnSearch\[(.+)\]$/;
|
|
103
|
+
|
|
104
|
+
Object.keys(query).forEach(key => {
|
|
105
|
+
// Skip excluded keys and columnSearch params
|
|
106
|
+
if (excludedKeys.includes(key) || columnSearchPattern.test(key)) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const value = query[key];
|
|
111
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
112
|
+
filters[key] = value;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return filters;
|
|
22
117
|
}
|
|
23
118
|
|
|
24
119
|
/**
|
|
@@ -58,9 +58,24 @@ export class Container {
|
|
|
58
58
|
throw new Error(`No binding found for "${abstract}"`);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
// Handle factory functions vs constructors
|
|
62
|
+
let instance;
|
|
63
|
+
if (typeof concrete === 'function') {
|
|
64
|
+
// Check if it's a factory function (arrow function or function that takes app parameter)
|
|
65
|
+
// Factory functions receive the app instance as parameter
|
|
66
|
+
const isFactory = concrete.length > 0 || concrete.toString().includes('=>');
|
|
67
|
+
|
|
68
|
+
if (isFactory) {
|
|
69
|
+
// Call factory function with app instance
|
|
70
|
+
instance = concrete(this);
|
|
71
|
+
} else {
|
|
72
|
+
// Call as constructor
|
|
73
|
+
instance = new concrete();
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
// Direct value
|
|
77
|
+
instance = concrete;
|
|
78
|
+
}
|
|
64
79
|
|
|
65
80
|
if (this.singletons.has(key)) {
|
|
66
81
|
this.instances.set(key, instance);
|
|
@@ -4,7 +4,6 @@ export { NotificationServiceProvider } from './NotificationServiceProvider.js';
|
|
|
4
4
|
export { HashServiceProvider } from './HashServiceProvider.js';
|
|
5
5
|
export { ValidationServiceProvider } from './ValidationServiceProvider.js';
|
|
6
6
|
export { EventServiceProvider } from './EventServiceProvider.js';
|
|
7
|
-
export { LogServiceProvider } from './LogServiceProvider.js';
|
|
8
7
|
export { EncryptionServiceProvider } from './EncryptionServiceProvider.js';
|
|
9
8
|
export { CookieServiceProvider } from './CookieServiceProvider.js';
|
|
10
9
|
export { SessionServiceProvider } from './SessionServiceProvider.js';
|
|
@@ -22,6 +22,48 @@ export class ServiceProvider {
|
|
|
22
22
|
boot() {
|
|
23
23
|
// Override in subclass
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Bind a service to the container
|
|
28
|
+
*/
|
|
29
|
+
bind(abstract, concrete, singleton = false) {
|
|
30
|
+
return this.app.bind(abstract, concrete, singleton);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Bind a singleton to the container
|
|
35
|
+
*/
|
|
36
|
+
singleton(abstract, concrete) {
|
|
37
|
+
return this.app.singleton(abstract, concrete);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Bind an instance to the container
|
|
42
|
+
*/
|
|
43
|
+
instance(abstract, instance) {
|
|
44
|
+
return this.app.instance(abstract, instance);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create an alias
|
|
49
|
+
*/
|
|
50
|
+
alias(alias, abstract) {
|
|
51
|
+
return this.app.alias(alias, abstract);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolve a service from the container
|
|
56
|
+
*/
|
|
57
|
+
make(abstract) {
|
|
58
|
+
return this.app.make(abstract);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get configuration value
|
|
63
|
+
*/
|
|
64
|
+
config(key, defaultValue = null) {
|
|
65
|
+
return this.app.config(key, defaultValue);
|
|
66
|
+
}
|
|
25
67
|
}
|
|
26
68
|
|
|
27
69
|
export default ServiceProvider;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Handler Middleware
|
|
3
|
+
* Wraps async controller methods to catch errors automatically
|
|
4
|
+
* Laravel-style error handling for Express async routes
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* import { asyncHandler } from 'vasuzex/Http';
|
|
8
|
+
*
|
|
9
|
+
* router.get('/users', asyncHandler(async (req, res) => {
|
|
10
|
+
* const users = await User.all();
|
|
11
|
+
* res.json(users);
|
|
12
|
+
* }));
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Wrap an async function to catch errors
|
|
17
|
+
* @param {Function} fn - Async function to wrap
|
|
18
|
+
* @returns {Function} Express middleware function
|
|
19
|
+
*/
|
|
20
|
+
export function asyncHandler(fn) {
|
|
21
|
+
return (req, res, next) => {
|
|
22
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default asyncHandler;
|