mastercontroller 1.2.14 → 1.3.0

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/README.md CHANGED
@@ -1,58 +1,1518 @@
1
- ## MasterController Framework
1
+ # MasterController Framework
2
2
 
3
- MasterController is a lightweight MVC-style server framework for Node.js with routing, controllers, views, dependency injection, CORS, sessions, sockets, and more.
3
+ MasterController is a lightweight MVC-style server framework for Node.js with middleware pipeline, routing, controllers, views, dependency injection, CORS, sessions, sockets, and more.
4
4
 
5
- ### Install
6
- ```
5
+ ## Table of Contents
6
+ - [Installation](#installation)
7
+ - [Quickstart](#quickstart)
8
+ - [Middleware Pipeline](#middleware-pipeline)
9
+ - [Routing](#routing)
10
+ - [Controllers](#controllers)
11
+ - [Views and Templates](#views-and-templates)
12
+ - [View Pattern Hooks](#view-pattern-hooks)
13
+ - [Dependency Injection](#dependency-injection)
14
+ - [CORS](#cors)
15
+ - [Sessions](#sessions)
16
+ - [Security](#security)
17
+ - [Components](#components)
18
+ - [Timeout System](#timeout-system)
19
+ - [Error Handling](#error-handling)
20
+ - [HTTPS Setup](#https-setup)
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ ```bash
7
27
  npm install mastercontroller
