@reldens/cms 0.47.0 → 0.49.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
@@ -59,6 +59,7 @@ A powerful, flexible Content Management System built with Node.js, featuring an
59
59
  - **Modular service architecture** with specialized classes for better maintainability
60
60
  - **Event-driven system** with hooks for customization
61
61
  - **Extensible authentication** (database users or custom callbacks)
62
+ - **Password encryption** with PBKDF2 (100k iterations, SHA-512) - automatic on save
62
63
  - **File security** with path validation and dangerous key filtering
63
64
  - **Internationalization support** with translation files
64
65
 
@@ -150,1502 +151,125 @@ cms.start();
150
151
 
151
152
  ## Configuration
152
153
 
153
- ### Environment Variables
154
- ```env
155
- RELDENS_APP_HOST=http://localhost
156
- RELDENS_APP_PORT=8080
157
- RELDENS_ADMIN_ROUTE_PATH=/admin
158
- RELDENS_ADMIN_SECRET=your-secret-key
159
-
160
- RELDENS_DB_CLIENT=mysql
161
- RELDENS_DB_HOST=localhost
162
- RELDENS_DB_PORT=3306
163
- RELDENS_DB_NAME=cms_db
164
- RELDENS_DB_USER=username
165
- RELDENS_DB_PASSWORD=password
166
- RELDENS_STORAGE_DRIVER=prisma
167
-
168
- RELDENS_DEFAULT_DOMAIN=example.com
169
- RELDENS_DOMAIN_MAPPING={"dev.example.com":"development"}
170
- RELDENS_SITE_KEY_MAPPING={"example.com":"main"}
171
- ```
172
-
173
- ### Template Reloading Configuration
174
- Configure template reloading for development environments:
175
-
176
- ```javascript
177
- const cms = new Manager({
178
- // Development: reload templates on every request when changes detected
179
- reloadTime: -1,
180
-
181
- // Production: disable template reloading (default)
182
- reloadTime: 0,
183
-
184
- // Interval-based: reload every 5 seconds when changes detected
185
- reloadTime: 5000
186
- });
187
- ```
188
-
189
- **Template Reloading Options:**
190
- - **`reloadTime: 0`** (default) - Template reloading disabled. Templates load once at startup.
191
- - **`reloadTime: -1`** - Reload templates on every request when file changes are detected. Best for active development.
192
- - **`reloadTime: > 0`** - Check for template changes at specified interval (milliseconds) and reload when needed. Good for development with lower overhead.
154
+ **Environment variables:** Database, server, admin, multi-domain settings
155
+ **Template reloading:** Configure for development (-1) or production (0)
156
+ **Entity configuration:** Custom entity properties, validation, and relationships
157
+ **Manager options:** Authentication, caching, domain mapping
193
158
 
194
- **How it works:**
195
- - Tracks file modification times for admin and frontend templates
196
- - Only reloads templates that have actually changed
197
- - Automatically updates admin contents and frontend template cache
198
- - Works with both admin panel templates and frontend templates/partials
199
- - Zero performance impact when disabled (`reloadTime: 0`)
200
-
201
- ### Custom Entity Configuration
202
- ```javascript
203
- const entityConfig = {
204
- articles: {
205
- listProperties: ['title', 'status', 'created_at'],
206
- showProperties: ['title', 'content', 'author', 'status'],
207
- editProperties: ['title', 'content', 'author_id', 'status'],
208
- filterProperties: ['status', 'author_id'],
209
- titleProperty: 'title',
210
- parentItemLabel: 'Content',
211
- properties: {
212
- title: { type: 'string', isRequired: true },
213
- content: { type: 'text' },
214
- author_id: { type: 'reference', reference: 'users' },
215
- featured_image: {
216
- type: 'string',
217
- isUpload: true,
218
- allowedTypes: 'image',
219
- bucket: 'uploads'
220
- }
221
- }
222
- }
223
- };
224
-
225
- const cms = new Manager({
226
- entitiesConfig: entityConfig
227
- });
228
- ```
229
-
230
- ## Advanced Installation Features
159
+ **Full documentation:** See `.claude/configuration-guide.md`
231
160
 
232
- ### Subprocess Installation Handling
233
- The installer now supports complex operations through subprocess management:
161
+ ## CLI Commands
234
162
 
235
- ```javascript
236
- const { Installer } = require('@reldens/cms');
237
-
238
- const installer = new Installer({
239
- projectRoot: process.cwd(),
240
- subprocessMaxAttempts: 1800, // 3 minutes timeout
241
- postInstallCallback: async (props) => {
242
- // Custom initialization after installation
243
- console.log('Entities loaded:', Object.keys(props.loadedEntities.rawRegisteredEntities));
244
- return true;
245
- }
246
- });
247
-
248
- // The installer automatically handles:
249
- // - Package dependency checking and installation
250
- // - Database schema creation via subprocess
251
- // - Prisma client generation with progress tracking
252
- // - Entity generation with validation
253
- // - Environment file creation
254
- // - Directory structure setup
255
- ```
256
-
257
- ### Enhanced Manager Initialization
258
- The Manager class now provides comprehensive service initialization:
259
-
260
- ```javascript
261
- const cms = new Manager({
262
- // Server validation - automatically validates provided instances
263
- app: customExpressApp, // Optional: provide your own Express app
264
- appServer: customAppServer, // Optional: provide your own HTTP server
265
- dataServer: customDataServer, // Optional: provide your own database driver
266
- adminManager: customAdmin, // Optional: provide your own admin manager
267
- frontend: customFrontend, // Optional: provide your own frontend handler
268
-
269
- // Configuration validation
270
- adminRoleId: 99, // Admin role ID for authentication
271
- authenticationMethod: 'db-users', // or 'custom'
272
- authenticationCallback: async (email, password, roleId) => {
273
- // Custom authentication logic
274
- return await yourAuthService.validate(email, password, roleId);
275
- },
276
-
277
- // Performance configuration
278
- cache: true, // Enable caching
279
- reloadTime: -1, // Template reloading for development
280
-
281
- // Multi-domain configuration
282
- defaultDomain: 'example.com',
283
- domainMapping: {'dev.example.com': 'development'},
284
- siteKeyMapping: {'example.com': 'main'}
285
- });
286
-
287
- // Manager automatically:
288
- // - Validates all provided instances
289
- // - Initializes missing services
290
- // - Sets up entity access control
291
- // - Generates admin entities
292
- // - Configures template reloading
293
- ```
163
+ ### Start CMS
294
164
 
295
- ### Development Mode Detection
296
-
297
- The CMS automatically detects development environments based on domain patterns. Domain mapping keys are **no longer automatically treated as development domains**.
298
-
299
- **Default Development Patterns:**
300
- ```javascript
301
- const patterns = [
302
- 'localhost',
303
- '127.0.0.1',
304
- '.local', // Domains ending with .local
305
- '.test', // Domains ending with .test
306
- '.dev', // Domains ending with .dev
307
- '.acc', // Domains ending with .acc
308
- '.staging', // Domains ending with .staging
309
- 'local.', // Domains starting with local.
310
- 'test.', // Domains starting with test.
311
- 'dev.', // Domains starting with dev.
312
- 'acc.', // Domains starting with acc.
313
- 'staging.' // Domains starting with staging.
314
- ];
315
- ```
316
-
317
- **Override Development Patterns:**
318
- ```javascript
319
- const cms = new Manager({
320
- // Only these patterns will trigger development mode
321
- developmentPatterns: [
322
- 'localhost',
323
- '127.0.0.1',
324
- '.local'
325
- ],
326
- domainMapping: {
327
- // These are just aliases - NOT automatically development
328
- 'www.example.com': 'example.com',
329
- 'new.example.com': 'example.com'
330
- }
331
- });
165
+ ```bash
166
+ npx reldens-cms
332
167
  ```
333
168
 
334
- **Important Notes:**
335
- - The bug in pattern matching where domains with common substrings (e.g., "reldens" in both "acc.reldens.com" and "reldens.new") incorrectly triggered development mode has been fixed.
336
- - Domain patterns now only match at the start or end of domains, not arbitrary positions.
337
- - Override `developmentPatterns` in production to prevent staging/acc domains from enabling development mode.
338
-
339
- ### Security Configuration
340
-
341
- Configure Content Security Policy and security headers through the app server config:
169
+ Runs the installer if not installed, otherwise starts the CMS server.
342
170
 
343
- #### External Domains for CSP
171
+ ### Generate Entities
344
172
 
345
- When configuring external domains for CSP directives, keys can be in either kebab-case or camelCase format:
346
-
347
- ```javascript
348
- const cms = new Manager({
349
- appServerConfig: {
350
- developmentExternalDomains: {
351
- // Both formats work - choose whichever you prefer
352
- 'scriptSrc': ['https://cdn.example.com'], // camelCase
353
- 'script-src': ['https://analytics.example.com'], // kebab-case (auto-converted)
354
- 'styleSrc': ['https://fonts.googleapis.com'], // camelCase
355
- 'font-src': ['https://fonts.gstatic.com'] // kebab-case (auto-converted)
356
- }
357
- }
358
- });
173
+ ```bash
174
+ npx reldens-cms-generate-entities
175
+ npx reldens-cms-generate-entities --override
359
176
  ```
