@feardread/fear 1.2.1 → 2.0.1

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/FEARServer.js CHANGED
@@ -2,14 +2,15 @@
2
2
 
3
3
  const path = require('path');
4
4
  const express = require('express');
5
- require("dotenv").config();
5
+ const fs = require('fs');
6
+ const dotenv = require('dotenv');
6
7
 
7
8
  const FearServer = (function () {
8
9
  // Private constants
9
10
  const DEFAULT_PATHS = {
10
11
  root: path.resolve(),
11
- app: '/backend/dashboard/build',
12
- build: 'backend/dashboard/build'
12
+ app: 'backend/admin/build',
13
+ build: 'backend/admin/build'
13
14
  };
14
15
 
15
16
  const DEFAULT_PORT = 4000;
@@ -22,40 +23,170 @@ const FearServer = (function () {
22
23
  this.Router = null;
23
24
  this.isShuttingDown = false;
24
25
  this.rootDir = path.resolve();
26
+ this.reactApps = []; // Track multiple React apps
27
+ this.envLoaded = false;
25
28
  }
26
29
 
27
30
  FearServer.prototype = {
28
31
  constructor: FearServer,
29
32
 
33
+ /**
34
+ * Load environment variables from .env file
35
+ * @param {string|Object} envConfig - Path to .env file or dotenv config object
36
+ */
37
+ loadEnv(envConfig) {
38
+ let envPath;
39
+ let options = {};
40
+
41
+ // Handle different config formats
42
+ if (typeof envConfig === 'string') {
43
+ envPath = envConfig;
44
+ } else if (typeof envConfig === 'object') {
45
+ envPath = envConfig.path;
46
+ options = envConfig;
47
+ }
48
+
49
+ // Auto-detect .env file if not specified
50
+ if (!envPath) {
51
+ const possiblePaths = [
52
+ path.join(this.rootDir, '.env'),
53
+ path.join(this.rootDir, '.env.local'),
54
+ path.join(this.rootDir, '.env.development'),
55
+ path.join(this.rootDir, '.env.production'),
56
+ path.join(process.cwd(), '.env'),
57
+ ];
58
+
59
+ // Check NODE_ENV for environment-specific files
60
+ if (process.env.NODE_ENV) {
61
+ possiblePaths.unshift(
62
+ path.join(this.rootDir, `.env.${process.env.NODE_ENV}`),
63
+ path.join(this.rootDir, `.env.${process.env.NODE_ENV}.local`)
64
+ );
65
+ }
66
+
67
+ // Find first existing .env file
68
+ envPath = possiblePaths.find(p => fs.existsSync(p));
69
+ }
70
+
71
+ if (envPath && !fs.existsSync(envPath)) {
72
+ console.warn(`⚠️ Warning: .env file not found at ${envPath}`);
73
+ return false;
74
+ }
75
+
76
+ if (!envPath) {
77
+ console.warn('Warning: No .env file found in common locations');
78
+ console.warn(' Checked:', [
79
+ path.join(this.rootDir, '.env'),
80
+ path.join(this.rootDir, `.env.${process.env.NODE_ENV || 'development'}`),
81
+ path.join(process.cwd(), '.env')
82
+ ]);
83
+ return false;
84
+ }
85
+
86
+ // Load the .env file
87
+ const result = dotenv.config({ path: envPath, ...options });
88
+
89
+ if (result.error) {
90
+ console.error('Error loading .env file:', result.error);
91
+ return false;
92
+ }
93
+
94
+ this.envLoaded = true;
95
+ return true;
96
+ },
97
+
98
+ /**
99
+ * Validate that required files exist for React app
100
+ * @param {string} buildPath - Path to build directory
101
+ * @param {string} indexPath - Path to index.html
102
+ */
103
+ validateReactApp(buildPath, indexPath) {
104
+ if (!fs.existsSync(buildPath)) {
105
+ throw new Error(`Build directory not found: ${buildPath}`);
106
+ }
107
+
108
+ if (!fs.existsSync(indexPath)) {
109
+ throw new Error(`index.html not found: ${indexPath}`);
110
+ }
111
+
112
+ this.fear.getLogger().info('✓ React app files validated');
113
+ return true;
114
+ },
115
+
30
116
  /**
31
117
  * Configure static file serving for React SPA
32
118
  * @param {string} root - Root directory path
33
- * @param {string} app - App directory path
34
- * @param {string} build - Build directory path
35
- * @param {string} basePath - Base path for the app (e.g., '/fear/sites/ghap')
119
+ * @param {string} app - App directory path relative to root
120
+ * @param {string} build - Build directory path for index.html
121
+ * @param {string} basePath - Base path for the app (e.g., '/admin', '/dashboard')
36
122
  */
37
123
  setupStaticFiles(root, app, build, basePath = '') {
38
124
  this.rootDir = root || path.resolve();
39
- const buildPath = path.join(this.rootDir, app);
40
- const indexPath = path.resolve(this.rootDir, build, "index.html");
41
-
125
+
126
+ // Construct paths - handle both absolute and relative paths
127
+ const buildPath = path.isAbsolute(app)
128
+ ? app
129
+ : path.join(this.rootDir, app);
130
+
131
+ const indexPath = path.isAbsolute(build)
132
+ ? path.join(build, "index.html")
133
+ : path.join(this.rootDir, build, "index.html");
134
+
135
+ // Normalize base path
42
136
  const normalizedBasePath = basePath
43
137
  ? `/${basePath.replace(/^\/+|\/+$/g, '')}`
44
138
  : '';
45
139
 
46
-
47
- this.fear.getLogger().info(`Serving static files from: ${buildPath}`);
48
- this.fear.getLogger().info(`Base URL path: ${normalizedBasePath || '/'}`);
140
+ // Debug logging
141
+ this.fear.getLogger().info('═══════════════════════════════════════');
142
+ this.fear.getLogger().info('React App Path Resolution:');
143
+ this.fear.getLogger().info(`→ Build path: ${buildPath}`);
144
+ this.fear.getLogger().info(`→ Index path: ${indexPath}`);
145
+ this.fear.getLogger().info(`→ Base path: ${normalizedBasePath || '/'}`);
49
146
 
147
+ // Validate React app exists
148
+ try {
149
+ this.validateReactApp(buildPath, indexPath);
150
+ } catch (error) {
151
+ this.fear.getLogger().error('React app validation failed:', error.message);
152
+ throw error;
153
+ }
154
+
155
+ // Serve static files with caching headers
50
156
  this.fear.getApp().use(
51
157
  normalizedBasePath,
158
+ (req, res, next) => {
159
+ this.fear.getLogger().debug(`Static file request: ${req.path}`);
160
+ next();
161
+ },
52
162
  express.static(buildPath, {
53
163
  index: false,
54
- fallthrough: true
164
+ fallthrough: true,
165
+ maxAge: '1h', // Cache static assets
166
+ etag: true,
167
+ lastModified: true,
168
+ setHeaders: (res, filePath) => {
169
+ // Don't cache index.html
170
+ if (filePath.endsWith('index.html')) {
171
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
172
+ }
173
+ // Cache JS/CSS files aggressively
174
+ else if (filePath.match(/\.(js|css|woff2?|ttf|eot)$/)) {
175
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
176
+ }
177
+ }
55
178
  })
56
179
  );
57
180
 
58
- this.fear.getApp().get(`${normalizedBasePath}/*`, (req, res) => {
181
+ this.fear.getApp().get(`${normalizedBasePath}/*`, (req, res, next) => {
182
+ this.fear.getLogger().debug(`Route request: ${req.path}`);
183
+
184
+ // Skip if it's an API route
185
+ if (req.path.startsWith('/fear/api')) {
186
+ this.fear.getLogger().debug('Skipping - API route');
187
+ return next();
188
+ }
189
+
59
190
  res.sendFile(indexPath, (err) => {
60
191
  if (err) {
61
192
  this.fear.getLogger().error('Error serving index.html:', err);
@@ -63,6 +194,85 @@ const FearServer = (function () {
63
194
  }
64
195
  });
65
196
  });
197
+
198
+ // Track registered React apps
199
+ this.reactApps.push({
200
+ basePath: normalizedBasePath || '/',
201
+ buildPath,
202
+ indexPath
203
+ });
204
+
205
+ this.fear.getLogger().info(`React app configured at: ${normalizedBasePath || '/'}`);
206
+ },
207
+
208
+ /**
209
+ * Add multiple React apps at different base paths
210
+ * @param {Array} apps - Array of app configurations
211
+ * Example: [{root: __dirname, app: '/build', build: 'build', basePath: '/admin'}]
212
+ */
213
+ setupMultipleReactApps(apps) {
214
+ if (!Array.isArray(apps)) {
215
+ throw new Error('setupMultipleReactApps expects an array of app configurations');
216
+ }
217
+
218
+ apps.forEach((appConfig, index) => {
219
+ this.fear.getLogger().info(`Setting up React app ${index + 1}/${apps.length}`);
220
+ this.setupStaticFiles(
221
+ appConfig.root,
222
+ appConfig.app,
223
+ appConfig.build,
224
+ appConfig.basePath
225
+ );
226
+ });
227
+ },
228
+
229
+ /**
230
+ * Setup CORS for React development
231
+ * @param {Object} options - CORS options
232
+ */
233
+ setupCORS(options = {}) {
234
+ const defaultOptions = {
235
+ origin: options.origin || '*',
236
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
237
+ allowedHeaders: ['Content-Type', 'Authorization'],
238
+ credentials: options.credentials || false
239
+ };
240
+
241
+ this.fear.getApp().use((req, res, next) => {
242
+ res.header('Access-Control-Allow-Origin', defaultOptions.origin);
243
+ res.header('Access-Control-Allow-Methods', defaultOptions.methods.join(', '));
244
+ res.header('Access-Control-Allow-Headers', defaultOptions.allowedHeaders.join(', '));
245
+
246
+ if (defaultOptions.credentials) {
247
+ res.header('Access-Control-Allow-Credentials', 'true');
248
+ }
249
+
250
+ // Handle preflight
251
+ if (req.method === 'OPTIONS') {
252
+ return res.sendStatus(200);
253
+ }
254
+
255
+ next();
256
+ });
257
+
258
+ this.fear.getLogger().info('CORS configured for React development');
259
+ },
260
+
261
+ /**
262
+ * Setup API routes prefix (useful to avoid conflicts with React routes)
263
+ * @param {string} prefix - API prefix (e.g., '/api')
264
+ */
265
+ setupAPIPrefix(prefix = '/fear/api') {
266
+ const normalizedPrefix = `/${prefix.replace(/^\/+|\/+$/g, '')}`;
267
+
268
+ this.fear.getApp().use(normalizedPrefix, (req, res, next) => {
269
+
270
+ req.isAPIRoute = true;
271
+ next();
272
+ });
273
+
274
+ this.fear.getLogger().info(`API routes configured with prefix: ${normalizedPrefix}`);
275
+ return normalizedPrefix;
66
276
  },
67
277
 
68
278
  /**
@@ -71,9 +281,9 @@ const FearServer = (function () {
71
281
  setupProcessHandlers() {
72
282
  // Handle unhandled promise rejections
73
283
  process.on("unhandledRejection", (reason, promise) => {
74
- console.log('Unhandled Rejection at:', promise, 'reason:', reason);
284
+ console.log('Unhandled Rejection at:', promise);
75
285
  this.fear.getLogger().error('Unhandled Rejection at:', promise, 'reason:', reason);
76
- //this.gracefulShutdown('unhandledRejection');
286
+ this.gracefulShutdown('unhandledRejection');
77
287
  });
78
288
 
79
289
  // Handle uncaught exceptions
@@ -183,15 +393,46 @@ const FearServer = (function () {
183
393
 
184
394
  /**
185
395
  * Initialize FEAR application
396
+ * @param {Object} paths - Path configuration
397
+ * @param {boolean} ADD_PAYMENTS - Whether to add payment routes
398
+ * @param {Object} reactConfig - Additional React configuration
399
+ * @param {string|Object} envConfig - Environment file configuration
186
400
  */
187
- initialize(paths = DEFAULT_PATHS) {
401
+ initialize(paths = DEFAULT_PATHS, ADD_PAYMENTS = false, reactConfig = {}, envConfig = null) {
188
402
  try {
403
+ // Set root directory first
404
+ this.rootDir = paths.root || path.resolve();
405
+
406
+ // Load environment variables if config provided
407
+ if (envConfig) {
408
+ this.loadEnv(envConfig);
409
+ } else if (!this.envLoaded) {
410
+ // Try auto-loading if not already loaded
411
+ this.loadEnv();
412
+ }
413
+
189
414
  // Import FEAR after dotenv is configured
190
415
  const FearFactory = require("./FEAR");
191
- this.fear = new FearFactory();
416
+ this.fear = new FearFactory({ADD_PAYMENTS});
192
417
  this.Router = this.fear.Router;
193
418
 
194
- this.setupStaticFiles(paths.root, paths.app, paths.build, paths.basePath);
419
+ // Setup CORS if enabled
420
+ if (reactConfig.enableCORS) {
421
+ this.setupCORS(reactConfig.corsOptions);
422
+ }
423
+
424
+ // Setup API prefix if provided
425
+ if (reactConfig.apiPrefix) {
426
+ this.setupAPIPrefix(reactConfig.apiPrefix);
427
+ }
428
+
429
+ // Setup React app(s)
430
+ if (reactConfig.multipleApps && Array.isArray(reactConfig.apps)) {
431
+ this.setupMultipleReactApps(reactConfig.apps);
432
+ } else {
433
+ this.setupStaticFiles(paths.root, paths.app, paths.build, paths.basePath);
434
+ }
435
+
195
436
  this.setupProcessHandlers();
196
437
 
197
438
  return Promise.resolve(this.fear);
@@ -213,6 +454,9 @@ const FearServer = (function () {
213
454
  logger.warn(this.fear.logo);
214
455
  }
215
456
 
457
+ logger.info('Starting FEAR Server...');
458
+ logger.info('═══════════════════════════════════════');
459
+
216
460
  // Initialize database connection
217
461
  return this.initializeDatabase()
218
462
  .then(() => {
@@ -221,7 +465,19 @@ const FearServer = (function () {
221
465
  })
222
466
  .then((server) => {
223
467
  this.server = server;
224
- logger.info(`FEAR API Initialized :: Port ${port}`);
468
+ logger.info('═══════════════════════════════════════');
469
+ logger.info(`FEAR API Server Running on Port ${port}`);
470
+ logger.info('═══════════════════════════════════════');
471
+
472
+ // Display registered React apps
473
+ if (this.reactApps.length > 0) {
474
+ logger.info('📱 React Apps:');
475
+ this.reactApps.forEach(app => {
476
+ logger.info(`• http://localhost:${port}${app.basePath}`);
477
+ });
478
+ logger.info('═══════════════════════════════════════');
479
+ }
480
+
225
481
  return server;
226
482
  })
227
483
  .catch((error) => {
@@ -270,6 +526,20 @@ const FearServer = (function () {
270
526
  */
271
527
  getRootDir() {
272
528
  return this.rootDir;
529
+ },
530
+
531
+ /**
532
+ * Get registered React apps
533
+ */
534
+ getReactApps() {
535
+ return this.reactApps;
536
+ },
537
+
538
+ /**
539
+ * Check if environment variables are loaded
540
+ */
541
+ isEnvLoaded() {
542
+ return this.envLoaded;
273
543
  }
274
544
  };
275
545
 
@@ -0,0 +1,9 @@
1
+ const Address = require("../models/address");
2
+ const methods = require("./crud");
3
+
4
+ const crud = methods.crudController( Address );
5
+ for(prop in crud) {
6
+ if(crud.hasOwnProperty(prop)) {
7
+ module.exports[prop] = crud[prop];
8
+ }
9
+ }