8
28
  ```
9
29
 
10
- ### Quickstart
11
- ```js
30
+ ---
31
+
32
+ ## Quickstart
33
+
34
+ ```javascript
12
35
  // server.js
13
- const master = require('./MasterControl');
36
+ const master = require('mastercontroller');
37
+
38
+ master.root = __dirname;
39
+ master.environmentType = 'development'; // or process.env.NODE_ENV
14
40
 
15
- master.root = __dirname; // your project root
16
- master.environmentType = 'development'; // or process.env.NODE_ENV
41
+ const server = master.setupServer('http'); // or 'https'
42
+
43
+ // Load configuration (registers middleware, routes, DI services)
44
+ require('./config/initializers/config');
17
45
 
18
- const server = master.setupServer('http'); // or 'https'
19
46
  master.start(server);
20
- master.serverSettings({ httpPort: 3000, hostname: '127.0.0.1', requestTimeout: 60000 });
47
+ ```
48
+
49
+ ```javascript
50
+ // config/initializers/config.js
51
+ const master = require('mastercontroller');
52
+ const cors = require('./cors.json');
53
+
54
+ // Initialize CORS (auto-registers with pipeline)
55
+ master.cors.init(cors);
56
+
57
+ // Initialize sessions (auto-registers with pipeline)
58
+ master.sessions.init();
59
+
60
+ // Auto-discover custom middleware from middleware/ folder
61
+ master.pipeline.discoverMiddleware('middleware');
62
+
63
+ // Configure server settings
64
+ master.serverSettings({
65
+ httpPort: 3000,
66
+ hostname: '127.0.0.1',
67
+ requestTimeout: 60000
68
+ });
69
+
70
+ // Register routes
71
+ master.startMVC('config');
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Middleware Pipeline
77
+
78
+ MasterController uses an ASP.NET Core-style middleware pipeline for request processing.
79
+
80
+ ### Core Methods
81
+
82
+ #### `master.pipeline.use(middleware)`
83
+ Add pass-through middleware that calls `next()` to continue the chain.
84
+
85
+ ```javascript
86
+ master.pipeline.use(async (ctx, next) => {
87
+ // Before request
88
+ console.log(`→ ${ctx.type.toUpperCase()} ${ctx.request.url}`);
89
+
90
+ await next(); // Continue to next middleware
91
+
92
+ // After response
93
+ console.log(`← ${ctx.response.statusCode}`);
94
+ });
95
+ ```
96
+
97
+ #### `master.pipeline.run(middleware)`
98
+ Add terminal middleware that ends the pipeline (does not call `next()`).
99
+
100
+ ```javascript
101
+ master.pipeline.run(async (ctx) => {
102
+ ctx.response.statusCode = 200;
103
+ ctx.response.end('Hello World');
104
+ });
105
+ ```
106
+
107
+ #### `master.pipeline.map(path, configure)`
108
+ Conditionally execute middleware only for matching paths.
109
+
110
+ ```javascript
111
+ // Apply authentication only to /api/* routes
112
+ master.pipeline.map('/api/*', (api) => {
113
+ api.use(async (ctx, next) => {
114
+ const token = ctx.request.headers['authorization'];
115
+ if (!token) {
116
+ ctx.response.statusCode = 401;
117
+ ctx.response.end('Unauthorized');
118
+ return;
119
+ }
120
+ ctx.state.user = await validateToken(token);
121
+ await next();
122
+ });
123
+
124
+ // Apply rate limiting to API
125
+ api.use(rateLimitMiddleware);
126
+ });
127
+ ```
128
+
129
+ #### `master.pipeline.useError(errorHandler)`
130
+ Add error handling middleware.
131
+
132
+ ```javascript
133
+ master.pipeline.useError(async (error, ctx, next) => {
134
+ console.error('Error:', error);
135
+
136
+ if (!ctx.response.headersSent) {
137
+ ctx.response.statusCode = 500;
138
+ ctx.response.end('Internal Server Error');
139
+ }
140
+ });
141
+ ```
142
+
143
+ #### `master.pipeline.discoverMiddleware(options)`
144
+ Auto-discover and load middleware from folders.
145
+
146
+ ```javascript
147
+ // Single folder
148
+ master.pipeline.discoverMiddleware('middleware');
149
+
150
+ // Multiple folders
151
+ master.pipeline.discoverMiddleware({
152
+ folders: ['middleware', 'app/middleware']
153
+ });
154
+ ```
155
+
156
+ ### Context Object
157
+
158
+ Middleware receives a context object:
159
+
160
+ ```javascript
161
+ {
162
+ request: req, // Node.js request object
163
+ response: res, // Node.js response object
164
+ requrl: parsedUrl, // Parsed URL with query
165
+ pathName: 'api/users', // Normalized path (lowercase)
166
+ type: 'get', // HTTP method (lowercase)
167
+ params: { // Route parameters + query + form data
168
+ query: {}, // Query string parameters
169
+ formData: {}, // POST body data
170
+ periodId: '123' // Route parameters (e.g., /period/:periodId)
171
+ },
172
+ state: {}, // Custom state to share between middleware
173
+ master: master, // Framework instance
174
+ isStatic: false // Is this a static file request?
175
+ }
176
+ ```
177
+
178
+ ### Custom Middleware Files
179
+
180
+ Create middleware files that are auto-discovered:
181
+
182
+ **Simple function export:**
183
+ ```javascript
184
+ // middleware/01-logger.js
185
+ module.exports = async (ctx, next) => {
186
+ const start = Date.now();
187
+ await next();
188
+ const duration = Date.now() - start;
189
+ console.log(`${ctx.type.toUpperCase()} ${ctx.request.url} - ${duration}ms`);
190
+ };
191
+ ```
192
+
193
+ **Object with register() method:**
194
+ ```javascript
195
+ // middleware/02-auth.js
196
+ module.exports = {
197
+ register: (master) => {
198
+ master.pipeline.map('/admin/*', (admin) => {
199
+ admin.use(async (ctx, next) => {
200
+ if (!ctx.state.user?.isAdmin) {
201
+ ctx.response.statusCode = 403;
202
+ ctx.response.end('Forbidden');
203
+ return;
204
+ }
205
+ await next();
206
+ });
207
+ });
208
+ }
209
+ };
210
+ ```
211
+
212
+ Files are loaded alphabetically (use `01-`, `02-` prefixes for ordering).
213
+
214
+ ---
215
+
216
+ ## Routing
217
+
218
+ ### Setup Routes
219
+
220
+ Create `config/routes.js`:
221
+
222
+ ```javascript
223
+ var master = require('mastercontroller');
224
+ var router = master.router.start();
225
+
226
+ // Basic route
227
+ router.route('/users', 'users#index', 'get');
228
+
229
+ // Route with parameters (preserves casing!)
230
+ router.route('/period/:periodId/items/:itemId', 'period#show', 'get');
231
+
232
+ // RESTful routes (generates 7 routes automatically)
233
+ router.resources('posts');
234
+ ```
235
+
236
+ ### API
237
+
238
+ #### `router.route(path, toPath, method, constraint)`
239
+ Register a single route.
240
+
241
+ - `path`: URL path (can include `:paramName`)
242
+ - `toPath`: Controller#action (e.g., `'users#index'`)
243
+ - `method`: HTTP method (`'get'`, `'post'`, `'put'`, `'delete'`, `'patch'`)
244
+ - `constraint`: Optional constraint function
245
+
246
+ **Parameter casing is preserved:**
247
+ ```javascript
248
+ router.route('/period/:periodId', 'period#show', 'get');
249
+ // In controller: obj.params.periodId (not periodid)
250
+ ```
251
+
252
+ #### `router.resources(routeName)`
253
+ Generate RESTful routes for a resource:
254
+
255
+ ```javascript
256
+ router.resources('posts');
257
+
258
+ // Generates:
259
+ // GET /posts -> posts#index
260
+ // GET /posts/new -> posts#new
261
+ // POST /posts -> posts#create
262
+ // GET /posts/:id -> posts#show
263
+ // GET /posts/:id/edit -> posts#edit
264
+ // PUT /posts/:id -> posts#update
265
+ // DELETE /posts/:id -> posts#destroy
266
+ ```
267
+
268
+ #### Route Constraints
269
+
270
+ Add custom logic to routes with constraints:
271
+
272
+ ```javascript
273
+ router.route('/admin', 'admin#index', 'get', function(requestObject) {
274
+ // Check authentication
275
+ if (!isAuthenticated(requestObject)) {
276
+ requestObject.response.statusCode = 401;
277
+ requestObject.response.end('Unauthorized');
278
+ return;
279
+ }
280
+
281
+ // Continue to controller
282
+ this.next();
283
+ });
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Controllers
289
+
290
+ ### Creating Controllers
291
+
292
+ Create controllers in `app/controllers/`:
293
+
294
+ ```javascript
295
+ // app/controllers/usersController.js
296
+ class UsersController {
297
+ constructor(requestObject) {
298
+ // Called for every request
299
+ this.requestObject = requestObject;
300
+ }
301
+
302
+ // Actions
303
+ index(obj) {
304
+ // obj = requestObject
305
+ this.render('index', {
306
+ users: ['Alice', 'Bob', 'Charlie']
307
+ });
308
+ }
309
+
310
+ show(obj) {
311
+ const userId = obj.params.id;
312
+ this.render('show', { userId });
313
+ }
314
+
315
+ create(obj) {
316
+ const userData = obj.params.formData;
317
+ // Save user...
318
+ this.redirect('/users');
319
+ }
320
+ }
321
+
322
+ module.exports = UsersController;
323
+ ```
324
+
325
+ ### Controller API
326
+
327
+ #### `this.render(view, data)`
328
+ Render a view with data.
329
+
330
+ ```javascript
331
+ this.render('index', {
332
+ title: 'Users',
333
+ users: userList
334
+ });
335
+ ```
336
+
337
+ Views are located at: `app/views/<controller>/<view>.html`
338
+
339
+ #### `this.redirect(path)`
340
+ Redirect to another path.
341
+
342
+ ```javascript
343
+ this.redirect('/users');
344
+ this.redirect('/users/123');
345
+ ```
346
+
347
+ #### `this.renderComponent(componentName, viewName, data)`
348
+ Render a view from a component.
349
+
350
+ ```javascript
351
+ this.renderComponent('mail', 'inbox', { emails });
352
+ ```
353
+
354
+ #### `this.json(data)`
355
+ Send JSON response.
356
+
357
+ ```javascript
358
+ this.json({
359
+ success: true,
360
+ users: userList
361
+ });
362
+ ```
363
+
364
+ #### Access Request Data
365
+
366
+ ```javascript
367
+ class UsersController {
368
+ show(obj) {
369
+ // Route parameters
370
+ const userId = obj.params.id;
371
+ const periodId = obj.params.periodId; // Casing preserved!
372
+
373
+ // Query string
374
+ const search = obj.params.query.search;
375
+
376
+ // Form data
377
+ const email = obj.params.formData.email;
378
+
379
+ // Files (multipart/form-data)
380
+ const avatar = obj.params.formData.files.avatar;
381
+
382
+ // Request method
383
+ const method = obj.type; // 'get', 'post', etc.
384
+
385
+ // Full request/response
386
+ const req = obj.request;
387
+ const res = obj.response;
388
+ }
389
+ }
390
+ ```
391
+
392
+ ### Before/After Action Filters
393
+
394
+ Execute code before or after specific actions:
395
+
396
+ ```javascript
397
+ class UsersController {
398
+ constructor(requestObject) {
399
+ // Run before 'edit' and 'update' actions
400
+ this.beforeAction(['edit', 'update'], function(obj) {
401
+ if (!isAuthenticated(obj)) {
402
+ obj.response.statusCode = 401;
403
+ obj.response.end('Unauthorized');
404
+ return;
405
+ }
406
+
407
+ // Continue to action
408
+ this.next();
409
+ });
410
+
411
+ // Run after 'create' and 'update' actions
412
+ this.afterAction(['create', 'update'], function(obj) {
413
+ console.log('User saved');
414
+ });
415
+ }
416
+
417
+ edit(obj) {
418
+ // beforeAction runs first
419
+ this.render('edit');
420
+ }
421
+
422
+ update(obj) {
423
+ // beforeAction runs first
424
+ // ... update user ...
425
+ // afterAction runs after
426
+ this.redirect('/users');
427
+ }
428
+ }
429
+ ```
430
+
431
+ **Methods:**
432
+ - `this.beforeAction(actionList, callback)` - Run before specific actions
433
+ - `this.afterAction(actionList, callback)` - Run after specific actions
434
+ - `this.next()` - Continue from beforeAction to action
435
+
436
+ ---
437
+
438
+ ## Views and Templates
439
+
440
+ ### View Structure
441
+
442
+ ```
443
+ app/
444
+ views/
445
+ layouts/
446
+ master.html # Main layout
447
+ users/
448
+ index.html # Users index view
449
+ show.html # Users show view
450
+ ```
451
+
452
+ ### Layout (master.html)
453
+
454
+ ```html
455
+ <!DOCTYPE html>
456
+ <html>
457
+ <head>
458
+ <title>{{title}}</title>
459
+ </head>
460
+ <body>
461
+ <header>
462
+ <h1>My App</h1>
463
+ </header>
464
+
465
+ <main>
466
+ {{body}} <!-- View content inserted here -->
467
+ </main>
468
+
469
+ <footer>
470
+ &copy; 2025
471
+ </footer>
472
+ </body>
473
+ </html>
474
+ ```
475
+
476
+ ### View (users/index.html)
477
+
478
+ ```html
479
+ <h2>{{title}}</h2>
480
+
481
+ <ul>
482
+ {{#each users}}
483
+ <li>{{this}}</li>
484
+ {{/each}}
485
+ </ul>
486
+ ```
487
+
488
+ ### Template Syntax
489
+
490
+ MasterController uses Handlebars-style templates:
491
+
492
+ ```html
493
+ <!-- Variables -->
494
+ {{name}}
495
+ {{user.email}}
496
+
497
+ <!-- HTML escaping (automatic) -->
498
+ {{description}}
499
+
500
+ <!-- Conditionals -->
501
+ {{#if isAdmin}}
502
+ <a href="/admin">Admin Panel</a>
503
+ {{/if}}
504
+
505
+ {{#unless isGuest}}
506
+ <p>Welcome back!</p>
507
+ {{/unless}}
508
+
509
+ <!-- Loops -->
510
+ {{#each items}}
511
+ <div>{{this.name}}</div>
512
+ {{/each}}
513
+
514
+ <!-- Partials -->
515
+ {{> header}}
516
+ ```
517
+
518
+ ---
519
+
520
+ ## View Pattern Hooks
521
+
522
+ Extend views with custom methods using the **view pattern hook system**.
523
+
524
+ ### `master.extendView(name, ViewClass)`
525
+
526
+ Add custom methods that are available in all views via `this` keyword.
527
+
528
+ ```javascript
529
+ // Create a view helper class
530
+ class MyViewHelpers {
531
+ // Format currency
532
+ currency(amount) {
533
+ return `$${amount.toFixed(2)}`;
534
+ }
535
+
536
+ // Format date
537
+ formatDate(date) {
538
+ return new Date(date).toLocaleDateString();
539
+ }
540
+
541
+ // Truncate text
542
+ truncate(text, length) {
543
+ if (text.length <= length) return text;
544
+ return text.substring(0, length) + '...';
545
+ }
546
+
547
+ // Check if user has permission
548
+ can(permission) {
549
+ // Access request context if needed
550
+ return this.__requestObject.user?.permissions.includes(permission);
551
+ }
552
+ }
553
+
554
+ // Register the helpers
555
+ master.extendView('helpers', MyViewHelpers);
556
+ ```
557
+
558
+ **Use in views:**
559
+
560
+ ```html
561
+ <p>Price: {{helpers.currency(product.price)}}</p>
562
+ <p>Posted: {{helpers.formatDate(post.createdAt)}}</p>
563
+ <p>{{helpers.truncate(post.body, 100)}}</p>
564
+
565
+ {{#if helpers.can('edit')}}
566
+ <button>Edit</button>
567
+ {{/if}}
568
+ ```
569
+
570
+ ### Built-in View Context
571
+
572
+ View methods have access to:
573
+ - `this.__requestObject` - Full request object
574
+ - `this.__response` - Response object
575
+ - `this.__request` - Request object
576
+ - `this.__namespace` - Controller namespace
577
+ - All methods from registered view extensions
578
+
579
+ **Example: Access request data in view helpers**
580
+
581
+ ```javascript
582
+ class AuthHelpers {
583
+ currentUser() {
584
+ return this.__requestObject.session?.user;
585
+ }
586
+
587
+ isAuthenticated() {
588
+ return !!this.currentUser();
589
+ }
590
+
591
+ csrf() {
592
+ // Generate CSRF token
593
+ return this.__requestObject.csrfToken;
594
+ }
595
+ }
596
+
597
+ master.extendView('auth', AuthHelpers);
598
+ ```
599
+
600
+ ```html
601
+ <!-- In views -->
602
+ {{#if auth.isAuthenticated}}
603
+ <p>Welcome, {{auth.currentUser.name}}!</p>
604
+ {{else}}
605
+ <a href="/login">Login</a>
606
+ {{/if}}
607
+
608
+ <form method="post">
609
+ <input type="hidden" name="_csrf" value="{{auth.csrf}}">
610
+ <!-- form fields -->
611
+ </form>
612
+ ```
613
+
614
+ ---
615
+
616
+ ## Dependency Injection
617
+
618
+ MasterController provides three DI lifetimes:
619
+
620
+ ### `master.addSingleton(name, Class)`
621
+ One instance for the entire application lifetime.
622
+
623
+ ```javascript
624
+ class DatabaseConnection {
625
+ constructor() {
626
+ this.connection = createDbConnection();
627
+ }
628
+
629
+ query(sql) {
630
+ return this.connection.query(sql);
631
+ }
632
+ }
633
+
634
+ master.addSingleton('db', DatabaseConnection);
635
+ ```
636
+
637
+ **Usage in controllers:**
638
+ ```javascript
639
+ class UsersController {
640
+ index(obj) {
641
+ const users = this.db.query('SELECT * FROM users');
642
+ this.render('index', { users });
643
+ }
644
+ }
645
+ ```
646
+
647
+ ### `master.addScoped(name, Class)`
648
+ One instance per request (scoped to request lifetime).
649
+
650
+ ```javascript
651
+ class RequestLogger {
652
+ constructor() {
653
+ this.logs = [];
654
+ }
655
+
656
+ log(message) {
657
+ this.logs.push({ message, timestamp: Date.now() });
658
+ }
659
+
660
+ flush() {
661
+ console.log('Request logs:', this.logs);
662
+ }
663
+ }
664
+
665
+ master.addScoped('logger', RequestLogger);
666
+ ```
667
+
668
+ **Usage:**
669
+ ```javascript
670
+ class UsersController {
671
+ index(obj) {
672
+ this.logger.log('Fetching users');
673
+ const users = getUsers();
674
+ this.logger.log('Users fetched');
675
+ this.logger.flush();
676
+ this.render('index', { users });
677
+ }
678
+ }
679
+ ```
680
+
681
+ ### `master.addTransient(name, Class)`
682
+ New instance every time it's accessed.
683
+
684
+ ```javascript
685
+ class EmailService {
686
+ constructor() {
687
+ this.id = Math.random();
688
+ }
689
+
690
+ send(to, subject, body) {
691
+ console.log(`Sending email from instance ${this.id}`);
692
+ // Send email...
693
+ }
694
+ }
695
+
696
+ master.addTransient('email', EmailService);
697
+ ```
698
+
699
+ **Usage:**
700
+ ```javascript
701
+ class UsersController {
702
+ create(obj) {
703
+ // New instance each access
704
+ this.email.send(obj.params.formData.email, 'Welcome!', 'Thanks for joining');
705
+ }
706
+ }
707
+ ```
708
+
709
+ ### Accessing Services
710
+
711
+ Services are automatically available on `this` in controllers:
712
+
713
+ ```javascript
714
+ class UsersController {
715
+ index(obj) {
716
+ // Access singleton
717
+ const users = this.db.query('SELECT * FROM users');
718
+
719
+ // Access scoped
720
+ this.logger.log('Query executed');
721
+
722
+ // Access transient
723
+ this.email.send(user.email, 'Subject', 'Body');
724
+
725
+ this.render('index', { users });
726
+ }
727
+ }
728
+ ```
729
+
730
+ ---
731
+
732
+ ## CORS
21
733
 