360
177
 
361
- The system automatically:
362
- - Converts kebab-case keys to camelCase (e.g., `'script-src'` → `scriptSrc`)
363
- - Adds domains to both the base directive and the `-elem` variant (e.g., `scriptSrc` and `scriptSrcElem`)
364
-
365
- #### CSP Directive Merging vs Override
366
-
367
- By default, custom CSP directives are **merged** with security defaults:
178
+ Generate entity classes from database schema.
368
179
 
369
- ```javascript
370
- const cms = new Manager({
371
- appServerConfig: {
372
- helmetConfig: {
373
- contentSecurityPolicy: {
374
- // Default: merge with base directives
375
- directives: {
376
- scriptSrc: ['https://cdn.example.com']
377
- }
378
- }
379
- }
380
- }
381
- });
180
+ ### Update User Password
382
181
 
383
- // Result: default scriptSrc values + 'https://cdn.example.com'
384
- ```
385
-
386
- **Default Base Directives:**
387
- ```javascript
388
- {
389
- defaultSrc: ["'self'"],
390
- scriptSrc: ["'self'"],
391
- scriptSrcElem: ["'self'"],
392
- styleSrc: ["'self'", "'unsafe-inline'"],
393
- styleSrcElem: ["'self'", "'unsafe-inline'"],
394
- imgSrc: ["'self'", "data:", "https:"],
395
- fontSrc: ["'self'"],
396
- connectSrc: ["'self'"],
397
- frameAncestors: ["'none'"],
398
- baseUri: ["'self'"],
399
- formAction: ["'self'"]
400
- }
182
+ ```bash
183
+ npx reldens-cms-update-password --email=admin@example.com
184
+ npx reldens-cms-update-password --username=admin
401
185
  ```
402
186
 
403
- To **completely replace** the default directives, use `overrideDirectives: true`:
187
+ Securely update user passwords via CLI. Password will be prompted if not provided.
404
188
 
405
- ```javascript
406
- const cms = new Manager({
407
- appServerConfig: {
408
- helmetConfig: {
409
- contentSecurityPolicy: {
410
- overrideDirectives: true, // Replace defaults entirely
411
- directives: {
412
- defaultSrc: ["'self'"],
413
- scriptSrc: ["'self'", "https://trusted-cdn.com"],
414
- styleSrc: ["'self'", "'unsafe-inline'"],
415
- imgSrc: ["'self'", "data:", "https:"],
416
- fontSrc: ["'self'"],
417
- connectSrc: ["'self'"],
418
- frameAncestors: ["'none'"],
419
- baseUri: ["'self'"],
420
- formAction: ["'self'"]
421
- }
422
- }
423
- }
424
- }
425
- });
426
- ```
189
+ ## Password Management
427
190
 
428
- #### Additional Helmet Security Headers
191
+ **Automatic encryption:** PBKDF2 with 100k iterations, SHA-512
192
+ **CLI password update:** `npx reldens-cms-update-password --email=user@example.com`
193
+ **Event-driven:** Encrypts passwords automatically on save via admin panel
194
+ **Storage format:** salt:hash (192 characters total)
195
+ **Enabled by default:** Set `enablePasswordEncryption: false` to disable
429
196
 
430
- Configure other security headers through `helmetConfig`:
197
+ **Full documentation:** See `.claude/password-management-guide.md`
431
198
 
432
- ```javascript
433
- const cms = new Manager({
434
- appServerConfig: {
435
- helmetConfig: {
436
- // HTTP Strict Transport Security
437
- hsts: {
438
- maxAge: 31536000, // 1 year in seconds
439
- includeSubDomains: true,
440
- preload: true
441
- },
442
- // Cross-Origin-Opener-Policy
443
- crossOriginOpenerPolicy: {
444
- policy: "same-origin"
445
- },
446
- // Cross-Origin-Resource-Policy
447
- crossOriginResourcePolicy: {
448
- policy: "same-origin"
449
- },
450
- // Cross-Origin-Embedder-Policy
451
- crossOriginEmbedderPolicy: {
452
- policy: "require-corp"
453
- }
454
- }
455
- }
456
- });
457
- ```
199
+ ## Advanced Installation Features
458
200
 
459
- **Note:** In development mode, CSP and HSTS are automatically disabled to ease development. Security headers are only enforced when the CMS is not in development mode.
201
+ **Subprocess handling** for complex operations with progress tracking
202
+ **Enhanced Manager** with service validation and automatic initialization
203
+ **Development mode detection** with configurable patterns
204
+ **Security configuration** with CSP, Helmet headers, and external domain support
460
205
 
461
- **Trusted Types:** To enable Trusted Types for enhanced XSS protection, add to CSP directives:
462
- ```javascript
463
- requireTrustedTypesFor: ["'script'"]
464
- ```
465
- However, this requires updating all JavaScript code to use the Trusted Types API.
206
+ **Full documentation:** See `.claude/installation-guide.md`
466
207
 
467
208
  ## Dynamic Forms System
468
209
 
469
- ### Basic Form Usage
470
- Create forms in your templates using the `<cmsForm>` tag:
471
-
472
- ```html
473
- <!-- Render all form fields -->
474
- <cmsForm key="contactForm"/>
475
-
476
- <!-- Render specific fields only -->
477
- <cmsForm key="contactForm" fields="name,email,subject,message"/>
478
-
479
- <!-- Custom form attributes -->
480
- <cmsForm key="newsletterSignup"
481
- fields="email,name"
482
- submitButtonText="Subscribe Now"
483
- cssClass="newsletter-form"
484
- successRedirect="/thank-you"
485
- errorRedirect="/contact-error"/>
486
- ```
487
-
488
- ### Form Configuration in Database
489
- Forms are configured in the `cms_forms` table via the admin panel:
490
-
491
- ```sql
492
- -- Example form configuration
493
- INSERT INTO cms_forms (form_key, fields_schema, enabled) VALUES
494
- ('contactForm', '[
495
- {
496
- "name": "name",
497
- "type": "text",
498
- "label": "Full Name",
499
- "required": true,
500
- "maxLength": 100,
501
- "placeholder": "Enter your full name"
502
- },
503
- {
504
- "name": "email",
505
- "type": "email",
506
- "label": "Email Address",
507
- "required": true,
508
- "placeholder": "your@email.com"
509
- },
510
- {
511
- "name": "subject",
512
- "type": "select",
513
- "label": "Subject",
514
- "required": true,
515
- "options": [
516
- {"value": "general", "label": "General Inquiry"},
517
- {"value": "support", "label": "Technical Support"},
518
- {"value": "sales", "label": "Sales Question"}
519
- ]
520
- },
521
- {
522
- "name": "message",
523
- "type": "textarea",
524
- "label": "Message",
525
- "required": true,
526
- "maxLength": 1000,
527
- "placeholder": "Enter your message here..."
528
- }
529
- ]', 1);
530
- ```
531
-
532
- ### Supported Field Types
533
- The forms system supports various field types with validation:
534
-
535
- - **text** - Basic text input with maxLength, pattern validation
536
- - **email** - Email input with built-in email validation
537
- - **number** - Numeric input with min/max validation
538
- - **textarea** - Multi-line text with maxLength
539
- - **select** - Dropdown with options array
540
- - **password** - Password input (masked)
541
- - **tel** - Phone number input
542
- - **url** - URL input with validation
543
- - **date** - Date picker input
544
-
545
- ### Field Schema Properties
546
- Each field in the `fields_schema` JSON supports:
547
-
548
- ```json
549
- {
550
- "name": "fieldName", // Required: Field identifier
551
- "type": "text", // Required: Field type
552
- "label": "Field Label", // Display label
553
- "required": true, // Validation: required field
554
- "placeholder": "Enter text...", // Input placeholder
555
- "helpText": "Additional help", // Help text below field
556
- "maxLength": 100, // String length limit
557
- "minLength": 3, // Minimum string length
558
- "pattern": "^[A-Za-z]+$", // Regex validation pattern
559
- "min": 0, // Number minimum value
560
- "max": 100, // Number maximum value
561
- "step": 1, // Number step increment
562
- "defaultValue": "default", // Default field value
563
- "options": [ // Select/radio options
564
- {"value": "val1", "label": "Option 1"},
565
- {"value": "val2", "label": "Option 2"}
566
- ]
567
- }
568
- ```
569
-
570
- ### Security Features
571
- The form system includes comprehensive security measures:
572
-
573
- #### 1. Honeypot Protection
574
- Automatic bot detection using invisible fields:
575
- ```html
576
- <!-- Automatically added to all forms -->
577
- <div class="hidden">
578
- <input type="text" name="website_url" value="" />
579
- </div>
580
- ```
581
-
582
- #### 2. Server-Side Validation
583
- - **SchemaValidator integration** - Uses `@reldens/utils` SchemaValidator
584
- - **Required field validation** - Ensures all required fields are provided
585
- - **Type validation** - Email, number, string validation with patterns
586
- - **Length limits** - Configurable per field via schema
587
- - **Custom validation** - Extensible validation rules
588
-
589
- #### 3. Data Sanitization
590
- - **XSS protection** - Handled by `@reldens/server-utils` SecurityConfigurer
591
- - **Input normalization** - Type-specific data processing
592
- - **Length truncation** - Based on field schema maxLength
593
-
594
- #### 4. Rate Limiting
595
- - **AppServerFactory integration** - Uses existing rate limiting from server-utils
596
- - **No duplicate implementation** - Leverages proven security measures
597
-
598
- ### Template Customization
599
- Forms use a domain-aware template fallback system:
600
-
601
- ```
602
- templates/
603
- ├── domains/
604
- │ └── example.com/
605
- │ └── cms_forms/
606
- │ ├── form.html # Domain-specific form wrapper
607
- │ ├── field_text.html # Domain-specific text field
608
- │ └── field_email.html # Domain-specific email field
609
- └── cms_forms/ # Default templates
610
- ├── form.html # Main form wrapper
611
- ├── field_text.html # Text input template
612
- ├── field_email.html # Email input template
613
- ├── field_textarea.html # Textarea template
614
- ├── field_select.html # Select dropdown template
615
- └── field_number.html # Number input template
616
- ```
617
-
618
- ### Custom Field Templates
619
- Create custom field templates for specific types:
620
-
621
- **templates/cms_forms/field_text.html:**
622
- ```html
623
- <div class="form-field {{errorClass}} {{requiredClass}}">
624
- <label for="{{fieldName}}" class="form-label">
625
- {{fieldLabel}}{{#isRequired}} <span class="required-indicator">*</span>{{/isRequired}}
626
- </label>
627
- <input type="{{fieldType}}"
628
- name="submittedValues[{{fieldName}}]"
629
- id="{{fieldName}}"
630
- value="{{fieldValue}}"
631
- class="form-control {{#hasError}}is-invalid{{/hasError}}"
632
- {{#isRequired}}required{{/isRequired}}
633
- {{#placeholder}}placeholder="{{placeholder}}"{{/placeholder}}
634
- {{#maxLength}}maxlength="{{maxLength}}"{{/maxLength}}
635
- {{#pattern}}pattern="{{pattern}}"{{/pattern}} />
636
- {{#helpText}}<div class="form-text">{{helpText}}</div>{{/helpText}}
637
- {{#hasError}}<div class="invalid-feedback">{{fieldError}}</div>{{/hasError}}
638
- </div>
639
- ```
640
-
641
- **templates/cms_forms/form.html:**
642
- ```html
643
- <form method="POST" action="{{submitUrl}}" class="{{cssClass}}">
644
- <input type="hidden" name="formKey" value="{{formKey}}" />
645
- <input type="hidden" name="successRedirect" value="{{successRedirect}}" />
646
- <input type="hidden" name="errorRedirect" value="{{errorRedirect}}" />
647
- <div class="hidden">
648
- <input type="text" name="{{honeypotFieldName}}" value="" />
649
- </div>
650
- {{&formFields}}
651
- <div class="form-submit">
652
- <button type="submit" class="btn btn-primary">{{submitButtonText}}</button>
653
- </div>
654
- </form>
655
- ```
656
-
657
- ### Forms with System Variables
658
- Forms can access system variables and enhanced data in templates:
659
-
660
- ```html
661
- <!-- Form with the current user context -->
662
- <cmsForm key="userProfile" fields="name,email,bio"/>
663
-
664
- <!-- In the form template, access system variables: -->
665
- <form method="POST" action="{{submitUrl}}" class="{{cssClass}}">
666
- <h2>Update Profile for {{currentRequest.host}}</h2>
667
- <p>Current time: {{systemInfo.timestamp}}</p>
668
- {{&formFields}}
669
- <button type="submit">Update Profile</button>
670
- </form>
671
- ```
672
-
673
- ### Event System Integration
674
- The forms system provides comprehensive event hooks:
675
-
676
- ```javascript
677
- // Listen for form events
678
- cms.events.on('reldens.formsTransformer.beforeRender', (eventData) => {
679
- console.log('Rendering form:', eventData.formKey);
680
- // Modify form attributes or fields before rendering
681
- eventData.formAttributes.cssClass += ' custom-form';
682
- });
683
-
684
- cms.events.on('reldens.dynamicForm.beforeValidation', (eventData) => {
685
- console.log('Validating form:', eventData.formKey);
686
- // Add custom validation logic
687
- });
688
-
689
- cms.events.on('reldens.dynamicForm.afterSave', (eventData) => {
690
- console.log('Form saved:', eventData.result.id);
691
- // Send notifications, trigger workflows, etc.
692
- });
693
-
694
- cms.events.on('reldens.dynamicFormRequestHandler.beforeSave', (eventData) => {
695
- // Modify prepared values before saving
696
- eventData.preparedValues.submissionDate = new Date().toISOString();
697
- });
698
- ```
699
-
700
- ### Available Form Events
701
- - `reldens.formsTransformer.beforeRender` - Before form rendering
702
- - `reldens.formsTransformer.afterRender` - After form rendering
703
- - `reldens.dynamicForm.beforeValidation` - Before form validation
704
- - `reldens.dynamicForm.afterValidation` - After form validation
705
- - `reldens.dynamicForm.beforeSave` - Before saving to the database
706
- - `reldens.dynamicForm.afterSave` - After successful save
707
- - `reldens.dynamicFormRenderer.beforeFieldsRender` - Before rendering fields
708
- - `reldens.dynamicFormRenderer.afterFieldsRender` - After rendering fields
709
- - `reldens.dynamicFormRequestHandler.beforeValidation` - Before request validation
710
- - `reldens.dynamicFormRequestHandler.beforeSave` - Before save process
711
- - `reldens.dynamicFormRequestHandler.afterSave` - After successful save
712
-
713
- ### Database Tables
714
- The forms system uses two main tables:
715
-
716
- #### cms_forms Table
717
- Store form configurations:
718
- ```sql
719
- CREATE TABLE `cms_forms` (
720
- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
721
- `form_key` VARCHAR(255) NOT NULL UNIQUE,
722
- `fields_schema` JSON NOT NULL,
723
- `enabled` TINYINT UNSIGNED NOT NULL DEFAULT '0',
724
- `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
725
- `updated_at` TIMESTAMP NOT NULL DEFAULT (NOW()) ON UPDATE CURRENT_TIMESTAMP,
726
- PRIMARY KEY (`id`)
727
- );
728
- ```
210
+ Create database-driven forms with `<cmsForm key="contactForm"/>` tags in templates. Features:
729
211
 
730
- #### cms_forms_submitted Table
731
- Store form submissions:
732
- ```sql
733
- CREATE TABLE `cms_forms_submitted` (
734
- `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
735
- `form_id` INT UNSIGNED NOT NULL,
736
- `submitted_values` JSON NOT NULL,
737
- `created_at` TIMESTAMP NOT NULL DEFAULT (NOW()),
738
- PRIMARY KEY (`id`),
739
- FOREIGN KEY (`form_id`) REFERENCES `cms_forms`(`id`)
740
- );
741
- ```
212
+ - Schema-based validation with multiple field types (text, email, select, etc.)
213
+ - Security: Honeypot protection, rate limiting, XSS protection
214
+ - Domain-aware template customization
215
+ - Event hooks for custom processing
216
+ - Database storage in cms_forms and cms_forms_submitted tables
742
217
 
743
- ### Form Processing Flow
744
- 1. **Template Processing** - `FormsTransformer` finds `<cmsForm>` tags
745
- 2. **Form Loading** - Loads form configuration from database
746
- 3. **Field Filtering** - Applies field filter if specified
747
- 4. **Template Rendering** - Renders form using domain-aware templates
748
- 5. **Form Submission** - POST request to `/dynamic-form` endpoint
749
- 6. **Validation** - Honeypot, required fields, and schema validation
750
- 7. **Data Processing** - Input sanitization and normalization
751
- 8. **Database Storage** - Save to `cms_forms_submitted` table
752
- 9. **Response** - Redirect with success/error parameters
753
-
754
- ### Advanced Form Usage
755
-
756
- #### Multi-Step Forms
757
- ```html
758
- <!-- Step 1: Basic info -->
759
- <cmsForm key="applicationForm" fields="name,email,phone"/>
760
-
761
- <!-- Step 2: Details (separate form) -->
762
- <cmsForm key="applicationDetails" fields="experience,portfolio"/>
763
- ```
764
-
765
- #### Conditional Field Display
766
- Use JavaScript to show/hide fields based on selections:
767
- ```html
768
- <cmsForm key="surveyForm" fields="age,experience,expertise"/>
769
-
770
- <script>
771
- document.addEventListener('DOMContentLoaded', function() {
772
- const ageField = document.getElementById('age');
773
- const experienceField = document.getElementById('experience');
774
-
775
- ageField.addEventListener('change', function() {
776
- if(parseInt(this.value) >= 18) {
777
- experienceField.parentElement.style.display = 'block';
778
- return;
779
- }
780
- experienceField.parentElement.style.display = 'none';
781
- });
782
- });
783
- </script>
784
- ```
785
-
786
- #### AJAX Form Submissions
787
- Enable JSON responses for AJAX handling:
788
- ```javascript
789
- const cms = new Manager({
790
- enableJsonResponse: true // Enable JSON responses for forms
791
- });
792
- ```
793
-
794
- ```javascript
795
- // Frontend AJAX handling
796
- document.querySelector('.dynamic-form').addEventListener('submit', async function(e) {
797
- e.preventDefault();
798
-
799
- const response = await fetch('/dynamic-form', {method: 'POST', body: new FormData(this)});
800
-
801
- const result = await response.json();
802
- if(result.success) {
803
- alert('Form submitted successfully!');
804
- return;
805
- }
806
- alert('Error: ' + result.error);
807
- });
808
- ```
218
+ **Full documentation:** See `.claude/forms-system-guide.md`
809
219
 
810
220
  ## Search Functionality
811
221
 
812
- ### Basic Search
813
- ```bash
814
- # Simple search
815
- /search?search=technology
816
-
817
- # Entity-specific search with custom limit
818
- /search?search=javascript&limit=20
819
-
820
- # Custom template rendering
821
- /search?search=news&renderPartial=newsListView&renderLayout=minimal
822
- ```
222
+ Multi-entity search with pagination and template data support.
823
223
 
824
- ### Advanced Search with Template Data
825
- ```bash
826
- # Pass custom template variables
827
- /search?search=articles&templateData[columnsClass]=col-md-4&templateData[showExcerpt]=true
828
-
829
- # Multiple template variables
830
- /search?search=technology&templateData[columnsClass]=col-lg-6&templateData[cardClass]=shadow-sm&templateData[showAuthor]=false
831
- ```
832
-
833
- ### Search Template Variables
834
- Templates receive dynamic data through URL parameters:
835
-
836
- **URL:** `/search?search=tech&templateData[columnsClass]=col-md-6&templateData[showDate]=true`
837
-
838
- **Template (entriesListView.html):**
839
- ```html
840
- <div class="{{columnsClass}}">
841
- <div class="card">
842
- <h3>{{row.title}}</h3>
843
- <p>{{row.content}}</p>
844
- {{#showDate}}
845
- <span class="date">{{row.created_at}}</span>
846
- {{/showDate}}
847
- </div>
848
- </div>
849
- ```
850
-
851
- **Default Values:**
852
- - `columnsClass` defaults to `col-lg-6` if not provided or empty
853
- - Custom variables can be added via `templateData[variableName]=value`
854
-
855
- ### Search Configuration
856
- ```javascript
857
- // Custom search sets in Manager configuration
858
- const searchSets = {
859
- articlesSearch: {
860
- entities: [{
861
- name: 'articles',
862
- fields: ['title', 'content', 'summary'],
863
- relations: 'authors'
864
- }],
865
- pagination: {active: true, limit: 15, sortBy: 'created_at', sortDirection: 'desc'}
866
- }
867
- };
224
+ **Usage:** `/search?search=technology&limit=20&templateData[columnsClass]=col-md-4`
868
225
 
869
- const cms = new Manager({
870
- searchSets: searchSets
871
- });
872
- ```
226
+ **Full documentation:** See `.claude/search-guide.md`
873
227
 
874
228
  ## Enhanced Templating System
875
229
 
876
- ### System Variables
877
- Every template has access to system variables providing context about the current request:
878
-
879
- ```html
880
- <!-- Current request information -->
881
- {{currentRequest.baseUrl}} <!-- https://example.com -->
882
- {{currentRequest.protocol}} <!-- https -->
883
- {{currentRequest.host}} <!-- example.com -->
884
- {{currentRequest.path}} <!-- /articles/123 -->
885
- {{currentRequest.method}} <!-- GET -->
886
- {{currentRequest.userAgent}} <!-- Browser information -->
887
- {{currentRequest.isSecure}} <!-- true/false -->
888
-
889
- <!-- Current route data (if matched) -->
890
- {{currentRoute.id}} <!-- Route ID -->
891
- {{currentRoute.path}} <!-- Route path pattern -->
892
- {{currentRoute.title}} <!-- Route title -->
893
- {{currentRoute.template}} <!-- Route template -->
894
- {{currentRoute.layout}} <!-- Route layout -->
895
-
896
- <!-- Current domain information -->
897
- {{currentDomain.current}} <!-- Current domain -->
898
- {{currentDomain.default}} <!-- Default domain -->
899
- {{currentDomain.resolved}} <!-- Resolved domain -->
900
-
901
- <!-- System information -->
902
- {{systemInfo.environment}} <!-- development/production -->
903
- {{systemInfo.nodeVersion}} <!-- Node.js version -->
904
- {{systemInfo.timestamp}} <!-- Current timestamp -->
905
- ```
906
-
907
- ### Template Functions
908
- Templates support dynamic functions for common operations:
909
-
910
- ```html
911
- <!-- URL generation with current domain -->
912
- [url(/articles)] <!-- https://example.com/articles -->
913
- [url(/contact#form)] <!-- https://example.com/contact#form -->
914
- [url(/css/styles.css)] <!-- https://example.com/css/styles.css -->
915
-
916
- <!-- Asset URLs with domain -->
917
- [asset(/assets/images/logo.png)] <!-- https://example.com/images/logo.png -->
918
-
919
- <!-- Date formatting -->
920
- [date()] <!-- Current date with default format -->
921
- [date(now, Y-m-d)] <!-- 2024-01-15 -->
922
- [date(2024-01-01, d/m/Y)] <!-- 01/01/2024 -->
923
-
924
- <!-- Internationalization -->
925
- [translate(welcome.message)] <!-- Translated text -->
926
- [t(hello.world, Hello World!)] <!-- With fallback -->
927
- [t(greeting, Hi {name}!, {name: John})] <!-- With interpolation -->
928
- ```
929
-
930
- ### Enhanced Context Passing
931
- Child content blocks and partials receive context from parent pages:
932
-
933
- ```html
934
- <!-- In a CMS page or layout -->
935
- <entity name="cmsBlocks" field="name" value="article-sidebar"/>
936
-
937
- <!-- Inside the article-sidebar block, you can access: -->
938
- {{currentEntity.title}} <!-- Parent page title -->
939
- {{currentEntity.id}} <!-- Parent page ID -->
940
- {{currentEntity.template}} <!-- Parent page template -->
941
- <!-- Any other parent page properties -->
942
- ```
943
-
944
- ### Template Functions
945
- Templates support dynamic content blocks, entity rendering, and collections with advanced query options:
946
-
947
- **Single Entity Rendering:**
948
- ```html
949
- <!-- Render by any identifier field (like 'name' for content blocks) -->
950
- <entity name="cmsBlocks" field="name" value="header-main"/>
951
- <entity name="cmsBlocks" field="name" value="sidebar-left"/>
952
-
953
- <!-- Render by ID (default identifier) -->
954
- <entity name="articles" id="123"/>
955
- <entity name="cmsPages" id="1"/>
956
- ```
957
-
958
- **Single Field Collections:**
959
- ```html
960
- <!-- Extract and concatenate a single field from multiple records -->
961
- <collection name="cmsBlocks" filters="{status: 'active', category: 'navigation'}" field="content"/>
962
- <collection name="articles" filters="{featured: true}" field="title"/>
963
-
964
- <!-- With query options for sorting and limiting -->
965
- <collection name="articles" filters="{featured: true}" field="title" data="{limit: 5, sortBy: 'created_at', sortDirection: 'desc'}"/>
966
- <collection name="cmsBlocks" filters="{status: 'active'}" field="content" data="{limit: 3, offset: 10, sortBy: 'priority'}"/>
967
- ```
968
-
969
- **Loop Collections:**
970
- ```html
971
- <!-- Loop through records with full template rendering -->
972
- <collection name="cmsBlocks" filters="{status: 'active'}">
973
- <div class="block">
974
- <h3>{{row.title}}</h3>
975
- <div class="content">{{row.content}}</div>
976
- </div>
977
- </collection>
978
-
979
- <collection name="articles" filters="{category: 'technology'}">
980
- <div class="article">
981
- <h4>{{row.title}}</h4>
982
- <p>{{row.summary}}</p>
983
- <img src="{{row.featured_image}}" alt="{{row.title}}">
984
- </div>
985
- </collection>
986
-
987
- <!-- With pagination and sorting -->
988
- <collection name="articles" filters="{featured: true}" data="{limit: 10, offset: 0, sortBy: 'created_at', sortDirection: 'asc'}">
989
- <div class="article-card">
990
- <h4>{{row.title}}</h4>
991
- <p>{{row.summary}}</p>
992
- </div>
993
- </collection>
994
-
995
- <!-- Paginated collections with navigation -->
996
- <collection name="articles"
997
- filters="{featured: true}"
998
- data="{limit: 10, sortBy: 'created_at', sortDirection: 'desc'}"
999
- pagination="articles-1"
1000
- container="pagedCollection"
1001
- prevPages="2"
1002
- nextPages="2">
1003
- <div class="article-card">
1004
- <h4>{{row.title}}</h4>
1005
- <p>{{row.summary}}</p>
1006
- <span class="date">{{row.created_at}}</span>
1007
- </div>
1008
- </collection>
1009
-
1010
- <!-- Multiple paginated collections on the same page -->
1011
- <collection name="news"
1012
- filters="{category: 'technology'}"
1013
- data="{limit: 5, sortBy: 'published_at'}"
1014
- pagination="news-tech"
1015
- container="customPager">
1016
- <article>{{row.title}}</article>
1017
- </collection>
1018
-
1019
- <collection name="events"
1020
- filters="{upcoming: true}"
1021
- data="{limit: 8}"
1022
- pagination="events-upcoming"
1023
- prevPages="3"
1024
- nextPages="1">
1025
- <div class="event">{{row.title}} - {{row.date}}</div>
1026
- </collection>
1027
- ```
1028
-
1029
- **Pagination Attributes:**
1030
- - `pagination="collection-id"` - Enables pagination with unique identifier
1031
- - `container="templateName"` - Custom pagination template (defaults to "pagedCollection")
1032
- - `prevPages="2"` - Number of previous page links to show (default: 2)
1033
- - `nextPages="2"` - Number of next page links to show (default: 2)
1034
-
1035
- **Pagination URL Parameters:**
1036
- Pagination state is managed via URL query parameters:
1037
- ```
1038
- /articles?articles-1-key={"page":2,"limit":10,"sortBy":"created_at","sortDirection":"desc"}
1039
- /news?news-tech-key={"page":3,"limit":5,"category":"technology"}
1040
- ```
1041
-
1042
- **Custom Pagination Template:**
1043
- Create `templates/partials/pagedCollection.html`:
1044
- ```html
1045
- <div class="row paginated-contents">
1046
- <div class="collection-content col-lg-12">
1047
- {{&collectionContentForCurrentPage}}
1048
- </div>
1049
- <div class="pagination col-lg-12">
1050
- <ul class="pagination-list">
1051
- {{#prevPageUrl}}
1052
- <li><a href="{{prevPageUrl}}" class="page-link">{{&prevPageLabel}}</a></li>
1053
- {{/prevPageUrl}}
1054
- {{#prevPages}}
1055
- <li><a href="{{pageUrl}}" class="page-link">{{&pageLabel}}</a></li>
1056
- {{/prevPages}}
1057
- <li class="current">{{&currentPage}}</li>
1058
- {{#nextPages}}
1059
- <li><a href="{{pageUrl}}" class="page-link">{{&pageLabel}}</a></li>
1060
- {{/nextPages}}
1061
- {{#nextPageUrl}}
1062
- <li><a href="{{nextPageUrl}}" class="page-link">{{&nextPageLabel}}</a></li>
1063
- {{/nextPageUrl}}
1064
- </ul>
1065
- </div>
1066
- </div>
1067
- ```
1068
-
1069
- **Available Pagination Template Variables:**
1070
- - `{{&collectionContentForCurrentPage}}` - Rendered collection items for current page
1071
- - `{{currentPage}}` - Current page number
1072
- - `{{totalPages}}` - Total number of pages
1073
- - `{{totalRecords}}` - Total number of records
1074
- - `{{prevPageUrl}}` / `{{nextPageUrl}}` - Previous/next page URLs
1075
- - `{{&prevPageLabel}}` / `{{&nextPageLabel}}` - Previous/next link labels ("Previous"/"Next")
1076
- - `{{#prevPages}}` / `{{#nextPages}}` - Arrays of page objects with `pageUrl` and `pageLabel`
1077
- - `{{hasNextPage}}` / `{{hasPrevPage}}` - Boolean flags for navigation availability
1078
-
1079
- **Custom Partials with Variables:**
1080
-
1081
- *New HTML-style partial tags:*
1082
- ```html
1083
- <!-- Clean HTML-style syntax for complex partials -->
1084
- <partial name="hero"
1085
- sectionStyle=" bg-black"
1086
- bigTextHtml="A free open-source platform to create multiplayer games!"
1087
- mediumTextHtml="Build with Node.js, MySQL, Colyseus, and Phaser 3"
1088
- htmlContentWrapper='<div class="d-lg-flex"><a href="/documentation" target="_blank" class="btn-get-started">Get Started!</a><a href="https://demo.reldens.com/" target="_blank" class="btn-watch-video"> Demo </a></div>'
1089
- imageUrl="/assets/web/reldens-check.png"
1090
- imageAlt="Reldens - MMORPG Platform" />
1091
-
1092
- <!-- Self-closing and open/close syntax both supported -->
1093
- <partial name="productCard"
1094
- title="Premium Package"
1095
- price="$99"
1096
- highlighted="true">
1097
- </partial>
1098
- ```
1099
-
1100
- *Traditional Mustache syntax is still supported:*
1101
- ```html
1102
- <!-- Call a partial with an inline JSON object -->
1103
- {{>hero -{
1104
- bigTextHtml: "A free open-source platform to create multiplayer games!",
1105
- mediumTextHtml: "Build with Node.js, MySQL, Colyseus, and Phaser 3",
1106
- imageUrl: "https://example.com/hero.jpg",
1107
- ctaText: "Get Started",
1108
- ctaLink: "/documentation"
1109
- }-}}
1110
-
1111
- <!-- Call a partial within collections using row data -->
1112
- <collection name="cmsPages" filters="{featured: true}" data="{limit: 3}">
1113
- {{>cardView -{row}-}}
1114
- </collection>
1115
-
1116
- <!-- Call a partial with mixed data -->
1117
- {{>productCard -{
1118
- title: "Premium Package",
1119
- price: "$99",
1120
- features: ["Advanced Analytics", "Priority Support", "Custom Themes"],
1121
- highlighted: true
1122
- }-}}
1123
- ```
1124
-
1125
- **Example Partial Templates:**
1126
-
1127
- **partials/hero.mustache:**
1128
- ```html
1129
- <section class="hero{{#sectionStyle}}{{sectionStyle}}{{/sectionStyle}}">
1130
- <div class="hero-content">
1131
- <h1>{{&bigTextHtml}}</h1>
1132
- <p>{{&mediumTextHtml}}</p>
1133
- {{#htmlContentWrapper}}
1134
- {{&htmlContentWrapper}}
1135
- {{/htmlContentWrapper}}
1136
- {{#imageUrl}}
1137
- <img src="{{imageUrl}}" alt="{{imageAlt}}" class="hero-image">
1138
- {{/imageUrl}}
1139
- </div>
1140
- </section>
1141
- ```
1142
-
1143
- **partials/cardView.mustache:**
1144
- ```html
1145
- <div class="card">
1146
- <h3>{{title}}</h3>
1147
- <p>{{json_data.excerpt}}</p>
1148
- {{#json_data.featured_image}}
1149
- <img src="{{json_data.featured_image}}" alt="{{title}}">
1150
- {{/json_data.featured_image}}
1151
- {{#json_data.cta_text}}
1152
- <a href="{{json_data.cta_link}}" class="btn">{{json_data.cta_text}}</a>
1153
- {{/json_data.cta_text}}
1154
- </div>
1155
- ```
1156
-
1157
- ### Collection Query Options
1158
- Collections support advanced query parameters for pagination and sorting:
1159
-
1160
- - **limit** - Maximum number of records to return
1161
- - **offset** - Number of records to skip (for pagination)
1162
- - **sortBy** - Field name to sort by
1163
- - **sortDirection** - Sort direction ('asc' or 'desc')
1164
-
1165
- **Examples:**
1166
- ```html
1167
- <!-- Get the first 5 articles sorted by title -->
1168
- <collection name="articles" filters="{}" field="title" data="{limit: 5, sortBy: 'title'}"/>
1169
-
1170
- <!-- Paginated results: skip first 20, get next 10 -->
1171
- <collection name="articles" filters="{published: true}" data="{limit: 10, offset: 20, sortBy: 'created_at', sortDirection: 'desc'}">
1172
- <article>{{row.title}}</article>
1173
- </collection>
1174
-
1175
- <!-- The latest 3 featured articles by creation date -->
1176
- <collection name="articles" filters="{featured: true}" data="{limit: 3, sortBy: 'created_at', sortDirection: 'desc'}">
1177
- <div class="featured-article">{{row.title}}</div>
1178
- </collection>
1179
- ```
1180
-
1181
- ## Internationalization
1182
-
1183
- ### Translation Files
1184
- Create translation files in the `translations` directory:
1185
-
1186
- **translations/en.json:**
1187
- ```json
1188
- {
1189
- "navigation": {
1190
- "home": "Home",
1191
- "about": "About Us",
1192
- "contact": "Contact"
1193
- },
1194
- "messages": {
1195
- "welcome": "Welcome to our site!",
1196
- "greeting": "Hello {name}!"
1197
- }
1198
- }
1199
- ```
1200
-
1201
- **translations/es.json:**
1202
- ```json
1203
- {
1204
- "navigation": {
1205
- "home": "Inicio",
1206
- "about": "Acerca de",
1207
- "contact": "Contacto"
1208
- },
1209
- "messages": {
1210
- "welcome": "¡Bienvenido a nuestro sitio!",
1211
- "greeting": "¡Hola {name}!"
1212
- }
1213
- }
1214
- ```
1215
-
1216
- ### Using Translations in Templates
1217
- ```html
1218
- <!-- Simple translation -->
1219
- [translate(navigation.home)]
1220
-
1221
- <!-- With fallback -->
1222
- [t(navigation.home, Home)]
230
+ Powerful template engine with Mustache integration, system variables, and dynamic content rendering.
1223
231
 
1224
- <!-- With interpolation -->
1225
- [t(messages.greeting, Hello!, {name: John})]
232
+ **Core Features:**
233
+ - System variables: `{{currentRequest.host}}`, `{{currentRoute.title}}`, `{{systemInfo.environment}}`
234
+ - Template functions: `[url(/path)]`, `[asset(/img.png)]`, `[date(now, Y-m-d)]`, `[translate(key)]`
235
+ - Entity rendering: `<entity name="cmsBlocks" field="name" value="header"/>`
236
+ - Collections with pagination: `<collection name="articles" filters="{}" data="{limit: 10}">`
237
+ - Custom partials: `<partial name="hero" title="Welcome"/>` or `{{>hero -{data}-}}`
1226
238
 
1227
- <!-- Locale detection from request headers or ?locale=es parameter -->
1228
- ```
1229
-
1230
- ### Layout System
1231
- The CMS uses a two-tier layout system:
1232
-
1233
- **page.html** - Full HTML wrapper:
1234
- ```html
1235
- <!DOCTYPE html>
1236
- <html lang="{{locale}}">
1237
- <head>
1238
- <title>{{title}}</title>
1239
- <meta name="description" content="{{description}}"/>
1240
- <link href="[url(/css/styles.css)]" rel="stylesheet"/>
1241
- </head>
1242
- <body class="{{siteHandle}}">
1243
- {{&content}}
1244
- <script src="[url(/js/scripts.js)]"></script>
1245
- </body>
1246
- </html>
1247
- ```
239
+ **Full documentation:** See `.claude/templating-system-guide.md`
1248
240
 
1249
- **layouts/default.html** - Body content only:
1250
- ```html
1251
- <entity name="cmsBlocks" field="name" value="header-main"/>
1252
-
1253
- <main id="main" class="main-container">
1254
- <div class="container">
1255
- <div class="row">
1256
- <div class="col-md-3">
1257
- <entity name="cmsBlocks" field="name" value="sidebar-left"/>
1258
- </div>
1259
- <div class="col-md-9">
1260
- {{&content}}
1261
- </div>
1262
- </div>
1263
- </div>
1264
- </main>
1265
-
1266
- <entity name="cmsBlocks" field="name" value="footer-main"/>
1267
- ```
1268
-
1269
- Pages can use different layouts by setting the `layout` field in `cms_pages`:
1270
- - `default` - Header, sidebar, main content, footer
1271
- - `full-width` - Full width without sidebars
1272
- - `minimal` - Basic layout with minimal styling
1273
-
1274
- ### Content Blocks
1275
- Create reusable content blocks in the `cms_blocks` table via the admin panel:
1276
- ```sql
1277
- INSERT INTO cms_blocks (name, title, content) VALUES
1278
- ('contact-info', 'Contact Information', '<p>Email: info@example.com</p>'),
1279
- ('article-sidebar', 'Article Categories',
1280
- '<div class="categories"><h3>Categories</h3><ul><li><a href="[url(/articles/technology)]">Technology</a></li></ul></div>');
1281
- ```
1282
-
1283
- ### Entity Access Control
1284
- Control which entities are publicly accessible:
1285
- ```javascript
1286
- const cms = new Manager({
1287
- entityAccess: {
1288
- articles: { public: true, operations: ['read'] },
1289
- cmsPages: { public: true, operations: ['read'] },
1290
- users: { public: false }
1291
- }
1292
- });
1293
- ```
241
+ ## Internationalization & Multi-Domain
1294
242
 
1295
- ## Multi-Domain Setup
243
+ **Translation files:** JSON-based i18n with `[translate(key)]` and `[t(key, fallback)]` functions
244
+ **Multi-domain support:** Domain-specific templates, partials, and configurations
245
+ **Layout system:** Two-tier layout (page.html + layouts/) with multiple layout options
246
+ **Content blocks:** Reusable content via cms_blocks table
247
+ **Entity access control:** Configure public/private entities and operations
1296
248
 
1297
- ### Directory Structure
1298
- ```
1299
- templates/
1300
- ├── layouts/
1301
- │ ├── default.html # Body content layouts
1302
- │ ├── full-width.html
1303
- │ └── minimal.html
1304
- ├── domains/
1305
- │ ├── example.com/
1306
- │ │ ├── layouts/ # Domain-specific layouts
1307
- │ │ ├── partials/
1308
- │ │ │ ├── header.html
1309
- │ │ │ └── footer.html
1310
- │ │ ├── cms_forms/ # Domain-specific form templates
1311
- │ │ │ ├── form.html
1312
- │ │ │ └── field_text.html
1313
- │ │ ├── page.html # Domain-specific page wrapper
1314
- │ │ └── index.html
1315
- │ └── dev.example.com/
1316
- │ └── page.html
1317
- ├── partials/
1318
- │ ├── header.html (default)
1319
- │ └── footer.html (default)
1320
- ├── cms_forms/ # Default form templates
1321
- │ ├── form.html
1322
- │ ├── field_text.html
1323
- │ └── field_email.html
1324
- ├── translations/
1325
- │ ├── en.json
1326
- │ ├── es.json
1327
- │ └── fr.json
1328
- ├── page.html (base HTML wrapper)
1329
- └── 404.html
1330
- ```
249
+ **Full documentation:** See `.claude/multi-domain-i18n-guide.md`
1331
250
 
1332
251
  ## Advanced Usage
1333
252
 
1334
- ### Template Reloading for Development
1335
- ```javascript
1336
- // Different configurations for development vs production
1337
- const isDevelopment = process.env.NODE_ENV === 'development';
1338
-
1339
- const cms = new Manager({
1340
- // Enable aggressive template reloading in development
1341
- reloadTime: isDevelopment ? -1 : 0,
1342
-
1343
- // Other development-friendly settings
1344
- cache: !isDevelopment,
1345
-
1346
- entityAccess: {
1347
- articles: { public: true, operations: ['read'] },
1348
- cmsPages: { public: true, operations: ['read'] }
1349
- }
1350
- });
1351
- ```
1352
-
1353
- **Development Workflow with Template Reloading:**
1354
- 1. Set `reloadTime: -1` for instant template updates
1355
- 2. Edit admin templates in `admin/templates/` - changes appear immediately
1356
- 3. Edit frontend templates in `templates/` - changes appear on next page load
1357
- 4. No server restart needed for template changes
1358
- 5. Switch to `reloadTime: 0` in production for optimal performance
1359
-
1360
- ### Event System
1361
- The CMS provides hooks for customization through event listeners:
253
+ **Template Reloading:** Set `reloadTime: -1` for development, `0` for production
254
+ **Event System:** Hook into CMS events for customization
255
+ **Custom Authentication:** Implement custom auth callbacks
256
+ **File Upload Config:** Configure MIME types and allowed extensions
1362
257
 
1363
- ```javascript
1364
- // Listen for template variable events
1365
- cms.events.on('reldens.afterVariablesCreated', (eventData) => {
1366
- // Add custom variables
1367
- eventData.variables.customData = {
1368
- timestamp: Date.now(),
1369
- version: '1.0.0'
1370
- };
1371
- });
1372
-
1373
- // Listen for content processing events
1374
- cms.events.on('reldens.beforeContentProcess', (eventData) => {
1375
- // Modify content before processing
1376
- eventData.content = eventData.content.replace(/\[custom\]/g, 'Custom Value');
1377
- });
1378
-
1379
- cms.events.on('reldens.afterContentProcess', (eventData) => {
1380
- // Modify processed content
1381
- eventData.processedContent += '\n<!-- Processed at ' + new Date() + ' -->';
1382
- });
1383
-
1384
- // Listen for template reloading events
1385
- cms.events.on('reldens.templateReloader.templatesChanged', (eventData) => {
1386
- console.log('Templates changed:', eventData.changedFiles);
1387
- });
1388
-
1389
- // Listen for form events
1390
- cms.events.on('reldens.dynamicForm.afterSave', (eventData) => {
1391
- // Send email notifications, trigger workflows, etc.
1392
- console.log('Form submission received:', eventData.result.id);
1393
- });
1394
- ```
1395
-
1396
- ### Custom Authentication
1397
- ```javascript
1398
- const customAuth = async (email, password, roleId) => {
1399
- const user = await yourAuthService.authenticate(email, password);
1400
- return user && user.role_id === roleId ? user : false;
1401
- };
1402
-
1403
- const cms = new Manager({
1404
- authenticationMethod: 'custom',
1405
- authenticationCallback: customAuth
1406
- });
1407
- ```
258
+ **Full documentation:** See `.claude/advanced-usage-guide.md`
1408
259
 
1409
- ### File Upload Configuration
1410
- ```javascript
1411
- const uploadConfig = {
1412
- mimeTypes: {
1413
- image: ['image/jpeg', 'image/png', 'image/webp'],
1414
- document: ['application/pdf', 'text/plain']
1415
- },
1416
- allowedExtensions: {
1417
- image: ['.jpg', '.jpeg', '.png', '.webp'],
1418
- document: ['.pdf', '.txt']
1419
- }
1420
- };
260
+ ## Database Schema
1421
261
 
1422
- const cms = new Manager(uploadConfig);
1423
- ```
262
+ **Core tables:** routes, cms_pages, cms_blocks, entities_access, entities_meta
263
+ **Forms tables:** cms_forms, cms_forms_submitted
264
+ **Optional:** users, roles
1424
265
 
1425
- ### Event Hooks
1426
- ```javascript
1427
- cms.events.on('reldens.setupAdminRoutes', ({adminManager}) => {
1428
- // Add custom admin routes
1429
- adminManager.adminRouter.get('/custom', (req, res) => {
1430
- res.send('Custom admin page');
1431
- });
1432
- });
1433
-
1434
- cms.events.on('adminEntityExtraData', ({entitySerializedData, entity}) => {
1435
- // Add extra data to admin views
1436
- entitySerializedData.customField = 'Custom Value';
1437
- });
1438
- ```
1439
-
1440
- ## Default database Schema
1441
-
1442
- ### Core Tables
1443
- - `routes` - URL routing and SEO metadata
1444
- - `cms_pages` - Page content with layout assignments
1445
- - `cms_blocks` - Reusable content blocks
1446
- - `entities_access` - Entity access control rules
1447
- - `entities_meta` - Generic metadata storage
1448
- - `cms_pages_meta` - Page-specific metadata
1449
-
1450
- ### Forms Tables
1451
- - `cms_forms` - Form configurations with JSON schema
1452
- - `cms_forms_submitted` - Form submissions with JSON data
1453
-
1454
- ### Installation Options
1455
- The installer provides checkboxes for:
1456
- - CMS core tables
1457
- - User authentication system
1458
- - Default admin user
1459
- - Default homepage
1460
- - Default content blocks
1461
- - Entity access control rules
1462
- - Dynamic forms system
266
+ **Full documentation:** See `.claude/database-schema.md`
1463
267
 
1464
268
  ## API Reference
1465
269
 
1466
- ### Manager Class
1467
- - `start()` - Initialize and start the CMS
1468
- - `isInstalled()` - Check if CMS is installed
1469
- - `initializeServices()` - Initialize all services
1470
- - `validateProvidedServer()` - Validate provided server instance
1471
- - `validateProvidedDataServer()` - Validate provided data server
1472
- - `validateProvidedAdminManager()` - Validate provided admin manager
1473
- - `validateProvidedFrontend()` - Validate provided frontend
1474
- - `buildAppServerConfiguration()` - Build server configuration
1475
- - `initializeCmsAfterInstall(props)` - Post-installation callback
1476
-
1477
- ### Installer Class
1478
- - `isInstalled()` - Check installation status
1479
- - `configureAppServerRoutes(app, appServer, appServerFactory, renderEngine)` - Setup installer routes
1480
- - `executeInstallProcess(req, res)` - Complete installation process
1481
- - `runSubprocessInstallation(dbConfig, templateVariables)` - Handle subprocess operations
1482
- - `checkAndInstallPackages(requiredPackages)` - Check and install dependencies
1483
- - `generateEntities(server, isOverride, isInstallationMode, isDryPrisma, dbConfig)` - Generate entities
1484
- - `createEnvFile(templateVariables)` - Create environment configuration
1485
- - `copyAdminDirectory()` - Copy admin assets and templates
1486
-
1487
- ### Frontend Architecture Classes
1488
-
1489
- #### Frontend Class (Orchestrator)
1490
- - `initialize()` - Set up frontend routes and templates
1491
- - `handleRequest(req, res)` - Main request handler
1492
- - `renderRoute(route, domain, res, req)` - Route-based rendering
1493
- - `setupStaticAssets()` - Configure static asset serving
1494
-
1495
- #### TemplateResolver Class
1496
- - `findTemplatePath(templateName, domain)` - Template discovery with domain fallback
1497
- - `findLayoutPath(layoutName, domain)` - Layout path resolution
1498
- - `findTemplateByPath(path, domain)` - Template lookup by URL path
1499
- - `resolveDomainToFolder(domain)` - Domain to folder mapping
1500
- - `resolveDomainToSiteKey(domain)` - Domain to site key mapping
1501
-
1502
- #### TemplateCache Class
1503
- - `loadPartials()` - Load and cache template partials
1504
- - `setupDomainTemplates()` - Initialize domain-specific templates
1505
- - `getPartialsForDomain(domain)` - Get domain-specific partials with fallback
1506
-
1507
- #### TemplateReloader Class
1508
- - `checkAndReloadAdminTemplates()` - Check and reload admin templates when changed
1509
- - `checkAndReloadFrontendTemplates()` - Check and reload frontend templates when changed
1510
- - `trackTemplateFiles(templatesPaths)` - Start tracking template files for changes
1511
- - `shouldReloadAdminTemplates(mappedAdminTemplates)` - Check if admin templates need reloading
1512
- - `shouldReloadFrontendTemplates(templatesPath, templateExtensions)` - Check if frontend templates need reloading
1513
- - `handleAdminTemplateReload(adminManager)` - Complete admin template reload process
1514
- - `handleFrontendTemplateReload(templateCache, templateResolver)` - Complete frontend template reload process
1515
-
1516
- #### RequestProcessor Class
1517
- - `findRouteByPath(path, domain)` - Database route lookup
1518
- - `handleRouteRedirect(route, res)` - Handle route redirects
1519
- - `getDomainFromRequest(req)` - Extract domain from request
1520
- - `buildCacheKey(path, req)` - Generate cache keys
1521
-
1522
- #### ContentRenderer Class
1523
- - `renderWithTemplateContent(content, data, domain, req, route)` - Main content rendering
1524
- - `generateRouteContent(route, domain, req)` - Route-based content generation
1525
- - `generateTemplateContent(templatePath, domain, req, data)` - Template-based content generation
1526
- - `fetchMetaFields(data)` - Process meta fields for templates
1527
-
1528
- #### EntityAccessManager Class
1529
- - `loadEntityAccessRules()` - Load entity access configuration
1530
- - `isEntityAccessible(entityName)` - Check entity accessibility
1531
- - `findEntityByPath(path)` - Entity lookup by URL path
1532
-
1533
- #### ResponseManager Class
1534
- - `renderWithCacheHandler(contentGenerator, errorHandler, responseHandler, domain, res, path, req)` - Generic cached response handler
1535
- - `renderNotFound(domain, res, req)` - 404 error handling
1536
-
1537
- #### SearchRequestHandler Class
1538
- - `handleSearchRequest(req, res)` - Process search requests with template data support
1539
-
1540
- ### TemplateEngine Class
1541
- - `render(template, data, partials, domain, req, route, currentEntityData)` - Main template rendering with enhanced context
1542
- - `processAllTemplateFunctions(template, domain, req, systemVariables)` - Process all template functions
1543
- - `buildEnhancedRenderData(data, systemVariables, currentEntityData)` - Build template context with system variables
1544
-
1545
- ### SystemVariablesProvider Class
1546
- - `buildSystemVariables(req, route, domain)` - Create system variables for templates
1547
- - `buildCurrentRequestData(req, domain)` - Build request context
1548
- - `buildCurrentRouteData(route)` - Build route context
1549
- - `buildCurrentDomainData(domain)` - Build domain context
1550
-
1551
- ### Search Classes
1552
- - `Search.parseSearchParameters(query)` - Parse search query parameters including templateData
1553
- - `Search.executeSearch(config)` - Execute search with configuration
1554
- - `SearchRenderer.renderSearchResults(searchResults, config, domain, req)` - Render search results with template data
1555
-
1556
- ### Forms System Classes
1557
-
1558
- #### DynamicForm Class
1559
- - `validateFormSubmission(formKey, submittedValues, req)` - Validate form submission
1560
- - `getFormConfig(formKey)` - Load form configuration from database
1561
- - `validateHoneypot(submittedValues)` - Check honeypot field for bots
1562
- - `validateFields(fieldsSchema, submittedValues)` - Schema-based field validation
1563
- - `prepareSubmittedValues(submittedValues, fieldsSchema)` - Process and normalize values
1564
- - `saveFormSubmission(formConfig, preparedValues)` - Save to database
1565
-
1566
- #### DynamicFormRenderer Class
1567
- - `renderForm(formConfig, fieldsToRender, domain, req, attributes)` - Render complete form
1568
- - `renderFormFields(fieldsToRender, domain, req)` - Render field set
1569
- - `renderFormField(field, domain, submittedValues, errors)` - Render individual field
1570
- - `loadFormTemplate(templateName, domain)` - Load form template with domain fallback
1571
- - `findFormTemplate(templateName, domain)` - Template discovery for forms
1572
-
1573
- #### DynamicFormRequestHandler Class
1574
- - `handleFormSubmission(req, res)` - Process POST form submissions
1575
- - `handleBadRequest(res, message)` - Handle validation errors
1576
- - `handleSuccessResponse(req, res, formKey, result)` - Handle successful submissions
1577
- - `buildErrorRedirectPath(req, error, formKey)` - Build error redirect URLs
1578
- - `buildSuccessRedirectPath(successRedirect, formKey)` - Build success redirect URLs
1579
-
1580
- #### FormsTransformer Class
1581
- - `transform(template, domain, req, systemVariables, enhancedData)` - Process cmsForm tags
1582
- - `findAllFormTags(template)` - Find cmsForm tags in template
1583
- - `parseFormAttributes(fullTag)` - Parse tag attributes
1584
- - `parseFieldsFilter(attributes, formConfig)` - Filter fields based on attributes
1585
-
1586
- ### AdminManager Class
1587
- - `setupAdmin()` - Initialize admin panel
1588
- - `generateListRouteContent()` - Entity list pages
1589
- - `generateEditRouteContent()` - Entity edit forms
1590
- - `processSaveEntity()` - Handle form submissions
1591
-
1592
- ## File Structure
270
+ Complete class and method reference for all CMS components.
1593
271
 
1594
- ```
1595
- project/
1596
- ├── admin/
1597
- │ └── templates/ # Admin panel templates
1598
- ├── lib/
1599
- │ ├── frontend/ # Frontend specialized classes
1600
- │ │ ├── template-resolver.js
1601
- │ │ ├── template-cache.js
1602
- │ │ ├── request-processor.js
1603
- │ │ ├── entity-access-manager.js
1604
- │ │ ├── content-renderer.js
1605
- │ │ └── response-manager.js
1606
- │ ├── template-engine/ # Template processing classes
1607
- │ │ └── forms-transformer.js
1608
- │ ├── frontend.js # Main Frontend orchestrator
1609
- │ ├── template-reloader.js # Template reloading functionality
1610
- │ ├── search-request-handler.js
1611
- │ ├── search.js # Search functionality
1612
- │ ├── search-renderer.js # Search result rendering
1613
- │ ├── dynamic-form.js # Forms validation and processing
1614
- │ ├── dynamic-form-renderer.js # Forms template rendering
1615
- │ ├── dynamic-form-request-handler.js # Forms request handling
1616
- │ ├── template-engine.js # Core template processing
1617
- │ ├── installer.js # Installation with subprocess handling
1618
- │ ├── manager.js # Main CMS orchestrator
1619
- │ ├── mysql-installer.js # MySQL-specific installation
1620
- │ └── prisma-subprocess-worker.js # Subprocess worker
1621
- ├── templates/
1622
- │ ├── layouts/ # Body content layouts
1623
- │ ├── domains/ # Domain-specific templates
1624
- │ │ └── example.com/
1625
- │ │ └── cms_forms/ # Domain-specific form templates
1626
- │ ├── partials/ # Shared template partials
1627
- │ ├── cms_forms/ # Default form templates
1628
- │ │ ├── form.html # Main form wrapper
1629
- │ │ ├── field_text.html # Text field template
1630
- │ │ ├── field_email.html # Email field template
1631
- │ │ └── field_select.html # Select field template
1632
- │ ├── page.html # Base HTML wrapper
1633
- │ └── 404.html # Error page
1634
- ├── translations/
1635
- │ ├── en.json # English translations
1636
- │ ├── es.json # Spanish translations
1637
- │ └── fr.json # French translations
1638
- ├── public/
1639
- │ ├── css/ # Stylesheets
1640
- │ ├── js/ # Client scripts
1641
- │ └── assets/ # Static assets
1642
- ├── entities/ # Generated entity classes
1643
- ├── .env # Environment configuration
1644
- ├── install.lock # Installation lock file
1645
- └── index.js # Main application file
1646
- ```
1647
-
1648
- ---
272
+ **Full documentation:** See `.claude/api-reference.md`
1649
273
 
1650
274
  ## Contributing
1651
275