@neohm/nestend 3.3.8 → 3.3.10
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 +238 -121
- package/dist/admin/admin.module.d.ts.map +1 -1
- package/dist/admin/admin.module.js.map +1 -1
- package/dist/admin/controllers/admin.api.key.controller.js +1 -1
- package/dist/admin/controllers/admin.api.key.controller.js.map +1 -1
- package/dist/admin/controllers/admin.email.template.controller.d.ts +24 -0
- package/dist/admin/controllers/admin.email.template.controller.d.ts.map +1 -0
- package/dist/admin/controllers/admin.email.template.controller.js +106 -0
- package/dist/admin/controllers/admin.email.template.controller.js.map +1 -0
- package/dist/admin/controllers/admin.pdf.template.controller.d.ts +24 -0
- package/dist/admin/controllers/admin.pdf.template.controller.d.ts.map +1 -0
- package/dist/admin/controllers/admin.pdf.template.controller.js +106 -0
- package/dist/admin/controllers/admin.pdf.template.controller.js.map +1 -0
- package/dist/admin/controllers/admin.scheduled.task.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.scheduled.task.controller.js +1 -1
- package/dist/admin/controllers/admin.scheduled.task.controller.js.map +1 -1
- package/dist/admin/controllers/admin.scope.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.scope.controller.js +1 -1
- package/dist/admin/controllers/admin.scope.controller.js.map +1 -1
- package/dist/admin/controllers/admin.system.user.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.system.user.controller.js +1 -1
- package/dist/admin/controllers/admin.system.user.controller.js.map +1 -1
- package/dist/admin/controllers/admin.tenant.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.tenant.controller.js +1 -1
- package/dist/admin/controllers/admin.tenant.controller.js.map +1 -1
- package/dist/admin/controllers/admin.tenant.user.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.tenant.user.controller.js +1 -1
- package/dist/admin/controllers/admin.tenant.user.controller.js.map +1 -1
- package/dist/admin/controllers/admin.user.controller.d.ts.map +1 -1
- package/dist/admin/controllers/admin.user.controller.js +1 -1
- package/dist/admin/controllers/admin.user.controller.js.map +1 -1
- package/dist/admin/controllers/system.role.controller.d.ts.map +1 -1
- package/dist/admin/controllers/system.role.controller.js +1 -1
- package/dist/admin/controllers/system.role.controller.js.map +1 -1
- package/dist/admin/dtos/add.admin.user.dto.d.ts +2 -2
- package/dist/admin/dtos/add.admin.user.dto.d.ts.map +1 -1
- package/dist/admin/dtos/add.admin.user.dto.js +2 -2
- package/dist/admin/dtos/add.admin.user.dto.js.map +1 -1
- package/dist/admin/dtos/add.email.template.dto.d.ts +9 -0
- package/dist/admin/dtos/add.email.template.dto.d.ts.map +1 -0
- package/dist/admin/dtos/add.email.template.dto.js +68 -0
- package/dist/admin/dtos/add.email.template.dto.js.map +1 -0
- package/dist/admin/dtos/add.pdf.template.dto.d.ts +8 -0
- package/dist/admin/dtos/add.pdf.template.dto.d.ts.map +1 -0
- package/dist/admin/dtos/add.pdf.template.dto.js +58 -0
- package/dist/admin/dtos/add.pdf.template.dto.js.map +1 -0
- package/dist/admin/dtos/add.scheduled.task.dto.d.ts +2 -2
- package/dist/admin/dtos/add.scheduled.task.dto.d.ts.map +1 -1
- package/dist/admin/dtos/add.scheduled.task.dto.js +2 -2
- package/dist/admin/dtos/add.scheduled.task.dto.js.map +1 -1
- package/dist/admin/dtos/add.scope.dto.d.ts +2 -2
- package/dist/admin/dtos/add.scope.dto.d.ts.map +1 -1
- package/dist/admin/dtos/add.scope.dto.js +2 -2
- package/dist/admin/dtos/add.scope.dto.js.map +1 -1
- package/dist/admin/dtos/add.system.role.dto.d.ts +2 -2
- package/dist/admin/dtos/add.system.role.dto.d.ts.map +1 -1
- package/dist/admin/dtos/add.system.role.dto.js +2 -2
- package/dist/admin/dtos/add.system.role.dto.js.map +1 -1
- package/dist/admin/dtos/add.tenant.dto.d.ts +3 -2
- package/dist/admin/dtos/add.tenant.dto.d.ts.map +1 -1
- package/dist/admin/dtos/add.tenant.dto.js +9 -2
- package/dist/admin/dtos/add.tenant.dto.js.map +1 -1
- package/dist/admin/dtos/base.user.dto.d.ts +2 -2
- package/dist/admin/dtos/base.user.dto.d.ts.map +1 -1
- package/dist/admin/dtos/base.user.dto.js +2 -2
- package/dist/admin/dtos/base.user.dto.js.map +1 -1
- package/dist/admin/dtos/email.template.list.dto.d.ts +4 -0
- package/dist/admin/dtos/email.template.list.dto.d.ts.map +1 -0
- package/dist/admin/dtos/email.template.list.dto.js +8 -0
- package/dist/admin/dtos/email.template.list.dto.js.map +1 -0
- package/dist/admin/dtos/pdf.template.list.dto.d.ts +4 -0
- package/dist/admin/dtos/pdf.template.list.dto.d.ts.map +1 -0
- package/dist/admin/dtos/pdf.template.list.dto.js +8 -0
- package/dist/admin/dtos/pdf.template.list.dto.js.map +1 -0
- package/dist/admin/es6.classes.d.ts +12 -4
- package/dist/admin/es6.classes.d.ts.map +1 -1
- package/dist/admin/es6.classes.js +22 -1
- package/dist/admin/es6.classes.js.map +1 -1
- package/dist/admin/libraries/admin.user.data.processor.js +1 -1
- package/dist/admin/libraries/admin.user.data.processor.js.map +1 -1
- package/dist/admin/libraries/admin.user.delete.processor.js +2 -2
- package/dist/admin/libraries/admin.user.delete.processor.js.map +1 -1
- package/dist/admin/libraries/api.key.data.processor.js +1 -1
- package/dist/admin/libraries/api.key.data.processor.js.map +1 -1
- package/dist/admin/libraries/bulk.user.data.processor.js +1 -1
- package/dist/admin/libraries/bulk.user.data.processor.js.map +1 -1
- package/dist/admin/libraries/client.user.data.processor.js +1 -1
- package/dist/admin/libraries/client.user.data.processor.js.map +1 -1
- package/dist/admin/libraries/client.user.delete.processor.js +1 -1
- package/dist/admin/libraries/client.user.delete.processor.js.map +1 -1
- package/dist/admin/libraries/email.template.data.processor.d.ts +13 -0
- package/dist/admin/libraries/email.template.data.processor.d.ts.map +1 -0
- package/dist/admin/libraries/email.template.data.processor.js +51 -0
- package/dist/admin/libraries/email.template.data.processor.js.map +1 -0
- package/dist/admin/libraries/email.template.list.processor.d.ts +15 -0
- package/dist/admin/libraries/email.template.list.processor.d.ts.map +1 -0
- package/dist/admin/libraries/email.template.list.processor.js +31 -0
- package/dist/admin/libraries/email.template.list.processor.js.map +1 -0
- package/dist/admin/libraries/pdf.template.data.processor.d.ts +13 -0
- package/dist/admin/libraries/pdf.template.data.processor.d.ts.map +1 -0
- package/dist/admin/libraries/pdf.template.data.processor.js +48 -0
- package/dist/admin/libraries/pdf.template.data.processor.js.map +1 -0
- package/dist/admin/libraries/pdf.template.list.processor.d.ts +15 -0
- package/dist/admin/libraries/pdf.template.list.processor.d.ts.map +1 -0
- package/dist/admin/libraries/pdf.template.list.processor.js +30 -0
- package/dist/admin/libraries/pdf.template.list.processor.js.map +1 -0
- package/dist/admin/libraries/scheduled.task.data.processor.js +1 -1
- package/dist/admin/libraries/scheduled.task.data.processor.js.map +1 -1
- package/dist/admin/libraries/scheduled.task.delete.processor.js +1 -1
- package/dist/admin/libraries/scheduled.task.delete.processor.js.map +1 -1
- package/dist/admin/libraries/scope.data.processor.js +1 -1
- package/dist/admin/libraries/scope.data.processor.js.map +1 -1
- package/dist/admin/libraries/system.role.data.processor.js +1 -1
- package/dist/admin/libraries/system.role.data.processor.js.map +1 -1
- package/dist/admin/libraries/system.user.data.processor.js +1 -1
- package/dist/admin/libraries/system.user.data.processor.js.map +1 -1
- package/dist/admin/libraries/tenant.data.processor.d.ts.map +1 -1
- package/dist/admin/libraries/tenant.data.processor.js +2 -1
- package/dist/admin/libraries/tenant.data.processor.js.map +1 -1
- package/dist/admin/libraries/tenant.delete.processor.js +1 -1
- package/dist/admin/libraries/tenant.delete.processor.js.map +1 -1
- package/dist/admin/services/template.service.d.ts +7 -0
- package/dist/admin/services/template.service.d.ts.map +1 -0
- package/dist/admin/services/template.service.js +34 -0
- package/dist/admin/services/template.service.js.map +1 -0
- package/dist/auth/processors/change.password.data.processor.js +2 -2
- package/dist/auth/processors/change.password.data.processor.js.map +1 -1
- package/dist/auth/processors/register.user.data.processor.js +1 -1
- package/dist/auth/processors/register.user.data.processor.js.map +1 -1
- package/dist/auth/services/impersonation.service.d.ts.map +1 -1
- package/dist/auth/services/impersonation.service.js +4 -3
- package/dist/auth/services/impersonation.service.js.map +1 -1
- package/dist/common/dtos/common.data.dto.d.ts +4 -0
- package/dist/common/dtos/common.data.dto.d.ts.map +1 -0
- package/dist/common/dtos/{common.payload.dto.js → common.data.dto.js} +5 -5
- package/dist/common/dtos/common.data.dto.js.map +1 -0
- package/dist/common/dtos/common.list.filter.dto.d.ts.map +1 -1
- package/dist/common/dtos/common.list.filter.dto.js +22 -0
- package/dist/common/dtos/common.list.filter.dto.js.map +1 -1
- package/dist/common/dtos/common.params.dto.d.ts +1 -0
- package/dist/common/dtos/common.params.dto.d.ts.map +1 -1
- package/dist/common/dtos/common.params.dto.js +6 -0
- package/dist/common/dtos/common.params.dto.js.map +1 -1
- package/dist/common/dtos/filter.dto.d.ts.map +1 -1
- package/dist/common/dtos/filter.dto.js +15 -0
- package/dist/common/dtos/filter.dto.js.map +1 -1
- package/dist/common/dtos/sort.dto.d.ts.map +1 -1
- package/dist/common/dtos/sort.dto.js +8 -0
- package/dist/common/dtos/sort.dto.js.map +1 -1
- package/dist/common/entities/system.audit.log.entity.d.ts +3 -3
- package/dist/common/entities/system.audit.log.entity.d.ts.map +1 -1
- package/dist/common/entities/system.audit.log.entity.js +6 -6
- package/dist/common/entities/system.audit.log.entity.js.map +1 -1
- package/dist/common/es6.classes.d.ts +2 -2
- package/dist/common/es6.classes.d.ts.map +1 -1
- package/dist/common/es6.classes.js +2 -2
- package/dist/common/es6.classes.js.map +1 -1
- package/dist/common/libraries/common.data.processor.d.ts +1 -1
- package/dist/common/libraries/common.data.processor.d.ts.map +1 -1
- package/dist/common/libraries/common.data.processor.js +1 -1
- package/dist/common/libraries/common.data.processor.js.map +1 -1
- package/dist/common/services/audit.service.d.ts +4 -4
- package/dist/common/services/audit.service.js +15 -15
- package/dist/common/services/audit.service.js.map +1 -1
- package/dist/common/services/queue.service.d.ts +3 -2
- package/dist/common/services/queue.service.d.ts.map +1 -1
- package/dist/common/services/queue.service.js +15 -1
- package/dist/common/services/queue.service.js.map +1 -1
- package/dist/common/subscribers/common.subscriber.d.ts.map +1 -1
- package/dist/common/subscribers/common.subscriber.js.map +1 -1
- package/dist/common/types/audit.types.d.ts +4 -4
- package/dist/common/types/audit.types.d.ts.map +1 -1
- package/dist/common/types/queue.types.d.ts +10 -0
- package/dist/common/types/queue.types.d.ts.map +1 -1
- package/dist/constants/entity.constants.d.ts +1 -0
- package/dist/constants/entity.constants.d.ts.map +1 -1
- package/dist/constants/entity.constants.js +1 -0
- package/dist/constants/entity.constants.js.map +1 -1
- package/dist/constants/entity.maps.d.ts +2 -0
- package/dist/constants/entity.maps.d.ts.map +1 -1
- package/dist/constants/entity.maps.js +2 -0
- package/dist/constants/entity.maps.js.map +1 -1
- package/dist/constants/job.constants.d.ts +1 -0
- package/dist/constants/job.constants.d.ts.map +1 -1
- package/dist/constants/job.constants.js +1 -0
- package/dist/constants/job.constants.js.map +1 -1
- package/dist/document/controllers/document.controller.js +1 -1
- package/dist/document/controllers/document.controller.js.map +1 -1
- package/dist/document/document.module.d.ts.map +1 -1
- package/dist/document/document.module.js +18 -2
- package/dist/document/document.module.js.map +1 -1
- package/dist/document/dtos/pdf.document.options.dto.d.ts +17 -0
- package/dist/document/dtos/pdf.document.options.dto.d.ts.map +1 -0
- package/dist/document/dtos/pdf.document.options.dto.js +13 -0
- package/dist/document/dtos/pdf.document.options.dto.js.map +1 -0
- package/dist/document/dtos/pdf.template.attributes.dto.d.ts +3 -0
- package/dist/document/dtos/pdf.template.attributes.dto.d.ts.map +1 -0
- package/dist/document/dtos/pdf.template.attributes.dto.js +7 -0
- package/dist/document/dtos/pdf.template.attributes.dto.js.map +1 -0
- package/dist/document/entities/document.entity.d.ts +1 -0
- package/dist/document/entities/document.entity.d.ts.map +1 -1
- package/dist/document/entities/document.entity.js +4 -0
- package/dist/document/entities/document.entity.js.map +1 -1
- package/dist/document/entities/pdf.template.entity.d.ts +9 -0
- package/dist/document/entities/pdf.template.entity.d.ts.map +1 -0
- package/dist/document/entities/pdf.template.entity.js +38 -0
- package/dist/document/entities/pdf.template.entity.js.map +1 -0
- package/dist/document/enums/document.default.enum.d.ts +3 -1
- package/dist/document/enums/document.default.enum.d.ts.map +1 -1
- package/dist/document/enums/document.default.enum.js +2 -0
- package/dist/document/enums/document.default.enum.js.map +1 -1
- package/dist/document/enums/pdf.default.enum.d.ts +2 -1
- package/dist/document/enums/pdf.default.enum.d.ts.map +1 -1
- package/dist/document/enums/pdf.default.enum.js +1 -0
- package/dist/document/enums/pdf.default.enum.js.map +1 -1
- package/dist/document/es6.classes.d.ts +8 -6
- package/dist/document/es6.classes.d.ts.map +1 -1
- package/dist/document/es6.classes.js +16 -3
- package/dist/document/es6.classes.js.map +1 -1
- package/dist/document/jobs/clean.orphan.documents.job.d.ts +16 -0
- package/dist/document/jobs/clean.orphan.documents.job.d.ts.map +1 -0
- package/dist/document/jobs/clean.orphan.documents.job.js +63 -0
- package/dist/document/jobs/clean.orphan.documents.job.js.map +1 -0
- package/dist/document/libraries/process.pdf.document.d.ts +16 -0
- package/dist/document/libraries/process.pdf.document.d.ts.map +1 -0
- package/dist/document/libraries/process.pdf.document.js +47 -0
- package/dist/document/libraries/process.pdf.document.js.map +1 -0
- package/dist/document/services/document.service.d.ts +3 -2
- package/dist/document/services/document.service.d.ts.map +1 -1
- package/dist/document/services/document.service.js +14 -2
- package/dist/document/services/document.service.js.map +1 -1
- package/dist/document/services/pdf.template.service.d.ts +12 -0
- package/dist/document/services/pdf.template.service.d.ts.map +1 -0
- package/dist/document/services/pdf.template.service.js +57 -0
- package/dist/document/services/pdf.template.service.js.map +1 -0
- package/dist/document/subscribers/document.subscriber.d.ts +2 -0
- package/dist/document/subscribers/document.subscriber.d.ts.map +1 -1
- package/dist/document/subscribers/document.subscriber.js +13 -0
- package/dist/document/subscribers/document.subscriber.js.map +1 -1
- package/dist/document/types/document.types.d.ts +3 -0
- package/dist/document/types/document.types.d.ts.map +1 -1
- package/dist/document/types/pdf.types.d.ts +4 -0
- package/dist/document/types/pdf.types.d.ts.map +1 -1
- package/dist/index.d.ts +9 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/index.js.map +1 -1
- package/dist/migrations/1749600000002.CreateSysTenantsTable.d.ts.map +1 -1
- package/dist/migrations/1749600000002.CreateSysTenantsTable.js +1 -0
- package/dist/migrations/1749600000002.CreateSysTenantsTable.js.map +1 -1
- package/dist/migrations/1749600000012.CreateSysAuditLogsTable.d.ts.map +1 -1
- package/dist/migrations/1749600000012.CreateSysAuditLogsTable.js +4 -4
- package/dist/migrations/1749600000012.CreateSysAuditLogsTable.js.map +1 -1
- package/dist/migrations/1749600000020.CreateSysEmailTemplatesTable.d.ts.map +1 -1
- package/dist/migrations/1749600000020.CreateSysEmailTemplatesTable.js +11 -1
- package/dist/migrations/1749600000020.CreateSysEmailTemplatesTable.js.map +1 -1
- package/dist/migrations/1749600000028.CreateSysDocumentsTable.d.ts.map +1 -1
- package/dist/migrations/1749600000028.CreateSysDocumentsTable.js +1 -0
- package/dist/migrations/1749600000028.CreateSysDocumentsTable.js.map +1 -1
- package/dist/migrations/1749600000029.CreateSysPdfTemplatesTable.d.ts +6 -0
- package/dist/migrations/1749600000029.CreateSysPdfTemplatesTable.d.ts.map +1 -0
- package/dist/migrations/1749600000029.CreateSysPdfTemplatesTable.js +33 -0
- package/dist/migrations/1749600000029.CreateSysPdfTemplatesTable.js.map +1 -0
- package/dist/notification/entities/email.template.entity.d.ts +2 -2
- package/dist/notification/entities/email.template.entity.d.ts.map +1 -1
- package/dist/notification/entities/email.template.entity.js +2 -2
- package/dist/notification/entities/email.template.entity.js.map +1 -1
- package/dist/tenant/entities/tenant.entity.d.ts +1 -0
- package/dist/tenant/entities/tenant.entity.d.ts.map +1 -1
- package/dist/tenant/entities/tenant.entity.js +5 -1
- package/dist/tenant/entities/tenant.entity.js.map +1 -1
- package/package.json +1 -1
- package/dist/common/dtos/common.payload.dto.d.ts +0 -4
- package/dist/common/dtos/common.payload.dto.d.ts.map +0 -1
- package/dist/common/dtos/common.payload.dto.js.map +0 -1
package/README.md
CHANGED
|
@@ -101,33 +101,33 @@ these are free to change without a breaking release.
|
|
|
101
101
|
|
|
102
102
|
## Feature Map
|
|
103
103
|
|
|
104
|
-
| Area | Key exports | Notes
|
|
105
|
-
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
106
|
-
| Entities | `RootEntity`, `CommonEntity`, `CommonAdminEntity`, `UserEntity`, `AdminUserEntity`, `SystemRoleEntity`, `SystemAdminRoleEntity`, ... | uuid PK, soft deletes, `created_by`/`updated_by` (+ `creator`/`updater` relations), jsonb `attributes`; entity-level `findOneOrNew`, `hardDelete`, `restore`, `paginate` statics plus a row-wise `softDelete()`
|
|
107
|
-
| Migrations | `MigrationUtility`, `SeederUtility` (the
|
|
108
|
-
| Queue | `QueueModule`, `QueueModule.withConnection()`, `QueueService`, `CommonJob`
|
|
109
|
-
| Scheduled events | `SystemScheduledTaskEntity`, `ScheduledEventEntity`, `ScheduleKind`, `ScheduleSpec`, `IsCronExpression` | schedule definitions in `sys_scheduled_tasks` → events in `sys_scheduled_events`; interval / cron / once specs with timezones, drift-free slots, horizon materialisation, catch-up + overlap policies, fire-once dispatch with backoff, cadence via `SCHEDULED_EVENT_MIN`
|
|
110
|
-
| Auth | `AuthModule`, `AuthService.validateRole()`, `RoleTier`, `TenantRole`, `Auth`, `JwtMiddleware`
|
|
111
|
-
| Admin auth | `AdminAuthService.validateRole()`, `AdminAuth`, `AdminJwtMiddleware` (endpoints at `/admin/auth`) | fully separate stack for back-office admins — own users table (`sys_admin_users`), own roles (`sys_admin_roles`/`sys_auser_roles`), own sessions/refresh tokens (shared tables, `is_admin` flag)
|
|
112
|
-
| API keys | `ApiKeyService`, `ApiKeyGuard` / `ApiKeyMiddleware`, `@ApiKeyScopes()`, `ApiKeyContext`
|
|
113
|
-
| Scopes | `ScopeService`, `AuthService.validateAccess()`, `ScopeEntity`, `TenantScopeEntity`, `ApiScopeEntity`
|
|
114
|
-
| Multi-tenancy | `TenantModule`, `TenantEntity`, `CommonTenantEntity`, `TenantContextService`, `TenantContextMiddleware` | AsyncLocalStorage tenant context; resolve by header, slug, or domain; tenants are onboarded by admins (`TenantEntity.creator` → `AdminUserEntity`)
|
|
115
|
-
| Admin | `AdminModule` (its controllers, DTOs and processors are internal — import the module and hit the endpoints), `GlobalAdminRoles` | back-office APIs: tenant onboarding, client/admin/system user registration (single + bulk), the `has_global_access` grant that enables impersonation, tenant + scheduled-task listing; 4-tier `GlobalAdminRoles` gate over `sys_admin_roles`; Swagger at `/docs` (hidden in production)
|
|
116
|
-
| Notifications | `NotificationModule`, `NotificationService`, `FcmPushNotificationService` | in-app rows + FCM push (optional `firebase-admin`); device token registry
|
|
117
|
-
| Email (SES) | `EmailNotificationService`, `ProcessEmailNotification`, `EmailTemplateEntity`, `EmailNotificationLogEntity` | templates in `sys_email_templates` rendered by a remote service, dispatched via SES per recipient (to/cc/bcc); logged to `sys_email_notification_logs` outside production; dispatch skipped when `AWS_SES_FROM_ADDRESS` unset
|
|
118
|
-
| Audit | `AuditService` | `saveAuditLog(old, new)` with changed-value diffing into `sys_audit_logs`
|
|
119
|
-
| Cache | `CacheService` | Redis, TTL objects (`{days, hours, min}`), transparent encryption
|
|
120
|
-
| Encryption | `EncryptionService` | AES-256-GCM, scrypt-derived keys
|
|
121
|
-
| System properties | `SystemPropertyModule`, `SystemPropertyService` | cached + optionally encrypted key/value settings in `sys_properties`
|
|
122
|
-
| Documents | `DocumentModule`, `DocumentService`, `S3BucketDocumentService`, `MinioBucketDocumentService` | `POST /api/documents/upload` (multipart) or `uploadDocument(multerFiles) → links[]`; driver chosen via `DOCUMENT_STORAGE_DRIVER`
|
|
123
|
-
| PDF
|
|
124
|
-
| HTTP | `CommonController`, `GlobalExceptionFilter`, common DTOs | health endpoint base controller, uniform error envelope
|
|
125
|
-
| Raw SQL | `SqlService`, `SqlTarget` | named `:key` placeholders always bound as driver params, `:...key` array expansion; runs on the read replica (`DB_READ_*`) with transparent primary fallback; `fetchOne`/`fetchScalar`/`exists`/`count`/`paginate` helpers
|
|
126
|
-
| List endpoints | `CommonListProcessor`, `CommonListFilterDto`, `FilterDto`, `CommonListFilterConfig` | allowlisted columns, parameterised operators (`eq`…`isNull`), typed value casting (`str`/`num`/`date`/`bool`/`null`), multi-sort + paginated on the read replica
|
|
127
|
-
| Outbound HTTP | `RemoteRequestService`, `ApiRequestTypeEnum`, `ApiContentTypeEnum` | injectable `fetch` wrapper (global via `CommonModule`); `getApiResponse<T>()` parses JSON/text and throws `OperationException` on non-2xx, `getApiRawResponse()` returns the raw `Response`; JSON + form-urlencoded body serialisation
|
|
128
|
-
| Observability | `LoggingInterceptor`, `MonitoringInterceptor`, `MonitoringService` | Sentry auto-registers when `@sentry/node` + `SENTRY_DSN` present; pluggable `MonitoringAdapter`
|
|
129
|
-
| Jobs/Events | `CommonJob`, `CommonSubscriber`, `CommonDataProcessor` | dispatch/handle separation, discovery-based registration, worker-event lifecycle, column-change detection, `created_by`/`updated_by` stamped from the `Auth` context in `beforeInsert`/`beforeUpdate`, field error collection
|
|
130
|
-
| Constants & maps | `baseEntityConstants`, `baseJobConstants`, `baseEntityMaps`, `baseJobMaps` | centralised dotted identifiers (`as const`) plus identifier → class maps of every concrete entity and job (abstract bases excluded); map keys are computed from the constants, so renames propagate at compile time
|
|
104
|
+
| Area | Key exports | Notes |
|
|
105
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| Entities | `RootEntity`, `CommonEntity`, `CommonAdminEntity`, `UserEntity`, `AdminUserEntity`, `SystemRoleEntity`, `SystemAdminRoleEntity`, ... | uuid PK, soft deletes, `created_by`/`updated_by` (+ `creator`/`updater` relations), jsonb `attributes`; entity-level `findOneOrNew`, `hardDelete`, `restore`, `paginate` statics plus a row-wise `softDelete()` |
|
|
107
|
+
| Migrations | `MigrationUtility`, `SeederUtility` (the 29 table migrations ship in `dist/migrations` — glob them, they are not barrel exports) | fluent `this.string()`, `this.numeric()`, `addRecord()` seeding |
|
|
108
|
+
| Queue | `QueueModule`, `QueueModule.withConnection()`, `QueueService`, `CommonJob` | BullMQ `@Processor`/`WorkerHost` consumer; auto-discovered jobs, duplicate prevention, recurring schedulers, completed jobs cleared from Redis on success (1-day TTL on failures), `clearQueue()`, retries, failures persisted to `sys_failed_jobs`; connection from `BULL_REDIS_*` or supplied by the app — never register `BullModule.forRoot()` yourself, two roots race over the worker's connection |
|
|
109
|
+
| Scheduled events | `SystemScheduledTaskEntity`, `ScheduledEventEntity`, `ScheduleKind`, `ScheduleSpec`, `IsCronExpression` | schedule definitions in `sys_scheduled_tasks` → events in `sys_scheduled_events`; interval / cron / once specs with timezones, drift-free slots, horizon materialisation, catch-up + overlap policies, fire-once dispatch with backoff, cadence via `SCHEDULED_EVENT_MIN` |
|
|
110
|
+
| Auth | `AuthModule`, `AuthService.validateRole()`, `RoleTier`, `TenantRole`, `Auth`, `JwtMiddleware` | imperative auth (open by default); `<module>.<tier>` role hierarchy (manager > writer > viewer), with `global.admin` overriding every role check; JwtMiddleware resolves the user into `Auth`'s AsyncLocalStorage context per request; access + rotating refresh tokens; session-based logout invalidation (`sys_auth_sessions`); Google SSO via ID token at `POST /auth/google`; impersonation — a tenantless `has_global_access` user signs into one organization at `POST /auth/impersonation/login` |
|
|
111
|
+
| Admin auth | `AdminAuthService.validateRole()`, `AdminAuth`, `AdminJwtMiddleware` (endpoints at `/admin/auth`) | fully separate stack for back-office admins — own users table (`sys_admin_users`), own roles (`sys_admin_roles`/`sys_auser_roles`), own sessions/refresh tokens (shared tables, `is_admin` flag) |
|
|
112
|
+
| API keys | `ApiKeyService`, `ApiKeyGuard` / `ApiKeyMiddleware`, `@ApiKeyScopes()`, `ApiKeyContext` | hashed keys in `sys_api_keys` (admin-owned), one tenant per key, expiry/revocation, `x-api-key` header; `validateAccess(scope)` returns the tenant the key acts for |
|
|
113
|
+
| Scopes | `ScopeService`, `AuthService.validateAccess()`, `ScopeEntity`, `TenantScopeEntity`, `ApiScopeEntity` | one feature named once in `sys_scopes`, then granted per organization (`sys_tenant_scopes`) or per API key (`sys_api_scopes`) — never an array on the principal |
|
|
114
|
+
| Multi-tenancy | `TenantModule`, `TenantEntity`, `CommonTenantEntity`, `TenantContextService`, `TenantContextMiddleware` | AsyncLocalStorage tenant context; resolve by header, slug, or domain; tenants are onboarded by admins (`TenantEntity.creator` → `AdminUserEntity`) |
|
|
115
|
+
| Admin | `AdminModule` (its controllers, DTOs and processors are internal — import the module and hit the endpoints), `GlobalAdminRoles` | back-office APIs: tenant onboarding, client/admin/system user registration (single + bulk), the `has_global_access` grant that enables impersonation, tenant + scheduled-task listing, the email/PDF template catalogues (read at content-reader, written at content-manager, deleted at global admin); 4-tier `GlobalAdminRoles` gate over `sys_admin_roles`; Swagger at `/docs` (hidden in production) |
|
|
116
|
+
| Notifications | `NotificationModule`, `NotificationService`, `FcmPushNotificationService` | in-app rows + FCM push (optional `firebase-admin`); device token registry |
|
|
117
|
+
| Email (SES) | `EmailNotificationService`, `ProcessEmailNotification`, `EmailTemplateEntity`, `EmailNotificationLogEntity` | templates in `sys_email_templates` (managed from the admin surface) rendered by a remote service, dispatched via SES per recipient (to/cc/bcc); logged to `sys_email_notification_logs` outside production; dispatch skipped when `AWS_SES_FROM_ADDRESS` unset |
|
|
118
|
+
| Audit | `AuditService` | `saveAuditLog(old, new)` with changed-value diffing into `sys_audit_logs` |
|
|
119
|
+
| Cache | `CacheService` | Redis, TTL objects (`{days, hours, min}`), transparent encryption |
|
|
120
|
+
| Encryption | `EncryptionService` | AES-256-GCM, scrypt-derived keys |
|
|
121
|
+
| System properties | `SystemPropertyModule`, `SystemPropertyService` | cached + optionally encrypted key/value settings in `sys_properties` |
|
|
122
|
+
| Documents | `DocumentModule`, `DocumentService`, `S3BucketDocumentService`, `MinioBucketDocumentService` | `POST /api/documents/upload` (multipart) or `uploadDocument(multerFiles) → links[]`; driver chosen via `DOCUMENT_STORAGE_DRIVER`; `sys_document_uploads` is the file, `sys_documents` the link to one of your records (`system_generated` marks the ones the backend produced itself) |
|
|
123
|
+
| PDF documents | `PdfService`, `PdfTemplateService`, `ProcessPdfDocument`, `PdfTemplateEntity`, `PdfRenderOptions` | templates in `sys_pdf_templates` rendered to HTML by a remote service, rasterized by headless Chromium (`renderHtmlToPdf(html) → Buffer`, honours the document's own `@page` rules and paginates to any length), then stored — `ProcessPdfDocument` writes both rows and flags the `sys_documents` one `system_generated`; optional peer dep `puppeteer` |
|
|
124
|
+
| HTTP | `CommonController`, `GlobalExceptionFilter`, common DTOs | health endpoint base controller, uniform error envelope |
|
|
125
|
+
| Raw SQL | `SqlService`, `SqlTarget` | named `:key` placeholders always bound as driver params, `:...key` array expansion; runs on the read replica (`DB_READ_*`) with transparent primary fallback; `fetchOne`/`fetchScalar`/`exists`/`count`/`paginate` helpers |
|
|
126
|
+
| List endpoints | `CommonListProcessor`, `CommonListFilterDto`, `FilterDto`, `CommonListFilterConfig` | allowlisted columns, parameterised operators (`eq`…`isNull`), typed value casting (`str`/`num`/`date`/`bool`/`null`), multi-sort + paginated on the read replica |
|
|
127
|
+
| Outbound HTTP | `RemoteRequestService`, `ApiRequestTypeEnum`, `ApiContentTypeEnum` | injectable `fetch` wrapper (global via `CommonModule`); `getApiResponse<T>()` parses JSON/text and throws `OperationException` on non-2xx, `getApiRawResponse()` returns the raw `Response`; JSON + form-urlencoded body serialisation |
|
|
128
|
+
| Observability | `LoggingInterceptor`, `MonitoringInterceptor`, `MonitoringService` | Sentry auto-registers when `@sentry/node` + `SENTRY_DSN` present; pluggable `MonitoringAdapter` |
|
|
129
|
+
| Jobs/Events | `CommonJob`, `CommonSubscriber`, `CommonDataProcessor` | dispatch/handle separation, discovery-based registration, worker-event lifecycle, column-change detection, `created_by`/`updated_by` stamped from the `Auth` context in `beforeInsert`/`beforeUpdate`, field error collection |
|
|
130
|
+
| Constants & maps | `baseEntityConstants`, `baseJobConstants`, `baseEntityMaps`, `baseJobMaps` | centralised dotted identifiers (`as const`) plus identifier → class maps of every concrete entity and job (abstract bases excluded); map keys are computed from the constants, so renames propagate at compile time |
|
|
131
131
|
|
|
132
132
|
## Environment Variables
|
|
133
133
|
|
|
@@ -209,7 +209,10 @@ sold to them?" and "is this person allowed to use it?" in that order.
|
|
|
209
209
|
|
|
210
210
|
```ts
|
|
211
211
|
const { user } = await this.authService.validateAccess('billing.invoices');
|
|
212
|
-
const { user } = await this.authService.validateAccess(
|
|
212
|
+
const { user } = await this.authService.validateAccess(
|
|
213
|
+
'billing.invoices',
|
|
214
|
+
'invoice.writer',
|
|
215
|
+
);
|
|
213
216
|
```
|
|
214
217
|
|
|
215
218
|
`AuthController` (mounted at `/auth`) exposes the ready-made flows: `POST
|
|
@@ -308,8 +311,8 @@ admin with the `admin` role can grant `sys_admin_roles` to other admins via
|
|
|
308
311
|
|
|
309
312
|
### Encryption
|
|
310
313
|
|
|
311
|
-
| Variable | Default | Description
|
|
312
|
-
| ------------------- | ------- |
|
|
314
|
+
| Variable | Default | Description |
|
|
315
|
+
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
313
316
|
| `ENCRYPTION_SECRET` | — | **Required** when `EncryptionService` is used — which now includes issuing API keys (`key_encrypted`). AES-256-GCM key material. Min 32 chars |
|
|
314
317
|
|
|
315
318
|
### Cache (Redis)
|
|
@@ -323,21 +326,21 @@ admin with the `admin` role can grant `sys_admin_roles` to other admins via
|
|
|
323
326
|
|
|
324
327
|
### Queue (BullMQ / Redis)
|
|
325
328
|
|
|
326
|
-
| Variable | Default | Description
|
|
327
|
-
| ----------------------------------- | ------------- |
|
|
328
|
-
| `BULL_REDIS_HOST` | `localhost` | Redis host for BullMQ
|
|
329
|
-
| `BULL_REDIS_PORT` | `6379` | Redis port
|
|
330
|
-
| `BULL_REDIS_PASSWORD` | _(empty)_ | Redis password
|
|
331
|
-
| `BULL_REDIS_DB` | `0` | Redis DB index
|
|
332
|
-
| `BULL_QUEUE_NAME` | `myapp-queue` | Name of the BullMQ queue
|
|
333
|
-
| `BULL_WORKERS` | `2` | Concurrent worker threads per process
|
|
334
|
-
| `BULL_QUEUE_WORKER` | `false` | Set `true` on consumer instances only — API servers should leave this `false`
|
|
329
|
+
| Variable | Default | Description |
|
|
330
|
+
| ----------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
331
|
+
| `BULL_REDIS_HOST` | `localhost` | Redis host for BullMQ |
|
|
332
|
+
| `BULL_REDIS_PORT` | `6379` | Redis port |
|
|
333
|
+
| `BULL_REDIS_PASSWORD` | _(empty)_ | Redis password |
|
|
334
|
+
| `BULL_REDIS_DB` | `0` | Redis DB index |
|
|
335
|
+
| `BULL_QUEUE_NAME` | `myapp-queue` | Name of the BullMQ queue |
|
|
336
|
+
| `BULL_WORKERS` | `2` | Concurrent worker threads per process |
|
|
337
|
+
| `BULL_QUEUE_WORKER` | `false` | Set `true` on consumer instances only — API servers should leave this `false` |
|
|
335
338
|
| `BULL_JOB_TTL_SECONDS` | `86400` | Retention (seconds) for **failed** jobs before removal from the queue + Redis (default 1 day); completed jobs are removed immediately |
|
|
336
|
-
| `MAX_JOB_RETRIES` | `3` | Max attempts per job (BullMQ `attempts` default) and the scheduled-event retry ceiling
|
|
337
|
-
| `SCHEDULED_EVENT_MIN` | `0` | `SetScheduledEventJob` cadence in minutes (every N minutes); `0`/unset disables self-scheduling
|
|
338
|
-
| `SCHEDULED_EVENT_HORIZON_MIN` | _(derived)_ | How far ahead occurrences are materialised; defaults to 2× the cadence, floored at one minute
|
|
339
|
-
| `SCHEDULED_EVENT_BATCH` | `500` | Occurrences dispatched per pass
|
|
340
|
-
| `ENABLE_SCHEDULED_EVENTS_EXECUTION` | _unset_ | Must be exactly `true` to arm scheduled-event materialisation and dispatch
|
|
339
|
+
| `MAX_JOB_RETRIES` | `3` | Max attempts per job (BullMQ `attempts` default) and the scheduled-event retry ceiling |
|
|
340
|
+
| `SCHEDULED_EVENT_MIN` | `0` | `SetScheduledEventJob` cadence in minutes (every N minutes); `0`/unset disables self-scheduling |
|
|
341
|
+
| `SCHEDULED_EVENT_HORIZON_MIN` | _(derived)_ | How far ahead occurrences are materialised; defaults to 2× the cadence, floored at one minute |
|
|
342
|
+
| `SCHEDULED_EVENT_BATCH` | `500` | Occurrences dispatched per pass |
|
|
343
|
+
| `ENABLE_SCHEDULED_EVENTS_EXECUTION` | _unset_ | Must be exactly `true` to arm scheduled-event materialisation and dispatch |
|
|
341
344
|
|
|
342
345
|
#### Jobs
|
|
343
346
|
|
|
@@ -493,7 +496,7 @@ jitter lands on the occurrence, never on the cursor, so it cannot accumulate.
|
|
|
493
496
|
##### Creating and editing a task
|
|
494
497
|
|
|
495
498
|
`POST /api/admin/scheduled-tasks` is an upsert. `AddScheduledTaskDto` extends
|
|
496
|
-
`
|
|
499
|
+
`CommonDataDto`, so it carries an optional `id`: send one to edit that task, omit
|
|
497
500
|
it to create. `identifier`, `name` and `schedule` are required by the DTO either way;
|
|
498
501
|
`identifier` uniqueness is checked in the processor, excluding the row being edited.
|
|
499
502
|
|
|
@@ -571,12 +574,12 @@ a subscriber only starts firing once something constructs it.
|
|
|
571
574
|
|
|
572
575
|
### Document Storage
|
|
573
576
|
|
|
574
|
-
| Variable | Default | Description
|
|
575
|
-
| --------------------------------------- | ------- |
|
|
576
|
-
| `DOCUMENT_STORAGE_DRIVER` | `minio` | Active driver: `s3` or `minio`
|
|
577
|
-
| `DOCUMENT_PRESIGNED_URL_EXPIRY_SECONDS` | `3600` | Presigned download URL validity in seconds
|
|
577
|
+
| Variable | Default | Description |
|
|
578
|
+
| --------------------------------------- | ------- | ---------------------------------------------- |
|
|
579
|
+
| `DOCUMENT_STORAGE_DRIVER` | `minio` | Active driver: `s3` or `minio` |
|
|
580
|
+
| `DOCUMENT_PRESIGNED_URL_EXPIRY_SECONDS` | `3600` | Presigned download URL validity in seconds |
|
|
578
581
|
| `DOCUMENT_MAX_UPLOAD_FILES` | `10` | Files per `POST /api/documents/upload` request |
|
|
579
|
-
| `DOCUMENT_MAX_UPLOAD_SIZE_MB` | `25` | Per-file size ceiling on that endpoint
|
|
582
|
+
| `DOCUMENT_MAX_UPLOAD_SIZE_MB` | `25` | Per-file size ceiling on that endpoint |
|
|
580
583
|
|
|
581
584
|
**S3 driver** (`DOCUMENT_STORAGE_DRIVER=s3`):
|
|
582
585
|
|
|
@@ -602,12 +605,12 @@ a subscriber only starts firing once something constructs it.
|
|
|
602
605
|
| `MINIO_BUCKET` | `documents` | Bucket name (auto-created if missing) |
|
|
603
606
|
| `MINIO_PUBLIC_URL` | _(empty)_ | CDN base URL when MinIO sits behind a proxy |
|
|
604
607
|
|
|
605
|
-
### PDF
|
|
608
|
+
### PDF Documents
|
|
606
609
|
|
|
607
610
|
Requires the optional peer dependency `puppeteer`.
|
|
608
611
|
|
|
609
|
-
| Variable | Default | Description
|
|
610
|
-
| ------------------------- | --------- |
|
|
612
|
+
| Variable | Default | Description |
|
|
613
|
+
| ------------------------- | --------- | ----------------------------------------------------------------------------------- |
|
|
611
614
|
| `PDF_CHROMIUM_PATH` | _(empty)_ | Path to an existing Chromium/Chrome binary; falls back to puppeteer's bundled one |
|
|
612
615
|
| `PDF_CHROMIUM_NO_SANDBOX` | `false` | `true` passes `--no-sandbox`. Needed in most containers; only use with trusted HTML |
|
|
613
616
|
| `PDF_RENDER_TIMEOUT_MS` | `30000` | Per-render page load timeout in milliseconds |
|
|
@@ -634,10 +637,10 @@ Requires the optional peer dependency `puppeteer`.
|
|
|
634
637
|
|
|
635
638
|
### API Keys
|
|
636
639
|
|
|
637
|
-
| Variable | Default | Description
|
|
638
|
-
| --------------------- | ----------- |
|
|
639
|
-
| `API_KEY_HEADER_NAME` | `x-api-key` | Request header carrying the API key
|
|
640
|
-
| `ENCRYPTION_SECRET` | — | **Required to issue keys** — `key_encrypted` is AES-256-GCM
|
|
640
|
+
| Variable | Default | Description |
|
|
641
|
+
| --------------------- | ----------- | ----------------------------------------------------------- |
|
|
642
|
+
| `API_KEY_HEADER_NAME` | `x-api-key` | Request header carrying the API key |
|
|
643
|
+
| `ENCRYPTION_SECRET` | — | **Required to issue keys** — `key_encrypted` is AES-256-GCM |
|
|
641
644
|
|
|
642
645
|
A key is stored three ways: its sha256 hash in `key_hash` (what lookups match), a
|
|
643
646
|
short visible prefix in `key_prefix`, and the key itself encrypted in
|
|
@@ -691,17 +694,28 @@ Requires the optional peer dependency `firebase-admin`. Provide **one** of:
|
|
|
691
694
|
Storage is split across two tables. `sys_document_uploads` (`DocumentUploadEntity`)
|
|
692
695
|
is the stored object — name, mime type, size, driver, path, url, `is_public` — and
|
|
693
696
|
every upload writes one row automatically. `sys_documents` (`DocumentEntity`) is just
|
|
694
|
-
the link to one of your records: `upload_id` + `source_type` + `source_id
|
|
695
|
-
metadata. So one file can hang off several records, and a
|
|
696
|
-
anything owns it.
|
|
697
|
+
the link to one of your records: `upload_id` + `source_type` + `source_id` +
|
|
698
|
+
`system_generated`, no file metadata. So one file can hang off several records, and a
|
|
699
|
+
file can exist before anything owns it.
|
|
700
|
+
|
|
701
|
+
`system_generated` answers a question the two tables otherwise cannot: whether anybody
|
|
702
|
+
uploaded this file. A generated invoice and an uploaded scan of one are the same pair of
|
|
703
|
+
rows, so the flag is what lets a listing tell them apart — and what lets a regeneration
|
|
704
|
+
know which of its records it may replace. It is set through
|
|
705
|
+
`setDocument(dto, { systemGenerated: true })`, which is how
|
|
706
|
+
[`ProcessPdfDocument`](#pdf-documents-pdfservice-pdftemplateservice-processpdfdocument)
|
|
707
|
+
marks what it produces; it is deliberately absent from `SetDocumentDto`, so the HTTP
|
|
708
|
+
endpoint cannot claim it.
|
|
697
709
|
|
|
698
710
|
`DocumentModule` ships two endpoints. `POST /api/documents/upload` is multipart: it
|
|
699
711
|
takes the files in a `files` field, uploads them through the active driver and returns
|
|
700
712
|
the saved `sys_document_uploads` row of each. `POST /api/documents/set` then attaches
|
|
701
713
|
one of those uploads to a record of yours — `{ upload_id, source_type, source_id }` —
|
|
702
714
|
and returns the `sys_documents` link row, re-sending an existing link rather than
|
|
703
|
-
duplicating it.
|
|
704
|
-
|
|
715
|
+
duplicating it. `documentService.deleteDocument(id)` soft deletes such a link —
|
|
716
|
+
no endpoint, since detaching belongs to the flow that owns the record — and leaves the
|
|
717
|
+
upload and its object alone. `DocumentSubscriber` records who did it under
|
|
718
|
+
`attributes.deleted_by`, as `sys_documents` has no such column.
|
|
705
719
|
|
|
706
720
|
Callers do not choose the folder. They send a `path_identifier` (`invoice`,
|
|
707
721
|
`avatar`, ...) which resolves through the `document.path.<path_identifier>` system
|
|
@@ -739,40 +753,105 @@ async upload(@UploadedFiles() files: Express.Multer.File[], @CurrentUser() user:
|
|
|
739
753
|
Set `DOCUMENT_STORAGE_DRIVER=minio` for a local MinIO bucket (auto-created) or
|
|
740
754
|
`DOCUMENT_STORAGE_DRIVER=s3` for Amazon S3. Switching drivers requires no code change.
|
|
741
755
|
|
|
742
|
-
|
|
756
|
+
Because uploading and attaching are separate steps, a file can end up claimed by
|
|
757
|
+
nothing. `CleanOrphanDocumentsJob` (`clean.orphan.documents.job`) sweeps those. Run
|
|
758
|
+
with no payload it collects every upload that has no `sys_documents` row and is more
|
|
759
|
+
than a day old, then re-dispatches itself as `{ id }` per row; each of those deletes
|
|
760
|
+
that object from the bucket and soft deletes its row, so one unreachable file retries
|
|
761
|
+
alone instead of stalling the sweep. Schedule it with a `sys_scheduled_tasks` row under
|
|
762
|
+
that identifier — the module registers the handler but never dispatches it.
|
|
763
|
+
|
|
764
|
+
## PDF Documents (PdfService, PdfTemplateService, ProcessPdfDocument)
|
|
765
|
+
|
|
766
|
+
The `/pdfs` counterpart of [Templated Email](#templated-email-aws-ses), and it reuses
|
|
767
|
+
that pipeline's shape exactly: a catalogue row names a template, a remote service
|
|
768
|
+
renders it, a processor drives the whole thing. What differs is the last mile — mail is
|
|
769
|
+
handed to SES, a document is rasterized and stored.
|
|
770
|
+
|
|
771
|
+
Three pieces, split along the one line that matters — where the browser binary lives:
|
|
743
772
|
|
|
744
|
-
`
|
|
745
|
-
|
|
773
|
+
- **`PdfTemplateService`** — resolves a `sys_pdf_templates` row by `identifier` and
|
|
774
|
+
POSTs your `data` to the remote template service, returning print-ready HTML. Exactly
|
|
775
|
+
`EmailNotificationService.getEmailBody()`, for documents.
|
|
776
|
+
- **`PdfService`** — turns that HTML into PDF bytes with a headless Chromium. Knows
|
|
777
|
+
nothing about templates or storage.
|
|
778
|
+
- **`ProcessPdfDocument`** — the entry point. Validates, renders, rasterizes, stores,
|
|
779
|
+
and returns the `sys_documents` row with its `upload` relation attached.
|
|
746
780
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
browser binary and puts the PDF bytes next to the storage and delivery code
|
|
751
|
-
that needs them.
|
|
781
|
+
All three are exported by `DocumentModule`.
|
|
782
|
+
|
|
783
|
+
### The whole thing in one call
|
|
752
784
|
|
|
753
785
|
```ts
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
786
|
+
const processor = new ProcessPdfDocument(
|
|
787
|
+
this.pdfTemplateService,
|
|
788
|
+
this.pdfService,
|
|
789
|
+
this.documentService,
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
const document = await processor.process({
|
|
793
|
+
identifier: 'purchase-order', // a sys_pdf_templates row
|
|
794
|
+
metadata: {
|
|
795
|
+
fileName: 'po-10432.pdf',
|
|
796
|
+
mimeType: 'application/pdf', // default
|
|
797
|
+
pathIdentifier: 'purchase.order', // → the document.path.purchase.order property
|
|
798
|
+
isPublic: false, // default
|
|
799
|
+
},
|
|
800
|
+
source: { // becomes the sys_documents row
|
|
801
|
+
source_type: 'purchase.order',
|
|
802
|
+
source_id: purchaseOrder.id,
|
|
803
|
+
},
|
|
804
|
+
data: { order: purchaseOrder, lines }, // payload for the remote renderer
|
|
761
805
|
});
|
|
762
806
|
|
|
763
|
-
|
|
807
|
+
const link = await this.documentService.getDocumentLink(document.upload);
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Four steps, in order:
|
|
811
|
+
|
|
812
|
+
1. `PdfTemplateService.getPdfHtml()` — loads the template (`OperationException` if the
|
|
813
|
+
identifier is unknown), builds the URL as the `pdf.template.base.url` system
|
|
814
|
+
property + `template.path` + `identifier`, and POSTs `data` to it. The URL is a
|
|
815
|
+
plain concatenation, so the property and `path` carry their own trailing slashes —
|
|
816
|
+
same contract as `email.template.base.url`.
|
|
817
|
+
2. `PdfService.renderHtmlToUploadableFile()` — the bytes, named (`.pdf` appended when
|
|
818
|
+
the file name lacks it).
|
|
819
|
+
3. `DocumentService.uploadSingleDocument()` — the object goes to the active driver and
|
|
820
|
+
one `sys_document_uploads` row records it. `pathIdentifier` is resolved through
|
|
821
|
+
`resolvePathPrefix()` first, so the destination folder is a `document.path.*` system
|
|
822
|
+
property rather than a caller-supplied path.
|
|
823
|
+
4. `DocumentService.setDocument(…, { systemGenerated: true })` — the `sys_documents`
|
|
824
|
+
row linking that upload to your record.
|
|
825
|
+
|
|
826
|
+
The upload is deliberately made _without_ `sourceType`/`sourceId` and linked in a
|
|
827
|
+
separate call: those options attach the document as an ordinary upload, and
|
|
828
|
+
`system_generated` is only settable on the explicit `setDocument()` — never from
|
|
829
|
+
`SetDocumentDto`, so the HTTP endpoint cannot claim it.
|
|
830
|
+
|
|
831
|
+
**Seed the catalogue.** A `sys_pdf_templates` row per document, plus the
|
|
832
|
+
`pdf.template.base.url` property; without either, `getPdfHtml()` raises. Manage them
|
|
833
|
+
through [the admin surface](#admin-module) — content manager to define or edit, global
|
|
834
|
+
admin to delete. There is no `PdfTemplateIdentifier` enum: the library dispatches no
|
|
835
|
+
documents of its own, so name yours in an enum in your app rather than inline.
|
|
836
|
+
|
|
837
|
+
### Using PdfService on its own
|
|
838
|
+
|
|
839
|
+
Skip the processor when you already have the HTML, or want the bytes rather than a
|
|
840
|
+
stored document:
|
|
841
|
+
|
|
842
|
+
```ts
|
|
764
843
|
const pdf: Buffer = await this.pdfService.renderHtmlToPdf(html);
|
|
765
844
|
|
|
766
845
|
// …or go straight to bucket storage.
|
|
767
|
-
const file = await this.pdfService.renderHtmlToUploadableFile(html, '
|
|
846
|
+
const file = await this.pdfService.renderHtmlToUploadableFile(html, 'report.pdf');
|
|
768
847
|
const [link] = await this.documentService.uploadDocument([file], {
|
|
769
848
|
pathPrefix: 'reports',
|
|
770
849
|
});
|
|
771
850
|
```
|
|
772
851
|
|
|
773
|
-
**Install puppeteer.** It is an
|
|
774
|
-
|
|
775
|
-
|
|
852
|
+
**Install puppeteer.** It is an _optional_ peer dependency — nothing imports it until a
|
|
853
|
+
PDF is actually requested, so apps that never generate one skip the ~170MB Chromium
|
|
854
|
+
download entirely:
|
|
776
855
|
|
|
777
856
|
```bash
|
|
778
857
|
yarn add puppeteer
|
|
@@ -784,7 +863,7 @@ yarn add puppeteer
|
|
|
784
863
|
|
|
785
864
|
- **The template owns the page setup.** `preferCssPageSize` defaults to `true`,
|
|
786
865
|
so an `@page { size: A4; margin: … }` rule in the HTML wins over the `format`
|
|
787
|
-
option. Page
|
|
866
|
+
option. Page _count_ is never specified by anyone — Chromium paginates and
|
|
788
867
|
emits as many pages as the content needs, which is what lets an A4-width
|
|
789
868
|
template grow to any length.
|
|
790
869
|
- **Print media is emulated**, so `@media screen` blocks (the sheet shadow a
|
|
@@ -799,11 +878,11 @@ yarn add puppeteer
|
|
|
799
878
|
|
|
800
879
|
### Configuration
|
|
801
880
|
|
|
802
|
-
| Variable | Default | Description
|
|
803
|
-
| ------------------------- | --------- |
|
|
804
|
-
| `PDF_CHROMIUM_PATH` | _(empty)_ | Path to an existing Chromium/Chrome binary; falls back to puppeteer's bundled one
|
|
805
|
-
| `PDF_CHROMIUM_NO_SANDBOX` | `false` | `true` passes `--no-sandbox`. Needed in most containers; only use with trusted HTML
|
|
806
|
-
| `PDF_RENDER_TIMEOUT_MS` | `30000` | Per-render page load timeout
|
|
881
|
+
| Variable | Default | Description |
|
|
882
|
+
| ------------------------- | --------- | ----------------------------------------------------------------------------------- |
|
|
883
|
+
| `PDF_CHROMIUM_PATH` | _(empty)_ | Path to an existing Chromium/Chrome binary; falls back to puppeteer's bundled one |
|
|
884
|
+
| `PDF_CHROMIUM_NO_SANDBOX` | `false` | `true` passes `--no-sandbox`. Needed in most containers; only use with trusted HTML |
|
|
885
|
+
| `PDF_RENDER_TIMEOUT_MS` | `30000` | Per-render page load timeout |
|
|
807
886
|
|
|
808
887
|
## Raw SQL (SqlService)
|
|
809
888
|
|
|
@@ -862,9 +941,15 @@ await new ProcessEmailNotification(emailNotificationService).process({
|
|
|
862
941
|
});
|
|
863
942
|
```
|
|
864
943
|
|
|
944
|
+
The catalogue is managed from [the admin surface](#admin-module) —
|
|
945
|
+
`POST /admin/email-templates` and friends — and `EmailTemplateEntity` extends
|
|
946
|
+
`CommonAdminEntity` for that reason: a row is authored from the back office, never by a
|
|
947
|
+
tenant user.
|
|
948
|
+
|
|
865
949
|
The templates the library itself dispatches are named by the
|
|
866
950
|
`EmailTemplateIdentifier` enum — seed a matching `sys_email_templates` row for each
|
|
867
|
-
one you want delivered
|
|
951
|
+
one you want delivered, and note that these two are exactly the rows a delete must not
|
|
952
|
+
reach:
|
|
868
953
|
|
|
869
954
|
| Identifier | Sent by | Recipient | `data` keys |
|
|
870
955
|
| ----------------------- | ------------------------- | ------------------ | --------------------------------------------------------------------------- |
|
|
@@ -937,7 +1022,8 @@ SQL runs. Results come back as `{ records, total, page, limit, total_pages }`.
|
|
|
937
1022
|
## Admin Module
|
|
938
1023
|
|
|
939
1024
|
`AdminModule` is the back-office surface — tenant onboarding, client/admin user
|
|
940
|
-
registration, tenant listing
|
|
1025
|
+
registration, tenant listing, scheduled-task management and the email/PDF template
|
|
1026
|
+
catalogues. It uses the same
|
|
941
1027
|
imperative auth model as the rest of the app: controllers are open by default and
|
|
942
1028
|
gate each action through `AdminAccessService` before handing the work to a
|
|
943
1029
|
processor.
|
|
@@ -963,42 +1049,73 @@ pass the actor to a processor.
|
|
|
963
1049
|
|
|
964
1050
|
### Endpoints
|
|
965
1051
|
|
|
966
|
-
| Route
|
|
967
|
-
|
|
|
968
|
-
| `POST
|
|
969
|
-
| `POST
|
|
970
|
-
| `GET
|
|
971
|
-
| `
|
|
972
|
-
| `POST
|
|
973
|
-
| `POST
|
|
974
|
-
| `POST
|
|
975
|
-
| `GET
|
|
976
|
-
| `
|
|
977
|
-
| `POST
|
|
978
|
-
| `POST
|
|
979
|
-
| `GET
|
|
980
|
-
| `
|
|
981
|
-
| `POST
|
|
982
|
-
| `POST
|
|
983
|
-
| `
|
|
984
|
-
| `
|
|
985
|
-
| `
|
|
986
|
-
| `POST
|
|
987
|
-
| `POST
|
|
988
|
-
| `POST
|
|
989
|
-
| `GET
|
|
990
|
-
| `
|
|
991
|
-
| `POST
|
|
992
|
-
| `POST
|
|
993
|
-
| `GET
|
|
994
|
-
| `
|
|
995
|
-
| `POST
|
|
1052
|
+
| Route | Min. role | Handler |
|
|
1053
|
+
| ------------------------------------ | --------------- | --------------------------------------------------------------- |
|
|
1054
|
+
| `POST /admin/tenants` | super admin | `TenantDataProcessor` (upsert — send `id` to update) |
|
|
1055
|
+
| `POST /admin/tenants/list` | content reader | `TenantListProcessor` |
|
|
1056
|
+
| `GET /admin/tenants/:id` | content reader | the row plus its `scopes` |
|
|
1057
|
+
| `DELETE /admin/tenants/:id` | super admin | `TenantDeleteProcessor` (cascades to users, keys, grants) |
|
|
1058
|
+
| `POST /admin/tenant-users` | admin | `ClientUserDataProcessor` |
|
|
1059
|
+
| `POST /admin/tenant-users/bulk` | admin | `BulkUserDataProcessor` |
|
|
1060
|
+
| `POST /admin/tenant-users/list` | content reader | `ClientUserListProcessor` |
|
|
1061
|
+
| `GET /admin/tenant-users/:id` | content reader | the row |
|
|
1062
|
+
| `DELETE /admin/tenant-users/:id` | admin | `ClientUserDeleteProcessor` |
|
|
1063
|
+
| `POST /admin/system-users` | super admin | `SystemUserDataProcessor` (upsert — send `id` to update) |
|
|
1064
|
+
| `POST /admin/system-users/list` | content reader | `SystemUserListProcessor` (scoped to `user_type = system.user`) |
|
|
1065
|
+
| `GET /admin/system-users/:id` | content reader | the row |
|
|
1066
|
+
| `DELETE /admin/system-users/:id` | super admin | `SystemUserService.delete` (revokes live sessions) |
|
|
1067
|
+
| `POST /admin/users` | super admin | `AdminUserDataProcessor` |
|
|
1068
|
+
| `POST /admin/users/list` | content reader | `AdminUserListProcessor` |
|
|
1069
|
+
| `GET /admin/users/:id` | content reader | the row |
|
|
1070
|
+
| `DELETE /admin/users/:id` | super admin | `AdminUserDeleteProcessor` |
|
|
1071
|
+
| `POST /admin/roles/list` | admin | `AdminRoleListProcessor` — picker feed (`sys_admin_roles`) |
|
|
1072
|
+
| `POST /admin/roles/tenant/list` | admin | `RoleListProcessor` — picker feed (`sys_roles`) |
|
|
1073
|
+
| `POST /admin/system-roles` | content manager | `SystemRoleDataProcessor` (upsert — send `id` to update) |
|
|
1074
|
+
| `POST /admin/system-roles/list` | content reader | `RoleListProcessor` |
|
|
1075
|
+
| `GET /admin/system-roles/:id` | content reader | the row |
|
|
1076
|
+
| `DELETE /admin/system-roles/:id` | content manager | `RoleService.deleteRole` (cascades to every grant) |
|
|
1077
|
+
| `POST /admin/scheduled-tasks` | content manager | `ScheduledTaskDataProcessor` (upsert — send `id` to update) |
|
|
1078
|
+
| `POST /admin/scheduled-tasks/list` | content reader | `ScheduledTaskListProcessor` |
|
|
1079
|
+
| `GET /admin/scheduled-tasks/:id` | content reader | the row |
|
|
1080
|
+
| `DELETE /admin/scheduled-tasks/:id` | content manager | `ScheduledTaskDeleteProcessor` |
|
|
1081
|
+
| `POST /admin/email-templates` | content manager | `EmailTemplateDataProcessor` (upsert — send `id` to update) |
|
|
1082
|
+
| `POST /admin/email-templates/list` | content reader | `EmailTemplateListProcessor` |
|
|
1083
|
+
| `GET /admin/email-templates/:id` | content reader | the row |
|
|
1084
|
+
| `DELETE /admin/email-templates/:id` | **admin** | `TemplateService.deleteEmailTemplate` |
|
|
1085
|
+
| `POST /admin/pdf-templates` | content manager | `PdfTemplateDataProcessor` (upsert — send `id` to update) |
|
|
1086
|
+
| `POST /admin/pdf-templates/list` | content reader | `PdfTemplateListProcessor` |
|
|
1087
|
+
| `GET /admin/pdf-templates/:id` | content reader | the row |
|
|
1088
|
+
| `DELETE /admin/pdf-templates/:id` | **admin** | `TemplateService.deletePdfTemplate` |
|
|
1089
|
+
| `POST /admin/scopes` | content manager | `ScopeDataProcessor` (upsert — send `id` to update) |
|
|
1090
|
+
| `POST /admin/scopes/list` | content reader | `ScopeListProcessor` |
|
|
1091
|
+
| `GET /admin/scopes/:id` | content reader | the row |
|
|
1092
|
+
| `DELETE /admin/scopes/:id` | content manager | `ScopeService.deleteScope` (cascades to every grant) |
|
|
1093
|
+
| `POST /admin/api-keys` | admin | `ApiKeyDataProcessor` — returns `{ raw_key, api_key }` |
|
|
1094
|
+
| `POST /admin/api-keys/list` | content reader | `ApiKeyListProcessor` |
|
|
1095
|
+
| `GET /admin/api-keys/:id` | content reader | the row, its `scopes`, and `key` from admin up |
|
|
1096
|
+
| `POST /admin/api-keys/:id/disable` | admin | `ApiKeyService.disableApiKey` (reversible) |
|
|
1097
|
+
| `DELETE /admin/api-keys/:id` | admin | `ApiKeyService.deleteApiKey` (soft, and revokes) |
|
|
996
1098
|
|
|
997
1099
|
List endpoints are `POST .../list` with the filter payload in the body (a
|
|
998
1100
|
`CommonListFilterDto`) — see [List Endpoints & Filtering](#list-endpoints--filtering).
|
|
999
1101
|
Deletes and the API-key disable are single-action, so they sit on the domain service
|
|
1000
1102
|
rather than in a processor; processors are for the writes that validate a payload.
|
|
1001
1103
|
|
|
1104
|
+
**Template catalogues.** `sys_email_templates` and `sys_pdf_templates` are the same
|
|
1105
|
+
surface across two controllers — same routes, same DTO, and a PDF template simply has
|
|
1106
|
+
no `subject` because a document has no envelope to address. Neither row holds markup:
|
|
1107
|
+
`path` + `identifier` are appended to a base-URL system property to reach the remote
|
|
1108
|
+
template service, so the catalogue is a table of pointers.
|
|
1109
|
+
|
|
1110
|
+
Reading is content-reader work and writing content-manager work, the same split scopes
|
|
1111
|
+
get. **Deleting sits one level higher, at global admin** — the one place these differ
|
|
1112
|
+
from every other catalogue. An identifier is named by _code_
|
|
1113
|
+
(`EmailTemplateIdentifier.TENANT_ONBOARDED`, whatever `ProcessPdfDocument` asks for),
|
|
1114
|
+
so deleting a row does not change what a flow says, it stops the flow rendering at all
|
|
1115
|
+
— and only at send/generate time, as an `OperationException`. Nothing cascades: the
|
|
1116
|
+
remote template, every mail already sent and every document already generated are left
|
|
1117
|
+
exactly as they are.
|
|
1118
|
+
|
|
1002
1119
|
**Scopes and grants.** A scope is defined once in `sys_scopes` (content manager),
|
|
1003
1120
|
then granted to the principals that hold it. `AddTenantDto.scope_ids` carries the
|
|
1004
1121
|
tenant's whole scope set — `setScopes()` grants what is new and soft-removes what is
|
|
@@ -1035,7 +1152,7 @@ registration deliberately sends nothing.
|
|
|
1035
1152
|
|
|
1036
1153
|
##### Create vs. update
|
|
1037
1154
|
|
|
1038
|
-
Creation DTOs extend `
|
|
1155
|
+
Creation DTOs extend `CommonDataDto`, which carries an optional UUID `id`. Where a
|
|
1039
1156
|
processor supports it, sending an `id` turns the same `POST` into an update.
|
|
1040
1157
|
|
|
1041
1158
|
The processor loads the row when an `id` is present, falls back to `Entity.create()`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.module.d.ts","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAIvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"admin.module.d.ts","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAIvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAG9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAa9D,qBAMa,WAAW;IACtB,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,kBAAuB,GAAG,aAAa;CAcjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.module.js","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,qDAAiD;AACjD,2DAAuD;AACvD,+CAA2C;AAE3C,6EAAyE;AACzE,6EAAwE;
|
|
1
|
+
{"version":3,"file":"admin.module.js","sourceRoot":"","sources":["../../src/admin/admin.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,qDAAiD;AACjD,2DAAuD;AACvD,+CAA2C;AAE3C,6EAAyE;AACzE,6EAAwE;AAoBjE,IAAM,WAAW,mBAAjB,MAAM,WAAW;IACtB,MAAM,CAAC,QAAQ,CAAC,UAA8B,EAAE;QAC9C,OAAO;YACL,MAAM,EAAE,aAAW;YACnB,OAAO,EAAE;gBACP,wBAAU,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC5B,4BAAY,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC9B,wCAAkB,CAAC,QAAQ,CAAC,OAAO,CAAC;gBACpC,6CAAoB,CAAC,QAAQ,CAAC,OAAO,CAAC;aACvC;YACD,WAAW,EAAE,CAAC,GAAG,wBAAU,CAAC,WAAW,CAAC;YACxC,SAAS,EAAE,CAAC,GAAG,wBAAU,CAAC,QAAQ,CAAC;YACnC,OAAO,EAAE,CAAC,GAAG,wBAAU,CAAC,QAAQ,CAAC;SAClC,CAAC;IACJ,CAAC;CACF,CAAA;AAfY,kCAAW;sBAAX,WAAW;IANvB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,wBAAU,EAAE,4BAAY,EAAE,wCAAkB,EAAE,6CAAoB,CAAC;QAC7E,WAAW,EAAE,CAAC,GAAG,wBAAU,CAAC,WAAW,CAAC;QACxC,SAAS,EAAE,CAAC,GAAG,wBAAU,CAAC,QAAQ,CAAC;QACnC,OAAO,EAAE,CAAC,GAAG,wBAAU,CAAC,QAAQ,CAAC;KAClC,CAAC;GACW,WAAW,CAevB"}
|
|
@@ -98,7 +98,7 @@ __decorate([
|
|
|
98
98
|
__metadata("design:returntype", Promise)
|
|
99
99
|
], AdminApiKeyController.prototype, "disable", null);
|
|
100
100
|
__decorate([
|
|
101
|
-
(0, common_1.
|
|
101
|
+
(0, common_1.Delete)(':id'),
|
|
102
102
|
(0, swagger_1.ApiOperation)({
|
|
103
103
|
summary: 'Delete an API key.',
|
|
104
104
|
description: 'Soft delete: the row keeps its data with `deleted_at` set and drops out of every listing. It is revoked in the same transaction, along with its scope grants — key validation matches on `is_active`/`revoked_at`, so a row that only left the listings would keep authenticating requests. Requires at least the global admin role.',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin.api.key.controller.js","sourceRoot":"","sources":["../../../src/admin/controllers/admin.api.key.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,
|
|
1
|
+
{"version":3,"file":"admin.api.key.controller.js","sourceRoot":"","sources":["../../../src/admin/controllers/admin.api.key.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4E;AAC5E,6CAAuE;AACvE,mEAA+D;AAC/D,yEAAoE;AACpE,sFAA4E;AAC5E,2EAAsE;AACtE,2EAAsE;AACtE,gFAA0E;AAC1E,gFAA0E;AAC1E,6DAAuD;AACvD,+DAAyD;AAUlD,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,YACmB,kBAAsC,EACtC,aAA4B,EAC5B,UAAsB;QAFtB,uBAAkB,GAAlB,kBAAkB,CAAoB;QACtC,kBAAa,GAAb,aAAa,CAAe;QAC5B,eAAU,GAAV,UAAU,CAAY;IACtC,CAAC;IAQE,AAAN,KAAK,CAAC,KAAK,CAAS,GAAiB;QACnC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC;QAC/D,OAAO,IAAI,4CAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACxE,CAAC;IAQK,AAAN,KAAK,CAAC,IAAI,CAAS,GAAkB;QACnC,MAAM,IAAI,CAAC,kBAAkB,CAAC,qBAAqB,EAAE,CAAC;QACtD,OAAO,IAAI,4CAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/D,CAAC;IAQK,AAAN,KAAK,CAAC,MAAM,CAAU,EAAE,EAAE,EAAmB;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,qBAAqB,EAAE,CAAC;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,CACjD,MAAM,EACN,0CAAgB,CAAC,YAAY,CAC9B,CAAC;QAEF,OAAO,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC;IAQK,AAAN,KAAK,CAAC,OAAO,CAAU,EAAE,EAAE,EAAmB;QAC5C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IAQK,AAAN,KAAK,CAAC,MAAM,CAAU,EAAE,EAAE,EAAmB;QAC3C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC;QAC/D,OAAO,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACtD,CAAC;CACF,CAAA;AAlEY,sDAAqB;AAa1B;IANL,IAAA,aAAI,GAAE;IACN,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,gCAAgC;QACzC,WAAW,EACT,0MAA0M;KAC7M,CAAC;IACW,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAM,8BAAY;;kDAGpC;AAQK;IANL,IAAA,aAAI,EAAC,MAAM,CAAC;IACZ,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,mCAAmC;QAC5C,WAAW,EACT,uKAAuK;KAC1K,CAAC;IACU,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAM,gCAAa;;iDAGpC;AAQK;IANL,IAAA,YAAG,EAAC,KAAK,CAAC;IACV,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,uBAAuB;QAChC,WAAW,EACT,mWAAmW;KACtW,CAAC;IACY,WAAA,IAAA,cAAK,GAAE,CAAA;;qCAAS,mCAAe;;mDAQ5C;AAQK;IANL,IAAA,aAAI,EAAC,aAAa,CAAC;IACnB,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,qBAAqB;QAC9B,WAAW,EACT,sLAAsL;KACzL,CAAC;IACa,WAAA,IAAA,cAAK,GAAE,CAAA;;qCAAS,mCAAe;;oDAG7C;AAQK;IANL,IAAA,eAAM,EAAC,KAAK,CAAC;IACb,IAAA,sBAAY,EAAC;QACZ,OAAO,EAAE,oBAAoB;QAC7B,WAAW,EACT,sUAAsU;KACzU,CAAC;IACY,WAAA,IAAA,cAAK,GAAE,CAAA;;qCAAS,mCAAe;;mDAG5C;gCAjEU,qBAAqB;IAHjC,IAAA,iBAAO,EAAC,kBAAkB,CAAC;IAC3B,IAAA,uBAAa,GAAE;IACf,IAAA,mBAAU,EAAC,oBAAoB,CAAC;qCAGQ,yCAAkB;QACvB,+BAAa;QAChB,wBAAU;GAJ9B,qBAAqB,CAkEjC"}
|