22
- // Or load from config/environments/env.<env>.json
23
- // master.serverSettings(master.env.server);
734
+ ### `master.cors.init(options)`
735
+
736
+ Initialize CORS (auto-registers with middleware pipeline).
737
+
738
+ ```javascript
739
+ master.cors.init({
740
+ origin: true, // Reflect request origin, or '*', or ['https://example.com']
741
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
742
+ allowedHeaders: true, // Reflect requested headers, or specify array
743
+ exposeHeaders: ['X-Total-Count'],
744
+ credentials: true,
745
+ maxAge: 86400
746
+ });
747
+ ```
748
+
749
+ **Options:**
750
+
751
+ - `origin`:
752
+ - `true` - Reflect request origin (or `*` if no credentials)
753
+ - `false` - Remove CORS headers
754
+ - `'*'` - Allow all origins
755
+ - `'https://example.com'` - Specific origin
756
+ - `['https://example.com', 'https://app.com']` - Array of origins
757
+ - `function(origin, req)` - Custom function returning `true`, `false`, or origin string
758
+
759
+ - `methods`: Array of allowed HTTP methods
760
+ - `allowedHeaders`: `true` (all), `false` (none), array, or string
761
+ - `exposeHeaders`: Array of headers to expose to browser
762
+ - `credentials`: `true` to allow credentials (cookies, auth headers)
763
+ - `maxAge`: Preflight cache duration in seconds
764
+
765
+ **CORS automatically:**
766
+ - Handles preflight OPTIONS requests
767
+ - Sets appropriate headers
768
+ - Varies by Origin for security
769
+
770
+ ### Advanced CORS
771
+
772
+ ```javascript
773
+ // Function-based origin validation
774
+ master.cors.init({
775
+ origin: (origin, req) => {
776
+ // Custom validation logic
777
+ if (req.headers['x-api-key'] === 'secret') {
778
+ return true; // Reflect origin
779
+ }
780
+ if (origin === 'https://trusted.com') {
781
+ return origin;
782
+ }
783
+ return false; // Deny
784
+ },
785
+ credentials: true
786
+ });
787
+ ```
788
+
789
+ ---
790
+
791
+ ## Sessions
792
+
793
+ ### `master.sessions.init(options)`
794
+
795
+ Initialize sessions (auto-registers with middleware pipeline).
796
+
797
+ ```javascript
798
+ master.sessions.init({
799
+ secret: 'your-secret-key',
800
+ maxAge: 900000, // 15 minutes
801
+ httpOnly: true,
802
+ secure: true, // HTTPS only
803
+ sameSite: true,
804
+ path: '/'
805
+ });
806
+ ```
807
+
808
+ ### Session API
809
+
810
+ #### `master.sessions.set(name, data, response, secret, options)`
811
+ Create a session.
812
+
813
+ ```javascript
814
+ class AuthController {
815
+ login(obj) {
816
+ const user = authenticateUser(obj.params.formData);
817
+
818
+ master.sessions.set('user', user, obj.response);
819
+
820
+ this.redirect('/dashboard');
821
+ }
822
+ }
823
+ ```
824
+
825
+ #### `master.sessions.get(name, request, secret)`
826
+ Retrieve session data.
827
+
828
+ ```javascript
829
+ class DashboardController {
830
+ index(obj) {
831
+ const user = master.sessions.get('user', obj.request);
832
+
833
+ if (!user) {
834
+ this.redirect('/login');
835
+ return;
836
+ }
837
+
838
+ this.render('dashboard', { user });
839
+ }
840
+ }
841
+ ```
842
+
843
+ #### `master.sessions.delete(name, response)`
844
+ Delete a session.
845
+
846
+ ```javascript
847
+ class AuthController {
848
+ logout(obj) {
849
+ master.sessions.delete('user', obj.response);
850
+ this.redirect('/');
851
+ }
852
+ }
853
+ ```
854
+
855
+ #### `master.sessions.reset()`
856
+ Clear all sessions (useful for testing).
857
+
858
+ ```javascript
859
+ master.sessions.reset();
860
+ ```
861
+
862
+ ### Cookie Methods
863
+
864
+ Direct cookie access:
865
+
866
+ #### `master.sessions.setCookie(name, value, response, options)`
867
+ ```javascript
868
+ master.sessions.setCookie('theme', 'dark', obj.response);
869
+ ```
870
+
871
+ #### `master.sessions.getCookie(name, request, secret)`
872
+ ```javascript
873
+ const theme = master.sessions.getCookie('theme', obj.request);
874
+ ```
875
+
876
+ #### `master.sessions.deleteCookie(name, response, options)`
877
+ ```javascript
878
+ master.sessions.deleteCookie('theme', obj.response);
879
+ ```
880
+
881
+ ---
882
+
883
+ ## Security
884
+
885
+ MasterController includes built-in security middleware.
886
+
887
+ ### Security Headers
888
+
889
+ ```javascript
890
+ const { pipelineSecurityHeaders } = require('./security/SecurityMiddleware');
891
+
892
+ master.pipeline.use(pipelineSecurityHeaders());
893
+ ```
894
+
895
+ **Applied headers:**
896
+ - `X-XSS-Protection: 1; mode=block`
897
+ - `X-Frame-Options: SAMEORIGIN`
898
+ - `X-Content-Type-Options: nosniff`
899
+ - `X-DNS-Prefetch-Control: off`
900
+ - `Permissions-Policy: geolocation=(), microphone=(), camera=()`
901
+ - `Referrer-Policy: strict-origin-when-cross-origin`
902
+ - `Strict-Transport-Security` (HTTPS production only)
903
+
904
+ ### Rate Limiting
905
+
906
+ ```javascript
907
+ const { pipelineRateLimit } = require('./security/SecurityMiddleware');
908
+
909
+ master.pipeline.use(pipelineRateLimit({
910
+ rateLimitWindow: 60000, // 1 minute
911
+ rateLimitMax: 100 // 100 requests per window
912
+ }));
913
+ ```
914
+
915
+ **Rate limit headers:**
916
+ - `X-RateLimit-Limit` - Maximum requests allowed
917
+ - `X-RateLimit-Remaining` - Requests remaining in window
918
+ - `X-RateLimit-Reset` - When the limit resets
919
+ - `Retry-After` - Seconds until retry (when blocked)
920
+
921
+ ### CSRF Protection
922
+
923
+ ```javascript
924
+ const { pipelineCsrf, generateCSRFToken } = require('./security/SecurityMiddleware');
925
+
926
+ // Apply to all routes
927
+ master.pipeline.use(pipelineCsrf());
928
+
929
+ // Or only to specific routes
930
+ master.pipeline.map('/admin/*', (admin) => {
931
+ admin.use(pipelineCsrf());
932
+ });
933
+ ```
934
+
935
+ **Generate token:**
936
+ ```javascript
937
+ const token = generateCSRFToken(sessionId);
938
+
939
+ // In controller
940
+ class FormController {
941
+ show(obj) {
942
+ const csrfToken = generateCSRFToken();
943
+ this.render('form', { csrfToken });
944
+ }
945
+ }
946
+ ```
947
+
948
+ **In forms:**
949
+ ```html
950
+ <form method="post">
951
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
952
+ <!-- or -->
953
+ <!-- Send as header: x-csrf-token -->
954
+ <!-- or -->
955
+ <!-- Send as query: ?_csrf=token -->
956
+ </form>
957
+ ```
958
+
959
+ ### Input Validation
960
+
961
+ ```javascript
962
+ const { validator } = require('./security/MasterValidator');
963
+
964
+ class UsersController {
965
+ create(obj) {
966
+ const email = obj.params.formData.email;
967
+
968
+ // Validate email
969
+ const emailCheck = validator.isEmail(email);
970
+ if (!emailCheck.valid) {
971
+ this.json({ error: emailCheck.error });
972
+ return;
973
+ }
974
+
975
+ // Continue with valid data
976
+ // ...
977
+ }
978
+ }
979
+ ```
980
+
981
+ **Available validators:**
982
+ - `validator.isEmail(email)`
983
+ - `validator.isURL(url)`
984
+ - `validator.isAlphanumeric(str)`
985
+ - `validator.isLength(str, min, max)`
986
+ - `detectPathTraversal(path)` - Detect `../` attacks
987
+ - `detectSQLInjection(input)` - Detect SQL injection
988
+ - `detectCommandInjection(input)` - Detect command injection
989
+
990
+ ---
991
+
992
+ ## Components
993
+
994
+ Components are self-contained modules with their own routes, controllers, and views.
995
+
996
+ ### Structure
24
997
 
