@restforgejs/platform 4.1.1 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) 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 +43 -4
  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 +526 -0
  18. package/generators/cli/schema/describe.js +3 -2
  19. package/generators/cli/schema/diff.js +322 -0
  20. package/generators/cli/schema/generate-ddl.js +7 -10
  21. package/generators/cli/schema/init.js +95 -172
  22. package/generators/cli/schema/introspect.js +3 -2
  23. package/generators/cli/schema/list.js +3 -2
  24. package/generators/cli/schema/migrate.js +13 -18
  25. package/generators/cli/schema/models.js +8 -12
  26. package/generators/cli/schema/template.js +222 -0
  27. package/generators/cli/schema/validate.js +8 -12
  28. package/generators/cli-entry.js +17 -2
  29. package/generators/lib/dbschema-kit/apply-engine.js +582 -0
  30. package/generators/lib/dbschema-kit/diff-engine.js +703 -0
  31. package/generators/lib/dbschema-kit/diff-reporter.js +272 -0
  32. package/generators/lib/dbschema-kit/emitters/alter-table.js +275 -0
  33. package/generators/lib/migration/audit-table-runner.js +213 -215
  34. package/generators/lib/payload/endpoint-schema-validator.js +171 -0
  35. package/generators/lib/payload/payload-runner.js +137 -220
  36. package/generators/lib/payload/schema-diff.js +277 -0
  37. package/generators/lib/templates/dashboard-catalog.js +1 -437
  38. package/generators/lib/templates/db-connection-env.js +1 -212
  39. package/generators/lib/templates/dbschema-catalog.js +1 -489
  40. package/generators/lib/templates/field-validation-catalog.js +1 -531
  41. package/generators/lib/templates/mysql-template.js +1 -3863
  42. package/generators/lib/templates/oracle-template.js +1 -3915
  43. package/generators/lib/templates/postgres-template.js +1 -5838
  44. package/generators/lib/templates/query-declarative-catalog.js +1 -199
  45. package/generators/lib/templates/sqlite-template.js +1 -3440
  46. package/generators/lib/utils/audit-columns.js +181 -0
  47. package/generators/lib/utils/cli-output.js +17 -0
  48. package/generators/lib/utils/database-introspector.js +16 -13
  49. package/generators/lib/utils/env-manager.js +6 -0
  50. package/generators/lib/utils/path-validator.js +71 -0
  51. package/generators/lib/validators/payload-validator.js +1 -2
  52. package/integrity-manifest.json +28 -10
  53. package/package.json +11 -3
  54. package/scripts/verify-integrity.js +1 -1
  55. package/server.js +1 -1
  56. package/src/components/handlers/adjust_handler.js +1 -1
  57. package/src/components/handlers/audit_handler.js +1 -1
  58. package/src/components/handlers/delete_handler.js +1 -1
  59. package/src/components/handlers/export_handler.js +1 -1
  60. package/src/components/handlers/import_handler.js +1 -1
  61. package/src/components/handlers/insert_handler.js +1 -1
  62. package/src/components/handlers/update_handler.js +1 -1
  63. package/src/components/handlers/upload_handler.js +1 -1
  64. package/src/components/handlers/workflow_handler.js +1 -1
  65. package/src/components/integrations/webhook.js +1 -1
  66. package/src/consumers/baseConsumer.js +1 -1
  67. package/src/consumers/declarativeMapper.js +1 -1
  68. package/src/consumers/handlers/apiHandler.js +1 -1
  69. package/src/consumers/handlers/consoleHandler.js +1 -1
  70. package/src/consumers/handlers/databaseHandler.js +1 -1
  71. package/src/consumers/handlers/index.js +1 -1
  72. package/src/consumers/handlers/kafkaHandler.js +1 -1
  73. package/src/consumers/index.js +1 -1
  74. package/src/consumers/messageTransformer.js +1 -1
  75. package/src/consumers/validator.js +1 -1
  76. package/src/core/db/dialect/base-dialect.js +1 -1
  77. package/src/core/db/dialect/index.js +1 -1
  78. package/src/core/db/dialect/mysql-dialect.js +1 -1
  79. package/src/core/db/dialect/oracle-dialect.js +1 -1
  80. package/src/core/db/dialect/postgres-dialect.js +1 -1
  81. package/src/core/db/dialect/sqlite-dialect.js +1 -1
  82. package/src/core/db/flatten-helper.js +1 -1
  83. package/src/core/db/query-builder-error.js +1 -1
  84. package/src/core/db/query-builder.js +1 -1
  85. package/src/core/db/relation-helper.js +1 -1
  86. package/src/core/handlers/delete_handler.js +1 -1
  87. package/src/core/handlers/insert_handler.js +1 -1
  88. package/src/core/handlers/update_handler.js +1 -1
  89. package/src/core/models/base-model.js +1 -1
  90. package/src/core/utils/cache-manager.js +1 -1
  91. package/src/core/utils/component-engine.js +1 -1
  92. package/src/core/utils/context-builder.js +1 -1
  93. package/src/core/utils/datetime-formatter.js +1 -1
  94. package/src/core/utils/datetime-parser.js +1 -1
  95. package/src/core/utils/db.js +1 -1
  96. package/src/core/utils/logger.js +1 -1
  97. package/src/core/utils/payload-loader.js +1 -1
  98. package/src/core/utils/security-checks.js +1 -1
  99. package/src/middleware/body-options.js +1 -1
  100. package/src/middleware/cors.js +1 -1
  101. package/src/middleware/idempotency.js +1 -1
  102. package/src/middleware/rate-limiter.js +1 -1
  103. package/src/middleware/request-logger.js +1 -1
  104. package/src/middleware/security-headers.js +1 -1
  105. package/src/models/base-model-mysql.js +1 -1
  106. package/src/models/base-model-oracle.js +1 -1
  107. package/src/models/base-model-sqlite.js +1 -1
  108. package/src/models/base-model.js +1 -1
  109. package/src/pro/caching/redis-client.js +1 -1
  110. package/src/pro/caching/redis-helper.js +1 -1
  111. package/src/pro/consumers/baseConsumer.js +1 -1
  112. package/src/pro/consumers/declarativeMapper.js +1 -1
  113. package/src/pro/consumers/handlers/apiHandler.js +1 -1
  114. package/src/pro/consumers/handlers/consoleHandler.js +1 -1
  115. package/src/pro/consumers/handlers/databaseHandler.js +1 -1
  116. package/src/pro/consumers/handlers/index.js +1 -1
  117. package/src/pro/consumers/handlers/kafkaHandler.js +1 -1
  118. package/src/pro/consumers/index.js +1 -1
  119. package/src/pro/consumers/messageTransformer.js +1 -1
  120. package/src/pro/consumers/validator.js +1 -1
  121. package/src/pro/database/base-model-mysql.js +1 -1
  122. package/src/pro/database/base-model-oracle.js +1 -1
  123. package/src/pro/database/base-model-sqlite.js +1 -1
  124. package/src/pro/database/db-mysql.js +1 -1
  125. package/src/pro/database/db-oracle.js +1 -1
  126. package/src/pro/database/db-sqlite.js +1 -1
  127. package/src/pro/excel/excel-generator.js +1 -1
  128. package/src/pro/excel/excel-parser.js +1 -1
  129. package/src/pro/excel/export-service.js +1 -1
  130. package/src/pro/excel/export_handler.js +1 -1
  131. package/src/pro/excel/import-service.js +1 -1
  132. package/src/pro/excel/import-validator.js +1 -1
  133. package/src/pro/excel/import_handler.js +1 -1
  134. package/src/pro/excel/upsert-builder.js +1 -1
  135. package/src/pro/idgen/idgen-routes.js +1 -1
  136. package/src/pro/integrations/lookup-resolver.js +1 -1
  137. package/src/pro/integrations/upload-handler-v2.js +1 -1
  138. package/src/pro/integrations/upload-handler.js +1 -1
  139. package/src/pro/integrations/webhook.js +1 -1
  140. package/src/pro/locking/lock-routes.js +1 -1
  141. package/src/pro/locking/resource-lock-manager.js +1 -1
  142. package/src/pro/messaging/kafkaConsumerService.js +1 -1
  143. package/src/pro/messaging/kafkaService.js +1 -1
  144. package/src/pro/messaging/messagehubService.js +1 -1
  145. package/src/pro/messaging/rabbitmqService.js +1 -1
  146. package/src/pro/scheduler/job-manager.js +1 -1
  147. package/src/pro/scheduler/job-routes.js +1 -1
  148. package/src/pro/scheduler/job-validator.js +1 -1
  149. package/src/pro/storage/base-storage-provider.js +1 -1
  150. package/src/pro/storage/file-metadata-helper.js +1 -1
  151. package/src/pro/storage/index.js +1 -1
  152. package/src/pro/storage/local-storage-provider.js +1 -1
  153. package/src/pro/storage/s3-storage-provider.js +1 -1
  154. package/src/pro/storage/upload-cleanup-job.js +1 -1
  155. package/src/pro/storage/upload-cleanup-scheduler.js +1 -1
  156. package/src/pro/storage/upload-pending-tracker.js +1 -1
  157. package/src/pro/websocket/broadcast-helper.js +1 -1
  158. package/src/pro/websocket/index.js +1 -1
  159. package/src/pro/websocket/livesync-server.js +1 -1
  160. package/src/pro/websocket/ws-broadcaster.js +1 -1
  161. package/src/services/export-service.js +1 -1
  162. package/src/services/import-service.js +1 -1
  163. package/src/services/kafkaConsumerService.js +1 -1
  164. package/src/services/kafkaService.js +1 -1
  165. package/src/services/messagehubService.js +1 -1
  166. package/src/services/rabbitmqService.js +1 -1
  167. package/src/utils/cache-invalidation-registry.js +1 -1
  168. package/src/utils/cache-manager.js +1 -1
  169. package/src/utils/component-engine.js +1 -1
  170. package/src/utils/config-extractor.js +1 -1
  171. package/src/utils/consumerLogger.js +1 -1
  172. package/src/utils/context-builder.js +1 -1
  173. package/src/utils/dashboard-helpers.js +1 -1
  174. package/src/utils/dateHelper.js +1 -1
  175. package/src/utils/datetime-formatter.js +1 -1
  176. package/src/utils/datetime-parser.js +1 -1
  177. package/src/utils/db-bootstrap.js +1 -1
  178. package/src/utils/db-mysql.js +1 -1
  179. package/src/utils/db-oracle.js +1 -1
  180. package/src/utils/db-sqlite.js +1 -1
  181. package/src/utils/db.js +1 -1
  182. package/src/utils/demo-generator.js +1 -1
  183. package/src/utils/excel-generator.js +1 -1
  184. package/src/utils/excel-parser.js +1 -1
  185. package/src/utils/file-watcher.js +1 -1
  186. package/src/utils/id-generator.js +1 -1
  187. package/src/utils/idempotency-manager.js +1 -1
  188. package/src/utils/import-validator.js +1 -1
  189. package/src/utils/license-client.js +1 -1
  190. package/src/utils/lock-manager.js +1 -1
  191. package/src/utils/logger.js +1 -1
  192. package/src/utils/lookup-resolver.js +1 -1
  193. package/src/utils/payload-loader.js +1 -1
  194. package/src/utils/processor-response.js +1 -1
  195. package/src/utils/rabbitmq.js +1 -1
  196. package/src/utils/redis-client.js +1 -1
  197. package/src/utils/redis-helper.js +1 -1
  198. package/src/utils/request-scope.js +1 -1
  199. package/src/utils/security-checks.js +1 -1
  200. package/src/utils/service-resolver.js +1 -1
  201. package/src/utils/shutdown-coordinator.js +1 -1
  202. package/src/utils/trusted-keys.js +1 -1
  203. package/src/utils/upload-handler.js +1 -1
  204. package/src/utils/upsert-builder.js +1 -1
  205. package/src/utils/workflow-hook-executor.js +1 -1
  206. package/generators/metadata/global.json +0 -58
  207. package/generators/metadata/test-mysql-workbench.json +0 -118
  208. package/generators/metadata/test-mysql.json +0 -56
  209. package/generators/metadata/test-oracle-workbench.json +0 -118
  210. package/generators/metadata/test-oracle.json +0 -56
  211. package/generators/metadata/test-pg-workbench.json +0 -118
  212. package/generators/metadata/test-pg.json +0 -56
  213. package/generators/scripts/obfuscate-source.js +0 -356
  214. package/generators/scripts/validate-catalog.js +0 -430
  215. package/generators/scripts/validate-dbschema-catalog.js +0 -708
  216. package/generators/tests/baseline/mysql/mini_inventory_item/src/models/mini-inventory/item.js +0 -944
  217. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  218. package/generators/tests/baseline/mysql/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  219. package/generators/tests/baseline/oracle/mini_inventory_item/src/models/mini-inventory/item.js +0 -1002
  220. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory/item.js +0 -740
  221. package/generators/tests/baseline/oracle/mini_inventory_item/src/modules/mini-inventory.js +0 -336
  222. package/generators/tests/baseline/postgres/mini_inventory_item/src/models/mini-inventory/item.js +0 -1333
  223. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory/item.js +0 -1173
  224. package/generators/tests/baseline/postgres/mini_inventory_item/src/modules/mini-inventory.js +0 -496
  225. package/generators/tests/fixtures/payloads/custom-sensitive.json +0 -27
  226. package/generators/tests/fixtures/payloads/dynamic-search-optout.json +0 -23
  227. package/generators/tests/fixtures/payloads/login-with-password.json +0 -22
  228. package/generators/tests/fixtures/payloads/order-process.json +0 -52
  229. package/generators/tests/fixtures/payloads/with-inline-sql.json +0 -26
  230. package/generators/tests/integration-tahap4b/README.md +0 -145
  231. package/generators/tests/integration-tahap4b/run-concurrent.js +0 -77
  232. package/generators/tests/integration-tahap4b/seed.sql +0 -53
  233. package/generators/tests/integration-tahap4b/verify.sql +0 -110
  234. package/generators/tests/unit/cli/create-dashboard.test.js +0 -505
  235. package/generators/tests/unit/cli/create-processor.test.js +0 -319
  236. package/generators/tests/unit/cli/dispatch-dashboard.test.js +0 -149
  237. package/generators/tests/unit/lib/dashboard-generator.test.js +0 -895
  238. package/generators/tests/unit/lib/dashboard-validator.test.js +0 -354
  239. package/generators/tests/unit/lib/dbschema-kit/apply-executor.test.js +0 -437
  240. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-introspect.test.js +0 -393
  241. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-generate-ddl.test.js +0 -104
  242. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-init.test.js +0 -119
  243. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-list.test.js +0 -48
  244. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-migrate.test.js +0 -175
  245. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-kit-validate.test.js +0 -102
  246. package/generators/tests/unit/lib/dbschema-kit/cli/dbschema-models.test.js +0 -43
  247. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/all-schemas-listing.js +0 -84
  248. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/connection-error.js +0 -13
  249. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/empty.js +0 -12
  250. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/multi-schema.js +0 -124
  251. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/single-schema-inventory.js +0 -64
  252. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/introspect-stubs/two-tables.js +0 -66
  253. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/connection-error.js +0 -9
  254. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/partial.js +0 -29
  255. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/rollback.js +0 -26
  256. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/migrate-stubs/success.js +0 -43
  257. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/audit/events.js +0 -18
  258. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/inventory/products.js +0 -9
  259. package/generators/tests/unit/lib/dbschema-kit/cli/fixtures/multi-schema/users.js +0 -8
  260. package/generators/tests/unit/lib/dbschema-kit/connection.test.js +0 -112
  261. package/generators/tests/unit/lib/dbschema-kit/ddl-generator.test.js +0 -205
  262. package/generators/tests/unit/lib/dbschema-kit/define-model.test.js +0 -56
  263. package/generators/tests/unit/lib/dbschema-kit/dialect/index.test.js +0 -46
  264. package/generators/tests/unit/lib/dbschema-kit/dialect/mysql.test.js +0 -126
  265. package/generators/tests/unit/lib/dbschema-kit/dialect/oracle.test.js +0 -126
  266. package/generators/tests/unit/lib/dbschema-kit/dialect/postgres.test.js +0 -131
  267. package/generators/tests/unit/lib/dbschema-kit/dialect/sqlite.test.js +0 -126
  268. package/generators/tests/unit/lib/dbschema-kit/driver-loader.test.js +0 -93
  269. package/generators/tests/unit/lib/dbschema-kit/emitters/create-index.test.js +0 -173
  270. package/generators/tests/unit/lib/dbschema-kit/emitters/create-table.test.js +0 -376
  271. package/generators/tests/unit/lib/dbschema-kit/emitters/drop-table.test.js +0 -78
  272. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/invalid-dialect.env +0 -6
  273. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-dialect.env +0 -5
  274. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/missing-host.env +0 -5
  275. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/oracle-valid.env +0 -6
  276. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/postgres-valid.env +0 -7
  277. package/generators/tests/unit/lib/dbschema-kit/fixtures/connection/sqlite-valid.env +0 -2
  278. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/category.js +0 -11
  279. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/item_product.js +0 -11
  280. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound.js +0 -24
  281. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/stock_inbound_item.js +0 -28
  282. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/supplier.js +0 -9
  283. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory/warehouse.js +0 -9
  284. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-invalid/orphan.js +0 -17
  285. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/category.js +0 -11
  286. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/item_product.js +0 -11
  287. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/supplier.js +0 -9
  288. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/master/warehouse.js +0 -9
  289. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound.js +0 -24
  290. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/mini-inventory-multifolder/transactions/stock_inbound_item.js +0 -28
  291. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/audit/events.js +0 -18
  292. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/inventory/products.js +0 -9
  293. package/generators/tests/unit/lib/dbschema-kit/fixtures/integration/multi-schema/public/users.js +0 -9
  294. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/extra/category.js +0 -8
  295. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-subfolder/master/category.js +0 -8
  296. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/bar.js +0 -8
  297. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/duplicate-tablename/foo.js +0 -8
  298. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/empty-folder/README.md +0 -1
  299. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-export/plain.js +0 -3
  300. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/invalid-schema/bad.js +0 -6
  301. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/legacy-pattern/legacy.js +0 -12
  302. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/audit/products.js +0 -9
  303. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-distinct/inventory/products.js +0 -9
  304. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/a/products.js +0 -8
  305. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/multi-schema-duplicate/b/products.js +0 -8
  306. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/nested-deep/a/b/c/deep_table.js +0 -8
  307. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/.hidden/ignored.js +0 -7
  308. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/category.js +0 -8
  309. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/master/supplier.js +0 -8
  310. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound.js +0 -8
  311. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/recursive-multi-folder/transactions/stock_inbound_item.js +0 -8
  312. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/category.js +0 -8
  313. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-multiple/item_product.js +0 -9
  314. package/generators/tests/unit/lib/dbschema-kit/fixtures/loader/valid-single/category.js +0 -8
  315. package/generators/tests/unit/lib/dbschema-kit/integration.test.js +0 -217
  316. package/generators/tests/unit/lib/dbschema-kit/introspect-mapper.test.js +0 -403
  317. package/generators/tests/unit/lib/dbschema-kit/ir-builder.test.js +0 -390
  318. package/generators/tests/unit/lib/dbschema-kit/loader.test.js +0 -128
  319. package/generators/tests/unit/lib/dbschema-kit/naming.test.js +0 -170
  320. package/generators/tests/unit/lib/dbschema-kit/parser/shorthand-parser.test.js +0 -237
  321. package/generators/tests/unit/lib/dbschema-kit/schema-printer.test.js +0 -251
  322. package/generators/tests/unit/lib/dbschema-kit/statement-modifier.test.js +0 -105
  323. package/generators/tests/unit/lib/dbschema-kit/statement-splitter.test.js +0 -165
  324. package/generators/tests/unit/lib/dbschema-kit/topological-sort.test.js +0 -135
  325. package/generators/tests/unit/lib/dbschema-kit/validator/check-compatibility-validator.test.js +0 -373
  326. package/generators/tests/unit/lib/dbschema-kit/validator/circular-relation-validator.test.js +0 -454
  327. package/generators/tests/unit/lib/dbschema-kit/validator/cross-model-validator.test.js +0 -512
  328. package/generators/tests/unit/lib/dbschema-kit/validator/enhanced-validate-integration.test.js +0 -390
  329. package/generators/tests/unit/lib/dbschema-kit/validator/naming-convention-validator.test.js +0 -306
  330. package/generators/tests/unit/lib/dbschema-kit/validator/schema-validator.test.js +0 -443
  331. package/generators/tests/unit/lib/dbschema-kit/validator/type-compatibility-validator.test.js +0 -440
  332. package/generators/tests/unit/lib/dbschema-kit/validator/validator-reporter.test.js +0 -172
  333. package/generators/tests/unit/lib/metadata-manager-dashboard.test.js +0 -256
  334. package/generators/tests/unit/lib/payload-validator-fieldpolicy.test.js +0 -240
  335. package/generators/tests/unit/lib/processor-validation-generator.test.js +0 -300
  336. package/generators/tests/unit/lib/sensitive-field-masker.test.js +0 -170
  337. package/generators/tests/unit/lib/sql-table-extractor.test.js +0 -119
  338. package/scripts/generate-integrity-manifest.js +0 -124
  339. package/scripts/snapshot-cli-contracts.js +0 -194
  340. 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_0x5ab30b=a0_0x2280;(function(_0x41d946,_0xe3bc60){const _0x265054=a0_0x2280,_0x4135bb=_0x41d946();while(!![]){try{const _0x529c47=parseInt(_0x265054(0x1e6))/0x1+-parseInt(_0x265054(0x292))/0x2*(parseInt(_0x265054(0x267))/0x3)+parseInt(_0x265054(0x1a7))/0x4+-parseInt(_0x265054(0x17c))/0x5+-parseInt(_0x265054(0x171))/0x6+-parseInt(_0x265054(0x24c))/0x7*(parseInt(_0x265054(0x227))/0x8)+parseInt(_0x265054(0x250))/0x9*(parseInt(_0x265054(0x258))/0xa);if(_0x529c47===_0xe3bc60)break;else _0x4135bb['push'](_0x4135bb['shift']());}catch(_0x54a374){_0x4135bb['push'](_0x4135bb['shift']());}}}(a0_0x45be,0xcd8e1));function toCamelCase(_0x57cb89){const _0xe9f7cd=a0_0x2280,_0x5c20fc={'dLqFF':function(_0x4f98ed,_0x12b40c){return _0x4f98ed===_0x12b40c;},'LUlLD':_0xe9f7cd(0x27b)};if(!_0x57cb89||typeof _0x57cb89!==_0x5c20fc['LUlLD'])return'';return _0x57cb89['replace'](/(?:^\w|[A-Z]|\b\w)/g,(_0x52d597,_0x23826e)=>{const _0x40133e=_0xe9f7cd;return _0x5c20fc[_0x40133e(0x1ec)](_0x23826e,0x0)?_0x52d597['toLowerCase']():_0x52d597[_0x40133e(0x248)]();})[_0xe9f7cd(0x2a1)](/\s+/g,'')['replace'](/[-_]/g,'');}function toPascalCase(_0x188891){const _0x3790fb=a0_0x2280,_0x10f90a={'HfDJd':function(_0x3bd89c,_0x33f3cb){return _0x3bd89c!==_0x33f3cb;},'ypDVZ':function(_0x10a53a,_0x3138f7){return _0x10a53a+_0x3138f7;}};if(!_0x188891||_0x10f90a[_0x3790fb(0x190)](typeof _0x188891,'string'))return'';const _0x3734fe=toCamelCase(_0x188891);return _0x10f90a['ypDVZ'](_0x3734fe['charAt'](0x0)['toUpperCase'](),_0x3734fe[_0x3790fb(0x1bd)](0x1));}function buildAuditColumnsSection(_0x2ba219,_0x534d9d){const _0x650c31=a0_0x2280,_0x181d24={'OgGTr':function(_0x5ec9e9,_0x1949ae){return _0x5ec9e9!==_0x1949ae;},'CWHnS':'auditColumns','mtLOC':function(_0x391107,_0x2b42b6){return _0x391107===_0x2b42b6;},'SokOg':'object','AZHXQ':'createdAt','fuZCt':_0x650c31(0x263)};if(!(_0x181d24[_0x650c31(0x1d8)]in _0x2ba219))return'';const _0x4fb1a7=_0x2ba219[_0x650c31(0x287)];if(_0x4fb1a7===![]||_0x4fb1a7===null)return'\x0a'+_0x534d9d+_0x650c31(0x247)+_0x534d9d+_0x650c31(0x213);if(_0x181d24[_0x650c31(0x2af)](typeof _0x4fb1a7,_0x181d24[_0x650c31(0x185)])&&!Array['isArray'](_0x4fb1a7)){const _0x50bf50=[_0x181d24['AZHXQ'],_0x650c31(0x276),_0x181d24['fuZCt'],_0x650c31(0x19e)],_0x2c3acd={};_0x50bf50['forEach'](_0x1bbcb8=>{if(_0x181d24['OgGTr'](_0x4fb1a7[_0x1bbcb8],undefined))_0x2c3acd[_0x1bbcb8]=_0x4fb1a7[_0x1bbcb8];});const _0x1e853b=JSON[_0x650c31(0x2a6)](_0x2c3acd,null,0x2)[_0x650c31(0x18e)]('\x0a')[_0x650c31(0x2aa)]((_0x4928b9,_0x24e47e)=>_0x24e47e===0x0?_0x4928b9:_0x534d9d+_0x4928b9)['join']('\x0a');return'\x0a'+_0x534d9d+_0x650c31(0x186)+_0x534d9d+_0x650c31(0x22a)+_0x1e853b+';\x0a';}throw new Error(_0x650c31(0x199)+_0x2ba219[_0x650c31(0x223)]+':\x20must\x20be\x20false,\x20null,\x20or\x20object');}function detectTextColumn(_0x12528b){const _0x10f620=a0_0x2280,_0x296d59={'iiTQe':_0x10f620(0x201),'WNzEU':_0x10f620(0x1ba),'aqZTs':'description','uQwjD':_0x10f620(0x23a),'bIwbO':_0x10f620(0x1f3),'UZdRr':_0x10f620(0x20f),'hSfBP':'nama'},_0x24299d=[_0x296d59['iiTQe'],_0x10f620(0x1c7),_0x296d59[_0x10f620(0x19b)],_0x10f620(0x1a8),_0x296d59['aqZTs'],_0x296d59[_0x10f620(0x1ca)],_0x296d59[_0x10f620(0x1b4)],_0x296d59['UZdRr']];for(const _0x495897 of _0x24299d){const _0x55b618=_0x12528b[_0x10f620(0x229)](_0xd0d0b7=>_0xd0d0b7['toLowerCase']()[_0x10f620(0x24e)](_0x495897));if(_0x55b618)return _0x55b618;}return _0x12528b[_0x10f620(0x229)](_0x65616a=>_0x65616a!=='id')||_0x12528b[0x0]||_0x296d59['hSfBP'];}function createMysqlMainModuleTemplate(_0x3e3207){const _0x2d40fd=a0_0x2280,_0x1e273f={'gufor':function(_0x4d4126,_0x3a9459){return _0x4d4126(_0x3a9459);}},_0x3cd749=_0x1e273f['gufor'](toPascalCase,_0x3e3207);return'const\x20express\x20=\x20require(\x27express\x27);\x0aconst\x20bodyParser\x20=\x20require(\x27body-parser\x27);\x0aconst\x20path\x20=\x20require(\x27path\x27);\x0aconst\x20fs\x20=\x20require(\x27fs\x27);\x0aconst\x20{\x20v4:\x20uuidv4\x20}\x20=\x20require(\x27uuid\x27);\x0aconst\x20{\x20logger,\x20logServerReady,\x20logEndpointRegistered,\x20createRequestLogger,\x20logRequest\x20}\x20=\x20require(\x27@restforgejs/platform/src/utils/logger\x27);\x0aconst\x20ExportHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/export_handler\x27);\x0aconst\x20ImportHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/import_handler\x27);\x0aconst\x20UploadHandler\x20=\x20require(\x27@restforgejs/platform/src/components/handlers/upload_handler\x27);\x0aconst\x20{\x20extractExportConfigFromEndpoint,\x20extractImportConfigFromEndpoint,\x20extractUploadConfigFromEndpoint\x20}\x20=\x20require(\x27@restforgejs/platform/src/utils/config-extractor\x27);\x0aconst\x20rateLimiter\x20=\x20require(\x27@restforgejs/platform/src/middleware/rate-limiter\x27);\x0aconst\x20idempotencyMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/idempotency\x27);\x0aconst\x20bodyOptionsMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/body-options\x27);\x0aconst\x20corsMiddleware\x20=\x20require(\x27@restforgejs/platform/src/middleware/cors\x27);\x0aconst\x20securityHeaders\x20=\x20require(\x27@restforgejs/platform/src/middleware/security-headers\x27);\x0a\x0a/**\x0a\x20*\x20Fungsi\x20untuk\x20mengeksekusi\x20modul\x20'+_0x3e3207+'\x20(MySQL\x20Database)\x0a\x20*\x20@param\x20{Object}\x20config\x20-\x20Konfigurasi\x20untuk\x20menjalankan\x20modul\x0a\x20*\x20@param\x20{number}\x20config.port\x20-\x20Port\x20untuk\x20server\x0a\x20*\x20@param\x20{string}\x20config.key\x20-\x20API\x20Key\x20(opsional)\x0a\x20*\x20@returns\x20{Promise<void>}\x20Promise\x20yang\x20tidak\x20pernah\x20resolve\x20agar\x20server\x20tetap\x20berjalan\x0a\x20*/\x0aasync\x20function\x20execute(config)\x20{\x0a\x20\x20return\x20new\x20Promise((resolve)\x20=>\x20{\x0a\x20\x20\x20\x20const\x20app\x20=\x20express();\x0a\x20\x20\x20\x20const\x20port\x20=\x20config.port\x20||\x203000;\x0a\x20\x20\x20\x20const\x20serverAddress\x20=\x20config.serverAddress\x20||\x20\x270.0.0.0\x27;\x0a\x20\x20\x20\x20const\x20moduleNameCapitalized\x20=\x20\x27'+_0x3cd749+_0x2d40fd(0x2b5)+_0x3e3207+'\x27;\x0a\x20\x20\x20\x20const\x20pluginPath\x20=\x20path.join(__dirname,\x20\x27..\x27,\x20\x27plugins\x27,\x20`${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20let\x20plugin\x20=\x20null;\x0a\x20\x20\x20\x20if\x20(fs.existsSync(pluginPath))\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20plugin\x20=\x20require(pluginPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(plugin.onBeforeEndpointsLoad)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20plugin.onBeforeEndpointsLoad(app,\x20config);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27plugin_loaded\x27,\x20plugin:\x20`${moduleName}-plugin`\x20},\x20`Plugin\x20loaded:\x20${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(pluginError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27plugin_load_error\x27,\x20error:\x20pluginError.message\x20},\x20`Failed\x20to\x20load\x20plugin:\x20${moduleName}-plugin.js`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Health\x20check\x20endpoint\x0a\x20\x20\x20\x20app.get(\x27/api/'+_0x3e3207+'/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'+_0x3e3207+_0x2d40fd(0x2ae)+_0x3e3207+_0x2d40fd(0x235)+_0x3e3207+_0x2d40fd(0x182)+_0x3e3207+'/${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'+_0x3e3207+_0x2d40fd(0x1cf)+_0x3e3207+_0x2d40fd(0x266)+_0x3e3207+_0x2d40fd(0x274)+_0x3e3207+_0x2d40fd(0x212)+_0x3e3207+'\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'+_0x3e3207+_0x2d40fd(0x25b)+_0x3e3207+'/health`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20serviceInfo:\x20`http://${displayHost}:${port}/api/'+_0x3e3207+_0x2d40fd(0x20d)+_0x3e3207+_0x2d40fd(0x22d);}function buildDefaultScopeSQL(_0x5bb1bd,_0x1264ff=''){const _0x1b3fa1=a0_0x2280,_0x5ea66e={'Oscxr':function(_0x3d8f3c,_0x26adc0){return _0x3d8f3c===_0x26adc0;},'OKNZV':_0x1b3fa1(0x293)};if(!_0x5bb1bd||typeof _0x5bb1bd!==_0x1b3fa1(0x1c5))return'';const _0x26de87=[];for(const [_0x1b5dbe,_0x2d8d2c]of Object['entries'](_0x5bb1bd)){if(_0x5ea66e['Oscxr'](typeof _0x2d8d2c,_0x1b3fa1(0x1b5)))_0x26de87[_0x1b3fa1(0x281)](''+_0x1264ff+_0x1b5dbe+_0x1b3fa1(0x2a2)+_0x2d8d2c+'\x27');else{if(typeof _0x2d8d2c==='string')_0x26de87[_0x1b3fa1(0x281)](''+_0x1264ff+_0x1b5dbe+_0x1b3fa1(0x2a2)+_0x2d8d2c['replace'](/'/g,'\x27\x27')+'\x27');else _0x5ea66e['Oscxr'](typeof _0x2d8d2c,_0x1b3fa1(0x1bf))&&_0x26de87['push'](''+_0x1264ff+_0x1b5dbe+_0x1b3fa1(0x1db)+_0x2d8d2c);}}return _0x26de87[_0x1b3fa1(0x2b7)](_0x5ea66e[_0x1b3fa1(0x21b)]);}function buildRequestScopeMiddleware(_0x1c3b29,_0x3c825a){const _0x6cbfc4=a0_0x2280,_0x364206={'FAwdT':function(_0x5616bc,_0x5a7063){return _0x5616bc||_0x5a7063;}};if(!_0x1c3b29[_0x6cbfc4(0x172)])return'';const {column:_0x2f4607,source:_0x33792e,bypassRoles:_0x37981b}=_0x1c3b29['requestScope'],_0x3686ae=JSON[_0x6cbfc4(0x2a6)](_0x37981b||[]),_0x90192a=_0x364206['FAwdT'](_0x3c825a,'')['replace'](/-/g,'_');return'\x0a//\x20───\x20Request\x20Scope\x20Middleware\x20(Layer\x201\x20RLS)\x20──────────────────────────\x0a//\x20Dynamic\x20per-request\x20filtering\x20by\x20\x27'+_0x2f4607+_0x6cbfc4(0x169)+_0x33792e+_0x6cbfc4(0x179)+_0x3686ae+_0x6cbfc4(0x26d)+_0x33792e+_0x6cbfc4(0x2ab)+_0x2f4607+_0x6cbfc4(0x1fb)+_0x2f4607+_0x6cbfc4(0x1d3)+_0x2f4607+'\x27]\x20=\x20scopeValue;\x0a\x20\x20}\x0a\x0a\x20\x20//\x20UPDATE:\x20force\x20scope\x20column\x20value\x20(ownership\x20verified\x20in\x20/update\x20handler)\x0a\x20\x20if\x20(endpoint\x20===\x20\x27update\x27)\x20{\x0a\x20\x20\x20\x20req.body[\x27'+_0x2f4607+_0x6cbfc4(0x26a)+_0x90192a+'\x27]\x20||\x20req.body;\x0a\x20\x20\x20\x20masterBody[\x27'+_0x2f4607+'\x27]\x20=\x20scopeValue;\x0a\x20\x20}\x0a\x0a\x20\x20next();\x0a});\x0a';}function a0_0x2280(_0x4d9779,_0x58880c){_0x4d9779=_0x4d9779-0x166;const _0x45bedf=a0_0x45be();let _0x2280a9=_0x45bedf[_0x4d9779];if(a0_0x2280['UeUzyM']===undefined){var _0x4594bb=function(_0x117837){const _0x18c7b0='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x297a9a='',_0x1e0831='';for(let _0x2a6fdf=0x0,_0x1c287a,_0x45c3a3,_0x14447d=0x0;_0x45c3a3=_0x117837['charAt'](_0x14447d++);~_0x45c3a3&&(_0x1c287a=_0x2a6fdf%0x4?_0x1c287a*0x40+_0x45c3a3:_0x45c3a3,_0x2a6fdf++%0x4)?_0x297a9a+=String['fromCharCode'](0xff&_0x1c287a>>(-0x2*_0x2a6fdf&0x6)):0x0){_0x45c3a3=_0x18c7b0['indexOf'](_0x45c3a3);}for(let _0x49f02f=0x0,_0x403f70=_0x297a9a['length'];_0x49f02f<_0x403f70;_0x49f02f++){_0x1e0831+='%'+('00'+_0x297a9a['charCodeAt'](_0x49f02f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1e0831);};a0_0x2280['PjJKXL']=_0x4594bb,a0_0x2280['SpcjFl']={},a0_0x2280['UeUzyM']=!![];}const _0x220720=_0x45bedf[0x0],_0x290043=_0x4d9779+_0x220720,_0x4a2126=a0_0x2280['SpcjFl'][_0x290043];return!_0x4a2126?(_0x2280a9=a0_0x2280['PjJKXL'](_0x2280a9),a0_0x2280['SpcjFl'][_0x290043]=_0x2280a9):_0x2280a9=_0x4a2126,_0x2280a9;}function createMysqlModelTemplate(_0x28dfaf,_0x442426,_0x23c0e2){const _0x4ab7e3=a0_0x2280,_0x1a0c02={'zvlcN':_0x4ab7e3(0x21c),'vhNwH':_0x4ab7e3(0x1d0),'pEBpV':'time','ZXisG':_0x4ab7e3(0x28b),'zjLbW':function(_0x40fbbe,_0x406482){return _0x40fbbe===_0x406482;},'qmQqs':'generated','VQkvh':_0x4ab7e3(0x204),'rXgcn':_0x4ab7e3(0x1e3),'bcHXc':function(_0x597a20,_0xb13fad){return _0x597a20+_0xb13fad;},'SKPli':function(_0x20b866,_0x13da51){return _0x20b866>_0x13da51;},'yBMqh':function(_0x4a6da2,_0x4c49b){return _0x4a6da2>_0x4c49b;},'urRyS':function(_0x309d1e,_0x559d00){return _0x309d1e===_0x559d00;},'xaxAA':'file:','cmleU':function(_0xd9d36a,_0x461c60,_0x5734fc){return _0xd9d36a(_0x461c60,_0x5734fc);},'TdqVK':function(_0x2d095a,_0x39175a){return _0x2d095a(_0x39175a);},'Swlae':_0x4ab7e3(0x20e),'QvLYy':function(_0x48cac2,_0x31baf3){return _0x48cac2>_0x31baf3;},'fjzTc':_0x4ab7e3(0x284),'HlHkG':'WHERE\x20'},_0x2b9fd7=_0x23c0e2[_0x4ab7e3(0x245)][_0x4ab7e3(0x2aa)](_0x20a2bc=>'\x27'+_0x20a2bc+'\x27')[_0x4ab7e3(0x2b7)](',\x20'),_0x4a474d=toPascalCase(_0x442426),_0x1c0059=_0x23c0e2[_0x4ab7e3(0x1fe)]||'id',_0x41524b=detectTextColumn(_0x23c0e2['fieldName']),_0x49feb1=new Date()[_0x4ab7e3(0x272)](),_0x52e707=_0x1a0c02[_0x4ab7e3(0x1e0)](buildAuditColumnsSection,_0x23c0e2,'\x20\x20'),_0x1e573f=_0x23c0e2['defaultScope']&&_0x23c0e2[_0x4ab7e3(0x194)]['lookup']?buildDefaultScopeSQL(_0x23c0e2[_0x4ab7e3(0x194)][_0x4ab7e3(0x2a4)]):'',_0x12c32e=_0x23c0e2['defaultScope']&&_0x23c0e2[_0x4ab7e3(0x194)][_0x4ab7e3(0x29a)]?_0x1a0c02[_0x4ab7e3(0x244)](buildDefaultScopeSQL,_0x23c0e2[_0x4ab7e3(0x194)][_0x4ab7e3(0x29a)]):'',_0x50972f={};return _0x23c0e2['fieldValidation']&&Array[_0x4ab7e3(0x1b7)](_0x23c0e2[_0x4ab7e3(0x22f)])&&_0x23c0e2[_0x4ab7e3(0x22f)][_0x4ab7e3(0x27c)](_0x6667e7=>{const _0x494d35=_0x4ab7e3;[_0x1a0c02['zvlcN'],'datetime',_0x1a0c02['vhNwH'],_0x1a0c02['pEBpV']]['includes'](_0x6667e7[_0x494d35(0x224)])&&(_0x50972f[_0x6667e7[_0x494d35(0x201)]]={'type':_0x6667e7['type'],'format':_0x6667e7['constraints']&&_0x6667e7[_0x494d35(0x232)]['format']||_0x1a0c02['ZXisG']});}),_0x4ab7e3(0x17e)+_0x4a474d+_0x4ab7e3(0x2a8)+_0x49feb1+'\x0a*\x0a*\x20Table:\x20'+_0x23c0e2[_0x4ab7e3(0x223)]+'\x0a*\x20Primary\x20Key:\x20'+_0x1c0059+'\x0a*\x20Fields:\x20'+_0x23c0e2[_0x4ab7e3(0x245)][_0x4ab7e3(0x256)]+_0x4ab7e3(0x278)+_0x4a474d+'Model\x20extends\x20BaseModel\x20{\x0a/**\x0a\x20*\x20Constructor\x0a\x20*/\x0aconstructor()\x20{\x0a\x20\x20const\x20validFields\x20=\x20[\x0a\x20\x20\x20\x20'+_0x2b9fd7+'\x0a\x20\x20];\x0a\x0a\x20\x20const\x20datatablesWhere\x20=\x20'+(_0x23c0e2['datatablesWhere']?JSON['stringify'](_0x23c0e2[_0x4ab7e3(0x16a)]):_0x1a0c02['Swlae'])+';\x0a\x0a\x20\x20super(\x27'+_0x23c0e2['tableName']+_0x4ab7e3(0x1e4)+_0x1c0059+_0x4ab7e3(0x286)+(_0x23c0e2['viewName']||_0x23c0e2[_0x4ab7e3(0x223)])+_0x4ab7e3(0x18a)+(_0x23c0e2['viewName']||_0x23c0e2['tableName'])+_0x4ab7e3(0x261)+_0x23c0e2[_0x4ab7e3(0x223)]+'\x27;\x0a'+_0x52e707+_0x4ab7e3(0x174)+!!_0x23c0e2['viewQuery']+_0x4ab7e3(0x29e)+!!_0x23c0e2['exportQuery']+';\x0a'+(_0x1a0c02['QvLYy'](Object[_0x4ab7e3(0x197)](_0x50972f)['length'],0x0)?'\x0a\x20\x20//\x20DateTime\x20fields\x20configuration\x20dari\x20fieldValidation\x0a\x20\x20this.dateTimeFields\x20=\x20'+JSON[_0x4ab7e3(0x2a6)](_0x50972f,null,0x4)[_0x4ab7e3(0x18e)]('\x0a')['join'](_0x4ab7e3(0x17b))+';\x0a':'')+(_0x23c0e2[_0x4ab7e3(0x16b)]&&_0x23c0e2['uploadConfig'][_0x4ab7e3(0x238)]?_0x4ab7e3(0x192)+JSON[_0x4ab7e3(0x2a6)](Object[_0x4ab7e3(0x197)](_0x23c0e2['uploadConfig'][_0x4ab7e3(0x238)]))+';\x0a':'')+_0x4ab7e3(0x2b4)+((()=>{const _0x5e99b9=_0x4ab7e3,_0x2522ad={'SAjao':'\x0a\x20\x20\x20\x20\x20\x20'};if(!_0x23c0e2[_0x5e99b9(0x22f)]||!Array['isArray'](_0x23c0e2['fieldValidation'])||_0x1a0c02[_0x5e99b9(0x23b)](_0x23c0e2[_0x5e99b9(0x22f)][_0x5e99b9(0x256)],0x0))return'\x20\x20this.validationConfig\x20=\x20{};\x20//\x20No\x20field\x20validation\x20config';const _0x2c31db=_0x23c0e2[_0x5e99b9(0x22f)]['map'](_0x21187e=>{const _0x5c5b24=_0x5e99b9,_0xb5bb49=JSON['stringify'](_0x21187e[_0x5c5b24(0x232)]||{},null,0x6)['replace'](/\n/g,_0x2522ad[_0x5c5b24(0x1a2)]);return'\x20\x20\x20\x20\x20\x20\x27'+_0x21187e[_0x5c5b24(0x201)]+'\x27:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20type:\x20\x27'+_0x21187e[_0x5c5b24(0x224)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20constraints:\x20'+_0xb5bb49+_0x5c5b24(0x170);})[_0x5e99b9(0x2b7)](',\x0a');return'\x20\x20this.validationConfig\x20=\x20{\x0a'+_0x2c31db+'\x0a\x20\x20\x20\x20}';})())+'\x0a\x0a\x20\x20//\x20Model\x20metadata\x0a\x20\x20this.modelMetadata\x20=\x20{\x0a\x20\x20\x20\x20endpointName:\x20\x27'+_0x442426+_0x4ab7e3(0x1e2)+_0x28dfaf+'\x27,\x0a\x20\x20\x20\x20tableName:\x20\x27'+_0x23c0e2['tableName']+_0x4ab7e3(0x240)+_0x1c0059+_0x4ab7e3(0x225)+_0x23c0e2[_0x4ab7e3(0x245)]['length']+_0x4ab7e3(0x1fc)+_0x49feb1+_0x4ab7e3(0x221)+(_0x23c0e2['advancedQueries']?Object['entries'](_0x23c0e2['advancedQueries'])[_0x4ab7e3(0x2aa)](([_0x3e22a2,_0x3042d9])=>_0x4ab7e3(0x277)+_0x3042d9+'\x22\x20===\x20\x27string\x27\x20&&\x20\x22'+_0x3042d9+'\x22.startsWith(\x27file:\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20relativePath\x20=\x20\x22'+_0x3042d9+_0x4ab7e3(0x18c)+_0x3e22a2+'\x22]\x20=\x20fs.readFileSync(filePath,\x20\x27utf8\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`SQL\x20Template\x20'+_0x3e22a2+_0x4ab7e3(0x187)+_0x3e22a2+'\x20not\x20found:\x20${filePath}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x3e22a2+'\x22]\x20=\x20null;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20templates[\x22'+_0x3e22a2+_0x4ab7e3(0x294)+_0x3042d9+_0x4ab7e3(0x1b2)+_0x3e22a2+_0x4ab7e3(0x1cb)+_0x3e22a2+_0x4ab7e3(0x1ef))['join'](''):_0x4ab7e3(0x1dd))+_0x4ab7e3(0x2b2)+_0x23c0e2['datatablesQuery']['replace'](/\$\{tableName\}/g,'${this.getTableSource(\x27read\x27)}')+_0x4ab7e3(0x253)+(_0x23c0e2['viewQuery']?'\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x23c0e2[_0x4ab7e3(0x22b)][_0x4ab7e3(0x2a1)](/\$\{tableName\}/g,_0x1a0c02[_0x4ab7e3(0x1c4)])+'\x0a\x20\x20`.trim();\x0a\x20\x20baseQuery\x20=\x20this.convertToMysqlSQL(baseQuery);\x0a\x20\x20return\x20baseQuery;':'\x20\x20return\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.readSource;')+_0x4ab7e3(0x23d)+(_0x12c32e?'\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'+_0x12c32e+_0x4ab7e3(0x231)+_0x12c32e+'\x27;\x0a\x20\x20\x20\x20}\x0a':'')+_0x4ab7e3(0x23f)+_0x1c0059+',\x20'+_0x41524b+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20WHERE\x20'+_0x41524b+'\x20LIKE\x20?'+(_0x1e573f?_0x4ab7e3(0x293)+_0x1e573f:'')+_0x4ab7e3(0x246)+_0x41524b+_0x4ab7e3(0x1e9)+_0x1c0059+_0x4ab7e3(0x1a4)+_0x41524b+_0x4ab7e3(0x193)+(_0x1e573f?_0x4ab7e3(0x203)+_0x1e573f+'\x27);\x0a':'')+_0x4ab7e3(0x1be)+_0x41524b+_0x4ab7e3(0x242)+_0x1c0059+',\x20'+_0x41524b+'\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20${whereClause}\x20ORDER\x20BY\x20'+_0x41524b+_0x4ab7e3(0x27f)+_0x1c0059+_0x4ab7e3(0x1a4)+_0x41524b+_0x4ab7e3(0x230)+_0x1c0059+',\x20'+_0x41524b+_0x4ab7e3(0x220)+(_0x1e573f?_0x4ab7e3(0x198)+_0x1e573f:'')+_0x4ab7e3(0x246)+_0x41524b+_0x4ab7e3(0x167)+_0x1c0059+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item.'+_0x41524b+'\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.'+_0x1c0059+_0x4ab7e3(0x1a3)+_0x41524b+_0x4ab7e3(0x2bb)+_0x1c0059+_0x4ab7e3(0x2ad)+_0x1c0059+'\x27,\x20\x27'+_0x41524b+_0x4ab7e3(0x21d)+_0x1c0059+'\x27;\x0a\x20\x20\x20\x20let\x20textField\x20=\x20\x27'+_0x41524b+'\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'+_0x1c0059+'\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20continue;\x20//\x20primary\x20key\x20sudah\x20ada\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Check\x20jika\x20ada\x20SQL\x20expression\x20dengan\x20alias\x20(menggunakan\x20AS)\x0a\x20\x20\x20\x20\x20\x20const\x20aliasRegex\x20=\x20new\x20RegExp(\x27(.+)\x5c\x5cs+as\x5c\x5cs+(\x5c\x5cw+)$\x27,\x20\x27i\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20aliasMatch\x20=\x20column.match(aliasRegex);\x0a\x20\x20\x20\x20\x20\x20if\x20(aliasMatch)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20expression\x20=\x20aliasMatch[1].trim();\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20alias\x20=\x20aliasMatch[2].trim();\x0a\x20\x20\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${expression}\x20AS\x20${alias}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20textField\x20=\x20alias;\x0a\x20\x20\x20\x20\x20\x20\x20\x20aliasField\x20=\x20alias;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Check\x20jika\x20simple\x20field\x20name\x0a\x20\x20\x20\x20\x20\x20if\x20(this.validFields.includes(column)\x20||\x20validTextFields.includes(column))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${column}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20textField\x20=\x20column;\x0a\x20\x20\x20\x20\x20\x20\x20\x20break;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Computed\x20column\x0a\x20\x20\x20\x20\x20\x20selectClause\x20+=\x20`,\x20${column}`;\x0a\x20\x20\x20\x20\x20\x20textField\x20=\x20column;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20query\x20=\x20`SELECT\x20${selectClause}\x20FROM\x20${this.getTableSource(\x27read\x27)}\x20'+(_0x1e573f?_0x4ab7e3(0x25f)+_0x1e573f+'\x20':'')+_0x4ab7e3(0x255)+(_0x1e573f?'AND\x20(':_0x1a0c02[_0x4ab7e3(0x1b9)])+'${whereResult.sql}'+(_0x1e573f?')':'')+'\x20`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20params\x20=\x20whereResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\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\x20error.statusCode\x20=\x20400;\x0a\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//\x20Handle\x20sort_columns\x0a\x20\x20\x20\x20if\x20(options.sort_columns\x20&&\x20Array.isArray(options.sort_columns)\x20&&\x20options.sort_columns.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20orderParts\x20=\x20options.sort_columns.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20column\x20=\x20item.column;\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20direction\x20=\x20(item.direction\x20||\x20\x27ASC\x27).toUpperCase();\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!column)\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(!this.validFields.includes(column))\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(direction\x20!==\x20\x27ASC\x27\x20&&\x20direction\x20!==\x20\x27DESC\x27)\x20return\x20null;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20`${column}\x20${direction}`;\x0a\x20\x20\x20\x20\x20\x20}).filter(Boolean);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(orderParts.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27No\x20valid\x20sort\x20columns\x20provided\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20query\x20+=\x20`ORDER\x20BY\x20${orderParts.join(\x27,\x20\x27)}`;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20query\x20+=\x20`ORDER\x20BY\x20${aliasField\x20||\x20textField}`;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27MySQL\x20Lookup\x20Filter\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27Parameters:\x27,\x20params);\x0a\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params.length\x20>\x200\x20?\x20params\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x1c0059+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item[aliasField\x20||\x20textField]\x20||\x20item.'+_0x41524b+_0x4ab7e3(0x2a9)+((()=>{const _0x2efa3f=_0x4ab7e3,_0x1e410f=_0x23c0e2[_0x2efa3f(0x1f4)]&&_0x23c0e2['masterDetail']['enabled'],_0x1f6c11=_0x23c0e2[_0x2efa3f(0x1c8)]||{};if(!_0x1e410f||!_0x1f6c11['createComposite']&&!_0x1f6c11[_0x2efa3f(0x1a9)]&&!_0x1f6c11[_0x2efa3f(0x23e)])return'';const _0x1808b8=_0x23c0e2['masterDetail'][_0x2efa3f(0x298)],_0x1ac5a5=_0x1808b8['split']('.')['pop'](),_0x4b1993=_0x23c0e2['masterDetail']['foreignKey'],_0x4f3ed6=_0x23c0e2[_0x2efa3f(0x1f4)]['detailConfig']?.['primaryKey']||_0x1ac5a5+_0x2efa3f(0x226),_0x418328=_0x23c0e2['masterDetail']['headerCalculations']||null,_0x154c45=_0x23c0e2['masterDetail'][_0x2efa3f(0x20a)]?.['autoCalculateFields']||{},_0x21e198=_0x23c0e2[_0x2efa3f(0x1f4)]['detailConfig']?.['detailQuery']||null,_0xac2a6a=[],_0x3bfcd2=[];for(const [_0x258317,_0x19d85c]of Object['entries'](_0x154c45)){if(_0x19d85c['type']===_0x1a0c02[_0x2efa3f(0x18d)])_0xac2a6a[_0x2efa3f(0x281)](_0x258317);else{const _0x49c54f=(_0x19d85c['formula']||'')[_0x2efa3f(0x18e)]('*')[_0x2efa3f(0x2aa)](_0x231425=>_0x231425[_0x2efa3f(0x1f5)]());_0x3bfcd2['push']({'fieldName':_0x258317,'qtyField':_0x49c54f[0x0],'priceField':_0x49c54f[0x1]});}}const _0x258d97=_0x154c45['total_amount']?.[_0x2efa3f(0x18f)]||'',_0x2d27f3=_0x258d97['split']('*')[_0x2efa3f(0x2aa)](_0x156a87=>_0x156a87[_0x2efa3f(0x1f5)]()),_0x588384=_0x418328?.[_0x2efa3f(0x1a1)]?.[_0x2efa3f(0x1c2)]?.['replace'](_0x1a0c02[_0x2efa3f(0x28d)],'')||'',_0x556075=_0x2d27f3[0x0]||_0x588384,_0x3b3754=_0x2d27f3[0x1]||_0x1a0c02[_0x2efa3f(0x1b8)],_0x5aaac3=_0x3bfcd2['map'](_0x4d20fb=>{const _0x569efd=_0x2efa3f;return _0x569efd(0x168)+_0x4d20fb['fieldName']+'\x20=\x20'+_0x4d20fb['qtyField']+'\x20*\x20'+_0x4d20fb[_0x569efd(0x22c)]+'\x0a\x20\x20\x20\x20\x20\x20item.'+_0x4d20fb['fieldName']+_0x569efd(0x236)+_0x4d20fb['qtyField']+')\x20||\x200)\x20*\x20(Number(item.'+_0x4d20fb[_0x569efd(0x22c)]+')\x20||\x200);';})['join']('\x0a');let _0x54874a='';_0x1f6c11[_0x2efa3f(0x1da)]&&(_0x54874a+=_0x2efa3f(0x27d)+_0x1ac5a5+'\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'+_0x23c0e2[_0x2efa3f(0x223)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1808b8+_0x2efa3f(0x1eb)+_0x4b1993+'\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'+_0x1808b8+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x4b1993+'\x27;\x0a\x20\x20\x20\x20const\x20detailPk\x20=\x20\x27'+_0x4f3ed6+_0x2efa3f(0x1b0)+(_0xac2a6a[_0x2efa3f(0x256)]>0x0?'const\x20generatedFields\x20=\x20'+JSON['stringify'](_0xac2a6a)+';':'')+'\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'+(_0x5aaac3?_0x1a0c02[_0x2efa3f(0x2a3)]('\x0a',_0x5aaac3)+'\x0a':'')+'\x0a\x20\x20\x20\x20\x20\x20const\x20detailFields\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20detailValues\x20=\x20[];\x0a\x20\x20\x20\x20\x20\x20const\x20detailPlaceholders\x20=\x20[];\x0a\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'+(_0xac2a6a[_0x2efa3f(0x256)]>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\x20detailFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailPlaceholders.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\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20this._appendCreateAuditColumns(detailFields,\x20detailValues,\x20detailPlaceholders,\x20item,\x20eventContext);\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20detailInsertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20(\x27\x20+\x20detailFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20detailPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20\x20\x20console.log(\x27Executing\x20detail\x20INSERT:\x27,\x20{\x20query:\x20detailInsertSql,\x20values:\x20detailValues\x20});\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(detailInsertSql,\x20detailValues);\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20SELECT\x20back\x20inserted\x20detail\x0a\x20\x20\x20\x20\x20\x20const\x20detailSelectSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20\x20\x20const\x20[detailRows]\x20=\x20await\x20connection.execute(detailSelectSql,\x20[item[detailPk]]);\x0a\x20\x20\x20\x20\x20\x20if\x20(detailRows[0])\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20insertedItems.push(detailRows[0]);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27Inserted\x20\x27\x20+\x20insertedItems.length\x20+\x20\x27\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onAfterCompositeInsert\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.buildCompositeInsertAfterContext(headerData,\x20insertedHeader,\x20insertedItems,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x23c0e2['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1808b8+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x4b1993+'\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');_0x1f6c11[_0x2efa3f(0x1a9)]&&(_0x54874a+='\x0a/**\x0a\x20*\x20Composite\x20update\x20-\x20Update\x20header\x20with\x20granular\x20detail\x20operations\x20(MySQL)\x0a\x20*/\x0aasync\x20updateComposite(data,\x20eventContext\x20=\x20null)\x20{\x0a\x20\x20const\x20connection\x20=\x20await\x20db.getConnection();\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20await\x20connection.beginTransaction();\x0a\x0a\x20\x20\x20\x20const\x20primaryKeyValue\x20=\x20data[this.primaryKey];\x0a\x20\x20\x20\x20if\x20(!primaryKeyValue)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Primary\x20key\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20is\x20required\x20for\x20update\x27);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Check\x20if\x20record\x20exists\x20(also\x20serves\x20as\x20prefetch\x20oldData\x20for\x20hooks)\x0a\x20\x20\x20\x20const\x20checkSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20this.writeSource\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20this.primaryKey\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20const\x20[checkRows]\x20=\x20await\x20connection.execute(checkSql,\x20[primaryKeyValue]);\x0a\x20\x20\x20\x20if\x20(!checkRows\x20||\x20checkRows.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Record\x20not\x20found\x27);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20oldData\x20=\x20checkRows[0];\x0a\x0a\x20\x20\x20\x20//\x20Extract\x20header\x20data\x0a\x20\x20\x20\x20const\x20headerData\x20=\x20{\x20...data\x20};\x0a\x20\x20\x20\x20const\x20detailKey\x20=\x20\x27'+_0x1ac5a5+'\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'+_0x23c0e2['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1808b8+_0x2efa3f(0x1eb)+_0x4b1993+'\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'+_0x1808b8+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x4b1993+'\x27;\x0a\x20\x20\x20\x20const\x20detailPk\x20=\x20\x27'+_0x4f3ed6+_0x2efa3f(0x1b0)+(_0x1a0c02[_0x2efa3f(0x1d7)](_0xac2a6a[_0x2efa3f(0x256)],0x0)?'const\x20generatedFields\x20=\x20'+JSON['stringify'](_0xac2a6a)+';':'')+'\x0a\x0a\x20\x20\x20\x20const\x20detailOperations\x20=\x20data[detailKey]\x20||\x20{};\x0a\x20\x20\x20\x20const\x20{\x20insert:\x20insertItems\x20=\x20[],\x20update:\x20updateItems\x20=\x20[],\x20delete:\x20deleteItems\x20=\x20[]\x20}\x20=\x20detailOperations;\x0a\x0a\x20\x20\x20\x20const\x20deletedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20updatedItems\x20=\x20[];\x0a\x20\x20\x20\x20const\x20insertedItems\x20=\x20[];\x0a\x0a\x20\x20\x20\x20//\x201.\x20DELETE\x20operations\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20deleteItems)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!item[detailPk])\x20throw\x20new\x20Error(\x27Missing\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20in\x20delete\x20operation\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20delSql\x20=\x20\x27DELETE\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(delSql,\x20[item[detailPk]]);\x0a\x20\x20\x20\x20\x20\x20deletedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Deleted\x20\x27\x20+\x20deletedItems.length\x20+\x20\x27\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x202.\x20UPDATE\x20operations\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20updateItems)\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!item[detailPk])\x20throw\x20new\x20Error(\x27Missing\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20in\x20update\x20operation\x27);\x0a'+(_0x5aaac3?'\x0a'+_0x5aaac3+'\x0a':'')+_0x2efa3f(0x16c)+(_0xac2a6a[_0x2efa3f(0x256)]>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\x20+\x20\x27\x20=\x20?\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dValues.push(value);\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\x20(updated_at,\x20updated_by)\x20untuk\x20detail\x20UPDATE\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20this._appendUpdateAuditColumns(dFields,\x20dValues,\x20item,\x20eventContext);\x0a\x0a\x20\x20\x20\x20\x20\x20dValues.push(item[detailPk]);\x0a\x20\x20\x20\x20\x20\x20const\x20dUpdateSql\x20=\x20\x27UPDATE\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20SET\x20\x27\x20+\x20dFields.join(\x27,\x20\x27)\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20detailPk\x20+\x20\x27\x20=\x20?\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20connection.execute(dUpdateSql,\x20dValues);\x0a\x20\x20\x20\x20\x20\x20updatedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Updated\x20\x27\x20+\x20updatedItems.length\x20+\x20\x27\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x203.\x20INSERT\x20operations\x0a\x20\x20\x20\x20for\x20(const\x20item\x20of\x20insertItems)\x20{\x0a\x20\x20\x20\x20\x20\x20item[fk]\x20=\x20primaryKeyValue;\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//\x20(sama\x20seperti\x20di\x20createComposite;\x20konsisten\x20dengan\x20kolom\x20VARCHAR\x0a\x20\x20\x20\x20\x20\x20//\x20DEFAULT\x20(UUID()))\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'+(_0x5aaac3?'\x0a'+_0x5aaac3+'\x0a':'')+_0x2efa3f(0x20c)+(_0x1a0c02['yBMqh'](_0xac2a6a[_0x2efa3f(0x256)],0x0)?_0x2efa3f(0x1a0):'')+_0x2efa3f(0x218)+(_0x418328?'\x0a\x20\x20\x20\x20//\x20Recalculate\x20header\x20totals\x0a\x20\x20\x20\x20const\x20calculations\x20=\x20'+JSON[_0x2efa3f(0x2a6)](_0x418328)+_0x2efa3f(0x2ba)+(_0x556075?_0x2efa3f(0x189)+_0x556075+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20price\x20=\x20Number(item.'+_0x3b3754+_0x2efa3f(0x288):'//\x20WARNING:\x20headerCalculations.total_amount\x20skipped\x20—\x20no\x20qty\x20field\x20configured')+_0x2efa3f(0x1b6):'')+_0x2efa3f(0x1e5)+_0x23c0e2[_0x2efa3f(0x223)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x1808b8+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x4b1993+_0x2efa3f(0x2b1));if(_0x1f6c11['readComposite']){let _0x34e7b2;if(_0x21e198&&_0x1a0c02['urRyS'](typeof _0x21e198,'string')&&_0x21e198['startsWith'](_0x1a0c02['xaxAA'])){const _0x503edd=_0x21e198['replace']('file:',''),_0x4d2a2c=_0x503edd['split']('/')['pop']();_0x34e7b2=_0x2efa3f(0x26b)+_0x4d2a2c+_0x2efa3f(0x25c);}else _0x21e198?_0x34e7b2='const\x20detailSql\x20=\x20`'+_0x21e198[_0x2efa3f(0x2a1)](/\$1/g,'?')+'`;':_0x34e7b2=_0x2efa3f(0x1ff)+_0x1808b8+_0x2efa3f(0x198)+_0x4b1993+_0x2efa3f(0x259);_0x54874a+='\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'+_0x1ac5a5+_0x2efa3f(0x1b0)+_0x34e7b2+_0x2efa3f(0x291);}return _0x54874a;})())+'\x0a}\x0a\x0amodule.exports\x20=\x20new\x20'+_0x4a474d+_0x4ab7e3(0x268);}function createMysqlSubmoduleTemplate(_0x176c0a,_0x3f2e01,_0x124b86){const _0x30b0ff=a0_0x2280,_0x13259c={'DdZiE':function(_0x90c0fb,_0x5f10e9){return _0x90c0fb+_0x5f10e9;},'VmjXu':function(_0x29e3ef,_0x1e5704){return _0x29e3ef(_0x1e5704);},'SkDwJ':'Model','wDsrV':'null','HLzJG':'let\x20componentEngine\x20=\x20null;\x0alet\x20ContextBuilder\x20=\x20null;\x0a','afdyI':function(_0x381848,_0x42d294){return _0x381848(_0x42d294);},'euMFA':_0x30b0ff(0x26c)},_0x46a517=_0x13259c[_0x30b0ff(0x1ac)](_0x13259c['VmjXu'](toCamelCase,_0x3f2e01),_0x13259c[_0x30b0ff(0x19f)]),_0xdc9a0e=_0x124b86[_0x30b0ff(0x1fe)]||'id',_0x4a8129=new Date()[_0x30b0ff(0x272)](),_0x12059b=Object[_0x30b0ff(0x1b3)](_0x124b86['action']||{})[_0x30b0ff(0x265)](([,_0x6d2bec])=>_0x6d2bec)[_0x30b0ff(0x2aa)](([_0x15925f])=>_0x15925f),_0x42931d=_0x124b86['components']&&Array[_0x30b0ff(0x1b7)](_0x124b86['components'])&&_0x124b86[_0x30b0ff(0x200)][_0x30b0ff(0x256)]>0x0,_0x39900b=_0x124b86['exportQuery']||'SELECT\x20'+_0x124b86[_0x30b0ff(0x245)][_0x30b0ff(0x2b7)](',\x20')+'\x20FROM\x20'+_0x124b86['tableName'],_0x3e97dd=_0x39900b['replace'](/\\/g,'\x5c\x5c')['replace'](/'/g,'\x5c\x27'),_0x48e6ca=JSON['stringify'](_0x124b86[_0x30b0ff(0x245)]),_0x49fca3=_0x124b86[_0x30b0ff(0x233)]?JSON['stringify'](_0x124b86[_0x30b0ff(0x233)]):_0x13259c[_0x30b0ff(0x184)],_0x2dcd5a=_0x124b86[_0x30b0ff(0x29f)]?JSON['stringify'](_0x124b86['fieldLabels']):_0x13259c[_0x30b0ff(0x184)],_0x5e65b3=_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86['action']['import']?{'enabled':!![],'upsertKeys':(_0x124b86[_0x30b0ff(0x19a)]||{})['upsertKeys']||[_0xdc9a0e],'upsertStrategy':(_0x124b86[_0x30b0ff(0x19a)]||{})['upsertStrategy']||_0x30b0ff(0x1ae),'requiredFields':(_0x124b86['importConfig']||{})['requiredFields']||[],'validations':(_0x124b86[_0x30b0ff(0x19a)]||{})[_0x30b0ff(0x1d4)]||{},'lookupFields':(_0x124b86['importConfig']||{})[_0x30b0ff(0x1d1)]||{},'excludeFromImport':(_0x124b86['importConfig']||{})['excludeFromImport']||[],'chunkSize':(_0x124b86['importConfig']||{})[_0x30b0ff(0x1f8)]||0x64}:null,_0x3b389c=_0x5e65b3?JSON['stringify'](_0x5e65b3):'null',_0x2ab034=_0x124b86['action']&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x16f)]?'\x27'+_0x3e97dd+'\x27':_0x30b0ff(0x173),_0x3cf294=_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x195)]?_0x124b86['adjustConfig']||{}:null,_0x20a497=_0x3cf294?JSON['stringify'](_0x3cf294):_0x13259c['wDsrV'],_0x2a1093=_0x124b86[_0x30b0ff(0x2b6)]?',\x20'+JSON[_0x30b0ff(0x2a6)](_0x124b86[_0x30b0ff(0x2b6)]):'';let _0x2aefe4=_0x13259c[_0x30b0ff(0x196)];_0x42931d&&(_0x2aefe4+=_0x30b0ff(0x217),_0x2aefe4+=_0x30b0ff(0x296));const _0x4c3272=_0x124b86['requestScope']?_0x30b0ff(0x1df)+_0x46a517+_0x30b0ff(0x1f0)+_0x3f2e01+_0x30b0ff(0x178):'',_0x53e52a=_0x124b86['requestScope']?'\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'+_0x3f2e01+_0x30b0ff(0x211):'',_0x2ef97b=_0x124b86['requestScope']?'\x0a\x20\x20\x20\x20//\x20Request\x20scope\x20ownership\x20verification\x20(Layer\x201\x20RLS)\x20—\x20master\x20record\x0a\x20\x20\x20\x20if\x20(req._requestScope)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20scopeCheck\x20=\x20await\x20'+_0x46a517+'.getData({\x0a\x20\x20\x20\x20\x20\x20\x20\x20where:\x20[\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20key:\x20\x27'+_0xdc9a0e+'\x27,\x20value:\x20data.'+_0xdc9a0e+_0x30b0ff(0x1c0):'';let _0x1e3437='const\x20express\x20=\x20require(\x27express\x27);\x0aconst\x20router\x20=\x20express.Router();\x0aconst\x20'+_0x46a517+_0x30b0ff(0x166)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x17f)+_0x2aefe4+_0x30b0ff(0x1f2)+_0x13259c[_0x30b0ff(0x1ab)](toPascalCase,_0x3f2e01)+_0x30b0ff(0x1fa)+_0x4a8129+_0x30b0ff(0x273)+_0x3f2e01+'\x0a*\x20Actions:\x20'+_0x12059b[_0x30b0ff(0x2b7)](',\x20')+'\x0a*\x20Table:\x20'+_0x124b86['tableName']+'\x0a*\x20Database:\x20MySQL\x0a*/\x0a\x0a//\x20Primary\x20key\x20untuk\x20endpoint\x20ini\x0aconst\x20primaryKey\x20=\x20\x27'+_0xdc9a0e+_0x30b0ff(0x24f)+_0x124b86['tableName']+_0x30b0ff(0x28c)+_0x48e6ca+',\x0a\x20\x20exportQuery:\x20'+_0x2ab034+_0x30b0ff(0x28f)+_0x49fca3+',\x0a\x20\x20fieldLabels:\x20'+_0x2dcd5a+_0x30b0ff(0x1ee)+_0x3b389c+_0x30b0ff(0x295)+_0x20a497+_0x30b0ff(0x24d)+(_0x124b86['uploadConfig']?JSON['stringify'](_0x124b86['uploadConfig']):_0x13259c[_0x30b0ff(0x184)])+_0x30b0ff(0x191)+(_0x124b86[_0x30b0ff(0x172)]?JSON[_0x30b0ff(0x2a6)](_0x124b86['requestScope']):'null')+'\x0a};\x0a'+(_0x42931d?_0x30b0ff(0x1e7)+JSON['stringify'](_0x124b86[_0x30b0ff(0x200)])+_0x30b0ff(0x188)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x214)+_0x176c0a+'/'+_0x3f2e01+':`,\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'+_0xdc9a0e+_0x30b0ff(0x251);_0x1e3437+=buildRequestScopeMiddleware(_0x124b86,_0x3f2e01);_0x124b86['action']&&_0x124b86['action'][_0x30b0ff(0x1f7)]&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+'/datatables\x20-\x20Data\x20untuk\x20DataTables\x0arouter.post(\x27/datatables\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20options\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20searchValue:\x20req.body.search?.value\x20||\x20req.body.searchValue\x20||\x20req.body.search_value\x20||\x20\x27\x27,\x0a\x20\x20\x20\x20\x20\x20searchBy:\x20req.body.searchBy\x20||\x20req.body.search_by\x20||\x20\x27all\x27,\x0a\x20\x20\x20\x20\x20\x20perPage:\x20Math.min(parseInt(req.body.length\x20||\x20req.body.pagination?.perpage\x20||\x2010,\x2010),\x201000),\x0a\x20\x20\x20\x20\x20\x20start:\x20Math.max(parseInt(req.body.start\x20||\x200,\x2010),\x200),\x0a\x20\x20\x20\x20\x20\x20draw:\x20req.body.draw\x20||\x20\x271\x27\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20//\x20Handle\x20sort_columns\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\x20options.sort_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//\x20Fallback:\x20Handle\x20DataTables\x20standard\x20format\x20(order[0][column]\x20dan\x20order[0][dir])\x0a\x20\x20\x20\x20if\x20(req.body[\x27order[0][column]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20options[\x27order[0][column]\x27]\x20=\x20req.body[\x27order[0][column]\x27];\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(req.body[\x27order[0][dir]\x27]\x20!==\x20undefined)\x20{\x0a\x20\x20\x20\x20\x20\x20options[\x27order[0][dir]\x27]\x20=\x20req.body[\x27order[0][dir]\x27];\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Handle\x20filters\x20dengan\x20sanitasi\x0a\x20\x20\x20\x20if\x20(req.body.filters\x20&&\x20typeof\x20req.body.filters\x20===\x20\x27object\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20sanitizedFilters\x20=\x20{};\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(req.body.filters))\x20{\x0a\x20\x20\x20\x20\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)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20sanitizedFilters[key]\x20=\x20value;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20if\x20(Object.keys(sanitizedFilters).length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20options.filters\x20=\x20sanitizedFilters;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x0a\x20\x20\x20\x20if\x20(req.body.where)\x20{\x0a\x20\x20\x20\x20\x20\x20options.where\x20=\x20req.body.where;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Advanced\x20filters\x20support\x0a\x20\x20\x20\x20if\x20(req.body.advanced_filters\x20&&\x20Array.isArray(req.body.advanced_filters))\x20{\x0a\x20\x20\x20\x20\x20\x20options.advancedFilters\x20=\x20req.body.advanced_filters;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Gunakan\x20model\x20untuk\x20mendapatkan\x20data\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x46a517+_0x30b0ff(0x2b3)+_0x3f2e01+'\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'+_0x3f2e01+'\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');_0x124b86['action']&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x2a4)]&&(_0x1e3437+='//\x20GET\x20/api/'+_0x176c0a+'/'+_0x3f2e01+'/lookup\x20-\x20MySQL\x20Dynamic\x20Lookup\x0arouter.get(\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\x27dynamic\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\x20dynamic\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\x20let\x20search\x20=\x20req.query.search\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20if\x20(Array.isArray(search))\x20{\x0a\x20\x20\x20\x20\x20\x20search\x20=\x20search[0]\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Search\x20length\x20validation\x0a\x20\x20\x20\x20if\x20(search.length\x20>\x20100)\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\x27Search\x20Too\x20Long\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Search\x20parameter\x20must\x20not\x20exceed\x20100\x20characters\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}\x20dynamic\x20search:\x20${search}`);\x0a\x0a\x20\x20\x20\x20//\x20Collect\x20extra\x20filters\x20dari\x20query\x20params\x0a\x20\x20\x20\x20const\x20extraFilters\x20=\x20{};\x0a\x20\x20\x20\x20for\x20(const\x20[key,\x20value]\x20of\x20Object.entries(req.query))\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(key\x20!==\x20\x27search\x27\x20&&\x20'+_0x46a517+'.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'+_0x46a517+'.getLookupDataDynamic(search,\x20extraFilters)\x20:\x0a\x20\x20\x20\x20\x20\x20await\x20'+_0x46a517+_0x30b0ff(0x1bc)+_0x3f2e01+'\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');_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)]['lookup']&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x22e)+_0x46a517+'.getLookupDataWithFilter(req.body);\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Legacy\x20format\x20dengan\x20selected_tag\x0a\x20\x20\x20\x20\x20\x20const\x20selectedTag\x20=\x20req.body.selected_tag\x20||\x20\x27\x27;\x0a\x20\x20\x20\x20\x20\x20list\x20=\x20await\x20'+_0x46a517+_0x30b0ff(0x1c3)+_0x3f2e01+_0x30b0ff(0x275));_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x290)]&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x19c)+_0x46a517+_0x30b0ff(0x29c)+_0x46a517+_0x30b0ff(0x29d)+(_0x42931d?'\x0a\x20\x20\x20\x20//\x20Component\x20engine:\x20build\x20eventContext\x20untuk\x20model-level\x20event\x20lifecycle\x0a\x20\x20\x20\x20if\x20(componentEngine\x20&&\x20ContextBuilder)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20eventContext\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20componentEngine:\x20componentEngine,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ContextBuilder:\x20ContextBuilder,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x124b86[_0x30b0ff(0x223)]+_0x30b0ff(0x215)+_0x46a517+'.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'+_0x46a517+_0x30b0ff(0x27e):_0x30b0ff(0x1aa)+_0x46a517+'.addData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20INSERT\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20INSERT\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a')+_0x30b0ff(0x254)+_0x3f2e01+'\x20data\x20added\x20successfully:\x20${result.'+_0xdc9a0e+_0x30b0ff(0x234)+_0x3f2e01+'\x20data\x20successfully\x20added\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\x20menambahkan\x20data\x20'+_0x3f2e01+':\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'+_0x3f2e01+'\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');_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)]['update']&&(_0x1e3437+=_0x30b0ff(0x279)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x202)+_0xdc9a0e+_0x30b0ff(0x2a0)+_0x4c3272+'\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x20dengan\x20model\x20jika\x20tersedia\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x46a517+'.validateData\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20validation\x20=\x20await\x20'+_0x46a517+_0x30b0ff(0x1f1)+(_0x42931d?'\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'+_0x124b86[_0x30b0ff(0x223)]+_0x30b0ff(0x183)+_0x46a517+'.updateData(req.body,\x20eventContext'+_0x2a1093+_0x30b0ff(0x249):'\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'+_0x46a517+_0x30b0ff(0x1a5)+_0x2a1093+_0x30b0ff(0x25e))+'\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0x3f2e01+_0x30b0ff(0x21f)+_0xdc9a0e+_0x30b0ff(0x1fd)+_0xdc9a0e+_0x30b0ff(0x1c1)+_0x3f2e01+'\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'+_0x3f2e01+_0x30b0ff(0x1af)+_0x3f2e01+_0x30b0ff(0x289)+_0x3f2e01+'\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');_0x124b86['action']&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x195)]&&(_0x1e3437+=_0x30b0ff(0x279)+_0x176c0a+'/'+_0x3f2e01+'/adjust\x20-\x20MySQL\x20Adjust\x20(atomic\x20increment/decrement)\x0arouter.post(\x27/adjust\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'+_0xdc9a0e+'\x27;\x0a\x20\x20\x20\x20if\x20(!req.body[primaryKey])\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})\x20is\x20required\x20for\x20adjust`,\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\x20adjustments\x20array\x0a\x20\x20\x20\x20if\x20(!req.body.adjustments\x20||\x20!Array.isArray(req.body.adjustments)\x20||\x20req.body.adjustments.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\x27adjustments\x20array\x20is\x20required\x20and\x20must\x20not\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'+_0x4c3272+'\x0a\x20\x20\x20\x20const\x20adjustConfig\x20=\x20componentConfig.adjustConfig\x20||\x20{};\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a'+(_0x42931d?_0x30b0ff(0x271)+_0x124b86['tableName']+'\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'+_0x46a517+'.adjustData(req.body,\x20adjustConfig,\x20eventContext'+_0x2a1093+_0x30b0ff(0x27a):_0x30b0ff(0x1c6)+_0x46a517+'.adjustData(req.body,\x20adjustConfig,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20}'+_0x2a1093+');\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')+_0x30b0ff(0x24b)+_0x3f2e01+_0x30b0ff(0x1e8)+_0xdc9a0e+'=${req.body[\x27'+_0xdc9a0e+_0x30b0ff(0x1c1)+_0x3f2e01+_0x30b0ff(0x264)+_0x3f2e01+_0x30b0ff(0x297)+_0x3f2e01+_0x30b0ff(0x17a)+_0x3f2e01+_0x30b0ff(0x29b));if(_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86['action']['workflow']){const _0x89a27=_0x124b86['workflow']?JSON['stringify'](_0x124b86[_0x30b0ff(0x1d2)]):'{}';_0x1e3437+=_0x30b0ff(0x279)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x239)+_0x3f2e01+'\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'+_0xdc9a0e+'\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'+_0x4c3272[_0x30b0ff(0x2a1)](/req\.body\[primaryKey\]/g,_0x13259c['euMFA'])+'\x0a\x20\x20\x20\x20const\x20workflowConfig\x20=\x20'+_0x89a27+_0x30b0ff(0x270)+_0x176c0a+_0x30b0ff(0x299)+(_0x42931d?'\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'+_0x124b86['tableName']+_0x30b0ff(0x1ed)+_0x46a517+_0x30b0ff(0x17d):'\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'+_0x46a517+_0x30b0ff(0x1f6))+_0x30b0ff(0x207)+_0x3f2e01+_0x30b0ff(0x16e)+_0x3f2e01+_0x30b0ff(0x210)+_0x3f2e01+'\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\x20//\x20Lock\x20acquisition\x20failed\x0a\x20\x20\x20\x20if\x20(error.message.includes(\x27Failed\x20to\x20acquire\x20lock\x27))\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\x27Resource\x20busy\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\x20changing\x20status\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(_0x124b86['action']&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x260)]){const _0x129b99=_0x124b86['aggregateConfig']?JSON['stringify'](_0x124b86[_0x30b0ff(0x25d)]):'{}';_0x1e3437+=_0x30b0ff(0x279)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x1ad)+_0x129b99+';\x0a\x20\x20\x20\x20const\x20result\x20=\x20await\x20'+_0x46a517+_0x30b0ff(0x2ac)+_0x3f2e01+':\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'+_0x3f2e01+_0x30b0ff(0x29b);}_0x124b86['action']&&_0x124b86['action'][_0x30b0ff(0x216)]&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+'/delete\x20-\x20MySQL\x20Delete\x0arouter.post(\x27/delete\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Validasi\x20request\x20body\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\x20if\x20(!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\x27Invalid\x20request\x20format:\x20where\x20parameter\x20is\x20required\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\x22id\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//\x20Validasi\x20format\x20where\x0a\x20\x20\x20\x20if\x20(!Array.isArray(req.body.where)\x20&&\x20!req.body.where.conditions)\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\x27Invalid\x20where\x20format\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[\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20{\x20\x22key\x22:\x20\x22id\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20}\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\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a\x20\x20\x20\x20//\x20Cek\x20apakah\x20data\x20exist\x20sebelum\x20delete\x20dan\x20ambil\x20old\x20data\x20untuk\x20event\x20lifecycle\x0a\x20\x20\x20\x20//\x20Menggunakan\x20SELECT\x20*\x20dari\x20tabel\x20utama\x20(tanpa\x20explicit\x20select)\x20karena\x20fieldName\x0a\x20\x20\x20\x20//\x20bisa\x20mengandung\x20kolom\x20dari\x20JOIN\x20(mis.\x20city_name)\x20yang\x20tidak\x20ada\x20di\x20tabel\x20utama\x0a\x20\x20\x20\x20if\x20(req.body.where\x20&&\x20Array.isArray(req.body.where)\x20&&\x20req.body.where.length\x20>\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20firstCondition\x20=\x20req.body.where[0];\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20existingData\x20=\x20await\x20'+_0x46a517+'.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'+_0x3f2e01+_0x30b0ff(0x1cc)+(_0x42931d?'\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'+_0x124b86[_0x30b0ff(0x223)]+'\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'+_0x46a517+_0x30b0ff(0x16d):'\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'+_0x46a517+'.deleteData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20DELETE\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\x20DELETE\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(`'+_0x3f2e01+_0x30b0ff(0x280)+_0x3f2e01+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27ER_ROW_IS_REFERENCED_2\x27\x20||\x20error.errno\x20===\x201451)\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\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Cannot\x20delete:\x20record\x20is\x20still\x20referenced\x20by\x20other\x20data\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\x20deleting\x20'+_0x3f2e01+_0x30b0ff(0x29b));_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86['action']['first']&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x21e)+_0xdc9a0e+'\x22,\x20\x22value\x22:\x20\x22your-id-value\x22\x20},\x0a\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},\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\x20dan\x20where.value\x0a\x20\x20\x20\x20if\x20(!req.body.where.key\x20||\x20req.body.where.value\x20===\x20undefined\x20||\x20req.body.where.value\x20===\x20null\x20||\x20req.body.where.value\x20===\x20\x27\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\x20where\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Where\x20key\x20and\x20value\x20are\x20required\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'+_0xdc9a0e+_0x30b0ff(0x23c)+JSON[_0x30b0ff(0x2a6)](_0x124b86['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'+_0x46a517+_0x30b0ff(0x208)+_0x53e52a+_0x30b0ff(0x262)+_0x3f2e01+':\x27,\x20error);\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\x20fetching\x20'+_0x3f2e01+_0x30b0ff(0x29b));_0x124b86['action']&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x29a)]&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+'/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[_0x30b0ff(0x2a6)](_0x124b86[_0x30b0ff(0x245)]||[])+';\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'+_0x46a517+_0x30b0ff(0x222)+_0x3f2e01+'\x20list:\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'+_0x3f2e01+'\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(_0x124b86[_0x30b0ff(0x1f4)]&&_0x124b86['masterDetail'][_0x30b0ff(0x1d6)]){const _0x5d3664=_0x124b86['masterDetail']['detailTable'][_0x30b0ff(0x18e)]('.')[_0x30b0ff(0x1de)](),_0xf69ae1=_0x3f2e01['replace'](/-/g,'_'),_0x45436c=_0x124b86['masterDetail']['headerCalculations']||null,_0x3b0719=_0x124b86['masterDetail']?.['detailConfig']?.[_0x30b0ff(0x2b0)]?.[_0x30b0ff(0x209)]?.[_0x30b0ff(0x18f)]||'',_0x4fa3ec=_0x3b0719['split']('*')['map'](_0x356ea8=>_0x356ea8[_0x30b0ff(0x1f5)]()),_0x31767d=_0x45436c?.['total_qty']?.[_0x30b0ff(0x1c2)]?.['replace']('items.','')||'',_0x3a1d33=_0x4fa3ec[0x0]||_0x31767d,_0x2d53bd=_0x4fa3ec[0x1]||_0x30b0ff(0x1e3);if(_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86['action'][_0x30b0ff(0x1da)]){const _0x456843=_0x45436c?'\x0a\x20\x20\x20\x20var\x20headerCalc\x20=\x20'+JSON[_0x30b0ff(0x2a6)](_0x45436c)+';\x0a\x20\x20\x20\x20if\x20(headerCalc.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_items\x20=\x20data.'+_0x5d3664+_0x30b0ff(0x26e)+_0x5d3664+_0x30b0ff(0x26f)+(_0x3a1d33?_0x30b0ff(0x2b9)+_0x5d3664+_0x30b0ff(0x2a7)+_0x3a1d33+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20var\x20price\x20=\x20Number(item.'+_0x2d53bd+_0x30b0ff(0x252):_0x30b0ff(0x1b1))+'\x0a\x20\x20\x20\x20console.log(\x27Calculated\x20totals:\x27,\x20{\x20total_items:\x20data.total_items,\x20total_qty:\x20data.total_qty,\x20total_amount:\x20data.total_amount\x20});\x0a\x20\x20\x20\x20':'';_0x1e3437+=_0x30b0ff(0x279)+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x206)+_0x3f2e01+'/create-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0xf69ae1+_0x30b0ff(0x1c9)+_0xf69ae1+'\x22\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\x20var\x20data\x20=\x20req.body.'+_0xf69ae1+_0x30b0ff(0x177)+_0x46a517+_0x30b0ff(0x228)+_0x5d3664+';\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20validation\x20=\x20await\x20'+_0x46a517+_0x30b0ff(0x175)+_0x5d3664+'\x20||\x20!Array.isArray(data.'+_0x5d3664+_0x30b0ff(0x205)+_0x5d3664+_0x30b0ff(0x1ea)+_0x5d3664+_0x30b0ff(0x1a6)+_0x456843+_0x30b0ff(0x181)+_0x124b86[_0x30b0ff(0x223)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x124b86['masterDetail'][_0x30b0ff(0x298)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x124b86[_0x30b0ff(0x1f4)][_0x30b0ff(0x25a)]+'\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'+_0x46a517+'.createComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x3f2e01+_0x30b0ff(0x21a)+_0x3f2e01+_0x30b0ff(0x269)+_0x3f2e01+_0x30b0ff(0x180)+_0x3f2e01+'\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';}_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)][_0x30b0ff(0x1a9)]&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x1d5)+_0x3f2e01+'/update-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0xf69ae1+_0x30b0ff(0x1c9)+_0xf69ae1+_0x30b0ff(0x1ce)+_0xf69ae1+';\x0a\x0a\x20\x20\x20\x20if\x20(!data.'+_0xdc9a0e+_0x30b0ff(0x237)+_0xdc9a0e+')\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'+_0x2ef97b+'\x0a\x20\x20\x20\x20//\x20Validasi\x20dan\x20sanitasi\x20header\x20data\x20dengan\x20constraint\x20(termasuk\x20hash)\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x46a517+_0x30b0ff(0x228)+_0x5d3664+_0x30b0ff(0x176)+_0x46a517+_0x30b0ff(0x18b)+_0x5d3664+_0x30b0ff(0x20b)+_0x5d3664+'\x20!==\x20\x27object\x27\x20||\x20Array.isArray(data.'+_0x5d3664+_0x30b0ff(0x1f9)+_0x5d3664+'\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'+_0x124b86[_0x30b0ff(0x223)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x124b86[_0x30b0ff(0x1f4)][_0x30b0ff(0x298)]+_0x30b0ff(0x282)+_0x124b86['masterDetail']['foreignKey']+_0x30b0ff(0x1dc)+_0x46a517+_0x30b0ff(0x28a)+_0x3f2e01+'\x20composite\x20update\x20successful:\x20'+_0xdc9a0e+'=\x27\x20+\x20data.'+_0xdc9a0e+_0x30b0ff(0x28e)+_0x3f2e01+'\x20data\x20successfully\x20updated\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\x20update\x20'+_0x3f2e01+':\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'+_0x3f2e01+_0x30b0ff(0x19d)+_0x3f2e01+_0x30b0ff(0x29b)),_0x124b86[_0x30b0ff(0x1c8)]&&_0x124b86[_0x30b0ff(0x1c8)]['readComposite']&&(_0x1e3437+='//\x20POST\x20/api/'+_0x176c0a+'/'+_0x3f2e01+_0x30b0ff(0x24a)+_0x3f2e01+'/read-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\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({\x20success:\x20false,\x20error:\x20\x27Invalid\x20payload\x27,\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x20timestamp:\x20new\x20Date().toISOString()\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(!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\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22field_name\x22,\x20\x22value\x22:\x20\x22field_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\x20if\x20(!Array.isArray(req.body.where)\x20&&\x20!req.body.where.conditions)\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\x27Invalid\x20where\x20format\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20example:\x20{\x20\x22where\x22:\x20[{\x20\x22key\x22:\x20\x22'+_0xdc9a0e+'\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'+_0x46a517+'.readComposite(req.body);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0x3f2e01+'\x20composite\x20read\x20successful:\x20\x27\x20+\x20(result.count\x20||\x200)\x20+\x20\x27\x20record(s)\x27);\x0a\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\x20composite\x20read\x20'+_0x3f2e01+_0x30b0ff(0x1cd)+_0x3f2e01+_0x30b0ff(0x241));}if(_0x124b86[_0x30b0ff(0x1c8)]?.[_0x30b0ff(0x1e1)]!==![]){const _0x56aed4={'datatables':!!_0x124b86[_0x30b0ff(0x1c8)]?.[_0x30b0ff(0x1f7)],'read':!!_0x124b86[_0x30b0ff(0x1c8)]?.['read'],'first':!!_0x124b86['action']?.['first'],'create':!!_0x124b86['action']?.[_0x30b0ff(0x290)],'update':!!_0x124b86[_0x30b0ff(0x1c8)]?.['update'],'delete':!!_0x124b86[_0x30b0ff(0x1c8)]?.[_0x30b0ff(0x216)],'lookup':!!_0x124b86['action']?.['lookup'],'export':!!_0x124b86['action']?.['export'],'import':!!_0x124b86[_0x30b0ff(0x1c8)]?.[_0x30b0ff(0x285)],'info':_0x124b86['action']?.[_0x30b0ff(0x1e1)]!==![]};_0x1e3437+=_0x30b0ff(0x243)+JSON['stringify'](_0x56aed4)+_0x30b0ff(0x1d9)+_0x46a517+'.getModelInfo(actions);\x0a\x0a\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0x3f2e01+_0x30b0ff(0x257)+_0x176c0a+_0x30b0ff(0x1bb)+_0x4a8129+'\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\x20console.error(\x27MySQL\x20info\x20error:\x27,\x20error);\x0a\x20\x20\x20\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Info\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20fetching\x20endpoint\x20info\x27,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x0a});';}else _0x1e3437+=_0x30b0ff(0x2b8);return _0x1e3437+='\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'+_0x46a517+_0x30b0ff(0x2a5)+_0x3f2e01+'\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'+_0x3f2e01+_0x30b0ff(0x219),_0x1e3437+='module.exports\x20=\x20router;',_0x1e3437;}function a0_0x45be(){const _0x537900=['cGOGicaGzM9YicHJB25ZDcbOzwfKzxiGB2yGAgvHzgvYuMvZDwX0CYKGEWOGicaGicbJB25ZDcbWA1zHBhvLid0GAgvHzgvYw3rOAxmUChjPBwfYEuTLEv07cIaGicaGignVBNn0igrLDgfPBfjLC3vSDhmGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOzgv0ywLSu3fSlcbBCgTwywX1zv0PoWOGicaGicbJB21WB3nPDgvszxn1BhrZlNb1C2GOEWOGicaGicaGic4UlMHLywrLCIWkicaGicaGicbBzgv0ywLSs2v5xtOGzgv0ywLSuMvZDwX0CYb8FcbBxqOGicaGicb9ktSkicaGih0kcIaGicbYzxr1CM4GEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbJB3vUDdOGy29TCg9ZAxrLuMvZDwX0CY5Szw5NDgGScIaGicaGigrHDge6ignVBxbVC2L0zvjLC3vSDhmkicaGih07cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUihjLywrdB21WB3nPDgu6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cG','nZy5mtq4B05xy092','ieforca','iL0GpsaI','laOGigfKANvZDenVBMzPzZOG','q29UDgv4Dej1AwXKzxiGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl3v0AwXZl2nVBNrLEhqTyNvPBgrLCICPoWO','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUC3rHDhvZq29Kzsa9pt0GndaZksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmYKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDqCM8GrMvHDhvYzsbszxf1AxjLzcCScIaGicaGicaGBwvZC2fNztOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb1CgDYywrLoIaNAhr0Chm6lY9Yzxn0zM9Yz2uUzgv2l3bYAwnPBMCNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGigLMicHLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDJB25ZDhjHAw50ihzPB2XHDgLVBICPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj2jLBg93ig1PBMLTDw0NksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdKPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNq29UC3rYywLUDcb2Aw9SyxrPB24NlaOGicaGicaGig1LC3nHz2u6igvYCM9YlM1LC3nHz2uScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj25VDcbJB25MAwD1CMvKigzVCIbHzgP1C3rTzw50jYKGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNAxmGCMvXDwLYzwqGzM9YigfKANvZDcCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj211C3qGyMuGysbUB24TEMvYBYbUDw1IzxiNksb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDUB3qGysb2ywXPzcbMAwvSzcCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj211C3qGBM90igjLigvTChr5jYKPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj1zHBgLKyxrPB24GzxjYB3iNlaOGicaGicaGig1LC3nHz2u6igvYCM9YlM1LC3nHz2uScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlM1LC3nHz2uGpt09icDeyxrHihrPzgfRigrPDgvTDwTHBICGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNBM90igzVDw5KjYKPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda0ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0rHDgeGBM90igzVDw5KjYWkicaGicaGicbTzxnZywDLoIaN','zgv0ywLSvgfIBgu','jZSkicaGigXLDcbYzxn1BhqGpsbUDwXSoWOk','CMvHza','igrHDgeNlaOGicaGicbKzxrHAwXZoIbWCM9JzxnZlMvUDI5ot0rfx0vovIa9pt0Gj2rLDMvSB3bTzw50jYa/igvYCM9YlM1LC3nHz2uGoIb1BMrLzMLUzwqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9cN0PoWOk','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicbJB25ZDcb2ywXPzgf0Aw9Uid0GyxDHAxqG','lNzHBgLKyxrLrgf0ysHYzxeUyM9KEsWGj2LUC2vYDcCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbYzxeUyM9KEsa9ihSGlI4UCMvXlMjVzhKSic4UlNzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysb9oWOGicaGFqOk','oWOGihrOAxmUAgfZrxHWB3j0uxvLCNKGpsa','zMLLBgrmywjLBhm','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YihvWzgf0zwaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cG','CMvWBgfJzq','id0GjW','yMniwgm','Bg9VA3vW','lMDLDenVBM5Ly3rPB25jBMzVkcK7cGOGicaGCMvZlMPZB24OEWOGicaGicbZDgf0Dxm6ignVBM5Ly3rPB25jBMzVid8Gj2HLywX0AhKNidOGj3vUA25VD24NlaOGicaGicbLBMrWB2LUDdOGjW','C3rYAw5NAwz5','lNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSkicaGicaGicb2yxiGCxr5id0GtNvTyMvYkgL0zw0U','ie1VzgvSic0GtxLtuuWGrgf0ywjHC2ukkIbhzw5LCMf0zwq6ia','ihX8icCNcIaGicb9ksK7cGOGicaGlY8Gq2fJAguGDgHLihjLC3vSDaOGicaGyxDHAxqGDgHPCY5ZzxrdywnOzwrmB29RDxaOy2fJAgvpChrPB25ZlcbYzxn1BhqSicDMAwX0zxiNktSkcIaGicbYzxr1CM4GCMvZDwX0oWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbPBIbNzxrmB29RDxbeyxrHv2L0AezPBhrLCJONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0kcI8QkGOGkIbcDwLSzcbHzhzHBMnLzcbMAwX0zxiGy29UzgL0Aw9UCYb1BNr1AYbnEvnrtaOGkIbaCgfYyw0GE0fYCMf5FsbMAwX0zxjZic0GqxjYyxKGB2yGE2nVBhvTBIWGDhLWzsWGDMfSDwuSihzHBhvLmN0kicOGqhjLDhvYBNmGE09IAMvJDh0GE3nXBcWGCgfYyw1ZFqOGkI8kyNvPBgrbzhzHBMnLzezPBhrLCKnVBMrPDgLVBIHMAwX0zxjZksb7cIaGAwyGkcfMAwX0zxjZihX8icfbCNjHEs5PC0fYCMf5kgzPBhrLCNmPihX8igzPBhrLCNmUBgvUz3rOid09psaWksb7cIaGicbYzxr1CM4GEYbZCwW6icCNlcbWyxjHBxm6ifTDih07cIaGFqOkicbJB25ZDcbJB25KAxrPB25Zid0Gw107cIaGy29UC3qGCgfYyw1Zid0Gw107cGOGigzVCIaOy29UC3qGzMLSDgvYig9MigzPBhrLCNmPihSkicaGignVBNn0ihSGy29SDw1Ulcb0ExbLlcb2ywX1zsWGDMfSDwuYih0GpsbMAwX0zxi7cGOGicaGAwyGkcfJB2X1Bw4GFhWGixrOAxmUDMfSAwrgAwvSzhmUAw5JBhvKzxmOy29SDw1UksKGy29UDgLUDwu7cGOGicaGC3DPDgnOicH0ExbLksb7cIaGicaGignHC2uGj2vXDwfSCYC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpsa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDUB3rFzxf1ywXZjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsa8pIa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDJB250ywLUCYC6cIaGicaGignHC2uGj2XPA2uNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieXjs0uGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkgaLjhT2ywX1zx0LycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj25VDf9JB250ywLUCYC6cIaGicaGignHC2uGj25VDf9SAwTLjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbot1qGteLlrsa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GOycuKE3zHBhvLFsvGktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNC3rHCNrZx3DPDgGNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieXjs0uGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkgaKE3zHBhvLFsvGktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNzw5KC193AxrOjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbmsuTfid9GktSkicaGicaGicbWyxjHBxmUChvZAcHGjsr7DMfSDwv9ycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2DYzwf0zxjFDgHHBIC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GpIa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDSzxnZx3rOyw4NoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59idWGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNz3jLyxrLCL9LCxvHBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GpJ0Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNBgvZC19LCxvHBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpd0Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNyMv0D2vLBIC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GqKvuv0vftIa/ieforca/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuSihzHBhvLmIK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2LUjZOkicaGicaGicbPzIaOqxjYyxKUAxnbCNjHEsH2ywX1zsKPihSkicaGicaGicaGignVBNn0igLUugXHy2vOB2XKzxjZid0GDMfSDwuUBwfWkcGPid0+icC/jYKUAM9PBIGNlcaNktSkicaGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieLoicGKE2LUugXHy2vOB2XKzxjZFsLGktSkicaGicaGicaGihbHCMfTCY5WDxnOkc4UlNzHBhvLktSkicaGicaGicb9cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj25VDf9PBIC6cIaGicaGicaGAwyGkefYCMf5lMLZqxjYyxKODMfSDwuPksb7cIaGicaGicaGicbJB25ZDcbUB3rjBLbSywnLAg9SzgvYCYa9ihzHBhvLlM1HCcGOksa9pIaNpYCPlMPVAw4OjYWGjYK7cIaGicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbot1qGsu4Gkcr7BM90sw5qBgfJzwHVBgrLCNn9kwaPoWOGicaGicaGicaGCgfYyw1ZlNb1C2GOlI4UDMfSDwuPoWOGicaGicaGih0kicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNAxnFBNvSBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GsvmGtLvmtgaPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDPC19UB3rFBNvSBcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnO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CgrHDgukicaGicaGicaGicaGFqOGicaGicaGicaGicbLBhnLihSkicaGicaGicaGicaGicbJB25ZDcbTzxnZywDLid0Gy29UC3rYywLUDhmUCMvXDwLYzwrnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbPCYbYzxf1AxjLzga7cIaGicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkg1LC3nHz2uPoWOGicaGicaGicaGicaGihjLC3vSDc5PC1zHBgLKid0GzMfSC2u7cIaGicaGicaGicaGih0kicaGicaGicaGih0kicaGicaGicaGignVBNrPBNvLoWOGicaGicaGih0kcIaGicaGicaGlY8Gu3rYAw5NigzPzwXKoIbOyxnOignVBNn0CMfPBNqGC3vWCg9YDaOGicaGicaGigLMicHJB25MAwCUDhLWzsa9pt0Gj3n0CMLUzYCGjIyGDhLWzw9MihzHBhvLid09psaNC3rYAw5NjYKGEWOGicaGicaGicaGBgv0ihnHBML0AxPLzca9ihzHBhvLoWOGicaGicaGicaGy29UC3qGzMLLBgrfCNjVCNmGpsbBxtSkicaGicaGicaGignVBNn0igLZsgfZAezPzwXKid0Gy29UC3rYywLUDhmUAgfZAca9pt0Gj2jJCNLWDcC7cGOGicaGicaGicaGlY8GvhjPBqOGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlNrYAw0PihSkicaGicaGicaGicaGC2fUAxrPEMvKid0GC2fUAxrPEMvKlNrYAw0OktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbdyxnLihrYyw5ZzM9YBwf0Aw9UicHZA2LWigPPA2eGAgfZAcbMAwvSzcKkicaGicaGicaGigLMicGHAxniyxnOrMLLBgqPihSkicaGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlMXVD2vYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B0XVD2vYq2fZzsGPoWOGicaGicaGicaGicb9igvSC2uGAwyGkgnVBNn0CMfPBNrZlNvWCgvYy2fZzsKGEWOGicaGicaGicaGicaGihnHBML0AxPLzca9ihnHBML0AxPLzc50B1vWCgvYq2fZzsGPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GtgvUz3rOihzHBgLKyxrPB24GkhzHBgLKyxnPihbSywLUDgv4DcbZzwjLBhvTigHHC2GPcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwLUtgvUz3rOicyMihnHBML0AxPLzc5Szw5NDgGGpcbJB25ZDhjHAw50CY5TAw5mzw5NDgGPihSkicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5TAw5mzw5NDgHnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbTDxn0igjLigf0igXLyxn0icr7y29UC3rYywLUDhmUBwLUtgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBwf4tgvUz3rOicyMicfPC0HHC2HgAwvSzcaMjIbZyw5PDgL6zwqUBgvUz3rOid4Gy29UC3rYywLUDhmUBwf4tgvUz3rOksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUBwf4tgvUz3rOtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGBxvZDcbUB3qGzxHJzwvKicr7y29UC3rYywLUDhmUBwf4tgvUz3rOFsbJAgfYywn0zxjZycK7cIaGicaGicaGicb9cGOGicaGicaGicaGlY8Gugf0DgvYBIb2ywXPzgf0Aw9UcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUCgf0DgvYBIKGEWOGicaGicaGicaGicbJB25ZDcbYzwDLEca9ig5LDYbszwDfEhaOy29UC3rYywLUDhmUCgf0DgvYBIK7cIaGicaGicaGicaGigLMicGHCMvNzxGUDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5Wyxr0zxjUtwvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGzg9LCYbUB3qGBwf0y2GGCMvXDwLYzwqGCgf0DgvYBMaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9cGOGicaGicaGicaGlY8GrM9YBwf0ihzHBgLKyxrPB24kicaGicaGicaGigLMicHJB25ZDhjHAw50CY5MB3jTyxqGpt09icDLBwfPBcCGjIyGis9Ew15CC0bDk0bBxLXZqf0Rxc5BxLXZqf0Rjc8UDgvZDcHZyw5PDgL6zwqPksb7cIaGicaGicaGicaGigzPzwXKrxjYB3jZlNb1C2GOy29UC3rYywLUDhmUzM9YBwf0twvZC2fNzsb8FcbGrMLLBgqGjYr7zMLLBgroyw1LFsCGAgfZigLUDMfSAwqGzw1HAwWGzM9YBwf0ycK7cIaGicaGicaGicb9cGOGicaGicaGicaGAwyGkgzPzwXKrxjYB3jZlMXLBMD0Aca+idaPihSkicaGicaGicaGicaGCMvZDwX0lMLZvMfSAwqGpsbMywXZztSkicaGicaGicaGicaGCMvZDwX0lMvYCM9YCY5WDxnOkc4UlMzPzwXKrxjYB3jZktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbiyxnOihrYyw5ZzM9YBwf0Aw9UicHZzxrLBgfOihnLBxvHihzHBgLKyxrPB24GCgfZCYKkicaGicaGicaGigLMicHPC0HHC2HgAwvSzcaMjIbMAwvSzevYCM9YCY5Szw5NDgGGpt09idaPihSkicaGicaGicaGicaGy29UC3qGyMnYExb0id0GCMvXDwLYzsGNyMnYExb0jYK7cIaGicaGicaGicaGignVBNn0ignVC3qGpsbJB25ZDhjHAw50CY5OyxnOq29ZDcb8FcaXmdSkicaGicaGicaGicaGC2fUAxrPEMvKid0GyxDHAxqGyMnYExb0lMHHC2GOC2fUAxrPEMvKlcbJB3n0ktSkicaGicaGicaGih0kcIaGicaGicaGicbYzxn1BhqUC2fUAxrPEMvKrgf0yvTMAwvSze5HBwvDid0GC2fUAxrPEMvKoWOGicaGicaGih0GzwXZzsb7cIaGicaGicaGicaVlYboB24TC3rYAw5NigzPzwXKoIbIyxnPyYbZyw5PDgL6yxrPB24kicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKtMfTzv0Gpsb2ywX1ztSkicaGicaGicb9cIaGicaGih0kcIaGicaGic8VifzHBgLKyxrLigzPzwXKihLHBMCGDgLK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','BwfW','oWOGigLMicHZy29WzvzHBhvLid09psb1BMrLzMLUzwqGFhWGC2nVCgvwywX1zsa9pt0GBNvSBcKGEWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaZks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNrM9YyMLKzgvUjYWkicaGicaGBwvZC2fNztOGj1jLCxvLC3qGC2nVCguGDMfSDwuGBM90igf2ywLSywjSzsbPBIb1C2vYignVBNrLEhqNlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqOkicaVlYbfEhbVC2uGC2nVCguGAw5MBYbMB3iGCM91DguGAgfUzgXLCNmGkg93BMvYC2HPCcb2zxjPzMLJyxrPB24GAw4Gl2zPCNn0lcaVDxbKyxrLlcaVDxbKyxrLlwnVBxbVC2L0zsKkicbYzxeUx3jLCxvLC3rty29Wzsa9ihSGy29SDw1UoIaN','lMfNz3jLz2f0zurHDgeOCMvXlMjVzhKGFhWGE30SigfNz3jLz2f0zunVBMzPzYK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGzgf0ytOGCMvZDwX0laOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUz2fNCMvNyxnPigrHDgeG','id09psbZzwXLy3rLzfrHzYKGEWOGicaGicaGihjVDY5ZzwXLy3rLzca9icD0CNvLjZSkicaGicaGFqOGicaGicbYzxr1CM4GCM93oWOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUie15u1fmigDLDfn0yxrPy0XVB2T1CerHDge6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cGOVkIOkicOGtg9VA3vWigrLBMDHBIbHzhzHBMnLzcbMAwX0zxiGC3vWCg9YDcb1BNr1AYbnEvnrtaOGkI8kyxn5BMmGz2v0tg9VA3vWrgf0yvDPDgHgAwX0zxiOB3b0Aw9UCYKGEWOGihrYEsb7cIaGicaVlYbdAgvJAYbJywnOzsbMAxjZDaOGicaGy29UC3qGy2fJAgvpChrPB25Zid0GEYaUlI5VChrPB25Zlcb0ExbLoIaNzMLSDgvYjYb9oWOGicaGy29UC3qGy2fJAgvKuMvZDwX0id0GyxDHAxqGDgHPCY5NzxrdywnOzwrmB29RDxaOy2fJAgvpChrPB25ZlcaNzMLSDgvYjYK7cIaGicbPzIaOy2fJAgvKuMvZDwX0ksbYzxr1CM4Gy2fJAgvKuMvZDwX0oWOkicaGignVBNn0ihnLBgvJDenVBhvTBNmGpsbVChrPB25ZlNnLBgvJDcb8FcbBjW','jYWkicaGicaGicb1ChrPBwu6ihbYB2nLC3mUDxb0Aw1LkcKScIaGicaGicaGBwvTB3j5oIbWCM9JzxnZlM1LBw9YEvvZywDLkcKScIaGicaGicaGC3LZDgvToIb7cIaGicaGicaGicbWBgf0zM9YBtOGChjVy2vZCY5WBgf0zM9YBsWkicaGicaGicaGig5VzgvwzxjZAw9UoIbWCM9JzxnZlNzLCNnPB24ScIaGicaGicaGicbWAwq6ihbYB2nLC3mUCgLKcIaGicaGicaGFqOGicaGicb9oWOkicaGicaGCMvZlMPZB24OAgvHBhrOsw5MBYK7cIaGicb9ktSkcIaGicaVlYbnDwf0ihnLBxvHihj1DguGzgfYAsbMB2XKzxiG','Bxrmt0m','yxv0B0nHBgn1Bgf0zuzPzwXKCW','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icaGicbku09olNbHCNnLkgj1zIK7cIaGicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicaGihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGicaGigvYCM9YoIaNsw52ywXPzcbku09oihbHEwXVywqNlaOGicaGicaGicaGicbTzxnZywDLoIaNvgHLihbHEwXVywqGC2vUDcbPCYbUB3qGysb2ywXPzcbku09oigzVCM1HDcCScIaGicaGicaGicaGigrLDgfPBhm6igvYCM9YlM1LC3nHz2ukicaGicaGicaGih0PoWOGicaGicaGicaGDgHYB3CGBMv3ievYCM9YkcDjBNzHBgLKiePtt04NktSkicaGicaGicb9cIaGicaGih0kicaGih0PktSkcIaGicbHChaUDxnLkgjVzhLqyxjZzxiUDxjSzw5JB2rLzcH7igv4DgvUzgvKoIb0CNvLih0PktSkcIaGicaVlYbszxf1zxn0igXVz2DPBMCGBwLKzgXLD2fYzsaOugLUBY1IyxnLzcKkicaGigfWCc51C2uOkhjLCsWGCMvZlcbUzxH0ksa9pIb7cIaGicaGihjLCs5Pzca9ihjLCs5OzwfKzxjZwYD4lxjLCxvLC3qTAwqNxsb8Fcb1DwLKDJqOktSkicaGicaGCMvZlNnLDcGNwc1szxf1zxn0luLejYWGCMvXlMLKktSkcIaGicaGihjLCs5SB2CGpsbJCMvHDgvszxf1zxn0tg9Nz2vYkhSkicaGicaGicbYzxf1zxn0swq6ihjLCs5PzcWkicaGicaGicbTzxrOB2q6ihjLCs5TzxrOB2qScIaGicaGicaGCgf0AdOGCMvXlNbHDgGScIaGicaGicaGAxa6ihjLCs5PCaOGicaGicb9ktSkcIaGicaGignVBNn0ihn0yxj0vgLTzsa9ihbYB2nLC3mUAhj0Aw1LkcK7cGOGicaGicbYzxmUB24Oj2zPBMLZAcCSicGPid0+ihSkicaGicaGicbJB25ZDcbBC2vJB25KCYWGBMfUB3nLy29UzhnDid0GChjVy2vZCY5OCNrPBwuOC3rHCNruAw1LktSkicaGicaGicbJB25ZDcbKDxjHDgLVBK1Zid0GCgfYC2vgBg9HDcGOC2vJB25KCYaQideWmdaGkYbUyw5VC2vJB25KCYaVidfLnIKUDg9gAxHLzcGYksK7cIaGicaGicaGBg9NuMvXDwvZDcHYzxeSihjLCYWGzhvYyxrPB25nCYK7cIaGicaGih0PoWOkicaGicaGBMv4DcGPoWOGicaGFsK7cGOGicaGlY8GtwLKzgXLD2fYzsb1BNr1AYb2ywXPzgfZAsbbueKGA2v5igPPA2eGzgLWzxjSDwTHBIaOy29UC3rHBNqTDgLTzsbJB21WyxjPC29UkqOGicaGAwyGkgnVBMzPzY5RzxKPihSkicaGicaGy29UC3qGy3j5ChrVid0GCMvXDwLYzsGNy3j5ChrVjYK7cIaGicaGignVBNn0igv4CgvJDgvKs2v5id0GqNvMzMvYlMzYB20Oy29UzMLNlMTLEsK7cGOGicaGicbHChaUDxnLkcHYzxeSihjLCYWGBMv4DcKGpt4GEWOGicaGicaGignVBNn0igfWAuTLEsa9ihjLCs5OzwfKzxjZwYD4lwfWAs1RzxKNxtSkicaGicaGicbPzIaOiwfWAuTLEsKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGignVBNn0ihbYB3zPzgvKs2v5id0GqNvMzMvYlMzYB20OyxbPs2v5ktSkicaGicaGicbPzIaOzxHWzwn0zwrlzxKUBgvUz3rOice9psbWCM92AwrLzeTLEs5Szw5NDgGGFhWkicaGicaGicaGicaGiwnYExb0BY50Aw1PBMDtywzLrxf1ywWOzxHWzwn0zwrlzxKSihbYB3zPzgvKs2v5ksKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGig5LEhqOktSkicaGicaGFsK7cIaGicb9cGOGicaGlY8GuMf0zsbSAw1PDgLUzYbTAwrKBgv3yxjLicHZDg9YztOGBwvTB3j5ihvUDhvRihnPBMDSzsbTB2rLlcbszwrPCYb1BNr1AYbJBhvZDgvYig1VzguPcIaGicbYyxrLtgLTAxrLCI5ZzxrtDg9YzsHJB25MAwCUy2X1C3rLCIa/icDYzwrPCYCGoIaNBwvTB3j5jYK7cIaGicbHChaUDxnLkcCVyxbPjYWGCMf0zuXPBwL0zxiUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbjzgvTCg90zw5JEsbTAwrKBgv3yxjLicHWCM90zwn0CYbTDxrHDgLVBIbLBMrWB2LUDhmGzNjVBsbKDxbSAwnHDguGzxHLy3v0Aw9UkqOGicaGAwyGkhbYB2nLC3mUzw52lKLeru1qt1rftKnzx0voqujmruqGpt09icD0CNvLjYKGEWOGicaGicbHChaUDxnLkcCVyxbPjYWGAwrLBxbVDgvUy3LnAwrKBgv3yxjLlM1PzgrSzxDHCMuOksK7cIaGicb9cGOGicaGlY8GqM9KEsbVChrPB25Zig1PzgrSzxDHCMuGkgv4DhjHy3qGE2rHDgeSig9WDgLVBNn9igzVCM1HDcbKyxjPihjLCxvLC3qGyM9KEsKkicaGigfWCc51C2uOjY9HCgKNlcbIB2r5t3b0Aw9UC01PzgrSzxDHCMuUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbbDxrVlwXVywqGCgX1z2LUicHQAwTHigfKysKkicaGignVBNn0ig1VzhvSzu5HBwuGpsaN','zMLLBgrqB2XPy3K','AM9PBG','lY8Grw5KCg9PBNqGl2LUzM8GzgLUB25HA3rPzMTHBIb2AweGywn0Aw9UlMLUzM8GpsbMywXZzqO','AwyGkgHLywrLCKnHBgmUDg90ywXFyw1VDw50ksb7cIaGicaGigrHDgeUDg90ywXFyw1VDw50id0Gzgf0ys4','oWOGicaGy29UC3qGCMvJywXJrMLLBgrZid0Gw107cIaGicbJB25ZDcbYzwnHBgnwywX1zxmGpsbBxtSkcIaGicbPzIaOy2fSy3vSyxrPB25ZlNrVDgfSx2L0zw1Zksb7cIaGicaGihjLy2fSy0zPzwXKCY5WDxnOkcD0B3rHBf9PDgvTCYa9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GOywXSsxrLBxmUBgvUz3rOktSkicaGih0kicaGigLMicHJywXJDwXHDgLVBNmUDg90ywXFCxr5icyMignHBgn1Bgf0Aw9UCY50B3rHBf9XDhKUC291CMnLksb7cIaGicaGignVBNn0ihf0EuzPzwXKid0Gy2fSy3vSyxrPB25ZlNrVDgfSx3f0Es5ZB3vYy2uUCMvWBgfJzsGNAxrLBxmUjYWGjYCPoWOGicaGicbJB25ZDcb0B3rHBff0Esa9igfSBeL0zw1ZlNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSGCMv0DxjUihn1BsaRicHoDw1IzxiOAxrLBvTXDhLgAwvSzf0PihX8idaPoYb9lcaWktSkicaGicaGCMvJywXJrMLLBgrZlNb1C2GOj3rVDgfSx3f0Esa9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GODg90ywXrDhKPoWOGicaGFqOGicaG','cIaGicaGih07cIaGicaGigLMicHPDgvTlG','id0GCMvXDwLYzsGNlI4VlI4VBw9KzwXZlW','ieXjtuLuideWmdbGoWOGicaGy29UC3qGzgf0ysa9igf3ywL0igrIlMv4zwn1DgvrDwvYEsHXDwvYEsK7cGOGicaGlY8Gq2fJAguGCMvZDwX0ihrHBNbHihnLBgvJDgvKigzSywCkicaGignVBNn0ignHy2HLrgf0ysa9igrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','icaGicaGlY8Gqxv0BY1JywXJDwXHDguG','jYbMCM9Tia','zgf0yxrHyMXLC1DOzxjL','DxbSB2fKq29UzMLN','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHPDgvTksKGEWOGicaGicaGigLMicHRzxKGpt09igrLDgfPBfbRksbJB250Aw51ztSkicaGicaGica','lMrLBgv0zurHDgeOCMvXlMjVzhKSigv2zw50q29UDgv4DcK7cIaGicaGignVBNnVBguUBg9NkcDBsu5uruDsqvrfrcbuuKfou0fdveLptL0GrevmrvrfignVBxbSzxrLzcbZDwnJzxnZzNvSBhKGD2L0AcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TjtLrfr1jbveveifrsqu5tqunusu9oxsberuXfveuGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','ihn0yxr1CYbJAgfUz2vKoIaKE3jLC3vSDc53B3jRzMXVDY5WCMv2Aw91C1n0yxr1C30GW6lIGkdIGjKGjhTYzxn1BhqUD29YA2zSB3CUBMv3u3rHDhvZFwaPoWOkicaGihjLDhvYBIbYzxmUC3rHDhvZkdiWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGig1LC3nHz2u6igbtDgf0DxmGy2HHBMDLzcbMCM9Ticr7CMvZDwX0lNDVCMTMBg93lNbYzxzPB3vZu3rHDhvZFsb0BYaKE3jLC3vSDc53B3jRzMXVDY5UzxDtDgf0Dxn9ycWkicaGicaGzgf0ytOGCMvZDwX0lMrHDgeScIaGicaGihDVCMTMBg93oIbYzxn1BhqUD29YA2zSB3CScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbJAgfUz2uTC3rHDhvZia','zxHWB3j0','cIaGicaGih0','ntqXmte0ofDXyxDmuG','CMvXDwvZDfnJB3bL','BNvSBa','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','cIaG','mtiYnty1nxrirvDyuq','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0LovevhuKfuruqGvfjbtLnbq1rjt05Dieniqu5hrs1tvefuvvmGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05Dieniqu5hrs1tvefuvvmGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','y29UC3qGqMfZzu1VzgvSid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY9TB2rLBhmVyMfZzs1TB2rLBc1TExnXBcCPoWPJB25ZDcbKyIa9ihjLCxvPCMuOj0bYzxn0zM9Yz2vQCY9WBgf0zM9YBs9ZCMmVDxrPBhmVzgiTBxLZCwWNktSky29UC3qGzNmGpsbYzxf1AxjLkcDMCYCPoWPJB25ZDcbWyxrOid0GCMvXDwLYzsGNCgf0AcCPoWOklYOQcIOG','jYK7cG','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUzxjYBM8Gpt09ideWnJiPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cIaGicbPzIaOzxjYB3iUzxjYBM8Gpt09ide0ntiPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0zVCMvPz24GA2v5ignVBNn0CMfPBNqNlaOGicaGicaGig1LC3nHz2u6icDszwzLCMvUy2vKigrHDgeGBM90igzVDw5KjYWkicaGicaGicbKzxrHAwXZoIbWCM9JzxnZlMvUDI5ot0rfx0vovIa9pt0Gj2rLDMvSB3bTzw50jYa/igvYCM9YlM1LC3nHz2uGoIb1BMrLzMLUzwqScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLignYzwf0Aw5Nia','cGOGicaGlY8GqNvPBgqGzxzLBNrdB250zxH0ihvUDhvRignVBxbVC2L0zsbOB29RCWOGicaGDMfYigv2zw50q29UDgv4Dca9ig51BgW7cIaGicbPzIaOy29TCg9Uzw50rw5NAw5LicyMienVBNrLEhrcDwLSzgvYksb7cIaGicaGigv2zw50q29UDgv4Dca9ihSkicaGicaGicbJB21WB25LBNrfBMDPBMu6ignVBxbVBMvUDevUz2LUzsWkicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGDgfIBgvoyw1LoIaN','jYK7cGOGicaGDhj5ihSkicaGicaGAwyGkcfMCY5LEgLZDhntEw5Jkg1VzhvSzxneAxiPksb7cIaGicaGicaGzNmUBwTKAxjtEw5Jkg1VzhvSzxneAxiSihSGCMvJDxjZAxzLoIb0CNvLih0PoWOGicaGicaGignVBNnVBguUBg9NkgbeAxjLy3rVCNKGjhTTB2r1BgvZrgLYFsbJCMvHDgvKihn1y2nLC3nMDwXSEwaPoWOGicaGicb9cGOGicaGicbJB25ZDcbMAwXLCYa9igzZlNjLywrKAxjtEw5Jkg1VzhvSzxneAxiPoWOGicaGicbJB25ZDcbLBMrWB2LUDezPBgvZid0GzMLSzxmUzMLSDgvYkgzPBguGpt4GzMLSzs5LBMrZv2L0AcGNlMPZjYKPoWOkicaGicaGAwyGkgvUzhbVAw50rMLSzxmUBgvUz3rOid09psaWksb7cIaGicaGicaGy29UC29Szs5SB2COye5VigvUzhbVAw50igzPBgvZigzVDw5KigLUicr7Bw9KDwXLC0rPCN1GktSkicaGicaGicbJB25ZB2XLlMXVzYHGqwrKigvUzhbVAw50igzPBgvZihrVigvUywjSzsbbueKGzNvUy3rPB25HBgL0EwaPoWOGicaGicb9igvSC2uGEWOGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icDLBMrWB2LUDhnFBg9HzgLUzYCSignVDw50oIbLBMrWB2LUDezPBgvZlMXLBMD0Acb9lcbGtg9HzgLUzYaKE2vUzhbVAw50rMLSzxmUBgvUz3rOFsbLBMrWB2LUDcHZkwaPoWOGicaGicb9cGOGicaGicbMB3iGkgnVBNn0igzPBguGB2yGzw5KCg9PBNrgAwXLCYKGEWOGicaGicaGihrYEsb7cIaGicaGicaGicbJB25ZDcbLBMrWB2LUDe5HBwuGpsbWyxrOlMjHC2vUyw1LkgzPBguSicCUANmNktSkicaGicaGicaGignVBNn0igvUzhbVAw50ugf0Aca9ihbHDgGUAM9PBIHTB2r1BgvZrgLYlcbMAwXLktSkcIaGicaGicaGicaVlYbdBgvHCIbTB2r1BguGy2fJAguGDw50DwSGzgv2zwXVCg1LBNqkicaGicaGicaGigLMicHYzxf1AxjLlMnHy2HLw2vUzhbVAw50ugf0Af0PihSkicaGicaGicaGicaGzgvSzxrLihjLCxvPCMuUy2fJAgvBzw5KCg9PBNrqyxrOxtSkicaGicaGicaGih0kcIaGicaGicaGicbJB25ZDcbTB2r1BgvsB3v0zxmGpsbYzxf1AxjLkgvUzhbVAw50ugf0AcK7cGOGicaGicaGicaGy29UC3qGzw5KCg9PBNrqCMvMAxGGpsbGl2fWAs8','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGDxnLCL9PzdOGCMvXlMHLywrLCNnBj3vZzxiTAwqNxsb8FcbYzxeUAgvHzgvYC1SNEc11C2vYlwLKj10GFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30kicaGicaGicb9cIaGicaGih07cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','D0rZCLy','u29Rt2C','lY8Gq3vZDg9Tigf1zgL0ignVBhvTBNmGBwfWCgLUzYbKyxjPihbHEwXVywqk','igXVywrLzcbZDwnJzxnZzNvSBhKGzNjVBsbnEvnrtcbMAwXLycK7cIaGicaGih0GzwXZzsb7cIaGicaGicaGy29UC29Szs5LCNjVCIHGu1fmifrLBxbSyxrLigzPBguG','ih07cMnVBxbVBMvUDevUz2LUzs5SB2fKq29UzMLNDxjHDgLVBKzYB21pyMPLy3qOx2nVBxbVBMvUDfbHEwXVywqPlNrOzw4OCMvZDwX0id0+ihSkicbPzIaOCMvZDwX0lNn1y2nLC3mPihSkicaGignVBNnVBguUBg9NkgbdB21WB25LBNqGy29UzMLNDxjHDgLVBIbSB2fKzwqGzM9Yia','AwyGkgnHBgn1Bgf0Aw9UCY50B3rHBf9HBw91BNqPihSkicaGicaGy29UC3qGDg90ywXbBw91BNqGpsbHBgXjDgvTCY5Yzwr1y2uOzNvUy3rPB24OC3vTlcbPDgvTksb7cIaGicaGicaGDMfYihf0Esa9ie51BwjLCIHPDgvTlG','jZSkicb0AgLZlNjLywrtB3vYy2uGpsaN','lNzHBgLKyxrLrgf0ysHOzwfKzxjeyxrHrM9YvMfSAwrHDgLVBIWGj3vWzgf0zsCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbpyMPLy3qUyxnZAwDUkgrHDgeSihzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysK7cIaGicb9cGOGicaGAwyGkgrHDgeU','iI5YzxbSywnLkcDMAwXLoICSicCNktSkicaGicaGy29UC3qGzMLSzvbHDgGGpsbWyxrOlMPVAw4Ox19KAxjUyw1LlcbYzwXHDgL2zvbHDgGPoWOkicaGicaGAwyGkgzZlMv4Axn0C1n5BMmOzMLSzvbHDgGPksb7cIaGicaGicaGDgvTCgXHDgvZwYi','Cw1rCxm','C3bSAxq','zM9YBxvSyq','sgzesMq','laOGihjLCxvLC3rty29WztOG','cIaGlY8GrMLSzsb1CgXVywqGzMLLBgrZicHku09oignVBhvTBNmPcIaGDgHPCY5MAwXLrMLLBgrZid0G','cIaGicb9ksK7cGOGicaGCMv0DxjUihjLC3vSDdSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGAw4GtxLtuuWGz2v0tg9VA3vWrgf0ytONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0kcI8QkGOGkIbeEw5HBwLJigXVB2T1CcbKzw5Nyw4GzxH0CMeGzMLSDgvYCYb1BNr1AYbnEvnrtaOGkI8kyxn5BMmGz2v0tg9VA3vWrgf0yur5BMfTAwmOC2vHCMnOlcbLEhrYyuzPBhrLCNmGpsb7FsKGEWOGihrYEsb7cIaGicbSzxqGCgfYyw1Zid0Gw107cIaGicbSzxqGD2HLCMvdB25KAxrPB25Zid0Gw107cG','zgvMyxvSDfnJB3bL','ywrQDxn0','seX6sKC','A2v5CW','ifDirvjfia','sw52ywXPzcbHDwrPDenVBhvTBNmGDMfSDwuGzM9Yia','Aw1WB3j0q29UzMLN','v056rvu','l2nYzwf0zsaTie15u1fmieLUC2vYDaPYB3v0zxiUCg9ZDcGNl2nYzwf0zsCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicbPzIaOixjLCs5IB2r5ihX8ie9IAMvJDc5RzxLZkhjLCs5IB2r5ks5Szw5NDgGGpt09idaPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCScIaGicaGicaGBwvZC2fNztOGj1bHEwXVywqGy2fUBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbwywXPzgfZAsbKyxrHcIaGicbPzIaODhLWzw9Mia','igrHDgeGBM90igzVDw5KjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kicaGigLMicHLCNjVCI5LCNjUBYa9pt0Gmta2mIKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdKPlMPZB24OEYbZDwnJzxnZoIbMywXZzsWGzxjYB3i6icDeDxbSAwnHDguGzw50CNKNlcbTzxnZywDLoIaNqsbYzwnVCMqGD2L0Acb0AgLZihzHBhvLigfSCMvHzhKGzxHPC3rZjYWGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKGFsK7cIaGicb9cIaGicbPzIaOzxjYB3iUzxjYBM8Gpt09ide0ntiPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSGC3vJy2vZCZOGzMfSC2uSigvYCM9YoIaNrM9YzwLNBIbRzxKGy29UC3rYywLUDcCSig1LC3nHz2u6icDszwzLCMvUy2vKigrHDgeGBM90igzVDw5KjYWGzgv0ywLSCZOGChjVy2vZCY5LBNyUtK9erv9ftLyGpt09icDKzxzLBg9WBwvUDcCGpYbLCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlcb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOksb9ktSkicaGih0kcIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGDxbKyxrPBMCG','DxbKyxrLzej5','u2TeD0O','AwyGkgDLBMvYyxrLzezPzwXKCY5PBMnSDwrLCYHRzxKPksbJB250Aw51ztS','Dg90ywXFCxr5','u0fQyw8','laOGicaGicaGihrLEhq6igL0zw0U','laOGicaGicb0zxH0oIbPDgvTlG','lNvWzgf0zurHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0','iIbTDxn0igjLigeGBM9UlwvTChr5igfYCMf5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGica','nty2ndiWohr5rvnSra','ANvKDwW','DxbKyxrLq29TCg9ZAxrL','cIaGicb0CNKGEWOGicaGicb2yxiGCMvZDwX0id0GyxDHAxqG','ywzKEuK','rgrAAuu','l2fNz3jLz2f0zsaTie15u1fmiefNz3jLz2f0zsaOy291BNqSihn1BsWGyxzNlcbTAw4Sig1HEcKkCM91DgvYlNbVC3qOjY9Hz2DYzwDHDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC3qGywDNCMvNyxrLq29UzMLNid0G','DxbKyxrLx2v4Axn0Aw5N','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUBwvZC2fNzsa9pt0Gj0rHDgeGDgLKywSGzgL0zw11A2fUjYb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDUB3qGzM91BMqNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdqPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGig1LC3nHz2u6icC','jZSkicaGia','lY8Gv0fstKLorZOGAgvHzgvYq2fSy3vSyxrPB25ZlNrVDgfSx2fTB3vUDcbZA2LWCgvKimoI4OkS4OcDig5Vihf0EsbMAwvSzcbJB25MAwD1CMvK','iJSkicaGih0kicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIHGrxjYB3iGBg9HzgLUzYbnEvnrtcb0zw1WBgf0zsa','zw50CMLLCW','yKL3yK8','yM9VBgvHBG','cGOGicaGAwyGkhjLy2fSy0zPzwXKCY5Szw5NDgGGpIaWksb7cIaGicaGic8VieLUAMvJDcbHDwrPDcbJB2X1Bw5ZigTLihjLy2fSy3vSyxrPB24GvvbeqvrfihzPysbOzwXWzxikicaGicaGDgHPCY5FyxbWzw5KvxbKyxrLqxvKAxrdB2X1Bw5ZkhjLy2fSy0zPzwXKCYWGCMvJywXJvMfSDwvZlcbKyxrHlcbLDMvUDenVBNrLEhqPoWOGicaGicbYzwnHBgnwywX1zxmUChvZAcHWCMLTyxj5s2v5vMfSDwuPoWOGicaGicb2yxiGCMvJywXJu3fSid0Gj1vqrefursaNicSGDgHPCY53CML0zvnVDxjJzsaRicCGu0vuicCGkYbYzwnHBgngAwvSzhmUAM9PBIGNlcaNksaRicCGv0HfuKuGjYaRihrOAxmUChjPBwfYEuTLEsaRicCGpsa/jZSkicaGicaGy29UC29Szs5SB2COj1jLy2fSy3vSyxrPBMCGAgvHzgvYihrVDgfSCZONlcb7ihf1zxj5oIbYzwnHBgntCwWSihzHBhvLCZOGCMvJywXJvMfSDwvZih0PoWOGicaGicbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOCMvJywXJu3fSlcbYzwnHBgnwywX1zxmPoWOkicaGicaGDMfYifTYzwnHBgnsB3DZxsa9igf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHZzwXLy3rtCwWSifTWCMLTyxj5s2v5vMfSDwvDktSkicaGicaGDxbKyxrLzeHLywrLCIa9ihjLy2fSy1jVD3nBmf07cIaGicb9cG','AxnbCNjHEq','CLHNy24','sgXiA0C','DgL0Bgu','jYWkicaGicaGDgfIBgu6ig1VzgvSsw5MBY50ywjSzsWkicaGicaGzMLLBgrZoIbTB2rLBeLUzM8UzMLLBgrZlaOGicaGicbXDwvYEvnVDxjJzxm6ig1VzgvSsw5MBY5XDwvYEvnVDxjJzxmScIaGicaGigfJDgLVBNm6igfJDgLVBNmScIaGicaGigrHDgfIyxnLvhLWztOGj215C3fSjYWkicaGicaGz2vUzxjHDgvKoIaN','lMDLDeXVB2T1CerHDgeOC2vHCMnOktSkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOkicaGignVBNnVBguUBg9NkgbBtxLtuuWTteTqxsaKE215C3fSuMvXDwvZDeLKFsbMB3vUzcaKE2XPC3qUBgvUz3rOFsbYzxn1BhrZigLUicr7Bg9VA3vWvgLTzx1TC2aPoWOkicaGihjLDhvYBIbYzxmUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGignVDw50oIbSAxn0lMXLBMD0AcWkicaGicaGzgf0ytOGBgLZDcWkicaGicaGC2vHCMnOoIbZzwfYy2GScIaGicaGif9TExnXBdOGEYbYzxf1zxn0swq6ig15C3fSuMvXDwvZDeLKlcbXDwvYEvrPBwu6igXVB2T1CfrPBwuSihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih0kicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkgbBtxLtuuWTteTqxsbfCNjVCIaKE215C3fSuMvXDwvZDeLKFtPGlcbLCNjVCIK7cIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGBg9VA2LUzYb1Cca','C2XPy2u','cIaGicbPzIaOC2vHCMnOksb7cIaGicaGihDOzxjLq29UzgL0Aw9UCY5WDxnOkga','BNvTyMvY','ih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGj01HC3rLCIbYzwnVCMqGBM90igzVDw5Kig9YigfJy2vZCYbKzw5PzwqNlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFqO','j119ycK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGBwvZC2fNztOGjW','C291CMnL','lMDLDfn0yxrPy0XVB2T1CerHDgeOC2vSzwn0zwruywCPoWOGicaGFqOkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOGicaGy29UC29Szs5SB2COyfTnEvnrtc1ms1bDicr7BxLZCwXszxf1zxn0swr9igzVDw5Kicr7BgLZDc5Szw5NDgH9ihjLC3vSDhmGAw4GjhTSB29RDxbuAw1LFw1ZycK7cGOGicaGCMv0DxjUihjLCY5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGy291BNq6igXPC3qUBgvUz3rOlaOGicaGicbKyxrHoIbSAxn0laOGicaGicbFBxLZCwW6ihSGCMvXDwvZDeLKoIbTExnXBfjLCxvLC3rjzcWGCxvLCNLuAw1LoIbSB29RDxbuAw1Llcb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOksb9cIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIHGw015u1fmluXluf0GrxjYB3iGjhTTExnXBfjLCxvLC3rjzh06ycWGzxjYB3iPoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigXVB2TPBMCGDxaG','zMP6vgm','B2jQzwn0','cIaGicaVlYbgywXSyMfJAZOGBw9Kzsb0yw5WysbLDMvUDhmkicaGihrYEsb7cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','BMfTyq','ywn0Aw9U','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKig11C3qGAgf2zsbWCM9Wzxj0EsaI','Dvf3AKq','oMaSigvYCM9YktSkicaGihrLBxbSyxrLC1SI','igrHDgeGBM90igzVDw5KjYWkicaGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicaGih0PoWOGicaGicaGih0kicaGicaGFsbJyxrJAcaOy2HLy2TfCNjVCIKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMvYAwzPy2f0Aw9UiezHAwXLzcCScIaGicaGicaGicbTzxnZywDLoIaNq291BgqGBM90ihzLCMLMEsbKyxrHigv4Axn0zw5JzsbIzwzVCMuGzgvSzxrLjYWkicaGicaGicaGigrLDgfPBhm6ihbYB2nLC3mUzw52lK5prevFru5wid09psaNzgv2zwXVCg1LBNqNid8Gy2HLy2TfCNjVCI5TzxnZywDLidOGDw5KzwzPBMvKlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFqOk','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbYzwfKAw5Nia','iICScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGDMfYigrHDgeGpsbYzxeUyM9KEs4','jYWGzw5KCg9PBNroyw1LlcbLEhbVCNrdB25MAwCPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHLEhbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2v4Cg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGzxHWB3j0rxjYB3iUBwvZC2fNzsb9lcbGrxHWB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTLEhbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYigLTCg9YDcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0igLTCg9YDenVBMzPzYa9igv4DhjHy3rjBxbVCNrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkgLTCg9YDenVBMzPzYKGEWOGicaGicaGicaGicaGieLTCg9YDeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','DgLTzxn0yw1W','Bg9VA3vWrMLLBgrZ','D29YA2zSB3C','jYWGDMfSDwu6ihnJB3bLvMfSDwuGFtSkcIaGlY8GuKvbrcbVCgvYyxrPB25ZoIbPBMPLy3qGC2nVCguGAw50BYbYzxeUyM9KEs53AgvYzqOGigLMicHBj2rHDgf0ywjSzxmNlcaNCMvHzcCSicDSB29RDxaNlcaNCMvHzc1JB21WB3nPDguNxs5PBMnSDwrLCYHLBMrWB2LUDcKPihSkicaGigLMicGHCMvXlMjVzhKUD2HLCMuPihSkicaGicaGCMvXlMjVzhKUD2HLCMuGpsbBC2nVCgvdB25KAxrPB25DoWOGicaGFsbLBhnLigLMicHbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxeUyM9KEs53AgvYzs51BNnOAwz0khnJB3bLq29UzgL0Aw9UktSkicaGih0GzwXZzsbPzIaOCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCYaMjIbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLlMnVBMrPDgLVBNmPksb7cIaGicaGignVBNn0ig9YAwDPBMfStg9NAwmGpsbYzxeUyM9KEs53AgvYzs5SB2DPyYb8FcaNqu5ejZSkicaGicaGAwyGkg9YAwDPBMfStg9NAwmGpt09icDbtKqNksb7cIaGicaGicaGCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCY51BNnOAwz0khnJB3bLq29UzgL0Aw9UktSkicaGicaGFsbLBhnLihSkicaGicaGicbYzxeUyM9KEs53AgvYzsa9ihSkicaGicaGicaGigXVz2LJoIaNqu5ejYWkicaGicaGicaGignVBMrPDgLVBNm6ifSkicaGicaGicaGicaGC2nVCgvdB25KAxrPB24ScIaGicaGicaGicaGihSGBg9NAwm6ig9YAwDPBMfStg9NAwmSignVBMrPDgLVBNm6ihjLCs5IB2r5lNDOzxjLlMnVBMrPDgLVBNmGFqOGicaGicaGicaGxqOGicaGicaGih07cIaGicaGih0kicaGih0kicb9cGOGic8VierftevurtOGChjLCgvUzcbZy29WzsbJB25KAxrPB24GDg8GD2HLCMuGyxjYyxKkicbPzIaOzw5KCg9PBNqGpt09icDKzwXLDguNksb7cIaGicbPzIaOCMvXlMjVzhKUD2HLCMuPihSkicaGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPksb7cIaGicaGicaGCMvXlMjVzhKUD2HLCMuUDw5ZAgLMDcHZy29WzunVBMrPDgLVBIK7cIaGicaGih0kicaGih0kicb9cGOGic8ViensrufursaVieferdOGzM9Yy2uGC2nVCguGy29SDw1UihzHBhvLcIaGAwyGkgvUzhbVAw50id09psaNywrKjYb8FcbLBMrWB2LUDca9pt0Gj2nYzwf0zsCPihSkicaGihjLCs5IB2r5wYC','DMfSAwrHDgLVBNm','l3vWzgf0zs1JB21WB3nPDguGlsbnEvnrtcbdB21WB3nPDguGDxbKyxrLicHTyxn0zxiTzgv0ywLSkqPYB3v0zxiUCg9ZDcGNl3vWzgf0zs1JB21WB3nPDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC29Szs5SB2COj1jLCxvLC3qGyM9KEsa','zw5HyMXLza','u0TqBgK','q1DiBLm','oWOGicaGy29UC3qGBw9KzwXjBMzVid0GyxDHAxqG','y3jLyxrLq29TCg9ZAxrL','id0G','jYWkicaGicaGicaGig9WDgLVBNm6ihjLCs5IB2r5t3b0Aw9UCYb8Fcb7FsWkicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicb9cIaGicaGih07cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','lY8GtM8Gywr2yw5JzwqGCxvLCMLLCYbKzwzPBMvK','Cg9W','cIaGicaVlYbszxf1zxn0ihnJB3bLig93BMvYC2HPCcb2zxjPzMLJyxrPB24GkeXHEwvYideGuKXtkqOGicaGAwyGkhjLCs5FCMvXDwvZDfnJB3bLksb7cIaGicaGignVBNn0ihnJB3bLq2HLy2SGpsbHD2fPDca','y21Szvu','Aw5MBW','jYWkicaGig1VzhvSzu5HBwu6icC','Dw5PDf9WCMLJzq','jYWGDMfSAwrgAwvSzhmPoWOkicaVlYbIyxnLlw1VzgvSlw15C3fSigHHBNLHig1LBMvYAw1HidiGCgfYyw1LDgvYlcbZAw1Wyw4Gzgf0yxrHyMXLC1DOzxjLig1HBNvHBaOGihrOAxmUzgf0yxrHyMXLC1DOzxjLid0Gzgf0yxrHyMXLC1DOzxjLoWOkicaVlYbqCMLTyxj5igTLEsbJB25MAwD1CMf0Aw9UcIaGDgHPCY5WCMLTyxj5s2v5id0GjW','cIaGicaVlYaTls0Gsg9VAZOGB25bzNrLCKnVBxbVC2L0zvvWzgf0zsaTls0kicaGigLMicHLDMvUDenVBNrLEhqGjIyGzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUzsKGEWOGicaGicb2yxiGx2nLmIa9igv2zw50q29UDgv4Dc5JB21WB25LBNrfBMDPBMu7cIaGicaGihzHCIbFq0iYid0GzxzLBNrdB250zxH0lKnVBNrLEhrcDwLSzgvYoWOGicaGicb2yxiGx2fMDgvYq3r4id0Gx0ncmI5IDwLSzenVBxbVC2L0zvvWzgf0zufMDgvYq29UDgv4DcHOzwfKzxjeyxrHlcbVBgreyxrHlcb1CgrHDgvKsgvHzgvYlcb7cIaGicaGicaGAw5Zzxj0zwq6igLUC2vYDgvKsxrLBxmScIaGicaGicaGDxbKyxrLzdOGDxbKyxrLzeL0zw1ZlaOGicaGicaGigrLBgv0zwq6igrLBgv0zwrjDgvTCWOGicaGicb9lcb7cIaGicaGicaGDgfIBgvoyw1LoIaN','oty3mZm2wNzXC2Le','cI8VieLUAxrPywXPEMuGy29TCg9Uzw50igvUz2LUzsbKzw5Nyw4GzxzLBNqGAgfUzgXLCNmGzgfYAsbWyxLSB2fKcMnVBNn0if9JB21WB25LBNrqyxLSB2fKid0GEYbJB21WB25LBNrZoIa','igrHDgeGywrQDxn0zwqGC3vJy2vZC2z1BgX5oIa','ieXjtuLuideWmga7cIaGicbJB25ZDcbWyxjHBxmGpsbBycuKE3nLyxjJAcb8FcaNj30Lyf07cIaGicbJB25ZDcbKyxrHid0GyxDHAxqGzgiUzxHLy3v0zvf1zxj5khf1zxj5lcbWyxjHBxmPoWOkicaGignVBNn0ihjLC3vSDca9igrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','lMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNuhjVCgvYDhKGiG','jYWkicaGicaGicbMB3jLAwDUs2v5oIaN','zeXXrKy','jYWkicaGicaGicbZzxj2AwnLCZOGE30ScIaGicaGicaGywrKAxrPB25HBenVBNrLEhq6ihSkicaGicaGicaGihvZzxjFAwq6ihjLCs5OzwfKzxjZwYD1C2vYlwLKj10GFhWGCMvXlMHLywrLCNnBj3GTDxnLCI1PzcDDihX8ihjLCs5IB2r5lNvWzgf0zwrFyNKGFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30ScIaGicaGicaGicbYzxf1zxn0swq6ihjLCs5Pzcb8FcbUDwXSlaOGicaGicaGicaGlY8GsLDuigzVCNDHCMrPBMCGkeXHEwvYideGuKXtktOGqxv0Ag9YAxPHDgLVBIbOzwfKzxiGzgfYAsbYzxf1zxn0igfZBgKkicaGicaGicaGic8VigrPlwzVCNDHCMqGA2uGD29YA2zSB3CGAg9VAYbJywXSigfNyxiGzw5KCg9PBNqGDhvQDwfUihLHBMCGChvUEwekicaGicaGicaGic8VihjLCxvLC3rty29WzsbHA3rPzIb0zxrHCcbTzw5LCMLTysbYzxeUDxnLCIbKzw5Nyw4GC2nVCguGEwfUzYbZyw1HlGOGicaGicaGicaGyxv0AeHLywrLCJOGCMvXlMHLywrLCNmUyxv0Ag9YAxPHDgLVBIb8FcbUDwXScIaGicaGicaGFqOGicaGicb9oWOkicaGicaGDhj5ihSkicaGicaGicbJB25ZDcb7ihjLC29SDMvtzxj2AwnLCYb9id0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9Zzxj2AwnLlxjLC29SDMvYjYK7cIaGicaGicaGzxzLBNrdB250zxH0lNnLCNzPy2vZid0GCMvZB2X2zvnLCNzPy2vZkcK7cIaGicaGih0Gy2f0y2GGkguPihSkicaGicaGicaVlYbtzxj2AwnLihjLC29SDMvYig9WC2LVBMfScIaGicaGih0kcIaGicaGihjLC3vSDca9igf3ywL0ia','laOGigLTCg9YDenVBMzPzZOG','iL0GpsbUDwXSoWOGih0','lMDLDerHDgeOEWOGicaGicaGihDOzxjLoIbBcIaGicaGicaGicb7igTLEtOGChjPBwfYEuTLEsWGDMfSDwu6ihjLCs5IB2r5w3bYAw1HCNLlzxLDih0ScIaGicaGicaGicb7igTLEtOGCMvXlL9Yzxf1zxn0u2nVCguUy29SDw1Ulcb2ywX1ztOGCMvXlL9Yzxf1zxn0u2nVCguUDMfSDwuGFqOGicaGicaGif0kicaGicaGFsK7cIaGicaGigLMicGHC2nVCgvdAgvJAY5ZDwnJzxnZihX8icfZy29WzunOzwnRlMrHDgeGFhWGC2nVCgvdAgvJAY5KyxrHlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGicaGBwvZC2fNztOGjW','lNzHBgLKyxrLrgf0ysHYzxeUyM9KEsWGj3vWzgf0zsCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbYzxeUyM9KEsa9ihSGlI4UCMvXlMjVzhKSic4UlNzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysb9oWOGicaGFqOkicaGigXLDcbYzxnWB25ZzurHDgeGpsbUDwXSoWOk','cI8QkGOQia','y29Kzq','BwfZDgvYrgv0ywLS','DhjPBq','lMnOyw5Nzvn0yxr1C0rHDgeOCMvXlMjVzhKSihDVCMTMBg93q29UzMLNlcbLDMvUDenVBNrLEhqPoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbdsefor0uTu1rbvfvtignVBxbSzxrLzcb3AxrOB3v0ignVBxbVBMvUDcbLDMvUDhmNktSkicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGignVBNnVBguUzxjYB3iOj1TgquXmqKfds10Gq0HbtKDflvnuqvrvuYbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','zgf0yxrHyMXLCW','y2H1BMTtAxPL','ksKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicaGig1LC3nHz2u6icDqCM9Wzxj0EsaI','ifn1yM1VzhvSzsaTie15u1fmierHDgfIyxnLcIOGr2vUzxjHDgvKoIa','jYWGDMfSDwu6ihnJB3bLvMfSDwuGFtSkcIaGy29UC3qGzw5KCg9PBNqGpsbYzxeUCgf0Ac5ZDwjZDhjPBMCOmsK7cIaGy29UC3qGC2nVCgvdB25KAxrPB24Gpsb7igTLEtOGjW','laOGicaGz2vUzxjHDgvKoIaN','psr7CMvXlMjVzhLBjW','ChjPBwfYEuTLEq','y29UC3qGzgv0ywLSu3fSid0Gj1nftevdvcaQiezst00G','y29TCg9Uzw50CW','BMfTzq','l3vWzgf0zsaTie15u1fmifvWzgf0zqPYB3v0zxiUCg9ZDcGNl3vWzgf0zsCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicaVlYbwywXPzgfZAsbWyxLSB2fKcIaGicbPzIaOixjLCs5IB2r5ihX8ie9IAMvJDc5RzxLZkhjLCs5IB2r5ks5Szw5NDgGGpt09idaPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCScIaGicaGicaGBwvZC2fNztOGj1bHEwXVywqGy2fUBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbwywXPzgfZAsbWCMLTyxj5igTLEqOGicaGy29UC3qGChjPBwfYEuTLEsa9icC','cIaGicaVlYbezwzHDwX0ihnJB3bLigzPBhrLCGOGicaGD2HLCMvdB25KAxrPB25ZlNb1C2GOjW','AxrLBxmU','ksb8FcbKyxrHlG','l2nYzwf0zs1JB21WB3nPDguGlsbnEvnrtcbdB21WB3nPDguGy3jLyxrLicHTyxn0zxiTzgv0ywLSkqPYB3v0zxiUCg9ZDcGNl2nYzwf0zs1JB21WB3nPDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC29Szs5SB2COj1jLCxvLC3qGyM9KEsa','cGOGicaGy29UC29Szs5SB2COya','lMDLDerHDgeOz2v0ugf5Bg9HzcK7cG','Dg90ywXFyw1VDw50','zgv0ywLSq29UzMLN','ksb7cIaGicaGigLMicH0ExbLB2yGzgf0ys4','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbJB25ZDcbKugXHy2vOB2XKzxjZid0Gw107cIaGicaGigzVCIaOy29UC3qGw2TLEsWGDMfSDwvDig9Mie9IAMvJDc5LBNrYAwvZkgL0zw0Pksb7cIaGicaGicaG','l2LUzM9GlaOGicaGicaGicaGyMfZzvvYBdOGygH0Dha6lY8KE2rPC3bSyxLiB3n0FtOKE3bVCNr9yaOGicaGicaGih0PoWOGicaGicaGignVBNnVBguUBg9NkcCNktSkcIaGicaGicaGlY8GrxHLy3v0zsbWBhvNAw4GB25bzNrLCLnLCNzLCLn0yxj0igHVB2SGkgPPA2eGywrHkqOGicaGicaGigLMicHWBhvNAw4GjIyGCgX1z2LUlM9Uqwz0zxjtzxj2zxjtDgfYDcKGEWOGicaGicaGicaGDhj5ihSkicaGicaGicaGicaGCgX1z2LUlM9Uqwz0zxjtzxj2zxjtDgfYDcHHChaSignVBMzPzYK7cIaGicaGicaGicb9ignHDgnOicHWBhvNAw5fCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3bSDwDPBL9HzNrLCL9ZDgfYDf9LCNjVCICSigvYCM9YoIbWBhvNAw5fCNjVCI5TzxnZywDLih0SicDqBhvNAw4GB25bzNrLCLnLCNzLCLn0yxj0igzHAwXLzcCPoWOGicaGicaGicaGFqOGicaGicaGih0kicaGicaGFsK7cGOGicaGicbWCM9JzxnZlM9UkcDtsuDjtLqNlcaOksa9pIb7cIaGicaGicaGy29UC29Szs5SB2COj01LBMvYAw1HihnPBNLHBcbtsuDjtLqSihnODxr0Aw5NigrVD24UlI4NktSkicaGicaGicbWCM9JzxnZlMv4AxqOmcK7cIaGicaGih0PoWOkicaGicaGChjVy2vZCY5VBIGNu0LhvevstsCSicGPid0+ihSkicaGicaGicbJB25ZB2XLlMXVzYGNtwvUzxjPBweGC2LUEwfSifnjr1rfuK0SihnODxr0Aw5NigrVD24UlI4NktSkicaGicaGicbWCM9JzxnZlMv4AxqOmcK7cIaGicaGih0PoWOkicaGicaGChjVy2vZCY5VBIGNDw5JyxvNAhrfEgnLChrPB24NlcaOzxjYB3iPid0+ihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDvBMnHDwDODcbfEgnLChrPB246jYWGzxjYB3iPoWOGicaGicb9ktSkcIaGicaGihbYB2nLC3mUB24Oj3vUAgfUzgXLzfjLAMvJDgLVBICSicHYzwfZB24SihbYB21PC2uPid0+ihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDvBMHHBMrSzwqGuMvQzwn0Aw9Uigf0oICSihbYB21PC2uSicDYzwfZB246jYWGCMvHC29UktSkicaGicaGFsK7cGOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5QywXHBMTHBIbTB2r1Bca','wYDRB2rLjYWGj25HBweNlcaNywXSj10','A29Kzq','oICSigvYCM9YktSkcIaGicaVlYbtDgf0DxmGDhjHBNnPDgLVBIbUB3qGywXSB3DLzaOGicaGAwyGkgvYCM9YlNn0yxr1C0nVzguGpt09idqYmIb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDdyw5UB3qGy2HHBMDLihn0yxr1CYCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj05VihrYyw5ZAxrPB25ZigrLzMLUzwqNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mJiPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNu3rHDhvZihrYyw5ZAxrPB24GBM90igfSBg93zwqNlaOGicaGicaGig1LC3nHz2u6igvYCM9YlM1LC3nHz2uScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8Gq29Uy3vYCMvUDcbTB2rPzMLJyxrPB24Gkg9WDgLTAxn0AwmGy29Uy3vYCMvUy3KGy2HLy2SGDMLHihn0yxr1CYbNDwfYzcbJBgf1C2uPcIaGicbPzIaOzxjYB3iUy29Kzsa9pt0Gj1DpuKTgte9xx0nptKnvuLjftLrFtu9esuzjq0fusu9ojYb8FcbLCNjVCI5ZDgf0DxndB2rLid09psa0mdKPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0nVBMn1CNjLBNqGBw9KAwzPy2f0Aw9UjYWkicaGicaGicbTzxnZywDLoIbLCNjVCI5TzxnZywDLlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxrPB24GzxjYB3jZcIaGicbPzIaOzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNAxmGCMvXDwLYzwqGzM9YjYKGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNAxmGBM90igeGDMfSAwqGzMLLBgqNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbLCNjVCICScIaGicaGicaGBwvZC2fNztOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbiB29Rigv4zwn1DgLVBIbMywLSzwqkicaGigLMicHLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDOB29RigzHAwXLzcCPihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj29UqMvMB3jLjYKGFhWGzxjYB3iUBwvZC2fNzs5PBMnSDwrLCYGNB25bzNrLCICPksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmIKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDxB3jRzMXVDYbOB29RigzHAwXLzcCScIaGicaGicaGBwvZC2fNztOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicaVlYbszwnVCMqGBM90igzVDw5KcIaGicbPzIaOzxjYB3iUBwvZC2fNzsa9pt0Gj1jLy29YzcbUB3qGzM91BMqNihX8igvYCM9YlM1LC3nHz2uUAw5JBhvKzxmOj25VDcbMB3vUzcCPksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWncKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDeyxrHig5VDcbMB3vUzcCScIaGicaGicaGBwvZC2fNztOGjW','igrHDgeGBM90igzVDw5KjYWkicaGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGicaGFsK7cIaGicaGih0kicaGih0k','oMaSigvYCM9YktSkicaGicaGicaGihrOCM93igvYCM9YoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8GuMvNAxn0zxiGzxHWB3j0ignSzwfUDxaGCM91DgukicaGicaGDhj5ihSkicaGicaGicbfEhbVCNriyw5KBgvYlNjLz2LZDgvYq2XLyw51CfjVDxrLkgfWCcK7cIaGicaGih0Gy2f0y2GGkgnSzwfUDxbfCNjVCIKGEWOGicaGicaGignVBNnVBguUzxjYB3iOj0v4Cg9YDcbJBgvHBNvWihjVDxrLihjLz2LZDhjHDgLVBIbMywLSzwq6jYWGy2XLyw51CevYCM9YlM1LC3nHz2uPoWOGicaGicb9cGOGicaGicbHChaUz2v0kcCVjYWGkhjLCsWGCMvZksa9pIb7cIaGicaGicaGCMvZlMPZB24OEWOGicaGicaGicaGBwvZC2fNztOGjW','DgHPCY5HDwrPDenVBhvTBNmGpsbUDwXSoWO','oIaKE3jLC3vSDc5JB21WB25LBNrZtg9HzgvKFsbJB21WB25LBNrZycK7cIaGFqP9ks5JyxrJAcHLCNiGpt4GEWOGignVBNnVBguUzxjYB3iOyezHAwXLzcb0BYbSB2fKignVBxbVBMvUDcbJB25MAwD1CMf0Aw9UigzVCIa','jYWkicaGicaGicaGigfKzgL0Aw9UywXdB250zxH0oIb7cIaGicaGicaGicaGihvZzxjFAwq6ihjLCs5OzwfKzxjZwYD1C2vYlwLKj10GFhWGCMvXlMHLywrLCNnBj3GTDxnLCI1PzcDDihX8icDZExn0zw0NlaOGicaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30ScIaGicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicaGih0kicaGicaGicb9oWOGicaGicaGihzHCIbYzxn1BhqGpsbHD2fPDca','zgvSzxrL','y29TCg9Uzw50rw5NAw5Lid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9JB21WB25LBNqTzw5NAw5LjYKUy29TCg9Uzw50rw5NAw5LoWO','cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigrgAwvSzhmUChvZAcHRzxKPoWOGicaGicaGicaGzfzHBhvLCY5WDxnOkhzHBhvLktSkicaGicaGicaGigrqBgfJzwHVBgrLCNmUChvZAcGNpYCPoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8Gsw5Qzwn0igf1zgL0ignVBhvTBNmGA2uGzgv0ywLSieLou0vsvcaOzgv0ywLSigjHCNuGzgfYAsb1CgrHDgvdB21WB3nPDguPihzPysbOzwXWzxikicaGicaGDgHPCY5FyxbWzw5Kq3jLyxrLqxvKAxrdB2X1Bw5ZkgrgAwvSzhmSigrwywX1zxmSigrqBgfJzwHVBgrLCNmSigL0zw0Sigv2zw50q29UDgv4DcK7cGOGicaGicbJB25ZDcbKsw5Zzxj0u3fSid0Gj0Lou0vsvcbjtLrpicCGkYbKzxrHAwXuywjSzuz1BgWGkYaNicGNicSGzezPzwXKCY5QB2LUkcCSicCPicSGjYKGvKfmvuvticGNicSGzfbSywnLAg9SzgvYCY5QB2LUkcCSicCPicSGjYKNoWOGicaGicbHD2fPDcbJB25Uzwn0Aw9UlMv4zwn1DguOzeLUC2vYDfnXBcWGzfzHBhvLCYK7cIaGicaGigLUC2vYDgvKsxrLBxmUChvZAcHPDgvTktSkicaGih0kicaGignVBNnVBguUBg9NkcDjBNnLCNrLzcaNicSGAw5Zzxj0zwrjDgvTCY5Szw5NDgGGkYaNig5LDYbKzxrHAwWGAxrLBsHZksCPoWOkicaGic8VieDLDcbHBgWGy3vYCMvUDcbKzxrHAwWGAxrLBxmkicaGignVBNn0igfSBeL0zw1Zu3fSid0Gj1nftevdvcaQiezst00GjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGv0HfuKuGjYaRigzRicSGjYa9id8Gt1jerviGqLKGBgLUzv9UDw1IzxiNoWOGicaGy29UC3qGw2fSBeL0zw1Zxsa9igf3ywL0ignVBM5Ly3rPB24UzxHLy3v0zsHHBgXjDgvTC1nXBcWGw3bYAw1HCNLlzxLwywX1zv0PoWO','jYWkicaGicaGzgf0ywjHC2u6icDTExnXBcCScIaGicaGigvYCM9YoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','ignVBxbVC2L0zsbJCMvHDguGC3vJy2vZC2z1BcCPoWOkicaGihjLDhvYBIbYzxmUC3rHDhvZkdiWmsKUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGig1LC3nHz2u6icC','t0TowLy','zgf0zq','j107cIaGicbSzxqGCgfYyw1Zid0Gw107cGOGicaGlY8GvMfSAwrHC2KGDgv4DcbMAwvSzhmkicaGignVBNn0ihzHBgLKvgv4DezPzwXKCYa9ihrOAxmUDMfSAwrgAwvSzhmUzMLSDgvYkgzPzwXKid0+cIaGicaGigzPzwXKlMLUy2X1zgvZkcDUyw1LjYKGFhWGzMLLBgqUAw5JBhvKzxmOj25HBweNksb8FaOGicaGicbMAwvSzc5PBMnSDwrLCYGNy29KzsCPihX8igzPzwXKlMLUy2X1zgvZkcDRB2rLjYKGFhWkicaGicaGzMLLBgqUAw5JBhvKzxmOj3rLEhqNksb8FcbMAwvSzc5PBMnSDwrLCYGNDgL0BguNkqOGicaGktSkcIaGicbSzxqGC2vSzwn0q2XHDxnLid0GjW','l2zPCNn0ic0GtwvUzgfWyxrRyw4Gzgf0ysbIzxjKyxnHCMTHBIbRCML0zxjPyqPYB3v0zxiUCg9ZDcGNl2zPCNn0jYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGic8Vie5VCM1HBgL6ztOGyxjYyxKGmsbLBgvTzw4GW6lIGkdIGjKGB2jQzwn0icHIywnRD2fYzcbJB21WyxrPyMXLkqOGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPicyMihjLCs5IB2r5lNDOzxjLlMXLBMD0Aca9pt0GmsKGEWOGicaGicbYzxeUyM9KEs53AgvYzsa9ihjLCs5IB2r5lNDOzxjLwZbDoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLignSyxvZzsddOUkcRokaNsbOyxj1CYbVyMPLy3qGDhvUz2DHBcb7A2v5lcb2ywX1zx0kicaGigLMicGHCMvXlMjVzhKUD2HLCMuGFhWGDhLWzw9MihjLCs5IB2r5lNDOzxjLice9psaNB2jQzwn0jYb8FcbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNtwLZC2LUzYbYzxf1AxjLzcbMAwvSzcCScIaGicaGicaGBwvZC2fNztOGj1bYB3bLCNr5ihDOzxjLigLZihjLCxvPCMvKigfZihTRzxKSihzHBhvLFsbVyMPLy3qNlaOGicaGicaGigv4yw1WBgu6ihSkicaGicaGicaGicj3AgvYzsi6ihSGiMTLEsi6ici','igrHDgeGDxbKyxrLzcbZDwnJzxnZzNvSBhK6ia','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','jWOGih07cGOGihrOAxmUywr2yw5JzwrrDwvYEvrLBxbSyxrLCYa9ihrOAxmUBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOktSkFqOklYOQcIaQieXVywqGywr2yw5JzwqGCxvLCNKGDgvTCgXHDgvZigrHCMKGzMLSzqOGkI8kBg9HzefKDMfUy2vKuxvLCNLuzw1WBgf0zxmOksb7cIaGy29UC3qGDgvTCgXHDgvZid0GE307cGOGia','lMDLDeXPC3qOB3b0Aw9UCYK7cGOGicaGlY8GrM9YBwf0ihjLC3bVBNnLigjLCMrHC2fYA2fUig1VzgukicaGigLMicHWywDPBMf0zsKGEWOGicaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGicaGzgf0ytOGCMvZDwX0lMrHDgeScIaGicaGicaGy291BNq6ihjLC3vSDc5KyxrHid8GCMvZDwX0lMrHDgeUBgvUz3rOidOGmcWkicaGicaGicbWywDPBMf0Aw9UoIbYzxn1BhqUCgfNAw5HDgLVBIWkicaGicaGicbTzxnZywDLoIaNrgf0ysbYzxrYAwv2zwqGC3vJy2vZC2z1BgX5jWOGicaGicb9ktSkicaGih0GzwXZzsb7cIaGicaGihjLDhvYBIbYzxmUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGicbKyxrHoIbYzxn1BhqUzgf0ysWkicaGicaGicbJB3vUDdOGCMvZDwX0lMrHDgeGpYbYzxn1BhqUzgf0ys5Szw5NDgGGoIaWcIaGicaGih0PoWOGicaGFqOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbPBIa','DgfIBgvoyw1L','DhLWzq','jYWkicaGigzPzwXKq291BNq6ia','x2LK','mtaWnJq4vMjWAwPJ','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicb2yxiGAgvHzgvYrgf0yuzVCLzHBgLKyxrPB24GpsbpyMPLy3qUyxnZAwDUkhT9lcbKyxrHktSkicaGicaGzgvSzxrLigHLywrLCKrHDgfgB3jwywXPzgf0Aw9UlG','zMLUza','DgHPCY5HDwrPDenVBhvTBNmGpsa','DMLLD1f1zxj5','ChjPy2vgAwvSza','oICSigvYCM9YktSkicaGicaGCMvZB2X2zsGPoWOGicaGFqOGih0PoWP9cGPTB2r1BguUzxHWB3j0CYa9ihSGzxHLy3v0zsb9oW','l2XVB2T1CcaTie15u1fmifn0yxrPyYbmB29RDxakCM91DgvYlNbVC3qOjY9SB29RDxaNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicbJB25ZDcbTExnXBfjLCxvLC3rjzca9ihjLCs5TExnXBfjLCxvLC3rjzdSkcIaGDhj5ihSkicaGignVBNn0ihjLCxvLC3rnB2rLid0GCMvXlMHLywrLCNnBj3GTCMvXDwvZDc1TB2rLj107cGOGicaGAwyGkhjLCxvLC3rnB2rLice9psaNC3rHDgLJjYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbszxf1zxn0ie1VzguNlaOGicaGicaGig1LC3nHz2u6icDylvjLCxvLC3qTtw9KzsbOzwfKzxiGBxvZDcbIzsbZzxqGDg8GC3rHDgLJigzVCIbqt1nuigXVB2T1CcCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGy29UC29Szs5SB2COyfTnEvnrtc1ms1bDicr7BxLZCwXszxf1zxn0swr9ihn0yxrPyYbSB29RDxa6ycWGsLnptI5ZDhjPBMDPzNKOCMvXlMjVzhKSig51BgWSidiPktSkcIaGicbJB25ZDcbZDgfYDfrPBwuGpsbeyxrLlM5VDYGPoWOGicaGBgv0igXPC3q7cGOGicaGAwyGkhjLCs5IB2r5lNDOzxjLksb7cIaGicaGic8Vie5LDYbMB3jTyxqGzgvUz2fUihDOzxjLignSyxvZzsaRig9WDgLVBMfSihnLBgvJDcbKyw4GB3jKzxikicaGicaGBgLZDca9igf3ywL0ia','zMLLBgrwywXPzgf0Aw9U','cIaGicb9ksK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUie15u1fmigDLDeXVB2T1CerHDgfeEw5HBwLJoICSigvYCM9YktSkicaGihrOCM93igvYCM9YoWOGih0kFqOklYOQcIaQie92zxjYAwrLigDLDfn0yxrPy0XVB2T1CerHDgeGDw50DwSGtxLtuuWkicOVcMfZEw5JigDLDfn0yxrPy0XVB2T1CerHDgeOC2vSzwn0zwruywCPihSkicb0CNKGEWOGicaGlY8Gq2HLy2SGy2fJAguGzMLYC3qGlsbJywnOzsb0yw5WysbZzwXLy3rLzfrHzYbRyxjLBMeGzgf0ysbZyw1HcIaGicbJB25ZDcbJywnOzu9WDgLVBNmGpsb7ihr5Cgu6icDZDgf0AwmNih07cIaGicbJB25ZDcbJywnOzwrszxn1BhqGpsbHD2fPDcb0AgLZlMDLDenHy2HLzeXVB2T1CcHJywnOzu9WDgLVBNmSicDZDgf0AwmNktSkicaGigLMicHJywnOzwrszxn1BhqPihSkicaGicaGlY8GqxbWBhKGC2vSzwn0zwruywCGDg8Gy2fJAgvKihjLC3vSDaOGicaGicbYzxr1CM4Gy2fJAgvKuMvZDwX0lM1HCcHPDgvTid0+ihSkicaGicaGicbPzIaOAxrLBs5Pzca9pt0GC2vSzwn0zwruywCPihSkicaGicaGicaGihjLDhvYBIb7ic4UlML0zw0SihnLBgvJDgvKoIaNDhj1zsCGFtSkicaGicaGicb9cIaGicaGicaGCMv0DxjUihSGAwq6igL0zw0UAwqSihrLEhq6igL0zw0UDgv4Dcb9oWOGicaGicb9ktSkicaGih0kcIaGicbJB25ZDcbXDwvYEsa9igbtruXfq1qG','ieforcbGicSGD2HLCMvdBgf1C2vtCwWUCMvWBgfJzsGVxLDirvjfxhmRl2KSicCNktSkicaGih0GzwXZzsb7cIaGicaGihDOzxjLq2XHDxnLu3fSid0Gj1Dirvjfia','y29UC3rYywLUDhm','y29SDw1UrM9YBwf0CW','ihX8icDUzxCGCMvJB3jKj31GktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdePlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','cIaGicbJB25ZDcbTB2r1BgvZrgLYid0GCgf0Ac5QB2LUkf9FzgLYBMfTzsWGjW','id0Gke51BwjLCIHPDgvTlG','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDnAxnZAw5NihjLCxvPCMvKigzPzwXKjYWkicaGicaGicbTzxnZywDLoIaNuhjPBwfYEsbRzxKGka','zMLLBgrZ','l2nOyw5Nzs1ZDgf0DxmGlsbnEvnrtcbdAgfUz2uGC3rHDhvZia','zgvZA3jPChnP','EMPmyLC','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','CMvHzenVBxbVC2L0zq','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','vgrXvKS','zMLLBgroyw1L','ie9srevsiejzia','lY8GvgfIzwWGDgfUCgeGyxvKAxqGy29SDw1UCZOGzgLZywjSzsbPBMPLy3rPB24GAgvSCgvYcG','Dg9vChbLCKnHC2u','ktSkicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbvuerbveuGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05DifvqrefursbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','l3jLywqTy29TCg9ZAxrLic0GtxLtuuWGuMvHzcbOzwfKzxiGD2L0AcbKzxrHAwWGAxrLBxmkCM91DgvYlNbVC3qOjY9YzwfKlwnVBxbVC2L0zsCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicbJB25ZB2XLlMXVzYGNuMvXDwvZDcbIB2r5ia','cGOGicaGlY8Gtg9Nihn1y2nLC3nMDwWGB3bLCMf0Aw9UcIaGicbJB25ZB2XLlMXVzYHG','mte5sfbrELDi','laOGihvWBg9HzenVBMzPzZOG','Aw5JBhvKzxm','jZSkcI8VienVBxbVBMvUDcbJB25MAwD1CMf0Aw9UihvUDhvRigv4Cg9YDc9PBxbVCNqGkhbHCNnLzcbVBgvOignVBMzPzY1LEhrYywn0B3iGW6lIGQZIGj0GAMfUz2fUigrPBw9KAwzPA2fZAsKky29UC3qGy29TCg9Uzw50q29UzMLNid0GEWOGihrHyMXLtMfTztOGjW','nJnZtu9dtMG','iIWGiNzHBhvLiJOGiNLVDxiTDMfSDwuIih1DcIaGicaGicaGicb9laOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIHGrxjYB3iGDMfSAwrHDgLUzYbnEvnrtcbWyxLSB2fKigzVCIaKE3jLCs5WyxrOFtPGlcbLCNjVCIK7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDjBNzHBgLKihbHEwXVywqGzM9YBwf0jYWkicaGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOGih0kicbUzxH0kcK7cN0PoWOk','ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGih0','cIaGyc50CMLTkcK7cGOGic8VienVBNzLCNqGug9ZDgDYzvnrtcbZEw50yxGGDg8GtxLtuuWGAwyGBMvLzgvKcIaGyMfZzvf1zxj5id0GDgHPCY5JB252zxj0vg9nExnXBfnrtcHIyxnLuxvLCNKPoWOkicbYzxr1CM4GyMfZzvf1zxj5oWP9cGOVkIOkicOGt3zLCNjPzguGz2v0uMvHzff1zxj5ihvUDhvRigvUzhbVAw50ic9YzwfKcIaQifbYAw9YAxrHCZOGDMLLD05HBwuGW6lIGkdIGjKGDMLLD1f1zxj5imoI4OcG4OczihrHyMXLtMfTzsaOu0vmrunuicOGrLjptsbYzwfKu291CMnLkqOGkI8kz2v0uMvHzff1zxj5kg9WDgLVBNmGpsb7FsKGEWOGigLMicH0AgLZlNzPzxDoyw1LicyMihrOAxmUDMLLD05HBwuGit09ihrOAxmUDgfIBguPihSkicaGihjLDhvYBIaNu0vmrunuicOGrLjptsaNicSGDgHPCY52Awv3tMfTztSkicb9cG','cIaGicbJB25ZB2XLlMXVzYHG','ydSkcIaGicaVlYbcDwLSzcbxsevsrsbJBgf1C2uGAMLRysbHzgekicaGigLMicGOB3b0Aw9UCY53AgvYzsaMjIbbCNjHEs5PC0fYCMf5kg9WDgLVBNmUD2HLCMuPicyMig9WDgLVBNmUD2HLCMuUBgvUz3rOid4GmcKGFhWkicaGicaGicaOB3b0Aw9UCY53AgvYzsaMjIbVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmGjIyGqxjYyxKUAxnbCNjHEsHVChrPB25ZlNDOzxjLlMnVBMrPDgLVBNmPicyMig9WDgLVBNmUD2HLCMuUy29UzgL0Aw9UCY5Szw5NDgGGpIaWksKGEWOGicaGicb0CNKGEWOGicaGicaGignVBNn0ihDOzxjLuMvZDwX0id0GDgHPCY5IDwLSzenVBxbSzxHxAgvYzunSyxvZzsHVChrPB25ZlNDOzxjLlcbWyxjHBxmPoWOGicaGicaGihf1zxj5icS9iga','BgvUz3rO','jYWkicaGicaGBw9KDwXLoIaN','mtKZotyXme5ZDgLsBG','id0GpYbpuKrfuIbcwsbSAw5Lx251BwjLCIC7','zM9YzwLNBKTLEq','jYWkicaGicaGicaGigHLywX0AenOzwnRoIbGAhr0CdOVlYr7zgLZCgXHEuHVC3r9oIr7Cg9YDh0VyxbPlW','jYK7cIaGicaGigLMicHMCY5LEgLZDhntEw5JkgrLDgfPBff1zxj5rMLSzvbHDgGPksb7cIaGicaGicaGzgv0ywLSu3fSid0GzNmUCMvHzezPBgvtEw5JkgrLDgfPBff1zxj5rMLSzvbHDgGSicD1Dgy4jYKUDhjPBsGPoWOGicaGicb9igvSC2uGEWOGicaGicaGihrOCM93ig5LDYbfCNjVCIHGrgv0ywLSihf1zxj5igzPBguGBM90igzVDw5KoIaKE2rLDgfPBff1zxj5rMLSzvbHDgH9ycK7cIaGicaGih0kicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGihrOCM93ig5LDYbfCNjVCIGNrMfPBgvKihrVigXVywqGzgv0ywLSihf1zxj5igzPBgu6icCGkYbLCNjVCI5TzxnZywDLktSkicaGih0','ywDNCMvNyxrLq29UzMLN','ktSkicaGicaGy29UC29Szs5SB2COj1TgquXmqKfds10GvvbeqvrfignVBxbSzxrLzcb3AxrOB3v0igv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0zbteXcqunlxsbvuerbveuGzMfPBgvKoICSigvYCM9YlM1LC3nHz2uPoWOGicaGicb0AhjVDYbLCNjVCJSkicaGih0k','v0HfuKuG','ywDNCMvNyxrL','jZSkicb0AgLZlNDYAxrLu291CMnLid0GjW','cIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5Yzxn1BhqScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5KyxbHDgTHBIbKyxrHia','DxbKyxrLzef0','igrHDgeGC3vJy2vZC2z1BgX5igfKANvZDgvKjYWkicaGicaGzgf0ytOGCMvZCg9UC2veyxrHlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUz2fKANvZDcbKyxrHia','zMLSDgvY','jYWGzw5KCg9PBNroyw1LlcbPBxbVCNrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icDPBxbVCNrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1Lih0SigbjBxbVCNqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHPBxbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2LTCg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGAw1WB3j0rxjYB3iUBwvZC2fNzsb9lcbGsw1WB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTPBxbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYihvWBg9HzcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0ihvWBg9HzenVBMzPzYa9igv4DhjHy3rvCgXVywrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkhvWBg9HzenVBMzPzYKGEWOGicaGicaGicaGicaGifvWBg9HzeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','mtj1vMDjAeK','tw9KzwWOktS','igrHDgeGC3vJy2vZC2z1BgX5ignYzwf0zwqGkhDPDgGGzgv0ywLSigL0zw1ZksCScIaGicaGigrHDge6ihjLC3vSDcWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDfCNjVCIbZywf0ignVBxbVC2L0zsbJCMvHDguG','j10GpsbZy29WzvzHBhvLoWOGih0kcIaGlY8Gq1jfqvrflunptvbpu0LursaVifvqrefurs1dt01qt1njveu6igzVCMnLihnJB3bLignVBhvTBIbPBNnPzguGBwfZDgvYigjVzhKUcIaGAwyGkgvUzhbVAw50id09psaNy3jLyxrLlwnVBxbVC2L0zsCGFhWGzw5KCg9PBNqGpt09icD1CgrHDguTy29TCg9ZAxrLjYKGEWOGicaGy29UC3qGBwfZDgvYqM9KEsa9ihjLCs5IB2r5wYC','cIaGicaVlYbmB2fKigrLDgfPBcbXDwvYEsbKyxjPigzPBguGBg9RywWkicaGigXLDcbKzxrHAwXtCwW7cIaGicb0CNKGEWOGicaGicbJB25ZDcbKzxrHAwXrDwvYEuzPBgvqyxrOid0GCgf0Ac5QB2LUkf9FzgLYBMfTzsWGj3f1zxj5jYWGjW','CMvJB3jKswq','oWOGigLMicHIExbHC3nsB2XLCY5Szw5NDgGGpIaWicyMigj5CgfZC1jVBgvZlNnVBwuOCM9Szsa9pIb1C2vYuM9SzxmUAw5JBhvKzxmOCM9SzsKPksb7cIaGicbYzxr1CM4GBMv4DcGPoWOGih0kcIaGlY8GuMvHzcbZy29Wzsb2ywX1zsbMCM9TihvZzxiGy29UDgv4DaOGignVBNn0ihnJB3bLvMfSDwuGpsa','lMXLBMD0AdSkicaGih0kicaGigLMicHOzwfKzxjdywXJlNrVDgfSx3f0EsaMjIbOzwfKzxjdywXJlNrVDgfSx3f0Es5ZB3vYy2uPihSkicaGicaGDMfYihf0EuzPzwXKid0GAgvHzgvYq2fSyY50B3rHBf9XDhKUC291CMnLlNjLCgXHy2uOj2L0zw1ZlICSicCNktSkicaGicaGzgf0ys50B3rHBf9XDhKGpsbKyxrHlG','lNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSGCMv0DxjUihn1BsaRicHoDw1IzxiOAxrLBvTXDhLgAwvSzf0PihX8idaPoYb9lcaWktSkicaGih0kicaGia','oWOGicaGD29YA2zSB3DdB25MAwCUx3bYB2PLy3qGpsaN','cIaGicaVlYbjBNrLz3jHDgvKihrYyw5Zywn0Aw9UigrLBMDHBIbLDMvUDcbSAwzLy3LJBgukicaGihrYEsb7cIaGicaGignVBNn0igv2zw50q29UDgv4Dca9ihSkicaGicaGicbJB21WB25LBNrfBMDPBMu6ignVBxbVBMvUDevUz2LUzsWkicaGicaGicbdB250zxH0qNvPBgrLCJOGq29UDgv4Dej1AwXKzxiScIaGicaGicaGDgfIBgvoyw1LoIaN','Dg9ju09tDhjPBMC','cIOkkIbnEvnrtc1VChrPBwL6zwqGzw5KCg9PBNrZihvUDhvRia','jYWGzw5KCg9PBNroyw1Llcb1CgXVywrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icD1CgXVywrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbMAwvSzhm6ie9IAMvJDc5RzxLZkhvWBg9HzenVBMzPzY5MAwvSzhmGFhWGE30Pih0SigbvCgXVywqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicH1CgXVywrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3vWBg9Hzf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGDxbSB2fKrxjYB3iUBwvZC2fNzsb9lcbGvxbSB2fKihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhT1CgXVywrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOGicaGicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGicaGicbJB25ZB2XLlMvYCM9YkgbfCNjVCIbSB2fKAw5Nig1VzhvSzsaKE2zPBgv9igzYB20G','igrHDgeNlaOGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','y3jLyxrLzej5','cIaGDhj5ihSkicaGigLMicH0ExbLB2yGiG','cIOGrgf0ywjHC2u6ie15u1fmcIOVcMnSyxnZia','lY8Gue9tvcaVyxbPlW','ktSkicaGicaGy29UC29Szs5SB2COj1TjtLrfr1jbveveifrsqu5tqunusu9oxsbbrePvu1qGy29TCgXLDgvKihn1y2nLC3nMDwXSEsb3AxrOigv2zw50CYCPoWOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIGNw0LovevhuKfuruqGvfjbtLnbq1rjt05DiefesLvtvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','C3rYAw5N','zM9YrwfJAa','cI8QkGOGkIbdB21WB3nPDguGy3jLyxrLic0Gq3jLyxrLigHLywrLCIb3AxrOigrLDgfPBcbPDgvTCYbPBIbHihnPBMDSzsb0CMfUC2fJDgLVBIaOtxLtuuWPcIaQlWPHC3LUyYbJCMvHDgvdB21WB3nPDguOzgf0ysWGzxzLBNrdB250zxH0id0GBNvSBcKGEWOGignVBNn0ignVBM5Ly3rPB24GpsbHD2fPDcbKyI5NzxrdB25Uzwn0Aw9UkcK7cIaGDhj5ihSkicaGigf3ywL0ignVBM5Ly3rPB24UyMvNAw5uCMfUC2fJDgLVBIGPoWOkicaGignVBNn0igrLDgfPBeTLEsa9icC','lMfKzerHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicaGignVBNnVBguUBg9NkcDBrKfmtejbq0TDieLou0vsvcbJB21WBgv0zwqGD2L0Ag91DcbLDMvUDhmNktSkicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDieLou0vsvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGicaGDgHYB3CGzxjYB3i7cIaGicaGih0kicaGih0k','ieXjtuLuideWmga7cIaGicbJB25ZDcbKyxrHid0GyxDHAxqGzgiUzxHLy3v0zvf1zxj5khf1zxj5lcbWyxjHBxmUBgvUz3rOid4Gmca/ihbHCMfTCYa6ihvUzgvMAw5LzcK7cGOGicaGCMv0DxjUigrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','igrHDgeGzgvSzxrLzcbZDwnJzxnZzNvSBhLGktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5YzxnWB25ZzurHDgeScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5NAgfWDxmGzgf0ysa','ChvZAa','jYWkicaGicaGicaGigzVCMvPz25lzxK6icC','zxHWB3j0CW','jhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','Aw1WB3j0','jZSkcIaGlY8GuMvHzc9xCML0zsbZB3vYy2uGy29UzMLNDxjHDgLVBGOGihrOAxmUDMLLD05HBwuGpsaN','yxvKAxrdB2X1Bw5Z','ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGicaGCMvJywXJrMLLBgrZlNb1C2GOj3rVDgfSx2fTB3vUDca9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GODg90ywXbBw91BNqPoWOGicaGFq','igrHDgeGBM90igzVDw5KjYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbPzIaOzxjYB3iUy29Kzsa9pt0Gj0vsx0rvuf9ftLrswsCGFhWGzxjYB3iUzxjYBM8Gpt09ideWnJiPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLihvWzgf0Aw5Nia','lNvWzgf0zunVBxbVC2L0zsHKyxrHlcbLDMvUDenVBNrLEhqPoWOkicaGignVBNnVBguUBg9NkcC','ExL5Es1nts1Kza','jYWkicbMAwvSze5HBwu6ia','vLfRDMG','ktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','laOGignVBhvTBKzVCM1HDhm6ia','y3jLyxrL'];a0_0x45be=function(){return _0x537900;};return a0_0x45be();}module[a0_0x5ab30b(0x283)]={'createMysqlMainModuleTemplate':createMysqlMainModuleTemplate,'createMysqlModelTemplate':createMysqlModelTemplate,'createMysqlSubmoduleTemplate':createMysqlSubmoduleTemplate};