@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,3440 +1 @@
1
- /**
2
- * SQLite Database Templates
3
- * Template generator untuk SQLite database
4
- * Paritas fungsional dengan Oracle, MySQL, 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
- * Deteksi kolom text utama untuk lookup dari daftar field names
31
- */
32
- function detectTextColumn(fieldNames) {
33
- const priorities = ['name', 'nama', 'title', 'judul', 'description', 'deskripsi', 'code', 'kode'];
34
- for (const priority of priorities) {
35
- const found = fieldNames.find(f => f.toLowerCase().includes(priority));
36
- if (found) return found;
37
- }
38
- return fieldNames.find(field => field !== 'id') || fieldNames[0] || 'nama';
39
- }
40
-
41
- /**
42
- * Membuat template untuk main module SQLite
43
- */
44
- function createSqliteMainModuleTemplate(moduleName) {
45
- const moduleNameCapitalized = toPascalCase(moduleName);
46
- return `const express = require('express');
47
- const bodyParser = require('body-parser');
48
- const path = require('path');
49
- const fs = require('fs');
50
- const { v4: uuidv4 } = require('uuid');
51
- const { logger, logServerReady, logEndpointRegistered, createRequestLogger, logRequest } = require('@restforgejs/platform/src/utils/logger');
52
- const ExportHandler = require('@restforgejs/platform/src/components/handlers/export_handler');
53
- const ImportHandler = require('@restforgejs/platform/src/components/handlers/import_handler');
54
- const UploadHandler = require('@restforgejs/platform/src/components/handlers/upload_handler');
55
- const { extractExportConfigFromEndpoint, extractImportConfigFromEndpoint, extractUploadConfigFromEndpoint } = require('@restforgejs/platform/src/utils/config-extractor');
56
- const rateLimiter = require('@restforgejs/platform/src/middleware/rate-limiter');
57
- const idempotencyMiddleware = require('@restforgejs/platform/src/middleware/idempotency');
58
- const bodyOptionsMiddleware = require('@restforgejs/platform/src/middleware/body-options');
59
- const corsMiddleware = require('@restforgejs/platform/src/middleware/cors');
60
- const securityHeaders = require('@restforgejs/platform/src/middleware/security-headers');
61
-
62
- /**
63
- * Fungsi untuk mengeksekusi modul ${moduleName} (SQLite Database)
64
- * @param {Object} config - Konfigurasi untuk menjalankan modul
65
- * @param {number} config.port - Port untuk server
66
- * @param {string} config.key - API Key (opsional)
67
- * @returns {Promise<void>} Promise yang tidak pernah resolve agar server tetap berjalan
68
- */
69
- async function execute(config) {
70
- return new Promise((resolve) => {
71
- const app = express();
72
- const port = config.port || 3000;
73
- const serverAddress = config.serverAddress || '0.0.0.0';
74
- const moduleNameCapitalized = '${moduleNameCapitalized}';
75
-
76
- // Configuration options
77
- const loggingEnabled = config.logging !== false;
78
- const apiKeyRequired = !!config.key;
79
-
80
- logger.info({
81
- event: 'module_starting',
82
- module: moduleNameCapitalized,
83
- port,
84
- cors: process.env.CORS_ENABLED !== 'false',
85
- helmet: process.env.HELMET_ENABLED === 'true',
86
- logging: loggingEnabled,
87
- apiKey: apiKeyRequired
88
- }, \`Starting \${moduleNameCapitalized} module\`);
89
-
90
- // CORS middleware (konfigurasi via CORS_ENABLED dan CORS_ORIGINS di .env)
91
- app.use(corsMiddleware.middleware());
92
-
93
- // Security headers middleware (konfigurasi via HELMET_ENABLED di .env)
94
- app.use(securityHeaders.middleware());
95
-
96
- // Middleware untuk parsing JSON dengan penanganan error
97
- app.use(bodyParser.json({
98
- verify: (req, res, buf, encoding) => {
99
- if (buf.length === 0) {
100
- return;
101
- }
102
- try {
103
- JSON.parse(buf);
104
- } catch (error) {
105
- res.status(400).json({
106
- success: false,
107
- error: 'Invalid JSON payload',
108
- message: 'The payload sent is not a valid JSON format',
109
- details: error.message
110
- });
111
- throw new Error('Invalid JSON');
112
- }
113
- }
114
- }));
115
-
116
- app.use(bodyParser.urlencoded({ extended: true }));
117
-
118
- // Request logging middleware (Pino-based)
119
- app.use((req, res, next) => {
120
- req.id = req.headers['x-request-id'] || uuidv4();
121
- res.set('X-Request-ID', req.id);
122
-
123
- req.log = createRequestLogger({
124
- requestId: req.id,
125
- method: req.method,
126
- path: req.path,
127
- ip: req.ip
128
- });
129
-
130
- const startTime = process.hrtime();
131
-
132
- res.on('finish', () => {
133
- const [seconds, nanoseconds] = process.hrtime(startTime);
134
- const durationMs = parseFloat((seconds * 1000 + nanoseconds / 1e6).toFixed(2));
135
- logRequest(req, res, durationMs);
136
- });
137
-
138
- next();
139
- });
140
-
141
- // Middleware untuk validasi API key jika diperlukan
142
- if (config.key) {
143
- app.use((req, res, next) => {
144
- const apiKey = req.headers['x-api-key'];
145
- if (!apiKey || apiKey !== config.key) {
146
- return res.status(401).json({ error: 'Unauthorized: Invalid API Key' });
147
- }
148
- next();
149
- });
150
- }
151
-
152
- // Rate limiting middleware (store: memory untuk single mode, Redis untuk cluster mode)
153
- rateLimiter.setStore(config.cluster ? 'redis' : 'memory');
154
- app.use('/api', rateLimiter.middleware());
155
-
156
- // Idempotency middleware (protects mutation endpoints from duplicate execution)
157
- if (process.env.IDEMPOTENCY_ENABLED === 'true') {
158
- app.use('/api', idempotencyMiddleware.middleware());
159
- }
160
-
161
- // Body options middleware (extract {data, options} format dari request body)
162
- app.use('/api', bodyOptionsMiddleware.middleware());
163
-
164
- // Auto-load plugin (jika ada)
165
- const moduleName = '${moduleName}';
166
- const pluginPath = path.join(__dirname, '..', 'plugins', \`\${moduleName}-plugin.js\`);
167
- let plugin = null;
168
- if (fs.existsSync(pluginPath)) {
169
- try {
170
- plugin = require(pluginPath);
171
- if (plugin.onBeforeEndpointsLoad) {
172
- plugin.onBeforeEndpointsLoad(app, config);
173
- }
174
- logger.info({ event: 'plugin_loaded', plugin: \`\${moduleName}-plugin\` }, \`Plugin loaded: \${moduleName}-plugin.js\`);
175
- } catch (pluginError) {
176
- logger.error({ event: 'plugin_load_error', error: pluginError.message }, \`Failed to load plugin: \${moduleName}-plugin.js\`);
177
- }
178
- }
179
-
180
- // Health check endpoint
181
- app.get('/api/${moduleName}/health', (req, res) => {
182
- const healthInfo = {
183
- status: 'ok',
184
- timestamp: new Date().toISOString().replace('T', ' ').replace(/\\.\\d{3}Z$/, ''),
185
- service: '${moduleName}',
186
- uptime: process.uptime(),
187
- memory: process.memoryUsage(),
188
- system: {
189
- platform: process.platform,
190
- nodeVersion: process.version,
191
- pid: process.pid
192
- }
193
- };
194
-
195
- res.json(healthInfo);
196
- });
197
-
198
- // Muat semua rute dari folder ${moduleName}
199
- const modulesDir = path.join(__dirname, '${moduleName}');
200
-
201
- try {
202
- if (!fs.existsSync(modulesDir)) {
203
- fs.mkdirSync(modulesDir, { recursive: true });
204
- console.log(\`Directory \$\{modulesDir} created successfully\`);
205
- }
206
-
207
- const files = fs.readdirSync(modulesDir);
208
- const endpointFiles = files.filter(file => file.endsWith('.js'));
209
-
210
- if (endpointFiles.length === 0) {
211
- console.log(\`No endpoint files found in \${modulesDir}\`);
212
- console.log(\`Add endpoint files to enable API functionality\`);
213
- } else {
214
- logger.info({ event: 'endpoints_loading', count: endpointFiles.length }, \`Loading \${endpointFiles.length} endpoint(s)\`);
215
- }
216
-
217
- for (const file of endpointFiles) {
218
- try {
219
- const endpointName = path.basename(file, '.js');
220
- const endpointPath = path.join(modulesDir, file);
221
-
222
- // Clear module cache untuk development
223
- if (require.cache[endpointPath]) {
224
- delete require.cache[endpointPath];
225
- }
226
-
227
- const moduleRoutes = require(endpointPath);
228
-
229
- const endpointPrefix = \`/api/${moduleName}/\$\{endpointName}\`;
230
- app.use(endpointPrefix, moduleRoutes);
231
- logEndpointRegistered(endpointName, endpointPrefix);
232
-
233
- // Register export routes via centralized handler
234
- try {
235
- const exportConfig = extractExportConfigFromEndpoint(endpointPath);
236
- if (exportConfig) {
237
- ExportHandler.registerRoutes(app, '${moduleName}', endpointName, exportConfig);
238
- }
239
- } catch (exportError) {
240
- logger.error({ event: 'export_registration_error', endpoint: endpointName, error: exportError.message }, \`Export registration failed for \$\{endpointName}: \$\{exportError.message}\`);
241
- }
242
-
243
- // Register import routes via centralized handler
244
- try {
245
- const importConfig = extractImportConfigFromEndpoint(endpointPath);
246
- if (importConfig) {
247
- ImportHandler.registerRoutes(app, '${moduleName}', endpointName, importConfig);
248
- logger.info({ event: 'import_routes_registered', endpoint: endpointName }, \`Import routes registered for \$\{endpointName}\`);
249
- }
250
- } catch (importError) {
251
- logger.error({ event: 'import_registration_error', endpoint: endpointName, error: importError.message }, \`Import registration failed for \$\{endpointName}: \$\{importError.message}\`);
252
- }
253
-
254
- // Register upload routes via centralized handler
255
- try {
256
- const uploadConfig = extractUploadConfigFromEndpoint(endpointPath);
257
- if (uploadConfig) {
258
- UploadHandler.registerRoutes(app, '${moduleName}', endpointName, uploadConfig);
259
- logger.info({ event: 'upload_routes_registered', endpoint: endpointName, fields: Object.keys(uploadConfig.fields || {}) }, \`Upload routes registered for \$\{endpointName}\`);
260
- }
261
- } catch (uploadError) {
262
- logger.error({ event: 'upload_registration_error', endpoint: endpointName, error: uploadError.message }, \`Upload registration failed for \$\{endpointName}: \$\{uploadError.message}\`);
263
- }
264
- } catch (error) {
265
- console.error(\`Error loading module \$\{file} from ${moduleName}:\`, error);
266
- throw error;
267
- }
268
- }
269
-
270
- // Register export cleanup route
271
- try {
272
- ExportHandler.registerCleanupRoute(app);
273
- } catch (cleanupError) {
274
- console.error('Export cleanup route registration failed:', cleanupError.message);
275
- }
276
-
277
- app.get('/', (req, res) => {
278
- res.json({
279
- message: '${moduleName} API (SQLite Database)',
280
- status: 'running',
281
- database: 'sqlite'
282
- });
283
- });
284
-
285
- // Error handling middleware
286
- app.use((err, req, res, next) => {
287
- console.error('Error:', err);
288
-
289
- if (err instanceof SyntaxError && err.status === 400 && 'body' in err && !res.headersSent) {
290
- return res.status(400).json({
291
- success: false,
292
- error: 'Invalid JSON payload',
293
- message: 'The payload sent is not a valid JSON format',
294
- details: err.message
295
- });
296
- }
297
-
298
- if (!res.headersSent) {
299
- return res.status(500).json({
300
- success: false,
301
- error: 'Internal Server Error',
302
- message: 'An error occurred on the server',
303
- details: err.message
304
- });
305
- }
306
-
307
- next(err);
308
- });
309
-
310
- app.use((req, res) => {
311
- res.status(403).json({
312
- success: false,
313
- error: 'Forbidden',
314
- message: 'Access to the requested resource is forbidden',
315
- timestamp: new Date().toISOString()
316
- });
317
- });
318
-
319
- const server = app.listen(port, serverAddress, (err) => {
320
- if (err) {
321
- console.error(\`Failed to start \${moduleNameCapitalized} server:\`, err);
322
- return;
323
- }
324
-
325
- // Determine display URL based on serverAddress
326
- const displayHost = (serverAddress === '0.0.0.0' || !serverAddress) ? 'localhost' : serverAddress;
327
-
328
- logServerReady({
329
- port,
330
- module: '${moduleName}',
331
- healthCheck: \`http://\${displayHost}:\${port}/api/${moduleName}/health\`,
332
- serviceInfo: \`http://\${displayHost}:\${port}/api/${moduleName}/info\`,
333
- baseUrl: \`http://\${displayHost}:\${port}\`
334
- });
335
- console.log('');
336
-
337
- // Execute plugin onAfterServerStart hook (jika ada)
338
- if (plugin && plugin.onAfterServerStart) {
339
- try {
340
- plugin.onAfterServerStart(app, config);
341
- } catch (pluginError) {
342
- logger.error({ event: 'plugin_after_start_error', error: pluginError.message }, 'Plugin onAfterServerStart failed');
343
- }
344
- }
345
- });
346
-
347
- process.on('SIGINT', () => {
348
- console.log('Menerima sinyal SIGINT, shutting down...');
349
- process.exit(0);
350
- });
351
-
352
- process.on('SIGTERM', () => {
353
- console.log('Menerima sinyal SIGTERM, shutting down...');
354
- process.exit(0);
355
- });
356
-
357
- process.on('uncaughtException', (error) => {
358
- console.error('Uncaught Exception:', error);
359
- });
360
-
361
- process.on('unhandledRejection', (reason, promise) => {
362
- console.error('Unhandled Rejection at:', promise, 'reason:', reason);
363
- });
364
-
365
- } catch (error) {
366
- console.error('Error saat menjalankan modul ${moduleName}:', error);
367
- resolve();
368
- }
369
- });
370
- }
371
-
372
- module.exports = { execute };`;
373
- }
374
-
375
- /**
376
- * Build SQL condition string dari defaultScope filter object.
377
- * Dijalankan di generation time — output di-hardcode ke generated module.
378
- * SQLite menyimpan boolean sebagai VARCHAR string 'true'/'false' (bukan 1/0).
379
- * @param {Object} scopeFilter - Object filter, misal { is_active: true }
380
- * @param {string} [prefix=''] - Prefix kolom (misal 'a.' atau '')
381
- * @returns {string} SQL condition string, misal "is_active = 'true'"
382
- */
383
- function buildDefaultScopeSQL(scopeFilter, prefix = '') {
384
- if (!scopeFilter || typeof scopeFilter !== 'object') return '';
385
- const conditions = [];
386
- for (const [col, val] of Object.entries(scopeFilter)) {
387
- if (typeof val === 'boolean') {
388
- conditions.push(`${prefix}${col} = '${val}'`);
389
- } else if (typeof val === 'string') {
390
- conditions.push(`${prefix}${col} = '${val.replace(/'/g, "''")}'`);
391
- } else if (typeof val === 'number') {
392
- conditions.push(`${prefix}${col} = ${val}`);
393
- }
394
- }
395
- return conditions.join(' AND ');
396
- }
397
-
398
- /**
399
- * Membuat template untuk model SQLite
400
- * @param {string} moduleName - Nama module
401
- * @param {string} endpointName - Nama endpoint
402
- * @param {Object} payload - Data payload dari JSON
403
- * @returns {string} Template model untuk SQLite
404
- */
405
- function createSqliteModelTemplate(moduleName, endpointName, payload) {
406
- const validFields = payload.fieldName.map(field => `'${field}'`).join(', ');
407
- const className = toPascalCase(endpointName);
408
- const primaryKey = payload.primaryKey || 'id';
409
- const textColumn = detectTextColumn(payload.fieldName);
410
- const timestamp = new Date().toISOString();
411
-
412
- // Build default scope SQL (generation time)
413
- const lookupScopeSQL = payload.defaultScope && payload.defaultScope.lookup
414
- ? buildDefaultScopeSQL(payload.defaultScope.lookup)
415
- : '';
416
- const readScopeSQL = payload.defaultScope && payload.defaultScope.read
417
- ? buildDefaultScopeSQL(payload.defaultScope.read)
418
- : '';
419
-
420
- // Generate dateTimeFields dari fieldValidation
421
- const dateTimeFields = {};
422
- if (payload.fieldValidation && Array.isArray(payload.fieldValidation)) {
423
- payload.fieldValidation.forEach(field => {
424
- if (['date', 'datetime', 'timestamp', 'time'].includes(field.type)) {
425
- dateTimeFields[field.name] = {
426
- type: field.type,
427
- format: (field.constraints && field.constraints.format) || 'yyyy-MM-dd'
428
- };
429
- }
430
- });
431
- }
432
-
433
- return `const BaseModel = require('@restforgejs/platform/src/models/base-model-sqlite');
434
- const db = require('@restforgejs/platform/src/utils/db-sqlite');
435
- const fs = require('fs');
436
- const path = require('path');
437
-
438
- /**
439
- * ${className} Model - SQLite Database
440
- * Generated: ${timestamp}
441
- *
442
- * Table: ${payload.tableName}
443
- * Primary Key: ${primaryKey}
444
- * Fields: ${payload.fieldName.length}
445
- * Database: SQLite
446
- */
447
- class ${className}Model extends BaseModel {
448
- /**
449
- * Constructor
450
- */
451
- constructor() {
452
- const validFields = [
453
- ${validFields}
454
- ];
455
-
456
- const datatablesWhere = ${payload.datatablesWhere ? JSON.stringify(payload.datatablesWhere) : "['kode', 'nama', 'all']"};
457
-
458
- super('${payload.tableName}', validFields);
459
-
460
- // base-model-sqlite hanya menerima 2 parameter, simpan datatablesWhere manual
461
- this.datatablesWhere = datatablesWhere;
462
-
463
- // Primary key configuration
464
- this.primaryKey = '${primaryKey}';
465
-
466
- // Read/Write source configuration
467
- this.viewName = '${payload.viewName || payload.tableName}';
468
- this.readSource = '${payload.viewName || payload.tableName}';
469
- this.writeSource = '${payload.tableName}';
470
-
471
- // Flag untuk self-documenting API (endpoint /info)
472
- this.hasViewQuery = ${!!payload.viewQuery};
473
- this.hasExportQuery = ${!!payload.exportQuery};
474
- ${Object.keys(dateTimeFields).length > 0 ? `
475
- // DateTime fields configuration dari fieldValidation
476
- this.dateTimeFields = ${JSON.stringify(dateTimeFields, null, 4).split('\n').join('\n ')};
477
- ` : ''}${payload.uploadConfig && payload.uploadConfig.fields ? `
478
- // File upload fields (TEXT/JSON columns)
479
- this.fileFields = ${JSON.stringify(Object.keys(payload.uploadConfig.fields))};
480
- ` : ''}
481
- // Field validation configuration
482
- ${(() => {
483
- if (!payload.fieldValidation || !Array.isArray(payload.fieldValidation) || payload.fieldValidation.length === 0) {
484
- return ' this.validationConfig = {}; // No field validation config';
485
- }
486
- const configEntries = payload.fieldValidation.map(field => {
487
- const constraints = JSON.stringify(field.constraints || {}, null, 6).replace(/\n/g, '\n ');
488
- return ` '${field.name}': {
489
- type: '${field.type}',
490
- constraints: ${constraints}
491
- }`;
492
- }).join(',\n');
493
- return ` this.validationConfig = {
494
- ${configEntries}
495
- }`;
496
- })()}
497
-
498
- // Model metadata
499
- this.modelMetadata = {
500
- endpointName: '${endpointName}',
501
- moduleName: '${moduleName}',
502
- tableName: '${payload.tableName}',
503
- databaseType: 'sqlite',
504
- primaryKey: '${primaryKey}',
505
- fieldCount: ${payload.fieldName.length},
506
- generated: '${timestamp}'
507
- };
508
-
509
- this.advancedQueryTemplates = this.loadAdvancedQueryTemplates();
510
- }
511
-
512
- /**
513
- * Load advanced query templates dari file
514
- */
515
- loadAdvancedQueryTemplates() {
516
- const templates = {};
517
-
518
- ${payload.advancedQueries ? Object.entries(payload.advancedQueries).map(([key, value]) => `
519
- try {
520
- if (typeof "${value}" === 'string' && "${value}".startsWith('file:')) {
521
- const relativePath = "${value}".replace('file:', '');
522
- const filePath = path.join(__dirname, relativePath);
523
-
524
- if (fs.existsSync(filePath)) {
525
- templates["${key}"] = fs.readFileSync(filePath, 'utf8');
526
- console.log(\`SQL Template ${key} loaded successfully from SQLite file\`);
527
- } else {
528
- console.error(\`SQL Template file ${key} not found: \${filePath}\`);
529
- templates["${key}"] = null;
530
- }
531
- } else {
532
- templates["${key}"] = "${value}";
533
- }
534
- } catch (error) {
535
- console.error(\`Error loading SQLite template ${key}:\`, error);
536
- templates["${key}"] = null;
537
- }`).join('') : '// No advanced queries defined'}
538
-
539
- return templates;
540
- }
541
-
542
- /**
543
- * Override getListQuery untuk SQLite syntax
544
- */
545
- getListQuery(options = {}) {
546
- let baseQuery = \`
547
- ${payload.datatablesQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
548
- \`.trim();
549
-
550
- // Convert PostgreSQL syntax to SQLite if needed
551
- baseQuery = this.convertToSqliteSQL(baseQuery);
552
-
553
- return baseQuery;
554
- }
555
-
556
- /**
557
- * Override getReadQuery untuk endpoint /read
558
- * Prioritas: viewName → viewQuery → tableName (SELECT * FROM readSource)
559
- */
560
- getReadQuery(options = {}) {
561
- if (this.viewName && this.viewName !== this.table) {
562
- return 'SELECT * FROM ' + this.viewName;
563
- }
564
- ${payload.viewQuery
565
- ? ` let baseQuery = \`
566
- ${payload.viewQuery.replace(/\$\{tableName\}/g, "${this.getTableSource('read')}")}
567
- \`.trim();
568
- baseQuery = this.convertToSqliteSQL(baseQuery);
569
- return baseQuery;`
570
- : ` return 'SELECT * FROM ' + this.readSource;`
571
- }
572
- }
573
-
574
- /**
575
- * Convert PostgreSQL SQL syntax to SQLite
576
- */
577
- convertToSqliteSQL(sql) {
578
- // ILIKE → LIKE (SQLite LIKE is case-insensitive for ASCII by default)
579
- sql = sql.replace(/\\bILIKE\\b/gi, 'LIKE');
580
- // NOW() → datetime('now')
581
- sql = sql.replace(/\\bNOW\\(\\)/gi, "datetime('now')");
582
- // CURRENT_DATE sudah sama di SQLite
583
- return sql;
584
- }
585
-
586
- /**
587
- * Override getDatatables untuk SQLite dengan pagination yang tepat
588
- * Paritas fungsional dengan Oracle, MySQL, dan PostgreSQL getDatatables
589
- */
590
- async getDatatables(options) {
591
- try {
592
- // Check cache first
593
- const cachedResult = await this.getCachedDatatables(options);
594
- if (cachedResult) return cachedResult;
595
-
596
- const {
597
- searchValue = '',
598
- searchBy = 'all',
599
- perPage = 10,
600
- start = 0,
601
- sort_columns = [],
602
- filters = {},
603
- advancedFilters = []
604
- } = options;
605
-
606
- // Resolve sort columns dengan prioritas: sort_columns > order[0][column] > default
607
- let resolvedSortColumns = sort_columns;
608
-
609
- // Fallback: cek format DataTables bawaan (order[0][column] dan order[0][dir])
610
- if ((!resolvedSortColumns || resolvedSortColumns.length === 0) &&
611
- options['order[0][column]'] !== undefined && options['order[0][dir]'] !== undefined) {
612
- const columnIndex = parseInt(options['order[0][column]']);
613
- const direction = options['order[0][dir]'];
614
-
615
- if (columnIndex >= 0 && columnIndex < this.validFields.length) {
616
- resolvedSortColumns = [{ column: this.validFields[columnIndex], direction: direction.toUpperCase() }];
617
- }
618
- }
619
-
620
- const orderClause = this.buildSortColumnsClause(resolvedSortColumns);
621
-
622
- const baseQuery = this.getListQuery(options);
623
-
624
- // Build WHERE clause (parameterized)
625
- const searchResult = this.buildWhereClause(searchValue, searchBy);
626
- let whereClauseSql = searchResult.sql;
627
- let whereParams = [...searchResult.params];
628
-
629
- // Build filter clause
630
- const filterClause = this.buildObjectFilterClause(filters);
631
- if (filterClause) {
632
- if (whereClauseSql) {
633
- whereClauseSql = \`\${whereClauseSql} AND \${filterClause}\`;
634
- } else {
635
- whereClauseSql = \`WHERE \${filterClause}\`;
636
- }
637
- }
638
-
639
- // Support WHERE conditions dari request body
640
- if (options.where) {
641
- try {
642
- const complexResult = this.buildComplexWhereClause(options.where, whereParams);
643
- if (whereClauseSql) {
644
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
645
- } else {
646
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
647
- }
648
- whereParams = complexResult.params;
649
- } catch (e) {
650
- const error = new Error('Invalid where conditions: ' + e.message);
651
- error.statusCode = 400;
652
- throw error;
653
- }
654
- }
655
-
656
- // Advanced filters support
657
- if (advancedFilters && advancedFilters.length > 0) {
658
- const advResult = this.buildAdvancedFilterCondition(advancedFilters);
659
- if (advResult.sql) {
660
- if (whereClauseSql) {
661
- whereClauseSql = \`\${whereClauseSql} AND \${advResult.sql}\`;
662
- } else {
663
- whereClauseSql = \`WHERE \${advResult.sql}\`;
664
- }
665
- whereParams.push(...advResult.params);
666
- }
667
- }
668
-
669
- // Check if query needs subquery wrapping (CTE or JOIN)
670
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
671
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
672
- const needsSubquery = isCteQuery || hasJoin;
673
-
674
- // Count total records
675
- const countTotalQuery = needsSubquery ?
676
- \`SELECT COUNT(*) as total FROM (\${baseQuery}) as base_query\` :
677
- 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
678
- const countTotalResult = await db.executeQuery(countTotalQuery);
679
- const totalRecords = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
680
-
681
- // Count filtered records
682
- let filteredRecords = totalRecords;
683
- if (whereClauseSql) {
684
- const countFilteredQuery = needsSubquery ?
685
- \`SELECT COUNT(*) as total FROM (\${baseQuery}) as base_query \${whereClauseSql}\` :
686
- 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + whereClauseSql;
687
- const countFilteredResult = await db.executeQuery(countFilteredQuery, whereParams.length > 0 ? whereParams : undefined);
688
- filteredRecords = countFilteredResult && countFilteredResult[0] ? parseInt(countFilteredResult[0].total) : 0;
689
- }
690
-
691
- // SQLite pagination using LIMIT/OFFSET
692
- const query = needsSubquery ?
693
- \`SELECT * FROM (\${baseQuery}) as base_query \${whereClauseSql || ''} \${orderClause} LIMIT \${perPage} OFFSET \${start}\` :
694
- \`\${baseQuery} \${whereClauseSql || ''} \${orderClause} LIMIT \${perPage} OFFSET \${start}\`;
695
-
696
- console.log('Final Query:', query);
697
- console.log('Query Parameters:', whereParams.length > 0 ? whereParams : []);
698
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
699
-
700
- // Format data: SQLite returns lowercase keys natively
701
- const data = rawData ? rawData.map((row, index) => {
702
- const formatted = this.formatResponseData(row);
703
- return {
704
- ...formatted,
705
- rownumerator: start + index + 1
706
- };
707
- }) : [];
708
-
709
- const result = {
710
- draw: parseInt(options.draw || '1', 10),
711
- recordsTotal: totalRecords,
712
- recordsFiltered: filteredRecords,
713
- data: data
714
- };
715
-
716
- // Cache result
717
- await this.setCachedDatatables(options, result);
718
-
719
- return result;
720
- } catch (error) {
721
- console.error('Error in getDatatables:', error);
722
- throw error;
723
- }
724
- }
725
-
726
- /**
727
- * Build WHERE clause untuk search (parameterized query)
728
- * @returns {Object} { sql: string, params: array }
729
- */
730
- buildWhereClause(searchValue, searchBy) {
731
- if (!searchValue || searchValue === '') {
732
- return { sql: '', params: [] };
733
- }
734
-
735
- const params = [];
736
- const searchPattern = \`%\${searchValue}%\`;
737
-
738
- if (searchBy === 'all') {
739
- const searchableFields = this.datatablesWhere.filter(field => field !== 'all');
740
- if (searchableFields.length > 0) {
741
- const conditions = searchableFields.map(field => {
742
- params.push(searchPattern);
743
- return \`\${field} LIKE ?\`;
744
- });
745
- return { sql: \`WHERE (\${conditions.join(' OR ')})\`, params };
746
- }
747
- } else if (this.validFields.includes(searchBy)) {
748
- params.push(searchPattern);
749
- return { sql: \`WHERE \${searchBy} LIKE ?\`, params };
750
- }
751
-
752
- return { sql: '', params: [] };
753
- }
754
-
755
- /**
756
- * Build filter clause dari object filters
757
- * @param {Object} filters - Filter object {column: value}
758
- * @returns {string} Filter conditions SQL (tanpa WHERE prefix)
759
- */
760
- buildObjectFilterClause(filters) {
761
- if (!filters || typeof filters !== 'object' || Object.keys(filters).length === 0) {
762
- return '';
763
- }
764
-
765
- const conditions = [];
766
- for (const [column, value] of Object.entries(filters)) {
767
- if (!this.validFields.includes(column)) continue;
768
- if (value === null || value === undefined || value === '' || value === 'all' || value === '-') continue;
769
-
770
- const escapedValue = value.toString().replace(/'/g, "''");
771
- conditions.push(\`\${column} = '\${escapedValue}'\`);
772
- }
773
-
774
- return conditions.length > 0 ? conditions.join(' AND ') : '';
775
- }
776
-
777
- /**
778
- * Get list data dengan pagination untuk SQLite
779
- */
780
- async getList(options) {
781
- try {
782
- // Check cache first
783
- const cachedResult = await this.getCachedList(options);
784
- if (cachedResult) {
785
- const { page: p = null, perPage: pp = 10, searchValue: sv = '', sort_columns: sc = [], where: w = null } = options;
786
- const scInfo = sc && sc.length > 0 ? sc.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
787
- console.log(\`[Cache] HIT for list - page:\${p}, perPage:\${pp}, sort:\${scInfo}, search:\${sv || 'none'}\${w ? ', where:yes' : ''}\`);
788
- return cachedResult;
789
- }
790
-
791
- const {
792
- page = null,
793
- perPage = 10,
794
- searchValue = '',
795
- searchBy = 'all',
796
- sort_columns = [],
797
- where = null,
798
- select = null,
799
- limit = 1000
800
- } = options;
801
-
802
- const paginate = page !== null;
803
- const scInfo = sort_columns && sort_columns.length > 0 ? sort_columns.map(s => \`\${s.column}:\${s.direction}\`).join(',') : 'default';
804
- const cacheInfo = \`page:\${page}, perPage:\${perPage}, sort:\${scInfo}, search:\${searchValue || 'none'}\${where ? ', where:yes' : ''}\`;
805
-
806
- console.log(\`[Cache] MISS for list - \${cacheInfo}\`);
807
-
808
- // 1. Mendapatkan query dasar
809
- let baseQuery;
810
- if (select && Array.isArray(select) && select.length > 0) {
811
- const selectedValidColumns = select.filter(col => this.validFields.includes(col));
812
- if (selectedValidColumns.length > 0) {
813
- baseQuery = 'SELECT ' + selectedValidColumns.map(col => '"' + col + '"').join(', ') + ' FROM ' + this.getTableSource('read') + ' a';
814
- } else {
815
- baseQuery = 'SELECT * FROM ' + this.getTableSource('read') + ' a';
816
- }
817
- } else {
818
- baseQuery = this.getReadQuery(options);
819
- }
820
-
821
- // Deteksi apakah query mengandung JOIN atau CTE (perlu subquery wrapping)
822
- const isCteQuery = baseQuery.toLowerCase().trim().startsWith('with');
823
- const hasJoin = /\\b(inner|left|right|cross|full)\\s+join\\b/i.test(baseQuery) || /\\bjoin\\b/i.test(baseQuery);
824
- const needsSubquery = isCteQuery || hasJoin;
825
-
826
- const searchResult = this.buildWhereClause(searchValue, searchBy);
827
- let whereClauseSql = searchResult.sql;
828
- let whereParams = [...searchResult.params];
829
- const orderClause = this.buildSortColumnsClause(sort_columns);
830
- ${readScopeSQL ? `
831
- // Default scope filter untuk read
832
- if (whereClauseSql) {
833
- whereClauseSql = \`WHERE ${readScopeSQL} AND \` + whereClauseSql.replace(/^WHERE\\s+/i, '');
834
- } else {
835
- whereClauseSql = 'WHERE ${readScopeSQL}';
836
- }
837
- ` : ''}
838
- // Support WHERE conditions dari request body
839
- if (where) {
840
- try {
841
- const complexResult = this.buildComplexWhereClause(where, whereParams);
842
- if (whereClauseSql) {
843
- whereClauseSql = \`\${whereClauseSql} AND \${complexResult.sql}\`;
844
- } else {
845
- whereClauseSql = \`WHERE \${complexResult.sql}\`;
846
- }
847
- whereParams = complexResult.params;
848
- } catch (e) {
849
- const error = new Error('Invalid where conditions: ' + e.message);
850
- error.statusCode = 400;
851
- throw error;
852
- }
853
- }
854
-
855
- // Count total unfiltered records
856
- const countTotalQuery = needsSubquery
857
- ? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query'
858
- : 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a';
859
- const countTotalResult = await db.executeQuery(countTotalQuery);
860
- const totalUnfiltered = countTotalResult && countTotalResult[0] ? parseInt(countTotalResult[0].total) : 0;
861
-
862
- // Count filtered records
863
- let totalRecords = totalUnfiltered;
864
- if (whereClauseSql) {
865
- const countQuery = needsSubquery
866
- ? 'SELECT COUNT(*) as total FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '')
867
- : 'SELECT COUNT(*) as total FROM ' + this.getTableSource('read') + ' a ' + (whereClauseSql || '');
868
- const countResult = await db.executeQuery(countQuery, whereParams.length > 0 ? whereParams : undefined);
869
- totalRecords = countResult && countResult[0] ? parseInt(countResult[0].total) : 0;
870
- }
871
-
872
- // Build query berdasarkan mode paginasi (subquery wrapping untuk JOIN/CTE)
873
- let query;
874
- if (paginate) {
875
- const offset = (page - 1) * perPage;
876
- query = needsSubquery
877
- ? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + perPage + ' OFFSET ' + offset
878
- : baseQuery + ' ' + (whereClauseSql || '') + orderClause + \` LIMIT \${perPage} OFFSET \${offset}\`;
879
- } else {
880
- query = needsSubquery
881
- ? 'SELECT * FROM (' + baseQuery + ') as base_query ' + (whereClauseSql || '') + orderClause + ' LIMIT ' + limit
882
- : baseQuery + ' ' + (whereClauseSql || '') + orderClause + \` LIMIT \${limit}\`;
883
- }
884
-
885
- console.log('List SQL Query:', query);
886
- console.log('List Query Parameters:', whereParams);
887
- const rawData = await db.executeQuery(query, whereParams.length > 0 ? whereParams : undefined);
888
-
889
- const data = rawData ? rawData.map((row) => {
890
- return this.formatResponseData(row);
891
- }) : [];
892
-
893
- const result = {
894
- success: true,
895
- data: data
896
- };
897
-
898
- if (paginate) {
899
- const totalPages = Math.ceil(totalRecords / perPage);
900
- result.pagination = {
901
- current_page: page,
902
- per_page: perPage,
903
- total_records: totalRecords,
904
- total_pages: totalPages,
905
- has_next: page < totalPages,
906
- has_previous: page > 1
907
- };
908
- }
909
-
910
- // Cache result
911
- await this.setCachedList(options, result);
912
- console.log(\`[Cache] SET for list - \${cacheInfo}\`);
913
-
914
- return result;
915
- } catch (error) {
916
- console.error('Error in getList:', error);
917
- throw error;
918
- }
919
- }
920
-
921
- /**
922
- * Override getLookupData untuk SQLite (dynamic search)
923
- */
924
- async getLookupData(search) {
925
- try {
926
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} WHERE ${textColumn} LIKE ?${lookupScopeSQL ? ` AND ${lookupScopeSQL}` : ''} ORDER BY ${textColumn} LIMIT 100\`;
927
- const params = [\`%\${search || ''}%\`];
928
- const data = await db.executeQuery(query, params);
929
-
930
- const result = data.map(item => ({
931
- id: item.${primaryKey},
932
- text: item.${textColumn}
933
- }));
934
-
935
- return result;
936
- } catch (error) {
937
- console.error('Error in SQLite getLookupData:', error);
938
- throw error;
939
- }
940
- }
941
-
942
- /**
943
- * Dynamic lookup dengan extra filters untuk SQLite
944
- */
945
- async getLookupDataDynamic(search, extraFilters = {}) {
946
- try {
947
- let params = [];
948
- let whereConditions = [];
949
- ${lookupScopeSQL ? `\n // Default scope filter\n whereConditions.push('${lookupScopeSQL}');\n` : ''}
950
- if (search) {
951
- whereConditions.push(\`${textColumn} LIKE ?\`);
952
- params.push(\`%\${search}%\`);
953
- }
954
-
955
- // Add extra filters
956
- if (extraFilters && Object.keys(extraFilters).length > 0) {
957
- for (const [key, value] of Object.entries(extraFilters)) {
958
- if (this.validFields.includes(key) && value !== null && value !== undefined) {
959
- whereConditions.push(\`\${key} = ?\`);
960
- params.push(value);
961
- }
962
- }
963
- }
964
-
965
- const whereClause = whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' AND ') : '';
966
-
967
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')} \${whereClause} ORDER BY ${textColumn} LIMIT 100\`;
968
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
969
-
970
- return data.map(item => ({
971
- id: item.${primaryKey},
972
- text: item.${textColumn}
973
- }));
974
- } catch (error) {
975
- console.error('Error in SQLite getLookupDataDynamic:', error);
976
- throw error;
977
- }
978
- }
979
-
980
- /**
981
- * Override getStaticLookupData untuk SQLite
982
- */
983
- async getStaticLookupData(selectedTag) {
984
- try {
985
- // Check cache first - cache tanpa selectedTag karena data sama
986
- const cacheOptions = { type: 'static' };
987
- const cachedResult = await this.getCachedLookup(cacheOptions, 'static');
988
- if (cachedResult) {
989
- // Apply selectedTag to cached result
990
- return cachedResult.map(item => {
991
- if (item.id === selectedTag) {
992
- return { ...item, selected: 'true' };
993
- }
994
- return { id: item.id, text: item.text };
995
- });
996
- }
997
-
998
- const query = \`SELECT ${primaryKey}, ${textColumn} FROM \${this.getTableSource('read')}${lookupScopeSQL ? ` WHERE ${lookupScopeSQL}` : ''} ORDER BY ${textColumn} LIMIT 1000\`;
999
- const data = await db.executeQuery(query);
1000
-
1001
- // Cache result tanpa selected flag
1002
- const cacheData = data.map(item => ({
1003
- id: item.${primaryKey},
1004
- text: item.${textColumn}
1005
- }));
1006
- await this.setCachedLookup(cacheOptions, cacheData, 'static');
1007
-
1008
- // Return dengan selected flag
1009
- return data.map(item => {
1010
- const row = {
1011
- id: item.${primaryKey},
1012
- text: item.${textColumn}
1013
- };
1014
- if (item.${primaryKey} === selectedTag) {
1015
- row.selected = 'true';
1016
- }
1017
- return row;
1018
- });
1019
- } catch (error) {
1020
- console.error('Error in SQLite getStaticLookupData:', error);
1021
- throw error;
1022
- }
1023
- }
1024
-
1025
- /**
1026
- * Lookup dengan advanced filter support untuk SQLite
1027
- */
1028
- async getLookupDataWithFilter(options) {
1029
- try {
1030
- // Check cache first
1031
- const cacheOptions = { ...options, type: 'filter' };
1032
- const cachedResult = await this.getCachedLookup(cacheOptions, 'filter');
1033
- if (cachedResult) return cachedResult;
1034
-
1035
- const selectColumns = options.select || ['${primaryKey}', '${textColumn}'];
1036
- let params = [];
1037
-
1038
- // Validasi text fields
1039
- const validTextFields = this.validFields.filter(field =>
1040
- field.includes('name') || field.includes('nama') ||
1041
- field.includes('code') || field.includes('kode') ||
1042
- field.includes('text') || field.includes('title')
1043
- );
1044
-
1045
- let selectClause = '${primaryKey}';
1046
- let textField = '${textColumn}';
1047
- let aliasField = null;
1048
-
1049
- // Proses setiap column dalam select
1050
- for (const column of selectColumns) {
1051
- if (column.toLowerCase() === '${primaryKey}') {
1052
- continue; // primary key sudah ada
1053
- }
1054
-
1055
- // Check jika ada SQL expression dengan alias (menggunakan AS)
1056
- const aliasRegex = new RegExp('(.+)\\\\s+as\\\\s+(\\\\w+)$', 'i');
1057
- const aliasMatch = column.match(aliasRegex);
1058
- if (aliasMatch) {
1059
- const expression = aliasMatch[1].trim();
1060
- const alias = aliasMatch[2].trim();
1061
- selectClause += \`, \${expression} AS \${alias}\`;
1062
- textField = alias;
1063
- aliasField = alias;
1064
- break;
1065
- }
1066
-
1067
- // Check jika simple field name
1068
- if (this.validFields.includes(column) || validTextFields.includes(column)) {
1069
- selectClause += \`, \${column}\`;
1070
- textField = column;
1071
- break;
1072
- }
1073
-
1074
- // Computed column
1075
- selectClause += \`, \${column}\`;
1076
- textField = column;
1077
- }
1078
-
1079
- let query = \`SELECT \${selectClause} FROM \${this.getTableSource('read')} ${lookupScopeSQL ? `WHERE ${lookupScopeSQL} ` : ''}\`;
1080
-
1081
- // Build WHERE clause jika ada
1082
- if ((options.where && Array.isArray(options.where) && options.where.length > 0) ||
1083
- (options.where && options.where.conditions && Array.isArray(options.where.conditions) && options.where.conditions.length > 0)) {
1084
- try {
1085
- const whereResult = this.buildComplexWhereClause(options.where, params);
1086
- query += \`${lookupScopeSQL ? 'AND (' : 'WHERE '}\${whereResult.sql}${lookupScopeSQL ? ')' : ''} \`;
1087
- params = whereResult.params;
1088
- } catch (e) {
1089
- const error = new Error('Invalid where conditions: ' + e.message);
1090
- error.statusCode = 400;
1091
- throw error;
1092
- }
1093
- }
1094
-
1095
- // Handle sort_columns
1096
- if (options.sort_columns && Array.isArray(options.sort_columns) && options.sort_columns.length > 0) {
1097
- const orderParts = options.sort_columns.map(item => {
1098
- const column = item.column;
1099
- const direction = (item.direction || 'ASC').toUpperCase();
1100
- if (!column) return null;
1101
- if (!this.validFields.includes(column)) return null;
1102
- if (direction !== 'ASC' && direction !== 'DESC') return null;
1103
- return \`\${column} \${direction}\`;
1104
- }).filter(Boolean);
1105
-
1106
- if (orderParts.length === 0) {
1107
- const error = new Error('No valid sort columns provided');
1108
- error.statusCode = 400;
1109
- throw error;
1110
- }
1111
- query += \`ORDER BY \${orderParts.join(', ')}\`;
1112
- } else {
1113
- query += \`ORDER BY \${aliasField || textField}\`;
1114
- }
1115
-
1116
- console.log('SQLite Lookup Filter Query:', query);
1117
- console.log('Parameters:', params);
1118
-
1119
- const data = await db.executeQuery(query, params.length > 0 ? params : undefined);
1120
-
1121
- const result = data.map(item => ({
1122
- id: item.${primaryKey},
1123
- text: item[aliasField || textField] || item.${textColumn} || ''
1124
- }));
1125
-
1126
- // Cache the result
1127
- await this.setCachedLookup(cacheOptions, result, 'filter');
1128
-
1129
- return result;
1130
- } catch (error) {
1131
- console.error('Error in getLookupDataWithFilter:', error);
1132
- throw error;
1133
- }
1134
- }
1135
-
1136
- /**
1137
- * Build advanced filter conditions untuk SQLite
1138
- * @param {Array} filters - Array of {column, type, value, value2}
1139
- * @returns {Object} {sql, params}
1140
- */
1141
- buildAdvancedFilterCondition(filters) {
1142
- if (!filters || !Array.isArray(filters) || filters.length === 0) {
1143
- return { sql: '', params: [] };
1144
- }
1145
-
1146
- const conditions = [];
1147
- const params = [];
1148
-
1149
- for (const filter of filters) {
1150
- const { column, type, value, value2 } = filter;
1151
-
1152
- if (!column || !this.validFields.includes(column)) continue;
1153
-
1154
- switch (type) {
1155
- case 'equals':
1156
- conditions.push(\`\${column} = ?\`);
1157
- params.push(value);
1158
- break;
1159
- case 'not_equals':
1160
- conditions.push(\`\${column} <> ?\`);
1161
- params.push(value);
1162
- break;
1163
- case 'contains':
1164
- case 'like':
1165
- conditions.push(\`\${column} LIKE ?\`);
1166
- params.push(\`%\${value}%\`);
1167
- break;
1168
- case 'not_contains':
1169
- case 'not_like':
1170
- conditions.push(\`\${column} NOT LIKE ?\`);
1171
- params.push(\`%\${value}%\`);
1172
- break;
1173
- case 'starts_with':
1174
- conditions.push(\`\${column} LIKE ?\`);
1175
- params.push(\`\${value}%\`);
1176
- break;
1177
- case 'ends_with':
1178
- conditions.push(\`\${column} LIKE ?\`);
1179
- params.push(\`%\${value}\`);
1180
- break;
1181
- case 'greater_than':
1182
- conditions.push(\`\${column} > ?\`);
1183
- params.push(value);
1184
- break;
1185
- case 'less_than':
1186
- conditions.push(\`\${column} < ?\`);
1187
- params.push(value);
1188
- break;
1189
- case 'greater_equal':
1190
- conditions.push(\`\${column} >= ?\`);
1191
- params.push(value);
1192
- break;
1193
- case 'less_equal':
1194
- conditions.push(\`\${column} <= ?\`);
1195
- params.push(value);
1196
- break;
1197
- case 'between':
1198
- conditions.push(\`\${column} BETWEEN ? AND ?\`);
1199
- params.push(value, value2);
1200
- break;
1201
- case 'in':
1202
- if (Array.isArray(value)) {
1203
- const inPlaceholders = value.map(() => '?').join(', ');
1204
- conditions.push(\`\${column} IN (\${inPlaceholders})\`);
1205
- params.push(...value);
1206
- }
1207
- break;
1208
- case 'not_in':
1209
- if (Array.isArray(value)) {
1210
- const notInPlaceholders = value.map(() => '?').join(', ');
1211
- conditions.push(\`\${column} NOT IN (\${notInPlaceholders})\`);
1212
- params.push(...value);
1213
- }
1214
- break;
1215
- case 'is_null':
1216
- conditions.push(\`\${column} IS NULL\`);
1217
- break;
1218
- case 'is_not_null':
1219
- conditions.push(\`\${column} IS NOT NULL\`);
1220
- break;
1221
- case 'date_equals':
1222
- conditions.push(\`date(\${column}) = ?\`);
1223
- params.push(value);
1224
- break;
1225
- case 'date_between':
1226
- conditions.push(\`date(\${column}) BETWEEN ? AND ?\`);
1227
- params.push(value, value2);
1228
- break;
1229
- case 'date_after':
1230
- conditions.push(\`date(\${column}) > ?\`);
1231
- params.push(value);
1232
- break;
1233
- case 'date_before':
1234
- conditions.push(\`date(\${column}) < ?\`);
1235
- params.push(value);
1236
- break;
1237
- default:
1238
- break;
1239
- }
1240
- }
1241
-
1242
- if (conditions.length === 0) {
1243
- return { sql: '', params: [] };
1244
- }
1245
-
1246
- return { sql: conditions.join(' AND '), params };
1247
- }
1248
-
1249
- /**
1250
- * Escape value untuk SQLite SQL (sanitization)
1251
- */
1252
- escapeValue(value) {
1253
- if (value === null || value === undefined) return null;
1254
- if (typeof value === 'number') return value;
1255
- return String(value).replace(/'/g, "''");
1256
- }
1257
-
1258
- /**
1259
- * Validasi data sebelum insert/update
1260
- */
1261
- async validateData(data, operation = 'insert') {
1262
- const result = {
1263
- isValid: true,
1264
- errors: [],
1265
- warnings: [],
1266
- sanitizedData: {}
1267
- };
1268
-
1269
- try {
1270
- const hasFieldValidation = this.validationConfig && Object.keys(this.validationConfig).length > 0;
1271
-
1272
- if (hasFieldValidation) {
1273
- // Loop semua field yang ada di validationConfig
1274
- for (const fieldName in this.validationConfig) {
1275
- let value = data[fieldName];
1276
- const config = this.validationConfig[fieldName];
1277
- const constraints = config.constraints || {};
1278
-
1279
- // Auto-generate value jika autoGenerate dan nilai kosong.
1280
- // String dan uuid diperlakukan sama: UUID v7 via uuid package
1281
- // (konsisten lintas dialect; cocok dengan konvensi payload category.json
1282
- // yang memakai type: "string" dengan constraint autoGenerate + primaryKey).
1283
- if (operation === 'insert' && constraints.autoGenerate && (!value || value === '')) {
1284
- if (config.type === 'uuid' || config.type === 'string') {
1285
- value = require('uuid').v7();
1286
- data[fieldName] = value;
1287
- }
1288
- }
1289
-
1290
- // Skip validation jika value kosong dan tidak required
1291
- if (value === undefined || value === null || value === '') {
1292
- if (constraints.required) {
1293
- // Skip: autoGenerate atau primaryKey di insert
1294
- if (operation === 'insert' && (constraints.autoGenerate || constraints.primaryKey)) {
1295
- // OK — akan di-generate otomatis
1296
- }
1297
- // Skip: update partial — field tidak dikirim berarti tidak diubah
1298
- else if (operation === 'update' && value === undefined) {
1299
- // OK — field tidak sedang di-update
1300
- }
1301
- else {
1302
- const message = constraints.requiredMessage || \`Field '\${fieldName}' is required\`;
1303
- result.errors.push(message);
1304
- result.isValid = false;
1305
- }
1306
- }
1307
- continue;
1308
- }
1309
-
1310
- // String field: hash constraint support
1311
- if (config.type === 'string' && typeof value === 'string') {
1312
- let sanitized = value;
1313
- const fieldErrors = [];
1314
- const isHashField = constraints.hash === 'bcrypt';
1315
-
1316
- // Trim
1317
- if (constraints.trim) {
1318
- sanitized = sanitized.trim();
1319
- }
1320
-
1321
- // Case transformation (skip jika hash field)
1322
- if (!isHashField) {
1323
- if (constraints.lowercase) {
1324
- sanitized = sanitized.toLowerCase();
1325
- } else if (constraints.uppercase) {
1326
- sanitized = sanitized.toUpperCase();
1327
- }
1328
- }
1329
-
1330
- // Length validation (validasi plaintext sebelum hash)
1331
- if (constraints.minLength && sanitized.length < constraints.minLength) {
1332
- fieldErrors.push(constraints.minLengthMessage || \`Field '\${fieldName}' must be at least \${constraints.minLength} characters\`);
1333
- }
1334
- if (constraints.maxLength && !isHashField && sanitized.length > constraints.maxLength) {
1335
- fieldErrors.push(constraints.maxLengthMessage || \`Field '\${fieldName}' must not exceed \${constraints.maxLength} characters\`);
1336
- }
1337
-
1338
- // Pattern validation
1339
- if (constraints.pattern) {
1340
- const regex = new RegExp(constraints.pattern);
1341
- if (!regex.test(sanitized)) {
1342
- fieldErrors.push(constraints.patternMessage || \`Field '\${fieldName}' does not match required pattern\`);
1343
- }
1344
- }
1345
-
1346
- // Format validation
1347
- if (constraints.format === 'email' && !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(sanitized)) {
1348
- fieldErrors.push(constraints.formatMessage || \`Field '\${fieldName}' has invalid email format\`);
1349
- }
1350
-
1351
- if (fieldErrors.length > 0) {
1352
- result.isValid = false;
1353
- result.errors.push(...fieldErrors);
1354
- }
1355
-
1356
- // Hash transformation (setelah semua validation pass)
1357
- if (isHashField && fieldErrors.length === 0) {
1358
- const bcrypt = require('bcrypt');
1359
- const cost = constraints.hashCost || 10;
1360
- sanitized = await bcrypt.hash(sanitized, cost);
1361
- }
1362
-
1363
- result.sanitizedData[fieldName] = sanitized;
1364
- } else {
1365
- // Non-string field: basic sanitization
1366
- result.sanitizedData[fieldName] = value;
1367
- }
1368
- }
1369
-
1370
- // Validate field yang tidak ada di validationConfig (backward compatibility)
1371
- for (const field of this.validFields) {
1372
- if (!this.validationConfig[field] && data[field] !== undefined && data[field] !== null) {
1373
- if (typeof data[field] === 'string') {
1374
- result.sanitizedData[field] = data[field].trim().replace(/\\0/g, '').substring(0, 4000);
1375
- } else {
1376
- result.sanitizedData[field] = data[field];
1377
- }
1378
- }
1379
- }
1380
- } else {
1381
- // Fallback: Tidak ada fieldValidation - gunakan generic sanitization
1382
- for (const field of this.validFields) {
1383
- const value = data[field];
1384
- if (value !== undefined && value !== null) {
1385
- if (typeof value === 'string') {
1386
- result.sanitizedData[field] = value.trim().replace(/\\0/g, '').substring(0, 4000);
1387
- } else {
1388
- result.sanitizedData[field] = value;
1389
- }
1390
- }
1391
- }
1392
- }
1393
- } catch (error) {
1394
- result.errors.push(\`Validation error: \${error.message}\`);
1395
- result.isValid = false;
1396
- }
1397
-
1398
- return result;
1399
- }
1400
-
1401
- /**
1402
- * Get field mapping information
1403
- */
1404
- getFieldMapping() {
1405
- return {
1406
- allFields: this.validFields,
1407
- primaryKey: this.primaryKey,
1408
- textFields: this.validFields.filter(f => f.includes('name') || f.includes('nama') || f.includes('description')),
1409
- dateFields: this.validFields.filter(f => f.includes('date') || f.includes('time')),
1410
- numericFields: this.validFields.filter(f => f.includes('amount') || f.includes('price') || f.includes('count'))
1411
- };
1412
- }
1413
-
1414
- /**
1415
- * Get SQLite connection info untuk health check
1416
- */
1417
- async getConnectionInfo() {
1418
- try {
1419
- const result = await db.executeQuery('SELECT 1 as test_con');
1420
- if (result && result.length > 0) {
1421
- return { connected: true, database: 'sqlite', retrievedAt: new Date().toISOString() };
1422
- }
1423
- return null;
1424
- } catch (error) {
1425
- return { connected: false, error: error.message, checkedAt: new Date().toISOString() };
1426
- }
1427
- }
1428
- ${(() => {
1429
- const isMasterDetail = payload.masterDetail && payload.masterDetail.enabled;
1430
- const actions = payload.action || {};
1431
- if (!isMasterDetail || (!actions.createComposite && !actions.updateComposite && !actions.readComposite)) return '';
1432
-
1433
- const detailTable = payload.masterDetail.detailTable;
1434
- const detailTableName = detailTable.split('.').pop();
1435
- const foreignKey = payload.masterDetail.foreignKey;
1436
- const detailPrimaryKey = payload.masterDetail.detailConfig?.primaryKey || `${detailTableName}_id`;
1437
- const headerCalculations = payload.masterDetail.headerCalculations || null;
1438
- const autoCalculateFields = payload.masterDetail.detailConfig?.autoCalculateFields || {};
1439
- const detailQuery = payload.masterDetail.detailConfig?.detailQuery || null;
1440
-
1441
- // Separate autoCalculateFields into "generated" (skip from SQL) vs "calculated" (compute & include in SQL)
1442
- const generatedFieldNames = [];
1443
- const calculatedFieldsConfig = [];
1444
- for (const [fieldName, config] of Object.entries(autoCalculateFields)) {
1445
- if (config.type === 'generated') {
1446
- generatedFieldNames.push(fieldName);
1447
- } else {
1448
- // default → "calculated"
1449
- const parts = (config.formula || '').split('*').map(s => s.trim());
1450
- calculatedFieldsConfig.push({ fieldName, qtyField: parts[0], priceField: parts[1] });
1451
- }
1452
- }
1453
-
1454
- // Extract qty and price field names for header recalculation
1455
- const amountFormula = autoCalculateFields.total_amount?.formula || '';
1456
- const amountFormulaParts = amountFormula.split('*').map(s => s.trim());
1457
- const headerCalcQtyField = headerCalculations?.total_qty?.source?.replace('items.', '') || '';
1458
- const amountQtyField = amountFormulaParts[0] || headerCalcQtyField;
1459
- const amountPriceField = amountFormulaParts[1] || 'unit_price';
1460
-
1461
- // Build calculation code block for "calculated" fields (runtime code)
1462
- const calcBlockCode = calculatedFieldsConfig.map(cf => {
1463
- return ` // Auto-calculate ${cf.fieldName} = ${cf.qtyField} * ${cf.priceField}\n item.${cf.fieldName} = (Number(item.${cf.qtyField}) || 0) * (Number(item.${cf.priceField}) || 0);`;
1464
- }).join('\n');
1465
-
1466
- let code = '';
1467
-
1468
- // createComposite
1469
- if (actions.createComposite) {
1470
- code += `
1471
- /**
1472
- * Composite create - Create header with detail items in a single transaction (SQLite)
1473
- */
1474
- async createComposite(data, eventContext = null) {
1475
- try {
1476
- // --- Hook: onBeforeCompositeInsert ---
1477
- if (eventContext && eventContext.componentEngine) {
1478
- var _ce = eventContext.componentEngine;
1479
- var _CB = eventContext.ContextBuilder;
1480
- var _detailItems = data['${detailTableName}'] || [];
1481
- var _headerData = { ...data };
1482
- delete _headerData['${detailTableName}'];
1483
- var _beforeCtx = _CB.buildCompositeInsertBeforeContext(_headerData, _detailItems, {
1484
- tableName: '${payload.tableName}',
1485
- detailTable: '${detailTable}',
1486
- foreignKey: '${foreignKey}',
1487
- ...(eventContext.additionalContext || {})
1488
- });
1489
- var _beforeResult = await _ce.executeOnBeforeComposite('insert', _beforeCtx);
1490
- if (!_beforeResult.success) {
1491
- throw new Error('onBeforeCompositeInsert failed: ' + _beforeResult.error);
1492
- }
1493
- }
1494
-
1495
- const detailKey = '${detailTableName}';
1496
- const headerData = { ...data };
1497
- delete headerData[detailKey];
1498
-
1499
- // Build header INSERT with RETURNING
1500
- const headerFields = [];
1501
- const headerValues = [];
1502
- const headerPlaceholders = [];
1503
- for (const [key, value] of Object.entries(headerData)) {
1504
- if (value !== undefined && value !== null) {
1505
- headerFields.push(key);
1506
- headerValues.push(value);
1507
- headerPlaceholders.push('?');
1508
- }
1509
- }
1510
-
1511
- // Inject audit columns (created_at, created_by, updated_at, updated_by) via helper
1512
- this._appendCreateAuditColumns(headerFields, headerValues, headerPlaceholders, headerData, eventContext);
1513
-
1514
- const insertSql = 'INSERT INTO ' + this.writeSource + ' (' + headerFields.join(', ') + ') VALUES (' + headerPlaceholders.join(', ') + ') RETURNING *';
1515
- console.log('Executing header INSERT:', { query: insertSql, values: headerValues });
1516
- const headerRows = await db.executeQuery(insertSql, headerValues);
1517
- const insertedHeader = headerRows[0];
1518
- const masterPkValue = headerData[this.primaryKey];
1519
-
1520
- console.log('Header inserted successfully: ' + this.primaryKey + '=' + masterPkValue);
1521
-
1522
- // Insert detail items
1523
- const insertedItems = [];
1524
- const detailTableFull = '${detailTable}';
1525
- const fk = '${foreignKey}';
1526
- const detailPk = '${detailPrimaryKey}';
1527
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1528
-
1529
- for (const item of data[detailKey] || []) {
1530
- item[fk] = masterPkValue;
1531
-
1532
- // Auto-generate UUID untuk detail PK bila client tidak menyupply.
1533
- // Konsisten dengan konvensi autoGenerate+primaryKey di single-table
1534
- // dan seragam lintas dialect (MySQL/Oracle/PG/SQLite): semua memakai
1535
- // uuid.v7() sehingga format UUID dan titik generasi identik.
1536
- if (item[detailPk] === undefined || item[detailPk] === null) {
1537
- item[detailPk] = require('uuid').v7();
1538
- }
1539
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1540
- const detailFields = [];
1541
- const detailValues = [];
1542
- const detailPlaceholders = [];
1543
-
1544
- for (const [key, value] of Object.entries(item)) {
1545
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1546
- if (value !== undefined && value !== null) {
1547
- detailFields.push(key);
1548
- detailValues.push(value);
1549
- detailPlaceholders.push('?');
1550
- }
1551
- }
1552
-
1553
- // Inject audit columns ke detail INSERT via helper
1554
- this._appendCreateAuditColumns(detailFields, detailValues, detailPlaceholders, item, eventContext);
1555
-
1556
- const detailInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + detailFields.join(', ') + ') VALUES (' + detailPlaceholders.join(', ') + ') RETURNING *';
1557
- console.log('Executing detail INSERT:', { query: detailInsertSql, values: detailValues });
1558
- const detailRows = await db.executeQuery(detailInsertSql, detailValues);
1559
- if (detailRows[0]) {
1560
- insertedItems.push(detailRows[0]);
1561
- }
1562
- }
1563
-
1564
- console.log('Inserted ' + insertedItems.length + ' detail item(s)');
1565
-
1566
- // --- Hook: onAfterCompositeInsert ---
1567
- if (eventContext && eventContext.componentEngine) {
1568
- var _ce2 = eventContext.componentEngine;
1569
- var _CB2 = eventContext.ContextBuilder;
1570
- var _afterCtx = _CB2.buildCompositeInsertAfterContext(headerData, insertedHeader, insertedItems, {
1571
- tableName: '${payload.tableName}',
1572
- detailTable: '${detailTable}',
1573
- foreignKey: '${foreignKey}',
1574
- primaryKey: this.primaryKey,
1575
- ...(eventContext.additionalContext || {})
1576
- });
1577
- var _afterResult = await _ce2.executeOnAfterComposite('insert', _afterCtx);
1578
- if (!_afterResult.success) {
1579
- throw new Error('onAfterCompositeInsert failed: ' + _afterResult.error);
1580
- }
1581
- }
1582
-
1583
- console.log('Composite create completed successfully');
1584
-
1585
- // Invalidate cache setelah write operation berhasil
1586
- await this.invalidateCache();
1587
-
1588
- return {
1589
- ...insertedHeader,
1590
- [detailKey]: insertedItems
1591
- };
1592
- } catch (error) {
1593
- console.error('Error in createComposite:', error);
1594
- throw error;
1595
- }
1596
- }
1597
- `;
1598
- }
1599
-
1600
- // updateComposite
1601
- if (actions.updateComposite) {
1602
- code += `
1603
- /**
1604
- * Composite update - Update header with granular detail operations (SQLite)
1605
- */
1606
- async updateComposite(data, eventContext = null) {
1607
- try {
1608
- const primaryKeyValue = data[this.primaryKey];
1609
- if (!primaryKeyValue) {
1610
- throw new Error('Primary key ' + this.primaryKey + ' is required for update');
1611
- }
1612
-
1613
- // Check if record exists (also serves as prefetch oldData for hooks)
1614
- const checkSql = 'SELECT * FROM ' + this.writeSource + ' WHERE ' + this.primaryKey + ' = ?';
1615
- const checkRows = await db.executeQuery(checkSql, [primaryKeyValue]);
1616
- if (!checkRows || checkRows.length === 0) {
1617
- throw new Error('Record not found');
1618
- }
1619
-
1620
- const oldData = checkRows[0];
1621
-
1622
- // Extract header data
1623
- const headerData = { ...data };
1624
- const detailKey = '${detailTableName}';
1625
- delete headerData[detailKey];
1626
- delete headerData[this.primaryKey];
1627
-
1628
- // --- Hook: onBeforeCompositeUpdate ---
1629
- if (eventContext && eventContext.componentEngine) {
1630
- var _ce = eventContext.componentEngine;
1631
- var _CB = eventContext.ContextBuilder;
1632
- var _detailOps = data[detailKey] || {};
1633
- var _beforeCtx = _CB.buildCompositeUpdateBeforeContext(headerData, oldData, {
1634
- insert: _detailOps.insert || [],
1635
- update: _detailOps.update || [],
1636
- delete: _detailOps.delete || []
1637
- }, {
1638
- tableName: '${payload.tableName}',
1639
- detailTable: '${detailTable}',
1640
- foreignKey: '${foreignKey}',
1641
- primaryKey: this.primaryKey,
1642
- ...(eventContext.additionalContext || {})
1643
- });
1644
- var _beforeResult = await _ce.executeOnBeforeComposite('update', _beforeCtx);
1645
- if (!_beforeResult.success) {
1646
- throw new Error('onBeforeCompositeUpdate failed: ' + _beforeResult.error);
1647
- }
1648
- }
1649
-
1650
- // Build header UPDATE with RETURNING
1651
- const headerFields = [];
1652
- const headerValues = [];
1653
- for (const [key, value] of Object.entries(headerData)) {
1654
- if (value !== undefined && value !== null) {
1655
- headerFields.push(key + ' = ?');
1656
- headerValues.push(value);
1657
- }
1658
- }
1659
-
1660
- // Inject audit columns (updated_at, updated_by) via helper
1661
- this._appendUpdateAuditColumns(headerFields, headerValues, headerData, eventContext);
1662
-
1663
- headerValues.push(primaryKeyValue);
1664
- const updateSql = 'UPDATE ' + this.writeSource + ' SET ' + headerFields.join(', ') + ' WHERE ' + this.primaryKey + ' = ? RETURNING *';
1665
- console.log('Executing header UPDATE:', { query: updateSql, values: headerValues });
1666
- const headerRows = await db.executeQuery(updateSql, headerValues);
1667
- let updatedHeader = headerRows[0];
1668
-
1669
- // Process detail items
1670
- const detailTableFull = '${detailTable}';
1671
- const fk = '${foreignKey}';
1672
- const detailPk = '${detailPrimaryKey}';
1673
- ${generatedFieldNames.length > 0 ? `const generatedFields = ${JSON.stringify(generatedFieldNames)};` : ''}
1674
-
1675
- const detailOperations = data[detailKey] || {};
1676
- const { insert: insertItems = [], update: updateItems = [], delete: deleteItems = [] } = detailOperations;
1677
-
1678
- const deletedItems = [];
1679
- const updatedItems = [];
1680
- const insertedItems = [];
1681
-
1682
- // 1. DELETE operations
1683
- for (const item of deleteItems) {
1684
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in delete operation');
1685
- const delSql = 'DELETE FROM ' + detailTableFull + ' WHERE ' + detailPk + ' = ?';
1686
- await db.executeQuery(delSql, [item[detailPk]]);
1687
- deletedItems.push(item);
1688
- }
1689
- console.log('Deleted ' + deletedItems.length + ' detail item(s)');
1690
-
1691
- // 2. UPDATE operations
1692
- for (const item of updateItems) {
1693
- if (!item[detailPk]) throw new Error('Missing ' + detailPk + ' in update operation');
1694
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1695
- const dFields = [];
1696
- const dValues = [];
1697
- for (const [key, value] of Object.entries(item)) {
1698
- if (key === detailPk) continue;
1699
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1700
- if (value !== undefined && value !== null) {
1701
- dFields.push(key + ' = ?');
1702
- dValues.push(value);
1703
- }
1704
- }
1705
-
1706
- // Inject audit columns (updated_at, updated_by) ke detail UPDATE via helper
1707
- this._appendUpdateAuditColumns(dFields, dValues, item, eventContext);
1708
-
1709
- dValues.push(item[detailPk]);
1710
- const dUpdateSql = 'UPDATE ' + detailTableFull + ' SET ' + dFields.join(', ') + ' WHERE ' + detailPk + ' = ?';
1711
- await db.executeQuery(dUpdateSql, dValues);
1712
- updatedItems.push(item);
1713
- }
1714
- console.log('Updated ' + updatedItems.length + ' detail item(s)');
1715
-
1716
- // 3. INSERT operations
1717
- for (const item of insertItems) {
1718
- item[fk] = primaryKeyValue;
1719
-
1720
- // Auto-generate UUID untuk detail PK bila client tidak menyupply
1721
- // (konsisten dengan createComposite dan konvensi autoGenerate)
1722
- if (item[detailPk] === undefined || item[detailPk] === null) {
1723
- item[detailPk] = require('uuid').v7();
1724
- }
1725
- ${calcBlockCode ? '\n' + calcBlockCode + '\n' : ''}
1726
- const dFields = [];
1727
- const dValues = [];
1728
- const dPlaceholders = [];
1729
- for (const [key, value] of Object.entries(item)) {
1730
- ${generatedFieldNames.length > 0 ? `if (generatedFields.includes(key)) continue;` : ''}
1731
- if (value !== undefined && value !== null) {
1732
- dFields.push(key);
1733
- dValues.push(value);
1734
- dPlaceholders.push('?');
1735
- }
1736
- }
1737
-
1738
- // Inject audit columns ke detail INSERT via helper
1739
- this._appendCreateAuditColumns(dFields, dValues, dPlaceholders, item, eventContext);
1740
-
1741
- const dInsertSql = 'INSERT INTO ' + detailTableFull + ' (' + dFields.join(', ') + ') VALUES (' + dPlaceholders.join(', ') + ')';
1742
- await db.executeQuery(dInsertSql, dValues);
1743
- insertedItems.push(item);
1744
- }
1745
- console.log('Inserted ' + insertedItems.length + ' new detail item(s)');
1746
-
1747
- // Get all current detail items
1748
- const allItemsSql = 'SELECT * FROM ' + detailTableFull + ' WHERE ' + fk + ' = ? ORDER BY line_number';
1749
- const allItems = await db.executeQuery(allItemsSql, [primaryKeyValue]);
1750
- ${headerCalculations ? `
1751
- // Recalculate header totals
1752
- const calculations = ${JSON.stringify(headerCalculations)};
1753
- const recalcFields = [];
1754
- const recalcValues = [];
1755
-
1756
- if (calculations.total_items) {
1757
- recalcFields.push('total_items = ?');
1758
- recalcValues.push(allItems.length);
1759
- }
1760
- if (calculations.total_qty && calculations.total_qty.source) {
1761
- const qtyField = calculations.total_qty.source.replace('items.', '');
1762
- const totalQty = allItems.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
1763
- recalcFields.push('total_qty = ?');
1764
- recalcValues.push(totalQty);
1765
- }
1766
- ${amountQtyField ? `if (calculations.total_amount) {
1767
- const totalAmount = allItems.reduce(function(sum, item) {
1768
- var qty = Number(item.${amountQtyField}) || 0;
1769
- var price = Number(item.${amountPriceField}) || 0;
1770
- return sum + (qty * price);
1771
- }, 0);
1772
- recalcFields.push('total_amount = ?');
1773
- recalcValues.push(totalAmount);
1774
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
1775
-
1776
- if (recalcFields.length > 0) {
1777
- // Inject audit columns (updated_at, updated_by) ke recalc UPDATE via helper
1778
- this._appendUpdateAuditColumns(recalcFields, recalcValues, data, eventContext);
1779
- recalcValues.push(primaryKeyValue);
1780
- var recalcSql = 'UPDATE ' + this.writeSource + ' SET ' + recalcFields.join(', ') + ' WHERE ' + this.primaryKey + ' = ? RETURNING *';
1781
- console.log('Recalculating header totals:', { query: recalcSql, values: recalcValues });
1782
- var recalcRows = await db.executeQuery(recalcSql, recalcValues);
1783
- updatedHeader = recalcRows[0];
1784
- }
1785
- ` : ''}
1786
- // --- Hook: onAfterCompositeUpdate ---
1787
- if (eventContext && eventContext.componentEngine) {
1788
- var _ce2 = eventContext.componentEngine;
1789
- var _CB2 = eventContext.ContextBuilder;
1790
- var _afterCtx = _CB2.buildCompositeUpdateAfterContext(headerData, oldData, updatedHeader, {
1791
- inserted: insertedItems,
1792
- updated: updatedItems,
1793
- deleted: deletedItems
1794
- }, {
1795
- tableName: '${payload.tableName}',
1796
- detailTable: '${detailTable}',
1797
- foreignKey: '${foreignKey}',
1798
- primaryKey: this.primaryKey,
1799
- ...(eventContext.additionalContext || {})
1800
- });
1801
- var _afterResult = await _ce2.executeOnAfterComposite('update', _afterCtx);
1802
- if (!_afterResult.success) {
1803
- throw new Error('onAfterCompositeUpdate failed: ' + _afterResult.error);
1804
- }
1805
- }
1806
-
1807
- console.log('Composite update completed successfully');
1808
-
1809
- // Invalidate cache setelah write operation berhasil
1810
- await this.invalidateCache();
1811
-
1812
- return {
1813
- ...updatedHeader,
1814
- [detailKey]: allItems,
1815
- _operations: {
1816
- deleted: deletedItems.length,
1817
- updated: updatedItems.length,
1818
- inserted: insertedItems.length
1819
- }
1820
- };
1821
- } catch (error) {
1822
- console.error('Error in updateComposite:', error);
1823
- throw error;
1824
- }
1825
- }
1826
- `;
1827
- }
1828
-
1829
- // readComposite
1830
- if (actions.readComposite) {
1831
- // Build detail query loader code yang support prefix `file:` maupun SQL literal
1832
- let sqliteDetailLoaderCode;
1833
- if (detailQuery && typeof detailQuery === 'string' && detailQuery.startsWith('file:')) {
1834
- const relativePath = detailQuery.replace('file:', '');
1835
- const fileName = relativePath.split('/').pop();
1836
- sqliteDetailLoaderCode = `
1837
- // Load detail query dari file lokal
1838
- let detailSql;
1839
- try {
1840
- const detailQueryFilePath = path.join(__dirname, 'query', '${fileName}');
1841
- if (fs.existsSync(detailQueryFilePath)) {
1842
- detailSql = fs.readFileSync(detailQueryFilePath, 'utf8').trim();
1843
- } else {
1844
- throw new Error(\`Detail query file not found: \${detailQueryFilePath}\`);
1845
- }
1846
- } catch (error) {
1847
- throw new Error('Failed to load detail query file: ' + error.message);
1848
- }`;
1849
- } else if (detailQuery) {
1850
- sqliteDetailLoaderCode = `const detailSql = \`${detailQuery.replace(/\$1/g, '?')}\`;`;
1851
- } else {
1852
- sqliteDetailLoaderCode = `const detailSql = 'SELECT * FROM ${detailTable} WHERE ${foreignKey} = ? ORDER BY line_number';`;
1853
- }
1854
-
1855
- code += `
1856
- /**
1857
- * Composite read - Read header with detail items (SQLite)
1858
- */
1859
- async readComposite(options) {
1860
- try {
1861
- if (!options.where) {
1862
- throw new Error('Invalid request format: where parameter is required');
1863
- }
1864
-
1865
- let whereClauseResult;
1866
- try {
1867
- whereClauseResult = this.buildComplexWhereClause(options.where);
1868
- } catch (e) {
1869
- const error = new Error('Invalid where conditions: ' + e.message);
1870
- error.statusCode = 400;
1871
- throw error;
1872
- }
1873
- const { sql: whereClause, params } = whereClauseResult;
1874
- const headerSql = 'SELECT * FROM ' + this.getTableSource('read') + ' WHERE ' + whereClause;
1875
- const headerResults = await db.executeQuery(headerSql, params);
1876
-
1877
- if (!headerResults || headerResults.length === 0) {
1878
- return { success: true, count: 0, data: [] };
1879
- }
1880
-
1881
- const compositeResults = [];
1882
- const detailKey = '${detailTableName}';
1883
- ${sqliteDetailLoaderCode}
1884
-
1885
- for (const header of headerResults) {
1886
- const pkValue = header[this.primaryKey];
1887
- const detailResults = await db.executeQuery(detailSql, [pkValue]);
1888
- compositeResults.push({
1889
- ...header,
1890
- [detailKey]: detailResults || []
1891
- });
1892
- }
1893
-
1894
- return {
1895
- success: true,
1896
- count: compositeResults.length,
1897
- data: compositeResults
1898
- };
1899
- } catch (error) {
1900
- console.error('Error in readComposite:', error);
1901
- throw error;
1902
- }
1903
- }
1904
- `;
1905
- }
1906
-
1907
- return code;
1908
- })()}
1909
- }
1910
-
1911
- module.exports = new ${className}Model();`;
1912
- }
1913
-
1914
- /**
1915
- * Membuat template untuk submodule SQLite
1916
- * @param {string} moduleName - Nama module
1917
- * @param {string} endpointName - Nama endpoint
1918
- * @param {Object} payload - Data payload dari JSON
1919
- * @returns {string} Template submodule untuk SQLite
1920
- */
1921
- function createSqliteSubmoduleTemplate(moduleName, endpointName, payload) {
1922
- const modelVarName = toCamelCase(endpointName) + 'Model';
1923
- const primaryKey = payload.primaryKey || 'id';
1924
- const timestamp = new Date().toISOString();
1925
- const enabledActions = Object.entries(payload.action || {})
1926
- .filter(([, enabled]) => enabled)
1927
- .map(([action]) => action);
1928
- const hasComponents = payload.components && Array.isArray(payload.components) && payload.components.length > 0;
1929
-
1930
- // Compute componentConfig values untuk generated submodule (parsed oleh config-extractor di runtime)
1931
- const exportQuery = payload.exportQuery || `SELECT ${payload.fieldName.join(', ')} FROM ${payload.tableName}`;
1932
- const exportQueryEscaped = exportQuery.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
1933
- const fieldNameStr = JSON.stringify(payload.fieldName);
1934
- const columnFormatsStr = payload.columnFormats ? JSON.stringify(payload.columnFormats) : 'null';
1935
- const fieldLabelsStr = payload.fieldLabels ? JSON.stringify(payload.fieldLabels) : 'null';
1936
- const importConfigObj = payload.action && payload.action.import ? {
1937
- enabled: true,
1938
- upsertKeys: (payload.importConfig || {}).upsertKeys || [primaryKey],
1939
- upsertStrategy: (payload.importConfig || {}).upsertStrategy || 'update_existing',
1940
- requiredFields: (payload.importConfig || {}).requiredFields || [],
1941
- validations: (payload.importConfig || {}).validations || {},
1942
- lookupFields: (payload.importConfig || {}).lookupFields || {},
1943
- excludeFromImport: (payload.importConfig || {}).excludeFromImport || [],
1944
- chunkSize: (payload.importConfig || {}).chunkSize || 100
1945
- } : null;
1946
- const importConfigStr = importConfigObj ? JSON.stringify(importConfigObj) : 'null';
1947
- const exportQueryStr = payload.action && payload.action.export ? `'${exportQueryEscaped}'` : 'null';
1948
- const adjustConfigObj = payload.action && payload.action.adjust ? (payload.adjustConfig || {}) : null;
1949
- const adjustConfigStr = adjustConfigObj ? JSON.stringify(adjustConfigObj) : 'null';
1950
-
1951
- // Component engine declarations: selalu emit `let`-binding sehingga composite
1952
- // handler (yang selalu mereferensi componentEngine/ContextBuilder untuk
1953
- // membangun eventContext) tidak throw ReferenceError saat payload tidak
1954
- // memiliki components. Runtime require hanya dilakukan bila hasComponents.
1955
- let additionalRequires = 'let componentEngine = null;\nlet ContextBuilder = null;\n';
1956
- if (hasComponents) {
1957
- additionalRequires += `componentEngine = require('@restforgejs/platform/src/utils/component-engine').componentEngine;\n`;
1958
- additionalRequires += `ContextBuilder = require('@restforgejs/platform/src/utils/context-builder');\n`;
1959
- }
1960
-
1961
- let subModuleContent = `const express = require('express');
1962
- const router = express.Router();
1963
- const ${modelVarName} = require('../../models/${moduleName}/${endpointName}');
1964
- ${additionalRequires}
1965
- /**
1966
- * ${toPascalCase(endpointName)} Submodule - SQLite Database
1967
- * Generated: ${timestamp}
1968
- *
1969
- * SQLite-optimized endpoints untuk ${endpointName}
1970
- * Actions: ${enabledActions.join(', ')}
1971
- * Table: ${payload.tableName}
1972
- * Database: SQLite
1973
- */
1974
-
1975
- // Primary key untuk endpoint ini
1976
- const primaryKey = '${primaryKey}';
1977
-
1978
- // Component configuration untuk export/import (parsed oleh config-extractor — jangan dimodifikasi)
1979
- const componentConfig = {
1980
- tableName: '${payload.tableName}',
1981
- fieldName: ${fieldNameStr},
1982
- exportQuery: ${exportQueryStr},
1983
- columnFormats: ${columnFormatsStr},
1984
- fieldLabels: ${fieldLabelsStr},
1985
- importConfig: ${importConfigStr},
1986
- adjustConfig: ${adjustConfigStr},
1987
- uploadConfig: ${payload.uploadConfig ? JSON.stringify(payload.uploadConfig) : 'null'}
1988
- };
1989
- ${hasComponents ? `
1990
- // Initialize component engine dengan event handlers dari payload
1991
- const _componentPayload = { components: ${JSON.stringify(payload.components)} };
1992
- componentEngine.loadConfigurationFromObject(_componentPayload).then(result => {
1993
- if (result.success) {
1994
- console.log(\`Component configuration loaded for ${moduleName}/${endpointName}: \${result.componentsLoaded} components\`);
1995
- }
1996
- }).catch(err => {
1997
- console.error(\`Failed to load component configuration for ${moduleName}/${endpointName}:\`, err.message);
1998
- });
1999
- ` : ''}
2000
- // CORS ditangani di level app oleh cors middleware (lihat konfigurasi CORS_ENABLED dan CORS_ORIGINS di .env)
2001
-
2002
- // Request ID untuk tracing — support correlation ID dari upstream
2003
- router.use((req, res, next) => {
2004
- req.sqliteRequestId = req.headers['x-correlation-id'] || \`sqlite_\${Date.now()}_\${Math.random().toString(36).substr(2, 9)}\`;
2005
- res.setHeader('X-Correlation-ID', req.sqliteRequestId);
2006
- res.setHeader('X-SQLite-Request-ID', req.sqliteRequestId);
2007
- next();
2008
- });
2009
-
2010
-
2011
-
2012
- // Middleware untuk validasi payload SQLite
2013
- router.use((req, res, next) => {
2014
- if (req.method === 'POST') {
2015
- // Skip validation untuk endpoint export/import (ditangani oleh centralized handler)
2016
- if (req.path.startsWith('/import') || req.path.startsWith('/export')) {
2017
- return next();
2018
- }
2019
- try {
2020
- if (!req.body || Object.keys(req.body).length === 0) {
2021
- return res.status(400).json({
2022
- success: false,
2023
- error: 'Missing payload',
2024
- message: 'Payload cannot be empty',
2025
- timestamp: new Date().toISOString()
2026
- });
2027
- }
2028
-
2029
- const endpoint = req.path.substring(1);
2030
-
2031
- if (endpoint === 'first') {
2032
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
2033
- req.body.where = req.body.where[0];
2034
- }
2035
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
2036
- return res.status(400).json({
2037
- success: false,
2038
- error: 'Invalid payload',
2039
- message: 'Where must be a single condition {key, value}',
2040
- example: {
2041
- "where": { "key": "field_name", "value": "field_value" },
2042
- "select": ["field1", "field2"]
2043
- },
2044
- timestamp: new Date().toISOString()
2045
- });
2046
- }
2047
- if (req.body.where.conditions || req.body.where.logic) {
2048
- return res.status(400).json({
2049
- success: false,
2050
- error: 'Invalid payload',
2051
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
2052
- example: {
2053
- "where": { "key": "field_name", "value": "field_value" }
2054
- },
2055
- timestamp: new Date().toISOString()
2056
- });
2057
- }
2058
- }
2059
-
2060
- if (endpoint === 'delete' && (!req.body.where)) {
2061
- return res.status(400).json({
2062
- success: false,
2063
- error: 'Invalid payload',
2064
- message: 'DELETE payload must include a where property',
2065
- example: {
2066
- "where": [{ "key": "${primaryKey}", "value": "your-value" }]
2067
- },
2068
- timestamp: new Date().toISOString()
2069
- });
2070
- }
2071
- } catch (error) {
2072
- console.error(\`Error validating SQLite payload for \${req.path}:\`, error);
2073
- return res.status(400).json({
2074
- success: false,
2075
- error: 'Invalid payload',
2076
- message: 'Invalid payload format',
2077
- details: error.message,
2078
- timestamp: new Date().toISOString()
2079
- });
2080
- }
2081
- }
2082
- next();
2083
- });
2084
-
2085
- `;
2086
-
2087
- // POST /datatables
2088
- if (payload.action && payload.action.datatables) {
2089
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/datatables - Data untuk DataTables
2090
- router.post('/datatables', async (req, res) => {
2091
- try {
2092
- const options = {
2093
- searchValue: req.body.search?.value || req.body.searchValue || req.body.search_value || '',
2094
- searchBy: req.body.searchBy || req.body.search_by || 'all',
2095
- perPage: Math.min(parseInt(req.body.length || req.body.pagination?.perpage || 10, 10), 1000),
2096
- start: Math.max(parseInt(req.body.start || 0, 10), 0),
2097
- draw: req.body.draw || '1'
2098
- };
2099
-
2100
- // Handle sort_columns
2101
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
2102
- options.sort_columns = req.body.sort_columns.map(item => ({
2103
- column: item.column,
2104
- direction: (item.direction || 'ASC').toUpperCase()
2105
- }));
2106
- }
2107
-
2108
- // Fallback: Handle DataTables standard format (order[0][column] dan order[0][dir])
2109
- if (req.body['order[0][column]'] !== undefined) {
2110
- options['order[0][column]'] = req.body['order[0][column]'];
2111
- }
2112
- if (req.body['order[0][dir]'] !== undefined) {
2113
- options['order[0][dir]'] = req.body['order[0][dir]'];
2114
- }
2115
-
2116
- // Handle filters dengan sanitasi
2117
- if (req.body.filters && typeof req.body.filters === 'object') {
2118
- const sanitizedFilters = {};
2119
- for (const [key, value] of Object.entries(req.body.filters)) {
2120
- if (value !== null && value !== undefined && value !== '' && value !== 'all' && value !== '-') {
2121
- sanitizedFilters[key] = value;
2122
- }
2123
- }
2124
- if (Object.keys(sanitizedFilters).length > 0) {
2125
- options.filters = sanitizedFilters;
2126
- }
2127
- }
2128
-
2129
- // Support WHERE conditions
2130
- if (req.body.where) {
2131
- options.where = req.body.where;
2132
- }
2133
-
2134
- // Advanced filters support
2135
- if (req.body.advanced_filters && Array.isArray(req.body.advanced_filters)) {
2136
- options.advancedFilters = req.body.advanced_filters;
2137
- }
2138
-
2139
- // Gunakan model untuk mendapatkan data
2140
- const result = await ${modelVarName}.getDatatables(options);
2141
-
2142
- // Menambahkan nomor baris untuk DataTables
2143
- if (result.data && Array.isArray(result.data)) {
2144
- result.data = result.data.map((item, index) => ({
2145
- ...item,
2146
- rownumerator: options.start + index + 1
2147
- }));
2148
- }
2149
-
2150
- return res.json(result);
2151
- } catch (error) {
2152
- console.error('Error in ${endpointName} datatables:', error);
2153
- const statusCode = error.statusCode || 500;
2154
- return res.status(statusCode).json({
2155
- success: false,
2156
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
2157
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} data',
2158
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2159
- timestamp: new Date().toISOString()
2160
- });
2161
- }
2162
- });
2163
-
2164
- `;
2165
- }
2166
-
2167
- // GET /lookup - Dynamic
2168
- if (payload.action && payload.action.lookup) {
2169
- subModuleContent += `// GET /api/${moduleName}/${endpointName}/lookup - SQLite Dynamic Lookup
2170
- router.get('/lookup', async (req, res) => {
2171
- const sqliteRequestId = req.sqliteRequestId;
2172
-
2173
- try {
2174
- const requestMode = req.headers['x-request-mode'];
2175
-
2176
- if (requestMode !== 'dynamic') {
2177
- return res.status(400).json({
2178
- success: false,
2179
- error: 'Invalid Request Mode',
2180
- message: 'X-Request-Mode header must be set to dynamic',
2181
- timestamp: new Date().toISOString()
2182
- });
2183
- }
2184
-
2185
- let search = req.query.search || '';
2186
- if (Array.isArray(search)) {
2187
- search = search[0] || '';
2188
- }
2189
-
2190
- // Search length validation
2191
- if (search.length > 100) {
2192
- return res.status(400).json({
2193
- success: false,
2194
- error: 'Search Too Long',
2195
- message: 'Search parameter must not exceed 100 characters',
2196
- timestamp: new Date().toISOString()
2197
- });
2198
- }
2199
-
2200
- console.log(\`[SQLite-LKP] \${sqliteRequestId} dynamic search: \${search}\`);
2201
-
2202
- // Collect extra filters dari query params
2203
- const extraFilters = {};
2204
- for (const [key, value] of Object.entries(req.query)) {
2205
- if (key !== 'search' && ${modelVarName}.validFields.includes(key) && value) {
2206
- extraFilters[key] = value;
2207
- }
2208
- }
2209
-
2210
- const startTime = Date.now();
2211
- const list = Object.keys(extraFilters).length > 0 ?
2212
- await ${modelVarName}.getLookupDataDynamic(search, extraFilters) :
2213
- await ${modelVarName}.getLookupData(search);
2214
- const lookupTime = Date.now() - startTime;
2215
-
2216
- console.log(\`[SQLite-LKP] \${sqliteRequestId} found \${list.length} results in \${lookupTime}ms\`);
2217
-
2218
- return res.json({
2219
- success: true,
2220
- count: list.length,
2221
- data: list,
2222
- search: search,
2223
- _sqlite: { requestId: sqliteRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2224
- });
2225
- } catch (error) {
2226
- console.error(\`[SQLite-LKP] Error \${sqliteRequestId}:\`, error);
2227
- return res.status(500).json({
2228
- success: false,
2229
- error: 'Internal Server Error',
2230
- message: 'An error occurred while looking up ${endpointName} data',
2231
- details: error.message,
2232
- timestamp: new Date().toISOString()
2233
- });
2234
- }
2235
- });
2236
-
2237
- `;
2238
- }
2239
-
2240
- // POST /lookup - Static
2241
- if (payload.action && payload.action.lookup) {
2242
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/lookup - SQLite Static Lookup
2243
- router.post('/lookup', async (req, res) => {
2244
- const sqliteRequestId = req.sqliteRequestId;
2245
-
2246
- try {
2247
- const requestMode = req.headers['x-request-mode'];
2248
-
2249
- if (requestMode !== 'static') {
2250
- return res.status(400).json({
2251
- success: false,
2252
- error: 'Invalid Request Mode',
2253
- message: 'X-Request-Mode header must be set to static for POST lookup',
2254
- timestamp: new Date().toISOString()
2255
- });
2256
- }
2257
-
2258
- console.log(\`[SQLite-LKP] \${sqliteRequestId} static lookup:\`, JSON.stringify(req.body, null, 2));
2259
-
2260
- const startTime = Date.now();
2261
- let list;
2262
-
2263
- if (req.body.where) {
2264
- // New format dengan where clause + optional select dan order
2265
- list = await ${modelVarName}.getLookupDataWithFilter(req.body);
2266
- } else {
2267
- // Legacy format dengan selected_tag
2268
- const selectedTag = req.body.selected_tag || '';
2269
- list = await ${modelVarName}.getStaticLookupData(selectedTag);
2270
- }
2271
-
2272
- const lookupTime = Date.now() - startTime;
2273
- console.log(\`[SQLite-LKP] \${sqliteRequestId} found \${list.length} results in \${lookupTime}ms\`);
2274
-
2275
- return res.json({
2276
- success: true,
2277
- count: list.length,
2278
- data: list,
2279
- _sqlite: { requestId: sqliteRequestId, queryTime: lookupTime, timestamp: new Date().toISOString() }
2280
- });
2281
- } catch (error) {
2282
- console.error(\`[SQLite-LKP] Error \${sqliteRequestId}:\`, error);
2283
- return res.status(500).json({
2284
- success: false,
2285
- error: 'Internal Server Error',
2286
- message: 'An error occurred while looking up ${endpointName} data',
2287
- details: error.message,
2288
- timestamp: new Date().toISOString()
2289
- });
2290
- }
2291
- });
2292
-
2293
- `;
2294
- }
2295
-
2296
- // POST /create
2297
- if (payload.action && payload.action.create) {
2298
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create - SQLite Insert
2299
- router.post('/create', async (req, res) => {
2300
- try {
2301
- if (!req.body || Object.keys(req.body).length === 0) {
2302
- return res.status(400).json({
2303
- success: false,
2304
- error: 'Invalid payload',
2305
- message: 'Payload cannot be empty',
2306
- timestamp: new Date().toISOString()
2307
- });
2308
- }
2309
-
2310
- // Validasi data
2311
- if (typeof ${modelVarName}.validateData === 'function') {
2312
- const validation = await ${modelVarName}.validateData(req.body, 'insert');
2313
- if (!validation.isValid) {
2314
- return res.status(400).json({
2315
- success: false,
2316
- error: 'Validation failed',
2317
- message: 'Invalid data',
2318
- errors: validation.errors,
2319
- timestamp: new Date().toISOString()
2320
- });
2321
- }
2322
- req.body = { ...req.body, ...validation.sanitizedData };
2323
- }
2324
-
2325
- ${hasComponents ? `
2326
- // Component engine: build eventContext untuk model-level event lifecycle
2327
- if (componentEngine && ContextBuilder) {
2328
- try {
2329
- const eventContext = {
2330
- componentEngine: componentEngine,
2331
- ContextBuilder: ContextBuilder,
2332
- tableName: '${payload.tableName}',
2333
- additionalContext: {
2334
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2335
- options: req.bodyOptions || {},
2336
- requestId: req.id || null
2337
- }
2338
- };
2339
- var result = await ${modelVarName}.addData(req.body, eventContext);
2340
- console.log('[INTEGRATED TRANSACTION] INSERT completed successfully with events');
2341
- } catch (error) {
2342
- console.error('[INTEGRATED TRANSACTION] INSERT failed:', error.message);
2343
- throw error;
2344
- }
2345
- } else {
2346
- try {
2347
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2348
- console.log('[FALLBACK] INSERT completed without events');
2349
- } catch (error) {
2350
- console.error('[FALLBACK] INSERT failed:', error.message);
2351
- throw error;
2352
- }
2353
- }
2354
- ` : `
2355
- try {
2356
- var result = await ${modelVarName}.addData(req.body, { additionalContext: { requestId: req.id || null } });
2357
- console.log('[FALLBACK] INSERT completed without events');
2358
- } catch (error) {
2359
- console.error('[FALLBACK] INSERT failed:', error.message);
2360
- throw error;
2361
- }
2362
- `}
2363
- console.log(\`${endpointName} data added successfully: \${result.${primaryKey} || 'new record'}\`);
2364
-
2365
- return res.status(201).json({
2366
- success: true,
2367
- message: '${endpointName} data successfully added',
2368
- data: result,
2369
- timestamp: new Date().toISOString()
2370
- });
2371
- } catch (error) {
2372
- console.error('Error saat menambahkan data ${endpointName}:', error);
2373
-
2374
- if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
2375
- return res.status(409).json({
2376
- success: false,
2377
- error: 'Duplicate entry',
2378
- message: 'A record with this value already exists',
2379
- timestamp: new Date().toISOString()
2380
- });
2381
- }
2382
-
2383
- if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
2384
- return res.status(400).json({
2385
- success: false,
2386
- error: 'Foreign key constraint',
2387
- message: 'Referenced data not found',
2388
- timestamp: new Date().toISOString()
2389
- });
2390
- }
2391
-
2392
- return res.status(500).json({
2393
- success: false,
2394
- error: 'Internal Server Error',
2395
- message: 'An error occurred while adding ${endpointName} data',
2396
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2397
- timestamp: new Date().toISOString()
2398
- });
2399
- }
2400
- });
2401
-
2402
- `;
2403
- }
2404
-
2405
- // POST /update
2406
- if (payload.action && payload.action.update) {
2407
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update - SQLite Update
2408
- router.post('/update', async (req, res) => {
2409
- try {
2410
- // Validasi payload
2411
- if (!req.body || Object.keys(req.body).length === 0) {
2412
- return res.status(400).json({
2413
- success: false,
2414
- error: 'Invalid payload',
2415
- message: 'Payload cannot be empty',
2416
- timestamp: new Date().toISOString()
2417
- });
2418
- }
2419
-
2420
- // Validasi primary key
2421
- const primaryKey = '${primaryKey}';
2422
- if (!req.body[primaryKey]) {
2423
- return res.status(400).json({
2424
- success: false,
2425
- error: 'Missing required field',
2426
- message: \`Primary key (\${primaryKey}) is required for update\`,
2427
- timestamp: new Date().toISOString()
2428
- });
2429
- }
2430
-
2431
- // Validasi data dengan model jika tersedia
2432
- if (typeof ${modelVarName}.validateData === 'function') {
2433
- const validation = await ${modelVarName}.validateData(req.body, 'update');
2434
- if (!validation.isValid) {
2435
- return res.status(400).json({
2436
- success: false,
2437
- error: 'Validation failed',
2438
- message: 'Invalid data',
2439
- errors: validation.errors,
2440
- timestamp: new Date().toISOString()
2441
- });
2442
- }
2443
- req.body = { ...req.body, ...validation.sanitizedData };
2444
- }
2445
-
2446
- let responseData = null;
2447
-
2448
- ${hasComponents ? `
2449
- // Integrated transaction dengan event lifecycle
2450
- try {
2451
- const eventContext = {
2452
- componentEngine: componentEngine,
2453
- ContextBuilder: ContextBuilder,
2454
- tableName: '${payload.tableName}',
2455
- additionalContext: {
2456
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2457
- options: req.bodyOptions || {}
2458
- }
2459
- };
2460
- responseData = await ${modelVarName}.updateData(req.body, eventContext);
2461
- console.log('[INTEGRATED TRANSACTION] UPDATE completed successfully with events');
2462
- } catch (error) {
2463
- console.error('[INTEGRATED TRANSACTION] UPDATE failed:', error.message);
2464
- throw error;
2465
- }
2466
- ` : `
2467
- // Fallback: mode tanpa events
2468
- try {
2469
- responseData = await ${modelVarName}.updateData(req.body, { additionalContext: { requestId: req.id || null } });
2470
- console.log('[FALLBACK] UPDATE completed without events');
2471
- } catch (error) {
2472
- console.error('[FALLBACK] UPDATE failed:', error.message);
2473
- throw error;
2474
- }
2475
- `}
2476
- // Log successful operation
2477
- console.log(\`${endpointName} data updated successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2478
-
2479
- return res.status(200).json({
2480
- success: true,
2481
- message: '${endpointName} data successfully updated',
2482
- data: responseData,
2483
- timestamp: new Date().toISOString()
2484
- });
2485
- } catch (error) {
2486
- console.error('Error saat mengupdate data ${endpointName}:', error);
2487
-
2488
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2489
- return res.status(404).json({
2490
- success: false,
2491
- error: 'Data not found',
2492
- message: '${endpointName} data not found',
2493
- timestamp: new Date().toISOString()
2494
- });
2495
- }
2496
-
2497
- if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
2498
- return res.status(409).json({
2499
- success: false,
2500
- error: 'Duplicate entry',
2501
- message: 'A record with this value already exists',
2502
- timestamp: new Date().toISOString()
2503
- });
2504
- }
2505
-
2506
- return res.status(500).json({
2507
- success: false,
2508
- error: 'Internal Server Error',
2509
- message: 'An error occurred while updating ${endpointName} data',
2510
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2511
- timestamp: new Date().toISOString()
2512
- });
2513
- }
2514
- });
2515
-
2516
- `;
2517
- }
2518
-
2519
- // POST /adjust
2520
- if (payload.action && payload.action.adjust) {
2521
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/adjust - SQLite Adjust (atomic increment/decrement)
2522
- router.post('/adjust', async (req, res) => {
2523
- try {
2524
- // Validasi payload
2525
- if (!req.body || Object.keys(req.body).length === 0) {
2526
- return res.status(400).json({
2527
- success: false,
2528
- error: 'Invalid payload',
2529
- message: 'Payload cannot be empty',
2530
- timestamp: new Date().toISOString()
2531
- });
2532
- }
2533
-
2534
- // Validasi primary key
2535
- const primaryKey = '${primaryKey}';
2536
- if (!req.body[primaryKey]) {
2537
- return res.status(400).json({
2538
- success: false,
2539
- error: 'Missing required field',
2540
- message: \`Primary key (\${primaryKey}) is required for adjust\`,
2541
- timestamp: new Date().toISOString()
2542
- });
2543
- }
2544
-
2545
- // Validasi adjustments array
2546
- if (!req.body.adjustments || !Array.isArray(req.body.adjustments) || req.body.adjustments.length === 0) {
2547
- return res.status(400).json({
2548
- success: false,
2549
- error: 'Invalid payload',
2550
- message: 'adjustments array is required and must not be empty',
2551
- timestamp: new Date().toISOString()
2552
- });
2553
- }
2554
-
2555
- const adjustConfig = componentConfig.adjustConfig || {};
2556
- let responseData = null;
2557
-
2558
- ${hasComponents ? `
2559
- // Integrated transaction dengan event lifecycle
2560
- try {
2561
- const eventContext = {
2562
- componentEngine: componentEngine,
2563
- ContextBuilder: ContextBuilder,
2564
- tableName: '${payload.tableName}',
2565
- additionalContext: {
2566
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2567
- options: req.bodyOptions || {}
2568
- }
2569
- };
2570
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, eventContext);
2571
- console.log('[INTEGRATED TRANSACTION] ADJUST completed successfully with events');
2572
- } catch (error) {
2573
- console.error('[INTEGRATED TRANSACTION] ADJUST failed:', error.message);
2574
- throw error;
2575
- }
2576
- ` : `
2577
- // Fallback: mode tanpa events
2578
- try {
2579
- responseData = await ${modelVarName}.adjustData(req.body, adjustConfig, { additionalContext: { requestId: req.id || null } });
2580
- console.log('[FALLBACK] ADJUST completed without events');
2581
- } catch (error) {
2582
- console.error('[FALLBACK] ADJUST failed:', error.message);
2583
- throw error;
2584
- }
2585
- `}
2586
-
2587
- // Log successful operation
2588
- console.log(\`${endpointName} data adjusted successfully: ${primaryKey}=\${req.body['${primaryKey}']}\`);
2589
-
2590
- return res.status(200).json({
2591
- success: true,
2592
- message: '${endpointName} data successfully adjusted',
2593
- data: responseData,
2594
- timestamp: new Date().toISOString()
2595
- });
2596
- } catch (error) {
2597
- console.error('Error saat mengadjust data ${endpointName}:', error);
2598
-
2599
- if (error.statusCode === 403) {
2600
- return res.status(403).json({
2601
- success: false,
2602
- error: 'Pro Feature Required',
2603
- message: error.message,
2604
- upgrade: 'https://restforge.dev/pricing',
2605
- timestamp: new Date().toISOString()
2606
- });
2607
- }
2608
-
2609
- if (error.message.includes('constraint violation') || error.message.includes('below minimum')) {
2610
- return res.status(409).json({
2611
- success: false,
2612
- error: 'Constraint violation',
2613
- message: error.message,
2614
- timestamp: new Date().toISOString()
2615
- });
2616
- }
2617
-
2618
- 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')) {
2619
- return res.status(400).json({
2620
- success: false,
2621
- error: 'Validation error',
2622
- message: error.message,
2623
- timestamp: new Date().toISOString()
2624
- });
2625
- }
2626
-
2627
- if (error.message === 'Data tidak ditemukan' || error.message.includes('not found')) {
2628
- return res.status(404).json({
2629
- success: false,
2630
- error: 'Data not found',
2631
- message: '${endpointName} data not found',
2632
- timestamp: new Date().toISOString()
2633
- });
2634
- }
2635
-
2636
- if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
2637
- return res.status(409).json({
2638
- success: false,
2639
- error: 'Duplicate entry',
2640
- message: 'A record with this value already exists',
2641
- timestamp: new Date().toISOString()
2642
- });
2643
- }
2644
-
2645
- return res.status(500).json({
2646
- success: false,
2647
- error: 'Internal Server Error',
2648
- message: 'An error occurred while adjusting ${endpointName} data',
2649
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2650
- timestamp: new Date().toISOString()
2651
- });
2652
- }
2653
- });
2654
-
2655
- `;
2656
- }
2657
-
2658
- // POST /aggregate
2659
- if (payload.action && payload.action.aggregate) {
2660
- const aggregateConfigStr = payload.aggregateConfig ? JSON.stringify(payload.aggregateConfig) : '{}';
2661
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/aggregate - SQLite Aggregate (count, sum, avg, min, max)
2662
- router.post('/aggregate', async (req, res) => {
2663
- try {
2664
- const aggregateConfig = ${aggregateConfigStr};
2665
- const result = await ${modelVarName}.aggregateData(req.body || {}, aggregateConfig);
2666
-
2667
- return res.status(200).json({
2668
- success: true,
2669
- data: result,
2670
- timestamp: new Date().toISOString()
2671
- });
2672
- } catch (error) {
2673
- console.error('Error saat mengagregasi data ${endpointName}:', error);
2674
-
2675
- if (error.statusCode === 403) {
2676
- return res.status(403).json({
2677
- success: false,
2678
- error: 'Pro Feature Required',
2679
- message: error.message,
2680
- upgrade: 'https://restforge.dev/pricing',
2681
- timestamp: new Date().toISOString()
2682
- });
2683
- }
2684
-
2685
- if (error.statusCode === 400 ||
2686
- error.message.includes('not a valid field') ||
2687
- error.message.includes('Invalid aggregate function') ||
2688
- error.message.includes('Invalid alias') ||
2689
- error.message.includes('only allowed with COUNT') ||
2690
- error.message.includes('not defined in aggregate config') ||
2691
- error.message.includes('no join definitions found')) {
2692
- return res.status(400).json({
2693
- success: false,
2694
- error: 'Validation error',
2695
- message: error.message,
2696
- timestamp: new Date().toISOString()
2697
- });
2698
- }
2699
-
2700
- return res.status(500).json({
2701
- success: false,
2702
- error: 'Internal Server Error',
2703
- message: 'An error occurred while aggregating ${endpointName} data',
2704
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2705
- timestamp: new Date().toISOString()
2706
- });
2707
- }
2708
- });
2709
-
2710
- `;
2711
- }
2712
-
2713
- // POST /delete
2714
- if (payload.action && payload.action.delete) {
2715
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/delete - SQLite Delete
2716
- router.post('/delete', async (req, res) => {
2717
- try {
2718
- // Validasi request body
2719
- if (!req.body || Object.keys(req.body).length === 0) {
2720
- return res.status(400).json({
2721
- success: false,
2722
- error: 'Invalid payload',
2723
- message: 'Payload cannot be empty',
2724
- timestamp: new Date().toISOString()
2725
- });
2726
- }
2727
-
2728
- if (!req.body.where) {
2729
- return res.status(400).json({
2730
- success: false,
2731
- error: 'Missing required field',
2732
- message: 'Invalid request format: where parameter is required',
2733
- example: {
2734
- "where": [{ "key": "id", "value": "your-id-value" }]
2735
- },
2736
- timestamp: new Date().toISOString()
2737
- });
2738
- }
2739
-
2740
- // Validasi format where
2741
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
2742
- return res.status(400).json({
2743
- success: false,
2744
- error: 'Invalid where format',
2745
- message: 'Invalid where format',
2746
- example: {
2747
- "where": [
2748
- { "key": "id", "value": "your-id-value" }
2749
- ]
2750
- },
2751
- timestamp: new Date().toISOString()
2752
- });
2753
- }
2754
-
2755
- let responseData = null;
2756
-
2757
- // Cek apakah data exist sebelum delete dan ambil old data untuk event lifecycle
2758
- // Menggunakan SELECT * dari tabel utama (tanpa explicit select) karena fieldName
2759
- // bisa mengandung kolom dari JOIN (mis. city_name) yang tidak ada di tabel utama
2760
- if (req.body.where && Array.isArray(req.body.where) && req.body.where.length > 0) {
2761
- const firstCondition = req.body.where[0];
2762
- try {
2763
- const existingData = await ${modelVarName}.getData({
2764
- where: [{ key: firstCondition.key, value: firstCondition.value }]
2765
- });
2766
-
2767
- if (!existingData.success || !existingData.data || existingData.data.length === 0) {
2768
- return res.status(404).json({
2769
- success: false,
2770
- error: 'Data not found',
2771
- message: '${endpointName} data not found',
2772
- timestamp: new Date().toISOString()
2773
- });
2774
- }
2775
- } catch (checkError) {
2776
- return res.status(500).json({
2777
- success: false,
2778
- error: 'Verification Failed',
2779
- message: 'Could not verify data existence before delete',
2780
- details: process.env.NODE_ENV === 'development' ? checkError.message : undefined,
2781
- timestamp: new Date().toISOString()
2782
- });
2783
- }
2784
- }
2785
-
2786
- ${hasComponents ? `
2787
- // Integrated transaction dengan event lifecycle
2788
- try {
2789
- const eventContext = {
2790
- componentEngine: componentEngine,
2791
- ContextBuilder: ContextBuilder,
2792
- tableName: '${payload.tableName}',
2793
- additionalContext: {
2794
- user_id: req.headers['user-id'] || req.headers['x-user-id'] || 'system',
2795
- options: req.bodyOptions || {}
2796
- }
2797
- };
2798
- responseData = await ${modelVarName}.deleteData(req.body, eventContext);
2799
- console.log('[INTEGRATED TRANSACTION] DELETE completed successfully with events');
2800
- } catch (error) {
2801
- console.error('[INTEGRATED TRANSACTION] DELETE failed:', error.message);
2802
- throw error;
2803
- }
2804
- ` : `
2805
- // Fallback: mode tanpa events
2806
- try {
2807
- responseData = await ${modelVarName}.deleteData(req.body, { additionalContext: { requestId: req.id || null } });
2808
- console.log('[FALLBACK] DELETE completed without events');
2809
- } catch (error) {
2810
- console.error('[FALLBACK] DELETE failed:', error.message);
2811
- throw error;
2812
- }
2813
- `}
2814
- // Log successful operation
2815
- console.log(\`${endpointName} data deleted successfully\`);
2816
-
2817
- return res.json({
2818
- ...responseData,
2819
- timestamp: new Date().toISOString()
2820
- });
2821
- } catch (error) {
2822
- console.error('Error saat menghapus data ${endpointName}:', error);
2823
-
2824
- if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
2825
- return res.status(409).json({
2826
- success: false,
2827
- error: 'Foreign key constraint',
2828
- message: 'Cannot delete: record is still referenced by other data',
2829
- timestamp: new Date().toISOString()
2830
- });
2831
- }
2832
-
2833
- return res.status(500).json({
2834
- success: false,
2835
- error: 'Internal Server Error',
2836
- message: 'An error occurred while deleting ${endpointName} data',
2837
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2838
- timestamp: new Date().toISOString()
2839
- });
2840
- }
2841
- });
2842
-
2843
- `;
2844
- }
2845
-
2846
- // POST /first
2847
- if (payload.action && payload.action.first) {
2848
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/first - Mendapatkan data berdasarkan kriteria
2849
- router.post('/first', async (req, res) => {
2850
- try {
2851
- // Normalize: array 1 elemen → object (backward compatible)
2852
- if (Array.isArray(req.body.where) && req.body.where.length === 1) {
2853
- req.body.where = req.body.where[0];
2854
- }
2855
-
2856
- // Validasi where clause — harus object tunggal {key, value}
2857
- if (!req.body.where || typeof req.body.where !== 'object' || Array.isArray(req.body.where)) {
2858
- return res.status(400).json({
2859
- success: false,
2860
- error: 'Missing required field',
2861
- message: 'Property where is required as {key, value} object',
2862
- example: {
2863
- "where": { "key": "${primaryKey}", "value": "your-id-value" },
2864
- "select": ["field1", "field2"]
2865
- },
2866
- timestamp: new Date().toISOString()
2867
- });
2868
- }
2869
-
2870
- // Validasi where.key dan where.value
2871
- if (!req.body.where.key || req.body.where.value === undefined || req.body.where.value === null || req.body.where.value === '') {
2872
- return res.status(400).json({
2873
- success: false,
2874
- error: 'Invalid where format',
2875
- message: 'Where key and value are required',
2876
- example: {
2877
- "where": { "key": "${primaryKey}", "value": "your-id-value" }
2878
- },
2879
- timestamp: new Date().toISOString()
2880
- });
2881
- }
2882
-
2883
- // Tolak format advanced (conditions/logic)
2884
- if (req.body.where.conditions || req.body.where.logic) {
2885
- return res.status(400).json({
2886
- success: false,
2887
- error: 'Invalid where format',
2888
- message: 'Advanced where format is not supported in /first endpoint. Use /read endpoint for complex queries',
2889
- timestamp: new Date().toISOString()
2890
- });
2891
- }
2892
-
2893
- // Validasi where.key ada di validFields
2894
- const validFields = ${JSON.stringify(payload.fieldName)};
2895
- if (!validFields.includes(req.body.where.key)) {
2896
- return res.status(400).json({
2897
- success: false,
2898
- error: 'Invalid where field',
2899
- message: \`Invalid field: \${req.body.where.key}\`,
2900
- validFields: validFields,
2901
- timestamp: new Date().toISOString()
2902
- });
2903
- }
2904
-
2905
- // Validasi select fields jika ada
2906
- if (req.body.select && Array.isArray(req.body.select)) {
2907
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
2908
-
2909
- if (invalidFields.length > 0) {
2910
- return res.status(400).json({
2911
- success: false,
2912
- error: 'Invalid select fields',
2913
- message: \`Invalid field(s): \${invalidFields.join(', ')}\`,
2914
- validFields: validFields,
2915
- timestamp: new Date().toISOString()
2916
- });
2917
- }
2918
- }
2919
-
2920
- // Convert ke array format untuk kompatibilitas dengan model.getData() → buildComplexWhereClause()
2921
- const getPayload = {
2922
- where: [{ key: req.body.where.key, value: req.body.where.value }],
2923
- select: req.body.select
2924
- };
2925
- const result = await ${modelVarName}.getData(getPayload);
2926
-
2927
- return res.json({
2928
- ...result,
2929
- timestamp: new Date().toISOString()
2930
- });
2931
- } catch (error) {
2932
- console.error('Error saat mendapatkan data ${endpointName}:', error);
2933
- return res.status(500).json({
2934
- success: false,
2935
- error: 'Internal Server Error',
2936
- message: 'An error occurred while fetching ${endpointName} data',
2937
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
2938
- timestamp: new Date().toISOString()
2939
- });
2940
- }
2941
- });
2942
-
2943
- `;
2944
- }
2945
-
2946
- // POST /read
2947
- if (payload.action && payload.action.read) {
2948
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read - Manual pagination endpoint
2949
- router.post('/read', async (req, res) => {
2950
- try {
2951
- // Deteksi mode: paginasi (page dikirim) atau non-paginasi (page tidak dikirim)
2952
- const paginate = req.body.page !== undefined;
2953
- const page = paginate ? parseInt(req.body.page, 10) : null;
2954
- const perPage = paginate ? Math.min(parseInt(req.body.per_page || 10, 10), 100) : null;
2955
- const limit = !paginate ? Math.min(Math.max(parseInt(req.body.limit || 1000, 10), 1), 5000) : null;
2956
- const searchValue = req.body.search_value || '';
2957
- const searchBy = req.body.search_by || 'all';
2958
-
2959
- // Parse sort_columns
2960
- let sort_columns = [];
2961
- if (req.body.sort_columns && Array.isArray(req.body.sort_columns) && req.body.sort_columns.length > 0) {
2962
- sort_columns = req.body.sort_columns.map(item => ({
2963
- column: item.column,
2964
- direction: (item.direction || 'ASC').toUpperCase()
2965
- }));
2966
- }
2967
-
2968
- // Validasi parameter paginasi (hanya jika mode paginasi)
2969
- if (paginate && page < 1) {
2970
- return res.status(400).json({
2971
- success: false,
2972
- error: 'Invalid page',
2973
- message: 'Page must be greater than 0',
2974
- timestamp: new Date().toISOString()
2975
- });
2976
- }
2977
-
2978
- // Proses parameter where dengan format advanced conditions
2979
- let where = null;
2980
- if (req.body.where && typeof req.body.where === 'object') {
2981
- if (Array.isArray(req.body.where) || (req.body.where.conditions && Array.isArray(req.body.where.conditions))) {
2982
- where = req.body.where;
2983
- }
2984
- }
2985
-
2986
- // Proses parameter select untuk kolom selektif
2987
- const validFields = ${JSON.stringify(payload.fieldName || [])};
2988
- let select = null;
2989
- if (req.body.select && Array.isArray(req.body.select)) {
2990
- const invalidFields = req.body.select.filter(field => !validFields.includes(field));
2991
- if (invalidFields.length > 0) {
2992
- return res.status(400).json({
2993
- success: false,
2994
- error: 'Invalid select fields',
2995
- message: 'Invalid field(s): ' + invalidFields.join(', '),
2996
- validFields: validFields,
2997
- timestamp: new Date().toISOString()
2998
- });
2999
- }
3000
- select = req.body.select;
3001
- }
3002
-
3003
- const options = {
3004
- searchValue,
3005
- searchBy,
3006
- sort_columns,
3007
- where: where,
3008
- select: select
3009
- };
3010
-
3011
- if (paginate) {
3012
- options.page = page;
3013
- options.perPage = perPage;
3014
- } else {
3015
- options.limit = limit;
3016
- }
3017
-
3018
- const result = await ${modelVarName}.getList(options);
3019
-
3020
- // Format response berdasarkan mode
3021
- if (paginate) {
3022
- return res.json({
3023
- success: true,
3024
- data: result.data,
3025
- count: result.data ? result.data.length : 0,
3026
- pagination: result.pagination,
3027
- message: 'Data retrieved successfully'
3028
- });
3029
- } else {
3030
- return res.json({
3031
- success: true,
3032
- data: result.data,
3033
- count: result.data ? result.data.length : 0
3034
- });
3035
- }
3036
- } catch (error) {
3037
- console.error('Error in ${endpointName} list:', error);
3038
- const statusCode = error.statusCode || 500;
3039
- return res.status(statusCode).json({
3040
- success: false,
3041
- error: statusCode === 400 ? 'Bad Request' : 'Internal Server Error',
3042
- message: statusCode === 400 ? error.message : 'An error occurred while fetching ${endpointName} list data',
3043
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3044
- timestamp: new Date().toISOString()
3045
- });
3046
- }
3047
- });
3048
-
3049
- `;
3050
- }
3051
-
3052
- // Composite endpoints (master-detail)
3053
- if (payload.masterDetail && payload.masterDetail.enabled) {
3054
- const _detailTableName = payload.masterDetail.detailTable.split('.').pop();
3055
- const _rootKey = endpointName.replace(/-/g, '_');
3056
- const _headerCalc = payload.masterDetail.headerCalculations || null;
3057
- // Fallback: autoCalculateFields.formula → headerCalculations.total_qty.source → empty (skip calculation)
3058
- const _amountFormula = payload.masterDetail?.detailConfig?.autoCalculateFields?.total_amount?.formula || '';
3059
- const _amountFormulaParts = _amountFormula.split('*').map(s => s.trim());
3060
- const _headerCalcQtyField = _headerCalc?.total_qty?.source?.replace('items.', '') || '';
3061
- const _amountQtyField = _amountFormulaParts[0] || _headerCalcQtyField;
3062
- const _amountPriceField = _amountFormulaParts[1] || 'unit_price';
3063
-
3064
- // createComposite endpoint
3065
- if (payload.action && payload.action.createComposite) {
3066
- const _headerCalcCode = _headerCalc ? `
3067
- var headerCalc = ${JSON.stringify(_headerCalc)};
3068
- if (headerCalc.total_items) {
3069
- data.total_items = data.${_detailTableName}.length;
3070
- }
3071
- if (headerCalc.total_qty && headerCalc.total_qty.source) {
3072
- var qtyField = headerCalc.total_qty.source.replace('items.', '');
3073
- data.total_qty = data.${_detailTableName}.reduce(function(sum, item) { return sum + (Number(item[qtyField]) || 0); }, 0);
3074
- }
3075
- ${_amountQtyField ? `if (headerCalc.total_amount) {
3076
- data.total_amount = data.${_detailTableName}.reduce(function(sum, item) {
3077
- var qty = Number(item.${_amountQtyField}) || 0;
3078
- var price = Number(item.${_amountPriceField}) || 0;
3079
- return sum + (qty * price);
3080
- }, 0);
3081
- }` : '// WARNING: headerCalculations.total_amount skipped — no qty field configured'}
3082
- console.log('Calculated totals:', { total_items: data.total_items, total_qty: data.total_qty, total_amount: data.total_amount });
3083
- ` : '';
3084
-
3085
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/create-composite - SQLite Composite create (master-detail)
3086
- router.post('/create-composite', async (req, res) => {
3087
- try {
3088
- console.log('Request body ${endpointName}/create-composite:', JSON.stringify(req.body, null, 2));
3089
-
3090
- if (!req.body || !req.body.${_rootKey}) {
3091
- return res.status(400).json({
3092
- success: false,
3093
- error: 'Invalid payload',
3094
- message: 'Payload must have property "${_rootKey}"',
3095
- timestamp: new Date().toISOString()
3096
- });
3097
- }
3098
-
3099
- var data = req.body.${_rootKey};
3100
-
3101
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3102
- if (typeof ${modelVarName}.validateData === 'function') {
3103
- var headerDataForValidation = Object.assign({}, data);
3104
- delete headerDataForValidation.${_detailTableName};
3105
-
3106
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'insert');
3107
- if (!validation.isValid) {
3108
- return res.status(400).json({
3109
- success: false,
3110
- error: 'Validation failed',
3111
- message: 'Invalid data',
3112
- errors: validation.errors,
3113
- timestamp: new Date().toISOString()
3114
- });
3115
- }
3116
- Object.assign(data, validation.sanitizedData);
3117
- }
3118
-
3119
- if (!data.${_detailTableName} || !Array.isArray(data.${_detailTableName}) || data.${_detailTableName}.length === 0) {
3120
- return res.status(400).json({
3121
- success: false,
3122
- error: 'Invalid payload',
3123
- message: 'Property "${_detailTableName}" must be a non-empty array',
3124
- timestamp: new Date().toISOString()
3125
- });
3126
- }
3127
-
3128
- ${_headerCalcCode}
3129
-
3130
- // Build eventContext untuk composite hooks
3131
- var eventContext = null;
3132
- if (componentEngine && ContextBuilder) {
3133
- eventContext = {
3134
- componentEngine: componentEngine,
3135
- ContextBuilder: ContextBuilder,
3136
- tableName: '${payload.tableName}',
3137
- additionalContext: {
3138
- detailTable: '${payload.masterDetail.detailTable}',
3139
- foreignKey: '${payload.masterDetail.foreignKey}',
3140
- options: req.bodyOptions || {},
3141
- requestId: req.id || null
3142
- }
3143
- };
3144
- }
3145
-
3146
- var result = await ${modelVarName}.createComposite(data, eventContext);
3147
-
3148
- console.log('${endpointName} composite create successful');
3149
-
3150
- return res.status(201).json({
3151
- success: true,
3152
- message: '${endpointName} data successfully created (with detail items)',
3153
- data: result,
3154
- timestamp: new Date().toISOString()
3155
- });
3156
- } catch (error) {
3157
- console.error('Error saat composite create ${endpointName}:', error);
3158
-
3159
- if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
3160
- return res.status(409).json({
3161
- success: false,
3162
- error: 'Duplicate entry',
3163
- message: 'A record with this value already exists',
3164
- timestamp: new Date().toISOString()
3165
- });
3166
- }
3167
- if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
3168
- return res.status(400).json({
3169
- success: false,
3170
- error: 'Foreign key constraint',
3171
- message: 'Referenced data not found',
3172
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3173
- timestamp: new Date().toISOString()
3174
- });
3175
- }
3176
-
3177
- return res.status(500).json({
3178
- success: false,
3179
- error: 'Internal Server Error',
3180
- message: 'An error occurred while creating ${endpointName} data',
3181
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3182
- timestamp: new Date().toISOString()
3183
- });
3184
- }
3185
- });
3186
-
3187
- `;
3188
- }
3189
-
3190
- // updateComposite endpoint
3191
- if (payload.action && payload.action.updateComposite) {
3192
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/update-composite - SQLite Composite update (master-detail)
3193
- router.post('/update-composite', async (req, res) => {
3194
- try {
3195
- console.log('Request body ${endpointName}/update-composite:', JSON.stringify(req.body, null, 2));
3196
-
3197
- if (!req.body || !req.body.${_rootKey}) {
3198
- return res.status(400).json({
3199
- success: false,
3200
- error: 'Invalid payload',
3201
- message: 'Payload must have property "${_rootKey}"',
3202
- timestamp: new Date().toISOString()
3203
- });
3204
- }
3205
-
3206
- var data = req.body.${_rootKey};
3207
-
3208
- if (!data.${primaryKey}) {
3209
- return res.status(400).json({
3210
- success: false,
3211
- error: 'Missing required field',
3212
- message: 'Primary key (${primaryKey}) is required for update',
3213
- timestamp: new Date().toISOString()
3214
- });
3215
- }
3216
-
3217
- // Validasi dan sanitasi header data dengan constraint (termasuk hash)
3218
- if (typeof ${modelVarName}.validateData === 'function') {
3219
- var headerDataForValidation = Object.assign({}, data);
3220
- delete headerDataForValidation.${_detailTableName};
3221
-
3222
- var validation = await ${modelVarName}.validateData(headerDataForValidation, 'update');
3223
- if (!validation.isValid) {
3224
- return res.status(400).json({
3225
- success: false,
3226
- error: 'Validation failed',
3227
- message: 'Invalid data',
3228
- errors: validation.errors,
3229
- timestamp: new Date().toISOString()
3230
- });
3231
- }
3232
- Object.assign(data, validation.sanitizedData);
3233
- }
3234
-
3235
- if (data.${_detailTableName}) {
3236
- if (typeof data.${_detailTableName} !== 'object' || Array.isArray(data.${_detailTableName})) {
3237
- return res.status(400).json({
3238
- success: false,
3239
- error: 'Invalid payload',
3240
- message: 'Property "${_detailTableName}" must be an object with structure {insert: [], update: [], delete: []}',
3241
- timestamp: new Date().toISOString()
3242
- });
3243
- }
3244
- }
3245
-
3246
- // Build eventContext untuk composite hooks
3247
- var eventContext = null;
3248
- if (componentEngine && ContextBuilder) {
3249
- eventContext = {
3250
- componentEngine: componentEngine,
3251
- ContextBuilder: ContextBuilder,
3252
- tableName: '${payload.tableName}',
3253
- additionalContext: {
3254
- detailTable: '${payload.masterDetail.detailTable}',
3255
- foreignKey: '${payload.masterDetail.foreignKey}',
3256
- options: req.bodyOptions || {},
3257
- requestId: req.id || null
3258
- }
3259
- };
3260
- }
3261
-
3262
- var result = await ${modelVarName}.updateComposite(data, eventContext);
3263
-
3264
- console.log('${endpointName} composite update successful: ${primaryKey}=' + data.${primaryKey});
3265
-
3266
- return res.status(200).json({
3267
- success: true,
3268
- message: '${endpointName} data successfully updated (with detail items)',
3269
- data: result,
3270
- timestamp: new Date().toISOString()
3271
- });
3272
- } catch (error) {
3273
- console.error('Error saat composite update ${endpointName}:', error);
3274
-
3275
- if (error.message === 'Record not found' || error.message.includes('not found')) {
3276
- return res.status(404).json({
3277
- success: false,
3278
- error: 'Data not found',
3279
- message: '${endpointName} data not found',
3280
- timestamp: new Date().toISOString()
3281
- });
3282
- }
3283
- if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
3284
- return res.status(409).json({ success: false, error: 'Duplicate entry', message: 'A record with this value already exists', timestamp: new Date().toISOString() });
3285
- }
3286
- if (error.code === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
3287
- 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() });
3288
- }
3289
-
3290
- return res.status(500).json({
3291
- success: false,
3292
- error: 'Internal Server Error',
3293
- message: 'An error occurred while updating ${endpointName} data',
3294
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3295
- timestamp: new Date().toISOString()
3296
- });
3297
- }
3298
- });
3299
-
3300
- `;
3301
- }
3302
-
3303
- // readComposite endpoint
3304
- if (payload.action && payload.action.readComposite) {
3305
- subModuleContent += `// POST /api/${moduleName}/${endpointName}/read-composite - SQLite Read header with detail items
3306
- router.post('/read-composite', async (req, res) => {
3307
- try {
3308
- console.log('Request body ${endpointName}/read-composite:', JSON.stringify(req.body, null, 2));
3309
-
3310
- if (!req.body || Object.keys(req.body).length === 0) {
3311
- return res.status(400).json({ success: false, error: 'Invalid payload', message: 'Payload cannot be empty', timestamp: new Date().toISOString() });
3312
- }
3313
-
3314
- if (!req.body.where) {
3315
- return res.status(400).json({
3316
- success: false,
3317
- error: 'Missing required field',
3318
- message: 'Property where is required',
3319
- example: { "where": [{ "key": "field_name", "value": "field_value" }] },
3320
- timestamp: new Date().toISOString()
3321
- });
3322
- }
3323
-
3324
- if (!Array.isArray(req.body.where) && !req.body.where.conditions) {
3325
- return res.status(400).json({
3326
- success: false,
3327
- error: 'Invalid where format',
3328
- message: 'Invalid where format',
3329
- example: { "where": [{ "key": "${primaryKey}", "value": "your-id-value" }] },
3330
- timestamp: new Date().toISOString()
3331
- });
3332
- }
3333
-
3334
- var result = await ${modelVarName}.readComposite(req.body);
3335
-
3336
- console.log('${endpointName} composite read successful: ' + (result.count || 0) + ' record(s)');
3337
-
3338
- return res.json({
3339
- ...result,
3340
- timestamp: new Date().toISOString()
3341
- });
3342
- } catch (error) {
3343
- console.error('Error saat composite read ${endpointName}:', error);
3344
- return res.status(500).json({
3345
- success: false,
3346
- error: 'Internal Server Error',
3347
- message: 'An error occurred while reading ${endpointName} composite data',
3348
- details: process.env.NODE_ENV === 'development' ? error.message : undefined,
3349
- timestamp: new Date().toISOString()
3350
- });
3351
- }
3352
- });
3353
-
3354
- `;
3355
- }
3356
- }
3357
-
3358
- // GET /info — self-documenting API
3359
- if (payload.action?.info !== false) {
3360
- const infoActions = {
3361
- datatables: !!(payload.action?.datatables),
3362
- read: !!(payload.action?.read),
3363
- first: !!(payload.action?.first),
3364
- create: !!(payload.action?.create),
3365
- update: !!(payload.action?.update),
3366
- delete: !!(payload.action?.delete),
3367
- lookup: !!(payload.action?.lookup),
3368
- export: !!(payload.action?.export),
3369
- import: !!(payload.action?.import),
3370
- info: payload.action?.info !== false
3371
- };
3372
-
3373
- subModuleContent += `// SQLite endpoint information — self-documenting API
3374
- router.get('/info', async (req, res) => {
3375
- try {
3376
- const actions = ${JSON.stringify(infoActions)};
3377
- const modelInfo = await ${modelVarName}.getModelInfo(actions);
3378
-
3379
- res.json({
3380
- success: true,
3381
- endpoint: '${endpointName}',
3382
- module: '${moduleName}',
3383
- table: modelInfo.table,
3384
- fields: modelInfo.fields,
3385
- querySources: modelInfo.querySources,
3386
- actions: actions,
3387
- databaseType: 'sqlite',
3388
- generated: '${timestamp}',
3389
- timestamp: new Date().toISOString()
3390
- });
3391
- } catch (error) {
3392
- console.error('SQLite info error:', error);
3393
- res.status(500).json({
3394
- success: false,
3395
- error: 'Info Error',
3396
- message: 'An error occurred while fetching endpoint info',
3397
- timestamp: new Date().toISOString()
3398
- });
3399
- }
3400
- });`;
3401
- } else {
3402
- subModuleContent += `// Endpoint /info dinonaktifkan via action.info = false\n`;
3403
- }
3404
-
3405
- subModuleContent += `
3406
- // SQLite health check
3407
- router.get('/health', async (req, res) => {
3408
- try {
3409
- const connectionInfo = await ${modelVarName}.getConnectionInfo();
3410
-
3411
- res.json({
3412
- status: connectionInfo ? 'healthy' : 'unknown',
3413
- endpoint: '${endpointName}',
3414
- database: 'sqlite',
3415
- connection: connectionInfo ? 'active' : 'unknown',
3416
- timestamp: new Date().toISOString()
3417
- });
3418
- } catch (error) {
3419
- res.status(503).json({
3420
- status: 'unhealthy',
3421
- endpoint: '${endpointName}',
3422
- database: 'sqlite',
3423
- error: error.message,
3424
- timestamp: new Date().toISOString()
3425
- });
3426
- }
3427
- });
3428
-
3429
- `;
3430
-
3431
- subModuleContent += `module.exports = router;`;
3432
-
3433
- return subModuleContent;
3434
- }
3435
-
3436
- module.exports = {
3437
- createSqliteMainModuleTemplate,
3438
- createSqliteModelTemplate,
3439
- createSqliteSubmoduleTemplate
3440
- };
1
+ function a0_0x29f1(){const _0xde84e=['lNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSkicaGicaGicb2yxiGCxr5id0GtNvTyMvYkgL0zw0U','ntC2mwrcALzpvq','ieXjtuLuideWmdbGoWOGicaGy29UC3qGzgf0ysa9igf3ywL0igrIlMv4zwn1DgvrDwvYEsHXDwvYEsK7cGOGicaGlY8Gq2fJAguGCMvZDwX0ihrHBNbHihnLBgvJDgvKigzSywCkicaGignVBNn0ignHy2HLrgf0ysa9igrHDgeUBwfWkgL0zw0Gpt4GkhSkicaGicaGAwq6igL0zw0U','ywrQDxn0','cIOGrgf0ywjHC2u6ifnrtgL0zqOQlWOklY8GuhjPBwfYEsbRzxKGDw50DwSGzw5KCg9PBNqGAw5PcMnVBNn0ihbYAw1HCNLlzxKGpsaN','jZSkicaGigrLBgv0zsbOzwfKzxjeyxrHw2rLDgfPBeTLEv07cIaGicbKzwXLDguGAgvHzgvYrgf0yvT0AgLZlNbYAw1HCNLlzxLDoWOkicaGic8Vic0TlsbiB29RoIbVBKjLzM9YzunVBxbVC2L0zvvWzgf0zsaTls0kicaGigLMicHLDMvUDenVBNrLEhqGjIyGzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUzsKGEWOGicaGicb2yxiGx2nLid0GzxzLBNrdB250zxH0lMnVBxbVBMvUDevUz2LUztSkicaGicaGDMfYif9dqIa9igv2zw50q29UDgv4Dc5dB250zxH0qNvPBgrLCJSkicaGicaGDMfYif9KzxrHAwXpChmGpsbKyxrHw2rLDgfPBeTLEv0GFhWGE307cIaGicaGihzHCIbFyMvMB3jLq3r4id0Gx0nclMj1AwXKq29TCg9ZAxrLvxbKyxrLqMvMB3jLq29UDgv4DcHOzwfKzxjeyxrHlcbVBgreyxrHlcb7cIaGicaGicaGAw5Zzxj0oIbFzgv0ywLSt3bZlMLUC2vYDcb8FcbBxsWkicaGicaGicb1CgrHDgu6if9KzxrHAwXpChmUDxbKyxrLihX8ifTDlaOGicaGicaGigrLBgv0ztOGx2rLDgfPBe9WCY5KzwXLDguGFhWGw10kicaGicaGFsWGEWOGicaGicaGihrHyMXLtMfTztOGjW','AKjyvKO','DMvPvxy','lY8Gue9tvcaVyxbPlW','l2zPCNn0ic0GtwvUzgfWyxrRyw4Gzgf0ysbIzxjKyxnHCMTHBIbRCML0zxjPyqPYB3v0zxiUCg9ZDcGNl2zPCNn0jYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGic8Vie5VCM1HBgL6ztOGyxjYyxKGmsbLBgvTzw4GW6lIGkdIGjKGB2jQzwn0icHIywnRD2fYzcbJB21WyxrPyMXLkqOGicaGAwyGkefYCMf5lMLZqxjYyxKOCMvXlMjVzhKUD2HLCMuPicyMihjLCs5IB2r5lNDOzxjLlMXLBMD0Aca9pt0GmsKGEWOGicaGicbYzxeUyM9KEs53AgvYzsa9ihjLCs5IB2r5lNDOzxjLwZbDoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLignSyxvZzsddOUkcRokaNsbOyxj1CYbVyMPLy3qGDhvUz2DHBcb7A2v5lcb2ywX1zx0kicaGigLMicGHCMvXlMjVzhKUD2HLCMuGFhWGDhLWzw9MihjLCs5IB2r5lNDOzxjLice9psaNB2jQzwn0jYb8FcbbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNtwLZC2LUzYbYzxf1AxjLzcbMAwvSzcCScIaGicaGicaGBwvZC2fNztOGj1bYB3bLCNr5ihDOzxjLigLZihjLCxvPCMvKigfZihTRzxKSihzHBhvLFsbVyMPLy3qNlaOGicaGicaGigv4yw1WBgu6ihSkicaGicaGicaGicj3AgvYzsi6ihSGiMTLEsi6ici','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbJB25ZDcbKugXHy2vOB2XKzxjZid0Gw107cIaGicaGigzVCIaOy29UC3qGw2TLEsWGDMfSDwvDig9Mie9IAMvJDc5LBNrYAwvZkgL0zw0Pksb7cIaGicaGicaG','jZSkcIaGlY8GuMvHzc9xCML0zsbZB3vYy2uGy29UzMLNDxjHDgLVBGOGihrOAxmUDMLLD05HBwuGpsaN','cIOkkIbuywjSztOG','ieXjs0uGpW','Dw5PDf9WCMLJzq','jZOGEWOGicaGicaGihr5Cgu6icC','C291CMnL','jYWkicaGicaGicb1ChrPBwu6ihbYB2nLC3mUDxb0Aw1LkcKScIaGicaGicaGBwvTB3j5oIbWCM9JzxnZlM1LBw9YEvvZywDLkcKScIaGicaGicaGC3LZDgvToIb7cIaGicaGicaGicbWBgf0zM9YBtOGChjVy2vZCY5WBgf0zM9YBsWkicaGicaGicaGig5VzgvwzxjZAw9UoIbWCM9JzxnZlNzLCNnPB24ScIaGicaGicaGicbWAwq6ihbYB2nLC3mUCgLKcIaGicaGicaGFqOGicaGicb9oWOkicaGicaGCMvZlMPZB24OAgvHBhrOsw5MBYK7cIaGicb9ktSkcIaGicaVlYbnDwf0ihnLBxvHihj1DguGzgfYAsbMB2XKzxiG','ignVBxbVC2L0zsbJCMvHDguGC3vJy2vZC2z1BcCPoWOkicaGihjLDhvYBIbYzxmUC3rHDhvZkdiWmsKUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGig1LC3nHz2u6icC','zgv0ywLSq29UzMLN','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihbHEwXVywqNlaOGicaGicaGig1LC3nHz2u6icDqyxLSB2fKig11C3qGAgf2zsbWCM9Wzxj0EsaI','igrHDgeGzgvSzxrLzcbZDwnJzxnZzNvSBhLGktSkcIaGicbYzxr1CM4GCMvZlMPZB24OEWOGicaGicaUlI5YzxnWB25ZzurHDgeScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGC2fHDcbTzw5NAgfWDxmGzgf0ysa','zxHWB3j0','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','AM9PBG','cGOGicaGy29UC3qGzgv0ywLSt3bLCMf0Aw9UCYa9igrHDgfBzgv0ywLSs2v5xsb8Fcb7FtSkicaGignVBNn0ihSGAw5Zzxj0oIbPBNnLCNrjDgvTCYa9ifTDlcb1CgrHDgu6ihvWzgf0zuL0zw1Zid0Gw10SigrLBgv0ztOGzgvSzxrLsxrLBxmGpsbBxsb9id0Gzgv0ywLSt3bLCMf0Aw9UCZSkcIaGicbJB25ZDcbKzwXLDgvKsxrLBxmGpsbBxtSkicaGignVBNn0ihvWzgf0zwrjDgvTCYa9ifTDoWOGicaGy29UC3qGAw5Zzxj0zwrjDgvTCYa9ifTDoWOkicaGic8VideUierftevursbVCgvYyxrPB25ZcIaGicbMB3iGkgnVBNn0igL0zw0GB2yGzgvSzxrLsxrLBxmPihSkicaGicaGAwyGkcfPDgvTw2rLDgfPBfbRxsKGDgHYB3CGBMv3ievYCM9YkcDnAxnZAw5NicCGkYbKzxrHAwXqAYaRicCGAw4GzgvSzxrLig9WzxjHDgLVBICPoWOGicaGicbJB25ZDcbKzwXtCwWGpsaNrevmrvrfiezst00GjYaRigrLDgfPBfrHyMXLrNvSBcaRicCGv0HfuKuGjYaRigrLDgfPBfbRicSGjYa9id8NoWOGicaGicbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOzgvSu3fSlcbBAxrLBvTKzxrHAwXqA11DktSkicaGicaGzgvSzxrLzeL0zw1ZlNb1C2GOAxrLBsK7cIaGicb9cIaGicbJB25ZB2XLlMXVzYGNrgvSzxrLzcaNicSGzgvSzxrLzeL0zw1ZlMXLBMD0AcaRicCGzgv0ywLSigL0zw0OCYKNktSkcIaGicaVlYaYlIbvuerbveuGB3bLCMf0Aw9UCWOGicaGzM9YicHJB25ZDcbPDgvTig9MihvWzgf0zuL0zw1Zksb7cIaGicaGigLMicGHAxrLBvTKzxrHAwXqA10PihrOCM93ig5LDYbfCNjVCIGNtwLZC2LUzYaNicSGzgv0ywLSugSGkYaNigLUihvWzgf0zsbVCgvYyxrPB24NktSk','lNzHBgLKyxrLrgf0ysHOzwfKzxjeyxrHrM9YvMfSAwrHDgLVBIWGj2LUC2vYDcCPoWOGicaGicbPzIaOixzHBgLKyxrPB24UAxnwywXPzcKGEWOGicaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicaGigvYCM9YoIaNvMfSAwrHDgLVBIbMywLSzwqNlaOGicaGicaGicaGBwvZC2fNztOGj0LUDMfSAwqGzgf0ysCScIaGicaGicaGicbLCNjVCNm6ihzHBgLKyxrPB24UzxjYB3jZlaOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGicbpyMPLy3qUyxnZAwDUkgrHDgeSihzHBgLKyxrPB24UC2fUAxrPEMvKrgf0ysK7cIaGicb9cGOGicaGAwyGkcfKyxrHlG','BwfW','j119ycK7cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOmJaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGBwvZC2fNztOGjW','ktSkcIaGicbYzxr1CM4GCMvZlNn0yxr1CYGYmdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIb0CNvLlaOGicaGicbTzxnZywDLoIaN','l3jLywqTy29TCg9ZAxrLic0Gu1fmAxrLifjLywqGAgvHzgvYihDPDgGGzgv0ywLSigL0zw1ZcNjVDxrLCI5WB3n0kcCVCMvHzc1JB21WB3nPDguNlcbHC3LUyYaOCMvXlcbYzxmPid0+ihSkicb0CNKGEWOGicaGy29UC29Szs5SB2COj1jLCxvLC3qGyM9KEsa','y29TCg9Uzw50CW','ywn0Aw9U','mJK1mtK4mLziDNPgtG','igXPC3q6jYWGzxjYB3iPoWOGicaGy29UC3qGC3rHDhvZq29Kzsa9igvYCM9YlNn0yxr1C0nVzguGFhWGntaWoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOC3rHDhvZq29KzsKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGC3rHDhvZq29Kzsa9pt0GndaWid8Gj0jHzcbszxf1zxn0jYa6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIbZDgf0DxndB2rLid09psa0mdaGpYbLCNjVCI5TzxnZywDLidOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigzLDgnOAw5Nia','zgf0zq','oICSigvYCM9YktSkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbMzxrJAgLUzYa','C3rHCNrZv2L0Aa','DMLLD1f1zxj5','mZi1mdq4BM9RD21g','ksb8FcaWoWOGicaGicaGihjLDhvYBIbZDw0GkYaOCxr5icOGChjPy2uPoWOGicaGicb9lcaWktSkicaGicaGCMvJywXJrMLLBgrZlNb1C2GOj3rVDgfSx2fTB3vUDca9id8NktSkicaGicaGCMvJywXJvMfSDwvZlNb1C2GODg90ywXbBw91BNqPoWOGicaGFq','jZSkcI8VienVBxbVBMvUDcbJB25MAwD1CMf0Aw9UihvUDhvRigv4Cg9YDc9PBxbVCNqGkhbHCNnLzcbVBgvOignVBMzPzY1LEhrYywn0B3iGW6lIGQZIGj0GAMfUz2fUigrPBw9KAwzPA2fZAsKky29UC3qGy29TCg9Uzw50q29UzMLNid0GEWOGihrHyMXLtMfTztOGjW','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9','DxbZzxj0s2v5CW','y29UC3rYywLUDhm','y29UC3qGzgv0ywLSu3fSid0Gj1nftevdvcaQiezst00G','lMDLDeXVB2T1CerHDgfxAxrOrMLSDgvYkhjLCs5IB2r5ktSkicaGih0GzwXZzsb7cIaGicaGic8VieXLz2fJEsbMB3jTyxqGzgvUz2fUihnLBgvJDgvKx3rHzWOGicaGicbJB25ZDcbZzwXLy3rLzfrHzYa9ihjLCs5IB2r5lNnLBgvJDgvKx3rHzYb8FcaNjZSkicaGicaGBgLZDca9igf3ywL0ia','lNjLzhvJzsHMDw5JDgLVBIHZDw0SigL0zw0PihSGCMv0DxjUihn1BsaRicHoDw1IzxiOAxrLBvTXDhLgAwvSzf0PihX8idaPoYb9lcaWktSkicaGih0kicaGia','zM9YBxvSyq','nJu1ndm0CMrUAw1W','jYKGEWOGicaGicaGignVBNrPBNvLoYaVlYbWCMLTyxj5igTLEsbZDwrHAcbHzgekicaGicaGFqOkicaGicaGlY8Gq2HLy2SGAMLRysbHzgeGu1fmigv4ChjLC3nPB24GzgvUz2fUigfSAwfZicHTzw5Nz3vUywTHBIbbuYKkicaGicaGy29UC3qGywXPyxnszwDLEca9ig5LDYbszwDfEhaOjYGUkYLCxhmRyxnCxhmRkfXCDYSPjcCSicDPjYK7cIaGicaGignVBNn0igfSAwfZtwf0y2GGpsbJB2X1Bw4UBwf0y2GOywXPyxnszwDLEcK7cIaGicaGigLMicHHBgLHC01HDgnOksb7cIaGicaGicaGy29UC3qGzxHWCMvZC2LVBIa9igfSAwfZtwf0y2HBmv0UDhjPBsGPoWOGicaGicaGignVBNn0igfSAwfZid0GywXPyxnnyxrJAfSYxs50CMLTkcK7cIaGicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7zxHWCMvZC2LVBN0GqvmGjhTHBgLHC31GoWOGicaGicaGihrLEhrgAwvSzca9igfSAwfZoWOGicaGicaGigfSAwfZrMLLBgqGpsbHBgLHCZSkicaGicaGicbICMvHAZSkicaGicaGFqOkicaGicaGlY8Gq2HLy2SGAMLRysbZAw1WBguGzMLLBgqGBMfTzqOGicaGicbPzIaODgHPCY52ywXPzezPzwXKCY5PBMnSDwrLCYHJB2X1Bw4PihX8ihzHBgLKvgv4DezPzwXKCY5PBMnSDwrLCYHJB2X1Bw4Pksb7cIaGicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7y29SDw1UFwa7cIaGicaGicaGDgv4DezPzwXKid0Gy29SDw1UoWOGicaGicaGigjYzwfRoWOGicaGicb9cGOGicaGicaVlYbdB21WDxrLzcbJB2X1Bw4kicaGicaGC2vSzwn0q2XHDxnLicS9igaSicr7y29SDw1UFwa7cIaGicaGihrLEhrgAwvSzca9ignVBhvTBJSkicaGih0kcIaGicbSzxqGCxvLCNKGpsbGu0vmrunuicr7C2vSzwn0q2XHDxnLFsbguK9nicr7DgHPCY5NzxruywjSzvnVDxjJzsGNCMvHzcCPFsa','r091qKS','Cxr5rMLLBgq','igrHDgeNlaOGicaGicbKzxrHAwXZoIbLCNjVCI5TzxnZywDLlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFqP9ktSkcG','jZSkicb0AgLZlNjLywrtB3vYy2uGpsaN','C3rYAw5NAwz5','jYWkicaGig1VzhvSzu5HBwu6icC','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUy29Kzsa9pt0Gj1nrteLurv9dt05tvfjbsu5ux1vosvfvrsCPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOnda5ks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0r1CgXPy2f0zsbLBNrYEsCScIaGicaGicaGBwvZC2fNztOGj0eGCMvJB3jKihDPDgGGDgHPCYb2ywX1zsbHBhjLywr5igv4Axn0CYCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkgvYCM9YlMnVzguGpt09icDtuuXjvevFq09ou1rsquLovf9gt1jfsuDos0vzjYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNrM9YzwLNBIbRzxKGy29UC3rYywLUDcCScIaGicaGicaGBwvZC2fNztOGj1jLzMvYzw5JzwqGzgf0ysbUB3qGzM91BMqNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGihjLDhvYBIbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUDgvYBMfSifnLCNzLCIbfCNjVCICScIaGicaGig1LC3nHz2u6icDbBIbLCNjVCIbVy2n1CNjLzcb3AgLSzsbHzgrPBMCG','DgL0Bgu','uMrVvfa','y29TCg9Uzw50rw5NAw5Lid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY91DgLSCY9JB21WB25LBNqTzw5NAw5LjYKUy29TCg9Uzw50rw5NAw5LoWO','cI8VifnrtgL0zsbOzwfSDgGGy2HLy2SkCM91DgvYlMDLDcGNl2HLywX0AcCSigfZEw5JicHYzxeSihjLCYKGpt4GEWOGihrYEsb7cIaGicbJB25ZDcbJB25Uzwn0Aw9Usw5MBYa9igf3ywL0ia','CMvHzenVBxbVC2L0zq','igrHDgeGC3vJy2vZC2z1BgX5igfKANvZDgvKjYWkicaGicaGzgf0ytOGCMvZCg9UC2veyxrHlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YihnHyxqGBwvUz2fKANvZDcbKyxrHia','jZSkicaGia','iIWGiNzHBhvLiJOGiNLVDxiTDMfSDwuIih1DcIaGicaGicaGicb9laOGicaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGicb9ktSkicaGicaGFqOGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGy29UC29Szs5LCNjVCIHGrxjYB3iGDMfSAwrHDgLUzYbtuuXPDguGCgf5Bg9HzcbMB3iGjhTYzxeUCgf0Ah06ycWGzxjYB3iPoWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNsw52ywXPzcbWyxLSB2fKigzVCM1HDcCScIaGicaGicaGzgv0ywLSCZOGzxjYB3iUBwvZC2fNzsWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kicb9cIaGBMv4DcGPoWP9ktSkcG','A2v5CW','cI8QkGOGkIbdB21WB3nPDguGDxbKyxrLic0GvxbKyxrLigHLywrLCIb3AxrOigDYyw51BgfYigrLDgfPBcbVCgvYyxrPB25ZicHtuuXPDguPcIaQlWPHC3LUyYb1CgrHDgvdB21WB3nPDguOzgf0ysWGzxzLBNrdB250zxH0id0GBNvSBcKGEWOGihrYEsb7cIaGicbJB25ZDcbWCMLTyxj5s2v5vMfSDwuGpsbKyxrHw3rOAxmUChjPBwfYEuTLEv07cIaGicbPzIaOixbYAw1HCNLlzxLwywX1zsKGEWOGicaGicb0AhjVDYbUzxCGrxjYB3iOj1bYAw1HCNKGA2v5icCGkYb0AgLZlNbYAw1HCNLlzxKGkYaNigLZihjLCxvPCMvKigzVCIb1CgrHDguNktSkicaGih0kcIaGicaVlYbdAgvJAYbPzIbYzwnVCMqGzxHPC3rZicHHBhnVihnLCNzLCYbHCYbWCMvMzxrJAcbVBgreyxrHigzVCIbOB29RCYKkicaGignVBNn0ignOzwnRu3fSid0Gj1nftevdvcaQiezst00GjYaRihrOAxmUD3jPDgvtB3vYy2uGkYaNifDirvjficCGkYb0AgLZlNbYAw1HCNLlzxKGkYaNid0GpYC7cIaGicbJB25ZDcbJAgvJA1jVD3mGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOy2HLy2TtCwWSifTWCMLTyxj5s2v5vMfSDwvDktSkicaGigLMicGHy2HLy2TsB3DZihX8ignOzwnRuM93CY5Szw5NDgGGpt09idaPihSkicaGicaGDgHYB3CGBMv3ievYCM9YkcDszwnVCMqGBM90igzVDw5KjYK7cIaGicb9cGOGicaGy29UC3qGB2XKrgf0ysa9ignOzwnRuM93C1SWxtSkcIaGicaVlYbfEhrYywn0igHLywrLCIbKyxrHcIaGicbJB25ZDcbOzwfKzxjeyxrHid0GEYaUlI5KyxrHih07cIaGicbJB25ZDcbKzxrHAwXlzxKGpsaN','ifDirvjfia','cN07cG','l2XVB2T1CcaTifnrtgL0zsbtDgf0AwmGtg9VA3vWcNjVDxrLCI5WB3n0kcCVBg9VA3vWjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGy29UC3qGC3fSAxrLuMvXDwvZDeLKid0GCMvXlNnXBgL0zvjLCxvLC3rjzdSkcIaGDhj5ihSkicaGignVBNn0ihjLCxvLC3rnB2rLid0GCMvXlMHLywrLCNnBj3GTCMvXDwvZDc1TB2rLj107cGOGicaGAwyGkhjLCxvLC3rnB2rLice9psaNC3rHDgLJjYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbszxf1zxn0ie1VzguNlaOGicaGicaGig1LC3nHz2u6icDylvjLCxvLC3qTtw9KzsbOzwfKzxiGBxvZDcbIzsbZzxqGDg8GC3rHDgLJigzVCIbqt1nuigXVB2T1CcCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGy29UC29Szs5SB2COyfTtuuXPDguTteTqxsaKE3nXBgL0zvjLCxvLC3rjzh0GC3rHDgLJigXVB2T1CdPGlcbku09olNn0CMLUz2LMEsHYzxeUyM9KEsWGBNvSBcWGmIKPoWOkicaGignVBNn0ihn0yxj0vgLTzsa9ierHDguUBM93kcK7cIaGicbSzxqGBgLZDdSkcIaGicbPzIaOCMvXlMjVzhKUD2HLCMuPihSkicaGicaGlY8GtMv3igzVCM1HDcbKzw5Nyw4GD2HLCMuGy2XHDxnLicSGB3b0Aw9UywWGC2vSzwn0igrHBIbVCMrLCGOGicaGicbSAxn0id0GyxDHAxqG','zMLLBgrwywXPzgf0Aw9U','zgv0ywLSvgfIBgu','Dg9mB3DLCKnHC2u','zgvZy3jPChrPB24','cIaGicaVlYbgywXSyMfJAZOGBw9Kzsb0yw5WysbLDMvUDhmkicaGihrYEsb7cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','BNvTyMvY','zgvMyxvSDfnJB3bL','l2fNz3jLz2f0zsaTifnrtgL0zsbbz2DYzwDHDguGkgnVDw50lcbZDw0Sigf2zYWGBwLUlcbTyxGPcNjVDxrLCI5WB3n0kcCVywDNCMvNyxrLjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGDhj5ihSkicaGignVBNn0igfNz3jLz2f0zunVBMzPzYa9ia','BMfTyq','AxnbCNjHEq','mtG0mdKWt1Lvuw52','cIaGicaVlYbmB2fKigrLDgfPBcbXDwvYEsbKyxjPigzPBguGBg9RywWkicaGigXLDcbKzxrHAwXtCwW7cIaGicb0CNKGEWOGicaGicbJB25ZDcbKzxrHAwXrDwvYEuzPBgvqyxrOid0GCgf0Ac5QB2LUkf9FzgLYBMfTzsWGj3f1zxj5jYWGjW','jZSkcIaGicaVlYbdB25MAwD1CMf0Aw9Uig9WDgLVBNmkicaGignVBNn0igXVz2DPBMDfBMfIBgvKid0Gy29UzMLNlMXVz2DPBMCGit09igzHBhnLoWOGicaGy29UC3qGyxbPs2v5uMvXDwLYzwqGpsaHiwnVBMzPzY5RzxK7cGOGicaGBg9Nz2vYlMLUzM8OEWOGicaGicbLDMvUDdOGj21VzhvSzv9ZDgfYDgLUzYCScIaGicaGig1VzhvSztOGBw9KDwXLtMfTzunHCgL0ywXPEMvKlaOGicaGicbWB3j0laOGicaGicbJB3jZoIbWCM9JzxnZlMvUDI5dt1jtx0voqujmruqGit09icDMywXZzsCScIaGicaGigHLBg1LDdOGChjVy2vZCY5LBNyUsevmtuvux0voqujmruqGpt09icD0CNvLjYWkicaGicaGBg9Nz2LUzZOGBg9Nz2LUz0vUywjSzwqScIaGicaGigfWAuTLEtOGyxbPs2v5uMvXDwLYzwqkicaGih0SigbtDgfYDgLUzYaKE21VzhvSzu5HBwvdyxbPDgfSAxPLzh0GBw9KDwXLycK7cGOGicaGlY8Gq09suYbTAwrKBgv3yxjLicHRB25MAwD1CMfZAsb2AweGq09su19ftKfcteveigrHBIbdt1jtx09ssuDjtLmGzgKGlMvUDIKkicaGigfWCc51C2uOy29YC01PzgrSzxDHCMuUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbtzwn1CML0EsbOzwfKzxjZig1PzgrSzxDHCMuGkgTVBMzPz3vYyxnPihzPysbiruXnrvrFru5bqKXfrcbKAsaUzw52kqOGicaGyxbWlNvZzsHZzwn1CML0EuHLywrLCNmUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbnAwrKBgv3yxjLihvUDhvRihbHCNnPBMCGsLnptIbKzw5Nyw4GCgvUyw5Nyw5HBIbLCNjVCGOGicaGyxbWlNvZzsHIB2r5ugfYC2vYlMPZB24OEWOGicaGicb2zxjPzNK6icHYzxeSihjLCYWGyNvMlcbLBMnVzgLUzYKGpt4GEWOGicaGicaGigLMicHIDwyUBgvUz3rOid09psaWksb7cIaGicaGicaGicbYzxr1CM47cIaGicaGicaGFqOGicaGicaGihrYEsb7cIaGicaGicaGicbku09olNbHCNnLkgj1zIK7cIaGicaGicaGFsbJyxrJAcaOzxjYB3iPihSkicaGicaGicaGihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGicaGigvYCM9YoIaNsw52ywXPzcbku09oihbHEwXVywqNlaOGicaGicaGicaGicbTzxnZywDLoIaNvgHLihbHEwXVywqGC2vUDcbPCYbUB3qGysb2ywXPzcbku09oigzVCM1HDcCScIaGicaGicaGicaGigrLDgfPBhm6igvYCM9YlM1LC3nHz2ukicaGicaGicaGih0PoWOGicaGicaGicaGDgHYB3CGBMv3ievYCM9YkcDjBNzHBgLKiePtt04NktSkicaGicaGicb9cIaGicaGih0kicaGih0PktSkcIaGicbHChaUDxnLkgjVzhLqyxjZzxiUDxjSzw5JB2rLzcH7igv4DgvUzgvKoIb0CNvLih0PktSkcIaGicaVlYbszxf1zxn0igXVz2DPBMCGBwLKzgXLD2fYzsaOugLUBY1IyxnLzcKkicaGigfWCc51C2uOkhjLCsWGCMvZlcbUzxH0ksa9pIb7cIaGicaGihjLCs5Pzca9ihjLCs5OzwfKzxjZwYD4lxjLCxvLC3qTAwqNxsb8Fcb1DwLKDJqOktSkicaGicaGCMvZlNnLDcGNwc1szxf1zxn0luLejYWGCMvXlMLKktSkcIaGicaGihjLCs5SB2CGpsbJCMvHDgvszxf1zxn0tg9Nz2vYkhSkicaGicaGicbYzxf1zxn0swq6ihjLCs5PzcWkicaGicaGicbTzxrOB2q6ihjLCs5TzxrOB2qScIaGicaGicaGCgf0AdOGCMvXlNbHDgGScIaGicaGicaGAxa6ihjLCs5PCaOGicaGicb9ktSkcIaGicaGignVBNn0ihn0yxj0vgLTzsa9ihbYB2nLC3mUAhj0Aw1LkcK7cGOGicaGicbYzxmUB24Oj2zPBMLZAcCSicGPid0+ihSkicaGicaGicbJB25ZDcbBC2vJB25KCYWGBMfUB3nLy29UzhnDid0GChjVy2vZCY5OCNrPBwuOC3rHCNruAw1LktSkicaGicaGicbJB25ZDcbKDxjHDgLVBK1Zid0GCgfYC2vgBg9HDcGOC2vJB25KCYaQideWmdaGkYbUyw5VC2vJB25KCYaVidfLnIKUDg9gAxHLzcGYksK7cIaGicaGicaGBg9NuMvXDwvZDcHYzxeSihjLCYWGzhvYyxrPB25nCYK7cIaGicaGih0PoWOkicaGicaGBMv4DcGPoWOGicaGFsK7cGOGicaGlY8GtwLKzgXLD2fYzsb1BNr1AYb2ywXPzgfZAsbbueKGA2v5igPPA2eGzgLWzxjSDwTHBIaOy29UC3rHBNqTDgLTzsbJB21WyxjPC29UkqOGicaGAwyGkgnVBMzPzY5RzxKPihSkicaGicaGy29UC3qGy3j5ChrVid0GCMvXDwLYzsGNy3j5ChrVjYK7cIaGicaGignVBNn0igv4CgvJDgvKs2v5id0GqNvMzMvYlMzYB20Oy29UzMLNlMTLEsK7cGOGicaGicbHChaUDxnLkcHYzxeSihjLCYWGBMv4DcKGpt4GEWOGicaGicaGignVBNn0igfWAuTLEsa9ihjLCs5OzwfKzxjZwYD4lwfWAs1RzxKNxtSkicaGicaGicbPzIaOiwfWAuTLEsKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGignVBNn0ihbYB3zPzgvKs2v5id0GqNvMzMvYlMzYB20OyxbPs2v5ktSkicaGicaGicbPzIaOzxHWzwn0zwrlzxKUBgvUz3rOice9psbWCM92AwrLzeTLEs5Szw5NDgGGFhWkicaGicaGicaGicaGiwnYExb0BY50Aw1PBMDtywzLrxf1ywWOzxHWzwn0zwrlzxKSihbYB3zPzgvKs2v5ksKGEWOGicaGicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaXks5QC29UkhSGzxjYB3i6icDvBMf1DgHVCML6zwq6ieLUDMfSAwqGqvbjieTLEsCGFsK7cIaGicaGicaGFqOGicaGicaGig5LEhqOktSkicaGicaGFsK7cIaGicb9cGOGicaGlY8GuMf0zsbSAw1PDgLUzYbTAwrKBgv3yxjLicHZDg9YztOGBwvTB3j5ihvUDhvRihnPBMDSzsbTB2rLlcbszwrPCYb1BNr1AYbJBhvZDgvYig1VzguPcIaGicbYyxrLtgLTAxrLCI5ZzxrtDg9YzsHJB25MAwCUy2X1C3rLCIa/icDYzwrPCYCGoIaNBwvTB3j5jYK7cIaGicbHChaUDxnLkcCVyxbPjYWGCMf0zuXPBwL0zxiUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbjzgvTCg90zw5JEsbTAwrKBgv3yxjLicHWCM90zwn0CYbTDxrHDgLVBIbLBMrWB2LUDhmGzNjVBsbKDxbSAwnHDguGzxHLy3v0Aw9UkqOGicaGAwyGkhbYB2nLC3mUzw52lKLeru1qt1rftKnzx0voqujmruqGpt09icD0CNvLjYKGEWOGicaGicbHChaUDxnLkcCVyxbPjYWGAwrLBxbVDgvUy3LnAwrKBgv3yxjLlM1PzgrSzxDHCMuOksK7cIaGicb9cGOGicaGlY8GqM9KEsbVChrPB25Zig1PzgrSzxDHCMuGkgv4DhjHy3qGE2rHDgeSig9WDgLVBNn9igzVCM1HDcbKyxjPihjLCxvLC3qGyM9KEsKkicaGigfWCc51C2uOjY9HCgKNlcbIB2r5t3b0Aw9UC01PzgrSzxDHCMuUBwLKzgXLD2fYzsGPktSkcIaGicaVlYbbDxrVlwXVywqGCgX1z2LUicHQAwTHigfKysKkicaGignVBNn0ig1VzhvSzu5HBwuGpsaN','C3rYAw5N','y29UC3qGqMfZzu1VzgvSid0GCMvXDwLYzsGNqhjLC3rMB3jNzwPZl3bSyxrMB3jTl3nYyY9TB2rLBhmVyMfZzs1TB2rLBc1ZCwXPDguNktSky29UC3qGzgiGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl3v0AwXZl2rIlxnXBgL0zsCPoWPJB25ZDcbMCYa9ihjLCxvPCMuOj2zZjYK7cMnVBNn0ihbHDgGGpsbYzxf1AxjLkcDWyxrOjYK7cGOVkIOkkIa','cIaGlY8GrMLSzsb1CgXVywqGzMLLBgrZicHurvHul0Ptt04Gy29SDw1UCYKkicb0AgLZlMzPBgvgAwvSzhmGpsa','Aw1WB3j0q29UzMLN','cIaGicaVlYbmB2CGC3vJy2vZC2z1BcbVCgvYyxrPB24kicaGignVBNnVBguUBg9Nkga','qu5eicG','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9ifDirvjfia','AxrLBxmU','uvHcr2W','laOGicaGicb0zxH0oIbPDgvTlG','Cg9W','tfbos0G','cIaGicaGih0','cIaGicaGicaGAwyGkhzHBhvLice9psb1BMrLzMLUzwqGjIyGDMfSDwuGit09ig51BgWPihSkicaGicaGicaGigrLDgfPBezPzwXKCY5WDxnOkgTLEsK7cIaGicaGicaGicbKzxrHAwXwywX1zxmUChvZAcH2ywX1zsK7cIaGicaGicaGicbKzxrHAwXqBgfJzwHVBgrLCNmUChvZAcGNpYCPoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8Gsw5Qzwn0igf1zgL0ignVBhvTBNmGA2uGzgv0ywLSieLou0vsvcb2AweGAgvSCgvYcIaGicaGihrOAxmUx2fWCgvUzenYzwf0zuf1zgL0q29SDw1UCYHKzxrHAwXgAwvSzhmSigrLDgfPBfzHBhvLCYWGzgv0ywLSugXHy2vOB2XKzxjZlcbPDgvTlcbLDMvUDenVBNrLEhqPoWOkicaGicaGy29UC3qGzgv0ywLSsw5Zzxj0u3fSid0Gj0Lou0vsvcbjtLrpicCGkYbKzxrHAwXuywjSzuz1BgWGkYaNicGNicSGzgv0ywLSrMLLBgrZlMPVAw4OjYWGjYKGkYaNksbwquXvrvmGkcCGkYbKzxrHAwXqBgfJzwHVBgrLCNmUAM9PBIGNlcaNksaRicCPifjfvfvstKLorYaQjZSkicaGicaGy29UC29Szs5SB2COj0v4zwn1DgLUzYbKzxrHAwWGsu5trvjuoICSihSGCxvLCNK6igrLDgfPBeLUC2vYDfnXBcWGDMfSDwvZoIbKzxrHAwXwywX1zxmGFsK7cIaGicaGignVBNn0igrLDgfPBfjVD3mGpsbHD2fPDcbKyI5LEgvJDxrLuxvLCNKOzgv0ywLSsw5Zzxj0u3fSlcbKzxrHAwXwywX1zxmPoWOGicaGicbPzIaOzgv0ywLSuM93C1SWxsKGEWOGicaGicaGigLUC2vYDgvKsxrLBxmUChvZAcHKzxrHAwXsB3DZwZbDktSkicaGicaGFqOGicaGFqOkicaGignVBNnVBguUBg9NkcDjBNnLCNrLzcaNicSGAw5Zzxj0zwrjDgvTCY5Szw5NDgGGkYaNigrLDgfPBcbPDgvTkhmPjYK7cGOGicaGlY8Gls0TieHVB2S6ig9Uqwz0zxjdB21WB3nPDgvjBNnLCNqGls0TcIaGicbPzIaOzxzLBNrdB250zxH0icyMigv2zw50q29UDgv4Dc5JB21WB25LBNrfBMDPBMuPihSkicaGicaGDMfYif9JztiGpsbLDMvUDenVBNrLEhqUy29TCg9Uzw50rw5NAw5LoWOGicaGicb2yxiGx0ncmIa9igv2zw50q29UDgv4Dc5dB250zxH0qNvPBgrLCJSkicaGicaGDMfYif9HzNrLCKn0Eca9if9dqJiUyNvPBgrdB21WB3nPDgvjBNnLCNrbzNrLCKnVBNrLEhqOAgvHzgvYrgf0ysWGAw5Zzxj0zwrizwfKzxiSigLUC2vYDgvKsxrLBxmSihSkicaGicaGicb0ywjSzu5HBwu6icC','ndC2otzpv3vwBKe','ksb8FcaWoWOGicaGicaGihzHCIbWCMLJzsa9ie51BwjLCIHPDgvTlG','ywr2yw5JzwrrDwvYAwvZ','jYWkicaGicaGicbHzgrPDgLVBMfSq29UDgv4DdOGEWOGicaGicaGicaGDxnLCL9PzdOGCMvXlMHLywrLCNnBj3vZzxiTAwqNxsb8FcbYzxeUAgvHzgvYC1SNEc11C2vYlwLKj10GFhWGj3n5C3rLBsCScIaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30kicaGicaGicb9cIaGicaGih07cIaGicaGihjLC3bVBNnLrgf0ysa9igf3ywL0ia','j107cIaGicbSzxqGCgfYyw1Zid0Gw107cGOGicaGlY8GvMfSAwrHC2KGDgv4DcbMAwvSzhmkicaGignVBNn0ihzHBgLKvgv4DezPzwXKCYa9ihrOAxmUDMfSAwrgAwvSzhmUzMLSDgvYkgzPzwXKid0+cIaGicaGigzPzwXKlMLUy2X1zgvZkcDUyw1LjYKGFhWGzMLLBgqUAw5JBhvKzxmOj25HBweNksb8FaOGicaGicbMAwvSzc5PBMnSDwrLCYGNy29KzsCPihX8igzPzwXKlMLUy2X1zgvZkcDRB2rLjYKGFhWkicaGicaGzMLLBgqUAw5JBhvKzxmOj3rLEhqNksb8FcbMAwvSzc5PBMnSDwrLCYGNDgL0BguNkqOGicaGktSkcIaGicbSzxqGC2vSzwn0q2XHDxnLid0GjW','laOGicaGicaGihrLEhq6igL0zw0U','y3jLyxrL','jYWkicaGicaGicaGigfKzgL0Aw9UywXdB250zxH0oIb7cIaGicaGicaGicaGihvZzxjFAwq6ihjLCs5OzwfKzxjZwYD1C2vYlwLKj10GFhWGCMvXlMHLywrLCNnBj3GTDxnLCI1PzcDDihX8icDZExn0zw0NlaOGicaGicaGicaGicbVChrPB25ZoIbYzxeUyM9KEu9WDgLVBNmGFhWGE30ScIaGicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicaGih0kicaGicaGicb9oWOGicaGicaGihzHCIbYzxn1BhqGpsbHD2fPDca','igrHDgeGDxbKyxrLzcbZDwnJzxnZzNvSBhK6ia','zgf0yxrHyMXLC1f1zxj5','A29Kzq','ice9psaNB2jQzwn0jYb8FcbbCNjHEs5PC0fYCMf5kgrHDgeU','yM9VBgvHBG','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y29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0Gpd4Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNy29UDgfPBNmNoGOGicaGicbJyxnLicDSAwTLjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbmsuTfid9GktSkicaGicaGicbWyxjHBxmUChvZAcHGjsr7DMfSDwv9jwaPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDUB3rFy29UDgfPBNmNoGOGicaGicbJyxnLicDUB3rFBgLRzsC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GtK9uieXjs0uGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkgaLjhT2ywX1zx0LycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj3n0yxj0C193AxrOjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbmsuTfid9GktSkicaGicaGicbWyxjHBxmUChvZAcHGjhT2ywX1zx0LycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2vUzhnFD2L0AcC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GteLlrsa/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GOycuKE3zHBhvLFwaPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDNCMvHDgvYx3rOyw4NoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59id4Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNBgvZC190AgfUjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsa8id9GktSkicaGicaGicbWyxjHBxmUChvZAcH2ywX1zsK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2DYzwf0zxjFzxf1ywWNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59id49id9GktSkicaGicaGicbWyxjHBxmUChvZAcH2ywX1zsK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2XLC3nFzxf1ywWNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59idW9id9GktSkicaGicaGicbWyxjHBxmUChvZAcH2ywX1zsK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2jLDhDLzw4NoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59iejfvfDfru4GpYbbtKqGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLlcb2ywX1ztiPoWOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDPBIC6cIaGicaGicaGAwyGkefYCMf5lMLZqxjYyxKODMfSDwuPksb7cIaGicaGicaGicbJB25ZDcbPBLbSywnLAg9SzgvYCYa9ihzHBhvLlM1HCcGOksa9pIaNpYCPlMPVAw4OjYWGjYK7cIaGicaGicaGicbJB25KAxrPB25ZlNb1C2GOycr7y29SDw1UFsbjtIaOjhTPBLbSywnLAg9SzgvYC30PycK7cIaGicaGicaGicbWyxjHBxmUChvZAcGUlI52ywX1zsK7cIaGicaGicaGFqOGicaGicaGigjYzwfRoWOGicaGicbJyxnLicDUB3rFAw4NoGOGicaGicaGigLMicHbCNjHEs5PC0fYCMf5khzHBhvLksKGEWOGicaGicaGicaGy29UC3qGBM90sw5qBgfJzwHVBgrLCNmGpsb2ywX1zs5TyxaOkcKGpt4GjZ8Nks5QB2LUkcCSicCPoWOGicaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgaKE2nVBhvTBN0GtK9uieLoicGKE25VDeLUugXHy2vOB2XKzxjZFsLGktSkicaGicaGicaGihbHCMfTCY5WDxnOkc4UlNzHBhvLktSkicaGicaGicb9cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2LZx251BgWNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieLtie5vteXGktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNAxnFBM90x251BgWNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGjhTJB2X1Bw59ieLtie5pvcbovuXmycK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2rHDgvFzxf1ywXZjZOkicaGicaGicbJB25KAxrPB25ZlNb1C2GOygrHDguOjhTJB2X1Bw59ksa9id9GktSkicaGicaGicbWyxjHBxmUChvZAcH2ywX1zsK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2rHDgvFyMv0D2vLBIC6cIaGicaGicaGy29UzgL0Aw9UCY5WDxnOkgbKyxrLkcr7y29SDw1UFsKGqKvuv0vftIa/ieforca/ycK7cIaGicaGicaGCgfYyw1ZlNb1C2GODMfSDwuSihzHBhvLmIK7cIaGicaGicaGyNjLywS7cIaGicaGignHC2uGj2rHDgvFywz0zxiNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGzgf0zsGKE2nVBhvTBN0Pid4Gp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGy2fZzsaNzgf0zv9IzwzVCMuNoGOGicaGicaGignVBMrPDgLVBNmUChvZAcHGzgf0zsGKE2nVBhvTBN0PidWGp2aPoWOGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicbICMvHAZSkicaGicaGzgvMyxvSDdOkicaGicaGicbICMvHAZSkicaGih0kicb9cGOGigLMicHJB25KAxrPB25ZlMXLBMD0Aca9pt0GmcKGEWOGicaGCMv0DxjUihSGC3fSoIaNjYWGCgfYyw1ZoIbBxsb9oWOGih0kcIaGCMv0DxjUihSGC3fSoIbJB25KAxrPB25ZlMPVAw4OjYbbtKqGjYKSihbHCMfTCYb9oWP9cGOVkIOkicOGrxnJyxbLihzHBhvLihvUDhvRifnrtgL0zsbtuuWGkhnHBML0AxPHDgLVBIKkicOVcMvZy2fWzvzHBhvLkhzHBhvLksb7cIaGAwyGkhzHBhvLid09psbUDwXSihX8ihzHBhvLid09psb1BMrLzMLUzwqPihjLDhvYBIbUDwXSoWOGigLMicH0ExbLB2yGDMfSDwuGpt09icDUDw1IzxiNksbYzxr1CM4GDMfSDwu7cIaGCMv0DxjUifn0CMLUzYH2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lxvWzgf0zqOGicaGicaGicaGicb9cIaGicaGicaGicaGigvSC2uGEWOGicaGicaGicaGicaGignVBNn0ig1LC3nHz2uGpsbJB25ZDhjHAw50CY5Yzxf1AxjLze1LC3nHz2uGFhWGyezPzwXKicCKE2zPzwXKtMfTzx0NigLZihjLCxvPCMvKydSkicaGicaGicaGicaGicbYzxn1BhqUzxjYB3jZlNb1C2GOBwvZC2fNzsK7cIaGicaGicaGicaGicaGCMvZDwX0lMLZvMfSAwqGpsbMywXZztSkicaGicaGicaGicaGFqOGicaGicaGicaGFqOGicaGicaGicaGy29UDgLUDwu7cIaGicaGicaGFqOkicaGicaGicaVlYbtDhjPBMCGzMLLBgq6igHHC2GGy29UC3rYywLUDcbZDxbWB3j0cIaGicaGicaGAwyGkgnVBMzPzY50ExbLid09psaNC3rYAw5NjYaMjIb0ExbLB2yGDMfSDwuGpt09icDZDhjPBMCNksb7cIaGicaGicaGicbSzxqGC2fUAxrPEMvKid0GDMfSDwu7cIaGicaGicaGicbJB25ZDcbMAwvSzevYCM9YCYa9ifTDoWOGicaGicaGicaGy29UC3qGAxniyxnOrMLLBgqGpsbJB25ZDhjHAw50CY5OyxnOid09psaNyMnYExb0jZSkcIaGicaGicaGicaVlYbuCMLTcIaGicaGicaGicbPzIaOy29UC3rYywLUDhmUDhjPBsKGEWOGicaGicaGicaGicbZyw5PDgL6zwqGpsbZyw5PDgL6zwqUDhjPBsGPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VienHC2uGDhjHBNnMB3jTyxrPB24GkhnRAxaGAMLRysbOyxnOigzPzwXKkqOGicaGicaGicaGAwyGkcfPC0HHC2HgAwvSzcKGEWOGicaGicaGicaGicbPzIaOy29UC3rYywLUDhmUBg93zxjJyxnLksb7cIaGicaGicaGicaGicaGC2fUAxrPEMvKid0GC2fUAxrPEMvKlNrVtg93zxjdyxnLkcK7cIaGicaGicaGicaGih0GzwXZzsbPzIaOy29UC3rYywLUDhmUDxbWzxjJyxnLksb7cIaGicaGicaGicaGicaGC2fUAxrPEMvKid0GC2fUAxrPEMvKlNrVvxbWzxjdyxnLkcK7cIaGicaGicaGicaGih0kicaGicaGicaGih0kcIaGicaGicaGicaVlYbmzw5NDgGGDMfSAwrHDgLVBIaODMfSAwrHC2KGCgXHAw50zxH0ihnLyMvSDw0GAgfZAcKkicaGicaGicaGigLMicHJB25ZDhjHAw50CY5TAw5mzw5NDgGGjIyGC2fUAxrPEMvKlMXLBMD0Aca8ignVBNn0CMfPBNrZlM1PBKXLBMD0AcKGEWOGicaGicaGicaGicbMAwvSzevYCM9YCY5WDxnOkgnVBNn0CMfPBNrZlM1PBKXLBMD0Ae1LC3nHz2uGFhWGyezPzwXKicCKE2zPzwXKtMfTzx0Nig11C3qGyMuGyxqGBgvHC3qGjhTJB25ZDhjHAw50CY5TAw5mzw5NDgH9ignOyxjHy3rLCNnGktSkicaGicaGicaGih0kicaGicaGicaGigLMicHJB25ZDhjHAw50CY5TyxHmzw5NDgGGjIyGiwLZsgfZAezPzwXKicyMihnHBML0AxPLzc5Szw5NDgGGpIbJB25ZDhjHAw50CY5TyxHmzw5NDgGPihSkicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5TyxHmzw5NDgHnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbTDxn0ig5VDcbLEgnLzwqGjhTJB25ZDhjHAw50CY5TyxHmzw5NDgH9ignOyxjHy3rLCNnGktSkicaGicaGicaGih0kcIaGicaGicaGicaVlYbqyxr0zxjUihzHBgLKyxrPB24kicaGicaGicaGigLMicHJB25ZDhjHAw50CY5Wyxr0zxjUksb7cIaGicaGicaGicaGignVBNn0ihjLz2v4id0GBMv3ifjLz0v4CcHJB25ZDhjHAw50CY5Wyxr0zxjUktSkicaGicaGicaGicaGAwyGkcfYzwDLEc50zxn0khnHBML0AxPLzcKPihSkicaGicaGicaGicaGicbMAwvSzevYCM9YCY5WDxnOkgnVBNn0CMfPBNrZlNbHDhrLCM5nzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbKB2vZig5VDcbTyxrJAcbYzxf1AxjLzcbWyxr0zxjUycK7cIaGicaGicaGicaGih0kicaGicaGicaGih0kcIaGicaGicaGicaVlYbgB3jTyxqGDMfSAwrHDgLVBGOGicaGicaGicaGAwyGkgnVBNn0CMfPBNrZlMzVCM1HDca9pt0Gj2vTywLSjYaMjIaHl15BxLXZqf0RqfTExhnaxsTClLTExhnaxsSKlY50zxn0khnHBML0AxPLzcKPihSkicaGicaGicaGicaGzMLLBgrfCNjVCNmUChvZAcHJB25ZDhjHAw50CY5MB3jTyxrnzxnZywDLihX8igbgAwvSzcaNjhTMAwvSze5HBwv9jYbOyxmGAw52ywXPzcbLBwfPBcbMB3jTyxrGktSkicaGicaGicaGih0kcIaGicaGicaGicbPzIaOzMLLBgrfCNjVCNmUBgvUz3rOid4GmcKGEWOGicaGicaGicaGicbYzxn1BhqUAxnwywXPzca9igzHBhnLoWOGicaGicaGicaGicbYzxn1BhqUzxjYB3jZlNb1C2GOlI4UzMLLBgrfCNjVCNmPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VieHHC2GGDhjHBNnMB3jTyxrPB24GkhnLDgvSywGGC2vTDweGDMfSAwrHDgLVBIbWyxnZkqOGicaGicaGicaGAwyGkgLZsgfZAezPzwXKicyMigzPzwXKrxjYB3jZlMXLBMD0Aca9pt0GmcKGEWOGicaGicaGicaGicbJB25ZDcbIy3j5ChqGpsbYzxf1AxjLkcDIy3j5ChqNktSkicaGicaGicaGicaGy29UC3qGy29ZDca9ignVBNn0CMfPBNrZlMHHC2HdB3n0ihX8ideWoWOGicaGicaGicaGicbZyw5PDgL6zwqGpsbHD2fPDcbIy3j5ChqUAgfZAcHZyw5PDgL6zwqSignVC3qPoWOGicaGicaGicaGFqOkicaGicaGicaGihjLC3vSDc5Zyw5PDgL6zwreyxrHw2zPzwXKtMfTzv0GpsbZyw5PDgL6zwq7cIaGicaGicaGFsbLBhnLihSkicaGicaGicaGic8Vie5VBI1ZDhjPBMCGzMLLBgq6igjHC2LJihnHBML0AxPHDgLVBGOGicaGicaGicaGCMvZDwX0lNnHBML0AxPLzerHDgfBzMLLBgroyw1Lxsa9ihzHBhvLoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8GvMfSAwrHDguGzMLLBgqGEwfUzYb0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','y2H1BMTtAxPL','cIaGlY8Grgf0zvrPBwuGzMLLBgrZignVBMzPz3vYyxrPB24GzgfYAsbMAwvSzfzHBgLKyxrPB24kicb0AgLZlMrHDgvuAw1LrMLLBgrZid0G','ywDNCMvNyxrL','sxzeuuq','AwyGkgnHBgn1Bgf0Aw9UCY50B3rHBf9HBw91BNqPihSkicaGicaGy29UC3qGDg90ywXbBw91BNqGpsbHBgXjDgvTCY5Yzwr1y2uOzNvUy3rPB24OC3vTlcbPDgvTksb7cIaGicaGicaGDMfYihf0Esa9ie51BwjLCIHPDgvTlG','B2jQzwn0','jYWkicaGicaGBw9KDwXLoIaN','cIaGDhj5ihSkicaGigLMicH0ExbLB2yGiG','B0DvCuK','C3bSAxq','jZSkcIaGlY8GrMXHzYb1BNr1AYbZzwXMlwrVy3vTzw50Aw5NiefqssaOzw5KCg9PBNqGl2LUzM8PcIaGDgHPCY5OyxnwAwv3uxvLCNKGpsa','oWOGicaGy29UC3qGBw9KzwXjBMzVid0GyxDHAxqG','laOGigLTCg9YDenVBMzPzZOG','BNfut28','DhLWzq','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','zMLLBgrmywjLBhm','yxv0B0nHBgn1Bgf0zuzPzwXKCW','zMLUza','icb0AgLZlNzHBgLKyxrPB25dB25MAwCGpsb7cG','ksb8FcaWksaQicHoDw1IzxiOAxrLBs4','cIaGicbJB25ZDcbTB2r1BgvZrgLYid0GCgf0Ac5QB2LUkf9FzgLYBMfTzsWGjW','u0vmrunuia','zhz2tvy','Bg9VA3vWrMLLBgrZ','BgvUz3rO','ChvZAa','y0fQs2S','Dg90ywXFyw1VDw50','CMvHza','cIaGicb0CNKGEWOGicaGicb2yxiGCMvZDwX0id0GyxDHAxqG','DxbKyxrLx2v4Axn0Aw5N','DxbKyxrL','BwfZDgvYrgv0ywLS','cIaGicb9ksK7cGOGicaGCMv0DxjUihjLC3vSDdSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIGNrxjYB3iGAw4Gu1fmAxrLigDLDeXVB2T1CerHDge6jYWGzxjYB3iPoWOGicaGDgHYB3CGzxjYB3i7cIaGFqP9cGOVkIOkicOGrhLUyw1PyYbSB29RDxaGzgvUz2fUigv4DhjHigzPBhrLCNmGDw50DwSGu1fmAxrLcIaQlWPHC3LUyYbNzxrmB29RDxbeyxrHrhLUyw1PyYHZzwfYy2GSigv4DhjHrMLSDgvYCYa9ihT9ksb7cIaGDhj5ihSkicaGigXLDcbWyxjHBxmGpsbBxtSkicaGigXLDcb3AgvYzunVBMrPDgLVBNmGpsbBxtSk','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUy29Kzsa9pt0Gj1nrteLurv9dt05tvfjbsu5ux0zpuKvjr05lrvKNksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWosKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDgB3jLAwDUigTLEsbJB25ZDhjHAw50jYWkicaGicaGicbTzxnZywDLoIaNq2fUBM90igrLBgv0ztOGCMvJB3jKigLZihn0AwXSihjLzMvYzw5JzwqGyNKGB3rOzxiGzgf0ysCScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5ZDgf0DxmOntaWks5QC29UkhSkicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGigvYCM9YoIaNsw50zxjUywWGu2vYDMvYievYCM9YjYWkicaGicaGBwvZC2fNztOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigrLBgv0Aw5Nia','nZbVy0z2vMe','jYWkicaGicaGicaGig9WDgLVBNm6ihjLCs5IB2r5t3b0Aw9UCYb8Fcb7FsWkicaGicaGicaGihjLCxvLC3rjzdOGCMvXlMLKihX8ig51BgWkicaGicaGicb9cIaGicaGih07cIaGicb9cGOGicaGDMfYihjLC3vSDca9igf3ywL0ia','jYWkicaGicaGicaGigzVCMvPz25lzxK6icC','y29SDw1UrM9YBwf0CW','ignVBxbVC2L0zsb1CgrHDguGC3vJy2vZC2z1BdOG','jYWkicaGicaGicbWCMLTyxj5s2v5oIb0AgLZlNbYAw1HCNLlzxKScIaGicaGicaGlI4Ukgv2zw50q29UDgv4Dc5HzgrPDgLVBMfSq29UDgv4Dcb8Fcb7FsKkicaGicaGFsK7cIaGicaGihzHCIbFywz0zxjszxn1BhqGpsbHD2fPDcbFy2uYlMv4zwn1DgvpBKfMDgvYq29TCg9ZAxrLkcDPBNnLCNqNlcbFywz0zxjdDhGPoWOGicaGicbPzIaOiv9HzNrLCLjLC3vSDc5ZDwnJzxnZksb7cIaGicaGicaGDgHYB3CGBMv3ievYCM9YkcDVBKfMDgvYq29TCg9ZAxrLsw5Zzxj0igzHAwXLzdOGjYaRif9HzNrLCLjLC3vSDc5LCNjVCIK7cIaGicaGih0kicaGih0kcIaGicbJB25ZB2XLlMXVzYGNq29TCg9ZAxrLignYzwf0zsbJB21WBgv0zwqGC3vJy2vZC2z1BgX5jYK7cGOGicaGlY8Gsw52ywXPzgf0zsbJywnOzsbZzxrLBgfOihDYAxrLig9WzxjHDgLVBIbIzxjOyxnPBaOGicaGyxDHAxqGDgHPCY5PBNzHBgLKyxrLq2fJAguOktSkcIaGicbYzxr1CM4GEWOGicaGicaUlI5PBNnLCNrLzeHLywrLCIWkicaGicaGw2rLDgfPBeTLEv06igLUC2vYDgvKsxrLBxmkicaGih07cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUignYzwf0zunVBxbVC2L0ztONlcbLCNjVCIK7cIaGicb0AhjVDYbLCNjVCJSkicb9cN0k','tePsz1a','zM9YzwLNBKTLEq','y3jLyxrLq29TCg9ZAxrL','zMLLBgrZ','lMDLDerHDgf0ywjSzxmOB3b0Aw9UCYK7cGOGicaGlY8GtwvUyw1IywHRyw4GBM9TB3iGyMfYAxmGDw50DwSGrgf0yvrHyMXLCWOGicaGAwyGkhjLC3vSDc5KyxrHicyMiefYCMf5lMLZqxjYyxKOCMvZDwX0lMrHDgePksb7cIaGicaGihjLC3vSDc5KyxrHid0GCMvZDwX0lMrHDgeUBwfWkcHPDgvTlcbPBMrLEcKGpt4GkhSkicaGicaGicaUlI5PDgvTlaOGicaGicaGihjVD251BwvYyxrVCJOGB3b0Aw9UCY5ZDgfYDcaRigLUzgv4icSGmqOGicaGicb9ksK7cIaGicb9cGOGicaGCMv0DxjUihjLCY5QC29UkhjLC3vSDcK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGignVBNnVBguUzxjYB3iOj0vYCM9YigLUia','lMDLDe1VzgvSsw5MBYHHy3rPB25ZktSkcIaGicbYzxmUANnVBIH7cIaGicaGihn1y2nLC3m6ihrYDwuScIaGicaGigvUzhbVAw50oIaN','DxbSB2fKq29UzMLN','psr7CMvXlMjVzhLBjW','AwyGkgDLBMvYyxrLzezPzwXKCY5PBMnSDwrLCYHRzxKPksbJB250Aw51ztS','l3vWzgf0zs1JB21WB3nPDgu6jYWGsLnptI5ZDhjPBMDPzNKOCMvXlMjVzhKSig51BgWSidiPktSkcIaGicbPzIaOixjLCs5IB2r5ihX8icfYzxeUyM9KEs4','zMLYC3q','lY8GtM8Gywr2yw5JzwqGCxvLCMLLCYbKzwzPBMvK','jZSkicaGignVBNn0igrLDgfPBfbRid0GjW','oWOkicaGicaGDMfYihzHBgLKyxrPB24GpsbHD2fPDca','ihX8icfbCNjHEs5PC0fYCMf5kgrHDgeU','iezst00GjhT0AgLZlMDLDfrHyMXLu291CMnLkcDYzwfKjYL9icr7D2HLCMvdBgf1C2v9ie9srevsiejzia','lNvWzgf0zurHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbvuerbveuGy29TCgXLDgvKihDPDgHVDxqGzxzLBNrZjYK7cIaGicb9ignHDgnOicHLCNjVCIKGEWOGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDifvqrefursbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','jYWGjW','zgf0yxrHyMXLC1DOzxjL','Dg9ju09tDhjPBMC','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','jYWkicaGihrHyMXLtMfTztOGjW','mtKWntHoCLz6z1y','cIaGxtSkcIaGy29UC3qGzgf0yxrHyMXLC1DOzxjLid0G','DxbKyxrLq29TCg9ZAxrL','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicbJB25ZDcb2ywXPzgf0Aw9Uid0GyxDHAxqG','jYWkicaGicaGicbKzxrHAwXuywjSztOGjW','jZSkicaGigLMicGHCMvXlMjVzhLBChjPBwfYEuTLEv0PihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6igbqCMLTyxj5igTLEsaOjhTWCMLTyxj5s2v5FsKGAxmGCMvXDwLYzwqGzM9YigfKANvZDgaScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGlY8GvMfSAwrHC2KGywrQDxn0BwvUDhmGyxjYyxKkicaGigLMicGHCMvXlMjVzhKUywrQDxn0BwvUDhmGFhWGiufYCMf5lMLZqxjYyxKOCMvXlMjVzhKUywrQDxn0BwvUDhmPihX8ihjLCs5IB2r5lMfKANvZDg1LBNrZlMXLBMD0Aca9pt0GmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcbWyxLSB2fKjYWkicaGicaGicbTzxnZywDLoIaNywrQDxn0BwvUDhmGyxjYyxKGAxmGCMvXDwLYzwqGyw5Kig11C3qGBM90igjLigvTChr5jYWkicaGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGicb9ktSkicaGih0kcIaGicbJB25ZDcbHzgP1C3rdB25MAwCGpsbJB21WB25LBNrdB25MAwCUywrQDxn0q29UzMLNihX8ihT9oWOGicaGBgv0ihjLC3bVBNnLrgf0ysa9ig51BgW7cGO','DMLLD05HBwu','lMfKzerHDgeOCMvXlMjVzhKSihSGywrKAxrPB25HBenVBNrLEhq6ihSGCMvXDwvZDeLKoIbYzxeUAwqGFhWGBNvSBcb9ih0PoWOGicaGicbJB25ZB2XLlMXVzYGNw0zbteXcqunlxsbjtLnfuLqGy29TCgXLDgvKihDPDgHVDxqGzxzLBNrZjYK7cIaGicb9ignHDgnOicHLCNjVCIKGEWOGicaGicbJB25ZB2XLlMvYCM9YkcDBrKfmtejbq0TDieLou0vsvcbMywLSzwq6jYWGzxjYB3iUBwvZC2fNzsK7cIaGicaGihrOCM93igvYCM9YoWOGicaGFqO','ChjPBwfYEuTLEq','jYWGzw5KCg9PBNroyw1Llcb1CgXVywrdB25MAwCPoWOGicaGicaGicaGicaGigXVz2DLCI5PBMzVkhSGzxzLBNq6icD1CgXVywrFCM91DgvZx3jLz2LZDgvYzwqNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbMAwvSzhm6ie9IAMvJDc5RzxLZkhvWBg9HzenVBMzPzY5MAwvSzhmGFhWGE30Pih0SigbvCgXVywqGCM91DgvZihjLz2LZDgvYzwqGzM9Yicr7zw5KCg9PBNroyw1LFwaPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicH1CgXVywrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj3vWBg9Hzf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGDxbSB2fKrxjYB3iUBwvZC2fNzsb9lcbGvxbSB2fKihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhT1CgXVywrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOGicaGicaGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicaGicaGicbJB25ZB2XLlMvYCM9YkgbfCNjVCIbSB2fKAw5Nig1VzhvSzsaKE2zPBgv9igzYB20G','oMaSigvYCM9YktSkicaGihrLBxbSyxrLC1SI','DhjPBq','mJG2rxPZr0DV','BNvSBa','cIaGicbJB25ZB2XLlMXVzYHG','id0GjW','zw5HyMXLza','ieforca','cIaGicaGignVBNn0igrgAwvSzhmGpsbBxtSkicaGicaGy29UC3qGzfzHBhvLCYa9ifTDoWOGicaGicbMB3iGkgnVBNn0ifTRzxKSihzHBhvLxsbVzIbpyMPLy3qUzw50CMLLCYHPDgvTksKGEWOGicaGicaGigLMicHRzxKGpt09igrLDgfPBfbRksbJB250Aw51ztSkicaGicaGica','AgvHzgvYq2fSy3vSyxrPB25Z','q29UDgv4Dej1AwXKzxiGpsbYzxf1AxjLkcDaCMvZDgzVCMDLANmVCgXHDgzVCM0VC3jJl3v0AwXZl2nVBNrLEhqTyNvPBgrLCICPoWO','cIOGrgf0ywjHC2u6ifnrtgL0zqOQlWPJBgfZCYa','ieforcbGicSGD2HLCMvdBgf1C2vtCwWUCMvWBgfJzsGVxLDirvjfxhmRl2KSicCNktSkicaGih0GzwXZzsb7cIaGicaGihDOzxjLq2XHDxnLu3fSid0Gj1Dirvjfia','jYWGzw5KCg9PBNroyw1LlcbLEhbVCNrdB25MAwCPoWOGicaGicaGicaGicb9cIaGicaGicaGicb9ignHDgnOicHLEhbVCNrfCNjVCIKGEWOGicaGicaGicaGicbSB2DNzxiUzxjYB3iOEYbLDMvUDdOGj2v4Cg9YDf9YzwDPC3rYyxrPB25FzxjYB3iNlcbLBMrWB2LUDdOGzw5KCg9PBNroyw1LlcbLCNjVCJOGzxHWB3j0rxjYB3iUBwvZC2fNzsb9lcbGrxHWB3j0ihjLz2LZDhjHDgLVBIbMywLSzwqGzM9Yicr7zw5KCg9PBNroyw1LFtOGjhTLEhbVCNrfCNjVCI5TzxnZywDLFwaPoWOGicaGicaGicaGFqOkicaGicaGicaGic8VifjLz2LZDgvYigLTCg9YDcbYB3v0zxmGDMLHignLBNrYywXPEMvKigHHBMrSzxikicaGicaGicaGihrYEsb7cIaGicaGicaGicaGignVBNn0igLTCg9YDenVBMzPzYa9igv4DhjHy3rjBxbVCNrdB25MAwDgCM9Trw5KCg9PBNqOzw5KCg9PBNrqyxrOktSkicaGicaGicaGicaGAwyGkgLTCg9YDenVBMzPzYKGEWOGicaGicaGicaGicaGieLTCg9YDeHHBMrSzxiUCMvNAxn0zxjsB3v0zxmOyxbWlcaN','ExL5Es1nts1Kza','cGOGicaGlY8Gtg9Nihn1y2nLC3nMDwWGB3bLCMf0Aw9UcIaGicbJB25ZB2XLlMXVzYHG','jYWkicaGicaGzgf0ywjHC2u6icDZCwXPDguNlaOGicaGicbJB25Uzwn0Aw9UoIbJB25Uzwn0Aw9Usw5MBYa/icDHy3rPDMuNidOGj3vUA25VD24NlaOGicaGicb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOkqOGicaGFsK7cIaGFsbJyxrJAcaOzxjYB3iPihSkicaGihjLCY5ZDgf0DxmOntaZks5QC29UkhSkicaGicaGC3rHDhvZoIaNDw5OzwfSDgH5jYWkicaGicaGzw5KCg9PBNq6icC','tw9KzwW','v0HfuKuG','y2HHCKf0','jYWkicaGicaGzgf0ywjHC2u6icDZCwXPDguNlaOGicaGicbLCNjVCJOGzxjYB3iUBwvZC2fNzsWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0kFsK7cGO','lNzHBgLKyxrLrgf0ysa9pt0Gj2z1BMn0Aw9UjYKGEWOGicaGicb2yxiGAgvHzgvYrgf0yuzVCLzHBgLKyxrPB24GpsbpyMPLy3qUyxnZAwDUkhT9lcbKyxrHktSkicaGicaGzgvSzxrLigHLywrLCKrHDgfgB3jwywXPzgf0Aw9UlG','lNzHBgLKrMLLBgrZlMLUy2X1zgvZkgTLEsKGjIyGDMfSDwuPihSkicaGicaGicbLEhrYyuzPBhrLCNnBA2v5xsa9ihzHBhvLoWOGicaGicb9cIaGicb9cGOGicaGy29UC3qGC3rHCNruAw1Lid0Grgf0zs5UB3COktSkicaGignVBNn0igXPC3qGpsbpyMPLy3qUA2v5CYHLEhrYyuzPBhrLCNmPlMXLBMD0Aca+idaGpWOGicaGicbHD2fPDca','qujlt3i','Afborum','icaGicaGlY8Gqxv0BY1JywXJDwXHDguG','iICScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGDMfYigrHDgeGpsbYzxeUyM9KEs4','ywrQDxn0q29UzMLN','jYWkicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGih0PoWOGih0Gy2f0y2GGkgvYCM9Yksb7cIaGicbJB25ZB2XLlMvYCM9YkcDtuuXPDguGAw5MBYbLCNjVCJONlcbLCNjVCIK7cIaGicbYzxmUC3rHDhvZkduWmcKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGj0LUzM8GrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGzMv0y2HPBMCGzw5KCg9PBNqGAw5MBYCScIaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicb9ktSkicb9cN0PoW','igrHDgf0ywjSzxm6jYWGzxjYB3iPoWOGicaGy29UC3qGC3rHDhvZq29Kzsa9igvYCM9YlNn0yxr1C0nVzguGFhWGntaWoWOGicaGCMv0DxjUihjLCY5ZDgf0DxmOC3rHDhvZq29KzsKUANnVBIH7cIaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicbLCNjVCJOGC3rHDhvZq29Kzsa9pt0GndaWid8Gj0jHzcbszxf1zxn0jYa6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIbZDgf0DxndB2rLid09psa0mdaGpYbLCNjVCI5TzxnZywDLidOGj0fUigvYCM9Yig9Jy3vYCMvKihDOAwXLigzLDgnOAw5Nia','jYK7cG','oMaSigvYCM9YktSkicaGicaGicaGihrOCM93igvYCM9YoWOGicaGicaGih0kicaGicaGFqOkicaGicaGlY8GuMvNAxn0zxiGzxHWB3j0ignSzwfUDxaGCM91DgukicaGicaGDhj5ihSkicaGicaGicbfEhbVCNriyw5KBgvYlNjLz2LZDgvYq2XLyw51CfjVDxrLkgfWCcK7cIaGicaGih0Gy2f0y2GGkgnSzwfUDxbfCNjVCIKGEWOGicaGicaGignVBNnVBguUzxjYB3iOj0v4Cg9YDcbJBgvHBNvWihjVDxrLihjLz2LZDhjHDgLVBIbMywLSzwq6jYWGy2XLyw51CevYCM9YlM1LC3nHz2uPoWOGicaGicb9cGOGicaGicbHChaUz2v0kcCVjYWGkhjLCsWGCMvZksa9pIb7cIaGicaGicaGCMvZlMPZB24OEWOGicaGicaGicaGBwvZC2fNztOGjW','oWOGicaGy29UC3qGCMvZDwX0id0GyxDHAxqG','Aw5JBhvKzxm','wYDRB2rLjYWGj25HBweNlcaNywXSj10','cIaGlY8GrMLLBgqGDMfSAwrHDgLVBIbJB25MAwD1CMf0Aw9UcG','BMfTzq','Dg90ywXFCxr5','cIaGyc50CMLTkcK7cGOGic8VienVBNzLCNqGug9ZDgDYzvnrtcbZEw50yxGGDg8Gu1fmAxrLigLMig5LzwrLzaOGigjHC2vrDwvYEsa9ihrOAxmUy29UDMvYDfrVu3fSAxrLu1fmkgjHC2vrDwvYEsK7cGOGihjLDhvYBIbIyxnLuxvLCNK7cN0kcI8QkGOGkIbpDMvYCMLKzsbNzxrszwfKuxvLCNKGDw50DwSGzw5KCg9PBNqGl3jLywqkicOGuhjPB3jPDgfZoIb2Awv3tMfTzsddOUkaOokaMsb2Awv3uxvLCNKGW6lIGkdIGjKGDgfIBgvoyw1LicHtruXfq1qGkIbguK9nihjLywrtB3vYy2uPcIaQlWPNzxrszwfKuxvLCNKOB3b0Aw9UCYa9ihT9ksb7cIaGAwyGkhrOAxmUDMLLD05HBwuGjIyGDgHPCY52Awv3tMfTzsaHpt0GDgHPCY50ywjSzsKGEWOGicaGCMv0DxjUicDtruXfq1qGkIbguK9nicCGkYb0AgLZlNzPzxDoyw1LoWOGih0k','cIOGvgfIBgu6ia','jYWkicaGicaGicbMB3jLAwDUs2v5oIaN','icbYzxr1CM4Gj1nftevdvcaQiezst00GjYaRihrOAxmUCMvHzfnVDxjJztS','id0G','l2XVB2T1CcaTifnrtgL0zsbeEw5HBwLJieXVB2T1CaPYB3v0zxiUz2v0kcCVBg9VA3vWjYWGyxn5BMmGkhjLCsWGCMvZksa9pIb7cIaGy29UC3qGC3fSAxrLuMvXDwvZDeLKid0GCMvXlNnXBgL0zvjLCxvLC3rjzdSkcIaGDhj5ihSkicaGignVBNn0ihjLCxvLC3rnB2rLid0GCMvXlMHLywrLCNnBj3GTCMvXDwvZDc1TB2rLj107cGOGicaGAwyGkhjLCxvLC3rnB2rLice9psaNzhLUyw1PyYCPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj0LUDMfSAwqGuMvXDwvZDcbnB2rLjYWkicaGicaGicbTzxnZywDLoIaNwc1szxf1zxn0lu1VzguGAgvHzgvYig11C3qGyMuGC2v0ihrVigr5BMfTAwmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGigXLDcbZzwfYy2GGpsbYzxeUCxvLCNKUC2vHCMnOihX8icCNoWOGicaGAwyGkefYCMf5lMLZqxjYyxKOC2vHCMnOksKGEWOGicaGicbZzwfYy2GGpsbZzwfYy2HBmf0GFhWGjYC7cIaGicb9cGOGicaGlY8Gu2vHCMnOigXLBMD0Acb2ywXPzgf0Aw9UcIaGicbPzIaOC2vHCMnOlMXLBMD0Aca+ideWmcKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNu2vHCMnOifrVBYbmB25NjYWkicaGicaGicbTzxnZywDLoIaNu2vHCMnOihbHCMfTzxrLCIbTDxn0ig5VDcbLEgnLzwqGmtaWignOyxjHy3rLCNmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGignVBNnVBguUBg9NkgbBu1fmAxrLluXluf0GjhTZCwXPDgvszxf1zxn0swr9igr5BMfTAwmGC2vHCMnOoIaKE3nLyxjJAh1GktSkcIaGicaVlYbdB2XSzwn0igv4DhjHigzPBhrLCNmGzgfYAsbXDwvYEsbWyxjHBxmkicaGignVBNn0igv4DhjHrMLSDgvYCYa9ihT9oWOGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOCMvXlNf1zxj5ksKGEWOGicaGicbPzIaOA2v5ice9psaNC2vHCMnOjYaMjIa','DgfIBgvoyw1L','lY8Gv0fstKLorZOGAgvHzgvYq2fSy3vSyxrPB25ZlNrVDgfSx2fTB3vUDcbZA2LWCgvKimoI4OkS4OcDig5Vihf0EsbMAwvSzcbJB25MAwD1CMvK','cIaGicaGigL0zw0U','ywDNCMvNyxrLq29UzMLN','zw50CMLLCW','iezst00G','jYWkicaGigzPzwXKq291BNq6ia','cIaGicaVlYbszwnHBgn1Bgf0zsbOzwfKzxiGDg90ywXZcIaGicbJB25ZDcbJywXJDwXHDgLVBNmGpsa','oICSigvYCM9YktSkcIaGicbPzIaOzxjYB3iUBwvZC2fNzsa9pt0Gj0rHDgeGDgLKywSGzgL0zw11A2fUjYb8FcbLCNjVCI5TzxnZywDLlMLUy2X1zgvZkcDUB3qGzM91BMqNksKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdqPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNrgf0ysbUB3qGzM91BMqNlaOGicaGicaGig1LC3nHz2u6icC','we91Cuu','l3jLywqTy29TCg9ZAxrLoICSiePtt04UC3rYAw5NAwz5khjLCs5IB2r5lcbUDwXSlcaYksK7cGOGicaGAwyGkcfYzxeUyM9KEsb8FcbpyMPLy3qUA2v5CYHYzxeUyM9KEsKUBgvUz3rOid09psaWksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7ihn1y2nLC3m6igzHBhnLlcbLCNjVCJOGj0LUDMfSAwqGCgf5Bg9HzcCSig1LC3nHz2u6icDqyxLSB2fKignHBM5VDcbIzsbLBxb0EsCSihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPih0PoWOGicaGFqOkicaGigLMicGHCMvXlMjVzhKUD2HLCMuPihSkicaGicaGCMv0DxjUihjLCY5ZDgf0DxmOndaWks5QC29UkhSkicaGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGicbLCNjVCJOGj01PC3nPBMCGCMvXDwLYzwqGzMLLBgqNlaOGicaGicaGig1LC3nHz2u6icDqCM9Wzxj0Esb3AgvYzsbPCYbYzxf1AxjLzcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiMzPzwXKx25HBwuIlcaIDMfSDwuIoIaIzMLLBgrFDMfSDwuIih1Dih0ScIaGicaGicaGDgLTzxn0yw1WoIbUzxCGrgf0zsGPlNrVsvnpu3rYAw5NkcKkicaGicaGFsK7cIaGicb9cGOGicaGAwyGkcfbCNjHEs5PC0fYCMf5khjLCs5IB2r5lNDOzxjLksaMjIaHCMvXlMjVzhKUD2HLCMuUy29UzgL0Aw9UCYKGEWOGicaGicbYzxr1CM4GCMvZlNn0yxr1CYG0mdaPlMPZB24OEWOGicaGicaGihn1y2nLC3m6igzHBhnLlaOGicaGicaGigvYCM9YoIaNsw52ywXPzcb3AgvYzsbMB3jTyxqNlaOGicaGicaGig1LC3nHz2u6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGzxHHBxbSztOGEYaID2HLCMuIoIbBEYaIA2v5iJOGiG','iIWGiNzHBhvLiJOGiNLVDxiTAwqTDMfSDwuIih0kicaGicaGicb9laOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifrVBgfRigzVCM1HDcbHzhzHBMnLzcaOy29UzgL0Aw9UCY9SB2DPyYKkicaGigLMicHYzxeUyM9KEs53AgvYzs5JB25KAxrPB25ZihX8ihjLCs5IB2r5lNDOzxjLlMXVz2LJksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDjBNzHBgLKihDOzxjLigzVCM1HDcCScIaGicaGicaGBwvZC2fNztOGj0fKDMfUy2vKihDOzxjLigzVCM1HDcbPCYbUB3qGC3vWCg9YDgvKigLUic9MAxjZDcbLBMrWB2LUDc4GvxnLic9YzwfKigvUzhbVAw50igzVCIbJB21WBgv4ihf1zxjPzxmNlaOGicaGicaGihrPBwvZDgfTCdOGBMv3ierHDguOks50B0Ltt1n0CMLUzYGPcIaGicaGih0PoWOGicaGFqOkicaGic8VifzHBgLKyxnPihDOzxjLlMTLEsbHzgeGzgKGDMfSAwrgAwvSzhmkicaGignVBNn0ihzHBgLKrMLLBgrZid0G','wg5iyvG','BfDfBM4','zgvSzxrL','AxnbENC','iL0GpsbMCY5YzwfKrMLSzvn5BMmOzMLSzvbHDgGSicD1Dgy4jYK7cIaGicaGicaGy29UC29Szs5SB2COyfnrtcbuzw1WBgf0zsa','DgLTzxn0yw1W','lMDLDeXVB2T1CerHDgeOC2vHCMnOktSkicaGignVBNn0igXVB2T1CfrPBwuGpsbeyxrLlM5VDYGPic0GC3rHCNruAw1LoWOkicaGignVBNnVBguUBg9NkgbBu1fmAxrLluXluf0GjhTZCwXPDgvszxf1zxn0swr9igzVDw5Kicr7BgLZDc5Szw5NDgH9ihjLC3vSDhmGAw4GjhTSB29RDxbuAw1LFw1ZycK7cGOGicaGCMv0DxjUihjLCY5QC29UkhSkicaGicaGC3vJy2vZCZOGDhj1zsWkicaGicaGy291BNq6igXPC3qUBgvUz3rOlaOGicaGicbKyxrHoIbSAxn0laOGicaGicbZzwfYy2G6ihnLyxjJAcWkicaGicaGx3nXBgL0ztOGEYbYzxf1zxn0swq6ihnXBgL0zvjLCxvLC3rjzcWGCxvLCNLuAw1LoIbSB29RDxbuAw1Llcb0Aw1LC3rHBxa6ig5LDYbeyxrLkcKUDg9ju09tDhjPBMCOksb9cIaGicb9ktSkicb9ignHDgnOicHLCNjVCIKGEWOGicaGy29UC29Szs5LCNjVCIHGw1nrtgL0zs1ms1bDievYCM9Yicr7C3fSAxrLuMvXDwvZDeLKFtPGlcbLCNjVCIK7cIaGicbYzxr1CM4GCMvZlNn0yxr1CYG1mdaPlMPZB24OEWOGicaGicbZDwnJzxnZoIbMywXZzsWkicaGicaGzxjYB3i6icDjBNrLCM5HBcbtzxj2zxiGrxjYB3iNlaOGicaGicbTzxnZywDLoIaNqw4GzxjYB3iGB2nJDxjYzwqGD2HPBguGBg9VA2LUzYb1Cca','y29UC3qGz2vUzxjHDgvKrMLLBgrZid0G','zMLLBgroyw1L','ieXjs0uGp2aPoWOGicaGicbWyxjHBxmUChvZAcHGjsr7C2vHCMnOFsvGktSkicaGih0kcIaGicaVlYbbzgqGzxH0CMeGzMLSDgvYCWOGicaGAwyGkgv4DhjHrMLSDgvYCYaMjIbpyMPLy3qUA2v5CYHLEhrYyuzPBhrLCNmPlMXLBMD0Aca+idaPihSkicaGicaGzM9YicHJB25ZDcbBA2v5lcb2ywX1zv0GB2yGt2jQzwn0lMvUDhjPzxmOzxH0CMfgAwX0zxjZksKGEWOGicaGicaGigLMicH0AgLZlNzHBgLKrMLLBgrZlMLUy2X1zgvZkgTLEsKGjIyGDMfSDwuGit09ig51BgWGjIyGDMfSDwuGit09ihvUzgvMAw5LzcKGEWOGicaGicaGicaGD2HLCMvdB25KAxrPB25ZlNb1C2GOycr7A2v5Fsa9id9GktSkicaGicaGicaGihbHCMfTCY5WDxnOkhzHBhvLktSkicaGicaGicb9cIaGicaGih0kicaGih0kcIaGicbJB25ZDcb3AgvYzunSyxvZzsa9ihDOzxjLq29UzgL0Aw9UCY5Szw5NDgGGpIaWid8Gj1DirvjficCGkYb3AgvYzunVBMrPDgLVBNmUAM9PBIGNieforcaNksa6icCNoWOkicaGignVBNn0ihf1zxj5id0Gyfnftevdvca','B3j0zKO','veLmuhe','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zxqGD2HLCMvdBgf1C2vtCwWGpsbZzwfYy2Hszxn1BhqUC3fSoWOGicaGBgv0ihDOzxjLugfYyw1Zid0GwY4UlNnLyxjJAfjLC3vSDc5WyxjHBxnDoWOkicaGic8Viej1AwXKigzPBhrLCIbJBgf1C2ukicaGignVBNn0igzPBhrLCKnSyxvZzsa9ihrOAxmUyNvPBgrpyMPLy3rgAwX0zxjdBgf1C2uOzMLSDgvYCYK7cIaGicbPzIaOzMLSDgvYq2XHDxnLksb7cIaGicaGigLMicH3AgvYzunSyxvZzvnXBcKGEWOGicaGicaGihDOzxjLq2XHDxnLu3fSid0Gycr7D2HLCMvdBgf1C2vtCwX9ieforcaKE2zPBhrLCKnSyxvZzx1GoWOGicaGicb9igvSC2uGEWOGicaGicaGihDOzxjLq2XHDxnLu3fSid0GyfDirvjficr7zMLSDgvYq2XHDxnLFwa7cIaGicaGih0kicaGih0kcIaGicaVlYbtDxbWB3j0ifDirvjfignVBMrPDgLVBNmGzgfYAsbYzxf1zxn0igjVzhKkicaGigLMicHVChrPB25ZlNDOzxjLksb7cIaGicaGihrYEsb7cIaGicaGicaGy29UC3qGy29TCgXLEfjLC3vSDca9ihrOAxmUyNvPBgrdB21WBgv4v2HLCMvdBgf1C2uOB3b0Aw9UCY53AgvYzsWGD2HLCMvqyxjHBxmPoWOGicaGicaGigLMicH3AgvYzunSyxvZzvnXBcKGEWOGicaGicaGicaGD2HLCMvdBgf1C2vtCwWGpsbGjhT3AgvYzunSyxvZzvnXBh0Gqu5eicr7y29TCgXLEfjLC3vSDc5ZCwX9ydSkicaGicaGicb9igvSC2uGEWOGicaGicaGicaGD2HLCMvdBgf1C2vtCwWGpsbGv0HfuKuGjhTJB21WBgv4uMvZDwX0lNnXBh1GoWOGicaGicaGih0kicaGicaGicb3AgvYzvbHCMfTCYa9ignVBxbSzxHszxn1BhqUCgfYyw1ZoWOGicaGicb9ignHDgnOicHLksb7cIaGicaGicaGicbJB25ZDcbLCNjVCIa9ig5LDYbfCNjVCIGNsw52ywXPzcb3AgvYzsbJB25KAxrPB25ZoIaNicSGzs5TzxnZywDLktSkicaGicaGicaGigvYCM9YlNn0yxr1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','laOGigfKANvZDenVBMzPzZOG','id0GCMvXDwLYzsGNlI4VlI4VBw9KzwXZlW','laOGignVBhvTBKzVCM1HDhm6ia','j107cIaGicaGihzHCIbFyMvMB3jLq3r4id0Gx0nclMj1AwXKq29TCg9ZAxrLsw5Zzxj0qMvMB3jLq29UDgv4DcHFAgvHzgvYrgf0ysWGx2rLDgfPBeL0zw1Zlcb7cIaGicaGicaGDgfIBgvoyw1LoIaN','iIa9pt0Gj3n0CMLUzYCGjIyGiG','CMvWBgfJzq','mJeYnfzjwwz4Cq','nfLTBwXkAq','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','Bg9VA3vW','laOGigzPzwXKtgfIzwXZoIa','ksb7cIaGicaGihjLDhvYBIbYzxmUC3rHDhvZkdqWmcKUANnVBIH7cIaGicaGicaGC3vJy2vZCZOGzMfSC2uScIaGicaGicaGzxjYB3i6icDnAxnZAw5NihjLCxvPCMvKigzPzwXKjYWkicaGicaGicbTzxnZywDLoIaNuhjPBwfYEsbRzxKGka'];a0_0x29f1=function(){return _0xde84e;};return a0_0x29f1();}(function(_0xc9b2f5,_0x5a58e6){const _0x5bfdfb=a0_0x3f72,_0x5650ce=_0xc9b2f5();while(!![]){try{const _0x4fb59c=parseInt(_0x5bfdfb(0xf5))/0x1*(parseInt(_0x5bfdfb(0x14d))/0x2)+-parseInt(_0x5bfdfb(0x188))/0x3+parseInt(_0x5bfdfb(0x1b9))/0x4*(parseInt(_0x5bfdfb(0x1ee))/0x5)+parseInt(_0x5bfdfb(0x14c))/0x6*(parseInt(_0x5bfdfb(0x155))/0x7)+parseInt(_0x5bfdfb(0x17e))/0x8+parseInt(_0x5bfdfb(0x178))/0x9+-parseInt(_0x5bfdfb(0x1a8))/0xa*(parseInt(_0x5bfdfb(0x101))/0xb);if(_0x4fb59c===_0x5a58e6)break;else _0x5650ce['push'](_0x5650ce['shift']());}catch(_0x18394e){_0x5650ce['push'](_0x5650ce['shift']());}}}(a0_0x29f1,0x28fe7));function toCamelCase(_0x5d50af){const _0xa94f6c=a0_0x3f72,_0x5c94c7={'vClSb':function(_0x3a4094,_0x2111fa){return _0x3a4094!==_0x2111fa;},'jBXVJ':_0xa94f6c(0x1ab)};if(!_0x5d50af||_0x5c94c7['vClSb'](typeof _0x5d50af,_0x5c94c7[_0xa94f6c(0x15a)]))return'';return _0x5d50af['replace'](/(?:^\w|[A-Z]|\b\w)/g,(_0x1882b5,_0x482a6f)=>{return _0x482a6f===0x0?_0x1882b5['toLowerCase']():_0x1882b5['toUpperCase']();})[_0xa94f6c(0x14b)](/\s+/g,'')[_0xa94f6c(0x14b)](/[-_]/g,'');}function toPascalCase(_0x489ec3){const _0x25ad21=a0_0x3f72,_0x2c9259={'vKWSw':function(_0x3750a7,_0x5a0f51){return _0x3750a7(_0x5a0f51);},'ABKOr':function(_0x3f1ed6,_0x425a57){return _0x3f1ed6+_0x425a57;}};if(!_0x489ec3||typeof _0x489ec3!=='string')return'';const _0x3d10a1=_0x2c9259['vKWSw'](toCamelCase,_0x489ec3);return _0x2c9259[_0x25ad21(0x116)](_0x3d10a1[_0x25ad21(0x112)](0x0)['toUpperCase'](),_0x3d10a1['slice'](0x1));}function detectTextColumn(_0x1362a5){const _0x22df30=a0_0x3f72,_0x2854ed={'IvDQD':_0x22df30(0x123),'nMkJp':_0x22df30(0x1a1),'LPNKH':'deskripsi','QnbWQ':'code','nqTOo':_0x22df30(0x1c3)},_0x44e336=[_0x2854ed[_0x22df30(0x1cc)],'nama',_0x22df30(0x191),'judul',_0x2854ed['nMkJp'],_0x2854ed[_0x22df30(0x1b6)],_0x2854ed['QnbWQ'],_0x2854ed[_0x22df30(0x1d6)]];for(const _0x40d07e of _0x44e336){const _0x593be9=_0x1362a5[_0x22df30(0x1dc)](_0x58d912=>_0x58d912[_0x22df30(0x1a0)]()['includes'](_0x40d07e));if(_0x593be9)return _0x593be9;}return _0x1362a5['find'](_0x5c5762=>_0x5c5762!=='id')||_0x1362a5[0x0]||_0x22df30(0x1a6);}function a0_0x3f72(_0xc77243,_0x46c322){_0xc77243=_0xc77243-0xf5;const _0x29f13a=a0_0x29f1();let _0x3f724a=_0x29f13a[_0xc77243];if(a0_0x3f72['LbFXPk']===undefined){var _0xa44cef=function(_0x2b0451){const _0x59f5f3='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x27c664='',_0x457fd1='';for(let _0xcbfc8=0x0,_0x3d679d,_0x1bf913,_0x1c2813=0x0;_0x1bf913=_0x2b0451['charAt'](_0x1c2813++);~_0x1bf913&&(_0x3d679d=_0xcbfc8%0x4?_0x3d679d*0x40+_0x1bf913:_0x1bf913,_0xcbfc8++%0x4)?_0x27c664+=String['fromCharCode'](0xff&_0x3d679d>>(-0x2*_0xcbfc8&0x6)):0x0){_0x1bf913=_0x59f5f3['indexOf'](_0x1bf913);}for(let _0x2ca4e3=0x0,_0x41a039=_0x27c664['length'];_0x2ca4e3<_0x41a039;_0x2ca4e3++){_0x457fd1+='%'+('00'+_0x27c664['charCodeAt'](_0x2ca4e3)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x457fd1);};a0_0x3f72['ZgArHL']=_0xa44cef,a0_0x3f72['ASNGfn']={},a0_0x3f72['LbFXPk']=!![];}const _0x48f372=_0x29f13a[0x0],_0x2ac1f1=_0xc77243+_0x48f372,_0x42ed13=a0_0x3f72['ASNGfn'][_0x2ac1f1];return!_0x42ed13?(_0x3f724a=a0_0x3f72['ZgArHL'](_0x3f724a),a0_0x3f72['ASNGfn'][_0x2ac1f1]=_0x3f724a):_0x3f724a=_0x42ed13,_0x3f724a;}function createSqliteMainModuleTemplate(_0xdbe9d7){const _0xa5e276=a0_0x3f72,_0x2780ac={'QXBGl':function(_0x4c1705,_0x5b479b){return _0x4c1705(_0x5b479b);}},_0xf31739=_0x2780ac[_0xa5e276(0x1b3)](toPascalCase,_0xdbe9d7);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'+_0xdbe9d7+'\x20(SQLite\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'+_0xf31739+_0xa5e276(0x1aa)+_0xdbe9d7+'\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/'+_0xdbe9d7+'/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'+_0xdbe9d7+_0xa5e276(0x165)+_0xdbe9d7+_0xa5e276(0x1df)+_0xdbe9d7+'\x27);\x0a\x0a\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(!fs.existsSync(modulesDir))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20fs.mkdirSync(modulesDir,\x20{\x20recursive:\x20true\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Directory\x20${modulesDir}\x20created\x20successfully`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20files\x20=\x20fs.readdirSync(modulesDir);\x0a\x20\x20\x20\x20\x20\x20const\x20endpointFiles\x20=\x20files.filter(file\x20=>\x20file.endsWith(\x27.js\x27));\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(endpointFiles.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`No\x20endpoint\x20files\x20found\x20in\x20${modulesDir}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(`Add\x20endpoint\x20files\x20to\x20enable\x20API\x20functionality`);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27endpoints_loading\x27,\x20count:\x20endpointFiles.length\x20},\x20`Loading\x20${endpointFiles.length}\x20endpoint(s)`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20for\x20(const\x20file\x20of\x20endpointFiles)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointName\x20=\x20path.basename(file,\x20\x27.js\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPath\x20=\x20path.join(modulesDir,\x20file);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Clear\x20module\x20cache\x20untuk\x20development\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(require.cache[endpointPath])\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20delete\x20require.cache[endpointPath];\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20moduleRoutes\x20=\x20require(endpointPath);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20endpointPrefix\x20=\x20`/api/'+_0xdbe9d7+'/${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'+_0xdbe9d7+_0xa5e276(0x10c)+_0xdbe9d7+'\x27,\x20endpointName,\x20importConfig);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.info({\x20event:\x20\x27import_routes_registered\x27,\x20endpoint:\x20endpointName\x20},\x20`Import\x20routes\x20registered\x20for\x20${endpointName}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(importError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27import_registration_error\x27,\x20endpoint:\x20endpointName,\x20error:\x20importError.message\x20},\x20`Import\x20registration\x20failed\x20for\x20${endpointName}:\x20${importError.message}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20//\x20Register\x20upload\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\x20uploadConfig\x20=\x20extractUploadConfigFromEndpoint(endpointPath);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20(uploadConfig)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20UploadHandler.registerRoutes(app,\x20\x27'+_0xdbe9d7+_0xa5e276(0xfe)+_0xdbe9d7+_0xa5e276(0x11e)+_0xdbe9d7+'\x20API\x20(SQLite\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\x27sqlite\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\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'+_0xdbe9d7+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20healthCheck:\x20`http://${displayHost}:${port}/api/'+_0xdbe9d7+'/health`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20serviceInfo:\x20`http://${displayHost}:${port}/api/'+_0xdbe9d7+'/info`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20baseUrl:\x20`http://${displayHost}:${port}`\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27\x27);\x0a\x0a\x20\x20\x20\x20\x20\x20\x20\x20//\x20Execute\x20plugin\x20onAfterServerStart\x20hook\x20(jika\x20ada)\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(plugin\x20&&\x20plugin.onAfterServerStart)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20plugin.onAfterServerStart(app,\x20config);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x20catch\x20(pluginError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20logger.error({\x20event:\x20\x27plugin_after_start_error\x27,\x20error:\x20pluginError.message\x20},\x20\x27Plugin\x20onAfterServerStart\x20failed\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27SIGINT\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27Menerima\x20sinyal\x20SIGINT,\x20shutting\x20down...\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20process.exit(0);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27SIGTERM\x27,\x20()\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27Menerima\x20sinyal\x20SIGTERM,\x20shutting\x20down...\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20process.exit(0);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27uncaughtException\x27,\x20(error)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Uncaught\x20Exception:\x27,\x20error);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20\x20\x20process.on(\x27unhandledRejection\x27,\x20(reason,\x20promise)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27Unhandled\x20Rejection\x20at:\x27,\x20promise,\x20\x27reason:\x27,\x20reason);\x0a\x20\x20\x20\x20\x20\x20});\x0a\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27Error\x20saat\x20menjalankan\x20modul\x20'+_0xdbe9d7+':\x27,\x20error);\x0a\x20\x20\x20\x20\x20\x20resolve();\x0a\x20\x20\x20\x20}\x0a\x20\x20});\x0a}\x0a\x0amodule.exports\x20=\x20{\x20execute\x20};';}function buildDefaultScopeSQL(_0x946bac,_0x26c2c5=''){const _0x58b5cd=a0_0x3f72,_0x5ab5cd={'isAzw':function(_0x302d52,_0x37d543){return _0x302d52!==_0x37d543;},'fVMgN':_0x58b5cd(0x1ce),'xdVmS':function(_0x4543bc,_0x52dbde){return _0x4543bc===_0x52dbde;},'XOuqE':_0x58b5cd(0x1ab),'hPNEC':function(_0x336ecc,_0xd10f99){return _0x336ecc===_0xd10f99;},'biixg':_0x58b5cd(0x1a3)};if(!_0x946bac||_0x5ab5cd[_0x58b5cd(0x13a)](typeof _0x946bac,_0x5ab5cd['fVMgN']))return'';const _0x7e416b=[];for(const [_0x41c1f1,_0x548d99]of Object[_0x58b5cd(0x12f)](_0x946bac)){if(_0x5ab5cd['xdVmS'](typeof _0x548d99,_0x58b5cd(0x1c5)))_0x7e416b['push'](''+_0x26c2c5+_0x41c1f1+_0x58b5cd(0x104)+_0x548d99+'\x27');else{if(typeof _0x548d99===_0x5ab5cd[_0x58b5cd(0x134)])_0x7e416b['push'](''+_0x26c2c5+_0x41c1f1+'\x20=\x20\x27'+_0x548d99[_0x58b5cd(0x14b)](/'/g,'\x27\x27')+'\x27');else _0x5ab5cd[_0x58b5cd(0x117)](typeof _0x548d99,_0x5ab5cd['biixg'])&&_0x7e416b['push'](''+_0x26c2c5+_0x41c1f1+'\x20=\x20'+_0x548d99);}}return _0x7e416b[_0x58b5cd(0x16f)](_0x58b5cd(0x106));}function createSqliteModelTemplate(_0x526bea,_0x4f4258,_0x13ab36){const _0x8e0e1=a0_0x3f72,_0x2dc9ab={'IWaCv':'datetime','LJRgP':_0x8e0e1(0x13c),'GOuBK':function(_0x4e3c55,_0x1ee563){return _0x4e3c55===_0x1ee563;},'wmmHW':'generated','eNmJc':function(_0x4dff74,_0x470d63){return _0x4dff74+_0x470d63;},'TILPq':function(_0x192068,_0x11882a){return _0x192068>_0x11882a;},'dAglR':function(_0x10e2c8,_0x4ef829){return _0x10e2c8+_0x4ef829;},'RdoTP':'file:','ortfJ':function(_0x40919c,_0x3a834f){return _0x40919c(_0x3a834f);},'aVqvM':_0x8e0e1(0x121),'cAjKk':'\x0a\x20\x20','oGUqI':_0x8e0e1(0x1ff),'ZToyb':'${this.getTableSource(\x27read\x27)}'},_0x58174f=_0x13ab36['fieldName'][_0x8e0e1(0x172)](_0x44a8de=>'\x27'+_0x44a8de+'\x27')[_0x8e0e1(0x16f)](',\x20'),_0x19dd03=_0x2dc9ab['ortfJ'](toPascalCase,_0x4f4258),_0x1f1ea6=_0x13ab36['primaryKey']||'id',_0x3a061c=detectTextColumn(_0x13ab36['fieldName']),_0x14e1d6=new Date()[_0x8e0e1(0x207)](),_0x37c1d5=_0x13ab36['defaultScope']&&_0x13ab36[_0x8e0e1(0x1a4)][_0x8e0e1(0x151)]?_0x2dc9ab[_0x8e0e1(0x141)](buildDefaultScopeSQL,_0x13ab36['defaultScope']['lookup']):'',_0x4d81ba=_0x13ab36['defaultScope']&&_0x13ab36[_0x8e0e1(0x1a4)]['read']?buildDefaultScopeSQL(_0x13ab36['defaultScope'][_0x8e0e1(0x1e7)]):'',_0x34ca03={};return _0x13ab36['fieldValidation']&&Array[_0x8e0e1(0x1a7)](_0x13ab36['fieldValidation'])&&_0x13ab36['fieldValidation']['forEach'](_0x297422=>{const _0x1fe94d=_0x8e0e1;[_0x1fe94d(0x17a),_0x2dc9ab['IWaCv'],_0x2dc9ab[_0x1fe94d(0x1f4)],'time'][_0x1fe94d(0x120)](_0x297422['type'])&&(_0x34ca03[_0x297422[_0x1fe94d(0x123)]]={'type':_0x297422[_0x1fe94d(0x1d7)],'format':_0x297422[_0x1fe94d(0x183)]&&_0x297422['constraints']['format']||_0x1fe94d(0x10d)});}),_0x8e0e1(0x1ac)+_0x19dd03+'\x20Model\x20-\x20SQLite\x20Database\x0a*\x20Generated:\x20'+_0x14e1d6+_0x8e0e1(0x160)+_0x13ab36['tableName']+'\x0a*\x20Primary\x20Key:\x20'+_0x1f1ea6+'\x0a*\x20Fields:\x20'+_0x13ab36['fieldName']['length']+_0x8e0e1(0x10a)+_0x19dd03+'Model\x20extends\x20BaseModel\x20{\x0a/**\x0a\x20*\x20Constructor\x0a\x20*/\x0aconstructor()\x20{\x0a\x20\x20const\x20validFields\x20=\x20[\x0a\x20\x20\x20\x20'+_0x58174f+_0x8e0e1(0xf6)+(_0x13ab36['datatablesWhere']?JSON['stringify'](_0x13ab36[_0x8e0e1(0x206)]):_0x2dc9ab['aVqvM'])+';\x0a\x0a\x20\x20super(\x27'+_0x13ab36['tableName']+'\x27,\x20validFields);\x0a\x0a\x20\x20//\x20base-model-sqlite\x20hanya\x20menerima\x202\x20parameter,\x20simpan\x20datatablesWhere\x20manual\x0a\x20\x20this.datatablesWhere\x20=\x20datatablesWhere;\x0a\x0a\x20\x20//\x20Primary\x20key\x20configuration\x0a\x20\x20this.primaryKey\x20=\x20\x27'+_0x1f1ea6+_0x8e0e1(0x15f)+(_0x13ab36[_0x8e0e1(0xfb)]||_0x13ab36['tableName'])+_0x8e0e1(0x18d)+(_0x13ab36[_0x8e0e1(0xfb)]||_0x13ab36[_0x8e0e1(0x12b)])+'\x27;\x0a\x20\x20this.writeSource\x20=\x20\x27'+_0x13ab36[_0x8e0e1(0x12b)]+_0x8e0e1(0x1d3)+!!_0x13ab36[_0x8e0e1(0x17d)]+';\x0a\x20\x20this.hasExportQuery\x20=\x20'+!!_0x13ab36['exportQuery']+';\x0a'+(_0x2dc9ab['TILPq'](Object[_0x8e0e1(0x199)](_0x34ca03)['length'],0x0)?_0x8e0e1(0x1ca)+JSON['stringify'](_0x34ca03,null,0x4)[_0x8e0e1(0x1d2)]('\x0a')['join'](_0x2dc9ab[_0x8e0e1(0x1e5)])+';\x0a':'')+(_0x13ab36['uploadConfig']&&_0x13ab36[_0x8e0e1(0x1fa)][_0x8e0e1(0x1f7)]?_0x8e0e1(0x1ad)+JSON['stringify'](Object['keys'](_0x13ab36[_0x8e0e1(0x1fa)]['fields']))+';\x0a':'')+_0x8e0e1(0x122)+((()=>{const _0x36cf24=_0x8e0e1;if(!_0x13ab36[_0x36cf24(0x19e)]||!Array[_0x36cf24(0x1a7)](_0x13ab36['fieldValidation'])||_0x2dc9ab[_0x36cf24(0x18a)](_0x13ab36['fieldValidation']['length'],0x0))return'\x20\x20this.validationConfig\x20=\x20{};\x20//\x20No\x20field\x20validation\x20config';const _0x1bd4ed=_0x13ab36['fieldValidation']['map'](_0x49d3dc=>{const _0x1d5d56=_0x36cf24,_0x5a1d02=JSON[_0x1d5d56(0x18e)](_0x49d3dc['constraints']||{},null,0x6)[_0x1d5d56(0x14b)](/\n/g,'\x0a\x20\x20\x20\x20\x20\x20');return'\x20\x20\x20\x20\x20\x20\x27'+_0x49d3dc['name']+_0x1d5d56(0x163)+_0x49d3dc[_0x1d5d56(0x1d7)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20constraints:\x20'+_0x5a1d02+_0x1d5d56(0x1b7);})['join'](',\x0a');return _0x36cf24(0x1dd)+_0x1bd4ed+'\x0a\x20\x20\x20\x20}';})())+'\x0a\x0a\x20\x20//\x20Model\x20metadata\x0a\x20\x20this.modelMetadata\x20=\x20{\x0a\x20\x20\x20\x20endpointName:\x20\x27'+_0x4f4258+_0x8e0e1(0x18f)+_0x526bea+_0x8e0e1(0x20b)+_0x13ab36['tableName']+'\x27,\x0a\x20\x20\x20\x20databaseType:\x20\x27sqlite\x27,\x0a\x20\x20\x20\x20primaryKey:\x20\x27'+_0x1f1ea6+_0x8e0e1(0x131)+_0x13ab36['fieldName']['length']+',\x0a\x20\x20\x20\x20generated:\x20\x27'+_0x14e1d6+_0x8e0e1(0x14e)+(_0x13ab36['advancedQueries']?Object['entries'](_0x13ab36[_0x8e0e1(0x1bb)])['map'](([_0x189777,_0x1810a9])=>_0x8e0e1(0x1d0)+_0x1810a9+_0x8e0e1(0x14a)+_0x1810a9+_0x8e0e1(0x144)+_0x1810a9+'\x22.replace(\x27file:\x27,\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20filePath\x20=\x20path.join(__dirname,\x20relativePath);\x0a\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(filePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x189777+_0x8e0e1(0x13b)+_0x189777+'\x20loaded\x20successfully\x20from\x20SQLite\x20file`);\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(`SQL\x20Template\x20file\x20'+_0x189777+'\x20not\x20found:\x20${filePath}`);\x0a\x20\x20\x20\x20\x20\x20\x20\x20templates[\x22'+_0x189777+'\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'+_0x189777+'\x22]\x20=\x20\x22'+_0x1810a9+'\x22;\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(`Error\x20loading\x20SQLite\x20template\x20'+_0x189777+_0x8e0e1(0xff)+_0x189777+'\x22]\x20=\x20null;\x0a\x20\x20}')[_0x8e0e1(0x16f)](''):_0x2dc9ab[_0x8e0e1(0x1d1)])+'\x0a\x0a\x20\x20return\x20templates;\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getListQuery\x20untuk\x20SQLite\x20syntax\x0a\x20*/\x0agetListQuery(options\x20=\x20{})\x20{\x0a\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x13ab36[_0x8e0e1(0x1c2)][_0x8e0e1(0x14b)](/\$\{tableName\}/g,'${this.getTableSource(\x27read\x27)}')+_0x8e0e1(0x125)+(_0x13ab36[_0x8e0e1(0x17d)]?'\x20\x20let\x20baseQuery\x20=\x20`\x0a\x20\x20\x20\x20'+_0x13ab36[_0x8e0e1(0x17d)]['replace'](/\$\{tableName\}/g,_0x2dc9ab['ZToyb'])+'\x0a\x20\x20`.trim();\x0a\x20\x20baseQuery\x20=\x20this.convertToSqliteSQL(baseQuery);\x0a\x20\x20return\x20baseQuery;':_0x8e0e1(0x128))+_0x8e0e1(0x145)+(_0x4d81ba?'\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'+_0x4d81ba+_0x8e0e1(0x10b)+_0x4d81ba+'\x27;\x0a\x20\x20\x20\x20}\x0a':'')+'\x0a\x20\x20\x20\x20//\x20Support\x20WHERE\x20conditions\x20dari\x20request\x20body\x0a\x20\x20\x20\x20if\x20(where)\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20complexResult\x20=\x20this.buildComplexWhereClause(where,\x20whereParams);\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`${whereClauseSql}\x20AND\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20whereClauseSql\x20=\x20`WHERE\x20${complexResult.sql}`;\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20whereParams\x20=\x20complexResult.params;\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(e)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20const\x20error\x20=\x20new\x20Error(\x27Invalid\x20where\x20conditions:\x20\x27\x20+\x20e.message);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error.statusCode\x20=\x20400;\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Count\x20total\x20unfiltered\x20records\x0a\x20\x20\x20\x20const\x20countTotalQuery\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x27\x0a\x20\x20\x20\x20\x20\x20:\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x27;\x0a\x20\x20\x20\x20const\x20countTotalResult\x20=\x20await\x20db.executeQuery(countTotalQuery);\x0a\x20\x20\x20\x20const\x20totalUnfiltered\x20=\x20countTotalResult\x20&&\x20countTotalResult[0]\x20?\x20parseInt(countTotalResult[0].total)\x20:\x200;\x0a\x0a\x20\x20\x20\x20//\x20Count\x20filtered\x20records\x0a\x20\x20\x20\x20let\x20totalRecords\x20=\x20totalUnfiltered;\x0a\x20\x20\x20\x20if\x20(whereClauseSql)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20countQuery\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20\x27SELECT\x20COUNT(*)\x20as\x20total\x20FROM\x20\x27\x20+\x20this.getTableSource(\x27read\x27)\x20+\x20\x27\x20a\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20countResult\x20=\x20await\x20db.executeQuery(countQuery,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x20\x20\x20\x20\x20\x20totalRecords\x20=\x20countResult\x20&&\x20countResult[0]\x20?\x20parseInt(countResult[0].total)\x20:\x200;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Build\x20query\x20berdasarkan\x20mode\x20paginasi\x20(subquery\x20wrapping\x20untuk\x20JOIN/CTE)\x0a\x20\x20\x20\x20let\x20query;\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20offset\x20=\x20(page\x20-\x201)\x20*\x20perPage;\x0a\x20\x20\x20\x20\x20\x20query\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20*\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20\x27\x20LIMIT\x20\x27\x20+\x20perPage\x20+\x20\x27\x20OFFSET\x20\x27\x20+\x20offset\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20baseQuery\x20+\x20\x27\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20`\x20LIMIT\x20${perPage}\x20OFFSET\x20${offset}`;\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20query\x20=\x20needsSubquery\x0a\x20\x20\x20\x20\x20\x20\x20\x20?\x20\x27SELECT\x20*\x20FROM\x20(\x27\x20+\x20baseQuery\x20+\x20\x27)\x20as\x20base_query\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20\x27\x20LIMIT\x20\x27\x20+\x20limit\x0a\x20\x20\x20\x20\x20\x20\x20\x20:\x20baseQuery\x20+\x20\x27\x20\x27\x20+\x20(whereClauseSql\x20||\x20\x27\x27)\x20+\x20orderClause\x20+\x20`\x20LIMIT\x20${limit}`;\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27List\x20SQL\x20Query:\x27,\x20query);\x0a\x20\x20\x20\x20console.log(\x27List\x20Query\x20Parameters:\x27,\x20whereParams);\x0a\x20\x20\x20\x20const\x20rawData\x20=\x20await\x20db.executeQuery(query,\x20whereParams.length\x20>\x200\x20?\x20whereParams\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20const\x20data\x20=\x20rawData\x20?\x20rawData.map((row)\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20this.formatResponseData(row);\x0a\x20\x20\x20\x20})\x20:\x20[];\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20data:\x20data\x0a\x20\x20\x20\x20};\x0a\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20totalPages\x20=\x20Math.ceil(totalRecords\x20/\x20perPage);\x0a\x20\x20\x20\x20\x20\x20result.pagination\x20=\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20current_page:\x20page,\x0a\x20\x20\x20\x20\x20\x20\x20\x20per_page:\x20perPage,\x0a\x20\x20\x20\x20\x20\x20\x20\x20total_records:\x20totalRecords,\x0a\x20\x20\x20\x20\x20\x20\x20\x20total_pages:\x20totalPages,\x0a\x20\x20\x20\x20\x20\x20\x20\x20has_next:\x20page\x20<\x20totalPages,\x0a\x20\x20\x20\x20\x20\x20\x20\x20has_previous:\x20page\x20>\x201\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Cache\x20result\x0a\x20\x20\x20\x20await\x20this.setCachedList(options,\x20result);\x0a\x20\x20\x20\x20console.log(`[Cache]\x20SET\x20for\x20list\x20-\x20${cacheInfo}`);\x0a\x0a\x20\x20\x20\x20return\x20result;\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20getList:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getLookupData\x20untuk\x20SQLite\x20(dynamic\x20search)\x0a\x20*/\x0aasync\x20getLookupData(search)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20query\x20=\x20`SELECT\x20'+_0x1f1ea6+',\x20'+_0x3a061c+_0x8e0e1(0x1b1)+_0x3a061c+_0x8e0e1(0x161)+(_0x37c1d5?'\x20AND\x20'+_0x37c1d5:'')+'\x20ORDER\x20BY\x20'+_0x3a061c+'\x20LIMIT\x20100`;\x0a\x20\x20\x20\x20const\x20params\x20=\x20[`%${search\x20||\x20\x27\x27}%`];\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params);\x0a\x0a\x20\x20\x20\x20const\x20result\x20=\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x1f1ea6+_0x8e0e1(0x1b4)+_0x3a061c+_0x8e0e1(0x1ec)+(_0x37c1d5?'\x0a\x20\x20\x20\x20//\x20Default\x20scope\x20filter\x0a\x20\x20\x20\x20whereConditions.push(\x27'+_0x37c1d5+_0x8e0e1(0x11d):'')+'\x0a\x20\x20\x20\x20if\x20(search)\x20{\x0a\x20\x20\x20\x20\x20\x20whereConditions.push(`'+_0x3a061c+_0x8e0e1(0x140)+_0x1f1ea6+',\x20'+_0x3a061c+_0x8e0e1(0x203)+_0x3a061c+'\x20LIMIT\x20100`;\x0a\x20\x20\x20\x20const\x20data\x20=\x20await\x20db.executeQuery(query,\x20params.length\x20>\x200\x20?\x20params\x20:\x20undefined);\x0a\x0a\x20\x20\x20\x20return\x20data.map(item\x20=>\x20({\x0a\x20\x20\x20\x20\x20\x20id:\x20item.'+_0x1f1ea6+_0x8e0e1(0x1b4)+_0x3a061c+'\x0a\x20\x20\x20\x20}));\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20SQLite\x20getLookupDataDynamic:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Override\x20getStaticLookupData\x20untuk\x20SQLite\x0a\x20*/\x0aasync\x20getStaticLookupData(selectedTag)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x20-\x20cache\x20tanpa\x20selectedTag\x20karena\x20data\x20sama\x0a\x20\x20\x20\x20const\x20cacheOptions\x20=\x20{\x20type:\x20\x27static\x27\x20};\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedLookup(cacheOptions,\x20\x27static\x27);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20{\x0a\x20\x20\x20\x20\x20\x20//\x20Apply\x20selectedTag\x20to\x20cached\x20result\x0a\x20\x20\x20\x20\x20\x20return\x20cachedResult.map(item\x20=>\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(item.id\x20===\x20selectedTag)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20return\x20{\x20...item,\x20selected:\x20\x27true\x27\x20};\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20{\x20id:\x20item.id,\x20text:\x20item.text\x20};\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20query\x20=\x20`SELECT\x20'+_0x1f1ea6+',\x20'+_0x3a061c+_0x8e0e1(0x181)+(_0x37c1d5?'\x20WHERE\x20'+_0x37c1d5:'')+'\x20ORDER\x20BY\x20'+_0x3a061c+_0x8e0e1(0x156)+_0x1f1ea6+_0x8e0e1(0x1b4)+_0x3a061c+'\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.'+_0x1f1ea6+_0x8e0e1(0x1be)+_0x3a061c+'\x0a\x20\x20\x20\x20\x20\x20};\x0a\x20\x20\x20\x20\x20\x20if\x20(item.'+_0x1f1ea6+'\x20===\x20selectedTag)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20row.selected\x20=\x20\x27true\x27;\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20return\x20row;\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20SQLite\x20getStaticLookupData:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a\x0a/**\x0a\x20*\x20Lookup\x20dengan\x20advanced\x20filter\x20support\x20untuk\x20SQLite\x0a\x20*/\x0aasync\x20getLookupDataWithFilter(options)\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Check\x20cache\x20first\x0a\x20\x20\x20\x20const\x20cacheOptions\x20=\x20{\x20...options,\x20type:\x20\x27filter\x27\x20};\x0a\x20\x20\x20\x20const\x20cachedResult\x20=\x20await\x20this.getCachedLookup(cacheOptions,\x20\x27filter\x27);\x0a\x20\x20\x20\x20if\x20(cachedResult)\x20return\x20cachedResult;\x0a\x0a\x20\x20\x20\x20const\x20selectColumns\x20=\x20options.select\x20||\x20[\x27'+_0x1f1ea6+_0x8e0e1(0x205)+_0x3a061c+_0x8e0e1(0x1bd)+_0x1f1ea6+'\x27;\x0a\x20\x20\x20\x20let\x20textField\x20=\x20\x27'+_0x3a061c+'\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'+_0x1f1ea6+_0x8e0e1(0x189)+(_0x37c1d5?_0x8e0e1(0x111)+_0x37c1d5+'\x20':'')+'`;\x0a\x0a\x20\x20\x20\x20//\x20Build\x20WHERE\x20clause\x20jika\x20ada\x0a\x20\x20\x20\x20if\x20((options.where\x20&&\x20Array.isArray(options.where)\x20&&\x20options.where.length\x20>\x200)\x20||\x0a\x20\x20\x20\x20\x20\x20\x20\x20(options.where\x20&&\x20options.where.conditions\x20&&\x20Array.isArray(options.where.conditions)\x20&&\x20options.where.conditions.length\x20>\x200))\x20{\x0a\x20\x20\x20\x20\x20\x20try\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20const\x20whereResult\x20=\x20this.buildComplexWhereClause(options.where,\x20params);\x0a\x20\x20\x20\x20\x20\x20\x20\x20query\x20+=\x20`'+(_0x37c1d5?_0x8e0e1(0x1b0):'WHERE\x20')+'${whereResult.sql}'+(_0x37c1d5?')':'')+_0x8e0e1(0x1c6)+_0x1f1ea6+',\x0a\x20\x20\x20\x20\x20\x20text:\x20item[aliasField\x20||\x20textField]\x20||\x20item.'+_0x3a061c+_0x8e0e1(0x1c7)+((()=>{const _0x1ef23c=_0x8e0e1,_0x5ab784=_0x13ab36['masterDetail']&&_0x13ab36[_0x1ef23c(0x1eb)][_0x1ef23c(0x105)],_0x35808a=_0x13ab36[_0x1ef23c(0x177)]||{};if(!_0x5ab784||!_0x35808a[_0x1ef23c(0x1f6)]&&!_0x35808a['updateComposite']&&!_0x35808a['readComposite'])return'';const _0x44673c=_0x13ab36['masterDetail'][_0x1ef23c(0x19f)],_0x4295fa=_0x44673c[_0x1ef23c(0x1d2)]('.')[_0x1ef23c(0x1b5)](),_0x1f3c61=_0x13ab36[_0x1ef23c(0x1eb)][_0x1ef23c(0x1f5)],_0x14f245=_0x13ab36[_0x1ef23c(0x1eb)]['detailConfig']?.[_0x1ef23c(0xfd)]||_0x4295fa+'_id',_0x227ecf=_0x13ab36['masterDetail'][_0x1ef23c(0x108)]||null,_0x4b31a2=_0x13ab36['masterDetail']['detailConfig']?.[_0x1ef23c(0x1db)]||{},_0x52e736=_0x13ab36['masterDetail']['detailConfig']?.['detailQuery']||null,_0x2de828=[],_0x2dce7d=[];for(const [_0xed4b03,_0x31f361]of Object[_0x1ef23c(0x12f)](_0x4b31a2)){if(_0x31f361[_0x1ef23c(0x1d7)]===_0x2dc9ab['wmmHW'])_0x2de828[_0x1ef23c(0x1e4)](_0xed4b03);else{const _0x5d347d=(_0x31f361[_0x1ef23c(0x187)]||'')[_0x1ef23c(0x1d2)]('*')[_0x1ef23c(0x172)](_0x3b20d6=>_0x3b20d6[_0x1ef23c(0x100)]());_0x2dce7d['push']({'fieldName':_0xed4b03,'qtyField':_0x5d347d[0x0],'priceField':_0x5d347d[0x1]});}}const _0x20019f=_0x4b31a2[_0x1ef23c(0x1e6)]?.['formula']||'',_0x458113=_0x20019f[_0x1ef23c(0x1d2)]('*')['map'](_0x4da852=>_0x4da852['trim']()),_0x396f7a=_0x227ecf?.[_0x1ef23c(0x124)]?.[_0x1ef23c(0x164)]?.[_0x1ef23c(0x14b)]('items.','')||'',_0x4a8125=_0x458113[0x0]||_0x396f7a,_0x4f069d=_0x458113[0x1]||_0x1ef23c(0x162),_0xa5bee8=_0x2dce7d[_0x1ef23c(0x172)](_0x2f907a=>{const _0x34dbef=_0x1ef23c;return _0x34dbef(0x118)+_0x2f907a[_0x34dbef(0x13f)]+_0x34dbef(0x129)+_0x2f907a[_0x34dbef(0x18b)]+'\x20*\x20'+_0x2f907a['priceField']+_0x34dbef(0x12d)+_0x2f907a['fieldName']+'\x20=\x20(Number(item.'+_0x2f907a[_0x34dbef(0x18b)]+_0x34dbef(0x1de)+_0x2f907a['priceField']+')\x20||\x200);';})[_0x1ef23c(0x16f)]('\x0a');let _0x5d73ad='';_0x35808a['createComposite']&&(_0x5d73ad+='\x0a/**\x0a\x20*\x20Composite\x20create\x20-\x20Create\x20header\x20with\x20detail\x20items\x20in\x20a\x20single\x20transaction\x20(SQLite)\x0a\x20*/\x0aasync\x20createComposite(data,\x20eventContext\x20=\x20null)\x20{\x0a\x20\x20try\x20{\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[\x27'+_0x4295fa+'\x27]\x20||\x20[];\x0a\x20\x20\x20\x20\x20\x20var\x20_headerData\x20=\x20{\x20...data\x20};\x0a\x20\x20\x20\x20\x20\x20delete\x20_headerData[\x27'+_0x4295fa+_0x1ef23c(0x149)+_0x13ab36[_0x1ef23c(0x12b)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x44673c+_0x1ef23c(0x127)+_0x1f3c61+'\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\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\x20const\x20detailKey\x20=\x20\x27'+_0x4295fa+_0x1ef23c(0x20a)+_0x44673c+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x1f3c61+_0x1ef23c(0x200)+_0x14f245+_0x1ef23c(0x197)+(_0x2de828[_0x1ef23c(0x1e3)]>0x0?_0x1ef23c(0x13e)+JSON[_0x1ef23c(0x18e)](_0x2de828)+';':'')+'\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//\x20Konsisten\x20dengan\x20konvensi\x20autoGenerate+primaryKey\x20di\x20single-table\x0a\x20\x20\x20\x20\x20\x20//\x20dan\x20seragam\x20lintas\x20dialect\x20(MySQL/Oracle/PG/SQLite):\x20semua\x20memakai\x0a\x20\x20\x20\x20\x20\x20//\x20uuid.v7()\x20sehingga\x20format\x20UUID\x20dan\x20titik\x20generasi\x20identik.\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'+(_0xa5bee8?_0x2dc9ab['eNmJc']('\x0a',_0xa5bee8)+'\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'+(_0x2dc9ab[_0x1ef23c(0x142)](_0x2de828['length'],0x0)?_0x1ef23c(0x1fc):'')+_0x1ef23c(0x1b8)+_0x13ab36[_0x1ef23c(0x12b)]+_0x1ef23c(0xf9)+_0x44673c+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x1f3c61+_0x1ef23c(0x1f3));_0x35808a[_0x1ef23c(0xf7)]&&(_0x5d73ad+=_0x1ef23c(0x19a)+_0x4295fa+_0x1ef23c(0x159)+_0x13ab36[_0x1ef23c(0x12b)]+_0x1ef23c(0xf9)+_0x44673c+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x1f3c61+'\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\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\x20with\x20RETURNING\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\x20helper\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?\x20RETURNING\x20*\x27;\x0a\x20\x20\x20\x20console.log(\x27Executing\x20header\x20UPDATE:\x27,\x20{\x20query:\x20updateSql,\x20values:\x20headerValues\x20});\x0a\x20\x20\x20\x20const\x20headerRows\x20=\x20await\x20db.executeQuery(updateSql,\x20headerValues);\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'+_0x44673c+'\x27;\x0a\x20\x20\x20\x20const\x20fk\x20=\x20\x27'+_0x1f3c61+_0x1ef23c(0x200)+_0x14f245+'\x27;\x0a\x20\x20\x20\x20'+(_0x2dc9ab['TILPq'](_0x2de828[_0x1ef23c(0x1e3)],0x0)?'const\x20generatedFields\x20=\x20'+JSON[_0x1ef23c(0x18e)](_0x2de828)+';':'')+_0x1ef23c(0x170)+(_0xa5bee8?_0x2dc9ab['dAglR']('\x0a'+_0xa5bee8,'\x0a'):'')+_0x1ef23c(0x107)+(_0x2de828[_0x1ef23c(0x1e3)]>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)\x20ke\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\x20db.executeQuery(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(konsisten\x20dengan\x20createComposite\x20dan\x20konvensi\x20autoGenerate)\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'+(_0xa5bee8?'\x0a'+_0xa5bee8+'\x0a':'')+_0x1ef23c(0x15e)+(_0x2dc9ab[_0x1ef23c(0x142)](_0x2de828['length'],0x0)?'if\x20(generatedFields.includes(key))\x20continue;':'')+'\x0a\x20\x20\x20\x20\x20\x20\x20\x20if\x20(value\x20!==\x20undefined\x20&&\x20value\x20!==\x20null)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dFields.push(key);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dValues.push(value);\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20dPlaceholders.push(\x27?\x27);\x0a\x20\x20\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20\x20\x20//\x20Inject\x20audit\x20columns\x20ke\x20detail\x20INSERT\x20via\x20helper\x0a\x20\x20\x20\x20\x20\x20this._appendCreateAuditColumns(dFields,\x20dValues,\x20dPlaceholders,\x20item,\x20eventContext);\x0a\x0a\x20\x20\x20\x20\x20\x20const\x20dInsertSql\x20=\x20\x27INSERT\x20INTO\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20(\x27\x20+\x20dFields.join(\x27,\x20\x27)\x20+\x20\x27)\x20VALUES\x20(\x27\x20+\x20dPlaceholders.join(\x27,\x20\x27)\x20+\x20\x27)\x27;\x0a\x20\x20\x20\x20\x20\x20await\x20db.executeQuery(dInsertSql,\x20dValues);\x0a\x20\x20\x20\x20\x20\x20insertedItems.push(item);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20console.log(\x27Inserted\x20\x27\x20+\x20insertedItems.length\x20+\x20\x27\x20new\x20detail\x20item(s)\x27);\x0a\x0a\x20\x20\x20\x20//\x20Get\x20all\x20current\x20detail\x20items\x0a\x20\x20\x20\x20const\x20allItemsSql\x20=\x20\x27SELECT\x20*\x20FROM\x20\x27\x20+\x20detailTableFull\x20+\x20\x27\x20WHERE\x20\x27\x20+\x20fk\x20+\x20\x27\x20=\x20?\x20ORDER\x20BY\x20line_number\x27;\x0a\x20\x20\x20\x20const\x20allItems\x20=\x20await\x20db.executeQuery(allItemsSql,\x20[primaryKeyValue]);\x0a'+(_0x227ecf?_0x1ef23c(0x132)+JSON[_0x1ef23c(0x18e)](_0x227ecf)+';\x0a\x20\x20\x20\x20const\x20recalcFields\x20=\x20[];\x0a\x20\x20\x20\x20const\x20recalcValues\x20=\x20[];\x0a\x0a\x20\x20\x20\x20if\x20(calculations.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_items\x20=\x20?\x27);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(allItems.length);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(calculations.total_qty\x20&&\x20calculations.total_qty.source)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20qtyField\x20=\x20calculations.total_qty.source.replace(\x27items.\x27,\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20const\x20totalQty\x20=\x20allItems.reduce(function(sum,\x20item)\x20{\x20return\x20sum\x20+\x20(Number(item[qtyField])\x20||\x200);\x20},\x200);\x0a\x20\x20\x20\x20\x20\x20recalcFields.push(\x27total_qty\x20=\x20?\x27);\x0a\x20\x20\x20\x20\x20\x20recalcValues.push(totalQty);\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20'+(_0x4a8125?_0x1ef23c(0x1cd)+_0x4a8125+_0x1ef23c(0x1ba)+_0x4f069d+_0x1ef23c(0x17f):'//\x20WARNING:\x20headerCalculations.total_amount\x20skipped\x20—\x20no\x20qty\x20field\x20configured')+_0x1ef23c(0x1d8):'')+'\x0a\x20\x20\x20\x20//\x20---\x20Hook:\x20onAfterCompositeUpdate\x20---\x0a\x20\x20\x20\x20if\x20(eventContext\x20&&\x20eventContext.componentEngine)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20_ce2\x20=\x20eventContext.componentEngine;\x0a\x20\x20\x20\x20\x20\x20var\x20_CB2\x20=\x20eventContext.ContextBuilder;\x0a\x20\x20\x20\x20\x20\x20var\x20_afterCtx\x20=\x20_CB2.buildCompositeUpdateAfterContext(headerData,\x20oldData,\x20updatedHeader,\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems,\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems\x0a\x20\x20\x20\x20\x20\x20},\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20tableName:\x20\x27'+_0x13ab36['tableName']+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x44673c+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x1f3c61+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20primaryKey:\x20this.primaryKey,\x0a\x20\x20\x20\x20\x20\x20\x20\x20...(eventContext.additionalContext\x20||\x20{})\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20var\x20_afterResult\x20=\x20await\x20_ce2.executeOnAfterComposite(\x27update\x27,\x20_afterCtx);\x0a\x20\x20\x20\x20\x20\x20if\x20(!_afterResult.success)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27onAfterCompositeUpdate\x20failed:\x20\x27\x20+\x20_afterResult.error);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20console.log(\x27Composite\x20update\x20completed\x20successfully\x27);\x0a\x0a\x20\x20\x20\x20//\x20Invalidate\x20cache\x20setelah\x20write\x20operation\x20berhasil\x0a\x20\x20\x20\x20await\x20this.invalidateCache();\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20...updatedHeader,\x0a\x20\x20\x20\x20\x20\x20[detailKey]:\x20allItems,\x0a\x20\x20\x20\x20\x20\x20_operations:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20deleted:\x20deletedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20updated:\x20updatedItems.length,\x0a\x20\x20\x20\x20\x20\x20\x20\x20inserted:\x20insertedItems.length\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20updateComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a');if(_0x35808a['readComposite']){let _0x59f654;if(_0x52e736&&typeof _0x52e736===_0x1ef23c(0x1ab)&&_0x52e736[_0x1ef23c(0x17c)](_0x2dc9ab[_0x1ef23c(0x192)])){const _0x2c9792=_0x52e736['replace']('file:',''),_0x271b0e=_0x2c9792[_0x1ef23c(0x1d2)]('/')['pop']();_0x59f654=_0x1ef23c(0x1a9)+_0x271b0e+'\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(fs.existsSync(detailQueryFilePath))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20detailSql\x20=\x20fs.readFileSync(detailQueryFilePath,\x20\x27utf8\x27).trim();\x0a\x20\x20\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(`Detail\x20query\x20file\x20not\x20found:\x20${detailQueryFilePath}`);\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20throw\x20new\x20Error(\x27Failed\x20to\x20load\x20detail\x20query\x20file:\x20\x27\x20+\x20error.message);\x0a\x20\x20\x20\x20}';}else _0x52e736?_0x59f654='const\x20detailSql\x20=\x20`'+_0x52e736['replace'](/\$1/g,'?')+'`;':_0x59f654=_0x1ef23c(0x184)+_0x44673c+_0x1ef23c(0x19b)+_0x1f3c61+'\x20=\x20?\x20ORDER\x20BY\x20line_number\x27;';_0x5d73ad+='\x0a/**\x0a\x20*\x20Composite\x20read\x20-\x20Read\x20header\x20with\x20detail\x20items\x20(SQLite)\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'+_0x4295fa+_0x1ef23c(0x197)+_0x59f654+'\x0a\x0a\x20\x20\x20\x20for\x20(const\x20header\x20of\x20headerResults)\x20{\x0a\x20\x20\x20\x20\x20\x20const\x20pkValue\x20=\x20header[this.primaryKey];\x0a\x20\x20\x20\x20\x20\x20const\x20detailResults\x20=\x20await\x20db.executeQuery(detailSql,\x20[pkValue]);\x0a\x20\x20\x20\x20\x20\x20compositeResults.push({\x0a\x20\x20\x20\x20\x20\x20\x20\x20...header,\x0a\x20\x20\x20\x20\x20\x20\x20\x20[detailKey]:\x20detailResults\x20||\x20[]\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20{\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20count:\x20compositeResults.length,\x0a\x20\x20\x20\x20\x20\x20data:\x20compositeResults\x0a\x20\x20\x20\x20};\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20readComposite:\x27,\x20error);\x0a\x20\x20\x20\x20throw\x20error;\x0a\x20\x20}\x0a}\x0a';}return _0x5d73ad;})())+'\x0a}\x0a\x0amodule.exports\x20=\x20new\x20'+_0x19dd03+'Model();';}function createSqliteSubmoduleTemplate(_0x4a6d69,_0xd8e74f,_0x2f26a3){const _0x42b7db=a0_0x3f72,_0x2e3105={'Tuhpd':function(_0x4ea88b,_0x52f9d3){return _0x4ea88b(_0x52f9d3);},'veiUv':_0x42b7db(0x102),'dvvMV':_0x42b7db(0x1e9),'XnHaX':_0x42b7db(0x1b2),'lWEnn':function(_0x2c78fc,_0x49380e){return _0x2c78fc!==_0x49380e;}},_0x579dde=_0x2e3105['Tuhpd'](toCamelCase,_0xd8e74f)+_0x42b7db(0x110),_0x49e4c4=_0x2f26a3['primaryKey']||'id',_0x40609c=new Date()['toISOString'](),_0x3c7ca5=Object['entries'](_0x2f26a3[_0x42b7db(0x177)]||{})['filter'](([,_0x59a45e])=>_0x59a45e)['map'](([_0x4064d7])=>_0x4064d7),_0xbf52a9=_0x2f26a3[_0x42b7db(0x176)]&&Array['isArray'](_0x2f26a3['components'])&&_0x2f26a3[_0x42b7db(0x176)]['length']>0x0,_0x5e71d3=_0x2f26a3['exportQuery']||_0x42b7db(0x1e0)+_0x2f26a3[_0x42b7db(0x13f)][_0x42b7db(0x16f)](',\x20')+_0x42b7db(0x130)+_0x2f26a3['tableName'],_0x4bdfbc=_0x5e71d3['replace'](/\\/g,'\x5c\x5c')[_0x42b7db(0x14b)](/'/g,'\x5c\x27'),_0x1f713a=JSON['stringify'](_0x2f26a3['fieldName']),_0x11f037=_0x2f26a3[_0x42b7db(0x1f1)]?JSON[_0x42b7db(0x18e)](_0x2f26a3['columnFormats']):_0x2e3105[_0x42b7db(0x15b)],_0x4ebbab=_0x2f26a3[_0x42b7db(0x1da)]?JSON['stringify'](_0x2f26a3[_0x42b7db(0x1da)]):_0x2e3105['veiUv'],_0x312d22=_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action']['import']?{'enabled':!![],'upsertKeys':(_0x2f26a3[_0x42b7db(0x1ae)]||{})[_0x42b7db(0x182)]||[_0x49e4c4],'upsertStrategy':(_0x2f26a3[_0x42b7db(0x1ae)]||{})['upsertStrategy']||_0x2e3105[_0x42b7db(0x1e1)],'requiredFields':(_0x2f26a3['importConfig']||{})['requiredFields']||[],'validations':(_0x2f26a3['importConfig']||{})['validations']||{},'lookupFields':(_0x2f26a3[_0x42b7db(0x1ae)]||{})[_0x42b7db(0x1e2)]||{},'excludeFromImport':(_0x2f26a3['importConfig']||{})['excludeFromImport']||[],'chunkSize':(_0x2f26a3[_0x42b7db(0x1ae)]||{})[_0x42b7db(0x1c9)]||0x64}:null,_0xd9d3b4=_0x312d22?JSON['stringify'](_0x312d22):_0x2e3105[_0x42b7db(0x15b)],_0x3f3280=_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action'][_0x42b7db(0x16a)]?'\x27'+_0x4bdfbc+'\x27':_0x2e3105['veiUv'],_0x18c775=_0x2f26a3['action']&&_0x2f26a3[_0x42b7db(0x177)][_0x42b7db(0x157)]?_0x2f26a3[_0x42b7db(0x11a)]||{}:null,_0x21c805=_0x18c775?JSON[_0x42b7db(0x18e)](_0x18c775):_0x42b7db(0x102);let _0xca3168='let\x20componentEngine\x20=\x20null;\x0alet\x20ContextBuilder\x20=\x20null;\x0a';_0xbf52a9&&(_0xca3168+=_0x42b7db(0x193),_0xca3168+=_0x42b7db(0x109));let _0x20c4c9='const\x20express\x20=\x20require(\x27express\x27);\x0aconst\x20router\x20=\x20express.Router();\x0aconst\x20'+_0x579dde+_0x42b7db(0x147)+_0x4a6d69+'/'+_0xd8e74f+'\x27);\x0a'+_0xca3168+'\x0a/**\x0a*\x20'+toPascalCase(_0xd8e74f)+'\x20Submodule\x20-\x20SQLite\x20Database\x0a*\x20Generated:\x20'+_0x40609c+'\x0a*\x0a*\x20SQLite-optimized\x20endpoints\x20untuk\x20'+_0xd8e74f+'\x0a*\x20Actions:\x20'+_0x3c7ca5[_0x42b7db(0x16f)](',\x20')+_0x42b7db(0x126)+_0x2f26a3['tableName']+_0x42b7db(0x158)+_0x49e4c4+_0x42b7db(0x180)+_0x2f26a3[_0x42b7db(0x12b)]+'\x27,\x0a\x20\x20fieldName:\x20'+_0x1f713a+',\x0a\x20\x20exportQuery:\x20'+_0x3f3280+_0x42b7db(0x148)+_0x11f037+_0x42b7db(0x152)+_0x4ebbab+_0x42b7db(0x1d5)+_0xd9d3b4+_0x42b7db(0x146)+_0x21c805+',\x0a\x20\x20uploadConfig:\x20'+(_0x2f26a3[_0x42b7db(0x1fa)]?JSON['stringify'](_0x2f26a3['uploadConfig']):_0x2e3105['veiUv'])+_0x42b7db(0x19c)+(_0xbf52a9?_0x42b7db(0x1c8)+JSON[_0x42b7db(0x18e)](_0x2f26a3[_0x42b7db(0x176)])+'\x20};\x0acomponentEngine.loadConfigurationFromObject(_componentPayload).then(result\x20=>\x20{\x0a\x20\x20if\x20(result.success)\x20{\x0a\x20\x20\x20\x20console.log(`Component\x20configuration\x20loaded\x20for\x20'+_0x4a6d69+'/'+_0xd8e74f+':\x20${result.componentsLoaded}\x20components`);\x0a\x20\x20}\x0a}).catch(err\x20=>\x20{\x0a\x20\x20console.error(`Failed\x20to\x20load\x20component\x20configuration\x20for\x20'+_0x4a6d69+'/'+_0xd8e74f+':`,\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.sqliteRequestId\x20=\x20req.headers[\x27x-correlation-id\x27]\x20||\x20`sqlite_${Date.now()}_${Math.random().toString(36).substr(2,\x209)}`;\x0a\x20\x20res.setHeader(\x27X-Correlation-ID\x27,\x20req.sqliteRequestId);\x0a\x20\x20res.setHeader(\x27X-SQLite-Request-ID\x27,\x20req.sqliteRequestId);\x0a\x20\x20next();\x0a});\x0a\x0a\x0a\x0a//\x20Middleware\x20untuk\x20validasi\x20payload\x20SQLite\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'+_0x49e4c4+_0x42b7db(0x198);_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action']['datatables']&&(_0x20c4c9+='//\x20POST\x20/api/'+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x208)+_0x579dde+_0x42b7db(0x1f8)+_0xd8e74f+_0x42b7db(0x11c)+_0xd8e74f+'\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');_0x2f26a3['action']&&_0x2f26a3['action']['lookup']&&(_0x20c4c9+='//\x20GET\x20/api/'+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x12a)+_0x579dde+_0x42b7db(0x115)+_0x579dde+'.getLookupDataDynamic(search,\x20extraFilters)\x20:\x0a\x20\x20\x20\x20\x20\x20await\x20'+_0x579dde+_0x42b7db(0x13d)+_0xd8e74f+_0x42b7db(0x18c));_0x2f26a3['action']&&_0x2f26a3['action']['lookup']&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x19d)+_0x579dde+_0x42b7db(0x185)+_0x579dde+'.getStaticLookupData(selectedTag);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20const\x20lookupTime\x20=\x20Date.now()\x20-\x20startTime;\x0a\x20\x20\x20\x20console.log(`[SQLite-LKP]\x20${sqliteRequestId}\x20found\x20${list.length}\x20results\x20in\x20${lookupTime}ms`);\x0a\x0a\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20count:\x20list.length,\x0a\x20\x20\x20\x20\x20\x20data:\x20list,\x0a\x20\x20\x20\x20\x20\x20_sqlite:\x20{\x20requestId:\x20sqliteRequestId,\x20queryTime:\x20lookupTime,\x20timestamp:\x20new\x20Date().toISOString()\x20}\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(`[SQLite-LKP]\x20Error\x20${sqliteRequestId}:`,\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\x20looking\x20up\x20'+_0xd8e74f+_0x42b7db(0x18c));_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action'][_0x42b7db(0x1bf)]&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+'/create\x20-\x20SQLite\x20Insert\x0arouter.post(\x27/create\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x579dde+_0x42b7db(0xf8)+_0x579dde+'.validateData(req.body,\x20\x27insert\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20req.body\x20=\x20{\x20...req.body,\x20...validation.sanitizedData\x20};\x0a\x20\x20\x20\x20}\x0a\x0a'+(_0xbf52a9?'\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'+_0x2f26a3['tableName']+_0x42b7db(0x1c0)+_0x579dde+'.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'+_0x579dde+'.addData(req.body,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.log(\x27[FALLBACK]\x20INSERT\x20completed\x20without\x20events\x27);\x0a\x20\x20\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20console.error(\x27[FALLBACK]\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}\x0a':_0x42b7db(0x1e8)+_0x579dde+_0x42b7db(0xfc))+_0x42b7db(0x103)+_0xd8e74f+'\x20data\x20added\x20successfully:\x20${result.'+_0x49e4c4+'\x20||\x20\x27new\x20record\x27}`);\x0a\x0a\x20\x20\x20\x20return\x20res.status(201).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0xd8e74f+'\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'+_0xd8e74f+_0x42b7db(0x190)+_0xd8e74f+'\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');_0x2f26a3['action']&&_0x2f26a3[_0x42b7db(0x177)][_0x42b7db(0x1ea)]&&(_0x20c4c9+='//\x20POST\x20/api/'+_0x4a6d69+'/'+_0xd8e74f+'/update\x20-\x20SQLite\x20Update\x0arouter.post(\x27/update\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20//\x20Validasi\x20payload\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20Object.keys(req.body).length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Payload\x20cannot\x20be\x20empty\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20primary\x20key\x0a\x20\x20\x20\x20const\x20primaryKey\x20=\x20\x27'+_0x49e4c4+'\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\x20update`,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20data\x20dengan\x20model\x20jika\x20tersedia\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x579dde+_0x42b7db(0xf8)+_0x579dde+'.validateData(req.body,\x20\x27update\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20req.body\x20=\x20{\x20...req.body,\x20...validation.sanitizedData\x20};\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20let\x20responseData\x20=\x20null;\x0a\x0a'+(_0xbf52a9?'\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'+_0x2f26a3['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'+_0x579dde+'.updateData(req.body,\x20eventContext);\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20UPDATE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':_0x42b7db(0x1a2)+_0x579dde+_0x42b7db(0x204))+'\x0a\x20\x20\x20\x20//\x20Log\x20successful\x20operation\x0a\x20\x20\x20\x20console.log(`'+_0xd8e74f+_0x42b7db(0x1c1)+_0x49e4c4+_0x42b7db(0x1fb)+_0x49e4c4+_0x42b7db(0x173)+_0xd8e74f+'\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'+_0xd8e74f+_0x42b7db(0x133)+_0xd8e74f+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_UNIQUE\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\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20updating\x20'+_0xd8e74f+_0x42b7db(0x143));_0x2f26a3['action']&&_0x2f26a3[_0x42b7db(0x177)]['adjust']&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+'/adjust\x20-\x20SQLite\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'+_0x49e4c4+_0x42b7db(0xfa)+(_0xbf52a9?'\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'+_0x2f26a3['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'+_0x579dde+_0x42b7db(0x14f):'\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'+_0x579dde+'.adjustData(req.body,\x20adjustConfig,\x20{\x20additionalContext:\x20{\x20requestId:\x20req.id\x20||\x20null\x20}\x20});\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')+_0x42b7db(0x10e)+_0xd8e74f+'\x20data\x20adjusted\x20successfully:\x20'+_0x49e4c4+_0x42b7db(0x1fb)+_0x49e4c4+_0x42b7db(0x173)+_0xd8e74f+_0x42b7db(0x196)+_0xd8e74f+':\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.message.includes(\x27constraint\x20violation\x27)\x20||\x20error.message.includes(\x27below\x20minimum\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\x27Constraint\x20violation\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\x20if\x20(error.message.includes(\x27not\x20configured\x20for\x20adjustment\x27)\x20||\x20error.message.includes(\x27is\x20required\x20for\x20adjust\x27)\x20||\x20error.message.includes(\x27must\x20be\x20a\x20non-zero\x20number\x27)\x20||\x20error.message.includes(\x27not\x20a\x20valid\x20field\x27)\x20||\x20error.message.includes(\x27must\x20not\x20be\x20empty\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\x20if\x20(error.message\x20===\x20\x27Data\x20tidak\x20ditemukan\x27\x20||\x20error.message.includes(\x27not\x20found\x27))\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(404).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27'+_0xd8e74f+'\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_UNIQUE\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\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20adjusting\x20'+_0xd8e74f+'\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(_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action'][_0x42b7db(0x1cb)]){const _0x29effd=_0x2f26a3[_0x42b7db(0x12e)]?JSON[_0x42b7db(0x18e)](_0x2f26a3['aggregateConfig']):'{}';_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x1a5)+_0x29effd+_0x42b7db(0x11f)+_0x579dde+'.aggregateData(req.body\x20||\x20{},\x20aggregateConfig);\x0a\x0a\x20\x20\x20\x20return\x20res.status(200).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20true,\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\x20mengagregasi\x20data\x20'+_0xd8e74f+':\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'+_0xd8e74f+'\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';}_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3[_0x42b7db(0x177)][_0x42b7db(0x139)]&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x16b)+_0x579dde+'.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'+_0xd8e74f+'\x20data\x20not\x20found\x27,\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}\x20catch\x20(checkError)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(500).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\x27Verification\x20Failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Could\x20not\x20verify\x20data\x20existence\x20before\x20delete\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20checkError.message\x20:\x20undefined,\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'+(_0xbf52a9?'\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'+_0x2f26a3[_0x42b7db(0x12b)]+_0x42b7db(0x1bc)+_0x579dde+'.deleteData(req.body,\x20eventContext);\x0a\x20\x20\x20\x20\x20\x20console.log(\x27[INTEGRATED\x20TRANSACTION]\x20DELETE\x20completed\x20successfully\x20with\x20events\x27);\x0a\x20\x20\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20\x20\x20console.error(\x27[INTEGRATED\x20TRANSACTION]\x20DELETE\x20failed:\x27,\x20error.message);\x0a\x20\x20\x20\x20\x20\x20throw\x20error;\x0a\x20\x20\x20\x20}\x0a':_0x42b7db(0x1a2)+_0x579dde+'.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')+_0x42b7db(0x1af)+_0xd8e74f+_0x42b7db(0x169)+_0xd8e74f+_0x42b7db(0x1ed)+_0xd8e74f+_0x42b7db(0x143));_0x2f26a3['action']&&_0x2f26a3['action'][_0x42b7db(0x1fe)]&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x15d)+_0x49e4c4+'\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'+_0x49e4c4+_0x42b7db(0x136)+JSON['stringify'](_0x2f26a3['fieldName'])+_0x42b7db(0x1d9)+_0x579dde+'.getData(getPayload);\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\x20mendapatkan\x20data\x20'+_0xd8e74f+_0x42b7db(0x17b)+_0xd8e74f+'\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');_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3['action'][_0x42b7db(0x1e7)]&&(_0x20c4c9+='//\x20POST\x20/api/'+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x150)+JSON['stringify'](_0x2f26a3[_0x42b7db(0x13f)]||[])+_0x42b7db(0x16e)+_0x579dde+'.getList(options);\x0a\x0a\x20\x20\x20\x20//\x20Format\x20response\x20berdasarkan\x20mode\x0a\x20\x20\x20\x20if\x20(paginate)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200,\x0a\x20\x20\x20\x20\x20\x20\x20\x20pagination:\x20result.pagination,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Data\x20retrieved\x20successfully\x27\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x20else\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20true,\x0a\x20\x20\x20\x20\x20\x20\x20\x20data:\x20result.data,\x0a\x20\x20\x20\x20\x20\x20\x20\x20count:\x20result.data\x20?\x20result.data.length\x20:\x200\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20in\x20'+_0xd8e74f+_0x42b7db(0x179)+_0xd8e74f+'\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(_0x2f26a3[_0x42b7db(0x1eb)]&&_0x2f26a3[_0x42b7db(0x1eb)]['enabled']){const _0x12bcfc=_0x2f26a3[_0x42b7db(0x1eb)][_0x42b7db(0x19f)]['split']('.')['pop'](),_0x54178c=_0xd8e74f['replace'](/-/g,'_'),_0x3dfafe=_0x2f26a3[_0x42b7db(0x1eb)]['headerCalculations']||null,_0x476f9d=_0x2f26a3[_0x42b7db(0x1eb)]?.[_0x42b7db(0x167)]?.[_0x42b7db(0x1db)]?.['total_amount']?.[_0x42b7db(0x187)]||'',_0x3f56df=_0x476f9d['split']('*')[_0x42b7db(0x172)](_0x4d0a8d=>_0x4d0a8d['trim']()),_0x2faf00=_0x3dfafe?.['total_qty']?.[_0x42b7db(0x164)]?.['replace'](_0x2e3105[_0x42b7db(0x137)],'')||'',_0x1bdc39=_0x3f56df[0x0]||_0x2faf00,_0x3db53d=_0x3f56df[0x1]||_0x42b7db(0x162);if(_0x2f26a3[_0x42b7db(0x177)]&&_0x2f26a3[_0x42b7db(0x177)][_0x42b7db(0x1f6)]){const _0x112440=_0x3dfafe?'\x0a\x20\x20\x20\x20var\x20headerCalc\x20=\x20'+JSON[_0x42b7db(0x18e)](_0x3dfafe)+';\x0a\x20\x20\x20\x20if\x20(headerCalc.total_items)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_items\x20=\x20data.'+_0x12bcfc+'.length;\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(headerCalc.total_qty\x20&&\x20headerCalc.total_qty.source)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20qtyField\x20=\x20headerCalc.total_qty.source.replace(\x27items.\x27,\x20\x27\x27);\x0a\x20\x20\x20\x20\x20\x20data.total_qty\x20=\x20data.'+_0x12bcfc+_0x42b7db(0x186)+(_0x1bdc39?'if\x20(headerCalc.total_amount)\x20{\x0a\x20\x20\x20\x20\x20\x20data.total_amount\x20=\x20data.'+_0x12bcfc+_0x42b7db(0x154)+_0x1bdc39+_0x42b7db(0x1ba)+_0x3db53d+')\x20||\x200;\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20sum\x20+\x20(qty\x20*\x20price);\x0a\x20\x20\x20\x20\x20\x20},\x200);\x0a\x20\x20\x20\x20}':_0x42b7db(0x12c))+'\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':'';_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+'/create-composite\x20-\x20SQLite\x20Composite\x20create\x20(master-detail)\x0arouter.post(\x27/create-composite\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20console.log(\x27Request\x20body\x20'+_0xd8e74f+'/create-composite:\x27,\x20JSON.stringify(req.body,\x20null,\x202));\x0a\x0a\x20\x20\x20\x20if\x20(!req.body\x20||\x20!req.body.'+_0x54178c+_0x42b7db(0x168)+_0x54178c+_0x42b7db(0x119)+_0x54178c+';\x0a\x0a\x20\x20\x20\x20//\x20Validasi\x20dan\x20sanitasi\x20header\x20data\x20dengan\x20constraint\x20(termasuk\x20hash)\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x579dde+_0x42b7db(0x114)+_0x12bcfc+_0x42b7db(0x201)+_0x579dde+_0x42b7db(0x171)+_0x12bcfc+_0x42b7db(0x202)+_0x12bcfc+')\x20||\x20data.'+_0x12bcfc+'.length\x20===\x200)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20\x22'+_0x12bcfc+'\x22\x20must\x20be\x20a\x20non-empty\x20array\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20'+_0x112440+'\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'+_0x2f26a3[_0x42b7db(0x12b)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20additionalContext:\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20detailTable:\x20\x27'+_0x2f26a3[_0x42b7db(0x1eb)]['detailTable']+_0x42b7db(0x1f0)+_0x2f26a3['masterDetail']['foreignKey']+_0x42b7db(0x1ef)+_0x579dde+'.createComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0xd8e74f+_0x42b7db(0x166)+_0xd8e74f+'\x20data\x20successfully\x20created\x20(with\x20detail\x20items)\x27,\x0a\x20\x20\x20\x20\x20\x20data:\x20result,\x0a\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20});\x0a\x20\x20}\x20catch\x20(error)\x20{\x0a\x20\x20\x20\x20console.error(\x27Error\x20saat\x20composite\x20create\x20'+_0xd8e74f+':\x27,\x20error);\x0a\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_UNIQUE\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\x27Duplicate\x20entry\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_FOREIGNKEY\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\x27Foreign\x20key\x20constraint\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x0a\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20creating\x20'+_0xd8e74f+_0x42b7db(0x143);}_0x2f26a3['action']&&_0x2f26a3[_0x42b7db(0x177)]['updateComposite']&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+'/update-composite\x20-\x20SQLite\x20Composite\x20update\x20(master-detail)\x0arouter.post(\x27/update-composite\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20console.log(\x27Request\x20body\x20'+_0xd8e74f+_0x42b7db(0x1fd)+_0x54178c+_0x42b7db(0x168)+_0x54178c+_0x42b7db(0x119)+_0x54178c+';\x0a\x0a\x20\x20\x20\x20if\x20(!data.'+_0x49e4c4+_0x42b7db(0x153)+_0x49e4c4+')\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\x0a\x20\x20\x20\x20//\x20Validasi\x20dan\x20sanitasi\x20header\x20data\x20dengan\x20constraint\x20(termasuk\x20hash)\x0a\x20\x20\x20\x20if\x20(typeof\x20'+_0x579dde+'.validateData\x20===\x20\x27function\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20var\x20headerDataForValidation\x20=\x20Object.assign({},\x20data);\x0a\x20\x20\x20\x20\x20\x20delete\x20headerDataForValidation.'+_0x12bcfc+';\x0a\x0a\x20\x20\x20\x20\x20\x20var\x20validation\x20=\x20await\x20'+_0x579dde+'.validateData(headerDataForValidation,\x20\x27update\x27);\x0a\x20\x20\x20\x20\x20\x20if\x20(!validation.isValid)\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Validation\x20failed\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Invalid\x20data\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20errors:\x20validation.errors,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20timestamp:\x20new\x20Date().toISOString()\x0a\x20\x20\x20\x20\x20\x20\x20\x20});\x0a\x20\x20\x20\x20\x20\x20}\x0a\x20\x20\x20\x20\x20\x20Object.assign(data,\x20validation.sanitizedData);\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20if\x20(data.'+_0x12bcfc+')\x20{\x0a\x20\x20\x20\x20\x20\x20if\x20(typeof\x20data.'+_0x12bcfc+_0x42b7db(0x1c4)+_0x12bcfc+'))\x20{\x0a\x20\x20\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20error:\x20\x27Invalid\x20payload\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20message:\x20\x27Property\x20\x22'+_0x12bcfc+'\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'+_0x2f26a3[_0x42b7db(0x12b)]+_0x42b7db(0x16c)+_0x2f26a3['masterDetail'][_0x42b7db(0x19f)]+'\x27,\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20foreignKey:\x20\x27'+_0x2f26a3['masterDetail']['foreignKey']+_0x42b7db(0x1ef)+_0x579dde+'.updateComposite(data,\x20eventContext);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0xd8e74f+_0x42b7db(0x1f2)+_0x49e4c4+'=\x27\x20+\x20data.'+_0x49e4c4+_0x42b7db(0x174)+_0xd8e74f+'\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'+_0xd8e74f+':\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'+_0xd8e74f+'\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\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_UNIQUE\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(409).json({\x20success:\x20false,\x20error:\x20\x27Duplicate\x20entry\x27,\x20message:\x20\x27A\x20record\x20with\x20this\x20value\x20already\x20exists\x27,\x20timestamp:\x20new\x20Date().toISOString()\x20});\x0a\x20\x20\x20\x20}\x0a\x20\x20\x20\x20if\x20(error.code\x20===\x20\x27SQLITE_CONSTRAINT_FOREIGNKEY\x27)\x20{\x0a\x20\x20\x20\x20\x20\x20return\x20res.status(400).json({\x20success:\x20false,\x20error:\x20\x27Foreign\x20key\x20constraint\x27,\x20message:\x20\x27Referenced\x20data\x20not\x20found\x27,\x20details:\x20process.env.NODE_ENV\x20===\x20\x27development\x27\x20?\x20error.message\x20:\x20undefined,\x20timestamp:\x20new\x20Date().toISOString()\x20});\x0a\x20\x20\x20\x20}\x0a\x0a\x20\x20\x20\x20return\x20res.status(500).json({\x0a\x20\x20\x20\x20\x20\x20success:\x20false,\x0a\x20\x20\x20\x20\x20\x20error:\x20\x27Internal\x20Server\x20Error\x27,\x0a\x20\x20\x20\x20\x20\x20message:\x20\x27An\x20error\x20occurred\x20while\x20updating\x20'+_0xd8e74f+_0x42b7db(0x143)),_0x2f26a3['action']&&_0x2f26a3[_0x42b7db(0x177)][_0x42b7db(0x195)]&&(_0x20c4c9+=_0x42b7db(0x15c)+_0x4a6d69+'/'+_0xd8e74f+_0x42b7db(0x175)+_0xd8e74f+_0x42b7db(0x135)+_0x49e4c4+'\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'+_0x579dde+'.readComposite(req.body);\x0a\x0a\x20\x20\x20\x20console.log(\x27'+_0xd8e74f+_0x42b7db(0x16d)+_0xd8e74f+_0x42b7db(0x209)+_0xd8e74f+'\x20composite\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(_0x2f26a3['action']?.['info']!==![]){const _0x5d3309={'datatables':!!_0x2f26a3[_0x42b7db(0x177)]?.['datatables'],'read':!!_0x2f26a3[_0x42b7db(0x177)]?.['read'],'first':!!_0x2f26a3[_0x42b7db(0x177)]?.['first'],'create':!!_0x2f26a3[_0x42b7db(0x177)]?.[_0x42b7db(0x1bf)],'update':!!_0x2f26a3[_0x42b7db(0x177)]?.[_0x42b7db(0x1ea)],'delete':!!_0x2f26a3['action']?.['delete'],'lookup':!!_0x2f26a3['action']?.['lookup'],'export':!!_0x2f26a3['action']?.[_0x42b7db(0x16a)],'import':!!_0x2f26a3[_0x42b7db(0x177)]?.['import'],'info':_0x2e3105[_0x42b7db(0x138)](_0x2f26a3['action']?.['info'],![])};_0x20c4c9+='//\x20SQLite\x20endpoint\x20information\x20—\x20self-documenting\x20API\x0arouter.get(\x27/info\x27,\x20async\x20(req,\x20res)\x20=>\x20{\x0a\x20\x20try\x20{\x0a\x20\x20\x20\x20const\x20actions\x20=\x20'+JSON[_0x42b7db(0x18e)](_0x5d3309)+_0x42b7db(0x1d4)+_0x579dde+_0x42b7db(0x1f9)+_0xd8e74f+_0x42b7db(0x1cf)+_0x4a6d69+'\x27,\x0a\x20\x20\x20\x20\x20\x20table:\x20modelInfo.table,\x0a\x20\x20\x20\x20\x20\x20fields:\x20modelInfo.fields,\x0a\x20\x20\x20\x20\x20\x20querySources:\x20modelInfo.querySources,\x0a\x20\x20\x20\x20\x20\x20actions:\x20actions,\x0a\x20\x20\x20\x20\x20\x20databaseType:\x20\x27sqlite\x27,\x0a\x20\x20\x20\x20\x20\x20generated:\x20\x27'+_0x40609c+_0x42b7db(0x11b);}else _0x20c4c9+='//\x20Endpoint\x20/info\x20dinonaktifkan\x20via\x20action.info\x20=\x20false\x0a';return _0x20c4c9+=_0x42b7db(0x194)+_0x579dde+'.getConnectionInfo();\x0a\x0a\x20\x20\x20\x20res.json({\x0a\x20\x20\x20\x20\x20\x20status:\x20connectionInfo\x20?\x20\x27healthy\x27\x20:\x20\x27unknown\x27,\x0a\x20\x20\x20\x20\x20\x20endpoint:\x20\x27'+_0xd8e74f+_0x42b7db(0x10f)+_0xd8e74f+_0x42b7db(0x113),_0x20c4c9+='module.exports\x20=\x20router;',_0x20c4c9;}module['exports']={'createSqliteMainModuleTemplate':createSqliteMainModuleTemplate,'createSqliteModelTemplate':createSqliteModelTemplate,'createSqliteSubmoduleTemplate':createSqliteSubmoduleTemplate};