25
- // Load your routes
26
- master.startMVC('app');
998
+ ```
999
+ components/
1000
+ user/
1001
+ config/
1002
+ initializers/
1003
+ config.js
1004
+ routes.js
1005
+ app/
1006
+ controllers/
1007
+ authController.js
1008
+ views/
1009
+ auth/
1010
+ login.html
1011
+ models/
1012
+ userContext.js
1013
+ ```
1014
+
1015
+ ### Register Component
1016
+
1017
+ ```javascript
1018
+ // In config/initializers/config.js
1019
+ master.component('components', 'user');
1020
+ master.component('components', 'mail');
1021
+ ```
1022
+
1023
+ ### Absolute Path Components
1024
+
1025
+ ```javascript
1026
+ // Load component from absolute path
1027
+ master.component('/var/www/shared-components', 'analytics');
27
1028
  ```
28
1029
 
29
- ### Routes
30
- Create `app/config/routes.js` and define routes with `master.router.start()` API.
1030
+ Components are isolated and can be reused across projects.
31
1031
 
32
- ### Controllers
33
- Place controllers under `app/controllers/*.js` and export methods matching your routes.
1032
+ ---
1033
+
1034
+ ## Timeout System
1035
+
1036
+ MasterController v2.0 includes a professional timeout system with per-request tracking (Rails/Django style).
1037
+
1038
+ ### Configuration
1039
+
1040
+ ```javascript
1041
+ // config/initializers/config.js
1042
+ master.timeout.init({
1043
+ globalTimeout: 120000, // 120 seconds (2 minutes) default
1044
+ enabled: true,
1045
+ onTimeout: (ctx, timeoutInfo) => {
1046
+ // Optional custom timeout handler
1047
+ console.log(`Request timeout: ${timeoutInfo.path}`);
1048
+ }
1049
+ });
1050
+
1051
+ // Register timeout middleware
1052
+ master.pipeline.use(master.timeout.middleware());
1053
+ ```
34
1054
 
