@restforgejs/platform 4.2.8 → 4.3.2

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.
Files changed (325) hide show
  1. package/SECURITY.md +83 -4
  2. package/bin/sdf-tools.exe +0 -0
  3. package/build-info.json +2 -2
  4. package/cli/consumer-deploy.js +1 -1
  5. package/cli/consumer.js +1 -1
  6. package/generators/cli/dashboard/create.js +4 -1
  7. package/generators/cli/endpoint/create.js +1 -1
  8. package/generators/cli/key/generate.js +2 -1
  9. package/generators/cli/key/revoke.js +2 -1
  10. package/generators/cli/payload/diff.js +3 -2
  11. package/generators/cli/payload/generate.js +3 -2
  12. package/generators/cli/payload/sync.js +3 -2
  13. package/generators/cli/payload/validate.js +3 -2
  14. package/generators/cli/processor/create.js +14 -3
  15. package/generators/cli/project/delete.js +2 -1
  16. package/generators/cli/query/validate.js +3 -2
  17. package/generators/cli/schema/apply.js +3 -2
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +3 -2
  20. package/generators/cli/schema/introspect.js +3 -2
  21. package/generators/cli/schema/list.js +3 -2
  22. package/generators/cli/schema/migrate.js +3 -2
  23. package/generators/lib/migration/audit-table-runner.js +213 -215
  24. package/generators/lib/payload/payload-runner.js +1 -1
  25. package/generators/lib/templates/dashboard-catalog.js +1 -437
  26. package/generators/lib/templates/db-connection-env.js +1 -212
  27. package/generators/lib/templates/dbschema-catalog.js +1 -489
  28. package/generators/lib/templates/field-validation-catalog.js +1 -531
  29. package/generators/lib/templates/mysql-template.js +1 -3863
  30. package/generators/lib/templates/oracle-template.js +1 -3915
  31. package/generators/lib/templates/postgres-template.js +1 -5838
  32. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  33. package/generators/lib/templates/sqlite-template.js +1 -3440
  34. package/generators/lib/utils/env-manager.js +6 -0
  35. package/generators/lib/utils/path-validator.js +71 -0
  36. package/generators/lib/validators/payload-validator.js +1 -2
  37. package/integrity-manifest.json +28 -10
  38. package/package.json +11 -3
  39. package/scripts/verify-integrity.js +1 -1
  40. package/server.js +1 -1
  41. package/src/components/handlers/adjust_handler.js +1 -1
  42. package/src/components/handlers/audit_handler.js +1 -1
  43. package/src/components/handlers/delete_handler.js +1 -1
  44. package/src/components/handlers/export_handler.js +1 -1
  45. package/src/components/handlers/import_handler.js +1 -1
  46. package/src/components/handlers/insert_handler.js +1 -1
  47. package/src/components/handlers/update_handler.js +1 -1
  48. package/src/components/handlers/upload_handler.js +1 -1
  49. package/src/components/handlers/workflow_handler.js +1 -1
  50. package/src/components/integrations/webhook.js +1 -1
  51. package/src/consumers/baseConsumer.js +1 -1
  52. package/src/consumers/declarativeMapper.js +1 -1
  53. package/src/consumers/handlers/apiHandler.js +1 -1
  54. package/src/consumers/handlers/consoleHandler.js +1 -1
  55. package/src/consumers/handlers/databaseHandler.js +1 -1
  56. package/src/consumers/handlers/index.js +1 -1
  57. package/src/consumers/handlers/kafkaHandler.js +1 -1
  58. package/src/consumers/index.js +1 -1
  59. package/src/consumers/messageTransformer.js +1 -1
  60. package/src/consumers/validator.js +1 -1
  61. package/src/core/db/dialect/base-dialect.js +1 -1
  62. package/src/core/db/dialect/index.js +1 -1
  63. package/src/core/db/dialect/mysql-dialect.js +1 -1
  64. package/src/core/db/dialect/oracle-dialect.js +1 -1
  65. package/src/core/db/dialect/postgres-dialect.js +1 -1
  66. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  67. package/src/core/db/flatten-helper.js +1 -1
  68. package/src/core/db/query-builder-error.js +1 -1
  69. package/src/core/db/query-builder.js +1 -1
  70. package/src/core/db/relation-helper.js +1 -1
  71. package/src/core/handlers/delete_handler.js +1 -1
  72. package/src/core/handlers/insert_handler.js +1 -1
  73. package/src/core/handlers/update_handler.js +1 -1
  74. package/src/core/models/base-model.js +1 -1
  75. package/src/core/utils/cache-manager.js +1 -1
  76. package/src/core/utils/component-engine.js +1 -1
  77. package/src/core/utils/context-builder.js +1 -1
  78. package/src/core/utils/datetime-formatter.js +1 -1
  79. package/src/core/utils/datetime-parser.js +1 -1
  80. package/src/core/utils/db.js +1 -1
  81. package/src/core/utils/logger.js +1 -1
  82. package/src/core/utils/payload-loader.js +1 -1
  83. package/src/core/utils/security-checks.js +1 -1
  84. package/src/middleware/body-options.js +1 -1
  85. package/src/middleware/cors.js +1 -1
  86. package/src/middleware/idempotency.js +1 -1
  87. package/src/middleware/rate-limiter.js +1 -1
  88. package/src/middleware/request-logger.js +1 -1
  89. package/src/middleware/security-headers.js +1 -1
  90. package/src/models/base-model-mysql.js +1 -1
  91. package/src/models/base-model-oracle.js +1 -1
  92. package/src/models/base-model-sqlite.js +1 -1
  93. package/src/models/base-model.js +1 -1
  94. package/src/pro/caching/redis-client.js +1 -1
  95. package/src/pro/caching/redis-helper.js +1 -1
  96. package/src/pro/consumers/baseConsumer.js +1 -1
  97. package/src/pro/consumers/declarativeMapper.js +1 -1
  98. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  99. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  100. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  101. package/src/pro/consumers/handlers/index.js +1 -1
  102. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  103. package/src/pro/consumers/index.js +1 -1
  104. package/src/pro/consumers/messageTransformer.js +1 -1
  105. package/src/pro/consumers/validator.js +1 -1
  106. package/src/pro/database/base-model-mysql.js +1 -1
  107. package/src/pro/database/base-model-oracle.js +1 -1
  108. package/src/pro/database/base-model-sqlite.js +1 -1
  109. package/src/pro/database/db-mysql.js +1 -1
  110. package/src/pro/database/db-oracle.js +1 -1
  111. package/src/pro/database/db-sqlite.js +1 -1
  112. package/src/pro/excel/excel-generator.js +1 -1
  113. package/src/pro/excel/excel-parser.js +1 -1
  114. package/src/pro/excel/export-service.js +1 -1
  115. package/src/pro/excel/export_handler.js +1 -1
  116. package/src/pro/excel/import-service.js +1 -1
  117. package/src/pro/excel/import-validator.js +1 -1
  118. package/src/pro/excel/import_handler.js +1 -1
  119. package/src/pro/excel/upsert-builder.js +1 -1
  120. package/src/pro/idgen/idgen-routes.js +1 -1
  121. package/src/pro/integrations/lookup-resolver.js +1 -1
  122. package/src/pro/integrations/upload-handler-v2.js +1 -1
  123. package/src/pro/integrations/upload-handler.js +1 -1
  124. package/src/pro/integrations/webhook.js +1 -1
  125. package/src/pro/locking/lock-routes.js +1 -1
  126. package/src/pro/locking/resource-lock-manager.js +1 -1
  127. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  128. package/src/pro/messaging/kafkaService.js +1 -1
  129. package/src/pro/messaging/messagehubService.js +1 -1
  130. package/src/pro/messaging/rabbitmqService.js +1 -1
  131. package/src/pro/scheduler/job-manager.js +1 -1
  132. package/src/pro/scheduler/job-routes.js +1 -1
  133. package/src/pro/scheduler/job-validator.js +1 -1
  134. package/src/pro/storage/base-storage-provider.js +1 -1
  135. package/src/pro/storage/file-metadata-helper.js +1 -1
  136. package/src/pro/storage/index.js +1 -1
  137. package/src/pro/storage/local-storage-provider.js +1 -1
  138. package/src/pro/storage/s3-storage-provider.js +1 -1
  139. package/src/pro/storage/upload-cleanup-job.js +1 -1
  140. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  141. package/src/pro/storage/upload-pending-tracker.js +1 -1
  142. package/src/pro/websocket/broadcast-helper.js +1 -1
  143. package/src/pro/websocket/index.js +1 -1
  144. package/src/pro/websocket/livesync-server.js +1 -1
  145. package/src/pro/websocket/ws-broadcaster.js +1 -1
  146. package/src/services/export-service.js +1 -1
  147. package/src/services/import-service.js +1 -1
  148. package/src/services/kafkaConsumerService.js +1 -1
  149. package/src/services/kafkaService.js +1 -1
  150. package/src/services/messagehubService.js +1 -1
  151. package/src/services/rabbitmqService.js +1 -1
  152. package/src/utils/cache-invalidation-registry.js +1 -1
  153. package/src/utils/cache-manager.js +1 -1
  154. package/src/utils/component-engine.js +1 -1
  155. package/src/utils/config-extractor.js +1 -1
  156. package/src/utils/consumerLogger.js +1 -1
  157. package/src/utils/context-builder.js +1 -1
  158. package/src/utils/dashboard-helpers.js +1 -1
  159. package/src/utils/dateHelper.js +1 -1
  160. package/src/utils/datetime-formatter.js +1 -1
  161. package/src/utils/datetime-parser.js +1 -1
  162. package/src/utils/db-bootstrap.js +1 -1
  163. package/src/utils/db-mysql.js +1 -1
  164. package/src/utils/db-oracle.js +1 -1
  165. package/src/utils/db-sqlite.js +1 -1
  166. package/src/utils/db.js +1 -1
  167. package/src/utils/demo-generator.js +1 -1
  168. package/src/utils/excel-generator.js +1 -1
  169. package/src/utils/excel-parser.js +1 -1
  170. package/src/utils/file-watcher.js +1 -1
  171. package/src/utils/id-generator.js +1 -1
  172. package/src/utils/idempotency-manager.js +1 -1
  173. package/src/utils/import-validator.js +1 -1
  174. package/src/utils/license-client.js +1 -1
  175. package/src/utils/lock-manager.js +1 -1
  176. package/src/utils/logger.js +1 -1
  177. package/src/utils/lookup-resolver.js +1 -1
  178. package/src/utils/payload-loader.js +1 -1
  179. package/src/utils/processor-response.js +1 -1
  180. package/src/utils/rabbitmq.js +1 -1
  181. package/src/utils/redis-client.js +1 -1
  182. package/src/utils/redis-helper.js +1 -1
  183. package/src/utils/request-scope.js +1 -1
  184. package/src/utils/security-checks.js +1 -1
  185. package/src/utils/service-resolver.js +1 -1
  186. package/src/utils/shutdown-coordinator.js +1 -1
  187. package/src/utils/trusted-keys.js +1 -1
  188. package/src/utils/upload-handler.js +1 -1
  189. package/src/utils/upsert-builder.js +1 -1
  190. package/src/utils/workflow-hook-executor.js +1 -1
  191. package/generators/metadata/global.json +0 -58
  192. package/generators/metadata/test-mysql-workbench.json +0 -118
  193. package/generators/metadata/test-mysql.json +0 -56
  194. package/generators/metadata/test-oracle-workbench.json +0 -118
  195. package/generators/metadata/test-oracle.json +0 -56
  196. package/generators/metadata/test-pg-workbench.json +0 -118
  197. package/generators/metadata/test-pg.json +0 -56
  198. package/generators/scripts/obfuscate-source.js +0 -356
  199. package/generators/scripts/validate-catalog.js +0 -430
  200. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  201. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  202. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  203. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  204. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  205. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  206. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  207. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  208. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  209. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  210. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  211. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  212. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  213. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  214. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  215. package/generators/tests/integration-tahap4b/README.md +0 -145
  216. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  217. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  218. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  219. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  220. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  221. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  222. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  223. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  224. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  225. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  226. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  227. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  228. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  229. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  230. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  231. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  232. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  233. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  234. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  235. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  236. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  237. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  238. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  239. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  240. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  241. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  242. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  243. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  244. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  245. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  246. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  247. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  248. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  249. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  250. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  251. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  252. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  253. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  254. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  255. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  256. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  257. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  258. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  259. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  260. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  261. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  262. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  263. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  264. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  265. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  266. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  267. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  268. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  269. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  270. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  271. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  299. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  300. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  301. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  302. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  303. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  304. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  305. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  306. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  307. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  308. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  309. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  310. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  311. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  312. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  313. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  314. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  315. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  316. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  317. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  318. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  319. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  320. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  321. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  322. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  323. package/scripts/generate-integrity-manifest.js +0 -124
  324. package/scripts/snapshot-cli-contracts.js +0 -194
  325. package/scripts/verify-publish.js +0 -56
@@ -1,3863 +1 @@
1
- /**
2
- * MySQL Database Templates
3
- * Template generator untuk MySQL database
4
- * Paritas fungsional dengan Oracle dan PostgreSQL template
5
- */
6
-
7
- /**
8
- * Konversi string kebab-case/snake_case ke camelCase
9
- */
10
- function toCamelCase(str) {
11
- if (!str || typeof str !== 'string') return '';
12
- return str
13
- .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
14
- return index === 0 ? word.toLowerCase() : word.toUpperCase();
15
- })
16
- .replace(/\s+/g, '')
17
- .replace(/[-_]/g, '');
18
- }
19
-
20
- /**
21
- * Konversi string kebab-case/snake_case ke PascalCase
22
- */
23
- function toPascalCase(str) {
24
- if (!str || typeof str !== 'string') return '';
25
- const camelCase = toCamelCase(str);
26
- return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
27
- }
28
-
29
- /**
30
- * Build audit columns section untuk constructor model.
31
- * Membaca payload.auditColumns dan emit baris this.auditColumns yang sesuai.
32
- */
33
- function buildAuditColumnsSection(payload, indent) {
34
- if (!('auditColumns' in payload)) return '';
35
- const value = payload.auditColumns;
36
-
37
- if (value === false || value === null) {
38
- return `\n${indent}// Tabel tanpa audit columns: disable injection helper\n${indent}this.auditColumns = null;\n`;
39
- }
40
-
41
- if (typeof value === 'object' && !Array.isArray(value)) {
42
- const validKeys = ['createdAt', 'createdBy', 'updatedAt', 'updatedBy'];
43
- const filtered = {};
44
- validKeys.forEach(k => {
45
- if (value[k] !== undefined) filtered[k] = value[k];
46
- });
47
- const json = JSON.stringify(filtered, null, 2)
48
- .split('\n')
49
- .map((l, i) => i === 0 ? l : indent + l)
50
- .join('\n');
51
- return `\n${indent}// Custom audit columns mapping dari payload\n${indent}this.auditColumns = ${json};\n`;
52
- }
53
-
54
- throw new Error(`Invalid auditColumns value for ${payload.tableName}: must be false, null, or object`);
55
- }
56
-
57
- /**
58
- * Deteksi kolom text utama untuk lookup dari daftar field names
59
- */
60
- function detectTextColumn(fieldNames) {
61
- const priorities = ['name', 'nama', 'title', 'judul', 'description', 'deskripsi', 'code', 'kode'];
62
- for (const priority of priorities) {
63
- const found = fieldNames.find(f => f.toLowerCase().includes(priority));
64
- if (found) return found;
65
- }
66
- return fieldNames.find(field => field !== 'id') || fieldNames[0] || 'nama';
67
- }
68
-
69
- /**
70
- * Membuat template untuk main module MySQL
71
- */
72
- function createMysqlMainModuleTemplate(moduleName) {
73
- const moduleNameCapitalized = toPascalCase(moduleName);
74
- return `const express = require('express');
75
- const bodyParser = require('body-parser');
76
- const path = require('path');
77
- const fs = require('fs');
78
- const { v4: uuidv4 } = require('uuid');
79
- const { logger, logServerReady, logEndpointRegistered, createRequestLogger, logRequest } = require('@restforgejs/platform/src/utils/logger');
80
- const ExportHandler = require('@restforgejs/platform/src/components/handlers/export_handler');
81
- const ImportHandler = require('@restforgejs/platform/src/components/handlers/import_handler');
82
- const UploadHandler = require('@restforgejs/platform/src/components/handlers/upload_handler');
83
- const { extractExportConfigFromEndpoint, extractImportConfigFromEndpoint, extractUploadConfigFromEndpoint } = require('@restforgejs/platform/src/utils/config-extractor');
84
- const rateLimiter = require('@restforgejs/platform/src/middleware/rate-limiter');
85
- const idempotencyMiddleware = require('@restforgejs/platform/src/middleware/idempotency');
86
- const bodyOptionsMiddleware = require('@restforgejs/platform/src/middleware/body-options');
87
- const corsMiddleware = require('@restforgejs/platform/src/middleware/cors');
88
- const securityHeaders = require('@restforgejs/platform/src/middleware/security-headers');
89
-
90
- /**
91
- * Fungsi untuk mengeksekusi modul ${moduleName} (MySQL Database)
92
- * @param {Object} config - Konfigurasi untuk menjalankan modul
93
- * @param {number} config.port - Port untuk server
94
- * @param {string} config.key - API Key (opsional)
95
- * @returns {Promise<void>} Promise yang tidak pernah resolve agar server tetap berjalan
96
- */
97
- async function execute(config) {
98
- return new Promise((resolve) => {
99
- const app = express();
100
- const port = config.port || 3000;
101
- const serverAddress = config.serverAddress || '0.0.0.0';
102
- const moduleNameCapitalized = '${moduleNameCapitalized}';
103
-
104
- // Configuration options
105
- const loggingEnabled = config.logging !== false;
106
- const apiKeyRequired = !!config.key;
107
-
108
- logger.info({
109
- event: 'module_starting',
110
- module: moduleNameCapitalized,
111
- port,
112
- cors: process.env.CORS_ENABLED !== 'false',
113
- helmet: process.env.HELMET_ENABLED === 'true',
114
- logging: loggingEnabled,
115
- apiKey: apiKeyRequired
116
- }, \`Starting \${moduleNameCapitalized} module\`);
117
-
118
- // CORS middleware (konfigurasi via CORS_ENABLED dan CORS_ORIGINS di .env)
119
- app.use(corsMiddleware.middleware());
120
-
121
- // Security headers middleware (konfigurasi via HELMET_ENABLED di .env)
122
- app.use(securityHeaders.middleware());
123
-
124
- // Middleware untuk parsing JSON dengan penanganan error
125
- app.use(bodyParser.json({
126
- verify: (req, res, buf, encoding) => {
127
- if (buf.length === 0) {
128
- return;
129
- }
130
- try {
131
- JSON.parse(buf);
132
- } catch (error) {
133
- res.status(400).json({
134
- success: false,
135
- error: 'Invalid JSON payload',
136
- message: 'The payload sent is not a valid JSON format',
137
- details: error.message
138
- });
139
- throw new Error('Invalid JSON');
140
- }
141
- }
142
- }));
143
-
144
- app.use(bodyParser.urlencoded({ extended: true }));
145
-
146
- // Request logging middleware (Pino-based)
147
- app.use((req, res, next) => {
148
- req.id = req.headers['x-request-id'] || uuidv4();
149
- res.set('X-Request-ID', req.id);
150
-
151
- req.log = createRequestLogger({
152
- requestId: req.id,
153
- method: req.method,
154
- path: req.path,
155
- ip: req.ip
156
- });
157
-
158
- const startTime = process.hrtime();
159
-
160
- res.on('finish', () => {
161
- const [seconds, nanoseconds] = process.hrtime(startTime);
162
- const durationMs = parseFloat((seconds * 1000 + nanoseconds / 1e6).toFixed(2));
163
- logRequest(req, res, durationMs);
164
- });
165
-
166
- next();
167
- });
168
-
169
- // Middleware untuk validasi API key jika diperlukan
170
- if (config.key) {
171
- app.use((req, res, next) => {
172
- const apiKey = req.headers['x-api-key'];
173
- if (!apiKey || apiKey !== config.key) {
174
- return res.status(401).json({ error: 'Unauthorized: Invalid API Key' });
175
- }
176
- next();
177
- });
178
- }
179
-
180
- // Rate limiting middleware (store: memory untuk single mode, Redis untuk cluster mode)
181
- rateLimiter.setStore(config.cluster ? 'redis' : 'memory');
182
- app.use('/api', rateLimiter.middleware());
183
-
184
- // Idempotency middleware (protects mutation endpoints from duplicate execution)
185
- if (process.env.IDEMPOTENCY_ENABLED === 'true') {
186
- app.use('/api', idempotencyMiddleware.middleware());
187
- }
188
-
189
- // Body options middleware (extract {data, options} format dari request body)
190
- app.use('/api', bodyOptionsMiddleware.middleware());
191
-
192
- // Auto-load plugin (jika ada)
193
- const moduleName = '${moduleName}';
194
- const pluginPath = path.join(__dirname, '..', 'plugins', \`\${moduleName}-plugin.js\`);
195
- let plugin = null;
196
- if (fs.existsSync(pluginPath)) {
197
- try {
198
- plugin = require(pluginPath);
199
- if (plugin.onBeforeEndpointsLoad) {
200
- plugin.onBeforeEndpointsLoad(app, config);
201
- }
202
- logger.info({ event: 'plugin_loaded', plugin: \`\${moduleName}-plugin\` }, \`Plugin loaded: \${moduleName}-plugin.js\`);
203
- } catch (pluginError) {
204
- logger.error({ event: 'plugin_load_error', error: pluginError.message }, \`Failed to load plugin: \${moduleName}-plugin.js\`);
205
- }
206
- }
207
-
208
- // Health check endpoint
209
- app.get('/api/${moduleName}/health', (req, res) => {
210
- const healthInfo = {
211
- status: 'ok',
212
- timestamp: new Date().toISOString().replace('T', ' ').replace(/\\.\\d{3}Z$/, ''),
213
- service: '${moduleName}',
214
- uptime: process.uptime(),
215
- memory: process.memoryUsage(),
216
- system: {
217
- platform: process.platform,
218
- nodeVersion: process.version,
219
- pid: process.pid
220
- }
221
- };
222
-
223
- res.json(healthInfo);
224
- });
225
-
226
- // Muat semua rute dari folder ${moduleName}
227
- const modulesDir = path.join(__dirname, '${moduleName}');
228
-
229
- try {
230
- if (!fs.existsSync(modulesDir)) {
231
- fs.mkdirSync(modulesDir, { recursive: true });
232
- console.log(\`Directory \$\{modulesDir} created successfully\`);
233
- }
234
-
235
- const files = fs.readdirSync(modulesDir);
236
- const endpointFiles = files.filter(file => file.endsWith('.js'));
237
-
238
- if (endpointFiles.length === 0) {
239
- console.log(\`No endpoint files found in \${modulesDir}\`);
240
- console.log(\`Add endpoint files to enable API functionality\`);
241
- } else {
242
- logger.info({ event: 'endpoints_loading', count: endpointFiles.length }, \`Loading \${endpointFiles.length} endpoint(s)\`);
243
- }
244
-
245
- for (const file of endpointFiles) {
246
- try {
247
- const endpointName = path.basename(file, '.js');
248
- const endpointPath = path.join(modulesDir, file);
249
-
250
- // Clear module cache untuk development
251
- if (require.cache[endpointPath]) {
252
- delete require.cache[endpointPath];
253
- }
254
-
255
- const moduleRoutes = require(endpointPath);
256
-
257
- const endpointPrefix = \`/api/${moduleName}/\$\{endpointName}\`;
258
- app.use(endpointPrefix, moduleRoutes);
259
- logEndpointRegistered(endpointName, endpointPrefix);
260
-
261
- // Register export routes via centralized handler
262
- try {
263
- const exportConfig = extractExportConfigFromEndpoint(endpointPath);
264
- if (exportConfig) {
265
- ExportHandler.registerRoutes(app, '${moduleName}', endpointName, exportConfig);
266
- }
267
- } catch (exportError) {
268
- logger.error({ event: 'export_registration_error', endpoint: endpointName, error: exportError.message }, \`Export registration failed for \$\{endpointName}: \$\{exportError.message}\`);
269
- }
270
-
271
- // Register import routes via centralized handler
272
- try {
273
- const importConfig = extractImportConfigFromEndpoint(endpointPath);
274
- if (importConfig) {
275
- ImportHandler.registerRoutes(app, '${moduleName}', endpointName, importConfig);
276
- logger.info({ event: 'import_routes_registered', endpoint: endpointName }, \`Import routes registered for \$\{endpointName}\`);
277
- }
278
- } catch (importError) {
279
- logger.error({ event: 'import_registration_error', endpoint: endpointName, error: importError.message }, \`Import registration failed for \$\{endpointName}: \$\{importError.message}\`);
280
- }
281
-
282
- // Register upload routes via centralized handler
283
- try {
284
- const uploadConfig = extractUploadConfigFromEndpoint(endpointPath);
285
- if (uploadConfig) {
286
- UploadHandler.registerRoutes(app, '${moduleName}', endpointName, uploadConfig);
287
- logger.info({ event: 'upload_routes_registered', endpoint: endpointName, fields: Object.keys(uploadConfig.fields || {}) }, \`Upload routes registered for \$\{endpointName}\`);
288
- }
289
- } catch (uploadError) {
290
- logger.error({ event: 'upload_registration_error', endpoint: endpointName, error: uploadError.message }, \`Upload registration failed for \$\{endpointName}: \$\{uploadError.message}\`);
291
- }
292
- } catch (error) {
293
- console.error(\`Error loading module \$\{file} from ${moduleName}:\`, error);
294
- throw error;
295
- }
296
- }
297
-
298
- // Register export cleanup route
299
- try {
300
- ExportHandler.registerCleanupRoute(app);
301
- } catch (cleanupError) {
302
- console.error('Export cleanup route registration failed:', cleanupError.message);
303
- }
304
-
305
- app.get('/', (req, res) => {
306
- res.json({
307
- message: '${moduleName} API (MySQL Database)',
308
- status: 'running',
309
- database: 'MySQL'
310
- });
311
- });
312
-
313
- // Error handling middleware
314
- app.use((err, req, res, next) => {
315
- console.error('Error:', err);
316
-
317
- if (err instanceof SyntaxError && err.status === 400 && 'body' in err && !res.headersSent) {
318
- return res.status(400).json({
319
- success: false,
320
- error: 'Invalid JSON payload',
321
- message: 'The payload sent is not a valid JSON format',
322
- details: err.message
323
- });
324
- }
325
-
326
- if (!res.headersSent) {
327
- return res.status(500).json({
328
- success: false,
329
- error: 'Internal Server Error',
330
- message: 'An error occurred on the server',
331
- details: err.message
332
- });
333
- }
334
-
335
- next(err);
336
- });
337
-
338
- // Hook untuk mount middleware tambahan sebelum catch-all 403
339
- // Digunakan oleh runtime untuk register admin endpoint dan middleware eksternal lainnya
340
- if (config.onAppReady && typeof config.onAppReady === 'function') {
341
- config.onAppReady(app);
342
- }
343
-
344
- app.use((req, res) => {
345
- res.status(403).json({
346
- success: false,
347
- error: 'Forbidden',
348
- message: 'Access to the requested resource is forbidden',
349
- timestamp: new Date().toISOString()
350
- });
351
- });
352
-
353
- const server = app.listen(port, serverAddress, (err) => {
354
- if (err) {
355
- console.error(\`Failed to start \${moduleNameCapitalized} server:\`, err);
356
- return;
357
- }
358
-
359
- // Determine display URL based on serverAddress
360
- const displayHost = (serverAddress === '0.0.0.0' || !serverAddress) ? 'localhost' : serverAddress;
361
-
362
- logServerReady({
363
- port,
364
- module: '${moduleName}',
365
- healthCheck: \`http://\${displayHost}:\${port}/api/${moduleName}/health\`,
366
- serviceInfo: \`http://\${displayHost}:\${port}/api/${moduleName}/info\`,
367
- baseUrl: \`http://\${displayHost}:\${port}\`
368
- });
369
- console.log('');
370
-
371
- // Execute plugin onAfterServerStart hook (jika ada)
372
- if (plugin && plugin.onAfterServerStart) {
373
- try {
374
- plugin.onAfterServerStart(app, config);
375
- } catch (pluginError) {
376
- logger.error({ event: 'plugin_after_start_error', error: pluginError.message }, 'Plugin onAfterServerStart failed');
377
- }
378
- }
379
- });
380
-
381
- process.on('SIGINT', () => {
382
- console.log('Menerima sinyal SIGINT, shutting down...');
383
- process.exit(0);
384
- });
385
-
386
- process.on('SIGTERM', () => {
387
- console.log('Menerima sinyal SIGTERM, shutting down...');
388
- process.exit(0);
389
- });
390
-
391
- process.on('uncaughtException', (error) => {
392
- console.error('Uncaught Exception:', error);
393
- });
394
-
395
- process.on('unhandledRejection', (reason, promise) => {
396
- console.error('Unhandled Rejection at:', promise, 'reason:', reason);
397
- });
398
-
399
- } catch (error) {
400
- console.error('Error saat menjalankan modul ${moduleName}:', error);
401
- resolve();
402
- }
403
- });
404
- }
405
-
406
- module.exports = { execute };`;
407
- }
408
-
409
- /**
410
- * Build SQL condition string dari defaultScope filter object.
411
- * Dijalankan di generation time — output di-hardcode ke generated module.
412
- * MySQL menyimpan boolean sebagai VARCHAR string 'true'/'false' (bukan 1/0).
413
- * @param {Object} scopeFilter - Object filter, misal { is_active: true }
414
- * @param {string} [prefix=''] - Prefix kolom (misal 'a.' atau '')
415
- * @returns {string} SQL condition string, misal "is_active = 'true'"
416
- */
417
- function buildDefaultScopeSQL(scopeFilter, prefix = '') {
418
- if (!scopeFilter || typeof scopeFilter !== 'object') return '';
419
- const conditions = [];
420
- for (const [col, val] of Object.entries(scopeFilter)) {
421
- if (typeof val === 'boolean') {
422
- conditions.push(`${prefix}${col} = '${val}'`);
423
- } else if (typeof val === 'string') {
424
- conditions.push(`${prefix}${col} = '${val.replace(/'/g, "''")}'`);
425
- } else if (typeof val === 'number') {
426
- conditions.push(`${prefix}${col} = ${val}`);
427
- }
428
- }
429
- return conditions.join(' AND ');
430
- }
431
-
432
- /**
433
- * Generate request scope middleware code (Layer 1 RLS) for MySQL template.
434
- * Middleware injected between validation middleware and route handlers.
435
- * Runs at GENERATION TIME to emit code string executed at RUNTIME.
436
- *
437
- * Mirrors postgres-template.js behavior — middleware code is JS, not SQL,
438
- * so it is identical across database dialects (see spec Bagian 4.1).
439
- *
440
- * @param {Object} payload - Endpoint payload JSON
441
- * @param {string} endpointName - Endpoint name (used to compute composite rootKey)
442
- * @returns {string} Generated middleware code, or empty string if requestScope absent
443
- */
444
- function buildRequestScopeMiddleware(payload, endpointName) {
445
- if (!payload.requestScope) return '';
446
-
447
- const { column, source, bypassRoles } = payload.requestScope;
448
- const bypassRolesJSON = JSON.stringify(bypassRoles || []);
449
- // Composite endpoints wrap master data under req.body[rootKey]
450
- // (rootKey mirrors the endpoint name with dashes replaced by underscores).
451
- const rootKey = (endpointName || '').replace(/-/g, '_');
452
-
453
- return `
454
- // ─── Request Scope Middleware (Layer 1 RLS) ──────────────────────────
455
- // Dynamic per-request filtering by '${column}' from ${source}
456
- // Generated from requestScope configuration in payload JSON
457
- router.use((req, res, next) => {
458
- if (req.method !== 'POST') return next();
459
-
460
- // Skip when no auth context (endpoint without auth middleware)
461
- if (!req.user) return next();
462
-
463
- // Skip when user has bypass role
464
- const userRoles = req.user.roles || [];
465
- const bypassRoles = ${bypassRolesJSON};
466
- if (bypassRoles.length > 0 && bypassRoles.some(role => userRoles.includes(role))) {
467
- return next();
468
- }
469
-
470
- // Read scope value from user context
471
- const scopeValue = ${source};
472
- if (scopeValue === undefined || scopeValue === null) {
473
- return res.status(403).json({
474
- success: false,
475
- error: 'Forbidden',
476
- message: 'Request scope value not available in user context',
477
- timestamp: new Date().toISOString()
478
- });
479
- }
480
-
481
- // Expose scope info for route handlers (ownership verification in /first, /update, /update-composite)
482
- req._requestScope = { column: '${column}', value: scopeValue };
483
-
484
- const endpoint = req.path.substring(1);
485
- const scopeCondition = { key: '${column}', value: scopeValue };
486
-
487
- // READ operations: inject scope into req.body.where
488
- if (['datatables', 'read', 'lookup', 'read-composite'].includes(endpoint)) {
489
- if (!req.body.where) {
490
- req.body.where = [scopeCondition];
491
- } else if (Array.isArray(req.body.where)) {
492
- req.body.where.unshift(scopeCondition);
493
- } else if (req.body.where.conditions && Array.isArray(req.body.where.conditions)) {
494
- const originalLogic = req.body.where.logic || 'AND';
495
- if (originalLogic === 'AND') {
496
- req.body.where.conditions.unshift(scopeCondition);
497
- } else {
498
- req.body.where = {
499
- logic: 'AND',
500
- conditions: [
501
- scopeCondition,
502
- { logic: originalLogic, conditions: req.body.where.conditions }
503
- ]
504
- };
505
- }
506
- }
507
- }
508
-
509
- // DELETE: prepend scope condition to where array
510
- if (endpoint === 'delete') {
511
- if (req.body.where) {
512
- if (Array.isArray(req.body.where)) {
513
- req.body.where.unshift(scopeCondition);
514
- }
515
- }
516
- }
517
-
518
- // CREATE / ADD: force scope column value
519
- if (endpoint === 'add' || endpoint === 'create') {
520
- req.body['${column}'] = scopeValue;
521
- }
522
-
523
- // UPDATE: force scope column value (ownership verified in /update handler)
524
- if (endpoint === 'update') {
525
- req.body['${column}'] = scopeValue;
526
- }
527
-
528
- // CREATE-COMPOSITE / UPDATE-COMPOSITE: force scope column inside master body.
529
- if (endpoint === 'create-composite' || endpoint === 'update-composite') {
530
- const masterBody = req.body['${rootKey}'] || req.body;
531
- masterBody['${column}'] = scopeValue;
532
- }
533
-
534
- next();
535
- });
536
- `;
537
- }
538
-
539
- /**
540
- * Membuat template untuk model MySQL
541
- * @param {string} moduleName - Nama module
542
- * @param {string} endpointName - Nama endpoint
543
- * @param {Object} payload - Data payload dari JSON
544
- * @returns {string} Template model untuk MySQL
545
- */
546
- function createMysqlModelTemplate(moduleName, endpointName, payload) {
547
- const validFields = payload.fieldName.map(field => `'${field}'`).join(', ');
548
- const className = toPascalCase(endpointName);
549
- const primaryKey = payload.primaryKey || 'id';
550
- const textColumn = detectTextColumn(payload.fieldName);
551
- const timestamp = new Date().toISOString();
552
- const auditColumnsSection = buildAuditColumnsSection(payload, ' ');
553
-
554
- // Build default scope SQL (generation time)
555
- const lookupScopeSQL = payload.defaultScope && payload.defaultScope.lookup
556
- ? buildDefaultScopeSQL(payload.defaultScope.lookup)
557
- : '';
558
- const readScopeSQL = payload.defaultScope && payload.defaultScope.read
559
- ? buildDefaultScopeSQL(payload.defaultScope.read)
560
- : '';
561
-
562
- // Generate dateTimeFields dari fieldValidation
563
- const dateTimeFields = {};
564
- if (payload.fieldValidation && Array.isArray(payload.fieldValidation)) {
565
- payload.fieldValidation.forEach(field => {
566
- if (['date', 'datetime', 'timestamp', 'time'].includes(field.type)) {
567
- dateTimeFields[field.name] = {
568
- type: field.type,
569
- format: (field.constraints && field.constraints.format) || 'yyyy-MM-dd'
570
- };
571
- }
572
- });
573
- }
574
-
575
- return `const BaseModel = require('@restforgejs/platform/src/models/base-model-mysql');
576
- const db = require('@restforgejs/platform/src/utils/db-mysql');
577
- const fs = require('fs');
578
- const path = require('path');
579
-
580
- /**
581
- * ${className} Model - MySQL Database
582
- * Generated: ${timestamp}
583
- *
584
- * Table: ${payload.tableName}
585
- * Primary Key: ${primaryKey}
586
- * Fields: ${payload.fieldName.length}
587
- * Database: MySQL
588
- */
589
- class ${className}Model extends BaseModel {
590
- /**
591
- * Constructor
592
- */
593
- constructor() {
594
- const validFields = [
595
- ${validFields}
596
- ];
597
-
598
- const datatablesWhere = ${payload.datatablesWhere ? JSON.stringify(payload.datatablesWhere) : "['kode', 'nama', 'all']"};
599
-
600
- super('${payload.tableName}', validFields);
601
-
602
- // base-model-mysql hanya menerima 2 parameter, simpan datatablesWhere manual
603
- this.datatablesWhere = datatablesWhere;
604
-
605
- // Primary key configuration
606
- this.primaryKey = '${primaryKey}';
607
-
608
- // Read/Write source configuration
609
- this.viewName = '${payload.viewName || payload.tableName}';
610
- this.readSource = '${payload.viewName || payload.tableName}';
611
- this.writeSource = '${payload.tableName}';
612
- ${auditColumnsSection}
613
- // Flag untuk self-documenting API (endpoint /info)
614
- this.hasViewQuery = ${!!payload.viewQuery};
615
- this.hasExportQuery = ${!!payload.exportQuery};
616
- ${Object.keys(dateTimeFields).length > 0 ? `
617
- // DateTime fields configuration dari fieldValidation
618
- this.dateTimeFields = ${JSON.stringify(dateTimeFields, null, 4).split('\n').join('\n ')};
619
- ` : ''}${payload.uploadConfig && payload.uploadConfig.fields ? `
620
- // File upload fields (JSON columns)
621
- this.fileFields = ${JSON.stringify(Object.keys(payload.uploadConfig.fields))};
622
- ` : ''}
623
- // Field validation configuration
624
- ${(() => {
625
- if (!payload.fieldValidation || !Array.isArray(payload.fieldValidation) || payload.fieldValidation.length === 0) {
626
- return ' this.validationConfig = {}; // No field validation config';
627
- }
628
- const configEntries = payload.fieldValidation.map(field => {
629
- const constraints = JSON.stringify(field.constraints || {}, null, 6).replace(/\n/g, '\n ');
630
- return ` '${field.name}': {
631
- type: '${field.type}',
632
- constraints: ${constraints}
633
- }`;
634
- }).join(',\n');
635
- return ` this.validationConfig = {
636
- ${configEntries}
637
- }`;
638
- })()}
639
-
640
- // Model metadata
641
- this.modelMetadata = {
642
- endpointName: '${endpointName}',
643
- moduleName: '${moduleName}',
644
- tableName: '${payload.tableName}',
645
- databaseType: 'mysql',
646
- primaryKey: '${primaryKey}',
647
- fieldCount: ${payload.fieldName.length},
648
- generated: '${timestamp}'
649
- };
650
-
651
- this.advancedQueryTemplates = this.loadAdvancedQueryTemplates();
652
- }
653
-
654
- /**
655
- * Load advanced query templates dari file
656
- */
657
- loadAdvancedQueryTemplates() {
658
- const templates = {};
659
-
660
- ${payload.advancedQueries ? Object.entries(payload.advancedQueries).map(([key, value]) => `
661
- try {
662
- if (typeof "${value}" === 'string' && "${value}".startsWith('file:')) {
663
- const relativePath = "${value}".replace('file:', '');
664
- const filePath = path.join(__dirname, relativePath);
665
-
666
- if (fs.existsSync(filePath)) {
667
- templates["${key}"] = fs.readFileSync(filePath, 'utf8');
668
- console.log(\`SQL Template ${key} loaded successfully from MySQL file\`);
669
- } else {
670
- console.error(\`SQL Template file ${key} not found: \${filePath}\`);
671
- templates["${key}"] = null;
672
- }
673
- } else {
674
- templates["${key}"] = "${value}";
675
- }
676
- } catch (error) {
677
- console.error(\`Error loading MySQL template ${key}:\`, error);
678
- templates["${key}"] = null;
679
- }`).join('') : '// No advanced queries defined'}
680
-
681
- return templates;
682
- }
683
-
684
- /**
685
- * Override getListQuery untuk MySQL syntax
686
- */
687
- getListQuery(options = {}) {
688
- let baseQuery = \`
689
- ${payload.datatablesQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
690
- \`.trim();
691
-
692
- // Convert PostgreSQL syntax to MySQL if needed
693
- baseQuery = this.convertToMysqlSQL(baseQuery);
694
-
695
- return baseQuery;
696
- }
697
-
698
- /**
699
- * Override getReadQuery untuk endpoint /read
700
- * Prioritas: viewName → viewQuery → tableName (SELECT * FROM readSource)
701
- */
702
- getReadQuery(options = {}) {
703
- if (this.viewName && this.viewName !== this.table) {
704
- return 'SELECT * FROM ' + this.viewName;
705
- }
706
- ${payload.viewQuery
707
- ? ` let baseQuery = \`
708
- ${payload.viewQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
709
- \`.trim();
710
- baseQuery = this.convertToMysqlSQL(baseQuery);
711
- return baseQuery;`
712
- : ` return 'SELECT * FROM ' + this.readSource;`
713
- }
714
- }
715
-
716
- /**
717
- * Convert PostgreSQL SQL syntax to MySQL
718
- */
719
- convertToMysqlSQL(sql) {
720
- // ILIKE → LIKE (MySQL case-insensitive by default with utf8mb4 collation)
721
- sql = sql.replace(/\\bILIKE\\b/gi, 'LIKE');
722
- // NOW() dan CURRENT_DATE sudah sama di MySQL
723
- return sql;
724
- }
725
-
726
- /**
727
- * Override getDatatables untuk MySQL dengan pagination yang tepat
728
- * Paritas fungsional dengan Oracle dan PostgreSQL getDatatables
729
- */
730
- async getDatatables(options) {
731
- try {
732
- // Check cache first
733
- const cachedResult = await this.getCachedDatatables(options);
734
- if (cachedResult) return cachedResult;
735
-
736
- const {
737
- searchValue = '',
738
- searchBy = 'all',
739
- perPage = 10,
740
- start = 0,
741
- sort_columns = [],
742
- filters = {},
743
- advancedFilters = []
744
- } = options;
745
-
746
- // Resolve sort columns dengan prioritas: sort_columns > order[0][column] > default
747
- let resolvedSortColumns = sort_columns;
748
-
749
- // Fallback: cek format DataTables bawaan (order[0][column] dan order[0][dir])
750
- if ((!resolvedSortColumns || resolvedSortColumns.length === 0) &&
751
- options['order[0][column]'] !== undefined && options['order[0][dir]'] !== undefined) {
752
- const columnIndex = parseInt(options['order[0][column]']);
753
- const direction = options['order[0][dir]'];
754
-
755
- if (columnIndex >= 0 && columnIndex < this.validFields.length) {
756
- resolvedSortColumns = [{ column: this.validFields[columnIndex], direction: direction.toUpperCase() }];
757
- }
758
- }
759
-
760
- const orderClause = this.buildSortColumnsClause(resolvedSortColumns);
761
-
762
- const baseQuery = this.getListQuery(options);
763
-
764
- // Build WHERE clause (parameterized)
765
- const searchResult = this.buildWhereClause(searchValue, searchBy);
766
- let whereClauseSql = searchResult.sql;
767
- let whereParams = [...searchResult.params];
768
-
769
- // Build filter clause
770
- const filterClause = this.buildObjectFilterClause(filters);
771
- if (filterClause) {
772
- if (whereClauseSql) {
773
- whereClauseSql = \`\${whereClauseSql} AND \${filterClause}\`;
774
- } else {
775
- whereClauseSql = \`WHERE \${filterClause}\`;
776
- }
777
- }
778
-
779
- // Support WHERE conditions dari request body
780
- if (options.where) {
781
- try {
782
- const complexResult = this.buildComplexWhereClause(options.where, whereParams);
783
- if (whereClauseSql) {
784
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
785
- } else {
786
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
787
- }
788
- whereParams = complexResult.params;
789
- } catch (e) {
790
- const error = new Error('Invalid where conditions: ' + e.message);
791
- error.statusCode = 400;
792
- throw error;
793
- }
794
- }
795
-
796
- // Advanced filters support
797
- if (advancedFilters && advancedFilters.length > 0) {
798
- const advResult = this.buildAdvancedFilterCondition(advancedFilters);
799
- if (advResult.sql) {
800
- if (whereClauseSql) {
801
- whereClauseSql = \`\${whereClauseSql} AND \${advResult.sql}\`;
802
- } else {
803
- whereClauseSql = \`WHERE \${advResult.sql}\`;
804
- }
805
- whereParams.push(...advResult.params);
806
- }
807
- }
808
-
809
- // Check if query needs subquery wrapping (CTE or JOIN)
810
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
811
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
812
- const needsSubquery = isCteQuery || hasJoin;
813
-
814
- // Count total records
815
- const countTotalQuery = needsSubquery ?
816
- \`SELECT COUNT(*) as total FROM (\${baseQuery}) as base_query\` :
817
- 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
818
- const countTotalResult = await db.executeQuery(countTotalQuery);
819
- const totalRecords = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
820
-
821
- // Count filtered records
822
- let filteredRecords = totalRecords;
823
- if (whereClauseSql) {
824
- const countFilteredQuery = needsSubquery ?
825
- \`SELECT COUNT(*) as total FROM (\${baseQuery}) as base_query \${whereClauseSql}\` :
826
- 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + whereClauseSql;
827
- const countFilteredResult = await db.executeQuery(countFilteredQuery, whereParams.length > 0 ? whereParams : undefined);
828
- filteredRecords = countFilteredResult && countFilteredResult[0] ? parseInt(countFilteredResult[0].total) : 0;
829
- }
830
-
831
- // MySQL pagination using LIMIT/OFFSET
832
- const query = needsSubquery ?
833
- \`SELECT * FROM (\${baseQuery}) as base_query \${whereClauseSql || ''} \${orderClause} LIMIT \${perPage} OFFSET \${start}\` :
834
- \`\${baseQuery} \${whereClauseSql || ''} \${orderClause} LIMIT \${perPage} OFFSET \${start}\`;
835
-
836
- console.log('Final Query:', query);
837
- console.log('Query Parameters:', whereParams.length > 0 ? whereParams : []);
838
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
839
-
840
- // Format data: MySQL returns lowercase keys natively
841
- const data = rawData ? rawData.map((row, index) => {
842
- const formatted = this.formatResponseData(row);
843
- return {
844
- ...formatted,
845
- rownumerator: start + index + 1
846
- };
847
- }) : [];
848
-
849
- const result = {
850
- draw: parseInt(options.draw || '1', 10),
851
- recordsTotal: totalRecords,
852
- recordsFiltered: filteredRecords,
853
- data: data
854
- };
855
-
856
- // Cache result
857
- await this.setCachedDatatables(options, result);
858
-
859
- return result;
860
- } catch (error) {
861
- console.error('Error in getDatatables:', error);
862
- throw error;
863
- }
864
- }
865
-
866
- /**
867
- * Build WHERE clause untuk search (parameterized query)
868
- * @returns {Object} { sql: string, params: array }
869
- */
870
- buildWhereClause(searchValue, searchBy) {
871
- if (!searchValue || searchValue === '') {
872
- return { sql: '', params: [] };
873
- }
874
-
875
- const params = [];
876
- const searchPattern = \`%\${searchValue}%\`;
877
-
878
- if (searchBy === 'all') {
879
- const searchableFields = this.datatablesWhere.filter(field => field !== 'all');
880
- if (searchableFields.length > 0) {
881
- const conditions = searchableFields.map(field => {
882
- params.push(searchPattern);
883
- return \`\${field} LIKE ?\`;
884
- });
885
- return { sql: \`WHERE (\${conditions.join(' OR ')})\`, params };
886
- }
887
- } else if (this.validFields.includes(searchBy)) {
888
- params.push(searchPattern);
889
- return { sql: \`WHERE \${searchBy} LIKE ?\`, params };
890
- }
891
-
892
- return { sql: '', params: [] };
893
- }
894
-
895
- /**
896
- * Build filter clause dari object filters
897
- * @param {Object} filters - Filter object {column: value}
898
- * @returns {string} Filter conditions SQL (tanpa WHERE prefix)
899
- */
900
- buildObjectFilterClause(filters) {
901
- if (!filters || typeof filters !== 'object' || Object.keys(filters).length === 0) {
902
- return '';
903
- }
904
-
905
- const conditions = [];
906
- for (const [column, value] of Object.entries(filters)) {
907
- if (!this.validFields.includes(column)) continue;
908
- if (value === null || value === undefined || value === '' || value === 'all' || value === '-') continue;
909
-
910
- const escapedValue = value.toString().replace(/'/g, "''");
911
- conditions.push(\`\${column} = '\${escapedValue}'\`);
912
- }
913
-
914
- return conditions.length > 0 ? conditions.join(' AND ') : '';
915
- }
916
-
917
- /**
918
- * Get list data dengan pagination untuk MySQL
919
- */
920
- async getList(options) {
921
- try {
922
- // Check cache first
923
- const cachedResult = await this.getCachedList(options);
924
- if (cachedResult) {
925
- const { page: p = null, perPage: pp = 10, searchValue: sv = '', sort_columns: sc = [], where: w = null } = options;
926
- const scInfo = sc && sc.length > 0 ? sc.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
927
- console.log(\`[Cache] HIT for list - page:\${p}, perPage:\${pp}, sort:\${scInfo}, search:\${sv || 'none'}\${w ? ', where:yes' : ''}\`);
928
- return cachedResult;
929
- }
930
-
931
- const {
932
- page = null,
933
- perPage = 10,
934
- searchValue = '',
935
- searchBy = 'all',
936
- sort_columns = [],
937
- where = null,
938
- select = null,
939
- limit = 1000
940
- } = options;
941
-
942
- const paginate = page !== null;
943
- const scInfo = sort_columns && sort_columns.length > 0 ? sort_columns.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
944
- const cacheInfo = \`page:\${page}, perPage:\${perPage}, sort:\${scInfo}, search:\${searchValue || 'none'}\${where ? ', where:yes' : ''}\`;
945
-
946
- console.log(\`[Cache] MISS for list - \${cacheInfo}\`);
947
-
948
- // 1. Mendapatkan query dasar
949
- let baseQuery;
950
- if (select && Array.isArray(select) && select.length > 0) {
951
- const selectedValidColumns = select.filter(col => this.validFields.includes(col));
952
- if (selectedValidColumns.length > 0) {
953
- baseQuery = 'SELECT ' + selectedValidColumns.map(col => '\`' + col + '\`').join(', ') + ' FROM ' + this.getTableSource('read') + ' a';
954
- } else {
955
- baseQuery = 'SELECT * FROM ' + this.getTableSource('read') + ' a';
956
- }
957
- } else {
958
- baseQuery = this.getReadQuery(options);
959
- }
960
-
961
- // Deteksi apakah query mengandung JOIN atau CTE (perlu subquery wrapping)
962
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
963
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
964
- const needsSubquery = isCteQuery || hasJoin;
965
-
966
- const searchResult = this.buildWhereClause(searchValue, searchBy);
967
- let whereClauseSql = searchResult.sql;
968
- let whereParams = [...searchResult.params];
969
- const orderClause = this.buildSortColumnsClause(sort_columns);
970
- ${readScopeSQL ? `
971
- // Default scope filter untuk read
972
- if (whereClauseSql) {
973
- whereClauseSql = \`WHERE ${readScopeSQL} AND \` + whereClauseSql.replace(/^WHERE\\s+/i, '');
974
- } else {
975
- whereClauseSql = 'WHERE ${readScopeSQL}';
976
- }
977
- ` : ''}
978
- // Support WHERE conditions dari request body
979
- if (where) {
980
- try {
981
- const complexResult = this.buildComplexWhereClause(where, whereParams);
982
- if (whereClauseSql) {
983
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
984
- } else {
985
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
986
- }
987
- whereParams = complexResult.params;
988
- } catch (e) {
989
- const error = new Error('Invalid where conditions: ' + e.message);
990
- error.statusCode = 400;
991
- throw error;
992
- }
993
- }
994
-
995
- // Count total unfiltered records
996
- const countTotalQuery = needsSubquery
997
- ? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query'
998
- : 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
999
- const countTotalResult = await db.executeQuery(countTotalQuery);
1000
- const totalUnfiltered = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
1001
-
1002
- // Count filtered records
1003
- let totalRecords = totalUnfiltered;
1004
- if (whereClauseSql) {
1005
- const countQuery = needsSubquery
1006
- ? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '')
1007
- : 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + (whereClauseSql || '');
1008
- const countResult = await db.executeQuery(countQuery, whereParams.length > 0 ? whereParams : undefined);
1009
- totalRecords = countResult && countResult[0] ? parseInt(countResult[0].total) : 0;
1010
- }
1011
-
1012
- // Build query berdasarkan mode paginasi (subquery wrapping untuk JOIN/CTE)
1013
- let query;
1014
- if (paginate) {
1015
- const offset = (page - 1) * perPage;
1016
- query = needsSubquery
1017
- ? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + perPage + ' OFFSET ' + offset
1018
- : baseQuery + ' ' + (whereClauseSql || '') + orderClause + \` LIMIT \${perPage} OFFSET \${offset}\`;
1019
- } else {
1020
- query = needsSubquery
1021
- ? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + limit
1022
- : baseQuery + ' ' + (whereClauseSql || '') + orderClause + \` LIMIT \${limit}\`;
1023
- }
1024
-
1025
- console.log('List SQL Query:', query);
1026
- console.log('List Query Parameters:', whereParams);
1027
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
1028
-
1029
- const data = rawData ? rawData.map((row) => {
1030
- return this.formatResponseData(row);
1031
- }) : [];
1032
-
1033
- const result = {
1034
- success: true,
1035
- data: data
1036
- };
1037
-
1038
- if (paginate) {
1039
- const totalPages = Math.ceil(totalRecords / perPage);
1040
- result.pagination = {
1041
- current_page: page,
1042
- per_page: perPage,
1043
- total_records: totalRecords,
1044
- total_pages: totalPages,
1045
- has_next: page < totalPages,
1046
- has_previous: page > 1
1047
- };
1048
- }
1049
-
1050
- // Cache result
1051
- await this.setCachedList(options, result);
1052
- console.log(\`[Cache] SET for list - \${cacheInfo}\`);
1053
-
1054
- return result;
1055
- } catch (error) {
1056
- console.error('Error in getList:', error);
1057
- throw error;
1058
- }
1059
- }
1060
-
1061
- /**
1062
- * Override getLookupData untuk MySQL (dynamic search)
1063
- */
1064
- async getLookupData(search) {
1065
- try {
1066
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} WHERE ${textColumn} LIKE ?${lookupScopeSQL ? ` AND ${lookupScopeSQL}` : ''} ORDER BY ${textColumn} LIMIT 100\`;
1067
- const params = [\`%\${search || ''}%\`];
1068
- const data = await db.executeQuery(query, params);
1069
-
1070
- const result = data.map(item => ({
1071
- id: item.${primaryKey},
1072
- text: item.${textColumn}
1073
- }));
1074
-
1075
- return result;
1076
- } catch (error) {
1077
- console.error('Error in MySQL getLookupData:', error);
1078
- throw error;
1079
- }
1080
- }
1081
-
1082
- /**
1083
- * Dynamic lookup dengan extra filters untuk MySQL
1084
- */
1085
- async getLookupDataDynamic(search, extraFilters = {}) {
1086
- try {
1087
- let params = [];
1088
- let whereConditions = [];
1089
- ${lookupScopeSQL ? `\n // Default scope filter\n whereConditions.push('${lookupScopeSQL}');\n` : ''}
1090
- if (search) {
1091
- whereConditions.push(\`${textColumn} LIKE ?\`);
1092
- params.push(\`%\${search}%\`);
1093
- }
1094
-
1095
- // Add extra filters
1096
- if (extraFilters && Object.keys(extraFilters).length > 0) {
1097
- for (const [key, value] of Object.entries(extraFilters)) {
1098
- if (this.validFields.includes(key) && value !== null && value !== undefined) {
1099
- whereConditions.push(\`\${key} = ?\`);
1100
- params.push(value);
1101
- }
1102
- }
1103
- }
1104
-
1105
- const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
1106
-
1107
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} \${whereClause} ORDER BY ${textColumn} LIMIT 100\`;
1108
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
1109
-
1110
- return data.map(item => ({
1111
- id: item.${primaryKey},
1112
- text: item.${textColumn}
1113
- }));
1114
- } catch (error) {
1115
- console.error('Error in MySQL getLookupDataDynamic:', error);
1116
- throw error;
1117
- }
1118
- }
1119
-
1120
- /**
1121
- * Override getStaticLookupData untuk MySQL
1122
- */
1123
- async getStaticLookupData(selectedTag) {
1124
- try {
1125
- // Check cache first - cache tanpa selectedTag karena data sama
1126
- const cacheOptions = { type: 'static' };
1127
- const cachedResult = await this.getCachedLookup(cacheOptions, 'static');
1128
- if (cachedResult) {
1129
- // Apply selectedTag to cached result
1130
- return cachedResult.map(item => {
1131
- if (item.id === selectedTag) {
1132
- return { ...item, selected: 'true' };
1133
- }
1134
- return { id: item.id, text: item.text };
1135
- });
1136
- }
1137
-
1138
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')}${lookupScopeSQL ? ` WHERE ${lookupScopeSQL}` : ''} ORDER BY ${textColumn} LIMIT 1000\`;
1139
- const data = await db.executeQuery(query);
1140
-
1141
- // Cache result tanpa selected flag
1142
- const cacheData = data.map(item => ({
1143
- id: item.${primaryKey},
1144
- text: item.${textColumn}
1145
- }));
1146
- await this.setCachedLookup(cacheOptions, cacheData, 'static');
1147
-
1148
- // Return dengan selected flag
1149
- return data.map(item => {
1150
- const row = {
1151
- id: item.${primaryKey},
1152
- text: item.${textColumn}
1153
- };
1154
- if (item.${primaryKey} === selectedTag) {
1155
- row.selected = 'true';
1156
- }
1157
- return row;
1158
- });
1159
- } catch (error) {
1160
- console.error('Error in MySQL getStaticLookupData:', error);
1161
- throw error;
1162
- }
1163
- }
1164
-
1165
- /**
1166
- * Lookup dengan advanced filter support untuk MySQL
1167
- */
1168
- async getLookupDataWithFilter(options) {
1169
- try {
1170
- // Check cache first
1171
- const cacheOptions = { ...options, type: 'filter' };
1172
- const cachedResult = await this.getCachedLookup(cacheOptions, 'filter');
1173
- if (cachedResult) return cachedResult;
1174
-
1175
- const selectColumns = options.select || ['${primaryKey}', '${textColumn}'];
1176
- let params = [];
1177
-
1178
- // Validasi text fields
1179
- const validTextFields = this.validFields.filter(field =>
1180
- field.includes('name') || field.includes('nama') ||
1181
- field.includes('code') || field.includes('kode') ||
1182
- field.includes('text') || field.includes('title')
1183
- );
1184
-
1185
- let selectClause = '${primaryKey}';
1186
- let textField = '${textColumn}';
1187
- let aliasField = null;
1188
-
1189
- // Proses setiap column dalam select
1190
- for (const column of selectColumns) {
1191
- if (column.toLowerCase() === '${primaryKey}') {
1192
- continue; // primary key sudah ada
1193
- }
1194
-
1195
- // Check jika ada SQL expression dengan alias (menggunakan AS)
1196
- const aliasRegex = new RegExp('(.+)\\\\s+as\\\\s+(\\\\w+)$', 'i');
1197
- const aliasMatch = column.match(aliasRegex);
1198
- if (aliasMatch) {
1199
- const expression = aliasMatch[1].trim();
1200
- const alias = aliasMatch[2].trim();
1201
- selectClause += \`, \${expression} AS \${alias}\`;
1202
- textField = alias;
1203
- aliasField = alias;
1204
- break;
1205
- }
1206
-
1207
- // Check jika simple field name
1208
- if (this.validFields.includes(column) || validTextFields.includes(column)) {
1209
- selectClause += \`, \${column}\`;
1210
- textField = column;
1211
- break;
1212
- }
1213
-
1214
- // Computed column
1215
- selectClause += \`, \${column}\`;
1216
- textField = column;
1217
- }
1218
-
1219
- let query = \`SELECT \${selectClause} FROM \${this.getTableSource('read')} ${lookupScopeSQL ? `WHERE ${lookupScopeSQL} ` : ''}\`;
1220
-
1221
- // Build WHERE clause jika ada
1222
- if ((options.where && Array.isArray(options.where) && options.where.length > 0) ||
1223
- (options.where && options.where.conditions && Array.isArray(options.where.conditions) && options.where.conditions.length > 0)) {
1224
- try {
1225
- const whereResult = this.buildComplexWhereClause(options.where, params);
1226
- query += \`${lookupScopeSQL ? 'AND (' : 'WHERE '}\${whereResult.sql}${lookupScopeSQL ? ')' : ''} \`;
1227
- params = whereResult.params;
1228
- } catch (e) {
1229
- const error = new Error('Invalid where conditions: ' + e.message);
1230
- error.statusCode = 400;
1231
- throw error;
1232
- }
1233
- }
1234
-
1235
- // Handle sort_columns
1236
- if (options.sort_columns && Array.isArray(options.sort_columns) && options.sort_columns.length > 0) {
1237
- const orderParts = options.sort_columns.map(item => {
1238
- const column = item.column;
1239
- const direction = (item.direction || 'ASC').toUpperCase();
1240
- if (!column) return null;
1241
- if (!this.validFields.includes(column)) return null;
1242
- if (direction !== 'ASC' && direction !== 'DESC') return null;
1243
- return \`\${column} \${direction}\`;
1244
- }).filter(Boolean);
1245
-
1246
- if (orderParts.length === 0) {
1247
- const error = new Error('No valid sort columns provided');
1248
- error.statusCode = 400;
1249
- throw error;
1250
- }
1251
- query += \`ORDER BY \${orderParts.join(', ')}\`;
1252
- } else {
1253
- query += \`ORDER BY \${aliasField || textField}\`;
1254
- }
1255
-
1256
- console.log('MySQL Lookup Filter Query:', query);
1257
- console.log('Parameters:', params);
1258
-
1259
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
1260
-
1261
- const result = data.map(item => ({
1262
- id: item.${primaryKey},
1263
- text: item[aliasField || textField] || item.${textColumn} || ''
1264
- }));
1265
-
1266
- // Cache the result
1267
- await this.setCachedLookup(cacheOptions, result, 'filter');
1268
-
1269
- return result;
1270
- } catch (error) {
1271
- console.error('Error in getLookupDataWithFilter:', error);
1272
- throw error;
1273
- }
1274
- }
1275
-
1276
- /**
1277
- * Build advanced filter conditions untuk MySQL
1278
- * @param {Array} filters - Array of {column, type, value, value2}
1279
- * @returns {Object} {sql, params}
1280
- */
1281
- buildAdvancedFilterCondition(filters) {
1282
- if (!filters || !Array.isArray(filters) || filters.length === 0) {
1283
- return { sql: '', params: [] };
1284
- }
1285
-
1286
- const conditions = [];
1287
- const params = [];
1288
-
1289
- for (const filter of filters) {
1290
- const { column, type, value, value2 } = filter;
1291
-
1292
- if (!column || !this.validFields.includes(column)) continue;
1293
-
1294
- switch (type) {
1295
- case 'equals':
1296
- conditions.push(\`\${column} = ?\`);
1297
- params.push(value);
1298
- break;
1299
- case 'not_equals':
1300
- conditions.push(\`\${column} <> ?\`);
1301
- params.push(value);
1302
- break;
1303
- case 'contains':
1304
- case 'like':
1305
- conditions.push(\`\${column} LIKE ?\`);
1306
- params.push(\`%\${value}%\`);
1307
- break;
1308
- case 'not_contains':
1309
- case 'not_like':
1310
- conditions.push(\`\${column} NOT LIKE ?\`);
1311
- params.push(\`%\${value}%\`);
1312
- break;
1313
- case 'starts_with':
1314
- conditions.push(\`\${column} LIKE ?\`);
1315
- params.push(\`\${value}%\`);
1316
- break;
1317
- case 'ends_with':
1318
- conditions.push(\`\${column} LIKE ?\`);
1319
- params.push(\`%\${value}\`);
1320
- break;
1321
- case 'greater_than':
1322
- conditions.push(\`\${column} > ?\`);
1323
- params.push(value);
1324
- break;
1325
- case 'less_than':
1326
- conditions.push(\`\${column} < ?\`);
1327
- params.push(value);
1328
- break;
1329
- case 'greater_equal':
1330
- conditions.push(\`\${column} >= ?\`);
1331
- params.push(value);
1332
- break;
1333
- case 'less_equal':
1334
- conditions.push(\`\${column} <= ?\`);
1335
- params.push(value);
1336
- break;
1337
- case 'between':
1338
- conditions.push(\`\${column} BETWEEN ? AND ?\`);
1339
- params.push(value, value2);
1340
- break;
1341
- case 'in':
1342
- if (Array.isArray(value)) {
1343
- const inPlaceholders = value.map(() => '?').join(', ');
1344
- conditions.push(\`\${column} IN (\${inPlaceholders})\`);
1345
- params.push(...value);
1346
- }
1347
- break;
1348
- case 'not_in':
1349
- if (Array.isArray(value)) {
1350
- const notInPlaceholders = value.map(() => '?').join(', ');
1351
- conditions.push(\`\${column} NOT IN (\${notInPlaceholders})\`);
1352
- params.push(...value);
1353
- }
1354
- break;
1355
- case 'is_null':
1356
- conditions.push(\`\${column} IS NULL\`);
1357
- break;
1358
- case 'is_not_null':
1359
- conditions.push(\`\${column} IS NOT NULL\`);
1360
- break;
1361
- case 'date_equals':
1362
- conditions.push(\`DATE(\${column}) = ?\`);
1363
- params.push(value);
1364
- break;
1365
- case 'date_between':
1366
- conditions.push(\`DATE(\${column}) BETWEEN ? AND ?\`);
1367
- params.push(value, value2);
1368
- break;
1369
- case 'date_after':
1370
- conditions.push(\`DATE(\${column}) > ?\`);
1371
- params.push(value);
1372
- break;
1373
- case 'date_before':
1374
- conditions.push(\`DATE(\${column}) < ?\`);
1375
- params.push(value);
1376
- break;
1377
- default:
1378
- break;
1379
- }
1380
- }
1381
-
1382
- if (conditions.length === 0) {
1383
- return { sql: '', params: [] };
1384
- }
1385
-
1386
- return { sql: conditions.join(' AND '), params };
1387
- }
1388
-
1389
- /**
1390
- * Escape value untuk MySQL SQL (sanitization)
1391
- */
1392
- escapeValue(value) {
1393
- if (value === null || value === undefined) return null;
1394
- if (typeof value === 'number') return value;
1395
- return String(value).replace(/'/g, "''");
1396
- }
1397
-
1398
- /**
1399
- * Validasi data sebelum insert/update
1400
- */
1401
- async validateData(data, operation = 'insert') {
1402
- const result = {
1403
- isValid: true,
1404
- errors: [],
1405
- warnings: [],
1406
- sanitizedData: {}
1407
- };
1408
-
1409
- try {
1410
- const hasFieldValidation = this.validationConfig && Object.keys(this.validationConfig).length > 0;
1411
-
1412
- if (hasFieldValidation) {
1413
- // Loop semua field yang ada di validationConfig
1414
- for (const fieldName in this.validationConfig) {
1415
- let value = data[fieldName];
1416
- const config = this.validationConfig[fieldName];
1417
- const constraints = config.constraints || {};
1418
-
1419
- // Auto-generate value jika autoGenerate dan nilai kosong.
1420
- // String dan uuid diperlakukan sama: UUID v7 via uuid package
1421
- // (konsisten lintas dialect; cocok dengan konvensi payload category.json
1422
- // yang memakai type: "string" dengan constraint autoGenerate + primaryKey).
1423
- if (operation === 'insert' && constraints.autoGenerate && (!value || value === '')) {
1424
- if (config.type === 'uuid' || config.type === 'string') {
1425
- value = require('uuid').v7();
1426
- data[fieldName] = value;
1427
- }
1428
- }
1429
-
1430
- // Skip validation jika value kosong dan tidak required
1431
- if (value === undefined || value === null || value === '') {
1432
- if (constraints.required) {
1433
- // Skip: autoGenerate atau primaryKey di insert
1434
- if (operation === 'insert' && (constraints.autoGenerate || constraints.primaryKey)) {
1435
- // OK — akan di-generate otomatis
1436
- }
1437
- // Skip: update partial — field tidak dikirim berarti tidak diubah
1438
- else if (operation === 'update' && value === undefined) {
1439
- // OK — field tidak sedang di-update
1440
- }
1441
- else {
1442
- const message = constraints.requiredMessage || \`Field '\${fieldName}' is required\`;
1443
- result.errors.push(message);
1444
- result.isValid = false;
1445
- }
1446
- }
1447
- continue;
1448
- }
1449
-
1450
- // String field: hash constraint support
1451
- if (config.type === 'string' && typeof value === 'string') {
1452
- let sanitized = value;
1453
- const fieldErrors = [];
1454
- const isHashField = constraints.hash === 'bcrypt';
1455
-
1456
- // Trim
1457
- if (constraints.trim) {
1458
- sanitized = sanitized.trim();
1459
- }
1460
-
1461
- // Case transformation (skip jika hash field)
1462
- if (!isHashField) {
1463
- if (constraints.lowercase) {
1464
- sanitized = sanitized.toLowerCase();
1465
- } else if (constraints.uppercase) {
1466
- sanitized = sanitized.toUpperCase();
1467
- }
1468
- }
1469
-
1470
- // Length validation (validasi plaintext sebelum hash)
1471
- if (constraints.minLength && sanitized.length < constraints.minLength) {
1472
- fieldErrors.push(constraints.minLengthMessage || \`Field '\${fieldName}' must be at least \${constraints.minLength} characters\`);
1473
- }
1474
- if (constraints.maxLength && !isHashField && sanitized.length > constraints.maxLength) {
1475
- fieldErrors.push(constraints.maxLengthMessage || \`Field '\${fieldName}' must not exceed \${constraints.maxLength} characters\`);
1476
- }
1477
-
1478
- // Pattern validation
1479
- if (constraints.pattern) {
1480
- const regex = new RegExp(constraints.pattern);
1481
- if (!regex.test(sanitized)) {
1482
- fieldErrors.push(constraints.patternMessage || \`Field '\${fieldName}' does not match required pattern\`);
1483
- }
1484
- }
1485
-
1486
- // Format validation
1487
- if (constraints.format === 'email' && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(sanitized)) {
1488
- fieldErrors.push(constraints.formatMessage || \`Field '\${fieldName}' has invalid email format\`);
1489
- }
1490
-
1491
- if (fieldErrors.length > 0) {
1492
- result.isValid = false;
1493
- result.errors.push(...fieldErrors);
1494
- }
1495
-
1496
- // Hash transformation (setelah semua validation pass)
1497
- if (isHashField && fieldErrors.length === 0) {
1498
- const bcrypt = require('bcrypt');
1499
- const cost = constraints.hashCost || 10;
1500
- sanitized = await bcrypt.hash(sanitized, cost);
1501
- }
1502
-
1503
- result.sanitizedData[fieldName] = sanitized;
1504
- } else {
1505
- // Non-string field: basic sanitization
1506
- result.sanitizedData[fieldName] = value;
1507
- }
1508
- }
1509
-
1510
- // Validate field yang tidak ada di validationConfig (backward compatibility)
1511
- for (const field of this.validFields) {
1512
- if (!this.validationConfig[field] && data[field] !== undefined && data[field] !== null) {
1513
- if (typeof data[field] === 'string') {
1514
- result.sanitizedData[field] = data[field].trim().replace(/\\0/g, '').substring(0, 4000);
1515
- } else {
1516
- result.sanitizedData[field] = data[field];
1517
- }
1518
- }
1519
- }
1520
- } else {
1521
- // Fallback: Tidak ada fieldValidation - gunakan generic sanitization
1522
- for (const field of this.validFields) {
1523
- const value = data[field];
1524
- if (value !== undefined && value !== null) {
1525
- if (typeof value === 'string') {
1526
- result.sanitizedData[field] = value.trim().replace(/\\0/g, '').substring(0, 4000);
1527
- } else {
1528
- result.sanitizedData[field] = value;
1529
- }
1530
- }
1531
- }
1532
- }
1533
- } catch (error) {
1534
- result.errors.push(\`Validation error: \${error.message}\`);
1535
- result.isValid = false;
1536
- }
1537
-
1538
- return result;
1539
- }
1540
-
1541
- /**
1542
- * Get field mapping information
1543
- */
1544
- getFieldMapping() {
1545
- return {
1546
- allFields: this.validFields,
1547
- primaryKey: this.primaryKey,
1548
- textFields: this.validFields.filter(f => f.includes('name') || f.includes('nama') || f.includes('description')),
1549
- dateFields: this.validFields.filter(f => f.includes('date') || f.includes('time')),
1550
- numericFields: this.validFields.filter(f => f.includes('amount') || f.includes('price') || f.includes('count'))
1551
- };
1552
- }
1553
-
1554
- /**
1555
- * Get MySQL connection info untuk health check
1556
- */
1557
- async getConnectionInfo() {
1558
- try {
1559
- const result = await db.executeQuery('SELECT 1 as test_con');
1560
- if (result && result.length > 0) {
1561
- return { connected: true, database: 'MySQL', retrievedAt: new Date().toISOString() };
1562
- }
1563
- return null;
1564
- } catch (error) {
1565
- return { connected: false, error: error.message, checkedAt: new Date().toISOString() };
1566
- }
1567
- }
1568
- ${(() => {
1569
- const isMasterDetail = payload.masterDetail && payload.masterDetail.enabled;
1570
- const actions = payload.action || {};
1571
- if (!isMasterDetail || (!actions.createComposite && !actions.updateComposite && !actions.readComposite)) return '';
1572
-
1573
- const detailTable = payload.masterDetail.detailTable;
1574
- const detailTableName = detailTable.split('.').pop();
1575
- const foreignKey = payload.masterDetail.foreignKey;
1576
- const detailPrimaryKey = payload.masterDetail.detailConfig?.primaryKey || `${detailTableName}_id`;
1577
- const headerCalculations = payload.masterDetail.headerCalculations || null;
1578
- const autoCalculateFields = payload.masterDetail.detailConfig?.autoCalculateFields || {};
1579
- const detailQuery = payload.masterDetail.detailConfig?.detailQuery || null;
1580
-
1581
- // Separate autoCalculateFields into "generated" (skip from SQL) vs "calculated" (compute & include in SQL)
1582
- const generatedFieldNames = [];
1583
- const calculatedFieldsConfig = [];
1584
- for (const [fieldName, config] of Object.entries(autoCalculateFields)) {
1585
- if (config.type === 'generated') {
1586
- generatedFieldNames.push(fieldName);
1587
- } else {
1588
- // default → "calculated"
1589
- const parts = (config.formula || '').split('*').map(s => s.trim());
1590
- calculatedFieldsConfig.push({ fieldName, qtyField: parts[0], priceField: parts[1] });
1591
- }
1592
- }
1593
-
1594
- // Extract qty and price field names for header recalculation
1595
- const amountFormula = autoCalculateFields.total_amount?.formula || '';
1596
- const amountFormulaParts = amountFormula.split('*').map(s => s.trim());
1597
- const headerCalcQtyField = headerCalculations?.total_qty?.source?.replace('items.', '') || '';
1598
- const amountQtyField = amountFormulaParts[0] || headerCalcQtyField;
1599
- const amountPriceField = amountFormulaParts[1] || 'unit_price';
1600
-
1601
- // Build calculation code block for "calculated" fields (runtime code)
1602
- const calcBlockCode = calculatedFieldsConfig.map(cf => {
1603
- return ` // Auto-calculate ${cf.fieldName} = ${cf.qtyField} * ${cf.priceField}\n item.${cf.fieldName} = (Number(item.${cf.qtyField}) || 0) * (Number(item.${cf.priceField}) || 0);`;
1604
- }).join('\n');
1605
-
1606
- let code = '';
1607
-
1608
- // createComposite
1609
- if (actions.createComposite) {
1610
- code += `
1611
- /**
1612
- * Composite create - Create header with detail items in a single transaction (MySQL)
1613
- */
1614
- async createComposite(data, eventContext = null) {
1615
- const connection = await db.getConnection();
1616
- try {
1617
- await connection.beginTransaction();
1618
-
1619
- const detailKey = '${detailTableName}';
1620
- const headerData = { ...data };
1621
- delete headerData[detailKey];
1622
-
1623
- // --- Hook: onBeforeCompositeInsert ---
1624
- if (eventContext && eventContext.componentEngine) {
1625
- var _ce = eventContext.componentEngine;
1626
- var _CB = eventContext.ContextBuilder;
1627
- var _detailItems = data[detailKey] || [];
1628
- var _beforeCtx = _CB.buildCompositeInsertBeforeContext(headerData, _detailItems, {
1629
- tableName: '${payload.tableName}',
1630
- detailTable: '${detailTable}',
1631
- foreignKey: '${foreignKey}',
1632
- ...(eventContext.additionalContext || {})
1633
- });
1634
- var _beforeResult = await _ce.executeOnBeforeComposite('insert', _beforeCtx);
1635
- if (!_beforeResult.success) {
1636
- await connection.rollback();
1637
- throw new Error('onBeforeCompositeInsert failed: ' + _beforeResult.error);
1638
- }
1639
- }
1640
-
1641
- // Build header INSERT
1642
- const headerFields = [];
1643
- const headerValues = [];
1644
- const headerPlaceholders = [];
1645
- for (const [key, value] of Object.entries(headerData)) {
1646
- if (value !== undefined && value !== null) {
1647
- headerFields.push(key);
1648
- headerValues.push(value);
1649
- headerPlaceholders.push('?');
1650
- }
1651
- }
1652
-
1653
- // Inject audit columns (created_at, created_by, updated_at, updated_by) via helper
1654
- this._appendCreateAuditColumns(headerFields, headerValues, headerPlaceholders, headerData, eventContext);
1655
-
1656
- const insertSql = 'INSERT INTO ' + this.writeSource + ' (' + headerFields.join(', ') + ') VALUES (' + headerPlaceholders.join(', ') + ')';
1657
- console.log('Executing header INSERT:', { query: insertSql, values: headerValues });
1658
- await connection.execute(insertSql, headerValues);
1659
-
1660
- // SELECT back inserted header
1661
- const selectSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + this.primaryKey + ' = ?';
1662
- const [headerRows] = await connection.execute(selectSql, [headerData[this.primaryKey]]);
1663
- const insertedHeader = headerRows[0];
1664
- const masterPkValue = headerData[this.primaryKey];
1665
-
1666
- console.log('Header inserted successfully: ' + this.primaryKey + '=' + masterPkValue);
1667
-
1668
- // Insert detail items
1669
- const insertedItems = [];
1670
- const detailTableFull = '${detailTable}';
1671
- const fk = '${foreignKey}';
1672
- const detailPk = '${detailPrimaryKey}';
1673
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1674
-
1675
- for (const item of data[detailKey] || []) {
1676
- item[fk] = masterPkValue;
1677
-
1678
- // Auto-generate UUID untuk detail PK bila client tidak menyupply.
1679
- // MySQL tidak mendukung RETURNING, sehingga generator perlu mengetahui
1680
- // nilai PK di muka agar SELECT-back dapat me-retrieve row. Cocok untuk
1681
- // kolom VARCHAR DEFAULT (UUID()); untuk AUTO_INCREMENT integer, client
1682
- // harus mengirim PK atau tidak menggunakan endpoint composite.
1683
- if (item[detailPk] === undefined || item[detailPk] === null) {
1684
- item[detailPk] = require('uuid').v7();
1685
- }
1686
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1687
- const detailFields = [];
1688
- const detailValues = [];
1689
- const detailPlaceholders = [];
1690
-
1691
- for (const [key, value] of Object.entries(item)) {
1692
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1693
- if (value !== undefined && value !== null) {
1694
- detailFields.push(key);
1695
- detailValues.push(value);
1696
- detailPlaceholders.push('?');
1697
- }
1698
- }
1699
-
1700
- // Inject audit columns ke detail INSERT via helper
1701
- this._appendCreateAuditColumns(detailFields, detailValues, detailPlaceholders, item, eventContext);
1702
-
1703
- const detailInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + detailFields.join(', ') + ') VALUES (' + detailPlaceholders.join(', ') + ')';
1704
- console.log('Executing detail INSERT:', { query: detailInsertSql, values: detailValues });
1705
- await connection.execute(detailInsertSql, detailValues);
1706
-
1707
- // SELECT back inserted detail
1708
- const detailSelectSql = 'SELECT * FROM ' + detailTableFull + ' WHERE ' + detailPk + ' = ?';
1709
- const [detailRows] = await connection.execute(detailSelectSql, [item[detailPk]]);
1710
- if (detailRows[0]) {
1711
- insertedItems.push(detailRows[0]);
1712
- }
1713
- }
1714
-
1715
- console.log('Inserted ' + insertedItems.length + ' detail item(s)');
1716
-
1717
- // --- Hook: onAfterCompositeInsert ---
1718
- if (eventContext && eventContext.componentEngine) {
1719
- var _ce2 = eventContext.componentEngine;
1720
- var _CB2 = eventContext.ContextBuilder;
1721
- var _afterCtx = _CB2.buildCompositeInsertAfterContext(headerData, insertedHeader, insertedItems, {
1722
- tableName: '${payload.tableName}',
1723
- detailTable: '${detailTable}',
1724
- foreignKey: '${foreignKey}',
1725
- primaryKey: this.primaryKey,
1726
- ...(eventContext.additionalContext || {})
1727
- });
1728
- var _afterResult = await _ce2.executeOnAfterComposite('insert', _afterCtx);
1729
- if (!_afterResult.success) {
1730
- await connection.rollback();
1731
- throw new Error('onAfterCompositeInsert failed: ' + _afterResult.error);
1732
- }
1733
- }
1734
-
1735
- await connection.commit();
1736
- console.log('Transaction committed successfully');
1737
-
1738
- // Invalidate cache setelah write operation berhasil
1739
- await this.invalidateCache();
1740
-
1741
- return {
1742
- ...insertedHeader,
1743
- [detailKey]: insertedItems
1744
- };
1745
- } catch (error) {
1746
- try { await connection.rollback(); } catch (rbErr) { console.error('Rollback failed:', rbErr.message); }
1747
- console.error('Error in createComposite:', error);
1748
- throw error;
1749
- } finally {
1750
- try { connection.release(); } catch (relErr) { console.error('Connection release failed:', relErr.message); }
1751
- }
1752
- }
1753
- `;
1754
- }
1755
-
1756
- // updateComposite
1757
- if (actions.updateComposite) {
1758
- code += `
1759
- /**
1760
- * Composite update - Update header with granular detail operations (MySQL)
1761
- */
1762
- async updateComposite(data, eventContext = null) {
1763
- const connection = await db.getConnection();
1764
- try {
1765
- await connection.beginTransaction();
1766
-
1767
- const primaryKeyValue = data[this.primaryKey];
1768
- if (!primaryKeyValue) {
1769
- throw new Error('Primary key ' + this.primaryKey + ' is required for update');
1770
- }
1771
-
1772
- // Check if record exists (also serves as prefetch oldData for hooks)
1773
- const checkSql = 'SELECT * FROM ' + this.writeSource + ' WHERE ' + this.primaryKey + ' = ?';
1774
- const [checkRows] = await connection.execute(checkSql, [primaryKeyValue]);
1775
- if (!checkRows || checkRows.length === 0) {
1776
- throw new Error('Record not found');
1777
- }
1778
-
1779
- const oldData = checkRows[0];
1780
-
1781
- // Extract header data
1782
- const headerData = { ...data };
1783
- const detailKey = '${detailTableName}';
1784
- delete headerData[detailKey];
1785
- delete headerData[this.primaryKey];
1786
-
1787
- // --- Hook: onBeforeCompositeUpdate ---
1788
- if (eventContext && eventContext.componentEngine) {
1789
- var _ce = eventContext.componentEngine;
1790
- var _CB = eventContext.ContextBuilder;
1791
- var _detailOps = data[detailKey] || {};
1792
- var _beforeCtx = _CB.buildCompositeUpdateBeforeContext(headerData, oldData, {
1793
- insert: _detailOps.insert || [],
1794
- update: _detailOps.update || [],
1795
- delete: _detailOps.delete || []
1796
- }, {
1797
- tableName: '${payload.tableName}',
1798
- detailTable: '${detailTable}',
1799
- foreignKey: '${foreignKey}',
1800
- primaryKey: this.primaryKey,
1801
- ...(eventContext.additionalContext || {})
1802
- });
1803
- var _beforeResult = await _ce.executeOnBeforeComposite('update', _beforeCtx);
1804
- if (!_beforeResult.success) {
1805
- await connection.rollback();
1806
- throw new Error('onBeforeCompositeUpdate failed: ' + _beforeResult.error);
1807
- }
1808
- }
1809
-
1810
- // Build header UPDATE
1811
- const headerFields = [];
1812
- const headerValues = [];
1813
- for (const [key, value] of Object.entries(headerData)) {
1814
- if (value !== undefined && value !== null) {
1815
- headerFields.push(key + ' = ?');
1816
- headerValues.push(value);
1817
- }
1818
- }
1819
-
1820
- // Inject audit columns (updated_at, updated_by) via base-model helper.
1821
- // Helper menangani kolom yang benar-benar ada (this.auditColumns); bila tabel
1822
- // tidak punya audit columns, helper no-op.
1823
- this._appendUpdateAuditColumns(headerFields, headerValues, headerData, eventContext);
1824
-
1825
- headerValues.push(primaryKeyValue);
1826
- const updateSql = 'UPDATE ' + this.writeSource + ' SET ' + headerFields.join(', ') + ' WHERE ' + this.primaryKey + ' = ?';
1827
- console.log('Executing header UPDATE:', { query: updateSql, values: headerValues });
1828
- await connection.execute(updateSql, headerValues);
1829
-
1830
- // SELECT back updated header
1831
- const selectSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + this.primaryKey + ' = ?';
1832
- const [headerRows] = await connection.execute(selectSql, [primaryKeyValue]);
1833
- let updatedHeader = headerRows[0];
1834
-
1835
- // Process detail items
1836
- const detailTableFull = '${detailTable}';
1837
- const fk = '${foreignKey}';
1838
- const detailPk = '${detailPrimaryKey}';
1839
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1840
-
1841
- const detailOperations = data[detailKey] || {};
1842
- const { insert: insertItems = [], update: updateItems = [], delete: deleteItems = [] } = detailOperations;
1843
-
1844
- const deletedItems = [];
1845
- const updatedItems = [];
1846
- const insertedItems = [];
1847
-
1848
- // 1. DELETE operations
1849
- for (const item of deleteItems) {
1850
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in delete operation');
1851
- const delSql = 'DELETE FROM ' + detailTableFull + ' WHERE ' + detailPk + ' = ?';
1852
- await connection.execute(delSql, [item[detailPk]]);
1853
- deletedItems.push(item);
1854
- }
1855
- console.log('Deleted ' + deletedItems.length + ' detail item(s)');
1856
-
1857
- // 2. UPDATE operations
1858
- for (const item of updateItems) {
1859
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in update operation');
1860
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1861
- const dFields = [];
1862
- const dValues = [];
1863
- for (const [key, value] of Object.entries(item)) {
1864
- if (key === detailPk) continue;
1865
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1866
- if (value !== undefined && value !== null) {
1867
- dFields.push(key + ' = ?');
1868
- dValues.push(value);
1869
- }
1870
- }
1871
-
1872
- // Inject audit columns (updated_at, updated_by) untuk detail UPDATE via helper
1873
- this._appendUpdateAuditColumns(dFields, dValues, item, eventContext);
1874
-
1875
- dValues.push(item[detailPk]);
1876
- const dUpdateSql = 'UPDATE ' + detailTableFull + ' SET ' + dFields.join(', ') + ' WHERE ' + detailPk + ' = ?';
1877
- await connection.execute(dUpdateSql, dValues);
1878
- updatedItems.push(item);
1879
- }
1880
- console.log('Updated ' + updatedItems.length + ' detail item(s)');
1881
-
1882
- // 3. INSERT operations
1883
- for (const item of insertItems) {
1884
- item[fk] = primaryKeyValue;
1885
-
1886
- // Auto-generate UUID untuk detail PK bila client tidak menyupply
1887
- // (sama seperti di createComposite; konsisten dengan kolom VARCHAR
1888
- // DEFAULT (UUID()))
1889
- if (item[detailPk] === undefined || item[detailPk] === null) {
1890
- item[detailPk] = require('uuid').v7();
1891
- }
1892
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1893
- const dFields = [];
1894
- const dValues = [];
1895
- const dPlaceholders = [];
1896
- for (const [key, value] of Object.entries(item)) {
1897
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1898
- if (value !== undefined && value !== null) {
1899
- dFields.push(key);
1900
- dValues.push(value);
1901
- dPlaceholders.push('?');
1902
- }
1903
- }
1904
-
1905
- // Inject audit columns ke detail INSERT (detail baru dari updateComposite) via helper
1906
- this._appendCreateAuditColumns(dFields, dValues, dPlaceholders, item, eventContext);
1907
-
1908
- const dInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + dFields.join(', ') + ') VALUES (' + dPlaceholders.join(', ') + ')';
1909
- await connection.execute(dInsertSql, dValues);
1910
- insertedItems.push(item);
1911
- }
1912
- console.log('Inserted ' + insertedItems.length + ' new detail item(s)');
1913
-
1914
- // Get all current detail items
1915
- const allItemsSql = 'SELECT * FROM ' + detailTableFull + ' WHERE ' + fk + ' = ? ORDER BY line_number';
1916
- const [allItems] = await connection.execute(allItemsSql, [primaryKeyValue]);
1917
- ${headerCalculations ? `
1918
- // Recalculate header totals
1919
- const calculations = ${JSON.stringify(headerCalculations)};
1920
- const recalcFields = [];
1921
- const recalcValues = [];
1922
-
1923
- if (calculations.total_items) {
1924
- recalcFields.push('total_items = ?');
1925
- recalcValues.push(allItems.length);
1926
- }
1927
- if (calculations.total_qty && calculations.total_qty.source) {
1928
- const qtyField = calculations.total_qty.source.replace('items.', '');
1929
- const totalQty = allItems.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
1930
- recalcFields.push('total_qty = ?');
1931
- recalcValues.push(totalQty);
1932
- }
1933
- ${amountQtyField ? `if (calculations.total_amount) {
1934
- const totalAmount = allItems.reduce(function(sum, item) {
1935
- var qty = Number(item.${amountQtyField}) || 0;
1936
- var price = Number(item.${amountPriceField}) || 0;
1937
- return sum + (qty * price);
1938
- }, 0);
1939
- recalcFields.push('total_amount = ?');
1940
- recalcValues.push(totalAmount);
1941
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
1942
-
1943
- if (recalcFields.length > 0) {
1944
- // Inject audit columns ke recalculation UPDATE via helper
1945
- this._appendUpdateAuditColumns(recalcFields, recalcValues, data, eventContext);
1946
- recalcValues.push(primaryKeyValue);
1947
- var recalcSql = 'UPDATE ' + this.writeSource + ' SET ' + recalcFields.join(', ') + ' WHERE ' + this.primaryKey + ' = ?';
1948
- console.log('Recalculating header totals:', { query: recalcSql, values: recalcValues });
1949
- await connection.execute(recalcSql, recalcValues);
1950
-
1951
- var [recalcRows] = await connection.execute(selectSql, [primaryKeyValue]);
1952
- updatedHeader = recalcRows[0];
1953
- }
1954
- ` : ''}
1955
- // --- Hook: onAfterCompositeUpdate ---
1956
- if (eventContext && eventContext.componentEngine) {
1957
- var _ce2 = eventContext.componentEngine;
1958
- var _CB2 = eventContext.ContextBuilder;
1959
- var _afterCtx = _CB2.buildCompositeUpdateAfterContext(headerData, oldData, updatedHeader, {
1960
- inserted: insertedItems,
1961
- updated: updatedItems,
1962
- deleted: deletedItems
1963
- }, {
1964
- tableName: '${payload.tableName}',
1965
- detailTable: '${detailTable}',
1966
- foreignKey: '${foreignKey}',
1967
- primaryKey: this.primaryKey,
1968
- ...(eventContext.additionalContext || {})
1969
- });
1970
- var _afterResult = await _ce2.executeOnAfterComposite('update', _afterCtx);
1971
- if (!_afterResult.success) {
1972
- await connection.rollback();
1973
- throw new Error('onAfterCompositeUpdate failed: ' + _afterResult.error);
1974
- }
1975
- }
1976
-
1977
- await connection.commit();
1978
- console.log('Transaction committed successfully');
1979
-
1980
- // Invalidate cache setelah write operation berhasil
1981
- await this.invalidateCache();
1982
-
1983
- return {
1984
- ...updatedHeader,
1985
- [detailKey]: allItems,
1986
- _operations: {
1987
- deleted: deletedItems.length,
1988
- updated: updatedItems.length,
1989
- inserted: insertedItems.length
1990
- }
1991
- };
1992
- } catch (error) {
1993
- try { await connection.rollback(); } catch (rbErr) { console.error('Rollback failed:', rbErr.message); }
1994
- console.error('Error in updateComposite:', error);
1995
- throw error;
1996
- } finally {
1997
- try { connection.release(); } catch (relErr) { console.error('Connection release failed:', relErr.message); }
1998
- }
1999
- }
2000
- `;
2001
- }
2002
-
2003
- // readComposite
2004
- if (actions.readComposite) {
2005
- // Build detail query loader code yang support prefix `file:` maupun SQL literal
2006
- let mysqlDetailLoaderCode;
2007
- if (detailQuery && typeof detailQuery === 'string' && detailQuery.startsWith('file:')) {
2008
- const relativePath = detailQuery.replace('file:', '');
2009
- const fileName = relativePath.split('/').pop();
2010
- mysqlDetailLoaderCode = `
2011
- // Load detail query dari file lokal
2012
- let detailSql;
2013
- try {
2014
- const detailQueryFilePath = path.join(__dirname, 'query', '${fileName}');
2015
- if (fs.existsSync(detailQueryFilePath)) {
2016
- detailSql = fs.readFileSync(detailQueryFilePath, 'utf8').trim();
2017
- } else {
2018
- throw new Error(\`Detail query file not found: \${detailQueryFilePath}\`);
2019
- }
2020
- } catch (error) {
2021
- throw new Error('Failed to load detail query file: ' + error.message);
2022
- }`;
2023
- } else if (detailQuery) {
2024
- mysqlDetailLoaderCode = `const detailSql = \`${detailQuery.replace(/\$1/g, '?')}\`;`;
2025
- } else {
2026
- mysqlDetailLoaderCode = `const detailSql = 'SELECT * FROM ${detailTable} WHERE ${foreignKey} = ? ORDER BY line_number';`;
2027
- }
2028
-
2029
- code += `
2030
- /**
2031
- * Composite read - Read header with detail items (MySQL)
2032
- */
2033
- async readComposite(options) {
2034
- try {
2035
- if (!options.where) {
2036
- throw new Error('Invalid request format: where parameter is required');
2037
- }
2038
-
2039
- let whereClauseResult;
2040
- try {
2041
- whereClauseResult = this.buildComplexWhereClause(options.where);
2042
- } catch (e) {
2043
- const error = new Error('Invalid where conditions: ' + e.message);
2044
- error.statusCode = 400;
2045
- throw error;
2046
- }
2047
- const { sql: whereClause, params } = whereClauseResult;
2048
- const headerSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + whereClause;
2049
- const headerResults = await db.executeQuery(headerSql, params);
2050
-
2051
- if (!headerResults || headerResults.length === 0) {
2052
- return { success: true, count: 0, data: [] };
2053
- }
2054
-
2055
- const compositeResults = [];
2056
- const detailKey = '${detailTableName}';
2057
- ${mysqlDetailLoaderCode}
2058
-
2059
- for (const header of headerResults) {
2060
- const pkValue = header[this.primaryKey];
2061
- const detailResults = await db.executeQuery(detailSql, [pkValue]);
2062
- compositeResults.push({
2063
- ...header,
2064
- [detailKey]: detailResults || []
2065
- });
2066
- }
2067
-
2068
- return {
2069
- success: true,
2070
- count: compositeResults.length,
2071
- data: compositeResults
2072
- };
2073
- } catch (error) {
2074
- console.error('Error in readComposite:', error);
2075
- throw error;
2076
- }
2077
- }
2078
- `;
2079
- }
2080
-
2081
- return code;
2082
- })()}
2083
- }
2084
-
2085
- module.exports = new ${className}Model();`;
2086
- }
2087
-
2088
- /**
2089
- * Membuat template untuk submodule MySQL
2090
- * @param {string} moduleName - Nama module
2091
- * @param {string} endpointName - Nama endpoint
2092
- * @param {Object} payload - Data payload dari JSON
2093
- * @returns {string} Template submodule untuk MySQL
2094
- */
2095
- function createMysqlSubmoduleTemplate(moduleName, endpointName, payload) {
2096
- const modelVarName = toCamelCase(endpointName) + 'Model';
2097
- const primaryKey = payload.primaryKey || 'id';
2098
- const timestamp = new Date().toISOString();
2099
- const enabledActions = Object.entries(payload.action || {})
2100
- .filter(([, enabled]) => enabled)
2101
- .map(([action]) => action);
2102
- const hasComponents = payload.components && Array.isArray(payload.components) && payload.components.length > 0;
2103
-
2104
- // Compute componentConfig values untuk generated submodule (parsed oleh config-extractor di runtime)
2105
- const exportQuery = payload.exportQuery || `SELECT ${payload.fieldName.join(', ')} FROM ${payload.tableName}`;
2106
- const exportQueryEscaped = exportQuery.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
2107
- const fieldNameStr = JSON.stringify(payload.fieldName);
2108
- const columnFormatsStr = payload.columnFormats ? JSON.stringify(payload.columnFormats) : 'null';
2109
- const fieldLabelsStr = payload.fieldLabels ? JSON.stringify(payload.fieldLabels) : 'null';
2110
- const importConfigObj = payload.action && payload.action.import ? {
2111
- enabled: true,
2112
- upsertKeys: (payload.importConfig || {}).upsertKeys || [primaryKey],
2113
- upsertStrategy: (payload.importConfig || {}).upsertStrategy || 'update_existing',
2114
- requiredFields: (payload.importConfig || {}).requiredFields || [],
2115
- validations: (payload.importConfig || {}).validations || {},
2116
- lookupFields: (payload.importConfig || {}).lookupFields || {},
2117
- excludeFromImport: (payload.importConfig || {}).excludeFromImport || [],
2118
- chunkSize: (payload.importConfig || {}).chunkSize || 100
2119
- } : null;
2120
- const importConfigStr = importConfigObj ? JSON.stringify(importConfigObj) : 'null';
2121
- const exportQueryStr = payload.action && payload.action.export ? `'${exportQueryEscaped}'` : 'null';
2122
- const adjustConfigObj = payload.action && payload.action.adjust ? (payload.adjustConfig || {}) : null;
2123
- const adjustConfigStr = adjustConfigObj ? JSON.stringify(adjustConfigObj) : 'null';
2124
-
2125
- // Layer 2 (Concurrency by Design): inject fieldPolicy sebagai argumen
2126
- // tambahan ke updateData()/adjustData(). Untuk payload TANPA fieldPolicy,
2127
- // policyArg = '' sehingga output identik dengan baseline path relaxed.
2128
- const policyArg = payload.fieldPolicy
2129
- ? `, ${JSON.stringify(payload.fieldPolicy)}`
2130
- : '';
2131
-
2132
- // Component engine declarations: selalu emit `let`-binding sehingga composite
2133
- // handler (yang selalu mereferensi componentEngine/ContextBuilder untuk
2134
- // membangun eventContext) tidak throw ReferenceError saat payload tidak
2135
- // memiliki components. Runtime require hanya dilakukan bila hasComponents.
2136
- let additionalRequires = 'let componentEngine = null;\nlet ContextBuilder = null;\n';
2137
- if (hasComponents) {
2138
- additionalRequires += `componentEngine = require('@restforgejs/platform/src/utils/component-engine').componentEngine;\n`;
2139
- additionalRequires += `ContextBuilder = require('@restforgejs/platform/src/utils/context-builder');\n`;
2140
- }
2141
-
2142
- // Request scope ownership verification snippets (Layer 1 RLS).
2143
- // Three variants are needed across the inline endpoint handlers:
2144
- // - standard: fetch by PK + scope, 404 on mismatch (/update, /adjust)
2145
- // - postFetch: compare after getData in JS (/first; where is {key,value} object)
2146
- // - master: composite master ownership with specific "Master record" message
2147
- // Each yields an empty string when payload.requestScope is absent.
2148
- const requestScopeOwnershipCheckStandard = payload.requestScope ? `
2149
- // Request scope ownership verification (Layer 1 RLS)
2150
- if (req._requestScope) {
2151
- const scopeCheck = await ${modelVarName}.getData({
2152
- where: [
2153
- { key: primaryKey, value: req.body[primaryKey] },
2154
- { key: req._requestScope.column, value: req._requestScope.value }
2155
- ]
2156
- });
2157
- if (!scopeCheck.success || !scopeCheck.data || scopeCheck.data.length === 0) {
2158
- return res.status(404).json({
2159
- success: false,
2160
- error: 'Data not found',
2161
- message: '${endpointName} data not found or access denied',
2162
- timestamp: new Date().toISOString()
2163
- });
2164
- }
2165
- }
2166
- ` : '';
2167
-
2168
- const requestScopeOwnershipCheckPostFetch = payload.requestScope ? `
2169
- // Request scope ownership verification (Layer 1 RLS)
2170
- if (req._requestScope && result.data && result.data.length > 0) {
2171
- const record = result.data[0];
2172
- if (record[req._requestScope.column] !== undefined &&
2173
- String(record[req._requestScope.column]) !== String(req._requestScope.value)) {
2174
- return res.status(404).json({
2175
- success: false,
2176
- error: 'Data not found',
2177
- message: '${endpointName} data not found',
2178
- timestamp: new Date().toISOString()
2179
- });
2180
- }
2181
- }
2182
- ` : '';
2183
-
2184
- const requestScopeOwnershipCheckMaster = payload.requestScope ? `
2185
- // Request scope ownership verification (Layer 1 RLS) — master record
2186
- if (req._requestScope) {
2187
- const scopeCheck = await ${modelVarName}.getData({
2188
- where: [
2189
- { key: '${primaryKey}', value: data.${primaryKey} },
2190
- { key: req._requestScope.column, value: req._requestScope.value }
2191
- ]
2192
- });
2193
- if (!scopeCheck.success || !scopeCheck.data || scopeCheck.data.length === 0) {
2194
- return res.status(404).json({
2195
- success: false,
2196
- error: 'Data not found',
2197
- message: 'Master record not found or access denied',
2198
- timestamp: new Date().toISOString()
2199
- });
2200
- }
2201
- }
2202
- ` : '';
2203
-
2204
- let subModuleContent = `const express = require('express');
2205
- const router = express.Router();
2206
- const ${modelVarName} = require('../../models/${moduleName}/${endpointName}');
2207
- ${additionalRequires}
2208
- /**
2209
- * ${toPascalCase(endpointName)} Submodule - MySQL Database
2210
- * Generated: ${timestamp}
2211
- *
2212
- * MySQL-optimized endpoints untuk ${endpointName}
2213
- * Actions: ${enabledActions.join(', ')}
2214
- * Table: ${payload.tableName}
2215
- * Database: MySQL
2216
- */
2217
-
2218
- // Primary key untuk endpoint ini
2219
- const primaryKey = '${primaryKey}';
2220
-
2221
- // Component configuration untuk export/import (parsed oleh config-extractor — jangan dimodifikasi)
2222
- const componentConfig = {
2223
- tableName: '${payload.tableName}',
2224
- fieldName: ${fieldNameStr},
2225
- exportQuery: ${exportQueryStr},
2226
- columnFormats: ${columnFormatsStr},
2227
- fieldLabels: ${fieldLabelsStr},
2228
- importConfig: ${importConfigStr},
2229
- adjustConfig: ${adjustConfigStr},
2230
- uploadConfig: ${payload.uploadConfig ? JSON.stringify(payload.uploadConfig) : 'null'},
2231
- requestScope: ${payload.requestScope ? JSON.stringify(payload.requestScope) : 'null'}
2232
- };
2233
- ${hasComponents ? `
2234
- // Initialize component engine dengan event handlers dari payload
2235
- const _componentPayload = { components: ${JSON.stringify(payload.components)} };
2236
- componentEngine.loadConfigurationFromObject(_componentPayload).then(result => {
2237
- if (result.success) {
2238
- console.log(\`Component configuration loaded for ${moduleName}/${endpointName}: \${result.componentsLoaded} components\`);
2239
- }
2240
- }).catch(err => {
2241
- console.error(\`Failed to load component configuration for ${moduleName}/${endpointName}:\`, err.message);
2242
- });
2243
- ` : ''}
2244
- // CORS ditangani di level app oleh cors middleware (lihat konfigurasi CORS_ENABLED dan CORS_ORIGINS di .env)
2245
-
2246
- // Request ID untuk tracing — support correlation ID dari upstream
2247
- router.use((req, res, next) => {
2248
- req.mysqlRequestId = req.headers['x-correlation-id'] || \`mysql_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
2249
- res.setHeader('X-Correlation-ID', req.mysqlRequestId);
2250
- res.setHeader('X-MySQL-Request-ID', req.mysqlRequestId);
2251
- next();
2252
- });
2253
-
2254
-
2255
-
2256
- // Middleware untuk validasi payload MySQL
2257
- router.use((req, res, next) => {
2258
- if (req.method === 'POST') {
2259
- // Skip validation untuk endpoint export/import (ditangani oleh centralized handler)
2260
- if (req.path.startsWith('/import') || req.path.startsWith('/export')) {
2261
- return next();
2262
- }
2263
- try {
2264
- if (!req.body || Object.keys(req.body).length === 0) {
2265
- return res.status(400).json({
2266
- success: false,
2267
- error: 'Missing payload',
2268
- message: 'Payload cannot be empty',
2269
- timestamp: new Date().toISOString()
2270
- });
2271
- }
2272
-
2273
- const endpoint = req.path.substring(1);
2274
-
2275
- if (endpoint === 'first') {
2276
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
2277
- req.body.where = req.body.where[0];
2278
- }
2279
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
2280
- return res.status(400).json({
2281
- success: false,
2282
- error: 'Invalid payload',
2283
- message: 'Where must be a single condition {key, value}',
2284
- example: {
2285
- "where": { "key": "field_name", "value": "field_value" },
2286
- "select": ["field1", "field2"]
2287
- },
2288
- timestamp: new Date().toISOString()
2289
- });
2290
- }
2291
- if (req.body.where.conditions || req.body.where.logic) {
2292
- return res.status(400).json({
2293
- success: false,
2294
- error: 'Invalid payload',
2295
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
2296
- example: {
2297
- "where": { "key": "field_name", "value": "field_value" }
2298
- },
2299
- timestamp: new Date().toISOString()
2300
- });
2301
- }
2302
- }
2303
-
2304
- if (endpoint === 'delete' && (!req.body.where)) {
2305
- return res.status(400).json({
2306
- success: false,
2307
- error: 'Invalid payload',
2308
- message: 'DELETE payload must include a where property',
2309
- example: {
2310
- "where": [{ "key": "${primaryKey}", "value": "your-value" }]
2311
- },
2312
- timestamp: new Date().toISOString()
2313
- });
2314
- }
2315
- } catch (error) {
2316
- console.error(\`Error validating MySQL payload for \${req.path}:\`, error);
2317
- return res.status(400).json({
2318
- success: false,
2319
- error: 'Invalid payload',
2320
- message: 'Invalid payload format',
2321
- details: error.message,
2322
- timestamp: new Date().toISOString()
2323
- });
2324
- }
2325
- }
2326
- next();
2327
- });
2328
-
2329
- `;
2330
-
2331
- // ─── Request Scope Middleware (Layer 1 RLS) ──────────────────
2332
- // Inject after validation middleware, before endpoint handlers.
2333
- // Empty string when payload.requestScope is absent (backward compat).
2334
- subModuleContent += buildRequestScopeMiddleware(payload, endpointName);
2335
-
2336
- // POST /datatables
2337
- if (payload.action && payload.action.datatables) {
2338
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/datatables - Data untuk DataTables
2339
- router.post('/datatables', async (req, res) => {
2340
- try {
2341
- const options = {
2342
- searchValue: req.body.search?.value || req.body.searchValue || req.body.search_value || '',
2343
- searchBy: req.body.searchBy || req.body.search_by || 'all',
2344
- perPage: Math.min(parseInt(req.body.length || req.body.pagination?.perpage || 10, 10), 1000),
2345
- start: Math.max(parseInt(req.body.start || 0, 10), 0),
2346
- draw: req.body.draw || '1'
2347
- };
2348
-
2349
- // Handle sort_columns
2350
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
2351
- options.sort_columns = req.body.sort_columns.map(item => ({
2352
- column: item.column,
2353
- direction: (item.direction || 'ASC').toUpperCase()
2354
- }));
2355
- }
2356
-
2357
- // Fallback: Handle DataTables standard format (order[0][column] dan order[0][dir])
2358
- if (req.body['order[0][column]'] !== undefined) {
2359
- options['order[0][column]'] = req.body['order[0][column]'];
2360
- }
2361
- if (req.body['order[0][dir]'] !== undefined) {
2362
- options['order[0][dir]'] = req.body['order[0][dir]'];
2363
- }
2364
-
2365
- // Handle filters dengan sanitasi
2366
- if (req.body.filters && typeof req.body.filters === 'object') {
2367
- const sanitizedFilters = {};
2368
- for (const [key, value] of Object.entries(req.body.filters)) {
2369
- if (value !== null && value !== undefined && value !== '' && value !== 'all' && value !== '-') {
2370
- sanitizedFilters[key] = value;
2371
- }
2372
- }
2373
- if (Object.keys(sanitizedFilters).length > 0) {
2374
- options.filters = sanitizedFilters;
2375
- }
2376
- }
2377
-
2378
- // Support WHERE conditions
2379
- if (req.body.where) {
2380
- options.where = req.body.where;
2381
- }
2382
-
2383
- // Advanced filters support
2384
- if (req.body.advanced_filters && Array.isArray(req.body.advanced_filters)) {
2385
- options.advancedFilters = req.body.advanced_filters;
2386
- }
2387
-
2388
- // Gunakan model untuk mendapatkan data
2389
- const result = await ${modelVarName}.getDatatables(options);
2390
-
2391
- // Menambahkan nomor baris untuk DataTables
2392
- if (result.data && Array.isArray(result.data)) {
2393
- result.data = result.data.map((item, index) => ({
2394
- ...item,
2395
- rownumerator: options.start + index + 1
2396
- }));
2397
- }
2398
-
2399
- return res.json(result);
2400
- } catch (error) {
2401
- console.error('Error in ${endpointName} datatables:', error);
2402
- const statusCode = error.statusCode || 500;
2403
- return res.status(statusCode).json({
2404
- success: false,
2405
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
2406
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} data',
2407
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2408
- timestamp: new Date().toISOString()
2409
- });
2410
- }
2411
- });
2412
-
2413
- `;
2414
- }
2415
-
2416
- // GET /lookup - Dynamic
2417
- if (payload.action && payload.action.lookup) {
2418
- subModuleContent += `// GET /api/${moduleName}/${endpointName}/lookup - MySQL Dynamic Lookup
2419
- router.get('/lookup', async (req, res) => {
2420
- const mysqlRequestId = req.mysqlRequestId;
2421
-
2422
- try {
2423
- const requestMode = req.headers['x-request-mode'];
2424
-
2425
- if (requestMode !== 'dynamic') {
2426
- return res.status(400).json({
2427
- success: false,
2428
- error: 'Invalid Request Mode',
2429
- message: 'X-Request-Mode header must be set to dynamic',
2430
- timestamp: new Date().toISOString()
2431
- });
2432
- }
2433
-
2434
- let search = req.query.search || '';
2435
- if (Array.isArray(search)) {
2436
- search = search[0] || '';
2437
- }
2438
-
2439
- // Search length validation
2440
- if (search.length > 100) {
2441
- return res.status(400).json({
2442
- success: false,
2443
- error: 'Search Too Long',
2444
- message: 'Search parameter must not exceed 100 characters',
2445
- timestamp: new Date().toISOString()
2446
- });
2447
- }
2448
-
2449
- console.log(\`[MySQL-LKP] \${mysqlRequestId} dynamic search: \${search}\`);
2450
-
2451
- // Collect extra filters dari query params
2452
- const extraFilters = {};
2453
- for (const [key, value] of Object.entries(req.query)) {
2454
- if (key !== 'search' && ${modelVarName}.validFields.includes(key) && value) {
2455
- extraFilters[key] = value;
2456
- }
2457
- }
2458
-
2459
- const startTime = Date.now();
2460
- const list = Object.keys(extraFilters).length > 0 ?
2461
- await ${modelVarName}.getLookupDataDynamic(search, extraFilters) :
2462
- await ${modelVarName}.getLookupData(search);
2463
- const lookupTime = Date.now() - startTime;
2464
-
2465
- console.log(\`[MySQL-LKP] \${mysqlRequestId} found \${list.length} results in \${lookupTime}ms\`);
2466
-
2467
- return res.json({
2468
- success: true,
2469
- count: list.length,
2470
- data: list,
2471
- search: search,
2472
- _mysql: { requestId: mysqlRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2473
- });
2474
- } catch (error) {
2475
- console.error(\`[MySQL-LKP] Error \${mysqlRequestId}:\`, error);
2476
- return res.status(500).json({
2477
- success: false,
2478
- error: 'Internal Server Error',
2479
- message: 'An error occurred while looking up ${endpointName} data',
2480
- details: error.message,
2481
- timestamp: new Date().toISOString()
2482
- });
2483
- }
2484
- });
2485
-
2486
- `;
2487
- }
2488
-
2489
- // POST /lookup - Static
2490
- if (payload.action && payload.action.lookup) {
2491
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/lookup - MySQL Static Lookup
2492
- router.post('/lookup', async (req, res) => {
2493
- const mysqlRequestId = req.mysqlRequestId;
2494
-
2495
- try {
2496
- const requestMode = req.headers['x-request-mode'];
2497
-
2498
- if (requestMode !== 'static') {
2499
- return res.status(400).json({
2500
- success: false,
2501
- error: 'Invalid Request Mode',
2502
- message: 'X-Request-Mode header must be set to static for POST lookup',
2503
- timestamp: new Date().toISOString()
2504
- });
2505
- }
2506
-
2507
- console.log(\`[MySQL-LKP] \${mysqlRequestId} static lookup:\`, JSON.stringify(req.body, null, 2));
2508
-
2509
- const startTime = Date.now();
2510
- let list;
2511
-
2512
- if (req.body.where) {
2513
- // New format dengan where clause + optional select dan order
2514
- list = await ${modelVarName}.getLookupDataWithFilter(req.body);
2515
- } else {
2516
- // Legacy format dengan selected_tag
2517
- const selectedTag = req.body.selected_tag || '';
2518
- list = await ${modelVarName}.getStaticLookupData(selectedTag);
2519
- }
2520
-
2521
- const lookupTime = Date.now() - startTime;
2522
- console.log(\`[MySQL-LKP] \${mysqlRequestId} found \${list.length} results in \${lookupTime}ms\`);
2523
-
2524
- return res.json({
2525
- success: true,
2526
- count: list.length,
2527
- data: list,
2528
- _mysql: { requestId: mysqlRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2529
- });
2530
- } catch (error) {
2531
- console.error(\`[MySQL-LKP] Error \${mysqlRequestId}:\`, error);
2532
- return res.status(500).json({
2533
- success: false,
2534
- error: 'Internal Server Error',
2535
- message: 'An error occurred while looking up ${endpointName} data',
2536
- details: error.message,
2537
- timestamp: new Date().toISOString()
2538
- });
2539
- }
2540
- });
2541
-
2542
- `;
2543
- }
2544
-
2545
- // POST /create
2546
- if (payload.action && payload.action.create) {
2547
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create - MySQL Insert
2548
- router.post('/create', async (req, res) => {
2549
- try {
2550
- if (!req.body || Object.keys(req.body).length === 0) {
2551
- return res.status(400).json({
2552
- success: false,
2553
- error: 'Invalid payload',
2554
- message: 'Payload cannot be empty',
2555
- timestamp: new Date().toISOString()
2556
- });
2557
- }
2558
-
2559
- // Validasi data
2560
- if (typeof ${modelVarName}.validateData === 'function') {
2561
- const validation = await ${modelVarName}.validateData(req.body, 'insert');
2562
- if (!validation.isValid) {
2563
- return res.status(400).json({
2564
- success: false,
2565
- error: 'Validation failed',
2566
- message: 'Invalid data',
2567
- errors: validation.errors,
2568
- timestamp: new Date().toISOString()
2569
- });
2570
- }
2571
- req.body = { ...req.body, ...validation.sanitizedData };
2572
- }
2573
-
2574
- ${hasComponents ? `
2575
- // Component engine: build eventContext untuk model-level event lifecycle
2576
- if (componentEngine && ContextBuilder) {
2577
- try {
2578
- const eventContext = {
2579
- componentEngine: componentEngine,
2580
- ContextBuilder: ContextBuilder,
2581
- tableName: '${payload.tableName}',
2582
- additionalContext: {
2583
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2584
- options: req.bodyOptions || {},
2585
- requestId: req.id || null
2586
- }
2587
- };
2588
- var result = await ${modelVarName}.addData(req.body, eventContext);
2589
- console.log('[INTEGRATED TRANSACTION] INSERT completed successfully with events');
2590
- } catch (error) {
2591
- console.error('[INTEGRATED TRANSACTION] INSERT failed:', error.message);
2592
- throw error;
2593
- }
2594
- } else {
2595
- try {
2596
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2597
- console.log('[FALLBACK] INSERT completed without events');
2598
- } catch (error) {
2599
- console.error('[FALLBACK] INSERT failed:', error.message);
2600
- throw error;
2601
- }
2602
- }
2603
- ` : `
2604
- try {
2605
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2606
- console.log('[FALLBACK] INSERT completed without events');
2607
- } catch (error) {
2608
- console.error('[FALLBACK] INSERT failed:', error.message);
2609
- throw error;
2610
- }
2611
- `}
2612
- console.log(\`${endpointName} data added successfully: \${result.${primaryKey} || 'new record'}\`);
2613
-
2614
- return res.status(201).json({
2615
- success: true,
2616
- message: '${endpointName} data successfully added',
2617
- data: result,
2618
- timestamp: new Date().toISOString()
2619
- });
2620
- } catch (error) {
2621
- console.error('Error saat menambahkan data ${endpointName}:', error);
2622
-
2623
- if (error.code === 'ER_DUP_ENTRY' || error.errno === 1062) {
2624
- return res.status(409).json({
2625
- success: false,
2626
- error: 'Duplicate entry',
2627
- message: 'A record with this value already exists',
2628
- timestamp: new Date().toISOString()
2629
- });
2630
- }
2631
-
2632
- if (error.code === 'ER_NO_REFERENCED_ROW_2' || error.errno === 1452) {
2633
- return res.status(400).json({
2634
- success: false,
2635
- error: 'Foreign key constraint',
2636
- message: 'Referenced data not found',
2637
- timestamp: new Date().toISOString()
2638
- });
2639
- }
2640
-
2641
- return res.status(500).json({
2642
- success: false,
2643
- error: 'Internal Server Error',
2644
- message: 'An error occurred while adding ${endpointName} data',
2645
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2646
- timestamp: new Date().toISOString()
2647
- });
2648
- }
2649
- });
2650
-
2651
- `;
2652
- }
2653
-
2654
- // POST /update
2655
- if (payload.action && payload.action.update) {
2656
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update - MySQL Update
2657
- router.post('/update', async (req, res) => {
2658
- try {
2659
- // Validasi payload
2660
- if (!req.body || Object.keys(req.body).length === 0) {
2661
- return res.status(400).json({
2662
- success: false,
2663
- error: 'Invalid payload',
2664
- message: 'Payload cannot be empty',
2665
- timestamp: new Date().toISOString()
2666
- });
2667
- }
2668
-
2669
- // Validasi primary key
2670
- const primaryKey = '${primaryKey}';
2671
- if (!req.body[primaryKey]) {
2672
- return res.status(400).json({
2673
- success: false,
2674
- error: 'Missing required field',
2675
- message: \`Primary key (\${primaryKey}) is required for update\`,
2676
- timestamp: new Date().toISOString()
2677
- });
2678
- }
2679
- ${requestScopeOwnershipCheckStandard}
2680
- // Validasi data dengan model jika tersedia
2681
- if (typeof ${modelVarName}.validateData === 'function') {
2682
- const validation = await ${modelVarName}.validateData(req.body, 'update');
2683
- if (!validation.isValid) {
2684
- return res.status(400).json({
2685
- success: false,
2686
- error: 'Validation failed',
2687
- message: 'Invalid data',
2688
- errors: validation.errors,
2689
- timestamp: new Date().toISOString()
2690
- });
2691
- }
2692
- req.body = { ...req.body, ...validation.sanitizedData };
2693
- }
2694
-
2695
- let responseData = null;
2696
-
2697
- ${hasComponents ? `
2698
- // Integrated transaction dengan event lifecycle
2699
- try {
2700
- const eventContext = {
2701
- componentEngine: componentEngine,
2702
- ContextBuilder: ContextBuilder,
2703
- tableName: '${payload.tableName}',
2704
- additionalContext: {
2705
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2706
- options: req.bodyOptions || {}
2707
- }
2708
- };
2709
- responseData = await ${modelVarName}.updateData(req.body, eventContext${policyArg});
2710
- console.log('[INTEGRATED TRANSACTION] UPDATE completed successfully with events');
2711
- } catch (error) {
2712
- console.error('[INTEGRATED TRANSACTION] UPDATE failed:', error.message);
2713
- throw error;
2714
- }
2715
- ` : `
2716
- // Fallback: mode tanpa events
2717
- try {
2718
- responseData = await ${modelVarName}.updateData(req.body, { additionalContext: { requestId: req.id || null } }${policyArg});
2719
- console.log('[FALLBACK] UPDATE completed without events');
2720
- } catch (error) {
2721
- console.error('[FALLBACK] UPDATE failed:', error.message);
2722
- throw error;
2723
- }
2724
- `}
2725
- // Log successful operation
2726
- console.log(\`${endpointName} data updated successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2727
-
2728
- return res.status(200).json({
2729
- success: true,
2730
- message: '${endpointName} data successfully updated',
2731
- data: responseData,
2732
- timestamp: new Date().toISOString()
2733
- });
2734
- } catch (error) {
2735
- console.error('Error saat mengupdate data ${endpointName}:', error);
2736
-
2737
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2738
- return res.status(404).json({
2739
- success: false,
2740
- error: 'Data not found',
2741
- message: '${endpointName} data not found',
2742
- timestamp: new Date().toISOString()
2743
- });
2744
- }
2745
-
2746
- if (error.code === 'ER_DUP_ENTRY' || error.errno === 1062) {
2747
- return res.status(409).json({
2748
- success: false,
2749
- error: 'Duplicate entry',
2750
- message: 'A record with this value already exists',
2751
- timestamp: new Date().toISOString()
2752
- });
2753
- }
2754
-
2755
- return res.status(500).json({
2756
- success: false,
2757
- error: 'Internal Server Error',
2758
- message: 'An error occurred while updating ${endpointName} data',
2759
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2760
- timestamp: new Date().toISOString()
2761
- });
2762
- }
2763
- });
2764
-
2765
- `;
2766
- }
2767
-
2768
- // POST /adjust
2769
- if (payload.action && payload.action.adjust) {
2770
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/adjust - MySQL Adjust (atomic increment/decrement)
2771
- router.post('/adjust', async (req, res) => {
2772
- try {
2773
- // Validasi payload
2774
- if (!req.body || Object.keys(req.body).length === 0) {
2775
- return res.status(400).json({
2776
- success: false,
2777
- error: 'Invalid payload',
2778
- message: 'Payload cannot be empty',
2779
- timestamp: new Date().toISOString()
2780
- });
2781
- }
2782
-
2783
- // Validasi primary key
2784
- const primaryKey = '${primaryKey}';
2785
- if (!req.body[primaryKey]) {
2786
- return res.status(400).json({
2787
- success: false,
2788
- error: 'Missing required field',
2789
- message: \`Primary key (\${primaryKey}) is required for adjust\`,
2790
- timestamp: new Date().toISOString()
2791
- });
2792
- }
2793
-
2794
- // Validasi adjustments array
2795
- if (!req.body.adjustments || !Array.isArray(req.body.adjustments) || req.body.adjustments.length === 0) {
2796
- return res.status(400).json({
2797
- success: false,
2798
- error: 'Invalid payload',
2799
- message: 'adjustments array is required and must not be empty',
2800
- timestamp: new Date().toISOString()
2801
- });
2802
- }
2803
- ${requestScopeOwnershipCheckStandard}
2804
- const adjustConfig = componentConfig.adjustConfig || {};
2805
- let responseData = null;
2806
-
2807
- ${hasComponents ? `
2808
- // Integrated transaction dengan event lifecycle
2809
- try {
2810
- const eventContext = {
2811
- componentEngine: componentEngine,
2812
- ContextBuilder: ContextBuilder,
2813
- tableName: '${payload.tableName}',
2814
- additionalContext: {
2815
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2816
- options: req.bodyOptions || {}
2817
- }
2818
- };
2819
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, eventContext${policyArg});
2820
- console.log('[INTEGRATED TRANSACTION] ADJUST completed successfully with events');
2821
- } catch (error) {
2822
- console.error('[INTEGRATED TRANSACTION] ADJUST failed:', error.message);
2823
- throw error;
2824
- }
2825
- ` : `
2826
- // Fallback: mode tanpa events
2827
- try {
2828
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, { additionalContext: { requestId: req.id || null } }${policyArg});
2829
- console.log('[FALLBACK] ADJUST completed without events');
2830
- } catch (error) {
2831
- console.error('[FALLBACK] ADJUST failed:', error.message);
2832
- throw error;
2833
- }
2834
- `}
2835
-
2836
- // Log successful operation
2837
- console.log(\`${endpointName} data adjusted successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2838
-
2839
- return res.status(200).json({
2840
- success: true,
2841
- message: '${endpointName} data successfully adjusted',
2842
- data: responseData,
2843
- timestamp: new Date().toISOString()
2844
- });
2845
- } catch (error) {
2846
- console.error('Error saat mengadjust data ${endpointName}:', error);
2847
-
2848
- if (error.statusCode === 403) {
2849
- return res.status(403).json({
2850
- success: false,
2851
- error: 'Pro Feature Required',
2852
- message: error.message,
2853
- upgrade: 'https://restforge.dev/pricing',
2854
- timestamp: new Date().toISOString()
2855
- });
2856
- }
2857
-
2858
- if (error.message.includes('constraint violation') || error.message.includes('below minimum')) {
2859
- return res.status(409).json({
2860
- success: false,
2861
- error: 'Constraint violation',
2862
- message: error.message,
2863
- timestamp: new Date().toISOString()
2864
- });
2865
- }
2866
-
2867
- if (error.message.includes('not configured for adjustment') || error.message.includes('is required for adjust') || error.message.includes('must be a non-zero number') || error.message.includes('not a valid field') || error.message.includes('must not be empty')) {
2868
- return res.status(400).json({
2869
- success: false,
2870
- error: 'Validation error',
2871
- message: error.message,
2872
- timestamp: new Date().toISOString()
2873
- });
2874
- }
2875
-
2876
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2877
- return res.status(404).json({
2878
- success: false,
2879
- error: 'Data not found',
2880
- message: '${endpointName} data not found',
2881
- timestamp: new Date().toISOString()
2882
- });
2883
- }
2884
-
2885
- if (error.code === 'ER_DUP_ENTRY' || error.errno === 1062) {
2886
- return res.status(409).json({
2887
- success: false,
2888
- error: 'Duplicate entry',
2889
- message: 'A record with this value already exists',
2890
- timestamp: new Date().toISOString()
2891
- });
2892
- }
2893
-
2894
- return res.status(500).json({
2895
- success: false,
2896
- error: 'Internal Server Error',
2897
- message: 'An error occurred while adjusting ${endpointName} data',
2898
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2899
- timestamp: new Date().toISOString()
2900
- });
2901
- }
2902
- });
2903
-
2904
- `;
2905
- }
2906
-
2907
- // POST /change-status (workflow)
2908
- if (payload.action && payload.action.workflow) {
2909
- const workflowConfigStr = payload.workflow ? JSON.stringify(payload.workflow) : '{}';
2910
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/change-status - MySQL Change status ${endpointName}
2911
- router.post('/change-status', async (req, res) => {
2912
- try {
2913
- if (!req.body || Object.keys(req.body).length === 0) {
2914
- return res.status(400).json({
2915
- success: false,
2916
- error: 'Invalid payload',
2917
- message: 'Payload cannot be empty',
2918
- timestamp: new Date().toISOString()
2919
- });
2920
- }
2921
-
2922
- const primaryKey = '${primaryKey}';
2923
- const recordId = req.body[primaryKey] || req.body.id;
2924
- if (!recordId) {
2925
- return res.status(400).json({
2926
- success: false,
2927
- error: 'Missing required field',
2928
- message: \`Primary key (\${primaryKey}) or id is required for change-status\`,
2929
- timestamp: new Date().toISOString()
2930
- });
2931
- }
2932
-
2933
- if (!req.body.status) {
2934
- return res.status(400).json({
2935
- success: false,
2936
- error: 'Missing required field',
2937
- message: 'status is required for change-status',
2938
- timestamp: new Date().toISOString()
2939
- });
2940
- }
2941
- ${requestScopeOwnershipCheckStandard.replace(/req\.body\[primaryKey\]/g, 'recordId')}
2942
- const workflowConfig = ${workflowConfigStr};
2943
- workflowConfig._project = '${moduleName}';
2944
- let result = null;
2945
-
2946
- ${hasComponents ? `
2947
- // Integrated transaction dengan event lifecycle
2948
- try {
2949
- const eventContext = {
2950
- componentEngine: componentEngine,
2951
- ContextBuilder: ContextBuilder,
2952
- tableName: '${payload.tableName}',
2953
- services: {},
2954
- additionalContext: {
2955
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || req.body.updated_by || 'system',
2956
- options: req.bodyOptions || {},
2957
- requestId: req.id || null,
2958
- // JWT forwarding (Layer 1 RLS): Authorization header dari request asli
2959
- // di-forward ke workflow hook call agar endpoint tujuan yang punya
2960
- // requestScope aktif tetap menerima req.user dengan scope yang sama.
2961
- authHeader: req.headers.authorization || null
2962
- }
2963
- };
2964
-
2965
- try {
2966
- const { resolveServices } = require('@restforgejs/platform/src/utils/service-resolver');
2967
- eventContext.services = resolveServices();
2968
- } catch (e) {
2969
- // Service resolver opsional
2970
- }
2971
-
2972
- result = await ${modelVarName}.changeStatusData(req.body, workflowConfig, eventContext);
2973
- console.log('[INTEGRATED TRANSACTION] CHANGE-STATUS completed successfully with events');
2974
- } catch (error) {
2975
- console.error('[INTEGRATED TRANSACTION] CHANGE-STATUS failed:', error.message);
2976
- throw error;
2977
- }
2978
- ` : `
2979
- // Fallback: tanpa component engine events (tetap forward authHeader untuk hook JWT forwarding)
2980
- try {
2981
- const eventContext = {
2982
- additionalContext: {
2983
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || req.body.updated_by || 'system',
2984
- requestId: req.id || null,
2985
- authHeader: req.headers.authorization || null
2986
- }
2987
- };
2988
- result = await ${modelVarName}.changeStatusData(req.body, workflowConfig, eventContext);
2989
- console.log('[FALLBACK] CHANGE-STATUS completed without component events');
2990
- } catch (error) {
2991
- console.error('[FALLBACK] CHANGE-STATUS failed:', error.message);
2992
- throw error;
2993
- }
2994
- `}
2995
-
2996
- console.log(\`${endpointName} status changed: \${result.workflow.previousStatus} → \${result.workflow.newStatus}\`);
2997
-
2998
- return res.status(200).json({
2999
- success: true,
3000
- message: \`Status changed from \${result.workflow.previousStatus} to \${result.workflow.newStatus}\`,
3001
- data: result.data,
3002
- workflow: result.workflow,
3003
- timestamp: new Date().toISOString()
3004
- });
3005
- } catch (error) {
3006
- console.error('Error saat change-status ${endpointName}:', error);
3007
-
3008
- // Status transition not allowed
3009
- if (error.statusCode === 422 || error.message.includes('Cannot change status') || error.message.includes('No transitions defined')) {
3010
- return res.status(422).json({
3011
- success: false,
3012
- error: 'Status transition not allowed',
3013
- message: error.message,
3014
- timestamp: new Date().toISOString()
3015
- });
3016
- }
3017
-
3018
- // Concurrent modification (optimistic concurrency check via status guard clause)
3019
- if (error.code === 'WORKFLOW_CONCURRENT_MODIFICATION' || error.statusCode === 409) {
3020
- return res.status(409).json({
3021
- success: false,
3022
- error: 'Concurrent modification',
3023
- message: error.message,
3024
- timestamp: new Date().toISOString()
3025
- });
3026
- }
3027
-
3028
- // Validation errors
3029
- if (error.message.includes('is required for') || error.message.includes('is not a valid field')) {
3030
- return res.status(400).json({
3031
- success: false,
3032
- error: 'Validation error',
3033
- message: error.message,
3034
- timestamp: new Date().toISOString()
3035
- });
3036
- }
3037
-
3038
- // Hook execution failed
3039
- if (error.message.includes('hook failed') || error.message.includes('onBefore') || error.message.includes('onAfter')) {
3040
- return res.status(502).json({
3041
- success: false,
3042
- error: 'Workflow hook failed',
3043
- message: error.message,
3044
- timestamp: new Date().toISOString()
3045
- });
3046
- }
3047
-
3048
- // Record not found
3049
- if (error.message === 'Record not found' || error.message.includes('not found')) {
3050
- return res.status(404).json({
3051
- success: false,
3052
- error: 'Data not found',
3053
- message: '${endpointName} data not found',
3054
- timestamp: new Date().toISOString()
3055
- });
3056
- }
3057
-
3058
- // Lock acquisition failed
3059
- if (error.message.includes('Failed to acquire lock')) {
3060
- return res.status(409).json({
3061
- success: false,
3062
- error: 'Resource busy',
3063
- message: error.message,
3064
- timestamp: new Date().toISOString()
3065
- });
3066
- }
3067
-
3068
- return res.status(500).json({
3069
- success: false,
3070
- error: 'Internal Server Error',
3071
- message: 'An error occurred while changing status',
3072
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3073
- timestamp: new Date().toISOString()
3074
- });
3075
- }
3076
- });
3077
-
3078
- `;
3079
- }
3080
-
3081
- // POST /aggregate
3082
- if (payload.action && payload.action.aggregate) {
3083
- const aggregateConfigStr = payload.aggregateConfig ? JSON.stringify(payload.aggregateConfig) : '{}';
3084
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/aggregate - MySQL Aggregate (count, sum, avg, min, max)
3085
- router.post('/aggregate', async (req, res) => {
3086
- try {
3087
- const aggregateConfig = ${aggregateConfigStr};
3088
- const result = await ${modelVarName}.aggregateData(req.body || {}, aggregateConfig);
3089
-
3090
- return res.status(200).json({
3091
- success: true,
3092
- data: result,
3093
- timestamp: new Date().toISOString()
3094
- });
3095
- } catch (error) {
3096
- console.error('Error saat mengagregasi data ${endpointName}:', error);
3097
-
3098
- if (error.statusCode === 403) {
3099
- return res.status(403).json({
3100
- success: false,
3101
- error: 'Pro Feature Required',
3102
- message: error.message,
3103
- upgrade: 'https://restforge.dev/pricing',
3104
- timestamp: new Date().toISOString()
3105
- });
3106
- }
3107
-
3108
- if (error.statusCode === 400 ||
3109
- error.message.includes('not a valid field') ||
3110
- error.message.includes('Invalid aggregate function') ||
3111
- error.message.includes('Invalid alias') ||
3112
- error.message.includes('only allowed with COUNT') ||
3113
- error.message.includes('not defined in aggregate config') ||
3114
- error.message.includes('no join definitions found')) {
3115
- return res.status(400).json({
3116
- success: false,
3117
- error: 'Validation error',
3118
- message: error.message,
3119
- timestamp: new Date().toISOString()
3120
- });
3121
- }
3122
-
3123
- return res.status(500).json({
3124
- success: false,
3125
- error: 'Internal Server Error',
3126
- message: 'An error occurred while aggregating ${endpointName} data',
3127
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3128
- timestamp: new Date().toISOString()
3129
- });
3130
- }
3131
- });
3132
-
3133
- `;
3134
- }
3135
-
3136
- // POST /delete
3137
- if (payload.action && payload.action.delete) {
3138
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/delete - MySQL Delete
3139
- router.post('/delete', async (req, res) => {
3140
- try {
3141
- // Validasi request body
3142
- if (!req.body || Object.keys(req.body).length === 0) {
3143
- return res.status(400).json({
3144
- success: false,
3145
- error: 'Invalid payload',
3146
- message: 'Payload cannot be empty',
3147
- timestamp: new Date().toISOString()
3148
- });
3149
- }
3150
-
3151
- if (!req.body.where) {
3152
- return res.status(400).json({
3153
- success: false,
3154
- error: 'Missing required field',
3155
- message: 'Invalid request format: where parameter is required',
3156
- example: {
3157
- "where": [{ "key": "id", "value": "your-id-value" }]
3158
- },
3159
- timestamp: new Date().toISOString()
3160
- });
3161
- }
3162
-
3163
- // Validasi format where
3164
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
3165
- return res.status(400).json({
3166
- success: false,
3167
- error: 'Invalid where format',
3168
- message: 'Invalid where format',
3169
- example: {
3170
- "where": [
3171
- { "key": "id", "value": "your-id-value" }
3172
- ]
3173
- },
3174
- timestamp: new Date().toISOString()
3175
- });
3176
- }
3177
-
3178
- let responseData = null;
3179
-
3180
- // Cek apakah data exist sebelum delete dan ambil old data untuk event lifecycle
3181
- // Menggunakan SELECT * dari tabel utama (tanpa explicit select) karena fieldName
3182
- // bisa mengandung kolom dari JOIN (mis. city_name) yang tidak ada di tabel utama
3183
- if (req.body.where && Array.isArray(req.body.where) && req.body.where.length > 0) {
3184
- const firstCondition = req.body.where[0];
3185
- try {
3186
- const existingData = await ${modelVarName}.getData({
3187
- where: [{ key: firstCondition.key, value: firstCondition.value }]
3188
- });
3189
-
3190
- if (!existingData.success || !existingData.data || existingData.data.length === 0) {
3191
- return res.status(404).json({
3192
- success: false,
3193
- error: 'Data not found',
3194
- message: '${endpointName} data not found',
3195
- timestamp: new Date().toISOString()
3196
- });
3197
- }
3198
- } catch (checkError) {
3199
- return res.status(500).json({
3200
- success: false,
3201
- error: 'Verification Failed',
3202
- message: 'Could not verify data existence before delete',
3203
- details: process.env.NODE_ENV === 'development' ? checkError.message : undefined,
3204
- timestamp: new Date().toISOString()
3205
- });
3206
- }
3207
- }
3208
-
3209
- ${hasComponents ? `
3210
- // Integrated transaction dengan event lifecycle
3211
- try {
3212
- const eventContext = {
3213
- componentEngine: componentEngine,
3214
- ContextBuilder: ContextBuilder,
3215
- tableName: '${payload.tableName}',
3216
- additionalContext: {
3217
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
3218
- options: req.bodyOptions || {}
3219
- }
3220
- };
3221
- responseData = await ${modelVarName}.deleteData(req.body, eventContext);
3222
- console.log('[INTEGRATED TRANSACTION] DELETE completed successfully with events');
3223
- } catch (error) {
3224
- console.error('[INTEGRATED TRANSACTION] DELETE failed:', error.message);
3225
- throw error;
3226
- }
3227
- ` : `
3228
- // Fallback: mode tanpa events
3229
- try {
3230
- responseData = await ${modelVarName}.deleteData(req.body, { additionalContext: { requestId: req.id || null } });
3231
- console.log('[FALLBACK] DELETE completed without events');
3232
- } catch (error) {
3233
- console.error('[FALLBACK] DELETE failed:', error.message);
3234
- throw error;
3235
- }
3236
- `}
3237
- // Log successful operation
3238
- console.log(\`${endpointName} data deleted successfully\`);
3239
-
3240
- return res.json({
3241
- ...responseData,
3242
- timestamp: new Date().toISOString()
3243
- });
3244
- } catch (error) {
3245
- console.error('Error saat menghapus data ${endpointName}:', error);
3246
-
3247
- if (error.code === 'ER_ROW_IS_REFERENCED_2' || error.errno === 1451) {
3248
- return res.status(409).json({
3249
- success: false,
3250
- error: 'Foreign key constraint',
3251
- message: 'Cannot delete: record is still referenced by other data',
3252
- timestamp: new Date().toISOString()
3253
- });
3254
- }
3255
-
3256
- return res.status(500).json({
3257
- success: false,
3258
- error: 'Internal Server Error',
3259
- message: 'An error occurred while deleting ${endpointName} data',
3260
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3261
- timestamp: new Date().toISOString()
3262
- });
3263
- }
3264
- });
3265
-
3266
- `;
3267
- }
3268
-
3269
- // POST /first
3270
- if (payload.action && payload.action.first) {
3271
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/first - Mendapatkan data berdasarkan kriteria
3272
- router.post('/first', async (req, res) => {
3273
- try {
3274
- // Normalize: array 1 elemen → object (backward compatible)
3275
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
3276
- req.body.where = req.body.where[0];
3277
- }
3278
-
3279
- // Validasi where clause — harus object tunggal {key, value}
3280
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
3281
- return res.status(400).json({
3282
- success: false,
3283
- error: 'Missing required field',
3284
- message: 'Property where is required as {key, value} object',
3285
- example: {
3286
- "where": { "key": "${primaryKey}", "value": "your-id-value" },
3287
- "select": ["field1", "field2"]
3288
- },
3289
- timestamp: new Date().toISOString()
3290
- });
3291
- }
3292
-
3293
- // Validasi where.key dan where.value
3294
- if (!req.body.where.key || req.body.where.value === undefined || req.body.where.value === null || req.body.where.value === '') {
3295
- return res.status(400).json({
3296
- success: false,
3297
- error: 'Invalid where format',
3298
- message: 'Where key and value are required',
3299
- example: {
3300
- "where": { "key": "${primaryKey}", "value": "your-id-value" }
3301
- },
3302
- timestamp: new Date().toISOString()
3303
- });
3304
- }
3305
-
3306
- // Tolak format advanced (conditions/logic)
3307
- if (req.body.where.conditions || req.body.where.logic) {
3308
- return res.status(400).json({
3309
- success: false,
3310
- error: 'Invalid where format',
3311
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
3312
- timestamp: new Date().toISOString()
3313
- });
3314
- }
3315
-
3316
- // Validasi where.key ada di validFields
3317
- const validFields = ${JSON.stringify(payload.fieldName)};
3318
- if (!validFields.includes(req.body.where.key)) {
3319
- return res.status(400).json({
3320
- success: false,
3321
- error: 'Invalid where field',
3322
- message: \`Invalid field: \${req.body.where.key}\`,
3323
- validFields: validFields,
3324
- timestamp: new Date().toISOString()
3325
- });
3326
- }
3327
-
3328
- // Validasi select fields jika ada
3329
- if (req.body.select && Array.isArray(req.body.select)) {
3330
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
3331
-
3332
- if (invalidFields.length > 0) {
3333
- return res.status(400).json({
3334
- success: false,
3335
- error: 'Invalid select fields',
3336
- message: \`Invalid field(s): \${invalidFields.join(', ')}\`,
3337
- validFields: validFields,
3338
- timestamp: new Date().toISOString()
3339
- });
3340
- }
3341
- }
3342
-
3343
- // Convert ke array format untuk kompatibilitas dengan model.getData() → buildComplexWhereClause()
3344
- const getPayload = {
3345
- where: [{ key: req.body.where.key, value: req.body.where.value }],
3346
- select: req.body.select
3347
- };
3348
- const result = await ${modelVarName}.getData(getPayload);
3349
- ${requestScopeOwnershipCheckPostFetch}
3350
- return res.json({
3351
- ...result,
3352
- timestamp: new Date().toISOString()
3353
- });
3354
- } catch (error) {
3355
- console.error('Error saat mendapatkan data ${endpointName}:', error);
3356
- return res.status(500).json({
3357
- success: false,
3358
- error: 'Internal Server Error',
3359
- message: 'An error occurred while fetching ${endpointName} data',
3360
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3361
- timestamp: new Date().toISOString()
3362
- });
3363
- }
3364
- });
3365
-
3366
- `;
3367
- }
3368
-
3369
- // POST /read
3370
- if (payload.action && payload.action.read) {
3371
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read - Manual pagination endpoint
3372
- router.post('/read', async (req, res) => {
3373
- try {
3374
- // Deteksi mode: paginasi (page dikirim) atau non-paginasi (page tidak dikirim)
3375
- const paginate = req.body.page !== undefined;
3376
- const page = paginate ? parseInt(req.body.page, 10) : null;
3377
- const perPage = paginate ? Math.min(parseInt(req.body.per_page || 10, 10), 100) : null;
3378
- const limit = !paginate ? Math.min(Math.max(parseInt(req.body.limit || 1000, 10), 1), 5000) : null;
3379
- const searchValue = req.body.search_value || '';
3380
- const searchBy = req.body.search_by || 'all';
3381
-
3382
- // Parse sort_columns
3383
- let sort_columns = [];
3384
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
3385
- sort_columns = req.body.sort_columns.map(item => ({
3386
- column: item.column,
3387
- direction: (item.direction || 'ASC').toUpperCase()
3388
- }));
3389
- }
3390
-
3391
- // Validasi parameter paginasi (hanya jika mode paginasi)
3392
- if (paginate && page < 1) {
3393
- return res.status(400).json({
3394
- success: false,
3395
- error: 'Invalid page',
3396
- message: 'Page must be greater than 0',
3397
- timestamp: new Date().toISOString()
3398
- });
3399
- }
3400
-
3401
- // Proses parameter where dengan format advanced conditions
3402
- let where = null;
3403
- if (req.body.where && typeof req.body.where === 'object') {
3404
- if (Array.isArray(req.body.where) || (req.body.where.conditions && Array.isArray(req.body.where.conditions))) {
3405
- where = req.body.where;
3406
- }
3407
- }
3408
-
3409
- // Proses parameter select untuk kolom selektif
3410
- const validFields = ${JSON.stringify(payload.fieldName || [])};
3411
- let select = null;
3412
- if (req.body.select && Array.isArray(req.body.select)) {
3413
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
3414
- if (invalidFields.length > 0) {
3415
- return res.status(400).json({
3416
- success: false,
3417
- error: 'Invalid select fields',
3418
- message: 'Invalid field(s): ' + invalidFields.join(', '),
3419
- validFields: validFields,
3420
- timestamp: new Date().toISOString()
3421
- });
3422
- }
3423
- select = req.body.select;
3424
- }
3425
-
3426
- const options = {
3427
- searchValue,
3428
- searchBy,
3429
- sort_columns,
3430
- where: where,
3431
- select: select
3432
- };
3433
-
3434
- if (paginate) {
3435
- options.page = page;
3436
- options.perPage = perPage;
3437
- } else {
3438
- options.limit = limit;
3439
- }
3440
-
3441
- const result = await ${modelVarName}.getList(options);
3442
-
3443
- // Format response berdasarkan mode
3444
- if (paginate) {
3445
- return res.json({
3446
- success: true,
3447
- data: result.data,
3448
- count: result.data ? result.data.length : 0,
3449
- pagination: result.pagination,
3450
- message: 'Data retrieved successfully'
3451
- });
3452
- } else {
3453
- return res.json({
3454
- success: true,
3455
- data: result.data,
3456
- count: result.data ? result.data.length : 0
3457
- });
3458
- }
3459
- } catch (error) {
3460
- console.error('Error in ${endpointName} list:', error);
3461
- const statusCode = error.statusCode || 500;
3462
- return res.status(statusCode).json({
3463
- success: false,
3464
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
3465
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} list data',
3466
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3467
- timestamp: new Date().toISOString()
3468
- });
3469
- }
3470
- });
3471
-
3472
- `;
3473
- }
3474
-
3475
- // Composite endpoints (master-detail)
3476
- if (payload.masterDetail && payload.masterDetail.enabled) {
3477
- const _detailTableName = payload.masterDetail.detailTable.split('.').pop();
3478
- const _rootKey = endpointName.replace(/-/g, '_');
3479
- const _headerCalc = payload.masterDetail.headerCalculations || null;
3480
- // Fallback: autoCalculateFields.formula → headerCalculations.total_qty.source → empty (skip calculation)
3481
- const _amountFormula = payload.masterDetail?.detailConfig?.autoCalculateFields?.total_amount?.formula || '';
3482
- const _amountFormulaParts = _amountFormula.split('*').map(s => s.trim());
3483
- const _headerCalcQtyField = _headerCalc?.total_qty?.source?.replace('items.', '') || '';
3484
- const _amountQtyField = _amountFormulaParts[0] || _headerCalcQtyField;
3485
- const _amountPriceField = _amountFormulaParts[1] || 'unit_price';
3486
-
3487
- // createComposite endpoint
3488
- if (payload.action && payload.action.createComposite) {
3489
- const _headerCalcCode = _headerCalc ? `
3490
- var headerCalc = ${JSON.stringify(_headerCalc)};
3491
- if (headerCalc.total_items) {
3492
- data.total_items = data.${_detailTableName}.length;
3493
- }
3494
- if (headerCalc.total_qty && headerCalc.total_qty.source) {
3495
- var qtyField = headerCalc.total_qty.source.replace('items.', '');
3496
- data.total_qty = data.${_detailTableName}.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
3497
- }
3498
- ${_amountQtyField ? `if (headerCalc.total_amount) {
3499
- data.total_amount = data.${_detailTableName}.reduce(function(sum, item) {
3500
- var qty = Number(item.${_amountQtyField}) || 0;
3501
- var price = Number(item.${_amountPriceField}) || 0;
3502
- return sum + (qty * price);
3503
- }, 0);
3504
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
3505
- console.log('Calculated totals:', { total_items: data.total_items, total_qty: data.total_qty, total_amount: data.total_amount });
3506
- ` : '';
3507
-
3508
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create-composite - MySQL Composite create (master-detail)
3509
- router.post('/create-composite', async (req, res) => {
3510
- try {
3511
- console.log('Request body ${endpointName}/create-composite:', JSON.stringify(req.body, null, 2));
3512
-
3513
- if (!req.body || !req.body.${_rootKey}) {
3514
- return res.status(400).json({
3515
- success: false,
3516
- error: 'Invalid payload',
3517
- message: 'Payload must have property "${_rootKey}"',
3518
- timestamp: new Date().toISOString()
3519
- });
3520
- }
3521
-
3522
- var data = req.body.${_rootKey};
3523
-
3524
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3525
- if (typeof ${modelVarName}.validateData === 'function') {
3526
- var headerDataForValidation = Object.assign({}, data);
3527
- delete headerDataForValidation.${_detailTableName};
3528
-
3529
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'insert');
3530
- if (!validation.isValid) {
3531
- return res.status(400).json({
3532
- success: false,
3533
- error: 'Validation failed',
3534
- message: 'Invalid data',
3535
- errors: validation.errors,
3536
- timestamp: new Date().toISOString()
3537
- });
3538
- }
3539
- Object.assign(data, validation.sanitizedData);
3540
- }
3541
-
3542
- if (!data.${_detailTableName} || !Array.isArray(data.${_detailTableName}) || data.${_detailTableName}.length === 0) {
3543
- return res.status(400).json({
3544
- success: false,
3545
- error: 'Invalid payload',
3546
- message: 'Property "${_detailTableName}" must be a non-empty array',
3547
- timestamp: new Date().toISOString()
3548
- });
3549
- }
3550
-
3551
- ${_headerCalcCode}
3552
-
3553
- // Build eventContext untuk composite hooks
3554
- var eventContext = null;
3555
- if (componentEngine && ContextBuilder) {
3556
- eventContext = {
3557
- componentEngine: componentEngine,
3558
- ContextBuilder: ContextBuilder,
3559
- tableName: '${payload.tableName}',
3560
- additionalContext: {
3561
- detailTable: '${payload.masterDetail.detailTable}',
3562
- foreignKey: '${payload.masterDetail.foreignKey}',
3563
- options: req.bodyOptions || {},
3564
- requestId: req.id || null
3565
- }
3566
- };
3567
- }
3568
-
3569
- var result = await ${modelVarName}.createComposite(data, eventContext);
3570
-
3571
- console.log('${endpointName} composite create successful');
3572
-
3573
- return res.status(201).json({
3574
- success: true,
3575
- message: '${endpointName} data successfully created (with detail items)',
3576
- data: result,
3577
- timestamp: new Date().toISOString()
3578
- });
3579
- } catch (error) {
3580
- console.error('Error saat composite create ${endpointName}:', error);
3581
-
3582
- if (error.errno === 1062) {
3583
- return res.status(409).json({
3584
- success: false,
3585
- error: 'Duplicate entry',
3586
- message: 'A record with this value already exists',
3587
- timestamp: new Date().toISOString()
3588
- });
3589
- }
3590
- if (error.errno === 1452) {
3591
- return res.status(400).json({
3592
- success: false,
3593
- error: 'Foreign key constraint',
3594
- message: 'Referenced data not found',
3595
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3596
- timestamp: new Date().toISOString()
3597
- });
3598
- }
3599
-
3600
- return res.status(500).json({
3601
- success: false,
3602
- error: 'Internal Server Error',
3603
- message: 'An error occurred while creating ${endpointName} data',
3604
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3605
- timestamp: new Date().toISOString()
3606
- });
3607
- }
3608
- });
3609
-
3610
- `;
3611
- }
3612
-
3613
- // updateComposite endpoint
3614
- if (payload.action && payload.action.updateComposite) {
3615
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update-composite - MySQL Composite update (master-detail)
3616
- router.post('/update-composite', async (req, res) => {
3617
- try {
3618
- console.log('Request body ${endpointName}/update-composite:', JSON.stringify(req.body, null, 2));
3619
-
3620
- if (!req.body || !req.body.${_rootKey}) {
3621
- return res.status(400).json({
3622
- success: false,
3623
- error: 'Invalid payload',
3624
- message: 'Payload must have property "${_rootKey}"',
3625
- timestamp: new Date().toISOString()
3626
- });
3627
- }
3628
-
3629
- var data = req.body.${_rootKey};
3630
-
3631
- if (!data.${primaryKey}) {
3632
- return res.status(400).json({
3633
- success: false,
3634
- error: 'Missing required field',
3635
- message: 'Primary key (${primaryKey}) is required for update',
3636
- timestamp: new Date().toISOString()
3637
- });
3638
- }
3639
- ${requestScopeOwnershipCheckMaster}
3640
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3641
- if (typeof ${modelVarName}.validateData === 'function') {
3642
- var headerDataForValidation = Object.assign({}, data);
3643
- delete headerDataForValidation.${_detailTableName};
3644
-
3645
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'update');
3646
- if (!validation.isValid) {
3647
- return res.status(400).json({
3648
- success: false,
3649
- error: 'Validation failed',
3650
- message: 'Invalid data',
3651
- errors: validation.errors,
3652
- timestamp: new Date().toISOString()
3653
- });
3654
- }
3655
- Object.assign(data, validation.sanitizedData);
3656
- }
3657
-
3658
- if (data.${_detailTableName}) {
3659
- if (typeof data.${_detailTableName} !== 'object' || Array.isArray(data.${_detailTableName})) {
3660
- return res.status(400).json({
3661
- success: false,
3662
- error: 'Invalid payload',
3663
- message: 'Property "${_detailTableName}" must be an object with structure {insert: [], update: [], delete: []}',
3664
- timestamp: new Date().toISOString()
3665
- });
3666
- }
3667
- }
3668
-
3669
- // Build eventContext untuk composite hooks
3670
- var eventContext = null;
3671
- if (componentEngine && ContextBuilder) {
3672
- eventContext = {
3673
- componentEngine: componentEngine,
3674
- ContextBuilder: ContextBuilder,
3675
- tableName: '${payload.tableName}',
3676
- additionalContext: {
3677
- detailTable: '${payload.masterDetail.detailTable}',
3678
- foreignKey: '${payload.masterDetail.foreignKey}',
3679
- options: req.bodyOptions || {},
3680
- requestId: req.id || null
3681
- }
3682
- };
3683
- }
3684
-
3685
- var result = await ${modelVarName}.updateComposite(data, eventContext);
3686
-
3687
- console.log('${endpointName} composite update successful: ${primaryKey}=' + data.${primaryKey});
3688
-
3689
- return res.status(200).json({
3690
- success: true,
3691
- message: '${endpointName} data successfully updated (with detail items)',
3692
- data: result,
3693
- timestamp: new Date().toISOString()
3694
- });
3695
- } catch (error) {
3696
- console.error('Error saat composite update ${endpointName}:', error);
3697
-
3698
- if (error.message === 'Record not found' || error.message.includes('not found')) {
3699
- return res.status(404).json({
3700
- success: false,
3701
- error: 'Data not found',
3702
- message: '${endpointName} data not found',
3703
- timestamp: new Date().toISOString()
3704
- });
3705
- }
3706
- if (error.errno === 1062) {
3707
- return res.status(409).json({ success: false, error: 'Duplicate entry', message: 'A record with this value already exists', timestamp: new Date().toISOString() });
3708
- }
3709
- if (error.errno === 1452) {
3710
- return res.status(400).json({ success: false, error: 'Foreign key constraint', message: 'Referenced data not found', details: process.env.NODE_ENV === 'development' ? error.message : undefined, timestamp: new Date().toISOString() });
3711
- }
3712
-
3713
- return res.status(500).json({
3714
- success: false,
3715
- error: 'Internal Server Error',
3716
- message: 'An error occurred while updating ${endpointName} data',
3717
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3718
- timestamp: new Date().toISOString()
3719
- });
3720
- }
3721
- });
3722
-
3723
- `;
3724
- }
3725
-
3726
- // readComposite endpoint
3727
- if (payload.action && payload.action.readComposite) {
3728
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read-composite - MySQL Read header with detail items
3729
- router.post('/read-composite', async (req, res) => {
3730
- try {
3731
- console.log('Request body ${endpointName}/read-composite:', JSON.stringify(req.body, null, 2));
3732
-
3733
- if (!req.body || Object.keys(req.body).length === 0) {
3734
- return res.status(400).json({ success: false, error: 'Invalid payload', message: 'Payload cannot be empty', timestamp: new Date().toISOString() });
3735
- }
3736
-
3737
- if (!req.body.where) {
3738
- return res.status(400).json({
3739
- success: false,
3740
- error: 'Missing required field',
3741
- message: 'Property where is required',
3742
- example: { "where": [{ "key": "field_name", "value": "field_value" }] },
3743
- timestamp: new Date().toISOString()
3744
- });
3745
- }
3746
-
3747
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
3748
- return res.status(400).json({
3749
- success: false,
3750
- error: 'Invalid where format',
3751
- message: 'Invalid where format',
3752
- example: { "where": [{ "key": "${primaryKey}", "value": "your-id-value" }] },
3753
- timestamp: new Date().toISOString()
3754
- });
3755
- }
3756
-
3757
- var result = await ${modelVarName}.readComposite(req.body);
3758
-
3759
- console.log('${endpointName} composite read successful: ' + (result.count || 0) + ' record(s)');
3760
-
3761
- return res.json({
3762
- ...result,
3763
- timestamp: new Date().toISOString()
3764
- });
3765
- } catch (error) {
3766
- console.error('Error saat composite read ${endpointName}:', error);
3767
- return res.status(500).json({
3768
- success: false,
3769
- error: 'Internal Server Error',
3770
- message: 'An error occurred while reading ${endpointName} composite data',
3771
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3772
- timestamp: new Date().toISOString()
3773
- });
3774
- }
3775
- });
3776
-
3777
- `;
3778
- }
3779
- }
3780
-
3781
- // GET /info — self-documenting API
3782
- if (payload.action?.info !== false) {
3783
- const infoActions = {
3784
- datatables: !!(payload.action?.datatables),
3785
- read: !!(payload.action?.read),
3786
- first: !!(payload.action?.first),
3787
- create: !!(payload.action?.create),
3788
- update: !!(payload.action?.update),
3789
- delete: !!(payload.action?.delete),
3790
- lookup: !!(payload.action?.lookup),
3791
- export: !!(payload.action?.export),
3792
- import: !!(payload.action?.import),
3793
- info: payload.action?.info !== false
3794
- };
3795
-
3796
- subModuleContent += `// MySQL endpoint information — self-documenting API
3797
- router.get('/info', async (req, res) => {
3798
- try {
3799
- const actions = ${JSON.stringify(infoActions)};
3800
- const modelInfo = await ${modelVarName}.getModelInfo(actions);
3801
-
3802
- res.json({
3803
- success: true,
3804
- endpoint: '${endpointName}',
3805
- module: '${moduleName}',
3806
- table: modelInfo.table,
3807
- fields: modelInfo.fields,
3808
- querySources: modelInfo.querySources,
3809
- actions: actions,
3810
- databaseType: 'mysql',
3811
- generated: '${timestamp}',
3812
- timestamp: new Date().toISOString()
3813
- });
3814
- } catch (error) {
3815
- console.error('MySQL info error:', error);
3816
- res.status(500).json({
3817
- success: false,
3818
- error: 'Info Error',
3819
- message: 'An error occurred while fetching endpoint info',
3820
- timestamp: new Date().toISOString()
3821
- });
3822
- }
3823
- });`;
3824
- } else {
3825
- subModuleContent += `// Endpoint /info dinonaktifkan via action.info = false\n`;
3826
- }
3827
-
3828
- subModuleContent += `
3829
- // MySQL health check
3830
- router.get('/health', async (req, res) => {
3831
- try {
3832
- const connectionInfo = await ${modelVarName}.getConnectionInfo();
3833
-
3834
- res.json({
3835
- status: connectionInfo ? 'healthy' : 'unknown',
3836
- endpoint: '${endpointName}',
3837
- database: 'mysql',
3838
- connection: connectionInfo ? 'active' : 'unknown',
3839
- timestamp: new Date().toISOString()
3840
- });
3841
- } catch (error) {
3842
- res.status(503).json({
3843
- status: 'unhealthy',
3844
- endpoint: '${endpointName}',
3845
- database: 'mysql',
3846
- error: error.message,
3847
- timestamp: new Date().toISOString()
3848
- });
3849
- }
3850
- });
3851
-
3852
- `;
3853
-
3854
- subModuleContent += `module.exports = router;`;
3855
-
3856
- return subModuleContent;
3857
- }
3858
-
3859
- module.exports = {
3860
- createMysqlMainModuleTemplate,
3861
- createMysqlModelTemplate,
3862
- createMysqlSubmoduleTemplate
3863
- };
1
+ const a0_0x53db4a=a0_0x12be;(function(_0x24971c,_0x28143c){const _0xb2518a=a0_0x12be,_0x5c4033=_0x24971c();while(!![]){try{const _0x27d8a7=parseInt(_0xb2518a(0x10d))/0x1+parseInt(_0xb2518a(0x132))/0x2+-parseInt(_0xb2518a(0x163))/0x3*(-parseInt(_0xb2518a(0x11e))/0x4)+-parseInt(_0xb2518a(0x1ef))/0x5+parseInt(_0xb2518a(0x11a))/0x6*(parseInt(_0xb2518a(0x1d4))/0x7)+parseInt(_0xb2518a(0x200))/0x8+parseInt(_0xb2518a(0x138))/0x9*(-parseInt(_0xb2518a(0x16e))/0xa);if(_0x27d8a7===_0x28143c)break;else _0x5c4033['push'](_0x5c4033['shift']());}catch(_0x5f5b12){_0x5c4033['push'](_0x5c4033['shift']());}}}(a0_0x8163,0x2e41f));function toCamelCase(_0x449536){const _0x44fdd3=a0_0x12be,_0x4da124={'ekADa':function(_0x6b4c30,_0x4589cc){return _0x6b4c30!==_0x4589cc;}};if(!_0x449536||_0x4da124['ekADa'](typeof _0x449536,'string'))return'';return _0x449536['replace'](/(?:^\w|[A-Z]|\b\w)/g,(_0x173686,_0x429d59)=>{const _0xd5442b=a0_0x12be;return _0x429d59===0x0?_0x173686[_0xd5442b(0x21a)]():_0x173686['toUpperCase']();})[_0x44fdd3(0x1c0)](/\s+/g,'')[_0x44fdd3(0x1c0)](/[-_]/g,'');}function a0_0x8163(){const _0x205206=['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','jZSkicb0AgLZlNjLywrtB3vYy2uGpsaN','AM9PBG','Aw1WB3j0q29UzMLN','zgv0ywLSvgfIBgu','lMDLDe1VzgvSsw5MBYHHy3rPB25ZktSkcIaGicbYzxmUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGigvUzhbVAw50oIaN','zgvMyxvSDfnJB3bL','zgf0zxrPBwu','yMvVEuS','zw5HyMXLza','cIaGicaVlYbgywXSyMfJAZOGBw9Kzsb0yw5WysbLDMvUDhmkicaGihrYEsb7cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','zMLLBgrqB2XPy3K','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicbJB25ZDcb2ywXPzgf0Aw9Uid0GyxDHAxqG','uKHnAxq','cIOGrgf0ywjHC2u6ie15u1fmcIOVcGOVlYbqCMLTyxj5igTLEsb1BNr1AYbLBMrWB2LUDcbPBMKky29UC3qGChjPBwfYEuTLEsa9icC','icbSzxqGyMfZzvf1zxj5id0GyaOGicaG','igrHDgeGBM90igzVDw5KjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbmB2nRigfJCxvPC2L0Aw9UigzHAwXLzaOGicaGAwyGkgvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj0zHAwXLzcb0BYbHy3f1AxjLigXVy2SNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdKPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNuMvZB3vYy2uGyNvZEsCScIaGicaGicaGBwvZC2fNztOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGy2HHBMDPBMCGC3rHDhvZjYWkicaGicaGzgv0ywLSCZOGChjVy2vZCY5LBNyUtK9erv9ftLyGpt09icDKzxzLBg9WBwvUDcCGpYbLCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','n0vNC3fhzG','zMLYC3q','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbdsefor0uTu1rbvfvtignVBxbSzxrLzcb3AxrOB3v0ignVBxbVBMvUDcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TgquXmqKfds10Gq0HbtKDflvnuqvrvuYbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','lMDLDeXVB2T1CerHDgfxAxrOrMLSDgvYkhjLCs5IB2r5ktSkicaGih0GzwXZzsb7cIaGicaGic8VieXLz2fJEsbMB3jTyxqGzgvUz2fUihnLBgvJDgvKx3rHzWOGicaGicbJB25ZDcbZzwXLy3rLzfrHzYa9ihjLCs5IB2r5lNnLBgvJDgvKx3rHzYb8FcaNjZSkicaGicaGBgLZDca9igf3ywL0ia','tw9KzwWGzxH0zw5KCYbcyxnLtw9KzwWGEWOVkIOkicOGq29UC3rYDwn0B3ikicOVcMnVBNn0CNvJDg9YkcKGEWOGignVBNn0ihzHBgLKrMLLBgrZid0GwWOGicaG','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih0ScIaGicaGicaGicaIC2vSzwn0iJOGwYjMAwvSzdeIlcaIzMLLBgqYiL0kicaGicaGicb9laOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLlMTLEsbKyw4GD2HLCMuUDMfSDwukicaGigLMicGHCMvXlMjVzhKUD2HLCMuUA2v5ihX8ihjLCs5IB2r5lNDOzxjLlNzHBhvLid09psb1BMrLzMLUzwqGFhWGCMvXlMjVzhKUD2HLCMuUDMfSDwuGpt09ig51BgWGFhWGCMvXlMjVzhKUD2HLCMuUDMfSDwuGpt09icCNksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGBwvZC2fNztOGj1DOzxjLigTLEsbHBMqGDMfSDwuGyxjLihjLCxvPCMvKjYWkicaGicaGicbLEgfTCgXLoIb7cIaGicaGicaGicaID2HLCMuIoIb7icjRzxKIoIaI','laOGicaGz2vUzxjHDgvKoIaN','zxHWB3j0CW','l2fKANvZDcaTie15u1fmiefKANvZDcaOyxrVBwLJigLUy3jLBwvUDc9KzwnYzw1LBNqPcNjVDxrLCI5WB3n0kcCVywrQDxn0jYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGic8VifzHBgLKyxnPihbHEwXVywqkicaGigLMicGHCMvXlMjVzhKGFhWGt2jQzwn0lMTLExmOCMvXlMjVzhKPlMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNugf5Bg9HzcbJyw5UB3qGyMuGzw1WDhKNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihbYAw1HCNKGA2v5cIaGicbJB25ZDcbWCMLTyxj5s2v5id0GjW','ie9srevsiejzia','cIaGicb9ksK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUie15u1fmigDLDeXVB2T1CerHDgfeEw5HBwLJoICSigvYCM9YktSkicaGihrOCM93igvYCM9YoWOGih0kFqOklYOQcIaQie92zxjYAwrLigDLDfn0yxrPy0XVB2T1CerHDgeGDw50DwSGtxLtuuWkicOVcMfZEw5JigDLDfn0yxrPy0XVB2T1CerHDgeOC2vSzwn0zwruywCPihSkicb0CNKGEWOGicaGlY8Gq2HLy2SGy2fJAguGzMLYC3qGlsbJywnOzsb0yw5WysbZzwXLy3rLzfrHzYbRyxjLBMeGzgf0ysbZyw1HcIaGicbJB25ZDcbJywnOzu9WDgLVBNmGpsb7ihr5Cgu6icDZDgf0AwmNih07cIaGicbJB25ZDcbJywnOzwrszxn1BhqGpsbHD2fPDcb0AgLZlMDLDenHy2HLzeXVB2T1CcHJywnOzu9WDgLVBNmSicDZDgf0AwmNktSkicaGigLMicHJywnOzwrszxn1BhqPihSkicaGicaGlY8GqxbWBhKGC2vSzwn0zwruywCGDg8Gy2fJAgvKihjLC3vSDaOGicaGicbYzxr1CM4Gy2fJAgvKuMvZDwX0lM1HCcHPDgvTid0+ihSkicaGicaGicbPzIaOAxrLBs5Pzca9pt0GC2vSzwn0zwruywCPihSkicaGicaGicaGihjLDhvYBIb7ic4UlML0zw0SihnLBgvJDgvKoIaNDhj1zsCGFtSkicaGicaGicb9cIaGicaGicaGCMv0DxjUihSGAwq6igL0zw0UAwqSihrLEhq6igL0zw0UDgv4Dcb9oWOGicaGicb9ktSkicaGih0kcIaGicbJB25ZDcbXDwvYEsa9igbtruXfq1qG','jZSkicb0AgLZlNDYAxrLu291CMnLid0GjW','Bw9KDwXLlMv4Cg9YDhmGpsbYB3v0zxi7','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGzgv0ywLSvgfIBgu6icC','cIaGicb9','BNvSBa','ieforca','cIaGicaVlYbwywXPzgfZAsbKyxrHigrLBMDHBIbTB2rLBcbQAwTHihrLCNnLzgLHcIaGicbPzIaODhLWzw9Mia','ihX8icDUzxCGCMvJB3jKj31GktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdePlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','qundCw8','igrHDgeGC3vJy2vZC2z1BgX5ihvWzgf0zwqGkhDPDgGGzgv0ywLSigL0zw1ZksCScIaGicaGigrHDge6ihjLC3vSDcWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbZywf0ignVBxbVC2L0zsb1CgrHDguG','jYWGDMfSDwu6ihnJB3bLvMfSDwuGFtSkcIaGlY8GuKvbrcbVCgvYyxrPB25ZoIbPBMPLy3qGC2nVCguGAw50BYbYzxeUyM9KEs53AgvYzqOGigLMicHBj2rHDgf0ywjSzxmNlcaNCMvHzcCSicDSB29RDxaNlcaNCMvHzc1JB21WB3nPDguNxs5PBMnSDwrLCYHLBMrWB2LUDcKPihSkicaGigLMicGHCMvXlMjVzhKUD2HLCMuPihSkicaGicaGCMvXlMjVzhKUD2HLCMuGpsbBC2nVCgvdB25KAxrPB25DoWOGicaGFsbLBhnLigLMicHbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxeUyM9KEs53AgvYzs51BNnOAwz0khnJB3bLq29UzgL0Aw9UktSkicaGih0GzwXZzsbPzIaOCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCYaMjIbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLlMnVBMrPDgLVBNmPksb7cIaGicaGignVBNn0ig9YAwDPBMfStg9NAwmGpsbYzxeUyM9KEs53AgvYzs5SB2DPyYb8FcaNqu5ejZSkicaGicaGAwyGkg9YAwDPBMfStg9NAwmGpt09icDbtKqNksb7cIaGicaGicaGCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCY51BNnOAwz0khnJB3bLq29UzgL0Aw9UktSkicaGicaGFsbLBhnLihSkicaGicaGicbYzxeUyM9KEs53AgvYzsa9ihSkicaGicaGicaGigXVz2LJoIaNqu5ejYWkicaGicaGicaGignVBMrPDgLVBNm6ifSkicaGicaGicaGicaGC2nVCgvdB25KAxrPB24ScIaGicaGicaGicaGihSGBg9NAwm6ig9YAwDPBMfStg9NAwmSignVBMrPDgLVBNm6ihjLCs5IB2r5lNDOzxjLlMnVBMrPDgLVBNmGFqOGicaGicaGicaGxqOGicaGicaGih07cIaGicaGih0kicaGih0kicb9cGOGic8VierftevurtOGChjLCgvUzcbZy29WzsbJB25KAxrPB24GDg8GD2HLCMuGyxjYyxKkicbPzIaOzw5KCg9PBNqGpt09icDKzwXLDguNksb7cIaGicbPzIaOCMvXlMjVzhKUD2HLCMuPihSkicaGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPksb7cIaGicaGicaGCMvXlMjVzhKUD2HLCMuUDw5ZAgLMDcHZy29WzunVBMrPDgLVBIK7cIaGicaGih0kicaGih0kicb9cGOGic8ViensrufursaVieferdOGzM9Yy2uGC2nVCguGy29SDw1UihzHBhvLcIaGAwyGkgvUzhbVAw50id09psaNywrKjYb8FcbLBMrWB2LUDca9pt0Gj2nYzwf0zsCPihSkicaGihjLCs5IB2r5wYC','DgLTzq','lMDLDerHDgeOEWOGicaGicaGihDOzxjLoIbBcIaGicaGicaGicb7igTLEtOGChjPBwfYEuTLEsWGDMfSDwu6ihjLCs5IB2r5w3bYAw1HCNLlzxLDih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGjW','cIaGlY8GrMLLBgqGDMfSAwrHDgLVBIbJB25MAwD1CMf0Aw9UcG','CMvXDwLYzwrgAwvSzhm','lNvWzgf0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4Da','ndi2nZmWt1vNBuzM','oWOGigLMicHIExbHC3nsB2XLCY5Szw5NDgGGpIaWicyMigj5CgfZC1jVBgvZlNnVBwuOCM9Szsa9pIb1C2vYuM9SzxmUAw5JBhvKzxmOCM9SzsKPksb7cIaGicbYzxr1CM4GBMv4DcGPoWOGih0kcIaGlY8GuMvHzcbZy29Wzsb2ywX1zsbMCM9TihvZzxiGy29UDgv4DaOGignVBNn0ihnJB3bLvMfSDwuGpsa','cIOGqwn0Aw9UCZOG','icOG','zMLLBgroyw1L','y29UC3qGz2vUzxjHDgvKrMLLBgrZid0G','C3rYAw5NAwz5','y29UC3qGzxHWCMvZCYa9ihjLCxvPCMuOj2v4ChjLC3mNktSky29UC3qGCM91DgvYid0GzxHWCMvZCY5sB3v0zxiOktSky29UC3qG','ieXjtuLuideWmdbGoWOGicaGy29UC3qGzgf0ysa9igf3ywL0igrIlMv4zwn1DgvrDwvYEsHXDwvYEsK7cGOGicaGlY8Gq2fJAguGCMvZDwX0ihrHBNbHihnLBgvJDgvKigzSywCkicaGignVBNn0ignHy2HLrgf0ysa9igrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','lMfKzerHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbjtLnfuLqGy29TCgXLDgvKihDPDgHVDxqGzxzLBNrZjYK7cIaGicb9ignHDgnOicHLCNjVCIKGEWOGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDieLou0vsvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','ihX8icCNcIaGicb9ksK7cGOGicaGlY8Gq2fJAguGDgHLihjLC3vSDaOGicaGyxDHAxqGDgHPCY5ZzxrdywnOzwrmB29RDxaOy2fJAgvpChrPB25ZlcbYzxn1BhqSicDMAwX0zxiNktSkcIaGicbYzxr1CM4GCMvZDwX0oWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbPBIbNzxrmB29RDxbeyxrHv2L0AezPBhrLCJONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0kcI8QkGOGkIbcDwLSzcbHzhzHBMnLzcbMAwX0zxiGy29UzgL0Aw9UCYb1BNr1AYbnEvnrtaOGkIbaCgfYyw0GE0fYCMf5FsbMAwX0zxjZic0GqxjYyxKGB2yGE2nVBhvTBIWGDhLWzsWGDMfSDwuSihzHBhvLmN0kicOGqhjLDhvYBNmGE09IAMvJDh0GE3nXBcWGCgfYyw1ZFqOGkI8kyNvPBgrbzhzHBMnLzezPBhrLCKnVBMrPDgLVBIHMAwX0zxjZksb7cIaGAwyGkcfMAwX0zxjZihX8icfbCNjHEs5PC0fYCMf5kgzPBhrLCNmPihX8igzPBhrLCNmUBgvUz3rOid09psaWksb7cIaGicbYzxr1CM4GEYbZCwW6icCNlcbWyxjHBxm6ifTDih07cIaGFqOkicbJB25ZDcbJB25KAxrPB25Zid0Gw107cIaGy29UC3qGCgfYyw1Zid0Gw107cGOGigzVCIaOy29UC3qGzMLSDgvYig9MigzPBhrLCNmPihSkicaGignVBNn0ihSGy29SDw1Ulcb0ExbLlcb2ywX1zsWGDMfSDwuYih0GpsbMAwX0zxi7cGOGicaGAwyGkcfJB2X1Bw4GFhWGixrOAxmUDMfSAwrgAwvSzhmUAw5JBhvKzxmOy29SDw1UksKGy29UDgLUDwu7cGOGicaGC3DPDgnOicH0ExbLksb7cIaGicaGignHC2uGj2vXDwfSCYC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpsa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDUB3rFzxf1ywXZjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsa8pIa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDJB250ywLUCYC6cIaGicaGignHC2uGj2XPA2uNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieXjs0uGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkgaLjhT2ywX1zx0LycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj25VDf9JB250ywLUCYC6cIaGicaGignHC2uGj25VDf9SAwTLjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbot1qGteLlrsa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GOycuKE3zHBhvLFsvGktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNC3rHCNrZx3DPDgGNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieXjs0uGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkgaKE3zHBhvLFsvGktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNzw5KC193AxrOjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbmsuTfid9GktSkicaGicaGicbWyxjHBxmUChvZAcHGjsr7DMfSDwv9ycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2DYzwf0zxjFDgHHBIC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GpIa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDSzxnZx3rOyw4NoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59idWGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNz3jLyxrLCL9LCxvHBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GpJ0Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNBgvZC19LCxvHBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpd0Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNyMv0D2vLBIC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GqKvuv0vftIa/ieforca/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuSihzHBhvLmIK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2LUjZOkicaGicaGicbPzIaOqxjYyxKUAxnbCNjHEsH2ywX1zsKPihSkicaGicaGicaGignVBNn0igLUugXHy2vOB2XKzxjZid0GDMfSDwuUBwfWkcGPid0+icC/jYKUAM9PBIGNlcaNktSkicaGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieLoicGKE2LUugXHy2vOB2XKzxjZFsLGktSkicaGicaGicaGihbHCMfTCY5WDxnOkc4UlNzHBhvLktSkicaGicaGicb9cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj25VDf9PBIC6cIaGicaGicaGAwyGkefYCMf5lMLZqxjYyxKODMfSDwuPksb7cIaGicaGicaGicbJB25ZDcbUB3rjBLbSywnLAg9SzgvYCYa9ihzHBhvLlM1HCcGOksa9pIaNpYCPlMPVAw4OjYWGjYK7cIaGicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbot1qGsu4Gkcr7BM90sw5qBgfJzwHVBgrLCNn9kwaPoWOGicaGicaGicaGCgfYyw1ZlNb1C2GOlI4UDMfSDwuPoWOGicaGicaGih0kicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNAxnFBNvSBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GsvmGtLvmtgaPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDPC19UB3rFBNvSBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnO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CgrHDgukicaGicaGicaGicaGFqOGicaGicaGicaGicbLBhnLihSkicaGicaGicaGicaGicbJB25ZDcbTzxnZywDLid0Gy29UC3rYywLUDhmUCMvXDwLYzwrnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbPCYbYzxf1AxjLzga7cIaGicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkg1LC3nHz2uPoWOGicaGicaGicaGicaGihjLC3vSDc5PC1zHBgLKid0GzMfSC2u7cIaGicaGicaGicaGih0kicaGicaGicaGih0kicaGicaGicaGignVBNrPBNvLoWOGicaGicaGih0kcIaGicaGicaGlY8Gu3rYAw5NigzPzwXKoIbOyxnOignVBNn0CMfPBNqGC3vWCg9YDaOGicaGicaGigLMicHJB25MAwCUDhLWzsa9pt0Gj3n0CMLUzYCGjIyGDhLWzw9MihzHBhvLid09psaNC3rYAw5NjYKGEWOGicaGicaGicaGBgv0ihnHBML0AxPLzca9ihzHBhvLoWOGicaGicaGicaGy29UC3qGzMLLBgrfCNjVCNmGpsbBxtSkicaGicaGicaGignVBNn0igLZsgfZAezPzwXKid0Gy29UC3rYywLUDhmUAgfZAca9pt0Gj2jJCNLWDcC7cGOGicaGicaGicaGlY8GvhjPBqOGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlNrYAw0PihSkicaGicaGicaGicaGC2fUAxrPEMvKid0GC2fUAxrPEMvKlNrYAw0OktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbdyxnLihrYyw5ZzM9YBwf0Aw9UicHZA2LWigPPA2eGAgfZAcbMAwvSzcKkicaGicaGicaGigLMicGHAxniyxnOrMLLBgqPihSkicaGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlMXVD2vYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B0XVD2vYq2fZzsGPoWOGicaGicaGicaGicb9igvSC2uGAwyGkgnVBNn0CMfPBNrZlNvWCgvYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B1vWCgvYq2fZzsGPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GtgvUz3rOihzHBgLKyxrPB24GkhzHBgLKyxnPihbSywLUDgv4DcbZzwjLBhvTigHHC2GPcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwLUtgvUz3rOicyMihnHBML0AxPLzc5Szw5NDgGGpcbJB25ZDhjHAw50CY5TAw5mzw5NDgGPihSkicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5TAw5mzw5NDgHnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbTDxn0igjLigf0igXLyxn0icr7y29UC3rYywLUDhmUBwLUtgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwf4tgvUz3rOicyMicfPC0HHC2HgAwvSzcaMjIbZyw5PDgL6zwqUBgvUz3rOid4Gy29UC3rYywLUDhmUBwf4tgvUz3rOksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUBwf4tgvUz3rOtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGBxvZDcbUB3qGzxHJzwvKicr7y29UC3rYywLUDhmUBwf4tgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cGOGicaGicaGicaGlY8Gugf0DgvYBIb2ywXPzgf0Aw9UcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUCgf0DgvYBIKGEWOGicaGicaGicaGicbJB25ZDcbYzwDLEca9ig5LDYbszwDfEhaOy29UC3rYywLUDhmUCgf0DgvYBIK7cIaGicaGicaGicaGigLMicGHCMvNzxGUDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5Wyxr0zxjUtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGzg9LCYbUB3qGBwf0y2GGCMvXDwLYzwqGCgf0DgvYBMaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GrM9YBwf0ihzHBgLKyxrPB24kicaGicaGicaGigLMicHJB25ZDhjHAw50CY5MB3jTyxqGpt09icDLBwfPBcCGjIyGis9Ew15CC0bDk0bBxLXZqf0Rxc5BxLXZqf0Rjc8UDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUzM9YBwf0twvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGAgfZigLUDMfSAwqGzw1HAwWGzM9YBwf0ycK7cIaGicaGicaGicb9cGOGicaGicaGicaGAwyGkgzPzwXKrxjYB3jZlMXLBMD0Aca+idaPihSkicaGicaGicaGicaGCMvZDwX0lMLZvMfSAwqGpsbMywXZztSkicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkc4UlMzPzwXKrxjYB3jZktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbiyxnOihrYyw5ZzM9YBwf0Aw9UicHZzxrLBgfOihnLBxvHihzHBgLKyxrPB24GCgfZCYKkicaGicaGicaGigLMicHPC0HHC2HgAwvSzcaMjIbMAwvSzevYCM9YCY5Szw5NDgGGpt09idaPihSkicaGicaGicaGicaGy29UC3qGyMnYExb0id0GCMvXDwLYzsGNyMnYExb0jYK7cIaGicaGicaGicaGignVBNn0ignVC3qGpsbJB25ZDhjHAw50CY5OyxnOq29ZDcb8FcaXmdSkicaGicaGicaGicaGC2fUAxrPEMvKid0GyxDHAxqGyMnYExb0lMHHC2GOC2fUAxrPEMvKlcbJB3n0ktSkicaGicaGicaGih0kcIaGicaGicaGicbYzxn1BhqUC2fUAxrPEMvKrgf0yvTMAwvSze5HBwvDid0GC2fUAxrPEMvKoWOGicaGicaGih0GzwXZzsb7cIaGicaGicaGicaVlYboB24TC3rYAw5NigzPzwXKoIbIyxnPyYbZyw5PDgL6yxrPB24kicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKtMfTzv0Gpsb2ywX1ztSkicaGicaGicb9cIaGicaGih0kcIaGicaGic8VifzHBgLKyxrLigzPzwXKihLHBMCGDgLK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','jYWkicaGigzPzwXKq291BNq6ia','ChjPy2vgAwvSza','ieXjtuLuideWmga7cIaGicbJB25ZDcbWyxjHBxmGpsbBycuKE3nLyxjJAcb8FcaNj30Lyf07cIaGicbJB25ZDcbKyxrHid0GyxDHAxqGzgiUzxHLy3v0zvf1zxj5khf1zxj5lcbWyxjHBxmPoWOkicaGignVBNn0ihjLC3vSDca9igrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','cIaGicbJB25ZDcbHzgP1C3rdB25MAwCGpsbJB21WB25LBNrdB25MAwCUywrQDxn0q29UzMLNihX8ihT9oWOGicaGBgv0ihjLC3bVBNnLrgf0ysa9ig51BgW7cGO','lY8Grw5KCg9PBNqGl2LUzM8GzgLUB25HA3rPzMTHBIb2AweGywn0Aw9UlMLUzM8GpsbMywXZzqO','mta3oduYmezfzNvkvW','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGlY8Gq1jfqvrflunptvbpu0LursaVifvqrefurs1dt01qt1njveu6igzVCMnLihnJB3bLignVBhvTBIbPBNnPzguGBwfZDgvYigjVzhKUcIaGAwyGkgvUzhbVAw50id09psaNy3jLyxrLlwnVBxbVC2L0zsCGFhWGzw5KCg9PBNqGpt09icD1CgrHDguTy29TCg9ZAxrLjYKGEWOGicaGy29UC3qGBwfZDgvYqM9KEsa9ihjLCs5IB2r5wYC','ywrQDxn0','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGDxnLCL9PzdOGCMvXlMHLywrLCNnBj3vZzxiTAwqNxsb8FcbYzxeUAgvHzgvYC1SNEc11C2vYlwLKj10GFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30kicaGicaGicb9cIaGicaGih07cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','cIaGicaVlYbjBNrLz3jHDgvKihrYyw5Zywn0Aw9UigrLBMDHBIbLDMvUDcbSAwzLy3LJBgukicaGihrYEsb7cIaGicaGignVBNn0igv2zw50q29UDgv4Dca9ihSkicaGicaGicbJB21WB25LBNrfBMDPBMu6ignVBxbVBMvUDevUz2LUzsWkicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGDgfIBgvoyw1LoIaN','oWOkicaGic8VifzHBgLKyxnPigrHBIbZyw5PDgfZAsbOzwfKzxiGzgf0ysbKzw5Nyw4Gy29UC3rYywLUDcaODgvYBwfZDwSGAgfZAcKkicaGigLMicH0ExbLB2yG','D29YA2zSB3C','yxvKAxrdB2X1Bw5Z','CMvHzenVBxbVC2L0zq','jYWkicaGihrHyMXLtMfTztOGjW','x2LK','vejAs1i','icHnEvnrtcbeyxrHyMfZzsKkicOGqhbHCMfTihTpyMPLy3r9ignVBMzPzYaTieTVBMzPz3vYyxnPihvUDhvRig1LBMPHBgfUA2fUig1VzhvScIaQiebWyxjHBsb7BNvTyMvYFsbJB25MAwCUCg9YDcaTifbVCNqGDw50DwSGC2vYDMvYcIaQiebWyxjHBsb7C3rYAw5NFsbJB25MAwCUA2v5ic0GqvbjieTLEsaOB3bZAw9UywWPcIaQiebYzxr1CM5ZihTqCM9TAxnLphzVAwq+FsbqCM9TAxnLihLHBMCGDgLKywSGCgvYBMfOihjLC29SDMuGywDHCIbZzxj2zxiGDgv0yxaGyMvYAMfSyw4kicOVcMfZEw5Jigz1BMn0Aw9Uigv4zwn1DguOy29UzMLNksb7cIaGCMv0DxjUig5LDYbqCM9TAxnLkcHYzxnVBhzLksa9pIb7cIaGicbJB25ZDcbHChaGpsbLEhbYzxnZkcK7cIaGicbJB25ZDcbWB3j0id0Gy29UzMLNlNbVCNqGFhWGmZaWmdSkicaGignVBNn0ihnLCNzLCKfKzhjLC3mGpsbJB25MAwCUC2vYDMvYqwrKCMvZCYb8FcaNmc4WlJaUmcC7cIaGicbJB25ZDcbTB2r1Bgvoyw1Lq2fWAxrHBgL6zwqGpsaN','laOGigLTCg9YDenVBMzPzZOG','Eungwwu','y29SDw1UrM9YBwf0CW','id0GpYbpuKrfuIbcwsbSAw5Lx251BwjLCIC7','laOGigzPzwXKtgfIzwXZoIa','cIaGyc50CMLTkcK7cIaGyMfZzvf1zxj5id0GDgHPCY5JB252zxj0vg9nExnXBfnrtcHIyxnLuxvLCNKPoWOGihjLDhvYBIbIyxnLuxvLCNK7','jYbMCM9Tia','Bg9VA3vWrMLLBgrZ','sfHiEem','lMDLDerHDgeOEWOGicaGicaGihDOzxjLoIbBcIaGicaGicaGicb7igTLEtOGjW','l2HLywX0AgaScIaGicaGicaGicbZzxj2AwnLsw5MBZOGygH0Dha6lY8KE2rPC3bSyxLiB3n0FtOKE3bVCNr9l2fWAs8','zMLLBgrmywjLBhm','igrHDgeGDxbKyxrLzcbZDwnJzxnZzNvSBhK6ia','Dg9mB3DLCKnHC2u','jYWkicaGicaGBw9KDwXLoIaN','zw50CMLLCW','BwfW','jYWkicaGicaGzgf0ywjHC2u6icDTExnXBcCScIaGicaGigvYCM9YoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','Cg9W','jWOGih07cGOGihrOAxmUywr2yw5JzwrrDwvYEvrLBxbSyxrLCYa9ihrOAxmUBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOktSkFqOklYOQcIaQieXVywqGywr2yw5JzwqGCxvLCNKGDgvTCgXHDgvZigrHCMKGzMLSzqOGkI8kBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOksb7cIaGy29UC3qGDgvTCgXHDgvZid0GE307cGOGia','iL0GpsbUDwXSoWOGicaGicb9cIaGicb9igvSC2uGEWOGicaGicb0zw1WBgf0zxnBiG','lMDLDeXVB2T1CerHDgfeEw5HBwLJkhnLyxjJAcWGzxH0CMfgAwX0zxjZksa6cIaGicaGigf3ywL0ia','cIOGuhjPBwfYEsblzxK6ia','jYWGzw5KCg9PBNroyw1LlcbPBxbVCNrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icDPBxbVCNrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1Lih0SigbjBxbVCNqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHPBxbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2LTCg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGAw1WB3j0rxjYB3iUBwvZC2fNzsb9lcbGsw1WB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTPBxbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYihvWBg9HzcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0ihvWBg9HzenVBMzPzYa9igv4DhjHy3rvCgXVywrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkhvWBg9HzenVBMzPzYKGEWOGicaGicaGicaGicaGifvWBg9HzeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','cIaGicaGignVBNn0igrLDgfPBezPzwXKCYa9ifTDoWOGicaGicbJB25ZDcbKzxrHAwXwywX1zxmGpsbBxtSkicaGicaGy29UC3qGzgv0ywLSugXHy2vOB2XKzxjZid0Gw107cGOGicaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHPDgvTksKGEWOGicaGicaGia','Dg90ywXFCxr5','icbYzxr1CM4Gj1nftevdvcaQiezst00GjYaRihrOAxmUCMvHzfnVDxjJztS','ywr2yw5JzwrrDwvYAwvZ','ktSkicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbbrePvu1qGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05DiefesLvtvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','jYWkicaGicaGicbZzxj2AwnLCZOGE30ScIaGicaGicaGywrKAxrPB25HBenVBNrLEhq6ihSkicaGicaGicaGihvZzxjFAwq6ihjLCs5OzwfKzxjZwYD1C2vYlwLKj10GFhWGCMvXlMHLywrLCNnBj3GTDxnLCI1PzcDDihX8ihjLCs5IB2r5lNvWzgf0zwrFyNKGFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30ScIaGicaGicaGicbYzxf1zxn0swq6ihjLCs5Pzcb8FcbUDwXSlaOGicaGicaGicaGlY8GsLDuigzVCNDHCMrPBMCGkeXHEwvYideGuKXtktOGqxv0Ag9YAxPHDgLVBIbOzwfKzxiGzgfYAsbYzxf1zxn0igfZBgKkicaGicaGicaGic8VigrPlwzVCNDHCMqGA2uGD29YA2zSB3CGAg9VAYbJywXSigfNyxiGzw5KCg9PBNqGDhvQDwfUihLHBMCGChvUEwekicaGicaGicaGic8VihjLCxvLC3rty29WzsbHA3rPzIb0zxrHCcbTzw5LCMLTysbYzxeUDxnLCIbKzw5Nyw4GC2nVCguGEwfUzYbZyw1HlGOGicaGicaGicaGyxv0AeHLywrLCJOGCMvXlMHLywrLCNmUyxv0Ag9YAxPHDgLVBIb8FcbUDwXScIaGicaGicaGFqOGicaGicb9oWOkicaGicaGDhj5ihSkicaGicaGicbJB25ZDcb7ihjLC29SDMvtzxj2AwnLCYb9id0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9Zzxj2AwnLlxjLC29SDMvYjYK7cIaGicaGicaGzxzLBNrdB250zxH0lNnLCNzPy2vZid0GCMvZB2X2zvnLCNzPy2vZkcK7cIaGicaGih0Gy2f0y2GGkguPihSkicaGicaGicaVlYbtzxj2AwnLihjLC29SDMvYig9WC2LVBMfScIaGicaGih0kcIaGicaGihjLC3vSDca9igf3ywL0ia','iIWGiNzHBhvLiJOGiNLVDxiTDMfSDwuIih1DcIaGicaGicaGicb9laOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIHGrxjYB3iGDMfSAwrHDgLUzYbnEvnrtcbWyxLSB2fKigzVCIaKE3jLCs5WyxrOFtPGlcbLCNjVCIK7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDjBNzHBgLKihbHEwXVywqGzM9YBwf0jYWkicaGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOGih0kicbUzxH0kcK7cN0PoWOk','DuPHwwe','lNvWzgf0zurHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0','jYWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDnEvnrtcbPBMzVigvYCM9YoICSigvYCM9YktSkicaGihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw5MBYbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbMzxrJAgLUzYbLBMrWB2LUDcbPBMzVjYWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0kFsK7','oWOGicaGy29UC3qGCMvJywXJrMLLBgrZid0Gw107cIaGicbJB25ZDcbYzwnHBgnwywX1zxmGpsbBxtSkcIaGicbPzIaOy2fSy3vSyxrPB25ZlNrVDgfSx2L0zw1Zksb7cIaGicaGihjLy2fSy0zPzwXKCY5WDxnOkcD0B3rHBf9PDgvTCYa9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GOywXSsxrLBxmUBgvUz3rOktSkicaGih0kicaGigLMicHJywXJDwXHDgLVBNmUDg90ywXFCxr5icyMignHBgn1Bgf0Aw9UCY50B3rHBf9XDhKUC291CMnLksb7cIaGicaGignVBNn0ihf0EuzPzwXKid0Gy2fSy3vSyxrPB25ZlNrVDgfSx3f0Es5ZB3vYy2uUCMvWBgfJzsGNAxrLBxmUjYWGjYCPoWOGicaGicbJB25ZDcb0B3rHBff0Esa9igfSBeL0zw1ZlNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSGCMv0DxjUihn1BsaRicHoDw1IzxiOAxrLBvTXDhLgAwvSzf0PihX8idaPoYb9lcaWktSkicaGicaGCMvJywXJrMLLBgrZlNb1C2GOj3rVDgfSx3f0Esa9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GODg90ywXrDhKPoWOGicaGFqOGicaG','zw16wLy','y3jLyxrLzef0','j119ycK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGBwvZC2fNztOGjW','DgfIBgvoyw1L','cIaGicaVlYbdB21WB25LBNqGzw5NAw5LoIbIDwLSzcbLDMvUDenVBNrLEhqGDw50DwSGBw9KzwWTBgv2zwWGzxzLBNqGBgLMzwn5y2XLcIaGicbPzIaOy29TCg9Uzw50rw5NAw5LicyMienVBNrLEhrcDwLSzgvYksb7cIaGicaGihrYEsb7cIaGicaGicaGy29UC3qGzxzLBNrdB250zxH0id0GEWOGicaGicaGicaGy29TCg9Uzw50rw5NAw5LoIbJB21WB25LBNrfBMDPBMuScIaGicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGicb0ywjSzu5HBwu6icC','lMfKzerHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicaGignVBNnVBguUBg9NkcDBrKfmtejbq0TDieLou0vsvcbJB21WBgv0zwqGD2L0Ag91DcbLDMvUDhmNktSkicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDieLou0vsvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGicaGDgHYB3CGzxjYB3i7cIaGicaGih0kicaGih0k','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKig11C3qGAgf2zsbWCM9Wzxj0EsaI','DxbKyxrLx2v4Axn0Aw5N','cGOGicaGzM9YicHJB25ZDcbOzwfKzxiGB2yGAgvHzgvYuMvZDwX0CYKGEWOGicaGicbJB25ZDcbWA1zHBhvLid0GAgvHzgvYw3rOAxmUChjPBwfYEuTLEv07cIaGicaGignVBNn0igrLDgfPBfjLC3vSDhmGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOzgv0ywLSu3fSlcbBCgTwywX1zv0PoWOGicaGicbJB21WB3nPDgvszxn1BhrZlNb1C2GOEWOGicaGicaGic4UlMHLywrLCIWkicaGicaGicbBzgv0ywLSs2v5xtOGzgv0ywLSuMvZDwX0CYb8FcbBxqOGicaGicb9ktSkicaGih0kcIaGicbYzxr1CM4GEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbJB3vUDdOGy29TCg9ZAxrLuMvZDwX0CY5Szw5NDgGScIaGicaGigrHDge6ignVBxbVC2L0zvjLC3vSDhmkicaGih07cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUihjLywrdB21WB3nPDgu6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cG','iIa9pt0Gj3n0CMLUzYCGjIyGiG','BMfTzq','zgv0ywLSuxvLCNK','cIaGicaVlYbwywXPzgfZAsbKyw4GC2fUAxrHC2KGAgvHzgvYigrHDgeGzgvUz2fUignVBNn0CMfPBNqGkhrLCM1HC3vRigHHC2GPcIaGicbPzIaODhLWzw9Mia','jZSkicaGia','lY8Gv0fstKLorZOGAgvHzgvYq2fSy3vSyxrPB25ZlNrVDgfSx2fTB3vUDcbZA2LWCgvKimoI4OkS4OcDig5Vihf0EsbMAwvSzcbJB25MAwD1CMvK','cIaGicaVlYbszxf1zxn0ihnJB3bLig93BMvYC2HPCcb2zxjPzMLJyxrPB24GkeXHEwvYideGuKXtksddOUkcRokaNsbTyxn0zxiGCMvJB3jKcIaGicbPzIaOCMvXlL9Yzxf1zxn0u2nVCguPihSkicaGicaGy29UC3qGC2nVCgvdAgvJAYa9igf3ywL0ia','cIaGlY8Grgf0zvrPBwuGzMLLBgrZignVBMzPz3vYyxrPB24GzgfYAsbMAwvSzfzHBgLKyxrPB24kicb0AgLZlMrHDgvuAw1LrMLLBgrZid0G','cIaGicaGih0','lY8Gue9tvcaVyxbPlW','jZSkicaGignVBNn0igrLDgfPBfbRid0GjW','lMrLBgv0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4DcK7cIaGicaGignVBNnVBguUBg9NkcDBsu5uruDsqvrfrcbuuKfou0fdveLptL0GrevmrvrfignVBxbSzxrLzcbZDwnJzxnZzNvSBhKGD2L0AcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TjtLrfr1jbveveifrsqu5tqunusu9oxsberuXfveuGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','oWOkicbZDxbLCIGN','jYWGDMfSAwrgAwvSzhmPoWOkicaVlYbIyxnLlw1VzgvSlw15C3fSigHHBNLHig1LBMvYAw1HidiGCgfYyw1LDgvYlcbZAw1Wyw4Gzgf0yxrHyMXLC1DOzxjLig1HBNvHBaOGihrOAxmUzgf0yxrHyMXLC1DOzxjLid0Gzgf0yxrHyMXLC1DOzxjLoWOkicaVlYbqCMLTyxj5igTLEsbJB25MAwD1CMf0Aw9UcIaGDgHPCY5WCMLTyxj5s2v5id0GjW','mJK2ntu1EhzIrgDM','zM9YBxvSyq','cI8QkGOGkIbdB21WB3nPDguGy3jLyxrLic0Gq3jLyxrLigHLywrLCIb3AxrOigrLDgfPBcbPDgvTCYbPBIbHihnPBMDSzsb0CMfUC2fJDgLVBIaOtxLtuuWPcIaQlWPHC3LUyYbJCMvHDgvdB21WB3nPDguOzgf0ysWGzxzLBNrdB250zxH0id0GBNvSBcKGEWOGignVBNn0ignVBM5Ly3rPB24GpsbHD2fPDcbKyI5NzxrdB25Uzwn0Aw9UkcK7cIaGDhj5ihSkicaGigf3ywL0ignVBM5Ly3rPB24UyMvNAw5uCMfUC2fJDgLVBIGPoWOkicaGignVBNn0igrLDgfPBeTLEsa9icC','ksb8FcaWoWOGicaGicaGihzHCIbWCMLJzsa9ie51BwjLCIHPDgvTlG','rKjkBhC','C3rYAw5N','zgf0yxrHyMXLCW','vwzvwuy','CMvHza','jYWkicaGicaGicaGig9WDgLVBNm6ihjLCs5IB2r5t3b0Aw9UCYb8Fcb7FsWkicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicb9cIaGicaGih07cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','jZSkicaGignVBNn0ihbSDwDPBLbHDgGGpsbWyxrOlMPVAw4Ox19KAxjUyw1LlcaNlI4NlcaNCgX1z2LUCYCSigaKE21VzhvSzu5HBwv9lxbSDwDPBI5QC2aPoWOGicaGBgv0ihbSDwDPBIa9ig51BgW7cIaGicbPzIaOzNmUzxHPC3rZu3LUyYHWBhvNAw5qyxrOksKGEWOGicaGicb0CNKGEWOGicaGicaGihbSDwDPBIa9ihjLCxvPCMuOCgX1z2LUugf0AcK7cIaGicaGicaGAwyGkhbSDwDPBI5VBKjLzM9YzuvUzhbVAw50C0XVywqPihSkicaGicaGicaGihbSDwDPBI5VBKjLzM9YzuvUzhbVAw50C0XVywqOyxbWlcbJB25MAwCPoWOGicaGicaGih0kicaGicaGicbSB2DNzxiUAw5MBYH7igv2zw50oIaNCgX1z2LUx2XVywrLzcCSihbSDwDPBJOGycr7Bw9KDwXLtMfTzx0TCgX1z2LUycb9lcbGugX1z2LUigXVywrLzdOGjhTTB2r1Bgvoyw1LFs1WBhvNAw4UANnGktSkicaGicaGFsbJyxrJAcaOCgX1z2LUrxjYB3iPihSkicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3bSDwDPBL9SB2fKx2vYCM9YjYWGzxjYB3i6ihbSDwDPBKvYCM9YlM1LC3nHz2uGFsWGyezHAwXLzcb0BYbSB2fKihbSDwDPBJOGjhTTB2r1Bgvoyw1LFs1WBhvNAw4UANnGktSkicaGicaGFqOGicaGFqOkicaGic8VieHLywX0AcbJAgvJAYbLBMrWB2LUDaOGicaGyxbWlMDLDcGNl2fWAs8','jYWkicaGicaGicaGigzVCMvPz25lzxK6icC','ChjPBwfYEuTLEq','mJeXntq4nM1Vv0r1tW','AgvHzgvYq2fSy3vSyxrPB25Z','id0GjW','zgvSzxrL','mZq4suXZvu9n','z2vUzxjHDgvK','cN0kcM1VzhvSzs5LEhbVCNrZid0GBMv3ia','BgvUz3rO','AMHrDxC','AfLYyuC','DxbKyxrL','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUC3rHDhvZq29Kzsa9pt0GndaZksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmYKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDqCM8GrMvHDhvYzsbszxf1AxjLzcCScIaGicaGicaGBwvZC2fNztOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb1CgDYywrLoIaNAhr0Chm6lY9Yzxn0zM9Yz2uUzgv2l3bYAwnPBMCNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGigLMicHLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDJB25ZDhjHAw50ihzPB2XHDgLVBICPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj2jLBg93ig1PBMLTDw0NksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdKPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNq29UC3rYywLUDcb2Aw9SyxrPB24NlaOGicaGicaGig1LC3nHz2u6igvYCM9YlM1LC3nHz2uScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj25VDcbJB25MAwD1CMvKigzVCIbHzgP1C3rTzw50jYKGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNAxmGCMvXDwLYzwqGzM9YigfKANvZDcCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj211C3qGyMuGysbUB24TEMvYBYbUDw1IzxiNksb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDUB3qGysb2ywXPzcbMAwvSzcCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj211C3qGBM90igjLigvTChr5jYKPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj1zHBgLKyxrPB24GzxjYB3iNlaOGicaGicaGig1LC3nHz2u6igvYCM9YlM1LC3nHz2uScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlM1LC3nHz2uGpt09icDeyxrHihrPzgfRigrPDgvTDwTHBICGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNBM90igzVDw5KjYKPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda0ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0rHDgeGBM90igzVDw5KjYWkicaGicaGicbTzxnZywDLoIaN','Dw5PDf9WCMLJzq','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUy29Kzsa9pt0Gj0vsx1jpv19ju19sruzfuKvoq0vexZiNihX8igvYCM9YlMvYCM5Vid09psaXnduXksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWosKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDgB3jLAwDUigTLEsbJB25ZDhjHAw50jYWkicaGicaGicbTzxnZywDLoIaNq2fUBM90igrLBgv0ztOGCMvJB3jKigLZihn0AwXSihjLzMvYzw5JzwqGyNKGB3rOzxiGzgf0ysCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigrLBgv0Aw5Nia','l3jLywqTy29TCg9ZAxrLoICSiePtt04UC3rYAw5NAwz5khjLCs5IB2r5lcbUDwXSlcaYksK7cGOGicaGAwyGkcfYzxeUyM9KEsb8FcbpyMPLy3qUA2v5CYHYzxeUyM9KEsKUBgvUz3rOid09psaWksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7ihn1y2nLC3m6igzHBhnLlcbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCSig1LC3nHz2u6icDqyxLSB2fKignHBM5VDcbIzsbLBxb0EsCSihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih0PoWOGicaGFqOkicaGigLMicGHCMvXlMjVzhKUD2HLCMuPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6icDqCM9Wzxj0Esb3AgvYzsbPCYbYzxf1AxjLzcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiMzPzwXKx25HBwuIlcaIDMfSDwuIoIaIzMLLBgrFDMfSDwuIih1Dih0ScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkcfbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksaMjIaHCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcb3AgvYzsbMB3jTyxqNlaOGicaGicaGig1LC3nHz2u6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiG','zM9YzwLNBKTLEq','ksb8FcbKyxrHlG','ChvZAa','y29UC3qGzgv0ywLSu3fSid0Gj1nftevdvcaQiezst00G','BNvTyMvY','cI8QkGOQia','j107cIaGicbSzxqGCgfYyw1Zid0Gw107cGOGicaGlY8GvMfSAwrHC2KGDgv4DcbMAwvSzhmkicaGignVBNn0ihzHBgLKvgv4DezPzwXKCYa9ihrOAxmUDMfSAwrgAwvSzhmUzMLSDgvYkgzPzwXKid0+cIaGicaGigzPzwXKlMLUy2X1zgvZkcDUyw1LjYKGFhWGzMLLBgqUAw5JBhvKzxmOj25HBweNksb8FaOGicaGicbMAwvSzc5PBMnSDwrLCYGNy29KzsCPihX8igzPzwXKlMLUy2X1zgvZkcDRB2rLjYKGFhWkicaGicaGzMLLBgqUAw5JBhvKzxmOj3rLEhqNksb8FcbMAwvSzc5PBMnSDwrLCYGNDgL0BguNkqOGicaGktSkcIaGicbSzxqGC2vSzwn0q2XHDxnLid0GjW','iI5ZDgfYDhnxAxrOkcDMAwXLoICPksb7cIaGicaGignVBNn0ihjLBgf0AxzLugf0Aca9ici','y29UC3qGqMfZzu1VzgvSid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY9TB2rLBhmVyMfZzs1TB2rLBc1TExnXBcCPoWPJB25ZDcbKyIa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVDxrPBhmVzgiTBxLZCwWNktSky29UC3qGzNmGpsbYzxf1AxjLkcDMCYCPoWPJB25ZDcbWyxrOid0GCMvXDwLYzsGNCgf0AcCPoWOklYOQcIOG','mZu0nta2C2HgqwrA','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','ywrQDxn0q29UzMLN','mZKWntfZs2vxz1i','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGlY8GvvbeqvrfoIbMB3jJzsbZy29WzsbJB2X1Bw4GDMfSDwuGkg93BMvYC2HPCcb2zxjPzMLLzcbPBIaVDxbKyxrLigHHBMrSzxiPcIaGAwyGkgvUzhbVAw50id09psaNDxbKyxrLjYKGEWOGicaGCMvXlMjVzhLBjW','y29UC3qGzxHWCMvZCYa9ihjLCxvPCMuOj2v4ChjLC3mNktSky29UC3qGyM9KEvbHCNnLCIa9ihjLCxvPCMuOj2jVzhKTCgfYC2vYjYK7cMnVBNn0ihbHDgGGpsbYzxf1AxjLkcDWyxrOjYK7cMnVBNn0igzZid0GCMvXDwLYzsGNzNmNktSky29UC3qGEYb2nZOGDxvPzhy3ih0GpsbYzxf1AxjLkcD1DwLKjYK7cMnVBNn0ihSGBg9Nz2vYlcbSB2Dtzxj2zxjszwfKEsWGBg9Nrw5KCg9PBNrszwDPC3rLCMvKlcbJCMvHDgvszxf1zxn0tg9Nz2vYlcbSB2Dszxf1zxn0ih0GpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl3v0AwXZl2XVz2DLCICPoWPJB25ZDcbfEhbVCNriyw5KBgvYid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY9JB21WB25LBNrZl2HHBMrSzxjZl2v4Cg9YDf9Oyw5KBgvYjYK7cMnVBNn0ieLTCg9YDeHHBMrSzxiGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl2nVBxbVBMvUDhmVAgfUzgXLCNmVAw1WB3j0x2HHBMrSzxiNktSky29UC3qGvxbSB2fKsgfUzgXLCIa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVy29TCg9Uzw50CY9Oyw5KBgvYCY91CgXVywrFAgfUzgXLCICPoWPJB25ZDcb7igv4DhjHy3rfEhbVCNrdB25MAwDgCM9Trw5KCg9PBNqSigv4DhjHy3rjBxbVCNrdB25MAwDgCM9Trw5KCg9PBNqSigv4DhjHy3rvCgXVywrdB25MAwDgCM9Trw5KCg9PBNqGFsa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVDxrPBhmVy29UzMLNlwv4DhjHy3rVCICPoWPJB25ZDcbYyxrLtgLTAxrLCIa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVBwLKzgXLD2fYzs9YyxrLlwXPBwL0zxiNktSky29UC3qGAwrLBxbVDgvUy3LnAwrKBgv3yxjLid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY9TAwrKBgv3yxjLl2LKzw1WB3rLBMn5jYK7cMnVBNn0igjVzhLpChrPB25ZtwLKzgXLD2fYzsa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVBwLKzgXLD2fYzs9IB2r5lw9WDgLVBNmNktSky29UC3qGy29YC01PzgrSzxDHCMuGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl21PzgrSzxDHCMuVy29YCYCPoWPJB25ZDcbZzwn1CML0EuHLywrLCNmGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl21PzgrSzxDHCMuVC2vJDxjPDhKTAgvHzgvYCYCPoWOklYOQcIaQiez1BMDZAsb1BNr1AYbTzw5NzwTZzwT1C2KGBw9KDwWG','oWOkicaGigLMicGHzgf0ys4','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','icaGicaGlY8Gqxv0BY1JywXJDwXHDguG','ih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGj01HC3rLCIbYzwnVCMqGBM90igzVDw5Kig9YigfJy2vZCYbKzw5PzwqNlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFqO','igXPC3q6jYWGzxjYB3iPoWOGicaGy29UC3qGC3rHDhvZq29Kzsa9igvYCM9YlNn0yxr1C0nVzguGFhWGntaWoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOC3rHDhvZq29KzsKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGC3rHDhvZq29Kzsa9pt0GndaWid8Gj0jHzcbszxf1zxn0jYa6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIbZDgf0DxndB2rLid09psa0mdaGpYbLCNjVCI5TzxnZywDLidOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigzLDgnOAw5Nia','tw9KzwW','jYWGzw5KCg9PBNroyw1Llcb1CgXVywrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icD1CgXVywrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbMAwvSzhm6ie9IAMvJDc5RzxLZkhvWBg9HzenVBMzPzY5MAwvSzhmGFhWGE30Pih0SigbvCgXVywqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicH1CgXVywrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3vWBg9Hzf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGDxbSB2fKrxjYB3iUBwvZC2fNzsb9lcbGvxbSB2fKihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhT1CgXVywrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOGicaGicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGicaGicbJB25ZB2XLlMvYCM9YkgbfCNjVCIbSB2fKAw5Nig1VzhvSzsaKE2zPBgv9igzYB20G','igrHDgeNlaOGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbMzxrJAgLUzYa','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YihvWzgf0zwaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cG','ExL5Es1nts1Kza','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','y3jLyxrL','psr7CMvXlMjVzhLBjW','lMfNz3jLz2f0zurHDgeOCMvXlMjVzhKGFhWGE30SigfNz3jLz2f0zunVBMzPzYK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGzgf0ytOGCMvZDwX0laOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUz2fNCMvNyxnPigrHDgeG','ih07cMnVBxbVBMvUDevUz2LUzs5SB2fKq29UzMLNDxjHDgLVBKzYB21pyMPLy3qOx2nVBxbVBMvUDfbHEwXVywqPlNrOzw4OCMvZDwX0id0+ihSkicbPzIaOCMvZDwX0lNn1y2nLC3mPihSkicaGignVBNnVBguUBg9NkgbdB21WB25LBNqGy29UzMLNDxjHDgLVBIbSB2fKzwqGzM9Yia','jYWkicbMAwvSze5HBwu6ia','jYWkicaGicaGDgfIBgu6ig1VzgvSsw5MBY50ywjSzsWkicaGicaGzMLLBgrZoIbTB2rLBeLUzM8UzMLLBgrZlaOGicaGicbXDwvYEvnVDxjJzxm6ig1VzgvSsw5MBY5XDwvYEvnVDxjJzxmScIaGicaGigfJDgLVBNm6igfJDgLVBNmScIaGicaGigrHDgfIyxnLvhLWztOGj215C3fSjYWkicaGicaGz2vUzxjHDgvKoIaN','l2nOyw5Nzs1ZDgf0DxmGlsbnEvnrtcbdAgfUz2uGC3rHDhvZia','cIOGrMLLBgrZoIa','AxnbCNjHEq','y1bOC00','cIOGvgfIBgu6ia','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDnAxnZAw5NihjLCxvPCMvKigzPzwXKjYWkicaGicaGicbTzxnZywDLoIaNuhjPBwfYEsbRzxKGka','cIaGicaVlYbszxf1zxn0ihnJB3bLig93BMvYC2HPCcb2zxjPzMLJyxrPB24GkeXHEwvYideGuKXtkqOGicaGAwyGkhjLCs5FCMvXDwvZDfnJB3bLksb7cIaGicaGignVBNn0ihnJB3bLq2HLy2SGpsbHD2fPDca','y29UC3rYywLUDhm','jYWkicaGicaGicbMB3jLAwDUs2v5oIaN','C291CMnL','DhjPBq','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbYzwfKAw5Nia','C3bSAxq','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHPDgvTksKGEWOGicaGicaGigLMicHRzxKGpt09igrLDgfPBfbRksbJB250Aw51ztSkicaGicaGica','ywn0Aw9U','zMLLBgrwywXPzgf0Aw9U','jYWGDMfSDwu6ihnJB3bLvMfSDwuGFtSkcIaGy29UC3qGzw5KCg9PBNqGpsbYzxeUCgf0Ac5ZDwjZDhjPBMCOmsK7cIaGy29UC3qGC2nVCgvdB25KAxrPB24Gpsb7igTLEtOGjW','Cxr5rMLLBgq','lMXLBMD0AdSkicaGih0kicaGigLMicHOzwfKzxjdywXJlNrVDgfSx3f0EsaMjIbOzwfKzxjdywXJlNrVDgfSx3f0Es5ZB3vYy2uPihSkicaGicaGDMfYihf0EuzPzwXKid0GAgvHzgvYq2fSyY50B3rHBf9XDhKUC291CMnLlNjLCgXHy2uOj2L0zw1ZlICSicCNktSkicaGicaGzgf0ys50B3rHBf9XDhKGpsbKyxrHlG','mtC2n1DxzurSqW','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','cIOkkIbuywjSztOG','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicb2yxiGAgvHzgvYrgf0yuzVCLzHBgLKyxrPB24GpsbpyMPLy3qUyxnZAwDUkhT9lcbKyxrHktSkicaGicaGzgvSzxrLigHLywrLCKrHDgfgB3jwywXPzgf0Aw9UlG','y3jLyxrLzej5','Aw5JBhvKzxm','y3jLyxrLq29TCg9ZAxrL','cIaGicbJB25ZB2XLlMXVzYGNq2fSy3vSyxrLzcb0B3rHBhm6jYWGEYb0B3rHBf9PDgvTCZOGzgf0ys50B3rHBf9PDgvTCYWGDg90ywXFCxr5oIbKyxrHlNrVDgfSx3f0EsWGDg90ywXFyw1VDw50oIbKyxrHlNrVDgfSx2fTB3vUDcb9ktSkicaGia','ignVBxbVC2L0zsbKyxrHjYWkicaGicaGzgv0ywLSCZOGChjVy2vZCY5LBNyUtK9erv9ftLyGpt09icDKzxzLBg9WBwvUDcCGpYbLCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','zgv0ywLSq29UzMLN','mtCWmeLswufSCW','jYKGEWOGicaGicaGignVBNrPBNvLoYaVlYbWCMLTyxj5igTLEsbZDwrHAcbHzgekicaGicaGFqOkicaGicaGlY8Gq2HLy2SGAMLRysbHzgeGu1fmigv4ChjLC3nPB24GzgvUz2fUigfSAwfZicHTzw5Nz3vUywTHBIbbuYKkicaGicaGy29UC3qGywXPyxnszwDLEca9ig5LDYbszwDfEhaOjYGUkYLCxhmRyxnCxhmRkfXCDYSPjcCSicDPjYK7cIaGicaGignVBNn0igfSAwfZtwf0y2GGpsbJB2X1Bw4UBwf0y2GOywXPyxnszwDLEcK7cIaGicaGigLMicHHBgLHC01HDgnOksb7cIaGicaGicaGy29UC3qGzxHWCMvZC2LVBIa9igfSAwfZtwf0y2HBmv0UDhjPBsGPoWOGicaGicaGignVBNn0igfSAwfZid0GywXPyxnnyxrJAfSYxs50CMLTkcK7cIaGicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7zxHWCMvZC2LVBN0GqvmGjhTHBgLHC31GoWOGicaGicaGihrLEhrgAwvSzca9igfSAwfZoWOGicaGicaGigfSAwfZrMLLBgqGpsbHBgLHCZSkicaGicaGicbICMvHAZSkicaGicaGFqOkicaGicaGlY8Gq2HLy2SGAMLRysbZAw1WBguGzMLLBgqGBMfTzqOGicaGicbPzIaODgHPCY52ywXPzezPzwXKCY5PBMnSDwrLCYHJB2X1Bw4PihX8ihzHBgLKvgv4DezPzwXKCY5PBMnSDwrLCYHJB2X1Bw4Pksb7cIaGicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7y29SDw1UFwa7cIaGicaGicaGDgv4DezPzwXKid0Gy29SDw1UoWOGicaGicaGigjYzwfRoWOGicaGicb9cGOGicaGicaVlYbdB21WDxrLzcbJB2X1Bw4kicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7y29SDw1UFwa7cIaGicaGihrLEhrgAwvSzca9ignVBhvTBJSkicaGih0kcIaGicbSzxqGCxvLCNKGpsbGu0vmrunuicr7C2vSzwn0q2XHDxnLFsbguK9nicr7DgHPCY5NzxruywjSzvnVDxjJzsGNCMvHzcCPFsa','oMaSigvYCM9YktSkicaGicaGicaGihrOCM93igvYCM9YoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8GuMvNAxn0zxiGzxHWB3j0ignSzwfUDxaGCM91DgukicaGicaGDhj5ihSkicaGicaGicbfEhbVCNriyw5KBgvYlNjLz2LZDgvYq2XLyw51CfjVDxrLkgfWCcK7cIaGicaGih0Gy2f0y2GGkgnSzwfUDxbfCNjVCIKGEWOGicaGicaGignVBNnVBguUzxjYB3iOj0v4Cg9YDcbJBgvHBNvWihjVDxrLihjLz2LZDhjHDgLVBIbMywLSzwq6jYWGy2XLyw51CevYCM9YlM1LC3nHz2uPoWOGicaGicb9cGOGicaGicbHChaUz2v0kcCVjYWGkhjLCsWGCMvZksa9pIb7cIaGicaGicaGCMvZlMPZB24OEWOGicaGicaGicaGBwvZC2fNztOGjW','lNzHBgLKyxrLrgf0ysHYzxeUyM9KEsWGj2LUC2vYDcCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbYzxeUyM9KEsa9ihSGlI4UCMvXlMjVzhKSic4UlNzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysb9oWOGicaGFqOk','zNDRtNa','tw9KzwWOktS','yM9VBgvHBG','DgHPCY5HDwrPDenVBhvTBNmGpsa','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGBMv4DcGPoWP9ktSk','oWOGigLMicHZy29WzvzHBhvLid09psb1BMrLzMLUzwqGFhWGC2nVCgvwywX1zsa9pt0GBNvSBcKGEWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaZks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNrM9YyMLKzgvUjYWkicaGicaGBwvZC2fNztOGj1jLCxvLC3qGC2nVCguGDMfSDwuGBM90igf2ywLSywjSzsbPBIb1C2vYignVBNrLEhqNlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqOkicaVlYbfEhbVC2uGC2nVCguGAw5MBYbMB3iGCM91DguGAgfUzgXLCNmGkg93BMvYC2HPCcb2zxjPzMLJyxrPB24GAw4Gl2zPCNn0lcaVDxbKyxrLlcaVDxbKyxrLlwnVBxbVC2L0zsKkicbYzxeUx3jLCxvLC3rty29Wzsa9ihSGy29SDw1UoIaN','ignVBxbVC2L0zsb1CgrHDguGC3vJy2vZC2z1BdOG','B2jQzwn0','AxrLBxmU','EMnPyMm','zxHWB3j0','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YigfKANvZDgaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGywrQDxn0BwvUDhmGyxjYyxKkicaGigLMicGHCMvXlMjVzhKUywrQDxn0BwvUDhmGFhWGiufYCMf5lMLZqxjYyxKOCMvXlMjVzhKUywrQDxn0BwvUDhmPihX8ihjLCs5IB2r5lMfKANvZDg1LBNrZlMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNywrQDxn0BwvUDhmGyxjYyxKGAxmGCMvXDwLYzwqGyw5Kig11C3qGBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0k','DxbSB2fKq29UzMLN','qu5eicG','igrHDgeGC3vJy2vZC2z1BgX5igfKzgvKjYWkicaGicaGzgf0ytOGCMvZDwX0laOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUyw1IywHRyw4Gzgf0ysa','cGOGicaGy29UC3qGzgv0ywLSt3bLCMf0Aw9UCYa9igrHDgfBzgv0ywLSs2v5xsb8Fcb7FtSkicaGignVBNn0ihSGAw5Zzxj0oIbPBNnLCNrjDgvTCYa9ifTDlcb1CgrHDgu6ihvWzgf0zuL0zw1Zid0Gw10SigrLBgv0ztOGzgvSzxrLsxrLBxmGpsbBxsb9id0Gzgv0ywLSt3bLCMf0Aw9UCZSkcIaGicbJB25ZDcbKzwXLDgvKsxrLBxmGpsbBxtSkicaGignVBNn0ihvWzgf0zwrjDgvTCYa9ifTDoWOGicaGy29UC3qGAw5Zzxj0zwrjDgvTCYa9ifTDoWOkicaGic8VideUierftevursbVCgvYyxrPB25ZcIaGicbMB3iGkgnVBNn0igL0zw0GB2yGzgvSzxrLsxrLBxmPihSkicaGicaGAwyGkcfPDgvTw2rLDgfPBfbRxsKGDgHYB3CGBMv3ievYCM9YkcDnAxnZAw5NicCGkYbKzxrHAwXqAYaRicCGAw4GzgvSzxrLig9WzxjHDgLVBICPoWOGicaGicbJB25ZDcbKzwXtCwWGpsaNrevmrvrfiezst00GjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGv0HfuKuGjYaRigrLDgfPBfbRicSGjYa9id8NoWOGicaGicbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOzgvSu3fSlcbBAxrLBvTKzxrHAwXqA11DktSkicaGicaGzgvSzxrLzeL0zw1ZlNb1C2GOAxrLBsK7cIaGicb9cIaGicbJB25ZB2XLlMXVzYGNrgvSzxrLzcaNicSGzgvSzxrLzeL0zw1ZlMXLBMD0AcaRicCGzgv0ywLSigL0zw0OCYKNktSkcIaGicaVlYaYlIbvuerbveuGB3bLCMf0Aw9UCWOGicaGzM9YicHJB25ZDcbPDgvTig9MihvWzgf0zuL0zw1Zksb7cIaGicaGigLMicGHAxrLBvTKzxrHAwXqA10PihrOCM93ig5LDYbfCNjVCIGNtwLZC2LUzYaNicSGzgv0ywLSugSGkYaNigLUihvWzgf0zsbVCgvYyxrPB24NktSk','ywDNCMvNyxrLq29UzMLN','oWOkicaGicaGDMfYihzHBgLKyxrPB24GpsbHD2fPDca','zMLUza','ie1VzgvSic0GtxLtuuWGrgf0ywjHC2ukkIbhzw5LCMf0zwq6ia','lY8GtxLtuuWGzw5KCg9PBNqGAw5MB3jTyxrPB24GW6lIGQZIGj0GC2vSzI1KB2n1BwvUDgLUzYbbueKkCM91DgvYlMDLDcGNl2LUzM8NlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC3qGywn0Aw9UCYa9ia','jhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','y29UC3qGzgv0ywLSu3fSid0Gya','igrHDgeGBM90igzVDw5KjYWkicaGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicaGih0PoWOGicaGicaGih0kicaGicaGFsbJyxrJAcaOy2HLy2TfCNjVCIKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMvYAwzPy2f0Aw9UiezHAwXLzcCScIaGicaGicaGicbTzxnZywDLoIaNq291BgqGBM90ihzLCMLMEsbKyxrHigv4Axn0zw5JzsbIzwzVCMuGzgvSzxrLjYWkicaGicaGicaGigrLDgfPBhm6ihbYB2nLC3mUzw52lK5prevFru5wid09psaNzgv2zwXVCg1LBNqNid8Gy2HLy2TfCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFqOk','laOGicaGicb0zxH0oIbPDgvTlG','Bg9VA3vW','DMLLD1f1zxj5','cIaGicaGia','cGOGicaGy29UC29Szs5SB2COya','cIaGicaVlYbmB2CGC3vJy2vZC2z1BcbVCgvYyxrPB24kicaGignVBNnVBguUBg9Nkga','zM9YrwfJAa','DhLWzq','jYWkicaGicaGicbKzxrHAwXuywjSztOGjW','wxHkvLO','y29TCg9Uzw50CW','jhT3AgvYzvjLC3vSDc5ZCwX9','l3vWzgf0zs1JB21WB3nPDguGlsbnEvnrtcbdB21WB3nPDguGDxbKyxrLicHTyxn0zxiTzgv0ywLSkqPYB3v0zxiUCg9ZDcGNl3vWzgf0zs1JB21WB3nPDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC29Szs5SB2COj1jLCxvLC3qGyM9KEsa','lY8GtM8Gywr2yw5JzwqGCxvLCMLLCYbKzwzPBMvK','Aw5MBW','cGOGicaGAwyGkhjLy2fSy0zPzwXKCY5Szw5NDgGGpIaWksb7cIaGicaGic8VieLUAMvJDcbHDwrPDcbJB2X1Bw5ZigTLihjLy2fSy3vSyxrPB24GvvbeqvrfihzPysbOzwXWzxikicaGicaGDgHPCY5FyxbWzw5KvxbKyxrLqxvKAxrdB2X1Bw5ZkhjLy2fSy0zPzwXKCYWGCMvJywXJvMfSDwvZlcbKyxrHlcbLDMvUDenVBNrLEhqPoWOGicaGicbYzwnHBgnwywX1zxmUChvZAcHWCMLTyxj5s2v5vMfSDwuPoWOGicaGicb2yxiGCMvJywXJu3fSid0Gj1vqrefursaNicSGDgHPCY53CML0zvnVDxjJzsaRicCGu0vuicCGkYbYzwnHBgngAwvSzhmUAM9PBIGNlcaNksaRicCGv0HfuKuGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGpsa/jZSkicaGicaGy29UC29Szs5SB2COj1jLy2fSy3vSyxrPBMCGAgvHzgvYihrVDgfSCZONlcb7ihf1zxj5oIbYzwnHBgntCwWSihzHBhvLCZOGCMvJywXJvMfSDwvZih0PoWOGicaGicbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOCMvJywXJu3fSlcbYzwnHBgnwywX1zxmPoWOkicaGicaGDMfYifTYzwnHBgnsB3DZxsa9igf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHZzwXLy3rtCwWSifTWCMLTyxj5s2v5vMfSDwvDktSkicaGicaGDxbKyxrLzeHLywrLCIa9ihjLy2fSy1jVD3nBmf07cIaGicb9cG','BwfZDgvYrgv0ywLS','DMLLD05HBwu','CMvXDwvZDfnJB3bL','ksb8FcaWksaQicHoDw1IzxiOAxrLBs4','Dg9ju09tDhjPBMC','jYWGzw5KCg9PBNroyw1LlcbLEhbVCNrdB25MAwCPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHLEhbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2v4Cg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGzxHWB3j0rxjYB3iUBwvZC2fNzsb9lcbGrxHWB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTLEhbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYigLTCg9YDcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0igLTCg9YDenVBMzPzYa9igv4DhjHy3rjBxbVCNrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkgLTCg9YDenVBMzPzYKGEWOGicaGicaGicaGicaGieLTCg9YDeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','cI8QkGOGkIbdB21WB3nPDguGDxbKyxrLic0GvxbKyxrLigHLywrLCIb3AxrOigDYyw51BgfYigrLDgfPBcbVCgvYyxrPB25ZicHnEvnrtcKkicOVcMfZEw5JihvWzgf0zunVBxbVC2L0zsHKyxrHlcbLDMvUDenVBNrLEhqGpsbUDwXSksb7cIaGy29UC3qGy29UBMvJDgLVBIa9igf3ywL0igrIlMDLDenVBM5Ly3rPB24OktSkicb0CNKGEWOGicaGyxDHAxqGy29UBMvJDgLVBI5IzwDPBLrYyw5Zywn0Aw9UkcK7cGOGicaGy29UC3qGChjPBwfYEuTLEvzHBhvLid0Gzgf0yvT0AgLZlNbYAw1HCNLlzxLDoWOGicaGAwyGkcfWCMLTyxj5s2v5vMfSDwuPihSkicaGicaGDgHYB3CGBMv3ievYCM9YkcDqCMLTyxj5igTLEsaNicSGDgHPCY5WCMLTyxj5s2v5icSGjYbPCYbYzxf1AxjLzcbMB3iGDxbKyxrLjYK7cIaGicb9cGOGicaGlY8Gq2HLy2SGAwyGCMvJB3jKigv4Axn0CYaOywXZBYbZzxj2zxmGyxmGChjLzMv0y2GGB2XKrgf0ysbMB3iGAg9VA3mPcIaGicbJB25ZDcbJAgvJA1nXBca9icDtruXfq1qGkIbguK9nicCGkYb0AgLZlNDYAxrLu291CMnLicSGjYbxsevsrsaNicSGDgHPCY5WCMLTyxj5s2v5icSGjYa9id8NoWOGicaGy29UC3qGw2nOzwnRuM93C10GpsbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOy2HLy2TtCwWSifTWCMLTyxj5s2v5vMfSDwvDktSkicaGigLMicGHy2HLy2TsB3DZihX8ignOzwnRuM93CY5Szw5NDgGGpt09idaPihSkicaGicaGDgHYB3CGBMv3ievYCM9YkcDszwnVCMqGBM90igzVDw5KjYK7cIaGicb9cGOGicaGy29UC3qGB2XKrgf0ysa9ignOzwnRuM93C1SWxtSkcIaGicaVlYbfEhrYywn0igHLywrLCIbKyxrHcIaGicbJB25ZDcbOzwfKzxjeyxrHid0GEYaUlI5KyxrHih07cIaGicbJB25ZDcbKzxrHAwXlzxKGpsaN','zMLSDgvY','id09psbZzwXLy3rLzfrHzYKGEWOGicaGicaGihjVDY5ZzwXLy3rLzca9icD0CNvLjZSkicaGicaGFqOGicaGicbYzxr1CM4GCM93oWOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUie15u1fmigDLDfn0yxrPy0XVB2T1CerHDge6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cGOVkIOkicOGtg9VA3vWigrLBMDHBIbHzhzHBMnLzcbMAwX0zxiGC3vWCg9YDcb1BNr1AYbnEvnrtaOGkI8kyxn5BMmGz2v0tg9VA3vWrgf0yvDPDgHgAwX0zxiOB3b0Aw9UCYKGEWOGihrYEsb7cIaGicaVlYbdAgvJAYbJywnOzsbMAxjZDaOGicaGy29UC3qGy2fJAgvpChrPB25Zid0GEYaUlI5VChrPB25Zlcb0ExbLoIaNzMLSDgvYjYb9oWOGicaGy29UC3qGy2fJAgvKuMvZDwX0id0GyxDHAxqGDgHPCY5NzxrdywnOzwrmB29RDxaOy2fJAgvpChrPB25ZlcaNzMLSDgvYjYK7cIaGicbPzIaOy2fJAgvKuMvZDwX0ksbYzxr1CM4Gy2fJAgvKuMvZDwX0oWOkicaGignVBNn0ihnLBgvJDenVBhvTBNmGpsbVChrPB25ZlNnLBgvJDcb8FcbBjW','zgvZy3jPChrPB24','id0Gke51BwjLCIHPDgvTlG','jZSk','v0HfuKuG','cN07cG','igrHDgeGzgvSzxrLzcbZDwnJzxnZzNvSBhLGktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5YzxnWB25ZzurHDgeScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5NAgfWDxmGzgf0ysa','CK93A2q','lMDLDfn0yxrPy0XVB2T1CerHDgeOC2vSzwn0zwruywCPoWOGicaGFqOkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOGicaGy29UC29Szs5SB2COyfTnEvnrtc1ms1bDicr7BxLZCwXszxf1zxn0swr9igzVDw5Kicr7BgLZDc5Szw5NDgH9ihjLC3vSDhmGAw4GjhTSB29RDxbuAw1LFw1ZycK7cGOGicaGCMv0DxjUihjLCY5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGy291BNq6igXPC3qUBgvUz3rOlaOGicaGicbKyxrHoIbSAxn0laOGicaGicbFBxLZCwW6ihSGCMvXDwvZDeLKoIbTExnXBfjLCxvLC3rjzcWGCxvLCNLuAw1LoIbSB29RDxbuAw1Llcb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOksb9cIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIHGw015u1fmluXluf0GrxjYB3iGjhTTExnXBfjLCxvLC3rjzh06ycWGzxjYB3iPoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigXVB2TPBMCGDxaG','DgL0Bgu','iICScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGDMfYigrHDgeGpsbYzxeUyM9KEs4','ignVBxbVC2L0zsbYzwfKihn1y2nLC3nMDwW6icCGkYaOCMvZDwX0lMnVDw50ihX8idaPicSGjYbYzwnVCMqOCYKNktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5Yzxn1BhqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbJB21WB3nPDguGCMvHzca','lMDLDerHDgf0ywjSzxmOB3b0Aw9UCYK7cGOGicaGlY8GtwvUyw1IywHRyw4GBM9TB3iGyMfYAxmGDw50DwSGrgf0yvrHyMXLCWOGicaGAwyGkhjLC3vSDc5KyxrHicyMiefYCMf5lMLZqxjYyxKOCMvZDwX0lMrHDgePksb7cIaGicaGihjLC3vSDc5KyxrHid0GCMvZDwX0lMrHDgeUBwfWkcHPDgvTlcbPBMrLEcKGpt4GkhSkicaGicaGicaUlI5PDgvTlaOGicaGicaGihjVD251BwvYyxrVCJOGB3b0Aw9UCY5ZDgfYDcaRigLUzgv4icSGmqOGicaGicb9ksK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5QC29UkhjLC3vSDcK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUia','Dg90ywXFyw1VDw50','DgzZtKq','igXVywrLzcbZDwnJzxnZzNvSBhKGzNjVBsbnEvnrtcbMAwXLycK7cIaGicaGih0GzwXZzsb7cIaGicaGicaGy29UC29Szs5LCNjVCIHGu1fmifrLBxbSyxrLigzPBguG','jZSkicaGigXLDcbYzxn1BhqGpsbUDwXSoWOk','CK56zwO','Aw1WB3j0','y29TCg9Uzw50rw5NAw5Lid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9JB21WB25LBNqTzw5NAw5LjYKUy29TCg9Uzw50rw5NAw5LoWO','uujxv20','DxbKyxrLq29TCg9ZAxrL','lMDLDeXVB2T1CerHDgeOC2vHCMnOktSkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOkicaGignVBNnVBguUBg9NkgbBtxLtuuWTteTqxsaKE215C3fSuMvXDwvZDeLKFsbMB3vUzcaKE2XPC3qUBgvUz3rOFsbYzxn1BhrZigLUicr7Bg9VA3vWvgLTzx1TC2aPoWOkicaGihjLDhvYBIbYzxmUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGignVDw50oIbSAxn0lMXLBMD0AcWkicaGicaGzgf0ytOGBgLZDcWkicaGicaGC2vHCMnOoIbZzwfYy2GScIaGicaGif9TExnXBdOGEYbYzxf1zxn0swq6ig15C3fSuMvXDwvZDeLKlcbXDwvYEvrPBwu6igXVB2T1CfrPBwuSihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih0kicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkgbBtxLtuuWTteTqxsbfCNjVCIaKE215C3fSuMvXDwvZDeLKFtPGlcbLCNjVCIK7cIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGBg9VA2LUzYb1Cca','laOGihjLCxvLC3rty29WztOG','igrHDgeNlaOGicaGicbKzxrHAwXZoIbWCM9JzxnZlMvUDI5ot0rfx0vovIa9pt0Gj2rLDMvSB3bTzw50jYa/igvYCM9YlM1LC3nHz2uGoIb1BMrLzMLUzwqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9cN0PoWOk','zxHWB3j0uxvLCNK','cIaGicb0CNKGEWOGicaGicb2yxiGCMvZDwX0id0GyxDHAxqG','svvdwgy','ifDirvjfia','ANreD2e','CMvWBgfJzq','cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigrgAwvSzhmUChvZAcHRzxKGkYaNid0GpYCPoWOGicaGicaGicaGzfzHBhvLCY5WDxnOkhzHBhvLktSkicaGicaGicb9cIaGicaGih0kcIaGicaGic8VieLUAMvJDcbHDwrPDcbJB2X1Bw5ZicH1CgrHDgvKx2f0lcb1CgrHDgvKx2j5ksb1BNr1AYbKzxrHAwWGvvbeqvrfihzPysbOzwXWzxikicaGicaGDgHPCY5FyxbWzw5KvxbKyxrLqxvKAxrdB2X1Bw5ZkgrgAwvSzhmSigrwywX1zxmSigL0zw0Sigv2zw50q29UDgv4DcK7cGOGicaGicbKvMfSDwvZlNb1C2GOAxrLBvTKzxrHAwXqA10PoWOGicaGicbJB25ZDcbKvxbKyxrLu3fSid0Gj1vqrefursaNicSGzgv0ywLSvgfIBgvgDwXSicSGjYbtrvqGjYaRigrgAwvSzhmUAM9PBIGNlcaNksaRicCGv0HfuKuGjYaRigrLDgfPBfbRicSGjYa9id8NoWOGicaGicbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOzfvWzgf0zvnXBcWGzfzHBhvLCYK7cIaGicaGihvWzgf0zwrjDgvTCY5WDxnOkgL0zw0PoWOGicaGFqOGicaGy29UC29Szs5SB2COj1vWzgf0zwqGjYaRihvWzgf0zwrjDgvTCY5Szw5NDgGGkYaNigrLDgfPBcbPDgvTkhmPjYK7cGOGicaGlY8GmY4Gsu5trvjuig9WzxjHDgLVBNmkicaGigzVCIaOy29UC3qGAxrLBsbVzIbPBNnLCNrjDgvTCYKGEWOGicaGicbPDgvTw2zRxsa9ihbYAw1HCNLlzxLwywX1ztSkcIaGicaGic8Vief1Dg8Tz2vUzxjHDguGvvvjrcb1BNr1AYbKzxrHAwWGueSGyMLSysbJBgLLBNqGDgLKywSGBwvUExvWCgX5cIaGicaGic8VicHZyw1HihnLCgvYDgKGzgKGy3jLyxrLq29TCg9ZAxrLoYbRB25ZAxn0zw4GzgvUz2fUigTVBg9TifzbuKniqvikicaGicaGlY8GrevgqvvmvcaOvvvjrcGPksKkicaGicaGAwyGkgL0zw1Bzgv0ywLSugTDid09psb1BMrLzMLUzwqGFhWGAxrLBvTKzxrHAwXqA10Gpt09ig51BgWPihSkicaGicaGicbPDgvTw2rLDgfPBfbRxsa9ihjLCxvPCMuOj3v1AwqNks52nYGPoWOGicaGicb9cG'];a0_0x8163=function(){return _0x205206;};return a0_0x8163();}function toPascalCase(_0x35be11){const _0x1e04dd=a0_0x12be,_0x2406ca={'frrMc':function(_0x23bd26,_0x4d0cb0){return _0x23bd26(_0x4d0cb0);}};if(!_0x35be11||typeof _0x35be11!==_0x1e04dd(0x112))return'';const _0x2f4cd8=_0x2406ca['frrMc'](toCamelCase,_0x35be11);return _0x2f4cd8['charAt'](0x0)['toUpperCase']()+_0x2f4cd8['slice'](0x1);}function buildAuditColumnsSection(_0x178204,_0x441f31){const _0x4dcc66=a0_0x12be,_0x4ad0a2={'zcibc':function(_0x31e531,_0x5e8436){return _0x31e531 in _0x5e8436;},'hYraG':function(_0x28d6f1,_0x443c12){return _0x28d6f1===_0x443c12;},'emzZV':_0x4dcc66(0x168),'yJNdh':'updatedBy'};if(!_0x4ad0a2[_0x4dcc66(0x17b)]('auditColumns',_0x178204))return'';const _0x1d37bf=_0x178204[_0x4dcc66(0x207)];if(_0x4ad0a2[_0x4dcc66(0x123)](_0x1d37bf,![])||_0x1d37bf===null)return'\x0a'+_0x441f31+'//\x20Tabel\x20tanpa\x20audit\x20columns:\x20disable\x20injection\x20helper\x0a'+_0x441f31+'this.auditColumns\x20=\x20null;\x0a';if(_0x4ad0a2[_0x4dcc66(0x123)](typeof _0x1d37bf,'object')&&!Array[_0x4dcc66(0x152)](_0x1d37bf)){const _0x47fca3=[_0x4dcc66(0x231),_0x4ad0a2[_0x4dcc66(0x230)],'updatedAt',_0x4ad0a2['yJNdh']],_0x481256={};_0x47fca3['forEach'](_0x26ae45=>{if(_0x1d37bf[_0x26ae45]!==undefined)_0x481256[_0x26ae45]=_0x1d37bf[_0x26ae45];});const _0x8edb48=JSON['stringify'](_0x481256,null,0x2)['split']('\x0a')['map']((_0x5996ab,_0xcd2d4b)=>_0xcd2d4b===0x0?_0x5996ab:_0x441f31+_0x5996ab)[_0x4dcc66(0x1c5)]('\x0a');return'\x0a'+_0x441f31+'//\x20Custom\x20audit\x20columns\x20mapping\x20dari\x20payload\x0a'+_0x441f31+_0x4dcc66(0x175)+_0x8edb48+';\x0a';}throw new Error('Invalid\x20auditColumns\x20value\x20for\x20'+_0x178204['tableName']+':\x20must\x20be\x20false,\x20null,\x20or\x20object');}function detectTextColumn(_0xfce54){const _0x34394d=a0_0x12be,_0x386e82={'mkudZ':_0x34394d(0x1a3),'hUQla':'code','YxJVZ':'nama'},_0x69a6e6=[_0x34394d(0x23a),'nama',_0x34394d(0x1ab),'judul',_0x386e82['mkudZ'],'deskripsi',_0x386e82['hUQla'],'kode'];for(const _0x4990f1 of _0x69a6e6){const _0x5b0af2=_0xfce54['find'](_0x17093b=>_0x17093b[_0x34394d(0x21a)]()[_0x34394d(0x169)](_0x4990f1));if(_0x5b0af2)return _0x5b0af2;}return _0xfce54[_0x34394d(0x184)](_0x3a6f49=>_0x3a6f49!=='id')||_0xfce54[0x0]||_0x386e82[_0x34394d(0x193)];}function createMysqlMainModuleTemplate(_0xb2f37d){const _0x12f97b=a0_0x12be,_0x4b7fff=toPascalCase(_0xb2f37d);return _0x12f97b(0x13a)+_0xb2f37d+_0x12f97b(0x20c)+_0x4b7fff+'\x27;\x0a\x0a\x20\x20\x20\x20//\x20Configuration\x20options\x0a\x20\x20\x20\x20const\x20loggingEnabled\x20=\x20config.logging\x20!==\x20false;\x0a\x20\x20\x20\x20const\x20apiKeyRequired\x20=\x20!!config.key;\x0a\x0a\x20\x20\x20\x20logger.info({\x0a\x20\x20\x20\x20\x20\x20event:\x20\x27module_starting\x27,\x0a\x20\x20\x20\x20\x20\x20module:\x20moduleNameCapitalized,\x0a\x20\x20\x20\x20\x20\x20port,\x0a\x20\x20\x20\x20\x20\x20cors:\x20process.env.CORS_ENABLED\x20!==\x20\x27false\x27,\x0a\x20\x20\x20\x20\x20\x20helmet:\x20process.env.HELMET_ENABLED\x20===\x20\x27true\x27,\x0a\x20\x20\x20\x20\x20\x20logging:\x20loggingEnabled,\x0a\x20\x20\x20\x20\x20\x20apiKey:\x20apiKeyRequired\x0a\x20\x20\x20\x20},\x20`Starting\x20${moduleNameCapitalized}\x20module`);\x0a\x0a\x20\x20\x20\x20//\x20CORS\x20middleware\x20(konfigurasi\x20via\x20CORS_ENABLED\x20dan\x20CORS_ORIGINS\x20di\x20.env)\x0a\x20\x20\x20\x20app.use(corsMiddleware.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Security\x20headers\x20middleware\x20(konfigurasi\x20via\x20HELMET_ENABLED\x20di\x20.env)\x0a\x20\x20\x20\x20app.use(securityHeaders.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Middleware\x20untuk\x20parsing\x20JSON\x20dengan\x20penanganan\x20error\x0a\x20\x20\x20\x20app.use(bodyParser.json({\x0a\x20\x20\x20\x20\x20\x20verify:\x20(req,\x20res,\x20buf,\x20encoding)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(buf.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20JSON.parse(buf);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20JSON\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27The\x20payload\x20sent\x20is\x20not\x20a\x20valid\x20JSON\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20error.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Invalid\x20JSON\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}));\x0a\x0a\x20\x20\x20\x20app.use(bodyParser.urlencoded({\x20extended:\x20true\x20}));\x0a\x0a\x20\x20\x20\x20//\x20Request\x20logging\x20middleware\x20(Pino-based)\x0a\x20\x20\x20\x20app.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20req.id\x20=\x20req.headers[\x27x-request-id\x27]\x20||\x20uuidv7();\x0a\x20\x20\x20\x20\x20\x20res.set(\x27X-Request-ID\x27,\x20req.id);\x0a\x0a\x20\x20\x20\x20\x20\x20req.log\x20=\x20createRequestLogger({\x0a\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id,\x0a\x20\x20\x20\x20\x20\x20\x20\x20method:\x20req.method,\x0a\x20\x20\x20\x20\x20\x20\x20\x20path:\x20req.path,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ip:\x20req.ip\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20startTime\x20=\x20process.hrtime();\x0a\x0a\x20\x20\x20\x20\x20\x20res.on(\x27finish\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20[seconds,\x20nanoseconds]\x20=\x20process.hrtime(startTime);\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20durationMs\x20=\x20parseFloat((seconds\x20*\x201000\x20+\x20nanoseconds\x20/\x201e6).toFixed(2));\x0a\x20\x20\x20\x20\x20\x20\x20\x20logRequest(req,\x20res,\x20durationMs);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20next();\x0a\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20//\x20Middleware\x20untuk\x20validasi\x20API\x20key\x20jika\x20diperlukan\x20(constant-time\x20comparison)\x0a\x20\x20\x20\x20if\x20(config.key)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20crypto\x20=\x20require(\x27crypto\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20expectedKey\x20=\x20Buffer.from(config.key);\x0a\x0a\x20\x20\x20\x20\x20\x20app.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20apiKey\x20=\x20req.headers[\x27x-api-key\x27];\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!apiKey)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(401).json({\x20error:\x20\x27Unauthorized:\x20Invalid\x20API\x20Key\x27\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20providedKey\x20=\x20Buffer.from(apiKey);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(expectedKey.length\x20!==\x20providedKey.length\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20!crypto.timingSafeEqual(expectedKey,\x20providedKey))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(401).json({\x20error:\x20\x27Unauthorized:\x20Invalid\x20API\x20Key\x27\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20next();\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Rate\x20limiting\x20middleware\x20(store:\x20memory\x20untuk\x20single\x20mode,\x20Redis\x20untuk\x20cluster\x20mode)\x0a\x20\x20\x20\x20rateLimiter.setStore(config.cluster\x20?\x20\x27redis\x27\x20:\x20\x27memory\x27);\x0a\x20\x20\x20\x20app.use(\x27/api\x27,\x20rateLimiter.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Idempotency\x20middleware\x20(protects\x20mutation\x20endpoints\x20from\x20duplicate\x20execution)\x0a\x20\x20\x20\x20if\x20(process.env.IDEMPOTENCY_ENABLED\x20===\x20\x27true\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20app.use(\x27/api\x27,\x20idempotencyMiddleware.middleware());\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Body\x20options\x20middleware\x20(extract\x20{data,\x20options}\x20format\x20dari\x20request\x20body)\x0a\x20\x20\x20\x20app.use(\x27/api\x27,\x20bodyOptionsMiddleware.middleware());\x0a\x0a\x20\x20\x20\x20//\x20Auto-load\x20plugin\x20(jika\x20ada)\x0a\x20\x20\x20\x20const\x20moduleName\x20=\x20\x27'+_0xb2f37d+_0x12f97b(0x117)+_0xb2f37d+'/health\x27,\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20healthInfo\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20status:\x20\x27ok\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString().replace(\x27T\x27,\x20\x27\x20\x27).replace(/\x5c.\x5cd{3}Z$/,\x20\x27\x27),\x0a\x20\x20\x20\x20\x20\x20\x20\x20service:\x20\x27'+_0xb2f37d+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20uptime:\x20process.uptime(),\x0a\x20\x20\x20\x20\x20\x20\x20\x20memory:\x20process.memoryUsage(),\x0a\x20\x20\x20\x20\x20\x20\x20\x20system:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20platform:\x20process.platform,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20nodeVersion:\x20process.version,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20pid:\x20process.pid\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20\x20\x20res.json(healthInfo);\x0a\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20//\x20Muat\x20semua\x20rute\x20dari\x20folder\x20'+_0xb2f37d+'\x0a\x20\x20\x20\x20const\x20modulesDir\x20=\x20path.join(__dirname,\x20\x27'+_0xb2f37d+'\x27);\x0a\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!fs.existsSync(modulesDir))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20fs.mkdirSync(modulesDir,\x20{\x20recursive:\x20true\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Directory\x20${modulesDir}\x20created\x20successfully`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20files\x20=\x20fs.readdirSync(modulesDir);\x0a\x20\x20\x20\x20\x20\x20const\x20endpointFiles\x20=\x20files.filter(file\x20=>\x20file.endsWith(\x27.js\x27));\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(endpointFiles.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`No\x20endpoint\x20files\x20found\x20in\x20${modulesDir}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Add\x20endpoint\x20files\x20to\x20enable\x20API\x20functionality`);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27endpoints_loading\x27,\x20count:\x20endpointFiles.length\x20},\x20`Loading\x20${endpointFiles.length}\x20endpoint(s)`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20file\x20of\x20endpointFiles)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointName\x20=\x20path.basename(file,\x20\x27.js\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPath\x20=\x20path.join(modulesDir,\x20file);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Clear\x20module\x20cache\x20untuk\x20development\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(require.cache[endpointPath])\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20delete\x20require.cache[endpointPath];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20moduleRoutes\x20=\x20require(endpointPath);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPrefix\x20=\x20`/api/'+_0xb2f37d+'/${endpointName}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20app.use(endpointPrefix,\x20moduleRoutes);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logEndpointRegistered(endpointName,\x20endpointPrefix);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Register\x20export\x20routes\x20via\x20centralized\x20handler\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20exportConfig\x20=\x20extractExportConfigFromEndpoint(endpointPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(exportConfig)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ExportHandler.registerRoutes(app,\x20\x27'+_0xb2f37d+_0x12f97b(0x19f)+_0xb2f37d+_0x12f97b(0x224)+_0xb2f37d+_0x12f97b(0x143)+_0xb2f37d+_0x12f97b(0x170)+_0xb2f37d+'\x20API\x20(MySQL\x20Database)\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20status:\x20\x27running\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20database:\x20\x27MySQL\x27\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Error\x20handling\x20middleware\x0a\x20\x20\x20\x20\x20\x20app.use((err,\x20req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Error:\x27,\x20err);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(err\x20instanceof\x20SyntaxError\x20&&\x20err.status\x20===\x20400\x20&&\x20\x27body\x27\x20in\x20err\x20&&\x20!res.headersSent)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20JSON\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27The\x20payload\x20sent\x20is\x20not\x20a\x20valid\x20JSON\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20err.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!res.headersSent)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20on\x20the\x20server\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20err.message\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20next(err);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Hook\x20untuk\x20mount\x20middleware\x20tambahan\x20sebelum\x20catch-all\x20403\x0a\x20\x20\x20\x20\x20\x20//\x20Digunakan\x20oleh\x20runtime\x20untuk\x20register\x20admin\x20endpoint\x20dan\x20middleware\x20eksternal\x20lainnya\x0a\x20\x20\x20\x20\x20\x20if\x20(config.onAppReady\x20&&\x20typeof\x20config.onAppReady\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20config.onAppReady(app);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20app.use((req,\x20res)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Forbidden\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Access\x20to\x20the\x20requested\x20resource\x20is\x20forbidden\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20server\x20=\x20app.listen(port,\x20serverAddress,\x20(err)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(err)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20console.error(`Failed\x20to\x20start\x20${moduleNameCapitalized}\x20server:`,\x20err);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Determine\x20display\x20URL\x20based\x20on\x20serverAddress\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20displayHost\x20=\x20(serverAddress\x20===\x20\x270.0.0.0\x27\x20||\x20!serverAddress)\x20?\x20\x27localhost\x27\x20:\x20serverAddress;\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20logServerReady({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20port,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20module:\x20\x27'+_0xb2f37d+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20healthCheck:\x20`http://${displayHost}:${port}/api/'+_0xb2f37d+_0x12f97b(0x217)+_0xb2f37d+'/info`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20baseUrl:\x20`http://${displayHost}:${port}`\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27\x27);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Execute\x20plugin\x20onAfterServerStart\x20hook\x20(jika\x20ada)\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(plugin\x20&&\x20plugin.onAfterServerStart)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20plugin.onAfterServerStart(app,\x20config);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(pluginError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27plugin_after_start_error\x27,\x20error:\x20pluginError.message\x20},\x20\x27Plugin\x20onAfterServerStart\x20failed\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27SIGINT\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27Menerima\x20sinyal\x20SIGINT,\x20shutting\x20down...\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20process.exit(0);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27SIGTERM\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27Menerima\x20sinyal\x20SIGTERM,\x20shutting\x20down...\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20process.exit(0);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27uncaughtException\x27,\x20(error)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Uncaught\x20Exception:\x27,\x20error);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27unhandledRejection\x27,\x20(reason,\x20promise)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Unhandled\x20Rejection\x20at:\x27,\x20promise,\x20\x27reason:\x27,\x20reason);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27Error\x20saat\x20menjalankan\x20modul\x20'+_0xb2f37d+':\x27,\x20error);\x0a\x20\x20\x20\x20\x20\x20resolve();\x0a\x20\x20\x20\x20}\x0a\x20\x20});\x0a}\x0a\x0amodule.exports\x20=\x20{\x20execute\x20};';}function buildDefaultScopeSQL(_0x459ae3,_0x25448b=''){const _0x3668a4=a0_0x12be,_0x16705f={'IUCXf':function(_0x2b99a0,_0x2e7c23){return _0x2b99a0!==_0x2e7c23;},'TBZKR':_0x3668a4(0x179),'NRWdh':function(_0x35f36d,_0x5b6dda){return _0x35f36d===_0x5b6dda;},'HLiwU':_0x3668a4(0x174),'cPhsM':'string','QBWWm':_0x3668a4(0x1e4)};if(!_0x459ae3||_0x16705f[_0x3668a4(0x1bd)](typeof _0x459ae3,_0x16705f[_0x3668a4(0x20b)]))return'';const _0xd682ef=[];for(const [_0x2f2a2d,_0x1b639f]of Object['entries'](_0x459ae3)){if(_0x16705f['NRWdh'](typeof _0x1b639f,_0x16705f['HLiwU']))_0xd682ef['push'](''+_0x25448b+_0x2f2a2d+'\x20=\x20\x27'+_0x1b639f+'\x27');else{if(typeof _0x1b639f===_0x16705f[_0x3668a4(0x153)])_0xd682ef['push'](''+_0x25448b+_0x2f2a2d+_0x3668a4(0x11c)+_0x1b639f[_0x3668a4(0x1c0)](/'/g,'\x27\x27')+'\x27');else typeof _0x1b639f===_0x3668a4(0x12d)&&_0xd682ef[_0x3668a4(0x12b)](''+_0x25448b+_0x2f2a2d+'\x20=\x20'+_0x1b639f);}}return _0xd682ef[_0x3668a4(0x1c5)](_0x16705f[_0x3668a4(0x1b6)]);}function buildRequestScopeMiddleware(_0x2bc8dd,_0x2467b2){const _0x14931c=a0_0x12be;if(!_0x2bc8dd[_0x14931c(0x19c)])return'';const {column:_0xac73d3,source:_0x115f69,bypassRoles:_0xc767ac}=_0x2bc8dd[_0x14931c(0x19c)],_0x3408cf=JSON[_0x14931c(0x1f5)](_0xc767ac||[]),_0x4ef7a0=(_0x2467b2||'')['replace'](/-/g,'_');return'\x0a//\x20───\x20Request\x20Scope\x20Middleware\x20(Layer\x201\x20RLS)\x20──────────────────────────\x0a//\x20Dynamic\x20per-request\x20filtering\x20by\x20\x27'+_0xac73d3+_0x14931c(0x213)+_0x115f69+'\x0a//\x20Generated\x20from\x20requestScope\x20configuration\x20in\x20payload\x20JSON\x0arouter.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20if\x20(req.method\x20!==\x20\x27POST\x27)\x20return\x20next();\x0a\x0a\x20\x20//\x20Skip\x20when\x20no\x20auth\x20context\x20(endpoint\x20without\x20auth\x20middleware)\x0a\x20\x20if\x20(!req.user)\x20return\x20next();\x0a\x0a\x20\x20//\x20Skip\x20when\x20user\x20has\x20bypass\x20role\x0a\x20\x20const\x20userRoles\x20=\x20req.user.roles\x20||\x20[];\x0a\x20\x20const\x20bypassRoles\x20=\x20'+_0x3408cf+_0x14931c(0x1f0)+_0x115f69+_0x14931c(0x177)+_0xac73d3+_0x14931c(0x160)+_0xac73d3+_0x14931c(0x1e9)+_0xac73d3+_0x14931c(0x139)+_0xac73d3+_0x14931c(0x201)+_0x4ef7a0+'\x27]\x20||\x20req.body;\x0a\x20\x20\x20\x20masterBody[\x27'+_0xac73d3+_0x14931c(0x176);}function createMysqlModelTemplate(_0x1aed70,_0xdfe191,_0x3355d2){const _0x35801a=a0_0x12be,_0x388b46={'fwkNp':_0x35801a(0x147),'HXHxC':_0x35801a(0x18d),'jhQuw':function(_0x5e504f,_0xa7db6e){return _0x5e504f===_0xa7db6e;},'SkKmT':'\x20\x20this.validationConfig\x20=\x20{};\x20//\x20No\x20field\x20validation\x20config','jtDwa':_0x35801a(0x126),'tfsND':function(_0x490c76,_0xa6f8c7){return _0x490c76>_0xa6f8c7;},'rxpmX':function(_0x106440,_0x57ed73){return _0x106440+_0x57ed73;},'yCFYe':function(_0x5beb16,_0x4e8206){return _0x5beb16>_0x4e8206;},'MPtNj':function(_0x40c44d,_0x3a6e0b){return _0x40c44d+_0x3a6e0b;},'UfUYF':function(_0x5ebe62,_0x3f77a7){return _0x5ebe62+_0x3f77a7;},'CyABc':_0x35801a(0x23e),'rNzej':'file:','rOwkd':function(_0xe099fc,_0x219b8d){return _0xe099fc(_0x219b8d);},'FBJlw':'[\x27kode\x27,\x20\x27nama\x27,\x20\x27all\x27]','RrAds':_0x35801a(0x197),'uJaYa':_0x35801a(0x187),'qrRmu':_0x35801a(0x17f)},_0x2192af=_0x3355d2['fieldName'][_0x35801a(0x21d)](_0x3f1f20=>'\x27'+_0x3f1f20+'\x27')[_0x35801a(0x1c5)](',\x20'),_0x490080=toPascalCase(_0xdfe191),_0x7be755=_0x3355d2['primaryKey']||'id',_0x13a985=_0x388b46[_0x35801a(0x1a9)](detectTextColumn,_0x3355d2[_0x35801a(0x1f3)]),_0x43633e=new Date()[_0x35801a(0x19e)](),_0x187175=buildAuditColumnsSection(_0x3355d2,'\x20\x20'),_0x1e976d=_0x3355d2[_0x35801a(0x1c9)]&&_0x3355d2['defaultScope']['lookup']?_0x388b46[_0x35801a(0x1a9)](buildDefaultScopeSQL,_0x3355d2['defaultScope']['lookup']):'',_0x5bee14=_0x3355d2['defaultScope']&&_0x3355d2[_0x35801a(0x1c9)]['read']?buildDefaultScopeSQL(_0x3355d2['defaultScope'][_0x35801a(0x115)]):'',_0x1d01be={};return _0x3355d2['fieldValidation']&&Array['isArray'](_0x3355d2[_0x35801a(0x15f)])&&_0x3355d2['fieldValidation'][_0x35801a(0x190)](_0x5890f2=>{const _0x3fa837=_0x35801a;['date',_0x3fa837(0x1ca),'timestamp',_0x3fa837(0x1ea)]['includes'](_0x5890f2[_0x3fa837(0x191)])&&(_0x1d01be[_0x5890f2['name']]={'type':_0x5890f2['type'],'format':_0x5890f2[_0x3fa837(0x157)]&&_0x5890f2[_0x3fa837(0x157)]['format']||_0x388b46[_0x3fa837(0x172)]});}),_0x35801a(0x131)+_0x490080+_0x35801a(0x185)+_0x43633e+_0x35801a(0x166)+_0x3355d2[_0x35801a(0x233)]+_0x35801a(0x223)+_0x7be755+_0x35801a(0x151)+_0x3355d2['fieldName'][_0x35801a(0x121)]+'\x0a*\x20Database:\x20MySQL\x0a*/\x0aclass\x20'+_0x490080+_0x35801a(0x1d8)+_0x2192af+'\x0a\x20\x20];\x0a\x0a\x20\x20const\x20datatablesWhere\x20=\x20'+(_0x3355d2['datatablesWhere']?JSON[_0x35801a(0x1f5)](_0x3355d2['datatablesWhere']):_0x388b46[_0x35801a(0x111)])+_0x35801a(0x10b)+_0x3355d2['tableName']+_0x35801a(0x10c)+_0x7be755+'\x27;\x0a\x0a\x20\x20//\x20Read/Write\x20source\x20configuration\x0a\x20\x20this.viewName\x20=\x20\x27'+(_0x3355d2['viewName']||_0x3355d2[_0x35801a(0x233)])+_0x35801a(0x1c4)+(_0x3355d2[_0x35801a(0x19b)]||_0x3355d2['tableName'])+_0x35801a(0x1df)+_0x3355d2['tableName']+_0x35801a(0x1a5)+_0x187175+'\x0a\x20\x20//\x20Flag\x20untuk\x20self-documenting\x20API\x20(endpoint\x20/info)\x0a\x20\x20this.hasViewQuery\x20=\x20'+!!_0x3355d2['viewQuery']+';\x0a\x20\x20this.hasExportQuery\x20=\x20'+!!_0x3355d2[_0x35801a(0x1bb)]+';\x0a'+(_0x388b46['yCFYe'](Object['keys'](_0x1d01be)['length'],0x0)?_0x35801a(0x106)+JSON['stringify'](_0x1d01be,null,0x4)['split']('\x0a')['join']('\x0a\x20\x20')+';\x0a':'')+(_0x3355d2[_0x35801a(0x17e)]&&_0x3355d2[_0x35801a(0x17e)]['fields']?'\x0a\x20\x20//\x20File\x20upload\x20fields\x20(JSON\x20columns)\x0a\x20\x20this.fileFields\x20=\x20'+JSON[_0x35801a(0x1f5)](Object['keys'](_0x3355d2[_0x35801a(0x17e)]['fields']))+';\x0a':'')+_0x35801a(0x1ec)+((()=>{const _0x4692d4=_0x35801a;if(!_0x3355d2[_0x4692d4(0x15f)]||!Array[_0x4692d4(0x152)](_0x3355d2[_0x4692d4(0x15f)])||_0x388b46[_0x4692d4(0x122)](_0x3355d2['fieldValidation'][_0x4692d4(0x121)],0x0))return _0x388b46['SkKmT'];const _0x467780=_0x3355d2['fieldValidation']['map'](_0x3b17cb=>{const _0x23f9eb=_0x4692d4,_0x257437=JSON['stringify'](_0x3b17cb['constraints']||{},null,0x6)['replace'](/\n/g,_0x388b46[_0x23f9eb(0x215)]);return'\x20\x20\x20\x20\x20\x20\x27'+_0x3b17cb[_0x23f9eb(0x23a)]+'\x27:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20type:\x20\x27'+_0x3b17cb[_0x23f9eb(0x191)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20constraints:\x20'+_0x257437+_0x23f9eb(0x107);})[_0x4692d4(0x1c5)](',\x0a');return'\x20\x20this.validationConfig\x20=\x20{\x0a'+_0x467780+_0x4692d4(0x1e2);})())+'\x0a\x0a\x20\x20//\x20Model\x20metadata\x0a\x20\x20this.modelMetadata\x20=\x20{\x0a\x20\x20\x20\x20endpointName:\x20\x27'+_0xdfe191+'\x27,\x0a\x20\x20\x20\x20moduleName:\x20\x27'+_0x1aed70+_0x35801a(0x209)+_0x3355d2['tableName']+'\x27,\x0a\x20\x20\x20\x20databaseType:\x20\x27mysql\x27,\x0a\x20\x20\x20\x20primaryKey:\x20\x27'+_0x7be755+_0x35801a(0x1fb)+_0x3355d2[_0x35801a(0x1f3)][_0x35801a(0x121)]+_0x35801a(0x1da)+_0x43633e+_0x35801a(0x220)+(_0x3355d2[_0x35801a(0x228)]?Object[_0x35801a(0x21c)](_0x3355d2[_0x35801a(0x228)])[_0x35801a(0x21d)](([_0x2af04b,_0x2eb13c])=>'\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(typeof\x20\x22'+_0x2eb13c+_0x35801a(0x239)+_0x2eb13c+_0x35801a(0x130)+_0x2eb13c+'\x22.replace(\x27file:\x27,\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20filePath\x20=\x20path.join(__dirname,\x20relativePath);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(filePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x2af04b+'\x22]\x20=\x20fs.readFileSync(filePath,\x20\x27utf8\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`SQL\x20Template\x20'+_0x2af04b+_0x35801a(0x1b1)+_0x2af04b+'\x20not\x20found:\x20${filePath}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x2af04b+_0x35801a(0x221)+_0x2af04b+'\x22]\x20=\x20\x22'+_0x2eb13c+'\x22;\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(`Error\x20loading\x20MySQL\x20template\x20'+_0x2af04b+':`,\x20error);\x0a\x20\x20\x20\x20templates[\x22'+_0x2af04b+'\x22]\x20=\x20null;\x0a\x20\x20}')['join'](''):_0x388b46['RrAds'])+'\x0a\x0a\x20\x20return\x20templates;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getListQuery\x20untuk\x20MySQL\x20syntax\x0a\x20*/\x0agetListQuery(options\x20=\x20{})\x20{\x0a\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x3355d2['datatablesQuery'][_0x35801a(0x1c0)](/\$\{tableName\}/g,_0x35801a(0x187))+'\x0a\x20\x20`.trim();\x0a\x0a\x20\x20//\x20Convert\x20PostgreSQL\x20syntax\x20to\x20MySQL\x20if\x20needed\x0a\x20\x20baseQuery\x20=\x20this.convertToMysqlSQL(baseQuery);\x0a\x0a\x20\x20return\x20baseQuery;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getReadQuery\x20untuk\x20endpoint\x20/read\x0a\x20*\x20Prioritas:\x20viewName\x20→\x20viewQuery\x20→\x20tableName\x20(SELECT\x20*\x20FROM\x20readSource)\x0a\x20*/\x0agetReadQuery(options\x20=\x20{})\x20{\x0a\x20\x20if\x20(this.viewName\x20&&\x20this.viewName\x20!==\x20this.table)\x20{\x0a\x20\x20\x20\x20return\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.viewName;\x0a\x20\x20}\x0a'+(_0x3355d2[_0x35801a(0x18c)]?_0x35801a(0x1d2)+_0x3355d2['viewQuery'][_0x35801a(0x1c0)](/\$\{tableName\}/g,_0x388b46[_0x35801a(0x22c)])+_0x35801a(0x212):_0x35801a(0x227))+'\x0a}\x0a\x0a/**\x0a\x20*\x20Convert\x20PostgreSQL\x20SQL\x20syntax\x20to\x20MySQL\x0a\x20*/\x0aconvertToMysqlSQL(sql)\x20{\x0a\x20\x20//\x20ILIKE\x20→\x20LIKE\x20(MySQL\x20case-insensitive\x20by\x20default\x20with\x20utf8mb4\x20collation)\x0a\x20\x20sql\x20=\x20sql.replace(/\x5cbILIKE\x5cb/gi,\x20\x27LIKE\x27);\x0a\x20\x20//\x20NOW()\x20dan\x20CURRENT_DATE\x20sudah\x20sama\x20di\x20MySQL\x0a\x20\x20return\x20sql;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getDatatables\x20untuk\x20MySQL\x20dengan\x20pagination\x20yang\x20tepat\x0a\x20*\x20Paritas\x20fungsional\x20dengan\x20Oracle\x20dan\x20PostgreSQL\x20getDatatables\x0a\x20*/\x0aasync\x20getDatatables(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedDatatables(options);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20return\x20cachedResult;\x0a\x0a\x20\x20\x20\x20const\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue\x20=\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy\x20=\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20perPage\x20=\x2010,\x0a\x20\x20\x20\x20\x20\x20start\x20=\x200,\x0a\x20\x20\x20\x20\x20\x20sort_columns\x20=\x20[],\x0a\x20\x20\x20\x20\x20\x20filters\x20=\x20{},\x0a\x20\x20\x20\x20\x20\x20advancedFilters\x20=\x20[]\x0a\x20\x20\x20\x20}\x20=\x20options;\x0a\x0a\x20\x20\x20\x20//\x20Resolve\x20sort\x20columns\x20dengan\x20prioritas:\x20sort_columns\x20>\x20order[0][column]\x20>\x20default\x0a\x20\x20\x20\x20let\x20resolvedSortColumns\x20=\x20sort_columns;\x0a\x0a\x20\x20\x20\x20//\x20Fallback:\x20cek\x20format\x20DataTables\x20bawaan\x20(order[0][column]\x20dan\x20order[0][dir])\x0a\x20\x20\x20\x20if\x20((!resolvedSortColumns\x20||\x20resolvedSortColumns.length\x20===\x200)\x20&&\x0a\x20\x20\x20\x20\x20\x20\x20\x20options[\x27order[0][column]\x27]\x20!==\x20undefined\x20&&\x20options[\x27order[0][dir]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20columnIndex\x20=\x20parseInt(options[\x27order[0][column]\x27]);\x0a\x20\x20\x20\x20\x20\x20const\x20direction\x20=\x20options[\x27order[0][dir]\x27];\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(columnIndex\x20>=\x200\x20&&\x20columnIndex\x20<\x20this.validFields.length)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20resolvedSortColumns\x20=\x20[{\x20column:\x20this.validFields[columnIndex],\x20direction:\x20direction.toUpperCase()\x20}];\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20orderClause\x20=\x20this.buildSortColumnsClause(resolvedSortColumns);\x0a\x0a\x20\x20\x20\x20const\x20baseQuery\x20=\x20this.getListQuery(options);\x0a\x0a\x20\x20\x20\x20//\x20Build\x20WHERE\x20clause\x20(parameterized)\x0a\x20\x20\x20\x20const\x20searchResult\x20=\x20this.buildWhereClause(searchValue,\x20searchBy);\x0a\x20\x20\x20\x20let\x20whereClauseSql\x20=\x20searchResult.sql;\x0a\x20\x20\x20\x20let\x20whereParams\x20=\x20[...searchResult.params];\x0a\x0a\x20\x20\x20\x20//\x20Build\x20filter\x20clause\x0a\x20\x20\x20\x20const\x20filterClause\x20=\x20this.buildObjectFilterClause(filters);\x0a\x20\x20\x20\x20if\x20(filterClause)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${filterClause}`;\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${filterClause}`;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x20dari\x20request\x20body\x0a\x20\x20\x20\x20if\x20(options.where)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20complexResult\x20=\x20this.buildComplexWhereClause(options.where,\x20whereParams);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams\x20=\x20complexResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Advanced\x20filters\x20support\x0a\x20\x20\x20\x20if\x20(advancedFilters\x20&&\x20advancedFilters.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20advResult\x20=\x20this.buildAdvancedFilterCondition(advancedFilters);\x0a\x20\x20\x20\x20\x20\x20if\x20(advResult.sql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${advResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${advResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams.push(...advResult.params);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Check\x20if\x20query\x20needs\x20subquery\x20wrapping\x20(CTE\x20or\x20JOIN)\x0a\x20\x20\x20\x20const\x20isCteQuery\x20=\x20baseQuery.toLowerCase().trim().startsWith(\x27with\x27);\x0a\x20\x20\x20\x20const\x20hasJoin\x20=\x20/\x5cb(inner|left|right|cross|full)\x5cs+join\x5cb/i.test(baseQuery)\x20||\x20/\x5cbjoin\x5cb/i.test(baseQuery);\x0a\x20\x20\x20\x20const\x20needsSubquery\x20=\x20isCteQuery\x20||\x20hasJoin;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20total\x20records\x0a\x20\x20\x20\x20const\x20countTotalQuery\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20`SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(${baseQuery})\x20as\x20base_query`\x20:\x0a\x20\x20\x20\x20\x20\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20const\x20countTotalResult\x20=\x20await\x20db.executeQuery(countTotalQuery);\x0a\x20\x20\x20\x20const\x20totalRecords\x20=\x20countTotalResult\x20&&\x20countTotalResult[0]\x20?\x20parseInt(countTotalResult[0].total)\x20:\x200;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20filtered\x20records\x0a\x20\x20\x20\x20let\x20filteredRecords\x20=\x20totalRecords;\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20countFilteredQuery\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20\x20\x20`SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(${baseQuery})\x20as\x20base_query\x20${whereClauseSql}`\x20:\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x20\x27\x20+\x20whereClauseSql;\x0a\x20\x20\x20\x20\x20\x20const\x20countFilteredResult\x20=\x20await\x20db.executeQuery(countFilteredQuery,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x20\x20\x20\x20\x20\x20filteredRecords\x20=\x20countFilteredResult\x20&&\x20countFilteredResult[0]\x20?\x20parseInt(countFilteredResult[0].total)\x20:\x200;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20MySQL\x20pagination\x20using\x20LIMIT/OFFSET\x0a\x20\x20\x20\x20const\x20query\x20=\x20needsSubquery\x20?\x0a\x20\x20\x20\x20\x20\x20`SELECT\x20*\x20FROM\x20(${baseQuery})\x20as\x20base_query\x20${whereClauseSql\x20||\x20\x27\x27}\x20${orderClause}\x20LIMIT\x20${perPage}\x20OFFSET\x20${start}`\x20:\x0a\x20\x20\x20\x20\x20\x20`${baseQuery}\x20${whereClauseSql\x20||\x20\x27\x27}\x20${orderClause}\x20LIMIT\x20${perPage}\x20OFFSET\x20${start}`;\x0a\x0a\x20\x20\x20\x20console.log(\x27Final\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27Query\x20Parameters:\x27,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20[]);\x0a\x20\x20\x20\x20const\x20rawData\x20=\x20await\x20db.executeQuery(query,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20//\x20Format\x20data:\x20MySQL\x20returns\x20lowercase\x20keys\x20natively\x0a\x20\x20\x20\x20const\x20data\x20=\x20rawData\x20?\x20rawData.map((row,\x20index)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20formatted\x20=\x20this.formatResponseData(row);\x0a\x20\x20\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20...formatted,\x0a\x20\x20\x20\x20\x20\x20\x20\x20rownumerator:\x20start\x20+\x20index\x20+\x201\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20})\x20:\x20[];\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20draw:\x20parseInt(options.draw\x20||\x20\x271\x27,\x2010),\x0a\x20\x20\x20\x20\x20\x20recordsTotal:\x20totalRecords,\x0a\x20\x20\x20\x20\x20\x20recordsFiltered:\x20filteredRecords,\x0a\x20\x20\x20\x20\x20\x20data:\x20data\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20//\x20Cache\x20result\x0a\x20\x20\x20\x20await\x20this.setCachedDatatables(options,\x20result);\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20getDatatables:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Build\x20WHERE\x20clause\x20untuk\x20search\x20(parameterized\x20query)\x0a\x20*\x20@returns\x20{Object}\x20{\x20sql:\x20string,\x20params:\x20array\x20}\x0a\x20*/\x0abuildWhereClause(searchValue,\x20searchBy)\x20{\x0a\x20\x20if\x20(!searchValue\x20||\x20searchValue\x20===\x20\x27\x27)\x20{\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a\x20\x20}\x0a\x0a\x20\x20const\x20params\x20=\x20[];\x0a\x20\x20const\x20searchPattern\x20=\x20`%${searchValue}%`;\x0a\x0a\x20\x20if\x20(searchBy\x20===\x20\x27all\x27)\x20{\x0a\x20\x20\x20\x20const\x20searchableFields\x20=\x20this.datatablesWhere.filter(field\x20=>\x20field\x20!==\x20\x27all\x27);\x0a\x20\x20\x20\x20if\x20(searchableFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20conditions\x20=\x20searchableFields.map(field\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20params.push(searchPattern);\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20`${field}\x20LIKE\x20?`;\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20return\x20{\x20sql:\x20`WHERE\x20(${conditions.join(\x27\x20OR\x20\x27)})`,\x20params\x20};\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20else\x20if\x20(this.validFields.includes(searchBy))\x20{\x0a\x20\x20\x20\x20params.push(searchPattern);\x0a\x20\x20\x20\x20return\x20{\x20sql:\x20`WHERE\x20${searchBy}\x20LIKE\x20?`,\x20params\x20};\x0a\x20\x20}\x0a\x0a\x20\x20return\x20{\x20sql:\x20\x27\x27,\x20params:\x20[]\x20};\x0a}\x0a\x0a/**\x0a\x20*\x20Build\x20filter\x20clause\x20dari\x20object\x20filters\x0a\x20*\x20@param\x20{Object}\x20filters\x20-\x20Filter\x20object\x20{column:\x20value}\x0a\x20*\x20@returns\x20{string}\x20Filter\x20conditions\x20SQL\x20(tanpa\x20WHERE\x20prefix)\x0a\x20*/\x0abuildObjectFilterClause(filters)\x20{\x0a\x20\x20if\x20(!filters\x20||\x20typeof\x20filters\x20!==\x20\x27object\x27\x20||\x20Object.keys(filters).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20return\x20\x27\x27;\x0a\x20\x20}\x0a\x0a\x20\x20const\x20conditions\x20=\x20[];\x0a\x20\x20for\x20(const\x20[column,\x20value]\x20of\x20Object.entries(filters))\x20{\x0a\x20\x20\x20\x20if\x20(!this.validFields.includes(column))\x20continue;\x0a\x20\x20\x20\x20if\x20(value\x20===\x20null\x20||\x20value\x20===\x20undefined\x20||\x20value\x20===\x20\x27\x27\x20||\x20value\x20===\x20\x27all\x27\x20||\x20value\x20===\x20\x27-\x27)\x20continue;\x0a\x0a\x20\x20\x20\x20const\x20escapedValue\x20=\x20value.toString().replace(/\x27/g,\x20\x22\x27\x27\x22);\x0a\x20\x20\x20\x20conditions.push(`${column}\x20=\x20\x27${escapedValue}\x27`);\x0a\x20\x20}\x0a\x0a\x20\x20return\x20conditions.length\x20>\x200\x20?\x20conditions.join(\x27\x20AND\x20\x27)\x20:\x20\x27\x27;\x0a}\x0a\x0a/**\x0a\x20*\x20Get\x20list\x20data\x20dengan\x20pagination\x20untuk\x20MySQL\x0a\x20*/\x0aasync\x20getList(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedList(options);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20{\x20page:\x20p\x20=\x20null,\x20perPage:\x20pp\x20=\x2010,\x20searchValue:\x20sv\x20=\x20\x27\x27,\x20sort_columns:\x20sc\x20=\x20[],\x20where:\x20w\x20=\x20null\x20}\x20=\x20options;\x0a\x20\x20\x20\x20\x20\x20const\x20scInfo\x20=\x20sc\x20&&\x20sc.length\x20>\x200\x20?\x20sc.map(s\x20=>\x20`${s.column}:${s.direction}`).join(\x27,\x27)\x20:\x20\x27default\x27;\x0a\x20\x20\x20\x20\x20\x20console.log(`[Cache]\x20HIT\x20for\x20list\x20-\x20page:${p},\x20perPage:${pp},\x20sort:${scInfo},\x20search:${sv\x20||\x20\x27none\x27}${w\x20?\x20\x27,\x20where:yes\x27\x20:\x20\x27\x27}`);\x0a\x20\x20\x20\x20\x20\x20return\x20cachedResult;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20{\x0a\x20\x20\x20\x20\x20\x20page\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20perPage\x20=\x2010,\x0a\x20\x20\x20\x20\x20\x20searchValue\x20=\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy\x20=\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20sort_columns\x20=\x20[],\x0a\x20\x20\x20\x20\x20\x20where\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20select\x20=\x20null,\x0a\x20\x20\x20\x20\x20\x20limit\x20=\x201000\x0a\x20\x20\x20\x20}\x20=\x20options;\x0a\x0a\x20\x20\x20\x20const\x20paginate\x20=\x20page\x20!==\x20null;\x0a\x20\x20\x20\x20const\x20scInfo\x20=\x20sort_columns\x20&&\x20sort_columns.length\x20>\x200\x20?\x20sort_columns.map(s\x20=>\x20`${s.column}:${s.direction}`).join(\x27,\x27)\x20:\x20\x27default\x27;\x0a\x20\x20\x20\x20const\x20cacheInfo\x20=\x20`page:${page},\x20perPage:${perPage},\x20sort:${scInfo},\x20search:${searchValue\x20||\x20\x27none\x27}${where\x20?\x20\x27,\x20where:yes\x27\x20:\x20\x27\x27}`;\x0a\x0a\x20\x20\x20\x20console.log(`[Cache]\x20MISS\x20for\x20list\x20-\x20${cacheInfo}`);\x0a\x0a\x20\x20\x20\x20//\x201.\x20Mendapatkan\x20query\x20dasar\x0a\x20\x20\x20\x20let\x20baseQuery;\x0a\x20\x20\x20\x20if\x20(select\x20&&\x20Array.isArray(select)\x20&&\x20select.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20selectedValidColumns\x20=\x20select.filter(col\x20=>\x20this.validFields.includes(col));\x0a\x20\x20\x20\x20\x20\x20if\x20(selectedValidColumns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20\x27SELECT\x20\x27\x20+\x20selectedValidColumns.map(col\x20=>\x20\x27`\x27\x20+\x20col\x20+\x20\x27`\x27).join(\x27,\x20\x27)\x20+\x20\x27\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20baseQuery\x20=\x20this.getReadQuery(options);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Deteksi\x20apakah\x20query\x20mengandung\x20JOIN\x20atau\x20CTE\x20(perlu\x20subquery\x20wrapping)\x0a\x20\x20\x20\x20const\x20isCteQuery\x20=\x20baseQuery.toLowerCase().trim().startsWith(\x27with\x27);\x0a\x20\x20\x20\x20const\x20hasJoin\x20=\x20/\x5cb(inner|left|right|cross|full)\x5cs+join\x5cb/i.test(baseQuery)\x20||\x20/\x5cbjoin\x5cb/i.test(baseQuery);\x0a\x20\x20\x20\x20const\x20needsSubquery\x20=\x20isCteQuery\x20||\x20hasJoin;\x0a\x0a\x20\x20\x20\x20const\x20searchResult\x20=\x20this.buildWhereClause(searchValue,\x20searchBy);\x0a\x20\x20\x20\x20let\x20whereClauseSql\x20=\x20searchResult.sql;\x0a\x20\x20\x20\x20let\x20whereParams\x20=\x20[...searchResult.params];\x0a\x20\x20\x20\x20const\x20orderClause\x20=\x20this.buildSortColumnsClause(sort_columns);\x0a'+(_0x5bee14?'\x0a\x20\x20\x20\x20//\x20Default\x20scope\x20filter\x20untuk\x20read\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20'+_0x5bee14+'\x20AND\x20`\x20+\x20whereClauseSql.replace(/^WHERE\x5cs+/i,\x20\x27\x27);\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20\x27WHERE\x20'+_0x5bee14+'\x27;\x0a\x20\x20\x20\x20}\x0a':'')+'\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x20dari\x20request\x20body\x0a\x20\x20\x20\x20if\x20(where)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20complexResult\x20=\x20this.buildComplexWhereClause(where,\x20whereParams);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams\x20=\x20complexResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Count\x20total\x20unfiltered\x20records\x0a\x20\x20\x20\x20const\x20countTotalQuery\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x27\x0a\x20\x20\x20\x20\x20\x20:\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20const\x20countTotalResult\x20=\x20await\x20db.executeQuery(countTotalQuery);\x0a\x20\x20\x20\x20const\x20totalUnfiltered\x20=\x20countTotalResult\x20&&\x20countTotalResult[0]\x20?\x20parseInt(countTotalResult[0].total)\x20:\x200;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20filtered\x20records\x0a\x20\x20\x20\x20let\x20totalRecords\x20=\x20totalUnfiltered;\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20countQuery\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20countResult\x20=\x20await\x20db.executeQuery(countQuery,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x20\x20\x20\x20\x20\x20totalRecords\x20=\x20countResult\x20&&\x20countResult[0]\x20?\x20parseInt(countResult[0].total)\x20:\x200;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20query\x20berdasarkan\x20mode\x20paginasi\x20(subquery\x20wrapping\x20untuk\x20JOIN/CTE)\x0a\x20\x20\x20\x20let\x20query;\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20offset\x20=\x20(page\x20-\x201)\x20*\x20perPage;\x0a\x20\x20\x20\x20\x20\x20query\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20*\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20\x27\x20LIMIT\x20\x27\x20+\x20perPage\x20+\x20\x27\x20OFFSET\x20\x27\x20+\x20offset\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20baseQuery\x20+\x20\x27\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20`\x20LIMIT\x20${perPage}\x20OFFSET\x20${offset}`;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20query\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20*\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20\x27\x20LIMIT\x20\x27\x20+\x20limit\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20baseQuery\x20+\x20\x27\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20`\x20LIMIT\x20${limit}`;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27List\x20SQL\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27List\x20Query\x20Parameters:\x27,\x20whereParams);\x0a\x20\x20\x20\x20const\x20rawData\x20=\x20await\x20db.executeQuery(query,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20const\x20data\x20=\x20rawData\x20?\x20rawData.map((row)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20this.formatResponseData(row);\x0a\x20\x20\x20\x20})\x20:\x20[];\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20data:\x20data\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20totalPages\x20=\x20Math.ceil(totalRecords\x20/\x20perPage);\x0a\x20\x20\x20\x20\x20\x20result.pagination\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20current_page:\x20page,\x0a\x20\x20\x20\x20\x20\x20\x20\x20per_page:\x20perPage,\x0a\x20\x20\x20\x20\x20\x20\x20\x20total_records:\x20totalRecords,\x0a\x20\x20\x20\x20\x20\x20\x20\x20total_pages:\x20totalPages,\x0a\x20\x20\x20\x20\x20\x20\x20\x20has_next:\x20page\x20<\x20totalPages,\x0a\x20\x20\x20\x20\x20\x20\x20\x20has_previous:\x20page\x20>\x201\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Cache\x20result\x0a\x20\x20\x20\x20await\x20this.setCachedList(options,\x20result);\x0a\x20\x20\x20\x20console.log(`[Cache]\x20SET\x20for\x20list\x20-\x20${cacheInfo}`);\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20getList:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getLookupData\x20untuk\x20MySQL\x20(dynamic\x20search)\x0a\x20*/\x0aasync\x20getLookupData(search)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20query\x20=\x20`SELECT\x20'+_0x7be755+',\x20'+_0x13a985+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20WHERE\x20'+_0x13a985+'\x20LIKE\x20?'+(_0x1e976d?_0x35801a(0x1e4)+_0x1e976d:'')+_0x35801a(0x1dd)+_0x13a985+_0x35801a(0x1fd)+_0x7be755+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x13a985+'\x0a\x20\x20\x20\x20}));\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20MySQL\x20getLookupData:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Dynamic\x20lookup\x20dengan\x20extra\x20filters\x20untuk\x20MySQL\x0a\x20*/\x0aasync\x20getLookupDataDynamic(search,\x20extraFilters\x20=\x20{})\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20let\x20params\x20=\x20[];\x0a\x20\x20\x20\x20let\x20whereConditions\x20=\x20[];\x0a'+(_0x1e976d?'\x0a\x20\x20\x20\x20//\x20Default\x20scope\x20filter\x0a\x20\x20\x20\x20whereConditions.push(\x27'+_0x1e976d+'\x27);\x0a':'')+'\x0a\x20\x20\x20\x20if\x20(search)\x20{\x0a\x20\x20\x20\x20\x20\x20whereConditions.push(`'+_0x13a985+'\x20LIKE\x20?`);\x0a\x20\x20\x20\x20\x20\x20params.push(`%${search}%`);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Add\x20extra\x20filters\x0a\x20\x20\x20\x20if\x20(extraFilters\x20&&\x20Object.keys(extraFilters).length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(extraFilters))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(this.validFields.includes(key)\x20&&\x20value\x20!==\x20null\x20&&\x20value\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereConditions.push(`${key}\x20=\x20?`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20params.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20whereClause\x20=\x20whereConditions.length\x20>\x200\x20?\x20\x27WHERE\x20\x27\x20+\x20whereConditions.join(\x27\x20AND\x20\x27)\x20:\x20\x27\x27;\x0a\x0a\x20\x20\x20\x20const\x20query\x20=\x20`SELECT\x20'+_0x7be755+',\x20'+_0x13a985+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20${whereClause}\x20ORDER\x20BY\x20'+_0x13a985+'\x20LIMIT\x20100`;\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params.length\x20>\x200\x20?\x20params\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20return\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x7be755+_0x35801a(0x18a)+_0x13a985+_0x35801a(0x1de)+_0x7be755+',\x20'+_0x13a985+'\x20FROM\x20${this.getTableSource(\x27read\x27)}'+(_0x1e976d?_0x35801a(0x1be)+_0x1e976d:'')+'\x20ORDER\x20BY\x20'+_0x13a985+_0x35801a(0x1f7)+_0x7be755+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x13a985+'\x0a\x20\x20\x20\x20}));\x0a\x20\x20\x20\x20await\x20this.setCachedLookup(cacheOptions,\x20cacheData,\x20\x27static\x27);\x0a\x0a\x20\x20\x20\x20//\x20Return\x20dengan\x20selected\x20flag\x0a\x20\x20\x20\x20return\x20data.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20row\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x7be755+',\x0a\x20\x20\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x13a985+'\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20if\x20(item.'+_0x7be755+_0x35801a(0x1a2)+_0x7be755+'\x27,\x20\x27'+_0x13a985+_0x35801a(0x12f)+_0x7be755+'\x27;\x0a\x20\x20\x20\x20let\x20textField\x20=\x20\x27'+_0x13a985+'\x27;\x0a\x20\x20\x20\x20let\x20aliasField\x20=\x20null;\x0a\x0a\x20\x20\x20\x20//\x20Proses\x20setiap\x20column\x20dalam\x20select\x0a\x20\x20\x20\x20for\x20(const\x20column\x20of\x20selectColumns)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(column.toLowerCase()\x20===\x20\x27'+_0x7be755+_0x35801a(0x16f)+(_0x1e976d?'WHERE\x20'+_0x1e976d+'\x20':'')+'`;\x0a\x0a\x20\x20\x20\x20//\x20Build\x20WHERE\x20clause\x20jika\x20ada\x0a\x20\x20\x20\x20if\x20((options.where\x20&&\x20Array.isArray(options.where)\x20&&\x20options.where.length\x20>\x200)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20(options.where\x20&&\x20options.where.conditions\x20&&\x20Array.isArray(options.where.conditions)\x20&&\x20options.where.conditions.length\x20>\x200))\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20whereResult\x20=\x20this.buildComplexWhereClause(options.where,\x20params);\x0a\x20\x20\x20\x20\x20\x20\x20\x20query\x20+=\x20`'+(_0x1e976d?_0x388b46['qrRmu']:_0x35801a(0x1a6))+_0x35801a(0x195)+(_0x1e976d?')':'')+_0x35801a(0x165)+_0x7be755+_0x35801a(0x136)+_0x13a985+_0x35801a(0x1f9)+((()=>{const _0x4d06d3=_0x35801a,_0x11f7e0=_0x3355d2[_0x4d06d3(0x19a)]&&_0x3355d2[_0x4d06d3(0x19a)]['enabled'],_0x40df0c=_0x3355d2[_0x4d06d3(0x15e)]||{};if(!_0x11f7e0||!_0x40df0c['createComposite']&&!_0x40df0c[_0x4d06d3(0x1b7)]&&!_0x40df0c['readComposite'])return'';const _0x5968ea=_0x3355d2['masterDetail']['detailTable'],_0x4fe6cb=_0x5968ea['split']('.')['pop'](),_0x128be6=_0x3355d2[_0x4d06d3(0x19a)]['foreignKey'],_0x3a1205=_0x3355d2[_0x4d06d3(0x19a)][_0x4d06d3(0x16d)]?.['primaryKey']||_0x4fe6cb+_0x4d06d3(0x20a),_0x380755=_0x3355d2[_0x4d06d3(0x19a)][_0x4d06d3(0x11b)]||null,_0x664d7f=_0x3355d2[_0x4d06d3(0x19a)][_0x4d06d3(0x16d)]?.['autoCalculateFields']||{},_0x50d5f4=_0x3355d2[_0x4d06d3(0x19a)]['detailConfig']?.[_0x4d06d3(0x23b)]||null,_0x35c120=[],_0x42739c=[];for(const [_0x23db14,_0x20b636]of Object['entries'](_0x664d7f)){if(_0x20b636[_0x4d06d3(0x191)]===_0x4d06d3(0x11f))_0x35c120['push'](_0x23db14);else{const _0x334f7a=(_0x20b636['formula']||'')['split']('*')[_0x4d06d3(0x21d)](_0x2470d3=>_0x2470d3['trim']());_0x42739c[_0x4d06d3(0x12b)]({'fieldName':_0x23db14,'qtyField':_0x334f7a[0x0],'priceField':_0x334f7a[0x1]});}}const _0x5406a8=_0x664d7f[_0x4d06d3(0x1af)]?.['formula']||'',_0x38ef8d=_0x5406a8[_0x4d06d3(0x15c)]('*')['map'](_0x594277=>_0x594277[_0x4d06d3(0x15a)]()),_0x48ac97=_0x380755?.[_0x4d06d3(0x226)]?.[_0x4d06d3(0x159)]?.[_0x4d06d3(0x1c0)]('items.','')||'',_0x4797a3=_0x38ef8d[0x0]||_0x48ac97,_0x5c4cd8=_0x38ef8d[0x1]||_0x388b46[_0x4d06d3(0x1bf)],_0x50cc4c=_0x42739c['map'](_0x2d88de=>{const _0x391477=_0x4d06d3;return _0x391477(0x13f)+_0x2d88de['fieldName']+'\x20=\x20'+_0x2d88de[_0x391477(0x161)]+_0x391477(0x1f2)+_0x2d88de[_0x391477(0x1fc)]+'\x0a\x20\x20\x20\x20\x20\x20item.'+_0x2d88de['fieldName']+_0x391477(0x1a4)+_0x2d88de[_0x391477(0x161)]+_0x391477(0x19d)+_0x2d88de['priceField']+')\x20||\x200);';})[_0x4d06d3(0x1c5)]('\x0a');let _0x479d27='';_0x40df0c[_0x4d06d3(0x16a)]&&(_0x479d27+=_0x4d06d3(0x10f)+_0x4fe6cb+'\x27;\x0a\x20\x20\x20\x20const\x20headerData\x20=\x20{\x20...data\x20};\x0a\x20\x20\x20\x20delete\x20headerData[detailKey];\x0a\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onBeforeCompositeInsert\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_detailItems\x20=\x20data[detailKey]\x20||\x20[];\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeCtx\x20=\x20_CB.buildCompositeInsertBeforeContext(headerData,\x20_detailItems,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x3355d2['tableName']+_0x4d06d3(0x192)+_0x5968ea+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x128be6+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeResult\x20=\x20await\x20_ce.executeOnBeforeComposite(\x27insert\x27,\x20_beforeCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_beforeResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onBeforeCompositeInsert\x20failed:\x20\x27\x20+\x20_beforeResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20header\x20INSERT\x0a\x20\x20\x20\x20const\x20headerFields\x20=\x20[];\x0a\x20\x20\x20\x20const\x20headerValues\x20=\x20[];\x0a\x20\x20\x20\x20const\x20headerPlaceholders\x20=\x20[];\x0a\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(headerData))\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerPlaceholders.push(\x27?\x27);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20(created_at,\x20created_by,\x20updated_at,\x20updated_by)\x20via\x20helper\x0a\x20\x20\x20\x20this._appendCreateAuditColumns(headerFields,\x20headerValues,\x20headerPlaceholders,\x20headerData,\x20eventContext);\x0a\x0a\x20\x20\x20\x20const\x20insertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20this.writeSource\x20+\x20\x27\x20(\x27\x20+\x20headerFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20headerPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20console.log(\x27Executing\x20header\x20INSERT:\x27,\x20{\x20query:\x20insertSql,\x20values:\x20headerValues\x20});\x0a\x20\x20\x20\x20await\x20connection.execute(insertSql,\x20headerValues);\x0a\x0a\x20\x20\x20\x20//\x20SELECT\x20back\x20inserted\x20header\x0a\x20\x20\x20\x20const\x20selectSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20const\x20[headerRows]\x20=\x20await\x20connection.execute(selectSql,\x20[headerData[this.primaryKey]]);\x0a\x20\x20\x20\x20const\x20insertedHeader\x20=\x20headerRows[0];\x0a\x20\x20\x20\x20const\x20masterPkValue\x20=\x20headerData[this.primaryKey];\x0a\x0a\x20\x20\x20\x20console.log(\x27Header\x20inserted\x20successfully:\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27=\x27\x20+\x20masterPkValue);\x0a\x0a\x20\x20\x20\x20//\x20Insert\x20detail\x20items\x0a\x20\x20\x20\x20const\x20insertedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20detailTableFull\x20=\x20\x27'+_0x5968ea+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x128be6+_0x4d06d3(0x109)+_0x3a1205+_0x4d06d3(0x23d)+(_0x388b46[_0x4d06d3(0x1b0)](_0x35c120[_0x4d06d3(0x121)],0x0)?_0x4d06d3(0x1f4)+JSON[_0x4d06d3(0x1f5)](_0x35c120)+';':'')+'\x0a\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20data[detailKey]\x20||\x20[])\x20{\x0a\x20\x20\x20\x20\x20\x20item[fk]\x20=\x20masterPkValue;\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Auto-generate\x20UUID\x20untuk\x20detail\x20PK\x20bila\x20client\x20tidak\x20menyupply.\x0a\x20\x20\x20\x20\x20\x20//\x20MySQL\x20tidak\x20mendukung\x20RETURNING,\x20sehingga\x20generator\x20perlu\x20mengetahui\x0a\x20\x20\x20\x20\x20\x20//\x20nilai\x20PK\x20di\x20muka\x20agar\x20SELECT-back\x20dapat\x20me-retrieve\x20row.\x20Cocok\x20untuk\x0a\x20\x20\x20\x20\x20\x20//\x20kolom\x20VARCHAR\x20DEFAULT\x20(UUID());\x20untuk\x20AUTO_INCREMENT\x20integer,\x20client\x0a\x20\x20\x20\x20\x20\x20//\x20harus\x20mengirim\x20PK\x20atau\x20tidak\x20menggunakan\x20endpoint\x20composite.\x0a\x20\x20\x20\x20\x20\x20if\x20(item[detailPk]\x20===\x20undefined\x20||\x20item[detailPk]\x20===\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20item[detailPk]\x20=\x20require(\x27uuid\x27).v7();\x0a\x20\x20\x20\x20\x20\x20}\x0a'+(_0x50cc4c?_0x388b46['rxpmX']('\x0a'+_0x50cc4c,'\x0a'):'')+_0x4d06d3(0x225)+(_0x35c120['length']>0x0?'if\x20(generatedFields.includes(key))\x20continue;':'')+_0x4d06d3(0x133)+_0x3355d2[_0x4d06d3(0x233)]+_0x4d06d3(0x192)+_0x5968ea+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x128be6+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_afterResult\x20=\x20await\x20_ce2.executeOnAfterComposite(\x27insert\x27,\x20_afterCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_afterResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onAfterCompositeInsert\x20failed:\x20\x27\x20+\x20_afterResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20await\x20connection.commit();\x0a\x20\x20\x20\x20console.log(\x27Transaction\x20committed\x20successfully\x27);\x0a\x0a\x20\x20\x20\x20//\x20Invalidate\x20cache\x20setelah\x20write\x20operation\x20berhasil\x0a\x20\x20\x20\x20await\x20this.invalidateCache();\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20...insertedHeader,\x0a\x20\x20\x20\x20\x20\x20[detailKey]:\x20insertedItems\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.rollback();\x20}\x20catch\x20(rbErr)\x20{\x20console.error(\x27Rollback\x20failed:\x27,\x20rbErr.message);\x20}\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20createComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x20finally\x20{\x0a\x20\x20\x20\x20try\x20{\x20connection.release();\x20}\x20catch\x20(relErr)\x20{\x20console.error(\x27Connection\x20release\x20failed:\x27,\x20relErr.message);\x20}\x0a\x20\x20}\x0a}\x0a');_0x40df0c[_0x4d06d3(0x1b7)]&&(_0x479d27+=_0x4d06d3(0x1a0)+_0x4fe6cb+'\x27;\x0a\x20\x20\x20\x20delete\x20headerData[detailKey];\x0a\x20\x20\x20\x20delete\x20headerData[this.primaryKey];\x0a\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onBeforeCompositeUpdate\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_detailOps\x20=\x20data[detailKey]\x20||\x20{};\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeCtx\x20=\x20_CB.buildCompositeUpdateBeforeContext(headerData,\x20oldData,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20insert:\x20_detailOps.insert\x20||\x20[],\x0a\x20\x20\x20\x20\x20\x20\x20\x20update:\x20_detailOps.update\x20||\x20[],\x0a\x20\x20\x20\x20\x20\x20\x20\x20delete:\x20_detailOps.delete\x20||\x20[]\x0a\x20\x20\x20\x20\x20\x20},\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x3355d2[_0x4d06d3(0x233)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x5968ea+_0x4d06d3(0x158)+_0x128be6+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_beforeResult\x20=\x20await\x20_ce.executeOnBeforeComposite(\x27update\x27,\x20_beforeCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_beforeResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onBeforeCompositeUpdate\x20failed:\x20\x27\x20+\x20_beforeResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20header\x20UPDATE\x0a\x20\x20\x20\x20const\x20headerFields\x20=\x20[];\x0a\x20\x20\x20\x20const\x20headerValues\x20=\x20[];\x0a\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(headerData))\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerFields.push(key\x20+\x20\x27\x20=\x20?\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20headerValues.push(value);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20(updated_at,\x20updated_by)\x20via\x20base-model\x20helper.\x0a\x20\x20\x20\x20//\x20Helper\x20menangani\x20kolom\x20yang\x20benar-benar\x20ada\x20(this.auditColumns);\x20bila\x20tabel\x0a\x20\x20\x20\x20//\x20tidak\x20punya\x20audit\x20columns,\x20helper\x20no-op.\x0a\x20\x20\x20\x20this._appendUpdateAuditColumns(headerFields,\x20headerValues,\x20headerData,\x20eventContext);\x0a\x0a\x20\x20\x20\x20headerValues.push(primaryKeyValue);\x0a\x20\x20\x20\x20const\x20updateSql\x20=\x20\x27UPDATE\x20\x27\x20+\x20this.writeSource\x20+\x20\x27\x20SET\x20\x27\x20+\x20headerFields.join(\x27,\x20\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20console.log(\x27Executing\x20header\x20UPDATE:\x27,\x20{\x20query:\x20updateSql,\x20values:\x20headerValues\x20});\x0a\x20\x20\x20\x20await\x20connection.execute(updateSql,\x20headerValues);\x0a\x0a\x20\x20\x20\x20//\x20SELECT\x20back\x20updated\x20header\x0a\x20\x20\x20\x20const\x20selectSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20const\x20[headerRows]\x20=\x20await\x20connection.execute(selectSql,\x20[primaryKeyValue]);\x0a\x20\x20\x20\x20let\x20updatedHeader\x20=\x20headerRows[0];\x0a\x0a\x20\x20\x20\x20//\x20Process\x20detail\x20items\x0a\x20\x20\x20\x20const\x20detailTableFull\x20=\x20\x27'+_0x5968ea+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x128be6+_0x4d06d3(0x109)+_0x3a1205+'\x27;\x0a\x20\x20\x20\x20'+(_0x388b46[_0x4d06d3(0x20e)](_0x35c120['length'],0x0)?'const\x20generatedFields\x20=\x20'+JSON[_0x4d06d3(0x1f5)](_0x35c120)+';':'')+_0x4d06d3(0x181)+(_0x50cc4c?_0x388b46['MPtNj']('\x0a'+_0x50cc4c,'\x0a'):'')+_0x4d06d3(0x15d)+(_0x388b46[_0x4d06d3(0x1b0)](_0x35c120[_0x4d06d3(0x121)],0x0)?'if\x20(generatedFields.includes(key))\x20continue;':'')+_0x4d06d3(0x1c1)+(_0x50cc4c?_0x388b46[_0x4d06d3(0x114)]('\x0a',_0x50cc4c)+'\x0a':'')+'\x0a\x20\x20\x20\x20\x20\x20const\x20dFields\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20dValues\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20dPlaceholders\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(item))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20'+(_0x388b46['yCFYe'](_0x35c120[_0x4d06d3(0x121)],0x0)?'if\x20(generatedFields.includes(key))\x20continue;':'')+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dPlaceholders.push(\x27?\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20ke\x20detail\x20INSERT\x20(detail\x20baru\x20dari\x20updateComposite)\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20this._appendCreateAuditColumns(dFields,\x20dValues,\x20dPlaceholders,\x20item,\x20eventContext);\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20dInsertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20(\x27\x20+\x20dFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20dPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(dInsertSql,\x20dValues);\x0a\x20\x20\x20\x20\x20\x20insertedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Inserted\x20\x27\x20+\x20insertedItems.length\x20+\x20\x27\x20new\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x20Get\x20all\x20current\x20detail\x20items\x0a\x20\x20\x20\x20const\x20allItemsSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20fk\x20+\x20\x27\x20=\x20?\x20ORDER\x20BY\x20line_number\x27;\x0a\x20\x20\x20\x20const\x20[allItems]\x20=\x20await\x20connection.execute(allItemsSql,\x20[primaryKeyValue]);\x0a'+(_0x380755?'\x0a\x20\x20\x20\x20//\x20Recalculate\x20header\x20totals\x0a\x20\x20\x20\x20const\x20calculations\x20=\x20'+JSON['stringify'](_0x380755)+_0x4d06d3(0x22f)+(_0x4797a3?'if\x20(calculations.total_amount)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20totalAmount\x20=\x20allItems.reduce(function(sum,\x20item)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20qty\x20=\x20Number(item.'+_0x4797a3+_0x4d06d3(0x110)+_0x5c4cd8+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20sum\x20+\x20(qty\x20*\x20price);\x0a\x20\x20\x20\x20\x20\x20},\x200);\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_amount\x20=\x20?\x27);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(totalAmount);\x0a\x20\x20\x20\x20}':_0x388b46['CyABc'])+_0x4d06d3(0x199):'')+'\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onAfterCompositeUpdate\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce2\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB2\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_afterCtx\x20=\x20_CB2.buildCompositeUpdateAfterContext(headerData,\x20oldData,\x20updatedHeader,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems\x0a\x20\x20\x20\x20\x20\x20},\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x3355d2[_0x4d06d3(0x233)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x5968ea+_0x4d06d3(0x158)+_0x128be6+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_afterResult\x20=\x20await\x20_ce2.executeOnAfterComposite(\x27update\x27,\x20_afterCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_afterResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20await\x20connection.rollback();\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onAfterCompositeUpdate\x20failed:\x20\x27\x20+\x20_afterResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20await\x20connection.commit();\x0a\x20\x20\x20\x20console.log(\x27Transaction\x20committed\x20successfully\x27);\x0a\x0a\x20\x20\x20\x20//\x20Invalidate\x20cache\x20setelah\x20write\x20operation\x20berhasil\x0a\x20\x20\x20\x20await\x20this.invalidateCache();\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20...updatedHeader,\x0a\x20\x20\x20\x20\x20\x20[detailKey]:\x20allItems,\x0a\x20\x20\x20\x20\x20\x20_operations:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems.length\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20try\x20{\x20await\x20connection.rollback();\x20}\x20catch\x20(rbErr)\x20{\x20console.error(\x27Rollback\x20failed:\x27,\x20rbErr.message);\x20}\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20updateComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x20finally\x20{\x0a\x20\x20\x20\x20try\x20{\x20connection.release();\x20}\x20catch\x20(relErr)\x20{\x20console.error(\x27Connection\x20release\x20failed:\x27,\x20relErr.message);\x20}\x0a\x20\x20}\x0a}\x0a');if(_0x40df0c[_0x4d06d3(0x208)]){let _0x114c4d;if(_0x50d5f4&&typeof _0x50d5f4==='string'&&_0x50d5f4['startsWith'](_0x388b46[_0x4d06d3(0x1b3)])){const _0x4f16ce=_0x50d5f4['replace'](_0x388b46[_0x4d06d3(0x1b3)],''),_0x4c2af8=_0x4f16ce[_0x4d06d3(0x15c)]('/')['pop']();_0x114c4d='\x0a\x20\x20\x20\x20//\x20Load\x20detail\x20query\x20dari\x20file\x20lokal\x0a\x20\x20\x20\x20let\x20detailSql;\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20detailQueryFilePath\x20=\x20path.join(__dirname,\x20\x27query\x27,\x20\x27'+_0x4c2af8+'\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(detailQueryFilePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailSql\x20=\x20fs.readFileSync(detailQueryFilePath,\x20\x27utf8\x27).trim();\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(`Detail\x20query\x20file\x20not\x20found:\x20${detailQueryFilePath}`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Failed\x20to\x20load\x20detail\x20query\x20file:\x20\x27\x20+\x20error.message);\x0a\x20\x20\x20\x20}';}else _0x50d5f4?_0x114c4d=_0x4d06d3(0x188)+_0x50d5f4['replace'](/\$1/g,'?')+'`;':_0x114c4d=_0x4d06d3(0x12c)+_0x5968ea+_0x4d06d3(0x1be)+_0x128be6+_0x4d06d3(0x210);_0x479d27+='\x0a/**\x0a\x20*\x20Composite\x20read\x20-\x20Read\x20header\x20with\x20detail\x20items\x20(MySQL)\x0a\x20*/\x0aasync\x20readComposite(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!options.where)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Invalid\x20request\x20format:\x20where\x20parameter\x20is\x20required\x27);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20whereClauseResult;\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20whereClauseResult\x20=\x20this.buildComplexWhereClause(options.where);\x0a\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20const\x20{\x20sql:\x20whereClause,\x20params\x20}\x20=\x20whereClauseResult;\x0a\x20\x20\x20\x20const\x20headerSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20whereClause;\x0a\x20\x20\x20\x20const\x20headerResults\x20=\x20await\x20db.executeQuery(headerSql,\x20params);\x0a\x0a\x20\x20\x20\x20if\x20(!headerResults\x20||\x20headerResults.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20{\x20success:\x20true,\x20count:\x200,\x20data:\x20[]\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20compositeResults\x20=\x20[];\x0a\x20\x20\x20\x20const\x20detailKey\x20=\x20\x27'+_0x4fe6cb+_0x4d06d3(0x23d)+_0x114c4d+_0x4d06d3(0x238);}return _0x479d27;})())+_0x35801a(0x120)+_0x490080+_0x35801a(0x173);}function createMysqlSubmoduleTemplate(_0x1034b1,_0x5de7d6,_0x8872e5){const _0x3aa5cd=a0_0x12be,_0x548940={'RHMit':_0x3aa5cd(0x237),'jsrxF':_0x3aa5cd(0x1e3),'zBKIM':'let\x20componentEngine\x20=\x20null;\x0alet\x20ContextBuilder\x20=\x20null;\x0a','boJes':function(_0x5dcc63,_0x5c0e9e){return _0x5dcc63(_0x5c0e9e);},'nrNlq':'recordId','yeOOl':_0x3aa5cd(0x17a),'beoyK':'unit_price','SUpkz':_0x3aa5cd(0x23e),'ACCqo':function(_0x22981d,_0x5a2593){return _0x22981d!==_0x5a2593;}},_0x494151=toCamelCase(_0x5de7d6)+_0x3aa5cd(0x142),_0x2211cc=_0x8872e5[_0x3aa5cd(0x119)]||'id',_0x2c9285=new Date()['toISOString'](),_0x476d35=Object[_0x3aa5cd(0x21c)](_0x8872e5[_0x3aa5cd(0x15e)]||{})[_0x3aa5cd(0x1a1)](([,_0xba09a7])=>_0xba09a7)['map'](([_0x53a7df])=>_0x53a7df),_0x50e649=_0x8872e5[_0x3aa5cd(0x194)]&&Array['isArray'](_0x8872e5[_0x3aa5cd(0x194)])&&_0x8872e5['components']['length']>0x0,_0x2c3c83=_0x8872e5[_0x3aa5cd(0x1bb)]||'SELECT\x20'+_0x8872e5['fieldName'][_0x3aa5cd(0x1c5)](',\x20')+'\x20FROM\x20'+_0x8872e5[_0x3aa5cd(0x233)],_0x2f6e6e=_0x2c3c83['replace'](/\\/g,'\x5c\x5c')['replace'](/'/g,'\x5c\x27'),_0x3dee9e=JSON['stringify'](_0x8872e5[_0x3aa5cd(0x1f3)]),_0x120426=_0x8872e5[_0x3aa5cd(0x20f)]?JSON[_0x3aa5cd(0x1f5)](_0x8872e5['columnFormats']):_0x3aa5cd(0x1e3),_0x54638a=_0x8872e5[_0x3aa5cd(0x218)]?JSON['stringify'](_0x8872e5[_0x3aa5cd(0x218)]):_0x3aa5cd(0x1e3),_0x4a5489=_0x8872e5['action']&&_0x8872e5['action']['import']?{'enabled':!![],'upsertKeys':(_0x8872e5['importConfig']||{})['upsertKeys']||[_0x2211cc],'upsertStrategy':(_0x8872e5[_0x3aa5cd(0x1c6)]||{})['upsertStrategy']||_0x548940[_0x3aa5cd(0x1d0)],'requiredFields':(_0x8872e5[_0x3aa5cd(0x1c6)]||{})[_0x3aa5cd(0x1ed)]||[],'validations':(_0x8872e5['importConfig']||{})['validations']||{},'lookupFields':(_0x8872e5[_0x3aa5cd(0x1c6)]||{})[_0x3aa5cd(0x214)]||{},'excludeFromImport':(_0x8872e5[_0x3aa5cd(0x1c6)]||{})['excludeFromImport']||[],'chunkSize':(_0x8872e5[_0x3aa5cd(0x1c6)]||{})['chunkSize']||0x64}:null,_0x43bfa3=_0x4a5489?JSON[_0x3aa5cd(0x1f5)](_0x4a5489):_0x3aa5cd(0x1e3),_0xc1210b=_0x8872e5['action']&&_0x8872e5[_0x3aa5cd(0x15e)][_0x3aa5cd(0x17c)]?'\x27'+_0x2f6e6e+'\x27':_0x548940['jsrxF'],_0x21988f=_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5['action'][_0x3aa5cd(0x202)]?_0x8872e5[_0x3aa5cd(0x137)]||{}:null,_0x22ff0d=_0x21988f?JSON[_0x3aa5cd(0x1f5)](_0x21988f):_0x3aa5cd(0x1e3),_0x1c2682=_0x8872e5['fieldPolicy']?',\x20'+JSON['stringify'](_0x8872e5[_0x3aa5cd(0x1ce)]):'';let _0x599528=_0x548940['zBKIM'];_0x50e649&&(_0x599528+=_0x3aa5cd(0x1b5),_0x599528+='ContextBuilder\x20=\x20require(\x27@restforgejs/platform/src/utils/context-builder\x27);\x0a');const _0x4166a5=_0x8872e5[_0x3aa5cd(0x19c)]?_0x3aa5cd(0x156)+_0x494151+_0x3aa5cd(0x1eb)+_0x5de7d6+'\x20data\x20not\x20found\x20or\x20access\x20denied\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a':'',_0x875799=_0x8872e5[_0x3aa5cd(0x19c)]?'\x0a\x20\x20\x20\x20//\x20Request\x20scope\x20ownership\x20verification\x20(Layer\x201\x20RLS)\x0a\x20\x20\x20\x20if\x20(req._requestScope\x20&&\x20result.data\x20&&\x20result.data.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20record\x20=\x20result.data[0];\x0a\x20\x20\x20\x20\x20\x20if\x20(record[req._requestScope.column]\x20!==\x20undefined\x20&&\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20String(record[req._requestScope.column])\x20!==\x20String(req._requestScope.value))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a':'',_0x54acc6=_0x8872e5['requestScope']?_0x3aa5cd(0x105)+_0x494151+_0x3aa5cd(0x216)+_0x2211cc+'\x27,\x20value:\x20data.'+_0x2211cc+_0x3aa5cd(0x140):'';let _0x22840e=_0x3aa5cd(0x1f6)+_0x494151+'\x20=\x20require(\x27../../models/'+_0x1034b1+'/'+_0x5de7d6+'\x27);\x0a'+_0x599528+_0x3aa5cd(0x12e)+_0x548940['boJes'](toPascalCase,_0x5de7d6)+'\x20Submodule\x20-\x20MySQL\x20Database\x0a*\x20Generated:\x20'+_0x2c9285+'\x0a*\x0a*\x20MySQL-optimized\x20endpoints\x20untuk\x20'+_0x5de7d6+_0x3aa5cd(0x1f1)+_0x476d35[_0x3aa5cd(0x1c5)](',\x20')+_0x3aa5cd(0x154)+_0x8872e5['tableName']+_0x3aa5cd(0x1d1)+_0x2211cc+'\x27;\x0a\x0a//\x20Component\x20configuration\x20untuk\x20export/import\x20(parsed\x20oleh\x20config-extractor\x20—\x20jangan\x20dimodifikasi)\x0aconst\x20componentConfig\x20=\x20{\x0a\x20\x20tableName:\x20\x27'+_0x8872e5['tableName']+_0x3aa5cd(0x14e)+_0x3dee9e+',\x0a\x20\x20exportQuery:\x20'+_0xc1210b+',\x0a\x20\x20columnFormats:\x20'+_0x120426+_0x3aa5cd(0x211)+_0x54638a+_0x3aa5cd(0x20d)+_0x43bfa3+',\x0a\x20\x20adjustConfig:\x20'+_0x22ff0d+',\x0a\x20\x20uploadConfig:\x20'+(_0x8872e5['uploadConfig']?JSON['stringify'](_0x8872e5[_0x3aa5cd(0x17e)]):'null')+_0x3aa5cd(0x1b9)+(_0x8872e5['requestScope']?JSON[_0x3aa5cd(0x1f5)](_0x8872e5['requestScope']):'null')+_0x3aa5cd(0x1a7)+(_0x50e649?'\x0a//\x20Initialize\x20component\x20engine\x20dengan\x20event\x20handlers\x20dari\x20payload\x0aconst\x20_componentPayload\x20=\x20{\x20components:\x20'+JSON[_0x3aa5cd(0x1f5)](_0x8872e5[_0x3aa5cd(0x194)])+_0x3aa5cd(0x14d)+_0x1034b1+'/'+_0x5de7d6+':\x20${result.componentsLoaded}\x20components`);\x0a\x20\x20}\x0a}).catch(err\x20=>\x20{\x0a\x20\x20console.error(`Failed\x20to\x20load\x20component\x20configuration\x20for\x20'+_0x1034b1+'/'+_0x5de7d6+':`,\x20err.message);\x0a});\x0a':'')+'\x0a//\x20CORS\x20ditangani\x20di\x20level\x20app\x20oleh\x20cors\x20middleware\x20(lihat\x20konfigurasi\x20CORS_ENABLED\x20dan\x20CORS_ORIGINS\x20di\x20.env)\x0a\x0a//\x20Request\x20ID\x20untuk\x20tracing\x20—\x20support\x20correlation\x20ID\x20dari\x20upstream\x0arouter.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20req.mysqlRequestId\x20=\x20req.headers[\x27x-correlation-id\x27]\x20||\x20`mysql_${Date.now()}_${Math.random().toString(36).substr(2,\x209)}`;\x0a\x20\x20res.setHeader(\x27X-Correlation-ID\x27,\x20req.mysqlRequestId);\x0a\x20\x20res.setHeader(\x27X-MySQL-Request-ID\x27,\x20req.mysqlRequestId);\x0a\x20\x20next();\x0a});\x0a\x0a\x0a\x0a//\x20Middleware\x20untuk\x20validasi\x20payload\x20MySQL\x0arouter.use((req,\x20res,\x20next)\x20=>\x20{\x0a\x20\x20if\x20(req.method\x20===\x20\x27POST\x27)\x20{\x0a\x20\x20\x20\x20//\x20Skip\x20validation\x20untuk\x20endpoint\x20export/import\x20(ditangani\x20oleh\x20centralized\x20handler)\x0a\x20\x20\x20\x20if\x20(req.path.startsWith(\x27/import\x27)\x20||\x20req.path.startsWith(\x27/export\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20next();\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20endpoint\x20=\x20req.path.substring(1);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(endpoint\x20===\x20\x27first\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(req.body.where)\x20&&\x20req.body.where.length\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20req.body.where\x20=\x20req.body.where[0];\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!req.body.where\x20||\x20typeof\x20req.body.where\x20!==\x20\x27object\x27\x20||\x20Array.isArray(req.body.where))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Where\x20must\x20be\x20a\x20single\x20condition\x20{key,\x20value}\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20{\x20\x22key\x22:\x20\x22field_name\x22,\x20\x22value\x22:\x20\x22field_value\x22\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22select\x22:\x20[\x22field1\x22,\x20\x22field2\x22]\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(req.body.where.conditions\x20||\x20req.body.where.logic)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Advanced\x20where\x20format\x20is\x20not\x20supported\x20in\x20/first\x20endpoint.\x20Use\x20/read\x20endpoint\x20for\x20complex\x20queries\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20{\x20\x22key\x22:\x20\x22field_name\x22,\x20\x22value\x22:\x20\x22field_value\x22\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(endpoint\x20===\x20\x27delete\x27\x20&&\x20(!req.body.where))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27DELETE\x20payload\x20must\x20include\x20a\x20where\x20property\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22'+_0x2211cc+_0x3aa5cd(0x22b);_0x22840e+=buildRequestScopeMiddleware(_0x8872e5,_0x5de7d6);_0x8872e5['action']&&_0x8872e5['action'][_0x3aa5cd(0x113)]&&(_0x22840e+='//\x20POST\x20/api/'+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x148)+_0x494151+_0x3aa5cd(0x1ae)+_0x5de7d6+'\x20datatables:\x27,\x20error);\x0a\x20\x20\x20\x20const\x20statusCode\x20=\x20error.statusCode\x20||\x20500;\x0a\x20\x20\x20\x20return\x20res.status(statusCode).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20statusCode\x20===\x20400\x20?\x20\x27Bad\x20Request\x27\x20:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20statusCode\x20===\x20400\x20?\x20error.message\x20:\x20\x27An\x20error\x20occurred\x20while\x20fetching\x20'+_0x5de7d6+_0x3aa5cd(0x1ba));_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5[_0x3aa5cd(0x15e)][_0x3aa5cd(0x18b)]&&(_0x22840e+='//\x20GET\x20/api/'+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x1fa)+_0x494151+'.validFields.includes(key)\x20&&\x20value)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20extraFilters[key]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20startTime\x20=\x20Date.now();\x0a\x20\x20\x20\x20const\x20list\x20=\x20Object.keys(extraFilters).length\x20>\x200\x20?\x0a\x20\x20\x20\x20\x20\x20await\x20'+_0x494151+_0x3aa5cd(0x222)+_0x494151+_0x3aa5cd(0x1b8)+_0x5de7d6+_0x3aa5cd(0x144));_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5['action'][_0x3aa5cd(0x18b)]&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/lookup\x20-\x20MySQL\x20Static\x20Lookup\x0arouter.post(\x27/lookup\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20const\x20mysqlRequestId\x20=\x20req.mysqlRequestId;\x0a\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20requestMode\x20=\x20req.headers[\x27x-request-mode\x27];\x0a\x0a\x20\x20\x20\x20if\x20(requestMode\x20!==\x20\x27static\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20Request\x20Mode\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27X-Request-Mode\x20header\x20must\x20be\x20set\x20to\x20static\x20for\x20POST\x20lookup\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(`[MySQL-LKP]\x20${mysqlRequestId}\x20static\x20lookup:`,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20const\x20startTime\x20=\x20Date.now();\x0a\x20\x20\x20\x20let\x20list;\x0a\x0a\x20\x20\x20\x20if\x20(req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20New\x20format\x20dengan\x20where\x20clause\x20+\x20optional\x20select\x20dan\x20order\x0a\x20\x20\x20\x20\x20\x20list\x20=\x20await\x20'+_0x494151+_0x3aa5cd(0x1d7)+_0x494151+_0x3aa5cd(0x1aa)+_0x5de7d6+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x8872e5['action']&&_0x8872e5['action']['create']&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/create\x20-\x20MySQL\x20Insert\x0arouter.post(\x27/create\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x494151+'.validateData\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20validation\x20=\x20await\x20'+_0x494151+_0x3aa5cd(0x171)+(_0x50e649?_0x3aa5cd(0x234)+_0x8872e5[_0x3aa5cd(0x233)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id\x20||\x20null\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x494151+'.addData(req.body,\x20eventContext);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20INSERT\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20INSERT\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x494151+_0x3aa5cd(0x235):_0x3aa5cd(0x1bc)+_0x494151+_0x3aa5cd(0x1f8))+'\x0a\x20\x20\x20\x20console.log(`'+_0x5de7d6+'\x20data\x20added\x20successfully:\x20${result.'+_0x2211cc+_0x3aa5cd(0x1e6)+_0x5de7d6+_0x3aa5cd(0x180)+_0x5de7d6+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27ER_DUP_ENTRY\x27\x20||\x20error.errno\x20===\x201062)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27ER_NO_REFERENCED_ROW_2\x27\x20||\x20error.errno\x20===\x201452)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20adding\x20'+_0x5de7d6+_0x3aa5cd(0x1ba));_0x8872e5['action']&&_0x8872e5['action'][_0x3aa5cd(0x124)]&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/update\x20-\x20MySQL\x20Update\x0arouter.post(\x27/update\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Validasi\x20payload\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20primary\x20key\x0a\x20\x20\x20\x20const\x20primaryKey\x20=\x20\x27'+_0x2211cc+_0x3aa5cd(0x146)+_0x4166a5+_0x3aa5cd(0x1e5)+_0x494151+_0x3aa5cd(0x1cf)+_0x494151+'.validateData(req.body,\x20\x27update\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20req.body\x20=\x20{\x20...req.body,\x20...validation.sanitizedData\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a'+(_0x50e649?_0x3aa5cd(0x204)+_0x8872e5[_0x3aa5cd(0x233)]+_0x3aa5cd(0x203)+_0x494151+_0x3aa5cd(0x1ee)+_0x1c2682+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':_0x3aa5cd(0x1cd)+_0x494151+_0x3aa5cd(0x22d)+_0x1c2682+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20UPDATE\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20UPDATE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+'\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0x5de7d6+_0x3aa5cd(0x219)+_0x2211cc+_0x3aa5cd(0x14b)+_0x2211cc+'\x27]}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+'\x20data\x20successfully\x20updated\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20responseData,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mengupdate\x20data\x20'+_0x5de7d6+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.message\x20===\x20\x27Data\x20tidak\x20ditemukan\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27ER_DUP_ENTRY\x27\x20||\x20error.errno\x20===\x201062)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20updating\x20'+_0x5de7d6+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x8872e5['action']&&_0x8872e5['action']['adjust']&&(_0x22840e+='//\x20POST\x20/api/'+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x1dc)+_0x2211cc+_0x3aa5cd(0x17d)+_0x4166a5+_0x3aa5cd(0x1fe)+(_0x50e649?'\x0a\x20\x20\x20\x20//\x20Integrated\x20transaction\x20dengan\x20event\x20lifecycle\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x8872e5[_0x3aa5cd(0x233)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20responseData\x20=\x20await\x20'+_0x494151+'.adjustData(req.body,\x20adjustConfig,\x20eventContext'+_0x1c2682+_0x3aa5cd(0x229):'\x0a\x20\x20\x20\x20//\x20Fallback:\x20mode\x20tanpa\x20events\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20responseData\x20=\x20await\x20'+_0x494151+'.adjustData(req.body,\x20adjustConfig,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20}'+_0x1c2682+');\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20ADJUST\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20ADJUST\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+'\x0a\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0x5de7d6+'\x20data\x20adjusted\x20successfully:\x20'+_0x2211cc+'=${req.body[\x27'+_0x2211cc+_0x3aa5cd(0x232)+_0x5de7d6+'\x20data\x20successfully\x20adjusted\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20responseData,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mengadjust\x20data\x20'+_0x5de7d6+_0x3aa5cd(0x125)+_0x5de7d6+_0x3aa5cd(0x164)+_0x5de7d6+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');if(_0x8872e5['action']&&_0x8872e5[_0x3aa5cd(0x15e)][_0x3aa5cd(0x206)]){const _0x43e047=_0x8872e5['workflow']?JSON[_0x3aa5cd(0x1f5)](_0x8872e5['workflow']):'{}';_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x150)+_0x5de7d6+'\x0arouter.post(\x27/change-status\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20primaryKey\x20=\x20\x27'+_0x2211cc+'\x27;\x0a\x20\x20\x20\x20const\x20recordId\x20=\x20req.body[primaryKey]\x20||\x20req.body.id;\x0a\x20\x20\x20\x20if\x20(!recordId)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Primary\x20key\x20(${primaryKey})\x20or\x20id\x20is\x20required\x20for\x20change-status`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!req.body.status)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27status\x20is\x20required\x20for\x20change-status\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a'+_0x4166a5['replace'](/req\.body\[primaryKey\]/g,_0x548940['nrNlq'])+'\x0a\x20\x20\x20\x20const\x20workflowConfig\x20=\x20'+_0x43e047+';\x0a\x20\x20\x20\x20workflowConfig._project\x20=\x20\x27'+_0x1034b1+_0x3aa5cd(0x1b2)+(_0x50e649?_0x3aa5cd(0x204)+_0x8872e5['tableName']+_0x3aa5cd(0x22a)+_0x494151+'.changeStatusData(req.body,\x20workflowConfig,\x20eventContext);\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20CHANGE-STATUS\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20CHANGE-STATUS\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':'\x0a\x20\x20\x20\x20//\x20Fallback:\x20tanpa\x20component\x20engine\x20events\x20(tetap\x20forward\x20authHeader\x20untuk\x20hook\x20JWT\x20forwarding)\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20user_id:\x20req.headers[\x27user-id\x27]\x20||\x20req.headers[\x27x-user-id\x27]\x20||\x20req.body.updated_by\x20||\x20\x27system\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id\x20||\x20null,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20authHeader:\x20req.headers.authorization\x20||\x20null\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20result\x20=\x20await\x20'+_0x494151+_0x3aa5cd(0x1d6))+_0x3aa5cd(0x18e)+_0x5de7d6+'\x20status\x20changed:\x20${result.workflow.previousStatus}\x20→\x20${result.workflow.newStatus}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20`Status\x20changed\x20from\x20${result.workflow.previousStatus}\x20to\x20${result.workflow.newStatus}`,\x0a\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20workflow:\x20result.workflow,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20change-status\x20'+_0x5de7d6+_0x3aa5cd(0x13c)+_0x5de7d6+_0x3aa5cd(0x1d3);}if(_0x8872e5['action']&&_0x8872e5[_0x3aa5cd(0x15e)]['aggregate']){const _0x1d3e8b=_0x8872e5[_0x3aa5cd(0x182)]?JSON['stringify'](_0x8872e5[_0x3aa5cd(0x182)]):'{}';_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/aggregate\x20-\x20MySQL\x20Aggregate\x20(count,\x20sum,\x20avg,\x20min,\x20max)\x0arouter.post(\x27/aggregate\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20aggregateConfig\x20=\x20'+_0x1d3e8b+';\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x494151+_0x3aa5cd(0x14c)+_0x5de7d6+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20403)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(403).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Pro\x20Feature\x20Required\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20upgrade:\x20\x27https://restforge.dev/pricing\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.statusCode\x20===\x20400\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27not\x20a\x20valid\x20field\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27Invalid\x20aggregate\x20function\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27Invalid\x20alias\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27only\x20allowed\x20with\x20COUNT\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27not\x20defined\x20in\x20aggregate\x20config\x27)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.message.includes(\x27no\x20join\x20definitions\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20error\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20error.message,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20aggregating\x20'+_0x5de7d6+_0x3aa5cd(0x1ba);}_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5['action'][_0x3aa5cd(0x11d)]&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x1c3)+_0x494151+'.getData({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20where:\x20[{\x20key:\x20firstCondition.key,\x20value:\x20firstCondition.value\x20}]\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!existingData.success\x20||\x20!existingData.data\x20||\x20existingData.data.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+_0x3aa5cd(0x189)+(_0x50e649?'\x0a\x20\x20\x20\x20//\x20Integrated\x20transaction\x20dengan\x20event\x20lifecycle\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x8872e5[_0x3aa5cd(0x233)]+_0x3aa5cd(0x203)+_0x494151+_0x3aa5cd(0x10a):_0x3aa5cd(0x1cd)+_0x494151+_0x3aa5cd(0x1c2))+_0x3aa5cd(0x18f)+_0x5de7d6+_0x3aa5cd(0x1a8)+_0x5de7d6+_0x3aa5cd(0x127)+_0x5de7d6+_0x3aa5cd(0x1ba));_0x8872e5['action']&&_0x8872e5[_0x3aa5cd(0x15e)]['first']&&(_0x22840e+='//\x20POST\x20/api/'+_0x1034b1+'/'+_0x5de7d6+'/first\x20-\x20Mendapatkan\x20data\x20berdasarkan\x20kriteria\x0arouter.post(\x27/first\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Normalize:\x20array\x201\x20elemen\x20→\x20object\x20(backward\x20compatible)\x0a\x20\x20\x20\x20if\x20(Array.isArray(req.body.where)\x20&&\x20req.body.where.length\x20===\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20req.body.where\x20=\x20req.body.where[0];\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20where\x20clause\x20—\x20harus\x20object\x20tunggal\x20{key,\x20value}\x0a\x20\x20\x20\x20if\x20(!req.body.where\x20||\x20typeof\x20req.body.where\x20!==\x20\x27object\x27\x20||\x20Array.isArray(req.body.where))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Missing\x20required\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20where\x20is\x20required\x20as\x20{key,\x20value}\x20object\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x22where\x22:\x20{\x20\x22key\x22:\x20\x22'+_0x2211cc+_0x3aa5cd(0x1d9)+_0x2211cc+'\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Tolak\x20format\x20advanced\x20(conditions/logic)\x0a\x20\x20\x20\x20if\x20(req.body.where.conditions\x20||\x20req.body.where.logic)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20where\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Advanced\x20where\x20format\x20is\x20not\x20supported\x20in\x20/first\x20endpoint.\x20Use\x20/read\x20endpoint\x20for\x20complex\x20queries\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20where.key\x20ada\x20di\x20validFields\x0a\x20\x20\x20\x20const\x20validFields\x20=\x20'+JSON[_0x3aa5cd(0x1f5)](_0x8872e5['fieldName'])+';\x0a\x20\x20\x20\x20if\x20(!validFields.includes(req.body.where.key))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20where\x20field\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Invalid\x20field:\x20${req.body.where.key}`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20validFields:\x20validFields,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20select\x20fields\x20jika\x20ada\x0a\x20\x20\x20\x20if\x20(req.body.select\x20&&\x20Array.isArray(req.body.select))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20invalidFields\x20=\x20req.body.select.filter(field\x20=>\x20!validFields.includes(field));\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(invalidFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20select\x20fields\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20`Invalid\x20field(s):\x20${invalidFields.join(\x27,\x20\x27)}`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20validFields:\x20validFields,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Convert\x20ke\x20array\x20format\x20untuk\x20kompatibilitas\x20dengan\x20model.getData()\x20→\x20buildComplexWhereClause()\x0a\x20\x20\x20\x20const\x20getPayload\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20where:\x20[{\x20key:\x20req.body.where.key,\x20value:\x20req.body.where.value\x20}],\x0a\x20\x20\x20\x20\x20\x20select:\x20req.body.select\x0a\x20\x20\x20\x20};\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x494151+'.getData(getPayload);\x0a'+_0x875799+'\x0a\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20...result,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20mendapatkan\x20data\x20'+_0x5de7d6+_0x3aa5cd(0x145)+_0x5de7d6+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5[_0x3aa5cd(0x15e)]['read']&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/read\x20-\x20Manual\x20pagination\x20endpoint\x0arouter.post(\x27/read\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Deteksi\x20mode:\x20paginasi\x20(page\x20dikirim)\x20atau\x20non-paginasi\x20(page\x20tidak\x20dikirim)\x0a\x20\x20\x20\x20const\x20paginate\x20=\x20req.body.page\x20!==\x20undefined;\x0a\x20\x20\x20\x20const\x20page\x20=\x20paginate\x20?\x20parseInt(req.body.page,\x2010)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20perPage\x20=\x20paginate\x20?\x20Math.min(parseInt(req.body.per_page\x20||\x2010,\x2010),\x20100)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20limit\x20=\x20!paginate\x20?\x20Math.min(Math.max(parseInt(req.body.limit\x20||\x201000,\x2010),\x201),\x205000)\x20:\x20null;\x0a\x20\x20\x20\x20const\x20searchValue\x20=\x20req.body.search_value\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20const\x20searchBy\x20=\x20req.body.search_by\x20||\x20\x27all\x27;\x0a\x0a\x20\x20\x20\x20//\x20Parse\x20sort_columns\x0a\x20\x20\x20\x20let\x20sort_columns\x20=\x20[];\x0a\x20\x20\x20\x20if\x20(req.body.sort_columns\x20&&\x20Array.isArray(req.body.sort_columns)\x20&&\x20req.body.sort_columns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20sort_columns\x20=\x20req.body.sort_columns.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20\x20\x20column:\x20item.column,\x0a\x20\x20\x20\x20\x20\x20\x20\x20direction:\x20(item.direction\x20||\x20\x27ASC\x27).toUpperCase()\x0a\x20\x20\x20\x20\x20\x20}));\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20parameter\x20paginasi\x20(hanya\x20jika\x20mode\x20paginasi)\x0a\x20\x20\x20\x20if\x20(paginate\x20&&\x20page\x20<\x201)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20page\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Page\x20must\x20be\x20greater\x20than\x200\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Proses\x20parameter\x20where\x20dengan\x20format\x20advanced\x20conditions\x0a\x20\x20\x20\x20let\x20where\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(req.body.where\x20&&\x20typeof\x20req.body.where\x20===\x20\x27object\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(Array.isArray(req.body.where)\x20||\x20(req.body.where.conditions\x20&&\x20Array.isArray(req.body.where.conditions)))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20where\x20=\x20req.body.where;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Proses\x20parameter\x20select\x20untuk\x20kolom\x20selektif\x0a\x20\x20\x20\x20const\x20validFields\x20=\x20'+JSON[_0x3aa5cd(0x1f5)](_0x8872e5[_0x3aa5cd(0x1f3)]||[])+';\x0a\x20\x20\x20\x20let\x20select\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(req.body.select\x20&&\x20Array.isArray(req.body.select))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20invalidFields\x20=\x20req.body.select.filter(field\x20=>\x20!validFields.includes(field));\x0a\x20\x20\x20\x20\x20\x20if\x20(invalidFields.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20select\x20fields\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20field(s):\x20\x27\x20+\x20invalidFields.join(\x27,\x20\x27),\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20validFields:\x20validFields,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20select\x20=\x20req.body.select;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20options\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue,\x0a\x20\x20\x20\x20\x20\x20searchBy,\x0a\x20\x20\x20\x20\x20\x20sort_columns,\x0a\x20\x20\x20\x20\x20\x20where:\x20where,\x0a\x20\x20\x20\x20\x20\x20select:\x20select\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20options.page\x20=\x20page;\x0a\x20\x20\x20\x20\x20\x20options.perPage\x20=\x20perPage;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20options.limit\x20=\x20limit;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x494151+'.getList(options);\x0a\x0a\x20\x20\x20\x20//\x20Format\x20response\x20berdasarkan\x20mode\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200,\x0a\x20\x20\x20\x20\x20\x20\x20\x20pagination:\x20result.pagination,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Data\x20retrieved\x20successfully\x27\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20'+_0x5de7d6+_0x3aa5cd(0x141)+_0x5de7d6+'\x20list\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a');if(_0x8872e5[_0x3aa5cd(0x19a)]&&_0x8872e5[_0x3aa5cd(0x19a)][_0x3aa5cd(0x1cc)]){const _0x36c18f=_0x8872e5['masterDetail'][_0x3aa5cd(0x1c7)]['split']('.')[_0x3aa5cd(0x21f)](),_0x18e969=_0x5de7d6[_0x3aa5cd(0x1c0)](/-/g,'_'),_0x45d69f=_0x8872e5[_0x3aa5cd(0x19a)]['headerCalculations']||null,_0x2815ad=_0x8872e5[_0x3aa5cd(0x19a)]?.[_0x3aa5cd(0x16d)]?.['autoCalculateFields']?.[_0x3aa5cd(0x1af)]?.[_0x3aa5cd(0x10e)]||'',_0x295f7c=_0x2815ad['split']('*')['map'](_0x271d47=>_0x271d47['trim']()),_0x527a20=_0x45d69f?.['total_qty']?.[_0x3aa5cd(0x159)]?.[_0x3aa5cd(0x1c0)](_0x548940['yeOOl'],'')||'',_0x1bb865=_0x295f7c[0x0]||_0x527a20,_0x44ea94=_0x295f7c[0x1]||_0x548940[_0x3aa5cd(0x1cb)];if(_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5['action']['createComposite']){const _0x1615b5=_0x45d69f?'\x0a\x20\x20\x20\x20var\x20headerCalc\x20=\x20'+JSON[_0x3aa5cd(0x1f5)](_0x45d69f)+';\x0a\x20\x20\x20\x20if\x20(headerCalc.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_items\x20=\x20data.'+_0x36c18f+_0x3aa5cd(0x162)+_0x36c18f+_0x3aa5cd(0x13d)+(_0x1bb865?'if\x20(headerCalc.total_amount)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_amount\x20=\x20data.'+_0x36c18f+'.reduce(function(sum,\x20item)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20qty\x20=\x20Number(item.'+_0x1bb865+_0x3aa5cd(0x110)+_0x44ea94+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20sum\x20+\x20(qty\x20*\x20price);\x0a\x20\x20\x20\x20\x20\x20},\x200);\x0a\x20\x20\x20\x20}':_0x548940['SUpkz'])+_0x3aa5cd(0x16b):'';_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x135)+_0x5de7d6+'/create-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0x18e969+')\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20must\x20have\x20property\x20\x22'+_0x18e969+_0x3aa5cd(0x1ac)+_0x18e969+_0x3aa5cd(0x205)+_0x494151+_0x3aa5cd(0x167)+_0x36c18f+_0x3aa5cd(0x183)+_0x494151+'.validateData(headerDataForValidation,\x20\x27insert\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20Object.assign(data,\x20validation.sanitizedData);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!data.'+_0x36c18f+'\x20||\x20!Array.isArray(data.'+_0x36c18f+_0x3aa5cd(0x12a)+_0x36c18f+'.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20\x22'+_0x36c18f+'\x22\x20must\x20be\x20a\x20non-empty\x20array\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20'+_0x1615b5+'\x0a\x0a\x20\x20\x20\x20//\x20Build\x20eventContext\x20untuk\x20composite\x20hooks\x0a\x20\x20\x20\x20var\x20eventContext\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x8872e5['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x8872e5['masterDetail']['detailTable']+_0x3aa5cd(0x118)+_0x8872e5[_0x3aa5cd(0x19a)]['foreignKey']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20options:\x20req.bodyOptions\x20||\x20{},\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20requestId:\x20req.id\x20||\x20null\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x494151+'.createComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x5de7d6+_0x3aa5cd(0x13e)+_0x5de7d6+'\x20data\x20successfully\x20created\x20(with\x20detail\x20items)\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20result,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20composite\x20create\x20'+_0x5de7d6+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.errno\x20===\x201062)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.errno\x20===\x201452)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20creating\x20'+_0x5de7d6+_0x3aa5cd(0x1ba);}_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5[_0x3aa5cd(0x15e)]['updateComposite']&&(_0x22840e+='//\x20POST\x20/api/'+_0x1034b1+'/'+_0x5de7d6+_0x3aa5cd(0x196)+_0x5de7d6+'/update-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0x18e969+_0x3aa5cd(0x236)+_0x18e969+_0x3aa5cd(0x1ac)+_0x18e969+_0x3aa5cd(0x13b)+_0x2211cc+_0x3aa5cd(0x155)+_0x2211cc+')\x20is\x20required\x20for\x20update\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a'+_0x54acc6+_0x3aa5cd(0x23c)+_0x494151+_0x3aa5cd(0x167)+_0x36c18f+_0x3aa5cd(0x183)+_0x494151+'.validateData(headerDataForValidation,\x20\x27update\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20Object.assign(data,\x20validation.sanitizedData);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(data.'+_0x36c18f+')\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(typeof\x20data.'+_0x36c18f+'\x20!==\x20\x27object\x27\x20||\x20Array.isArray(data.'+_0x36c18f+'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20\x22'+_0x36c18f+'\x22\x20must\x20be\x20an\x20object\x20with\x20structure\x20{insert:\x20[],\x20update:\x20[],\x20delete:\x20[]}\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20eventContext\x20untuk\x20composite\x20hooks\x0a\x20\x20\x20\x20var\x20eventContext\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x8872e5['tableName']+_0x3aa5cd(0x1e1)+_0x8872e5['masterDetail'][_0x3aa5cd(0x1c7)]+_0x3aa5cd(0x118)+_0x8872e5[_0x3aa5cd(0x19a)][_0x3aa5cd(0x129)]+_0x3aa5cd(0x116)+_0x494151+'.updateComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x5de7d6+_0x3aa5cd(0x178)+_0x2211cc+'=\x27\x20+\x20data.'+_0x2211cc+');\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+_0x3aa5cd(0x1e8)+_0x5de7d6+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.message\x20===\x20\x27Record\x20not\x20found\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0x5de7d6+_0x3aa5cd(0x134)+_0x5de7d6+'\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});\x0a\x0a'),_0x8872e5[_0x3aa5cd(0x15e)]&&_0x8872e5[_0x3aa5cd(0x15e)][_0x3aa5cd(0x208)]&&(_0x22840e+=_0x3aa5cd(0x108)+_0x1034b1+'/'+_0x5de7d6+'/read-composite\x20-\x20MySQL\x20Read\x20header\x20with\x20detail\x20items\x0arouter.post(\x27/read-composite\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20console.log(\x27Request\x20body\x20'+_0x5de7d6+_0x3aa5cd(0x128)+_0x2211cc+'\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}]\x20},\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20var\x20result\x20=\x20await\x20'+_0x494151+'.readComposite(req.body);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x5de7d6+_0x3aa5cd(0x1ad)+_0x5de7d6+_0x3aa5cd(0x15b)+_0x5de7d6+_0x3aa5cd(0x16c));}if(_0x8872e5[_0x3aa5cd(0x15e)]?.[_0x3aa5cd(0x198)]!==![]){const _0x58581e={'datatables':!!_0x8872e5[_0x3aa5cd(0x15e)]?.['datatables'],'read':!!_0x8872e5['action']?.[_0x3aa5cd(0x115)],'first':!!_0x8872e5['action']?.[_0x3aa5cd(0x1d5)],'create':!!_0x8872e5['action']?.[_0x3aa5cd(0x14a)],'update':!!_0x8872e5[_0x3aa5cd(0x15e)]?.[_0x3aa5cd(0x124)],'delete':!!_0x8872e5[_0x3aa5cd(0x15e)]?.[_0x3aa5cd(0x11d)],'lookup':!!_0x8872e5[_0x3aa5cd(0x15e)]?.[_0x3aa5cd(0x18b)],'export':!!_0x8872e5['action']?.[_0x3aa5cd(0x17c)],'import':!!_0x8872e5[_0x3aa5cd(0x15e)]?.[_0x3aa5cd(0x1b4)],'info':_0x548940[_0x3aa5cd(0x1e7)](_0x8872e5['action']?.['info'],![])};_0x22840e+=_0x3aa5cd(0x186)+JSON['stringify'](_0x58581e)+_0x3aa5cd(0x149)+_0x494151+_0x3aa5cd(0x1c8)+_0x5de7d6+_0x3aa5cd(0x21b)+_0x1034b1+_0x3aa5cd(0x14f)+_0x2c9285+_0x3aa5cd(0x22e);}else _0x22840e+=_0x3aa5cd(0x1ff);return _0x22840e+='\x0a//\x20MySQL\x20health\x20check\x0arouter.get(\x27/health\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20connectionInfo\x20=\x20await\x20'+_0x494151+'.getConnectionInfo();\x0a\x0a\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20status:\x20connectionInfo\x20?\x20\x27healthy\x27\x20:\x20\x27unknown\x27,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0x5de7d6+'\x27,\x0a\x20\x20\x20\x20\x20\x20database:\x20\x27mysql\x27,\x0a\x20\x20\x20\x20\x20\x20connection:\x20connectionInfo\x20?\x20\x27active\x27\x20:\x20\x27unknown\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20res.status(503).json({\x0a\x20\x20\x20\x20\x20\x20status:\x20\x27unhealthy\x27,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0x5de7d6+_0x3aa5cd(0x21e),_0x22840e+=_0x3aa5cd(0x1e0),_0x22840e;}function a0_0x12be(_0x360d57,_0x4b11f0){_0x360d57=_0x360d57-0x105;const _0x81637f=a0_0x8163();let _0x12be2e=_0x81637f[_0x360d57];if(a0_0x12be['sGJDmy']===undefined){var _0x5a9bf5=function(_0x44321d){const _0x2a0ed6='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x137f4c='',_0x502880='';for(let _0x10e457=0x0,_0x944673,_0x59a716,_0x5e241c=0x0;_0x59a716=_0x44321d['charAt'](_0x5e241c++);~_0x59a716&&(_0x944673=_0x10e457%0x4?_0x944673*0x40+_0x59a716:_0x59a716,_0x10e457++%0x4)?_0x137f4c+=String['fromCharCode'](0xff&_0x944673>>(-0x2*_0x10e457&0x6)):0x0){_0x59a716=_0x2a0ed6['indexOf'](_0x59a716);}for(let _0x6f5c5e=0x0,_0x5f57e7=_0x137f4c['length'];_0x6f5c5e<_0x5f57e7;_0x6f5c5e++){_0x502880+='%'+('00'+_0x137f4c['charCodeAt'](_0x6f5c5e)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x502880);};a0_0x12be['dgycnh']=_0x5a9bf5,a0_0x12be['ElrGLZ']={},a0_0x12be['sGJDmy']=!![];}const _0x26ddf2=_0x81637f[0x0],_0x3493ea=_0x360d57+_0x26ddf2,_0x6818b7=a0_0x12be['ElrGLZ'][_0x3493ea];return!_0x6818b7?(_0x12be2e=a0_0x12be['dgycnh'](_0x12be2e),a0_0x12be['ElrGLZ'][_0x3493ea]=_0x12be2e):_0x12be2e=_0x6818b7,_0x12be2e;}module[a0_0x53db4a(0x1db)]={'createMysqlMainModuleTemplate':createMysqlMainModuleTemplate,'createMysqlModelTemplate':createMysqlModelTemplate,'createMysqlSubmoduleTemplate':createMysqlSubmoduleTemplate};