@restforgejs/platform 4.1.1 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SECURITY.md +83 -4
- package/bin/sdf-tools.exe +0 -0
- package/build-info.json +2 -2
- package/cli/consumer-deploy.js +1 -1
- package/cli/consumer.js +1 -1
- package/generators/cli/dashboard/create.js +4 -1
- package/generators/cli/endpoint/create.js +43 -4
- package/generators/cli/key/generate.js +2 -1
- package/generators/cli/key/revoke.js +2 -1
- package/generators/cli/payload/diff.js +3 -2
- package/generators/cli/payload/generate.js +3 -2
- package/generators/cli/payload/sync.js +3 -2
- package/generators/cli/payload/validate.js +3 -2
- package/generators/cli/processor/create.js +14 -3
- package/generators/cli/project/delete.js +2 -1
- package/generators/cli/query/validate.js +3 -2
- package/generators/cli/schema/apply.js +526 -0
- package/generators/cli/schema/describe.js +3 -2
- package/generators/cli/schema/diff.js +322 -0
- package/generators/cli/schema/generate-ddl.js +7 -10
- package/generators/cli/schema/init.js +95 -172
- package/generators/cli/schema/introspect.js +3 -2
- package/generators/cli/schema/list.js +3 -2
- package/generators/cli/schema/migrate.js +13 -18
- package/generators/cli/schema/models.js +8 -12
- package/generators/cli/schema/template.js +222 -0
- package/generators/cli/schema/validate.js +8 -12
- package/generators/cli-entry.js +17 -2
- package/generators/lib/dbschema-kit/apply-engine.js +582 -0
- package/generators/lib/dbschema-kit/diff-engine.js +703 -0
- package/generators/lib/dbschema-kit/diff-reporter.js +272 -0
- package/generators/lib/dbschema-kit/emitters/alter-table.js +275 -0
- package/generators/lib/migration/audit-table-runner.js +213 -215
- package/generators/lib/payload/endpoint-schema-validator.js +171 -0
- package/generators/lib/payload/payload-runner.js +137 -220
- package/generators/lib/payload/schema-diff.js +277 -0
- package/generators/lib/templates/dashboard-catalog.js +1 -437
- package/generators/lib/templates/db-connection-env.js +1 -212
- package/generators/lib/templates/dbschema-catalog.js +1 -489
- package/generators/lib/templates/field-validation-catalog.js +1 -531
- package/generators/lib/templates/mysql-template.js +1 -3863
- package/generators/lib/templates/oracle-template.js +1 -3915
- package/generators/lib/templates/postgres-template.js +1 -5838
- package/generators/lib/templates/query-declarative-catalog.js +1 -199
- package/generators/lib/templates/sqlite-template.js +1 -3440
- package/generators/lib/utils/audit-columns.js +181 -0
- package/generators/lib/utils/cli-output.js +17 -0
- package/generators/lib/utils/database-introspector.js +16 -13
- package/generators/lib/utils/env-manager.js +6 -0
- package/generators/lib/utils/path-validator.js +71 -0
- package/generators/lib/validators/payload-validator.js +1 -2
- package/integrity-manifest.json +28 -10
- package/package.json +11 -3
- package/scripts/verify-integrity.js +1 -1
- package/server.js +1 -1
- package/src/components/handlers/adjust_handler.js +1 -1
- package/src/components/handlers/audit_handler.js +1 -1
- package/src/components/handlers/delete_handler.js +1 -1
- package/src/components/handlers/export_handler.js +1 -1
- package/src/components/handlers/import_handler.js +1 -1
- package/src/components/handlers/insert_handler.js +1 -1
- package/src/components/handlers/update_handler.js +1 -1
- package/src/components/handlers/upload_handler.js +1 -1
- package/src/components/handlers/workflow_handler.js +1 -1
- package/src/components/integrations/webhook.js +1 -1
- package/src/consumers/baseConsumer.js +1 -1
- package/src/consumers/declarativeMapper.js +1 -1
- package/src/consumers/handlers/apiHandler.js +1 -1
- package/src/consumers/handlers/consoleHandler.js +1 -1
- package/src/consumers/handlers/databaseHandler.js +1 -1
- package/src/consumers/handlers/index.js +1 -1
- package/src/consumers/handlers/kafkaHandler.js +1 -1
- package/src/consumers/index.js +1 -1
- package/src/consumers/messageTransformer.js +1 -1
- package/src/consumers/validator.js +1 -1
- package/src/core/db/dialect/base-dialect.js +1 -1
- package/src/core/db/dialect/index.js +1 -1
- package/src/core/db/dialect/mysql-dialect.js +1 -1
- package/src/core/db/dialect/oracle-dialect.js +1 -1
- package/src/core/db/dialect/postgres-dialect.js +1 -1
- package/src/core/db/dialect/sqlite-dialect.js +1 -1
- package/src/core/db/flatten-helper.js +1 -1
- package/src/core/db/query-builder-error.js +1 -1
- package/src/core/db/query-builder.js +1 -1
- package/src/core/db/relation-helper.js +1 -1
- package/src/core/handlers/delete_handler.js +1 -1
- package/src/core/handlers/insert_handler.js +1 -1
- package/src/core/handlers/update_handler.js +1 -1
- package/src/core/models/base-model.js +1 -1
- package/src/core/utils/cache-manager.js +1 -1
- package/src/core/utils/component-engine.js +1 -1
- package/src/core/utils/context-builder.js +1 -1
- package/src/core/utils/datetime-formatter.js +1 -1
- package/src/core/utils/datetime-parser.js +1 -1
- package/src/core/utils/db.js +1 -1
- package/src/core/utils/logger.js +1 -1
- package/src/core/utils/payload-loader.js +1 -1
- package/src/core/utils/security-checks.js +1 -1
- package/src/middleware/body-options.js +1 -1
- package/src/middleware/cors.js +1 -1
- package/src/middleware/idempotency.js +1 -1
- package/src/middleware/rate-limiter.js +1 -1
- package/src/middleware/request-logger.js +1 -1
- package/src/middleware/security-headers.js +1 -1
- package/src/models/base-model-mysql.js +1 -1
- package/src/models/base-model-oracle.js +1 -1
- package/src/models/base-model-sqlite.js +1 -1
- package/src/models/base-model.js +1 -1
- package/src/pro/caching/redis-client.js +1 -1
- package/src/pro/caching/redis-helper.js +1 -1
- package/src/pro/consumers/baseConsumer.js +1 -1
- package/src/pro/consumers/declarativeMapper.js +1 -1
- package/src/pro/consumers/handlers/apiHandler.js +1 -1
- package/src/pro/consumers/handlers/consoleHandler.js +1 -1
- package/src/pro/consumers/handlers/databaseHandler.js +1 -1
- package/src/pro/consumers/handlers/index.js +1 -1
- package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
- package/src/pro/consumers/index.js +1 -1
- package/src/pro/consumers/messageTransformer.js +1 -1
- package/src/pro/consumers/validator.js +1 -1
- package/src/pro/database/base-model-mysql.js +1 -1
- package/src/pro/database/base-model-oracle.js +1 -1
- package/src/pro/database/base-model-sqlite.js +1 -1
- package/src/pro/database/db-mysql.js +1 -1
- package/src/pro/database/db-oracle.js +1 -1
- package/src/pro/database/db-sqlite.js +1 -1
- package/src/pro/excel/excel-generator.js +1 -1
- package/src/pro/excel/excel-parser.js +1 -1
- package/src/pro/excel/export-service.js +1 -1
- package/src/pro/excel/export_handler.js +1 -1
- package/src/pro/excel/import-service.js +1 -1
- package/src/pro/excel/import-validator.js +1 -1
- package/src/pro/excel/import_handler.js +1 -1
- package/src/pro/excel/upsert-builder.js +1 -1
- package/src/pro/idgen/idgen-routes.js +1 -1
- package/src/pro/integrations/lookup-resolver.js +1 -1
- package/src/pro/integrations/upload-handler-v2.js +1 -1
- package/src/pro/integrations/upload-handler.js +1 -1
- package/src/pro/integrations/webhook.js +1 -1
- package/src/pro/locking/lock-routes.js +1 -1
- package/src/pro/locking/resource-lock-manager.js +1 -1
- package/src/pro/messaging/kafkaConsumerService.js +1 -1
- package/src/pro/messaging/kafkaService.js +1 -1
- package/src/pro/messaging/messagehubService.js +1 -1
- package/src/pro/messaging/rabbitmqService.js +1 -1
- package/src/pro/scheduler/job-manager.js +1 -1
- package/src/pro/scheduler/job-routes.js +1 -1
- package/src/pro/scheduler/job-validator.js +1 -1
- package/src/pro/storage/base-storage-provider.js +1 -1
- package/src/pro/storage/file-metadata-helper.js +1 -1
- package/src/pro/storage/index.js +1 -1
- package/src/pro/storage/local-storage-provider.js +1 -1
- package/src/pro/storage/s3-storage-provider.js +1 -1
- package/src/pro/storage/upload-cleanup-job.js +1 -1
- package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
- package/src/pro/storage/upload-pending-tracker.js +1 -1
- package/src/pro/websocket/broadcast-helper.js +1 -1
- package/src/pro/websocket/index.js +1 -1
- package/src/pro/websocket/livesync-server.js +1 -1
- package/src/pro/websocket/ws-broadcaster.js +1 -1
- package/src/services/export-service.js +1 -1
- package/src/services/import-service.js +1 -1
- package/src/services/kafkaConsumerService.js +1 -1
- package/src/services/kafkaService.js +1 -1
- package/src/services/messagehubService.js +1 -1
- package/src/services/rabbitmqService.js +1 -1
- package/src/utils/cache-invalidation-registry.js +1 -1
- package/src/utils/cache-manager.js +1 -1
- package/src/utils/component-engine.js +1 -1
- package/src/utils/config-extractor.js +1 -1
- package/src/utils/consumerLogger.js +1 -1
- package/src/utils/context-builder.js +1 -1
- package/src/utils/dashboard-helpers.js +1 -1
- package/src/utils/dateHelper.js +1 -1
- package/src/utils/datetime-formatter.js +1 -1
- package/src/utils/datetime-parser.js +1 -1
- package/src/utils/db-bootstrap.js +1 -1
- package/src/utils/db-mysql.js +1 -1
- package/src/utils/db-oracle.js +1 -1
- package/src/utils/db-sqlite.js +1 -1
- package/src/utils/db.js +1 -1
- package/src/utils/demo-generator.js +1 -1
- package/src/utils/excel-generator.js +1 -1
- package/src/utils/excel-parser.js +1 -1
- package/src/utils/file-watcher.js +1 -1
- package/src/utils/id-generator.js +1 -1
- package/src/utils/idempotency-manager.js +1 -1
- package/src/utils/import-validator.js +1 -1
- package/src/utils/license-client.js +1 -1
- package/src/utils/lock-manager.js +1 -1
- package/src/utils/logger.js +1 -1
- package/src/utils/lookup-resolver.js +1 -1
- package/src/utils/payload-loader.js +1 -1
- package/src/utils/processor-response.js +1 -1
- package/src/utils/rabbitmq.js +1 -1
- package/src/utils/redis-client.js +1 -1
- package/src/utils/redis-helper.js +1 -1
- package/src/utils/request-scope.js +1 -1
- package/src/utils/security-checks.js +1 -1
- package/src/utils/service-resolver.js +1 -1
- package/src/utils/shutdown-coordinator.js +1 -1
- package/src/utils/trusted-keys.js +1 -1
- package/src/utils/upload-handler.js +1 -1
- package/src/utils/upsert-builder.js +1 -1
- package/src/utils/workflow-hook-executor.js +1 -1
- package/generators/metadata/global.json +0 -58
- package/generators/metadata/test-mysql-workbench.json +0 -118
- package/generators/metadata/test-mysql.json +0 -56
- package/generators/metadata/test-oracle-workbench.json +0 -118
- package/generators/metadata/test-oracle.json +0 -56
- package/generators/metadata/test-pg-workbench.json +0 -118
- package/generators/metadata/test-pg.json +0 -56
- package/generators/scripts/obfuscate-source.js +0 -356
- package/generators/scripts/validate-catalog.js +0 -430
- package/generators/scripts/validate-dbschema-catalog.js +0 -708
- package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
- package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
- package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
- package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
- package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
- package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
- package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
- package/generators/tests/fixtures/payloads/order-process.json +0 -52
- package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
- package/generators/tests/integration-tahap4b/README.md +0 -145
- package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
- package/generators/tests/integration-tahap4b/seed.sql +0 -53
- package/generators/tests/integration-tahap4b/verify.sql +0 -110
- package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
- package/generators/tests/unit/cli/create-processor.test.js +0 -319
- package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
- package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
- package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
- package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
- package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
- package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
- package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
- package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
- package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
- package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
- package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
- package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
- package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
- package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
- package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
- package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
- package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
- package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
- package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
- package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
- package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
- package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
- package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
- package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
- package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
- package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
- package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
- package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
- package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
- package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
- package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
- package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
- package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
- package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
- package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
- package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
- package/scripts/generate-integrity-manifest.js +0 -124
- package/scripts/snapshot-cli-contracts.js +0 -194
- package/scripts/verify-publish.js +0 -56
package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js
DELETED
|
@@ -1,944 +0,0 @@
|
|
|
1
|
-
const BaseModel = require('restforgejs/src/models/base-model-mysql');
|
|
2
|
-
const db = require('restforgejs/src/utils/db-mysql');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Item Model - MySQL Database
|
|
8
|
-
* Generated: 2026-04-25T07:54:01.303Z
|
|
9
|
-
*
|
|
10
|
-
* Table: core.item
|
|
11
|
-
* Primary Key: id
|
|
12
|
-
* Fields: 12
|
|
13
|
-
* Database: MySQL
|
|
14
|
-
*/
|
|
15
|
-
class ItemModel extends BaseModel {
|
|
16
|
-
/**
|
|
17
|
-
* Constructor
|
|
18
|
-
*/
|
|
19
|
-
constructor() {
|
|
20
|
-
const validFields = [
|
|
21
|
-
'item_id', 'item_code', 'item_name', 'description', 'uom', 'unit_price', 'weight', 'is_active', 'created_at', 'created_by', 'updated_at', 'updated_by'
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
const datatablesWhere = ["item_code","item_name","description","all"];
|
|
25
|
-
|
|
26
|
-
super('core.item', validFields);
|
|
27
|
-
|
|
28
|
-
// base-model-mysql hanya menerima 2 parameter, simpan datatablesWhere manual
|
|
29
|
-
this.datatablesWhere = datatablesWhere;
|
|
30
|
-
|
|
31
|
-
// Primary key configuration
|
|
32
|
-
this.primaryKey = 'id';
|
|
33
|
-
|
|
34
|
-
// Read/Write source configuration
|
|
35
|
-
this.viewName = 'core.item';
|
|
36
|
-
this.readSource = 'core.item';
|
|
37
|
-
this.writeSource = 'core.item';
|
|
38
|
-
|
|
39
|
-
// Flag untuk self-documenting API (endpoint /info)
|
|
40
|
-
this.hasViewQuery = false;
|
|
41
|
-
this.hasExportQuery = false;
|
|
42
|
-
|
|
43
|
-
// Field validation configuration
|
|
44
|
-
this.validationConfig = {}; // No field validation config
|
|
45
|
-
|
|
46
|
-
// Model metadata
|
|
47
|
-
this.modelMetadata = {
|
|
48
|
-
endpointName: 'item',
|
|
49
|
-
moduleName: 'mini-inventory',
|
|
50
|
-
tableName: 'core.item',
|
|
51
|
-
databaseType: 'mysql',
|
|
52
|
-
primaryKey: 'id',
|
|
53
|
-
fieldCount: 12,
|
|
54
|
-
generated: '2026-04-25T07:54:01.303Z'
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
this.advancedQueryTemplates = this.loadAdvancedQueryTemplates();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Load advanced query templates dari file
|
|
62
|
-
*/
|
|
63
|
-
loadAdvancedQueryTemplates() {
|
|
64
|
-
const templates = {};
|
|
65
|
-
|
|
66
|
-
// No advanced queries defined
|
|
67
|
-
|
|
68
|
-
return templates;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Override getListQuery untuk MySQL syntax
|
|
73
|
-
*/
|
|
74
|
-
getListQuery(options = {}) {
|
|
75
|
-
let baseQuery = `
|
|
76
|
-
select item_id, item_code, item_name, description, uom, unit_price, weight, is_active, created_at, created_by, updated_at, updated_by from core.item
|
|
77
|
-
`.trim();
|
|
78
|
-
|
|
79
|
-
// Convert PostgreSQL syntax to MySQL if needed
|
|
80
|
-
baseQuery = this.convertToMysqlSQL(baseQuery);
|
|
81
|
-
|
|
82
|
-
return baseQuery;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Override getReadQuery untuk endpoint /read
|
|
87
|
-
* Prioritas: viewName → viewQuery → tableName (SELECT * FROM readSource)
|
|
88
|
-
*/
|
|
89
|
-
getReadQuery(options = {}) {
|
|
90
|
-
if (this.viewName && this.viewName !== this.table) {
|
|
91
|
-
return 'SELECT * FROM ' + this.viewName;
|
|
92
|
-
}
|
|
93
|
-
return 'SELECT * FROM ' + this.readSource;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Convert PostgreSQL SQL syntax to MySQL
|
|
98
|
-
*/
|
|
99
|
-
convertToMysqlSQL(sql) {
|
|
100
|
-
// ILIKE → LIKE (MySQL case-insensitive by default with utf8mb4 collation)
|
|
101
|
-
sql = sql.replace(/\bILIKE\b/gi, 'LIKE');
|
|
102
|
-
// NOW() dan CURRENT_DATE sudah sama di MySQL
|
|
103
|
-
return sql;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Override getDatatables untuk MySQL dengan pagination yang tepat
|
|
108
|
-
* Paritas fungsional dengan Oracle dan PostgreSQL getDatatables
|
|
109
|
-
*/
|
|
110
|
-
async getDatatables(options) {
|
|
111
|
-
try {
|
|
112
|
-
// Check cache first
|
|
113
|
-
const cachedResult = await this.getCachedDatatables(options);
|
|
114
|
-
if (cachedResult) return cachedResult;
|
|
115
|
-
|
|
116
|
-
const {
|
|
117
|
-
searchValue = '',
|
|
118
|
-
searchBy = 'all',
|
|
119
|
-
perPage = 10,
|
|
120
|
-
start = 0,
|
|
121
|
-
sort_columns = [],
|
|
122
|
-
filters = {},
|
|
123
|
-
advancedFilters = []
|
|
124
|
-
} = options;
|
|
125
|
-
|
|
126
|
-
// Resolve sort columns dengan prioritas: sort_columns > order[0][column] > default
|
|
127
|
-
let resolvedSortColumns = sort_columns;
|
|
128
|
-
|
|
129
|
-
// Fallback: cek format DataTables bawaan (order[0][column] dan order[0][dir])
|
|
130
|
-
if ((!resolvedSortColumns || resolvedSortColumns.length === 0) &&
|
|
131
|
-
options['order[0][column]'] !== undefined && options['order[0][dir]'] !== undefined) {
|
|
132
|
-
const columnIndex = parseInt(options['order[0][column]']);
|
|
133
|
-
const direction = options['order[0][dir]'];
|
|
134
|
-
|
|
135
|
-
if (columnIndex >= 0 && columnIndex < this.validFields.length) {
|
|
136
|
-
resolvedSortColumns = [{ column: this.validFields[columnIndex], direction: direction.toUpperCase() }];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const orderClause = this.buildSortColumnsClause(resolvedSortColumns);
|
|
141
|
-
|
|
142
|
-
const baseQuery = this.getListQuery(options);
|
|
143
|
-
|
|
144
|
-
// Build WHERE clause (parameterized)
|
|
145
|
-
const searchResult = this.buildWhereClause(searchValue, searchBy);
|
|
146
|
-
let whereClauseSql = searchResult.sql;
|
|
147
|
-
let whereParams = [...searchResult.params];
|
|
148
|
-
|
|
149
|
-
// Build filter clause
|
|
150
|
-
const filterClause = this.buildObjectFilterClause(filters);
|
|
151
|
-
if (filterClause) {
|
|
152
|
-
if (whereClauseSql) {
|
|
153
|
-
whereClauseSql = `${whereClauseSql} AND ${filterClause}`;
|
|
154
|
-
} else {
|
|
155
|
-
whereClauseSql = `WHERE ${filterClause}`;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Support WHERE conditions dari request body
|
|
160
|
-
if (options.where) {
|
|
161
|
-
try {
|
|
162
|
-
const complexResult = this.buildComplexWhereClause(options.where, whereParams);
|
|
163
|
-
if (whereClauseSql) {
|
|
164
|
-
whereClauseSql = `${whereClauseSql} AND ${complexResult.sql}`;
|
|
165
|
-
} else {
|
|
166
|
-
whereClauseSql = `WHERE ${complexResult.sql}`;
|
|
167
|
-
}
|
|
168
|
-
whereParams = complexResult.params;
|
|
169
|
-
} catch (e) {
|
|
170
|
-
const error = new Error('Invalid where conditions: ' + e.message);
|
|
171
|
-
error.statusCode = 400;
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Advanced filters support
|
|
177
|
-
if (advancedFilters && advancedFilters.length > 0) {
|
|
178
|
-
const advResult = this.buildAdvancedFilterCondition(advancedFilters);
|
|
179
|
-
if (advResult.sql) {
|
|
180
|
-
if (whereClauseSql) {
|
|
181
|
-
whereClauseSql = `${whereClauseSql} AND ${advResult.sql}`;
|
|
182
|
-
} else {
|
|
183
|
-
whereClauseSql = `WHERE ${advResult.sql}`;
|
|
184
|
-
}
|
|
185
|
-
whereParams.push(...advResult.params);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Check if query needs subquery wrapping (CTE or JOIN)
|
|
190
|
-
const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
|
|
191
|
-
const hasJoin = /\b(inner|left|right|cross|full)\s+join\b/i.test(baseQuery) || /\bjoin\b/i.test(baseQuery);
|
|
192
|
-
const needsSubquery = isCteQuery || hasJoin;
|
|
193
|
-
|
|
194
|
-
// Count total records
|
|
195
|
-
const countTotalQuery = needsSubquery ?
|
|
196
|
-
`SELECT COUNT(*) as total FROM (${baseQuery}) as base_query` :
|
|
197
|
-
'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
|
|
198
|
-
const countTotalResult = await db.executeQuery(countTotalQuery);
|
|
199
|
-
const totalRecords = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
|
|
200
|
-
|
|
201
|
-
// Count filtered records
|
|
202
|
-
let filteredRecords = totalRecords;
|
|
203
|
-
if (whereClauseSql) {
|
|
204
|
-
const countFilteredQuery = needsSubquery ?
|
|
205
|
-
`SELECT COUNT(*) as total FROM (${baseQuery}) as base_query ${whereClauseSql}` :
|
|
206
|
-
'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + whereClauseSql;
|
|
207
|
-
const countFilteredResult = await db.executeQuery(countFilteredQuery, whereParams.length > 0 ? whereParams : undefined);
|
|
208
|
-
filteredRecords = countFilteredResult && countFilteredResult[0] ? parseInt(countFilteredResult[0].total) : 0;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// MySQL pagination using LIMIT/OFFSET
|
|
212
|
-
const query = needsSubquery ?
|
|
213
|
-
`SELECT * FROM (${baseQuery}) as base_query ${whereClauseSql || ''} ${orderClause} LIMIT ${perPage} OFFSET ${start}` :
|
|
214
|
-
`${baseQuery} ${whereClauseSql || ''} ${orderClause} LIMIT ${perPage} OFFSET ${start}`;
|
|
215
|
-
|
|
216
|
-
console.log('Final Query:', query);
|
|
217
|
-
console.log('Query Parameters:', whereParams.length > 0 ? whereParams : []);
|
|
218
|
-
const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
|
|
219
|
-
|
|
220
|
-
// Format data: MySQL returns lowercase keys natively
|
|
221
|
-
const data = rawData ? rawData.map((row, index) => {
|
|
222
|
-
const formatted = this.formatResponseData(row);
|
|
223
|
-
return {
|
|
224
|
-
...formatted,
|
|
225
|
-
rownumerator: start + index + 1
|
|
226
|
-
};
|
|
227
|
-
}) : [];
|
|
228
|
-
|
|
229
|
-
const result = {
|
|
230
|
-
draw: parseInt(options.draw || '1', 10),
|
|
231
|
-
recordsTotal: totalRecords,
|
|
232
|
-
recordsFiltered: filteredRecords,
|
|
233
|
-
data: data
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
// Cache result
|
|
237
|
-
await this.setCachedDatatables(options, result);
|
|
238
|
-
|
|
239
|
-
return result;
|
|
240
|
-
} catch (error) {
|
|
241
|
-
console.error('Error in getDatatables:', error);
|
|
242
|
-
throw error;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Build WHERE clause untuk search (parameterized query)
|
|
248
|
-
* @returns {Object} { sql: string, params: array }
|
|
249
|
-
*/
|
|
250
|
-
buildWhereClause(searchValue, searchBy) {
|
|
251
|
-
if (!searchValue || searchValue === '') {
|
|
252
|
-
return { sql: '', params: [] };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const params = [];
|
|
256
|
-
const searchPattern = `%${searchValue}%`;
|
|
257
|
-
|
|
258
|
-
if (searchBy === 'all') {
|
|
259
|
-
const searchableFields = this.datatablesWhere.filter(field => field !== 'all');
|
|
260
|
-
if (searchableFields.length > 0) {
|
|
261
|
-
const conditions = searchableFields.map(field => {
|
|
262
|
-
params.push(searchPattern);
|
|
263
|
-
return `${field} LIKE ?`;
|
|
264
|
-
});
|
|
265
|
-
return { sql: `WHERE (${conditions.join(' OR ')})`, params };
|
|
266
|
-
}
|
|
267
|
-
} else if (this.validFields.includes(searchBy)) {
|
|
268
|
-
params.push(searchPattern);
|
|
269
|
-
return { sql: `WHERE ${searchBy} LIKE ?`, params };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return { sql: '', params: [] };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Build filter clause dari object filters
|
|
277
|
-
* @param {Object} filters - Filter object {column: value}
|
|
278
|
-
* @returns {string} Filter conditions SQL (tanpa WHERE prefix)
|
|
279
|
-
*/
|
|
280
|
-
buildObjectFilterClause(filters) {
|
|
281
|
-
if (!filters || typeof filters !== 'object' || Object.keys(filters).length === 0) {
|
|
282
|
-
return '';
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const conditions = [];
|
|
286
|
-
for (const [column, value] of Object.entries(filters)) {
|
|
287
|
-
if (!this.validFields.includes(column)) continue;
|
|
288
|
-
if (value === null || value === undefined || value === '' || value === 'all' || value === '-') continue;
|
|
289
|
-
|
|
290
|
-
const escapedValue = value.toString().replace(/'/g, "''");
|
|
291
|
-
conditions.push(`${column} = '${escapedValue}'`);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return conditions.length > 0 ? conditions.join(' AND ') : '';
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Get list data dengan pagination untuk MySQL
|
|
299
|
-
*/
|
|
300
|
-
async getList(options) {
|
|
301
|
-
try {
|
|
302
|
-
// Check cache first
|
|
303
|
-
const cachedResult = await this.getCachedList(options);
|
|
304
|
-
if (cachedResult) {
|
|
305
|
-
const { page: p = null, perPage: pp = 10, searchValue: sv = '', sort_columns: sc = [], where: w = null } = options;
|
|
306
|
-
const scInfo = sc && sc.length > 0 ? sc.map(s => `${s.column}:${s.direction}`).join(',') : 'default';
|
|
307
|
-
console.log(`[Cache] HIT for list - page:${p}, perPage:${pp}, sort:${scInfo}, search:${sv || 'none'}${w ? ', where:yes' : ''}`);
|
|
308
|
-
return cachedResult;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const {
|
|
312
|
-
page = null,
|
|
313
|
-
perPage = 10,
|
|
314
|
-
searchValue = '',
|
|
315
|
-
searchBy = 'all',
|
|
316
|
-
sort_columns = [],
|
|
317
|
-
where = null,
|
|
318
|
-
select = null,
|
|
319
|
-
limit = 1000
|
|
320
|
-
} = options;
|
|
321
|
-
|
|
322
|
-
const paginate = page !== null;
|
|
323
|
-
const scInfo = sort_columns && sort_columns.length > 0 ? sort_columns.map(s => `${s.column}:${s.direction}`).join(',') : 'default';
|
|
324
|
-
const cacheInfo = `page:${page}, perPage:${perPage}, sort:${scInfo}, search:${searchValue || 'none'}${where ? ', where:yes' : ''}`;
|
|
325
|
-
|
|
326
|
-
console.log(`[Cache] MISS for list - ${cacheInfo}`);
|
|
327
|
-
|
|
328
|
-
// 1. Mendapatkan query dasar
|
|
329
|
-
let baseQuery;
|
|
330
|
-
if (select && Array.isArray(select) && select.length > 0) {
|
|
331
|
-
const selectedValidColumns = select.filter(col => this.validFields.includes(col));
|
|
332
|
-
if (selectedValidColumns.length > 0) {
|
|
333
|
-
baseQuery = 'SELECT ' + selectedValidColumns.map(col => '`' + col + '`').join(', ') + ' FROM ' + this.getTableSource('read') + ' a';
|
|
334
|
-
} else {
|
|
335
|
-
baseQuery = 'SELECT * FROM ' + this.getTableSource('read') + ' a';
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
baseQuery = this.getReadQuery(options);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Deteksi apakah query mengandung JOIN atau CTE (perlu subquery wrapping)
|
|
342
|
-
const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
|
|
343
|
-
const hasJoin = /\b(inner|left|right|cross|full)\s+join\b/i.test(baseQuery) || /\bjoin\b/i.test(baseQuery);
|
|
344
|
-
const needsSubquery = isCteQuery || hasJoin;
|
|
345
|
-
|
|
346
|
-
const searchResult = this.buildWhereClause(searchValue, searchBy);
|
|
347
|
-
let whereClauseSql = searchResult.sql;
|
|
348
|
-
let whereParams = [...searchResult.params];
|
|
349
|
-
const orderClause = this.buildSortColumnsClause(sort_columns);
|
|
350
|
-
|
|
351
|
-
// Support WHERE conditions dari request body
|
|
352
|
-
if (where) {
|
|
353
|
-
try {
|
|
354
|
-
const complexResult = this.buildComplexWhereClause(where, whereParams);
|
|
355
|
-
if (whereClauseSql) {
|
|
356
|
-
whereClauseSql = `${whereClauseSql} AND ${complexResult.sql}`;
|
|
357
|
-
} else {
|
|
358
|
-
whereClauseSql = `WHERE ${complexResult.sql}`;
|
|
359
|
-
}
|
|
360
|
-
whereParams = complexResult.params;
|
|
361
|
-
} catch (e) {
|
|
362
|
-
const error = new Error('Invalid where conditions: ' + e.message);
|
|
363
|
-
error.statusCode = 400;
|
|
364
|
-
throw error;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Count total unfiltered records
|
|
369
|
-
const countTotalQuery = needsSubquery
|
|
370
|
-
? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query'
|
|
371
|
-
: 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
|
|
372
|
-
const countTotalResult = await db.executeQuery(countTotalQuery);
|
|
373
|
-
const totalUnfiltered = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
|
|
374
|
-
|
|
375
|
-
// Count filtered records
|
|
376
|
-
let totalRecords = totalUnfiltered;
|
|
377
|
-
if (whereClauseSql) {
|
|
378
|
-
const countQuery = needsSubquery
|
|
379
|
-
? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '')
|
|
380
|
-
: 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + (whereClauseSql || '');
|
|
381
|
-
const countResult = await db.executeQuery(countQuery, whereParams.length > 0 ? whereParams : undefined);
|
|
382
|
-
totalRecords = countResult && countResult[0] ? parseInt(countResult[0].total) : 0;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Build query berdasarkan mode paginasi (subquery wrapping untuk JOIN/CTE)
|
|
386
|
-
let query;
|
|
387
|
-
if (paginate) {
|
|
388
|
-
const offset = (page - 1) * perPage;
|
|
389
|
-
query = needsSubquery
|
|
390
|
-
? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + perPage + ' OFFSET ' + offset
|
|
391
|
-
: baseQuery + ' ' + (whereClauseSql || '') + orderClause + ` LIMIT ${perPage} OFFSET ${offset}`;
|
|
392
|
-
} else {
|
|
393
|
-
query = needsSubquery
|
|
394
|
-
? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + limit
|
|
395
|
-
: baseQuery + ' ' + (whereClauseSql || '') + orderClause + ` LIMIT ${limit}`;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
console.log('List SQL Query:', query);
|
|
399
|
-
console.log('List Query Parameters:', whereParams);
|
|
400
|
-
const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
|
|
401
|
-
|
|
402
|
-
const data = rawData ? rawData.map((row) => {
|
|
403
|
-
return this.formatResponseData(row);
|
|
404
|
-
}) : [];
|
|
405
|
-
|
|
406
|
-
const result = {
|
|
407
|
-
success: true,
|
|
408
|
-
data: data
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
if (paginate) {
|
|
412
|
-
const totalPages = Math.ceil(totalRecords / perPage);
|
|
413
|
-
result.pagination = {
|
|
414
|
-
current_page: page,
|
|
415
|
-
per_page: perPage,
|
|
416
|
-
total_records: totalRecords,
|
|
417
|
-
total_pages: totalPages,
|
|
418
|
-
has_next: page < totalPages,
|
|
419
|
-
has_previous: page > 1
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Cache result
|
|
424
|
-
await this.setCachedList(options, result);
|
|
425
|
-
console.log(`[Cache] SET for list - ${cacheInfo}`);
|
|
426
|
-
|
|
427
|
-
return result;
|
|
428
|
-
} catch (error) {
|
|
429
|
-
console.error('Error in getList:', error);
|
|
430
|
-
throw error;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Override getLookupData untuk MySQL (dynamic search)
|
|
436
|
-
*/
|
|
437
|
-
async getLookupData(search) {
|
|
438
|
-
try {
|
|
439
|
-
const query = `SELECT id, item_name FROM ${this.getTableSource('read')} WHERE item_name LIKE ? ORDER BY item_name LIMIT 100`;
|
|
440
|
-
const params = [`%${search || ''}%`];
|
|
441
|
-
const data = await db.executeQuery(query, params);
|
|
442
|
-
|
|
443
|
-
const result = data.map(item => ({
|
|
444
|
-
id: item.id,
|
|
445
|
-
text: item.item_name
|
|
446
|
-
}));
|
|
447
|
-
|
|
448
|
-
return result;
|
|
449
|
-
} catch (error) {
|
|
450
|
-
console.error('Error in MySQL getLookupData:', error);
|
|
451
|
-
throw error;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Dynamic lookup dengan extra filters untuk MySQL
|
|
457
|
-
*/
|
|
458
|
-
async getLookupDataDynamic(search, extraFilters = {}) {
|
|
459
|
-
try {
|
|
460
|
-
let params = [];
|
|
461
|
-
let whereConditions = [];
|
|
462
|
-
|
|
463
|
-
if (search) {
|
|
464
|
-
whereConditions.push(`item_name LIKE ?`);
|
|
465
|
-
params.push(`%${search}%`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Add extra filters
|
|
469
|
-
if (extraFilters && Object.keys(extraFilters).length > 0) {
|
|
470
|
-
for (const [key, value] of Object.entries(extraFilters)) {
|
|
471
|
-
if (this.validFields.includes(key) && value !== null && value !== undefined) {
|
|
472
|
-
whereConditions.push(`${key} = ?`);
|
|
473
|
-
params.push(value);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
|
|
479
|
-
|
|
480
|
-
const query = `SELECT id, item_name FROM ${this.getTableSource('read')} ${whereClause} ORDER BY item_name LIMIT 100`;
|
|
481
|
-
const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
|
|
482
|
-
|
|
483
|
-
return data.map(item => ({
|
|
484
|
-
id: item.id,
|
|
485
|
-
text: item.item_name
|
|
486
|
-
}));
|
|
487
|
-
} catch (error) {
|
|
488
|
-
console.error('Error in MySQL getLookupDataDynamic:', error);
|
|
489
|
-
throw error;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
/**
|
|
494
|
-
* Override getStaticLookupData untuk MySQL
|
|
495
|
-
*/
|
|
496
|
-
async getStaticLookupData(selectedTag) {
|
|
497
|
-
try {
|
|
498
|
-
// Check cache first - cache tanpa selectedTag karena data sama
|
|
499
|
-
const cacheOptions = { type: 'static' };
|
|
500
|
-
const cachedResult = await this.getCachedLookup(cacheOptions, 'static');
|
|
501
|
-
if (cachedResult) {
|
|
502
|
-
// Apply selectedTag to cached result
|
|
503
|
-
return cachedResult.map(item => {
|
|
504
|
-
if (item.id === selectedTag) {
|
|
505
|
-
return { ...item, selected: 'true' };
|
|
506
|
-
}
|
|
507
|
-
return { id: item.id, text: item.text };
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
const query = `SELECT id, item_name FROM ${this.getTableSource('read')} ORDER BY item_name LIMIT 1000`;
|
|
512
|
-
const data = await db.executeQuery(query);
|
|
513
|
-
|
|
514
|
-
// Cache result tanpa selected flag
|
|
515
|
-
const cacheData = data.map(item => ({
|
|
516
|
-
id: item.id,
|
|
517
|
-
text: item.item_name
|
|
518
|
-
}));
|
|
519
|
-
await this.setCachedLookup(cacheOptions, cacheData, 'static');
|
|
520
|
-
|
|
521
|
-
// Return dengan selected flag
|
|
522
|
-
return data.map(item => {
|
|
523
|
-
const row = {
|
|
524
|
-
id: item.id,
|
|
525
|
-
text: item.item_name
|
|
526
|
-
};
|
|
527
|
-
if (item.id === selectedTag) {
|
|
528
|
-
row.selected = 'true';
|
|
529
|
-
}
|
|
530
|
-
return row;
|
|
531
|
-
});
|
|
532
|
-
} catch (error) {
|
|
533
|
-
console.error('Error in MySQL getStaticLookupData:', error);
|
|
534
|
-
throw error;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/**
|
|
539
|
-
* Lookup dengan advanced filter support untuk MySQL
|
|
540
|
-
*/
|
|
541
|
-
async getLookupDataWithFilter(options) {
|
|
542
|
-
try {
|
|
543
|
-
// Check cache first
|
|
544
|
-
const cacheOptions = { ...options, type: 'filter' };
|
|
545
|
-
const cachedResult = await this.getCachedLookup(cacheOptions, 'filter');
|
|
546
|
-
if (cachedResult) return cachedResult;
|
|
547
|
-
|
|
548
|
-
const selectColumns = options.select || ['id', 'item_name'];
|
|
549
|
-
let params = [];
|
|
550
|
-
|
|
551
|
-
// Validasi text fields
|
|
552
|
-
const validTextFields = this.validFields.filter(field =>
|
|
553
|
-
field.includes('name') || field.includes('nama') ||
|
|
554
|
-
field.includes('code') || field.includes('kode') ||
|
|
555
|
-
field.includes('text') || field.includes('title')
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
let selectClause = 'id';
|
|
559
|
-
let textField = 'item_name';
|
|
560
|
-
let aliasField = null;
|
|
561
|
-
|
|
562
|
-
// Proses setiap column dalam select
|
|
563
|
-
for (const column of selectColumns) {
|
|
564
|
-
if (column.toLowerCase() === 'id') {
|
|
565
|
-
continue; // primary key sudah ada
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Check jika ada SQL expression dengan alias (menggunakan AS)
|
|
569
|
-
const aliasRegex = new RegExp('(.+)\\s+as\\s+(\\w+)$', 'i');
|
|
570
|
-
const aliasMatch = column.match(aliasRegex);
|
|
571
|
-
if (aliasMatch) {
|
|
572
|
-
const expression = aliasMatch[1].trim();
|
|
573
|
-
const alias = aliasMatch[2].trim();
|
|
574
|
-
selectClause += `, ${expression} AS ${alias}`;
|
|
575
|
-
textField = alias;
|
|
576
|
-
aliasField = alias;
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Check jika simple field name
|
|
581
|
-
if (this.validFields.includes(column) || validTextFields.includes(column)) {
|
|
582
|
-
selectClause += `, ${column}`;
|
|
583
|
-
textField = column;
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Computed column
|
|
588
|
-
selectClause += `, ${column}`;
|
|
589
|
-
textField = column;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
let query = `SELECT ${selectClause} FROM ${this.getTableSource('read')} `;
|
|
593
|
-
|
|
594
|
-
// Build WHERE clause jika ada
|
|
595
|
-
if ((options.where && Array.isArray(options.where) && options.where.length > 0) ||
|
|
596
|
-
(options.where && options.where.conditions && Array.isArray(options.where.conditions) && options.where.conditions.length > 0)) {
|
|
597
|
-
try {
|
|
598
|
-
const whereResult = this.buildComplexWhereClause(options.where, params);
|
|
599
|
-
query += `WHERE ${whereResult.sql} `;
|
|
600
|
-
params = whereResult.params;
|
|
601
|
-
} catch (e) {
|
|
602
|
-
const error = new Error('Invalid where conditions: ' + e.message);
|
|
603
|
-
error.statusCode = 400;
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Handle sort_columns
|
|
609
|
-
if (options.sort_columns && Array.isArray(options.sort_columns) && options.sort_columns.length > 0) {
|
|
610
|
-
const orderParts = options.sort_columns.map(item => {
|
|
611
|
-
const column = item.column;
|
|
612
|
-
const direction = (item.direction || 'ASC').toUpperCase();
|
|
613
|
-
if (!column) return null;
|
|
614
|
-
if (!this.validFields.includes(column)) return null;
|
|
615
|
-
if (direction !== 'ASC' && direction !== 'DESC') return null;
|
|
616
|
-
return `${column} ${direction}`;
|
|
617
|
-
}).filter(Boolean);
|
|
618
|
-
|
|
619
|
-
if (orderParts.length === 0) {
|
|
620
|
-
const error = new Error('No valid sort columns provided');
|
|
621
|
-
error.statusCode = 400;
|
|
622
|
-
throw error;
|
|
623
|
-
}
|
|
624
|
-
query += `ORDER BY ${orderParts.join(', ')}`;
|
|
625
|
-
} else {
|
|
626
|
-
query += `ORDER BY ${aliasField || textField}`;
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
console.log('MySQL Lookup Filter Query:', query);
|
|
630
|
-
console.log('Parameters:', params);
|
|
631
|
-
|
|
632
|
-
const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
|
|
633
|
-
|
|
634
|
-
const result = data.map(item => ({
|
|
635
|
-
id: item.id,
|
|
636
|
-
text: item[aliasField || textField] || item.item_name || ''
|
|
637
|
-
}));
|
|
638
|
-
|
|
639
|
-
// Cache the result
|
|
640
|
-
await this.setCachedLookup(cacheOptions, result, 'filter');
|
|
641
|
-
|
|
642
|
-
return result;
|
|
643
|
-
} catch (error) {
|
|
644
|
-
console.error('Error in getLookupDataWithFilter:', error);
|
|
645
|
-
throw error;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Build advanced filter conditions untuk MySQL
|
|
651
|
-
* @param {Array} filters - Array of {column, type, value, value2}
|
|
652
|
-
* @returns {Object} {sql, params}
|
|
653
|
-
*/
|
|
654
|
-
buildAdvancedFilterCondition(filters) {
|
|
655
|
-
if (!filters || !Array.isArray(filters) || filters.length === 0) {
|
|
656
|
-
return { sql: '', params: [] };
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
const conditions = [];
|
|
660
|
-
const params = [];
|
|
661
|
-
|
|
662
|
-
for (const filter of filters) {
|
|
663
|
-
const { column, type, value, value2 } = filter;
|
|
664
|
-
|
|
665
|
-
if (!column || !this.validFields.includes(column)) continue;
|
|
666
|
-
|
|
667
|
-
switch (type) {
|
|
668
|
-
case 'equals':
|
|
669
|
-
conditions.push(`${column} = ?`);
|
|
670
|
-
params.push(value);
|
|
671
|
-
break;
|
|
672
|
-
case 'not_equals':
|
|
673
|
-
conditions.push(`${column} <> ?`);
|
|
674
|
-
params.push(value);
|
|
675
|
-
break;
|
|
676
|
-
case 'contains':
|
|
677
|
-
case 'like':
|
|
678
|
-
conditions.push(`${column} LIKE ?`);
|
|
679
|
-
params.push(`%${value}%`);
|
|
680
|
-
break;
|
|
681
|
-
case 'not_contains':
|
|
682
|
-
case 'not_like':
|
|
683
|
-
conditions.push(`${column} NOT LIKE ?`);
|
|
684
|
-
params.push(`%${value}%`);
|
|
685
|
-
break;
|
|
686
|
-
case 'starts_with':
|
|
687
|
-
conditions.push(`${column} LIKE ?`);
|
|
688
|
-
params.push(`${value}%`);
|
|
689
|
-
break;
|
|
690
|
-
case 'ends_with':
|
|
691
|
-
conditions.push(`${column} LIKE ?`);
|
|
692
|
-
params.push(`%${value}`);
|
|
693
|
-
break;
|
|
694
|
-
case 'greater_than':
|
|
695
|
-
conditions.push(`${column} > ?`);
|
|
696
|
-
params.push(value);
|
|
697
|
-
break;
|
|
698
|
-
case 'less_than':
|
|
699
|
-
conditions.push(`${column} < ?`);
|
|
700
|
-
params.push(value);
|
|
701
|
-
break;
|
|
702
|
-
case 'greater_equal':
|
|
703
|
-
conditions.push(`${column} >= ?`);
|
|
704
|
-
params.push(value);
|
|
705
|
-
break;
|
|
706
|
-
case 'less_equal':
|
|
707
|
-
conditions.push(`${column} <= ?`);
|
|
708
|
-
params.push(value);
|
|
709
|
-
break;
|
|
710
|
-
case 'between':
|
|
711
|
-
conditions.push(`${column} BETWEEN ? AND ?`);
|
|
712
|
-
params.push(value, value2);
|
|
713
|
-
break;
|
|
714
|
-
case 'in':
|
|
715
|
-
if (Array.isArray(value)) {
|
|
716
|
-
const inPlaceholders = value.map(() => '?').join(', ');
|
|
717
|
-
conditions.push(`${column} IN (${inPlaceholders})`);
|
|
718
|
-
params.push(...value);
|
|
719
|
-
}
|
|
720
|
-
break;
|
|
721
|
-
case 'not_in':
|
|
722
|
-
if (Array.isArray(value)) {
|
|
723
|
-
const notInPlaceholders = value.map(() => '?').join(', ');
|
|
724
|
-
conditions.push(`${column} NOT IN (${notInPlaceholders})`);
|
|
725
|
-
params.push(...value);
|
|
726
|
-
}
|
|
727
|
-
break;
|
|
728
|
-
case 'is_null':
|
|
729
|
-
conditions.push(`${column} IS NULL`);
|
|
730
|
-
break;
|
|
731
|
-
case 'is_not_null':
|
|
732
|
-
conditions.push(`${column} IS NOT NULL`);
|
|
733
|
-
break;
|
|
734
|
-
case 'date_equals':
|
|
735
|
-
conditions.push(`DATE(${column}) = ?`);
|
|
736
|
-
params.push(value);
|
|
737
|
-
break;
|
|
738
|
-
case 'date_between':
|
|
739
|
-
conditions.push(`DATE(${column}) BETWEEN ? AND ?`);
|
|
740
|
-
params.push(value, value2);
|
|
741
|
-
break;
|
|
742
|
-
case 'date_after':
|
|
743
|
-
conditions.push(`DATE(${column}) > ?`);
|
|
744
|
-
params.push(value);
|
|
745
|
-
break;
|
|
746
|
-
case 'date_before':
|
|
747
|
-
conditions.push(`DATE(${column}) < ?`);
|
|
748
|
-
params.push(value);
|
|
749
|
-
break;
|
|
750
|
-
default:
|
|
751
|
-
break;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
if (conditions.length === 0) {
|
|
756
|
-
return { sql: '', params: [] };
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
return { sql: conditions.join(' AND '), params };
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Escape value untuk MySQL SQL (sanitization)
|
|
764
|
-
*/
|
|
765
|
-
escapeValue(value) {
|
|
766
|
-
if (value === null || value === undefined) return null;
|
|
767
|
-
if (typeof value === 'number') return value;
|
|
768
|
-
return String(value).replace(/'/g, "''");
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
/**
|
|
772
|
-
* Validasi data sebelum insert/update
|
|
773
|
-
*/
|
|
774
|
-
async validateData(data, operation = 'insert') {
|
|
775
|
-
const result = {
|
|
776
|
-
isValid: true,
|
|
777
|
-
errors: [],
|
|
778
|
-
warnings: [],
|
|
779
|
-
sanitizedData: {}
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
try {
|
|
783
|
-
const hasFieldValidation = this.validationConfig && Object.keys(this.validationConfig).length > 0;
|
|
784
|
-
|
|
785
|
-
if (hasFieldValidation) {
|
|
786
|
-
// Loop semua field yang ada di validationConfig
|
|
787
|
-
for (const fieldName in this.validationConfig) {
|
|
788
|
-
let value = data[fieldName];
|
|
789
|
-
const config = this.validationConfig[fieldName];
|
|
790
|
-
const constraints = config.constraints || {};
|
|
791
|
-
|
|
792
|
-
// Auto-generate value jika autoGenerate dan nilai kosong.
|
|
793
|
-
// String dan uuid diperlakukan sama: UUID v7 via uuid package
|
|
794
|
-
// (konsisten lintas dialect; cocok dengan konvensi payload category.json
|
|
795
|
-
// yang memakai type: "string" dengan constraint autoGenerate + primaryKey).
|
|
796
|
-
if (operation === 'insert' && constraints.autoGenerate && (!value || value === '')) {
|
|
797
|
-
if (config.type === 'uuid' || config.type === 'string') {
|
|
798
|
-
value = require('uuid').v7();
|
|
799
|
-
data[fieldName] = value;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// Skip validation jika value kosong dan tidak required
|
|
804
|
-
if (value === undefined || value === null || value === '') {
|
|
805
|
-
if (constraints.required) {
|
|
806
|
-
// Skip: autoGenerate atau primaryKey di insert
|
|
807
|
-
if (operation === 'insert' && (constraints.autoGenerate || constraints.primaryKey)) {
|
|
808
|
-
// OK — akan di-generate otomatis
|
|
809
|
-
}
|
|
810
|
-
// Skip: update partial — field tidak dikirim berarti tidak diubah
|
|
811
|
-
else if (operation === 'update' && value === undefined) {
|
|
812
|
-
// OK — field tidak sedang di-update
|
|
813
|
-
}
|
|
814
|
-
else {
|
|
815
|
-
const message = constraints.requiredMessage || `Field '${fieldName}' is required`;
|
|
816
|
-
result.errors.push(message);
|
|
817
|
-
result.isValid = false;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
continue;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// String field: hash constraint support
|
|
824
|
-
if (config.type === 'string' && typeof value === 'string') {
|
|
825
|
-
let sanitized = value;
|
|
826
|
-
const fieldErrors = [];
|
|
827
|
-
const isHashField = constraints.hash === 'bcrypt';
|
|
828
|
-
|
|
829
|
-
// Trim
|
|
830
|
-
if (constraints.trim) {
|
|
831
|
-
sanitized = sanitized.trim();
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
// Case transformation (skip jika hash field)
|
|
835
|
-
if (!isHashField) {
|
|
836
|
-
if (constraints.lowercase) {
|
|
837
|
-
sanitized = sanitized.toLowerCase();
|
|
838
|
-
} else if (constraints.uppercase) {
|
|
839
|
-
sanitized = sanitized.toUpperCase();
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Length validation (validasi plaintext sebelum hash)
|
|
844
|
-
if (constraints.minLength && sanitized.length < constraints.minLength) {
|
|
845
|
-
fieldErrors.push(constraints.minLengthMessage || `Field '${fieldName}' must be at least ${constraints.minLength} characters`);
|
|
846
|
-
}
|
|
847
|
-
if (constraints.maxLength && !isHashField && sanitized.length > constraints.maxLength) {
|
|
848
|
-
fieldErrors.push(constraints.maxLengthMessage || `Field '${fieldName}' must not exceed ${constraints.maxLength} characters`);
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
// Pattern validation
|
|
852
|
-
if (constraints.pattern) {
|
|
853
|
-
const regex = new RegExp(constraints.pattern);
|
|
854
|
-
if (!regex.test(sanitized)) {
|
|
855
|
-
fieldErrors.push(constraints.patternMessage || `Field '${fieldName}' does not match required pattern`);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Format validation
|
|
860
|
-
if (constraints.format === 'email' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(sanitized)) {
|
|
861
|
-
fieldErrors.push(constraints.formatMessage || `Field '${fieldName}' has invalid email format`);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
if (fieldErrors.length > 0) {
|
|
865
|
-
result.isValid = false;
|
|
866
|
-
result.errors.push(...fieldErrors);
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// Hash transformation (setelah semua validation pass)
|
|
870
|
-
if (isHashField && fieldErrors.length === 0) {
|
|
871
|
-
const bcrypt = require('bcrypt');
|
|
872
|
-
const cost = constraints.hashCost || 10;
|
|
873
|
-
sanitized = await bcrypt.hash(sanitized, cost);
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
result.sanitizedData[fieldName] = sanitized;
|
|
877
|
-
} else {
|
|
878
|
-
// Non-string field: basic sanitization
|
|
879
|
-
result.sanitizedData[fieldName] = value;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
// Validate field yang tidak ada di validationConfig (backward compatibility)
|
|
884
|
-
for (const field of this.validFields) {
|
|
885
|
-
if (!this.validationConfig[field] && data[field] !== undefined && data[field] !== null) {
|
|
886
|
-
if (typeof data[field] === 'string') {
|
|
887
|
-
result.sanitizedData[field] = data[field].trim().replace(/\0/g, '').substring(0, 4000);
|
|
888
|
-
} else {
|
|
889
|
-
result.sanitizedData[field] = data[field];
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
} else {
|
|
894
|
-
// Fallback: Tidak ada fieldValidation - gunakan generic sanitization
|
|
895
|
-
for (const field of this.validFields) {
|
|
896
|
-
const value = data[field];
|
|
897
|
-
if (value !== undefined && value !== null) {
|
|
898
|
-
if (typeof value === 'string') {
|
|
899
|
-
result.sanitizedData[field] = value.trim().replace(/\0/g, '').substring(0, 4000);
|
|
900
|
-
} else {
|
|
901
|
-
result.sanitizedData[field] = value;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
} catch (error) {
|
|
907
|
-
result.errors.push(`Validation error: ${error.message}`);
|
|
908
|
-
result.isValid = false;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
return result;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
/**
|
|
915
|
-
* Get field mapping information
|
|
916
|
-
*/
|
|
917
|
-
getFieldMapping() {
|
|
918
|
-
return {
|
|
919
|
-
allFields: this.validFields,
|
|
920
|
-
primaryKey: this.primaryKey,
|
|
921
|
-
textFields: this.validFields.filter(f => f.includes('name') || f.includes('nama') || f.includes('description')),
|
|
922
|
-
dateFields: this.validFields.filter(f => f.includes('date') || f.includes('time')),
|
|
923
|
-
numericFields: this.validFields.filter(f => f.includes('amount') || f.includes('price') || f.includes('count'))
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
/**
|
|
928
|
-
* Get MySQL connection info untuk health check
|
|
929
|
-
*/
|
|
930
|
-
async getConnectionInfo() {
|
|
931
|
-
try {
|
|
932
|
-
const result = await db.executeQuery('SELECT 1 as test_con');
|
|
933
|
-
if (result && result.length > 0) {
|
|
934
|
-
return { connected: true, database: 'MySQL', retrievedAt: new Date().toISOString() };
|
|
935
|
-
}
|
|
936
|
-
return null;
|
|
937
|
-
} catch (error) {
|
|
938
|
-
return { connected: false, error: error.message, checkedAt: new Date().toISOString() };
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
module.exports = new ItemModel();
|