35
- ### Views and Templates
36
- Views live under `app/views/<controller>/<action>.html` with a layout at `app/views/layouts/master.html`.
1055
+ ### Route-Specific Timeouts
1056
+
1057
+ ```javascript
1058
+ // Short timeout for API endpoints
1059
+ master.timeout.setRouteTimeout('/api/*', 30000); // 30 seconds
1060
+
1061
+ // Long timeout for reports
1062
+ master.timeout.setRouteTimeout('/admin/reports', 300000); // 5 minutes
1063
+
1064
+ // Very long timeout for batch operations
1065
+ master.timeout.setRouteTimeout('/batch/process', 600000); // 10 minutes
1066
+ ```
1067
+
1068
+ ### Timeout Statistics
1069
+
1070
+ ```javascript
1071
+ const stats = master.timeout.getStats();
1072
+
1073
+ console.log(stats);
1074
+ // {
1075
+ // enabled: true,
1076
+ // globalTimeout: 120000,
1077
+ // routeTimeouts: [
1078
+ // { pattern: '/api/*', timeout: 30000 }
1079
+ // ],
1080
+ // activeRequests: 5,
1081
+ // requests: [
1082
+ // {
1083
+ // requestId: 'req_1234567890_abc123',
1084
+ // path: 'api/users',
1085
+ // method: 'get',
1086
+ // timeout: 30000,
1087
+ // elapsed: 15000,
1088
+ // remaining: 15000
1089
+ // }
1090
+ // ]
1091
+ // }
1092
+ ```
1093
+
1094
+ ### Disable/Enable Timeouts
1095
+
1096
+ ```javascript
1097
+ // Disable for debugging
1098
+ master.timeout.disable();
1099
+
1100
+ // Re-enable
1101
+ master.timeout.enable();
1102
+ ```
37
1103
 
38
- ### CORS and Preflight
39
- `MasterCors` configures CORS headers. Preflight `OPTIONS` requests are short-circuited with 204.
1104
+ ---
40
1105
 
41
- ### HTTPS
42
- Use `setupServer('https', credentials)` or configure via environment TLS; see docs in `docs/` for multiple setups.
1106
+ ## Error Handling
43
1107
 
44
- ### Docs
45
- - `docs/server-setup-http.md`
46
- - `docs/server-setup-https-credentials.md`
47
- - `docs/server-setup-https-env-tls-sni.md`
48
- - `docs/server-setup-hostname-binding.md`
49
- - `docs/server-setup-nginx-reverse-proxy.md`
50
- - `docs/environment-tls-reference.md`
1108
+ MasterController v2.0 includes a professional error template system inspired by Rails and Django.
51
1109
 
52
- ### Production tips
53
- - Prefer a reverse proxy for TLS and serve Node on a high port.
54
- - If keeping TLS in Node, harden TLS and manage cert rotation.
1110
+ ### Error Renderer Configuration
1111
+
1112
+ ```javascript
1113
+ // config/initializers/config.js
1114
+ master.errorRenderer.init({
1115
+ templateDir: 'public/errors', // Error templates directory
1116
+ environment: master.environmentType,
1117
+ showStackTrace: master.environmentType === 'development' // Dev only
1118
+ });
1119
+ ```
1120
+
1121
+ ### Using Error Renderer
1122
+
1123
+ ```javascript
1124
+ // In middleware
1125
+ master.pipeline.use(async (ctx, next) => {
1126
+ if (!isAuthenticated(ctx)) {
1127
+ master.errorRenderer.send(ctx, 401, {
1128
+ message: 'Please log in to access this resource',
1129
+ suggestions: [
1130
+ 'Sign in with your credentials',
1131
+ 'Request a password reset if forgotten'
1132
+ ]
1133
+ });
1134
+ return;
1135
+ }
1136
+ await next();
1137
+ });
1138
+
1139
+ // In controllers
1140
+ class UsersController {
1141
+ async show(obj) {
1142
+ const userId = obj.params.userId;
1143
+ const user = await this.db.query('SELECT * FROM users WHERE id = ?', [userId]);
1144
+
1145
+ if (!user) {
1146
+ master.errorRenderer.send(obj, 404, {
1147
+ message: `User #${userId} not found`,
1148
+ suggestions: [
1149
+ 'Check the user ID',
1150
+ 'Browse all users',
1151
+ 'Search for the user by name'
1152
+ ]
1153
+ });
1154
+ return;
1155
+ }
1156
+
1157
+ this.render('show', { user });
1158
+ }
1159
+ }
1160
+ ```
1161
+
1162
+ ### Error Templates
1163
+
1164
+ Create templates in `public/errors/`:
1165
+
1166
+ ```
1167
+ public/errors/
1168
+ ├── 400.html # Bad Request
1169
+ ├── 401.html # Unauthorized
1170
+ ├── 403.html # Forbidden
1171
+ ├── 404.html # Not Found
1172
+ ├── 405.html # Method Not Allowed
1173
+ ├── 422.html # Unprocessable Entity
1174
+ ├── 429.html # Too Many Requests
1175
+ ├── 500.html # Internal Server Error
1176
+ ├── 502.html # Bad Gateway
1177
+ ├── 503.html # Service Unavailable
1178
+ └── 504.html # Gateway Timeout
1179
+ ```
1180
+
1181
+ **Template Variables:**
1182
+
1183
+ ```html
1184
+ <!DOCTYPE html>
1185
+ <html>
1186
+ <head>
1187
+ <title>{{title}} ({{statusCode}})</title>
1188
+ </head>
1189
+ <body>
1190
+ <h1>{{statusCode}} - {{title}}</h1>
1191
+ <p>{{message}}</p>
1192
+
1193
+ <!-- Conditionals (dev only) -->
1194
+ {{#if showStackTrace}}
1195
+ <pre>{{stack}}</pre>
1196
+ {{/if}}
1197
+
1198
+ <!-- Loops -->
1199
+ {{#each suggestions}}
1200
+ <li>{{this}}</li>
1201
+ {{/each}}
1202
+ </body>
1203
+ </html>
1204
+ ```
1205
+
1206
+ **Available Variables:**
1207
+ - `{{statusCode}}` - HTTP status code (404, 500, etc.)
1208
+ - `{{title}}` - Error title
1209
+ - `{{message}}` - Error message
1210
+ - `{{code}}` - Error code
1211
+ - `{{stack}}` - Stack trace (development only)
1212
+ - `{{suggestions}}` - Array of suggestions
1213
+ - `{{environment}}` - Current environment
1214
+
1215
+ ### Custom Error Handlers
1216
+
1217
+ ```javascript
1218
+ // Register custom handler for specific status code
1219
+ master.errorRenderer.registerHandler(503, (ctx, errorData) => {
1220
+ return `
1221
+ <!DOCTYPE html>
1222
+ <html>
1223
+ <body>
1224
+ <h1>Maintenance Mode</h1>
1225
+ <p>We'll be back soon! Expected completion: 2:00 PM EST</p>
1226
+ </body>
1227
+ </html>
1228
+ `;
1229
+ });
1230
+ ```
1231
+
1232
+ ### Content Negotiation
1233
+
1234
+ The error renderer automatically detects API requests and returns JSON:
1235
+
1236
+ ```javascript
1237
+ // Browser request → HTML
1238
+ GET /users/999
1239
+ Accept: text/html
1240
+ → Returns beautiful HTML error page
1241
+
1242
+ // API request → JSON
1243
+ GET /api/users/999
1244
+ Accept: application/json
1245
+ → Returns JSON error response
1246
+ {
1247
+ "error": "Page Not Found",
1248
+ "statusCode": 404,
1249
+ "code": "MC_HTTP_ERROR",
1250
+ "message": "The user you're looking for doesn't exist."
1251
+ }
1252
+ ```
1253
+
1254
+ ### Global Error Handler (Pipeline)
1255
+
1256
+ ```javascript
1257
+ master.pipeline.useError(async (error, ctx, next) => {
1258
+ console.error('Pipeline error:', error);
1259
+
1260
+ // Use error renderer for HTTP errors
1261
+ master.errorRenderer.send(ctx, 500, {
1262
+ message: error.message,
1263
+ code: error.code,
1264
+ stack: error.stack
1265
+ });
1266
+ });
1267
+ ```
1268
+
1269
+ ### Controller Error Handling
1270
+
1271
+ ```javascript
1272
+ class UsersController {
1273
+ async index(obj) {
1274
+ try {
1275
+ const users = await this.db.query('SELECT * FROM users');
1276
+ this.render('index', { users });
1277
+ } catch (error) {
1278
+ console.error('Database error:', error);
1279
+
1280
+ master.errorRenderer.send(obj, 500, {
1281
+ message: 'Failed to load users',
1282
+ code: 'DB_ERROR',
1283
+ stack: error.stack
1284
+ });
1285
+ }
1286
+ }
1287
+ }
1288
+ ```
1289
+
1290
+ ### Logging
1291
+
1292
+ ```javascript
1293
+ const { logger } = require('./error/MasterErrorLogger');
1294
+
1295
+ // In controllers or middleware
1296
+ logger.info({
1297
+ code: 'USER_LOGIN',
1298
+ message: 'User logged in',
1299
+ userId: user.id
1300
+ });
1301
+
1302
+ logger.warn({
1303
+ code: 'INVALID_INPUT',
1304
+ message: 'Invalid email format',
1305
+ email: input
1306
+ });
1307
+
1308
+ logger.error({
1309
+ code: 'DB_ERROR',
1310
+ message: 'Database query failed',
1311
+ error: error.message,
1312
+ stack: error.stack
1313
+ });
1314
+ ```
1315
+
1316
+ ---
1317
+
1318
+ ## HTTPS Setup
1319
+
1320
+ ### Basic HTTPS
1321
+
1322
+ ```javascript
1323
+ const fs = require('fs');
1324
+
1325
+ const credentials = {
1326
+ key: fs.readFileSync('path/to/key.pem'),
1327
+ cert: fs.readFileSync('path/to/cert.pem')
1328
+ };
1329
+
1330
+ const server = master.setupServer('https', credentials);
1331
+ ```
1332
+
1333
+ ### Environment-based TLS
1334
+
1335
+ Configure TLS in `config/environments/env.production.json`:
1336
+
1337
+ ```json
1338
+ {
1339
+ "server": {
1340
+ "httpPort": 443,
1341
+ "tls": {
1342
+ "default": {
1343
+ "keyPath": "/path/to/default.key",
1344
+ "certPath": "/path/to/default.crt"
1345
+ },
1346
+ "sni": {
1347
+ "example.com": {
1348
+ "keyPath": "/path/to/example.key",
1349
+ "certPath": "/path/to/example.crt"
1350
+ },
1351
+ "app.example.com": {
1352
+ "keyPath": "/path/to/app.key",
1353
+ "certPath": "/path/to/app.crt"
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ ```
1360
+
1361
+ ```javascript
1362
+ const server = master.setupServer('https');
1363
+ master.serverSettings(master.env.server);
1364
+ ```
1365
+
1366
+ ### HTTP to HTTPS Redirect
1367
+
1368
+ ```javascript
1369
+ // Start HTTPS server on 443
1370
+ const httpsServer = master.setupServer('https');
1371
+ master.start(httpsServer);
1372
+ master.serverSettings({ httpPort: 443 });
1373
+
1374
+ // Start redirect server on 80
1375
+ const redirectServer = master.startHttpToHttpsRedirect(80);
1376
+ ```
1377
+
1378
+ ### HSTS (HTTP Strict Transport Security)
1379
+
1380
+ ```javascript
1381
+ master.enableHSTS(); // In production HTTPS
1382
+ ```
1383
+
1384
+ ---
1385
+
1386
+ ## API Reference
1387
+
1388
+ ### Master Instance
1389
+
1390
+ - `master.root` - Project root directory
1391
+ - `master.environmentType` - Environment ('development', 'production', etc.)
1392
+ - `master.env` - Environment config from `config/environments/env.<env>.json`
1393
+ - `master.serverProtocol` - 'http' or 'https'
1394
+
1395
+ ### Setup Methods
1396
+
1397
+ - `master.setupServer(type, credentials)` - Create HTTP or HTTPS server
1398
+ - `master.start(server)` - Start the server
1399
+ - `master.serverSettings(options)` - Configure server (port, host, timeout)
1400
+ - `master.startMVC(folder)` - Load routes from folder
1401
+ - `master.component(folder, name)` - Load component
1402
+ - `master.enableHSTS()` - Enable HSTS for HTTPS
1403
+ - `master.startHttpToHttpsRedirect(port, host)` - Create redirect server
1404
+
1405
+ ### Middleware Pipeline
1406
+
1407
+ - `master.pipeline.use(middleware)` - Add middleware
1408
+ - `master.pipeline.run(middleware)` - Add terminal middleware
1409
+ - `master.pipeline.map(path, configure)` - Conditional middleware
1410
+ - `master.pipeline.useError(handler)` - Add error handler
1411
+ - `master.pipeline.discoverMiddleware(options)` - Auto-discover middleware
1412
+ - `master.pipeline.execute(context)` - Execute pipeline (internal)
1413
+ - `master.pipeline.clear()` - Clear all middleware (testing)
1414
+ - `master.pipeline.inspect()` - Inspect middleware stack (debugging)
1415
+
1416
+ ### Dependency Injection
1417
+
1418
+ - `master.addSingleton(name, Class)` - Register singleton service
1419
+ - `master.addScoped(name, Class)` - Register scoped service (per request)
1420
+ - `master.addTransient(name, Class)` - Register transient service (per access)
1421
+
1422
+ ### Extensions
1423
+
1424
+ - `master.extend(name, Class)` - Extend master with new functionality
1425
+ - `master.extendController(Class)` - Extend all controllers
1426
+ - `master.extendView(name, Class)` - Extend all views
1427
+
1428
+ ### Router
1429
+
1430
+ - `master.router.start()` - Get router API
1431
+ - `router.route(path, toPath, method, constraint)` - Register route
1432
+ - `router.resources(name)` - Register RESTful routes
1433
+ - `master.router.setup(options)` - Setup route namespace (internal)
1434
+ - `master.router.load(requestObject)` - Load and match routes (internal)
1435
+ - `master.router.currentRoute` - Current route info
1436
+ - `master.router.findMimeType(ext)` - Get MIME type for extension
1437
+ - `master.router.addMimeList(mimes)` - Add MIME type mappings
1438
+
1439
+ ### CORS
1440
+
1441
+ - `master.cors.init(options)` - Initialize CORS
1442
+ - `master.cors.load(params)` - Apply CORS headers (internal)
1443
+ - `master.cors.middleware()` - Get pipeline middleware
1444
+
1445
+ ### Sessions
1446
+
1447
+ - `master.sessions.init(options)` - Initialize sessions
1448
+ - `master.sessions.set(name, data, response, secret, options)` - Create session
1449
+ - `master.sessions.get(name, request, secret)` - Get session data
1450
+ - `master.sessions.delete(name, response)` - Delete session
1451
+ - `master.sessions.reset()` - Clear all sessions
1452
+ - `master.sessions.setCookie(name, value, response, options)` - Set cookie
1453
+ - `master.sessions.getCookie(name, request, secret)` - Get cookie
1454
+ - `master.sessions.deleteCookie(name, response, options)` - Delete cookie
1455
+ - `master.sessions.createSessionID()` - Generate random session ID
1456
+ - `master.sessions.middleware()` - Get pipeline middleware
1457
+
1458
+ ### Request
1459
+
1460
+ - `master.request.getRequestParam(request, response)` - Parse request body (internal)
1461
+
1462
+ ### HTML/Template
1463
+
1464
+ - `master.html.init(path)` - Set views path
1465
+ - `master.template.init(layout)` - Set layout template
1466
+
1467
+ ### Tools
1468
+
1469
+ - `master.tools.encrypt(data, secret)` - Encrypt data
1470
+ - `master.tools.decrypt(data, secret)` - Decrypt data
1471
+ - `master.tools.combineObjects(target, source)` - Merge objects
1472
+ - `master.tools.makeWordId(length)` - Generate random ID
1473
+
1474
+ ---
1475
+
1476
+ ## Production Tips
1477
+
1478
+ 1. **Use a reverse proxy** (nginx, Apache) for TLS termination
1479
+ 2. **Run Node.js on a high port** (3000, 8080) behind the proxy
1480
+ 3. **Enable HSTS** for HTTPS: `master.enableHSTS()`
1481
+ 4. **Use environment variables** for secrets and config
1482
+ 5. **Enable rate limiting** for public APIs
1483
+ 6. **Enable CSRF protection** for forms
1484
+ 7. **Use security headers** middleware
1485
+ 8. **Monitor logs** with `logger` module
1486
+ 9. **Use process manager** (PM2, systemd) for restarts
1487
+ 10. **Keep dependencies updated**
1488
+
1489
+ ---
1490
+
1491
+ ## Documentation
1492
+
1493
+ Detailed guides:
1494
+
1495
+ - [HTTP Server Setup](docs/server-setup-http.md)
1496
+ - [HTTPS with Credentials](docs/server-setup-https-credentials.md)
1497
+ - [HTTPS with Environment TLS & SNI](docs/server-setup-https-env-tls-sni.md)
1498
+ - [Hostname Binding](docs/server-setup-hostname-binding.md)
1499
+ - [Nginx Reverse Proxy](docs/server-setup-nginx-reverse-proxy.md)
1500
+ - [Environment TLS Reference](docs/environment-tls-reference.md)
1501
+
1502
+ ---
1503
+
1504
+ ## License
55
1505
 
56
- ### License
57
1506
  MIT
58
1507
 
1508
+ ---
1509
+
1510
+ ## Contributing
1511
+
1512
+ Contributions are welcome! Please open an issue or submit a pull request.
1513
+
1514
+ ---
1515
+
1516
+ ## Support
1517
+
1518
+ For issues and questions, please visit: [GitHub Issues](https://github.com/alexanderrich/MasterController/issues)