@open-mercato/core 0.6.7-develop.6788.1.c2a1520a30 → 0.6.7-develop.6814.1.0627c7e9f1

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 (394) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_deal_person_link/index.js +2 -0
  3. package/dist/generated/entities/customer_deal_person_link/index.js.map +2 -2
  4. package/dist/generated/entities/customer_user_invitation/index.js +2 -0
  5. package/dist/generated/entities/customer_user_invitation/index.js.map +2 -2
  6. package/dist/generated/entities.ids.generated.js +1 -0
  7. package/dist/generated/entities.ids.generated.js.map +2 -2
  8. package/dist/generated/entity-fields-registry.js +2 -0
  9. package/dist/generated/entity-fields-registry.js.map +2 -2
  10. package/dist/modules/business_rules/api/openmercato-call-options/route.js +85 -0
  11. package/dist/modules/business_rules/api/openmercato-call-options/route.js.map +7 -0
  12. package/dist/modules/business_rules/api/rules/route.js +71 -4
  13. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  14. package/dist/modules/business_rules/components/ActionBuilder.js +47 -4
  15. package/dist/modules/business_rules/components/ActionBuilder.js.map +3 -3
  16. package/dist/modules/business_rules/components/ActionRow.js +106 -16
  17. package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
  18. package/dist/modules/business_rules/components/utils/actionValidation.js +6 -0
  19. package/dist/modules/business_rules/components/utils/actionValidation.js.map +2 -2
  20. package/dist/modules/business_rules/lib/action-executor.js +148 -0
  21. package/dist/modules/business_rules/lib/action-executor.js.map +2 -2
  22. package/dist/modules/business_rules/lib/openmercato-call-options-types.js +5 -0
  23. package/dist/modules/business_rules/lib/openmercato-call-options-types.js.map +7 -0
  24. package/dist/modules/business_rules/lib/openmercato-call-options.js +231 -0
  25. package/dist/modules/business_rules/lib/openmercato-call-options.js.map +7 -0
  26. package/dist/modules/business_rules/lib/rule-engine.js +5 -1
  27. package/dist/modules/business_rules/lib/rule-engine.js.map +2 -2
  28. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js +11 -1
  29. package/dist/modules/communication_channels/api/post/webhook/[provider]/route.js.map +2 -2
  30. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js +11 -6
  31. package/dist/modules/communication_channels/api/post/webhooks/gmail/route.js.map +2 -2
  32. package/dist/modules/configs/lib/upgrade-actions.js +12 -0
  33. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  34. package/dist/modules/customer_accounts/api/admin/users/[id].js +10 -1
  35. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/users-invite.js +61 -4
  37. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/portal/users-invite.js +20 -2
  39. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  40. package/dist/modules/customer_accounts/data/entities.js +3 -0
  41. package/dist/modules/customer_accounts/data/entities.js.map +2 -2
  42. package/dist/modules/customer_accounts/data/validators.js +1 -0
  43. package/dist/modules/customer_accounts/data/validators.js.map +2 -2
  44. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js +26 -0
  45. package/dist/modules/customer_accounts/emails/CustomerInvitationEmail.js.map +7 -0
  46. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js +29 -3
  47. package/dist/modules/customer_accounts/lib/customerEntityOwnership.js.map +2 -2
  48. package/dist/modules/customer_accounts/lib/customerUrl.js +19 -10
  49. package/dist/modules/customer_accounts/lib/customerUrl.js.map +2 -2
  50. package/dist/modules/customer_accounts/lib/invitationEmail.js +35 -0
  51. package/dist/modules/customer_accounts/lib/invitationEmail.js.map +7 -0
  52. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js +13 -0
  53. package/dist/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.js.map +7 -0
  54. package/dist/modules/customer_accounts/services/customerInvitationService.js +32 -2
  55. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  56. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +47 -7
  57. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +2 -2
  58. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  59. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  60. package/dist/modules/customer_accounts/widgets/injection-table.js +1 -1
  61. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  62. package/dist/modules/customers/api/deals/[id]/people/route.js +4 -2
  63. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  64. package/dist/modules/customers/api/deals/[id]/route.js +7 -3
  65. package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
  66. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +11 -1
  67. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  68. package/dist/modules/customers/backend/customers/deals/[id]/page.js +14 -8
  69. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  70. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +21 -6
  71. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  72. package/dist/modules/customers/cli.js +17 -7
  73. package/dist/modules/customers/cli.js.map +2 -2
  74. package/dist/modules/customers/commands/deals.js +48 -10
  75. package/dist/modules/customers/commands/deals.js.map +2 -2
  76. package/dist/modules/customers/commands/people.js +2 -0
  77. package/dist/modules/customers/commands/people.js.map +2 -2
  78. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +26 -6
  79. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  80. package/dist/modules/customers/components/detail/DealDetailTabs.js +30 -8
  81. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  82. package/dist/modules/customers/components/detail/PersonDetailTabs.js +30 -8
  83. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  84. package/dist/modules/customers/data/entities.js +8 -0
  85. package/dist/modules/customers/data/entities.js.map +2 -2
  86. package/dist/modules/customers/data/validators.js +2 -1
  87. package/dist/modules/customers/data/validators.js.map +2 -2
  88. package/dist/modules/customers/migrations/Migration20260723210000_customers.js +15 -0
  89. package/dist/modules/customers/migrations/Migration20260723210000_customers.js.map +7 -0
  90. package/dist/modules/customers/search.js +50 -29
  91. package/dist/modules/customers/search.js.map +2 -2
  92. package/dist/modules/data_sync/api/mappings/[id]/route.js +22 -9
  93. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  94. package/dist/modules/data_sync/api/mappings/route.js +13 -4
  95. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  96. package/dist/modules/data_sync/api/options.js +7 -2
  97. package/dist/modules/data_sync/api/options.js.map +2 -2
  98. package/dist/modules/data_sync/api/run.js +7 -2
  99. package/dist/modules/data_sync/api/run.js.map +2 -2
  100. package/dist/modules/data_sync/api/runs/[id]/cancel.js +8 -3
  101. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  102. package/dist/modules/data_sync/api/runs/[id]/retry.js +7 -2
  103. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  104. package/dist/modules/data_sync/api/runs/[id]/route.js +8 -3
  105. package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
  106. package/dist/modules/data_sync/api/runs.js +7 -2
  107. package/dist/modules/data_sync/api/runs.js.map +2 -2
  108. package/dist/modules/data_sync/api/schedules/[id]/route.js +19 -6
  109. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  110. package/dist/modules/data_sync/api/schedules/route.js +13 -4
  111. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  112. package/dist/modules/data_sync/api/validate.js +9 -4
  113. package/dist/modules/data_sync/api/validate.js.map +2 -2
  114. package/dist/modules/design_system/acl.js +9 -0
  115. package/dist/modules/design_system/acl.js.map +7 -0
  116. package/dist/modules/design_system/backend/design-system/page.js +11 -0
  117. package/dist/modules/design_system/backend/design-system/page.js.map +7 -0
  118. package/dist/modules/design_system/backend/design-system/page.meta.js +15 -0
  119. package/dist/modules/design_system/backend/design-system/page.meta.js.map +7 -0
  120. package/dist/modules/design_system/gallery/components/CodeSnippet.js +52 -0
  121. package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +7 -0
  122. package/dist/modules/design_system/gallery/components/EntryCard.js +63 -0
  123. package/dist/modules/design_system/gallery/components/EntryCard.js.map +7 -0
  124. package/dist/modules/design_system/gallery/components/GalleryShell.js +163 -0
  125. package/dist/modules/design_system/gallery/components/GalleryShell.js.map +7 -0
  126. package/dist/modules/design_system/gallery/components/VariantPreview.js +9 -0
  127. package/dist/modules/design_system/gallery/components/VariantPreview.js.map +7 -0
  128. package/dist/modules/design_system/gallery/entries/banners.js +186 -0
  129. package/dist/modules/design_system/gallery/entries/banners.js.map +7 -0
  130. package/dist/modules/design_system/gallery/entries/buttons.js +361 -0
  131. package/dist/modules/design_system/gallery/entries/buttons.js.map +7 -0
  132. package/dist/modules/design_system/gallery/entries/charts.js +457 -0
  133. package/dist/modules/design_system/gallery/entries/charts.js.map +7 -0
  134. package/dist/modules/design_system/gallery/entries/dates.js +306 -0
  135. package/dist/modules/design_system/gallery/entries/dates.js.map +7 -0
  136. package/dist/modules/design_system/gallery/entries/detail.js +358 -0
  137. package/dist/modules/design_system/gallery/entries/detail.js.map +7 -0
  138. package/dist/modules/design_system/gallery/entries/display.js +785 -0
  139. package/dist/modules/design_system/gallery/entries/display.js.map +7 -0
  140. package/dist/modules/design_system/gallery/entries/feedback.js +658 -0
  141. package/dist/modules/design_system/gallery/entries/feedback.js.map +7 -0
  142. package/dist/modules/design_system/gallery/entries/filters.js +505 -0
  143. package/dist/modules/design_system/gallery/entries/filters.js.map +7 -0
  144. package/dist/modules/design_system/gallery/entries/foundations.js +453 -0
  145. package/dist/modules/design_system/gallery/entries/foundations.js.map +7 -0
  146. package/dist/modules/design_system/gallery/entries/icons.js +140 -0
  147. package/dist/modules/design_system/gallery/entries/icons.js.map +7 -0
  148. package/dist/modules/design_system/gallery/entries/inputs.js +1217 -0
  149. package/dist/modules/design_system/gallery/entries/inputs.js.map +7 -0
  150. package/dist/modules/design_system/gallery/entries/messages.js +334 -0
  151. package/dist/modules/design_system/gallery/entries/messages.js.map +7 -0
  152. package/dist/modules/design_system/gallery/entries/navigation.js +475 -0
  153. package/dist/modules/design_system/gallery/entries/navigation.js.map +7 -0
  154. package/dist/modules/design_system/gallery/entries/notifications.js +257 -0
  155. package/dist/modules/design_system/gallery/entries/notifications.js.map +7 -0
  156. package/dist/modules/design_system/gallery/entries/overlays.js +576 -0
  157. package/dist/modules/design_system/gallery/entries/overlays.js.map +7 -0
  158. package/dist/modules/design_system/gallery/entries/scaffolding.js +378 -0
  159. package/dist/modules/design_system/gallery/entries/scaffolding.js.map +7 -0
  160. package/dist/modules/design_system/gallery/entries/schedule.js +297 -0
  161. package/dist/modules/design_system/gallery/entries/schedule.js.map +7 -0
  162. package/dist/modules/design_system/gallery/registry.js +139 -0
  163. package/dist/modules/design_system/gallery/registry.js.map +7 -0
  164. package/dist/modules/design_system/gallery/types.js +1 -0
  165. package/dist/modules/design_system/gallery/types.js.map +7 -0
  166. package/dist/modules/design_system/index.js +9 -0
  167. package/dist/modules/design_system/index.js.map +7 -0
  168. package/dist/modules/design_system/setup.js +14 -0
  169. package/dist/modules/design_system/setup.js.map +7 -0
  170. package/dist/modules/entities/api/entity-settings.js +59 -9
  171. package/dist/modules/entities/api/entity-settings.js.map +2 -2
  172. package/dist/modules/entities/backend/config/settings/page.js +10 -0
  173. package/dist/modules/entities/backend/config/settings/page.js.map +7 -0
  174. package/dist/modules/entities/backend/config/settings/page.meta.js +36 -0
  175. package/dist/modules/entities/backend/config/settings/page.meta.js.map +7 -0
  176. package/dist/modules/entities/backend/entities/user/create/page.js +16 -7
  177. package/dist/modules/entities/backend/entities/user/create/page.js.map +2 -2
  178. package/dist/modules/entities/components/EntitySettingsManager.js +136 -0
  179. package/dist/modules/entities/components/EntitySettingsManager.js.map +7 -0
  180. package/dist/modules/inbox_ops/api/webhook/inbound.js +14 -10
  181. package/dist/modules/inbox_ops/api/webhook/inbound.js.map +2 -2
  182. package/dist/modules/inbox_ops/search.js +15 -4
  183. package/dist/modules/inbox_ops/search.js.map +2 -2
  184. package/dist/modules/integrations/api/[id]/credentials/route.js +11 -5
  185. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  186. package/dist/modules/integrations/api/[id]/health/route.js +6 -3
  187. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  188. package/dist/modules/integrations/api/[id]/route.js +6 -3
  189. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  190. package/dist/modules/integrations/api/[id]/state/route.js +6 -3
  191. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  192. package/dist/modules/integrations/api/[id]/version/route.js +6 -3
  193. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  194. package/dist/modules/integrations/api/logs/route.js +6 -3
  195. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  196. package/dist/modules/integrations/api/route.js +6 -3
  197. package/dist/modules/integrations/api/route.js.map +2 -2
  198. package/dist/modules/integrations/api/umes-read.js +2 -2
  199. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  200. package/dist/modules/integrations/lib/organization-scope.js +2 -9
  201. package/dist/modules/integrations/lib/organization-scope.js.map +2 -2
  202. package/dist/modules/messages/search.js +10 -5
  203. package/dist/modules/messages/search.js.map +2 -2
  204. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +11 -1
  205. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  206. package/dist/modules/payment_gateways/data/entities.js +4 -0
  207. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  208. package/dist/modules/payment_gateways/lib/gateway-service.js +12 -2
  209. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  210. package/dist/modules/payment_gateways/lib/session-idempotency.js +33 -0
  211. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +2 -2
  212. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js +18 -0
  213. package/dist/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.js.map +7 -0
  214. package/dist/modules/payment_gateways/setup.js +47 -0
  215. package/dist/modules/payment_gateways/setup.js.map +2 -2
  216. package/dist/modules/payment_gateways/workers/session-initialization-prune.js +50 -0
  217. package/dist/modules/payment_gateways/workers/session-initialization-prune.js.map +7 -0
  218. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +134 -0
  219. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +7 -0
  220. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js +11 -0
  221. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.js.map +7 -0
  222. package/dist/modules/portal/lib/navigation.js +7 -0
  223. package/dist/modules/portal/lib/navigation.js.map +7 -0
  224. package/dist/modules/query_index/data/entities.js +2 -1
  225. package/dist/modules/query_index/data/entities.js.map +2 -2
  226. package/dist/modules/query_index/lib/engine.js +75 -50
  227. package/dist/modules/query_index/lib/engine.js.map +2 -2
  228. package/dist/modules/query_index/lib/search-tokens.js +15 -5
  229. package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
  230. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js +17 -0
  231. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js.map +7 -0
  232. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +11 -1
  233. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +2 -2
  234. package/generated/entities/customer_deal_person_link/index.ts +1 -0
  235. package/generated/entities/customer_user_invitation/index.ts +1 -0
  236. package/generated/entities.ids.generated.ts +1 -0
  237. package/generated/entity-fields-registry.ts +2 -0
  238. package/package.json +16 -16
  239. package/src/modules/business_rules/api/openmercato-call-options/route.ts +94 -0
  240. package/src/modules/business_rules/api/rules/route.ts +92 -4
  241. package/src/modules/business_rules/components/ActionBuilder.tsx +54 -5
  242. package/src/modules/business_rules/components/ActionRow.tsx +156 -18
  243. package/src/modules/business_rules/components/utils/actionValidation.ts +6 -0
  244. package/src/modules/business_rules/i18n/de.json +12 -0
  245. package/src/modules/business_rules/i18n/en.json +12 -0
  246. package/src/modules/business_rules/i18n/es.json +12 -0
  247. package/src/modules/business_rules/i18n/pl.json +12 -0
  248. package/src/modules/business_rules/lib/action-executor.ts +198 -0
  249. package/src/modules/business_rules/lib/openmercato-call-options-types.ts +31 -0
  250. package/src/modules/business_rules/lib/openmercato-call-options.ts +297 -0
  251. package/src/modules/business_rules/lib/rule-engine.ts +4 -0
  252. package/src/modules/communication_channels/api/post/webhook/[provider]/route.ts +11 -1
  253. package/src/modules/communication_channels/api/post/webhooks/gmail/route.ts +17 -11
  254. package/src/modules/configs/lib/upgrade-actions.ts +14 -0
  255. package/src/modules/customer_accounts/api/admin/users/[id].ts +16 -1
  256. package/src/modules/customer_accounts/api/admin/users-invite.ts +78 -3
  257. package/src/modules/customer_accounts/api/portal/users-invite.ts +22 -1
  258. package/src/modules/customer_accounts/data/entities.ts +3 -0
  259. package/src/modules/customer_accounts/data/validators.ts +1 -0
  260. package/src/modules/customer_accounts/emails/CustomerInvitationEmail.tsx +45 -0
  261. package/src/modules/customer_accounts/i18n/de.json +6 -0
  262. package/src/modules/customer_accounts/i18n/en.json +6 -0
  263. package/src/modules/customer_accounts/i18n/es.json +6 -0
  264. package/src/modules/customer_accounts/i18n/pl.json +6 -0
  265. package/src/modules/customer_accounts/lib/customerEntityOwnership.ts +68 -2
  266. package/src/modules/customer_accounts/lib/customerUrl.ts +27 -11
  267. package/src/modules/customer_accounts/lib/invitationEmail.ts +42 -0
  268. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +16 -0
  269. package/src/modules/customer_accounts/migrations/Migration20260723120000_customer_accounts.ts +13 -0
  270. package/src/modules/customer_accounts/services/customerInvitationService.ts +56 -3
  271. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +75 -8
  272. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  273. package/src/modules/customer_accounts/widgets/injection-table.ts +4 -1
  274. package/src/modules/customers/api/deals/[id]/people/route.ts +3 -0
  275. package/src/modules/customers/api/deals/[id]/route.ts +10 -2
  276. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +12 -1
  277. package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +1 -0
  278. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -9
  279. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +24 -6
  280. package/src/modules/customers/cli.ts +17 -7
  281. package/src/modules/customers/commands/deals.ts +50 -9
  282. package/src/modules/customers/commands/people.ts +3 -0
  283. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +30 -7
  284. package/src/modules/customers/components/detail/DealDetailTabs.tsx +34 -9
  285. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +34 -9
  286. package/src/modules/customers/data/entities.ts +9 -1
  287. package/src/modules/customers/data/validators.ts +1 -0
  288. package/src/modules/customers/i18n/de.json +18 -0
  289. package/src/modules/customers/i18n/en.json +18 -0
  290. package/src/modules/customers/i18n/es.json +18 -0
  291. package/src/modules/customers/i18n/pl.json +18 -0
  292. package/src/modules/customers/migrations/.snapshot-open-mercato.json +27 -1
  293. package/src/modules/customers/migrations/Migration20260723210000_customers.ts +15 -0
  294. package/src/modules/customers/search.ts +59 -27
  295. package/src/modules/data_sync/AGENTS.md +1 -0
  296. package/src/modules/data_sync/api/mappings/[id]/route.ts +22 -9
  297. package/src/modules/data_sync/api/mappings/route.ts +13 -4
  298. package/src/modules/data_sync/api/options.ts +7 -2
  299. package/src/modules/data_sync/api/run.ts +7 -2
  300. package/src/modules/data_sync/api/runs/[id]/cancel.ts +8 -3
  301. package/src/modules/data_sync/api/runs/[id]/retry.ts +7 -2
  302. package/src/modules/data_sync/api/runs/[id]/route.ts +8 -3
  303. package/src/modules/data_sync/api/runs.ts +7 -2
  304. package/src/modules/data_sync/api/schedules/[id]/route.ts +19 -6
  305. package/src/modules/data_sync/api/schedules/route.ts +13 -4
  306. package/src/modules/data_sync/api/validate.ts +9 -4
  307. package/src/modules/design_system/AGENTS.md +34 -0
  308. package/src/modules/design_system/acl.ts +5 -0
  309. package/src/modules/design_system/backend/design-system/page.meta.ts +11 -0
  310. package/src/modules/design_system/backend/design-system/page.tsx +14 -0
  311. package/src/modules/design_system/gallery/components/CodeSnippet.tsx +55 -0
  312. package/src/modules/design_system/gallery/components/EntryCard.tsx +95 -0
  313. package/src/modules/design_system/gallery/components/GalleryShell.tsx +206 -0
  314. package/src/modules/design_system/gallery/components/VariantPreview.tsx +15 -0
  315. package/src/modules/design_system/gallery/entries/banners.tsx +226 -0
  316. package/src/modules/design_system/gallery/entries/buttons.tsx +422 -0
  317. package/src/modules/design_system/gallery/entries/charts.tsx +518 -0
  318. package/src/modules/design_system/gallery/entries/dates.tsx +325 -0
  319. package/src/modules/design_system/gallery/entries/detail.tsx +403 -0
  320. package/src/modules/design_system/gallery/entries/display.tsx +893 -0
  321. package/src/modules/design_system/gallery/entries/feedback.tsx +746 -0
  322. package/src/modules/design_system/gallery/entries/filters.tsx +570 -0
  323. package/src/modules/design_system/gallery/entries/foundations.tsx +529 -0
  324. package/src/modules/design_system/gallery/entries/icons.tsx +168 -0
  325. package/src/modules/design_system/gallery/entries/inputs.tsx +1516 -0
  326. package/src/modules/design_system/gallery/entries/messages.tsx +339 -0
  327. package/src/modules/design_system/gallery/entries/navigation.tsx +573 -0
  328. package/src/modules/design_system/gallery/entries/notifications.tsx +285 -0
  329. package/src/modules/design_system/gallery/entries/overlays.tsx +677 -0
  330. package/src/modules/design_system/gallery/entries/scaffolding.tsx +430 -0
  331. package/src/modules/design_system/gallery/entries/schedule.tsx +307 -0
  332. package/src/modules/design_system/gallery/registry.ts +152 -0
  333. package/src/modules/design_system/gallery/types.ts +32 -0
  334. package/src/modules/design_system/i18n/.hardcoded-allowlist.json +71 -0
  335. package/src/modules/design_system/i18n/de.json +46 -0
  336. package/src/modules/design_system/i18n/en.json +46 -0
  337. package/src/modules/design_system/i18n/es.json +46 -0
  338. package/src/modules/design_system/i18n/pl.json +46 -0
  339. package/src/modules/design_system/index.ts +7 -0
  340. package/src/modules/design_system/setup.ts +12 -0
  341. package/src/modules/entities/api/entity-settings.ts +66 -22
  342. package/src/modules/entities/backend/config/settings/page.meta.ts +34 -0
  343. package/src/modules/entities/backend/config/settings/page.tsx +12 -0
  344. package/src/modules/entities/backend/entities/user/create/page.tsx +16 -7
  345. package/src/modules/entities/components/EntitySettingsManager.tsx +160 -0
  346. package/src/modules/entities/i18n/de.json +9 -0
  347. package/src/modules/entities/i18n/en.json +9 -0
  348. package/src/modules/entities/i18n/es.json +9 -0
  349. package/src/modules/entities/i18n/pl.json +9 -0
  350. package/src/modules/inbox_ops/api/webhook/inbound.ts +14 -12
  351. package/src/modules/inbox_ops/i18n/de.json +3 -0
  352. package/src/modules/inbox_ops/i18n/en.json +3 -0
  353. package/src/modules/inbox_ops/i18n/es.json +3 -0
  354. package/src/modules/inbox_ops/i18n/pl.json +3 -0
  355. package/src/modules/inbox_ops/search.ts +20 -4
  356. package/src/modules/integrations/api/[id]/credentials/route.ts +11 -5
  357. package/src/modules/integrations/api/[id]/health/route.ts +6 -3
  358. package/src/modules/integrations/api/[id]/route.ts +6 -3
  359. package/src/modules/integrations/api/[id]/state/route.ts +6 -3
  360. package/src/modules/integrations/api/[id]/version/route.ts +6 -3
  361. package/src/modules/integrations/api/logs/route.ts +6 -3
  362. package/src/modules/integrations/api/route.ts +6 -3
  363. package/src/modules/integrations/api/umes-read.ts +2 -2
  364. package/src/modules/integrations/lib/organization-scope.ts +5 -25
  365. package/src/modules/messages/i18n/de.json +2 -0
  366. package/src/modules/messages/i18n/en.json +2 -0
  367. package/src/modules/messages/i18n/es.json +2 -0
  368. package/src/modules/messages/i18n/pl.json +2 -0
  369. package/src/modules/messages/search.ts +11 -5
  370. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +13 -1
  371. package/src/modules/payment_gateways/data/entities.ts +5 -0
  372. package/src/modules/payment_gateways/i18n/de.json +5 -1
  373. package/src/modules/payment_gateways/i18n/en.json +5 -1
  374. package/src/modules/payment_gateways/i18n/es.json +5 -1
  375. package/src/modules/payment_gateways/i18n/pl.json +5 -1
  376. package/src/modules/payment_gateways/lib/gateway-service.ts +12 -2
  377. package/src/modules/payment_gateways/lib/session-idempotency.ts +36 -0
  378. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +10 -1
  379. package/src/modules/payment_gateways/migrations/Migration20260803103035_payment_gateways.ts +17 -0
  380. package/src/modules/payment_gateways/setup.ts +62 -0
  381. package/src/modules/payment_gateways/workers/session-initialization-prune.ts +63 -0
  382. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.meta.ts +9 -0
  383. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +164 -0
  384. package/src/modules/portal/i18n/de.json +18 -0
  385. package/src/modules/portal/i18n/en.json +18 -0
  386. package/src/modules/portal/i18n/es.json +18 -0
  387. package/src/modules/portal/i18n/pl.json +18 -0
  388. package/src/modules/portal/lib/navigation.ts +3 -0
  389. package/src/modules/query_index/data/entities.ts +7 -0
  390. package/src/modules/query_index/lib/engine.ts +92 -63
  391. package/src/modules/query_index/lib/search-tokens.ts +16 -4
  392. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +12 -0
  393. package/src/modules/query_index/migrations/Migration20260731105052_query_index.ts +35 -0
  394. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +11 -1
@@ -0,0 +1,46 @@
1
+ {
2
+ "design_system.entries.button.description": "Disparador de acción principal con toda la escala de variantes y tamaños del DS.",
3
+ "design_system.entries.buttonGroup.description": "Grupo unido de botones de acción relacionados que comparten un borde.",
4
+ "design_system.entries.fancyButton.description": "Botón con degradado, de estilo promocional, para acciones destacadas.",
5
+ "design_system.entries.iconButton.description": "Botón cuadrado solo con icono; siempre requiere un aria-label.",
6
+ "design_system.entries.linkButton.description": "Enlace de texto con estilo de control del DS.",
7
+ "design_system.entries.socialButton.description": "Botón de autenticación con colores de marca para inicio de sesión social.",
8
+ "design_system.families.banners": "Avisos y llamadas",
9
+ "design_system.families.buttons": "Botones",
10
+ "design_system.families.charts": "Gráficos",
11
+ "design_system.families.dates": "Fechas y hora",
12
+ "design_system.families.detail": "Secciones de detalle",
13
+ "design_system.families.display": "Visualización de datos",
14
+ "design_system.families.feedback": "Retroalimentación",
15
+ "design_system.families.filters": "Filtros",
16
+ "design_system.families.foundations": "Fundamentos",
17
+ "design_system.families.icons": "Iconos",
18
+ "design_system.families.inputs": "Campos de formulario",
19
+ "design_system.families.messages": "Mensajes",
20
+ "design_system.families.navigation": "Navegación",
21
+ "design_system.families.notifications": "Notificaciones",
22
+ "design_system.families.overlays": "Superposiciones",
23
+ "design_system.families.scaffolding": "Estructura de página",
24
+ "design_system.families.schedule": "Agenda",
25
+ "design_system.gallery.copied": "Fragmento copiado al portapapeles",
26
+ "design_system.gallery.copy": "Copiar código",
27
+ "design_system.gallery.copyFailed": "No se pudo copiar el fragmento",
28
+ "design_system.gallery.familiesGroup": "Familias",
29
+ "design_system.gallery.familyContents": "Componentes de esta familia",
30
+ "design_system.gallery.iconCopied": "Copiado: {snippet}",
31
+ "design_system.gallery.iconCopyJsx": "Copiar JSX (lucide-react)",
32
+ "design_system.gallery.iconCopyMeta": "Copiar nombre para page.meta icon",
33
+ "design_system.gallery.iconCountAll": "{total} iconos",
34
+ "design_system.gallery.iconCountFiltered": "{visible} de {total} iconos",
35
+ "design_system.gallery.iconSearchPlaceholder": "Filtrar iconos…",
36
+ "design_system.gallery.loadFailed": "No se pudo cargar esta familia",
37
+ "design_system.gallery.noResults": "Ningún componente coincide con la búsqueda",
38
+ "design_system.gallery.openInFigma": "Abrir en Figma",
39
+ "design_system.gallery.retry": "Reintentar",
40
+ "design_system.gallery.searchPlaceholder": "Buscar componentes…",
41
+ "design_system.gallery.usageDo": "Haz",
42
+ "design_system.gallery.usageDont": "Evita",
43
+ "design_system.gallery.viewDocs": "Ver documentación",
44
+ "design_system.nav.group": "Desarrollador",
45
+ "design_system.nav.title": "Sistema de diseño"
46
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "design_system.entries.button.description": "Podstawowy przycisk akcji z pełną skalą wariantów i rozmiarów DS.",
3
+ "design_system.entries.buttonGroup.description": "Połączona grupa powiązanych przycisków akcji ze wspólnym obramowaniem.",
4
+ "design_system.entries.fancyButton.description": "Gradientowy przycisk o marketingowym charakterze dla wyróżnionych akcji.",
5
+ "design_system.entries.iconButton.description": "Kwadratowy przycisk z samą ikoną; zawsze wymaga aria-label.",
6
+ "design_system.entries.linkButton.description": "Tekstowy odnośnik w stylu kontrolki DS.",
7
+ "design_system.entries.socialButton.description": "Przycisk logowania w kolorach marki dla logowania społecznościowego.",
8
+ "design_system.families.banners": "Banery i wskazówki",
9
+ "design_system.families.buttons": "Przyciski",
10
+ "design_system.families.charts": "Wykresy",
11
+ "design_system.families.dates": "Daty i czas",
12
+ "design_system.families.detail": "Sekcje szczegółów",
13
+ "design_system.families.display": "Prezentacja danych",
14
+ "design_system.families.feedback": "Informacje zwrotne",
15
+ "design_system.families.filters": "Filtry",
16
+ "design_system.families.foundations": "Fundamenty",
17
+ "design_system.families.icons": "Ikony",
18
+ "design_system.families.inputs": "Pola formularzy",
19
+ "design_system.families.messages": "Wiadomości",
20
+ "design_system.families.navigation": "Nawigacja",
21
+ "design_system.families.notifications": "Powiadomienia",
22
+ "design_system.families.overlays": "Nakładki",
23
+ "design_system.families.scaffolding": "Szkielet strony",
24
+ "design_system.families.schedule": "Harmonogram",
25
+ "design_system.gallery.copied": "Fragment skopiowany do schowka",
26
+ "design_system.gallery.copy": "Kopiuj kod",
27
+ "design_system.gallery.copyFailed": "Nie udało się skopiować fragmentu",
28
+ "design_system.gallery.familiesGroup": "Rodziny",
29
+ "design_system.gallery.familyContents": "Komponenty w tej rodzinie",
30
+ "design_system.gallery.iconCopied": "Skopiowano: {snippet}",
31
+ "design_system.gallery.iconCopyJsx": "Kopiuj JSX (lucide-react)",
32
+ "design_system.gallery.iconCopyMeta": "Kopiuj nazwę do page.meta icon",
33
+ "design_system.gallery.iconCountAll": "Ikony: {total}",
34
+ "design_system.gallery.iconCountFiltered": "{visible} z {total} ikon",
35
+ "design_system.gallery.iconSearchPlaceholder": "Filtruj ikony…",
36
+ "design_system.gallery.loadFailed": "Nie udało się załadować tej rodziny",
37
+ "design_system.gallery.noResults": "Żaden komponent nie pasuje do wyszukiwania",
38
+ "design_system.gallery.openInFigma": "Otwórz w Figmie",
39
+ "design_system.gallery.retry": "Spróbuj ponownie",
40
+ "design_system.gallery.searchPlaceholder": "Szukaj komponentów…",
41
+ "design_system.gallery.usageDo": "Rób",
42
+ "design_system.gallery.usageDont": "Unikaj",
43
+ "design_system.gallery.viewDocs": "Zobacz dokumentację",
44
+ "design_system.nav.group": "Deweloper",
45
+ "design_system.nav.title": "Design system"
46
+ }
@@ -0,0 +1,7 @@
1
+ import type { ModuleInfo } from '@open-mercato/shared/modules/registry'
2
+
3
+ export const metadata: ModuleInfo = {
4
+ name: 'design_system',
5
+ title: 'Design system gallery',
6
+ description: 'Live, in-app component gallery for the Open Mercato design system.',
7
+ }
@@ -0,0 +1,12 @@
1
+ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
+
3
+ export const setup: ModuleSetupConfig = {
4
+ defaultRoleFeatures: {
5
+ // View-only module: the gallery reads no tenant data, so the grant is safe
6
+ // to hand out broadly. Superadmin is covered by wildcard grants.
7
+ admin: ['design_system.view'],
8
+ employee: ['design_system.view'],
9
+ },
10
+ }
11
+
12
+ export default setup
@@ -3,6 +3,10 @@ import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
6
+ import { beginEntitiesMutationGuard } from './definitions.mutation-guard'
7
+ import { enforceCommandOptimisticLockWithGuards } from '@open-mercato/shared/lib/crud/optimistic-lock-command'
8
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
+ import type { ModuleConfigService } from '../../configs/lib/module-config-service'
6
10
 
7
11
  // Tenant-scoped policy: when true, new custom entities are created with
8
12
  // `access_restricted = true` unless the create request explicitly sets the flag.
@@ -14,24 +18,10 @@ export const metadata = {
14
18
  PUT: { requireAuth: true, requireFeatures: ['entities.definitions.manage'] },
15
19
  }
16
20
 
17
- type ModuleConfigServiceLike = {
18
- getValue: (
19
- moduleId: string,
20
- name: string,
21
- options?: { defaultValue?: unknown; scope?: { tenantId?: string | null } },
22
- ) => Promise<unknown>
23
- setValue: (
24
- moduleId: string,
25
- name: string,
26
- value: unknown,
27
- scope?: { tenantId?: string | null },
28
- ) => Promise<unknown>
29
- }
30
-
31
21
  async function readPolicy(tenantId: string | null): Promise<boolean> {
32
22
  try {
33
23
  const { resolve } = await createRequestContainer()
34
- const moduleConfigService = resolve('moduleConfigService') as ModuleConfigServiceLike
24
+ const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService
35
25
  const value = await moduleConfigService.getValue(CONFIG_MODULE, NEW_ENTITIES_RESTRICTED_KEY, {
36
26
  defaultValue: false,
37
27
  scope: { tenantId },
@@ -45,12 +35,20 @@ async function readPolicy(tenantId: string | null): Promise<boolean> {
45
35
  export async function GET(req: Request) {
46
36
  const auth = await getAuthFromRequest(req)
47
37
  if (!auth || !auth.tenantId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
48
- const newEntitiesRestrictedByDefault = await readPolicy(auth.tenantId ?? null)
49
- return NextResponse.json({ newEntitiesRestrictedByDefault })
38
+ const { resolve } = await createRequestContainer()
39
+ const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService
40
+ const record = await moduleConfigService.getRecord(CONFIG_MODULE, NEW_ENTITIES_RESTRICTED_KEY, {
41
+ tenantId: auth.tenantId ?? null,
42
+ })
43
+ return NextResponse.json({
44
+ newEntitiesRestrictedByDefault: record ? record.value === true : false,
45
+ updatedAt: record ? record.updatedAt : null,
46
+ })
50
47
  }
51
48
 
52
49
  const putBodySchema = z.object({
53
50
  newEntitiesRestrictedByDefault: z.boolean(),
51
+ expectedUpdatedAt: z.string().optional(),
54
52
  })
55
53
 
56
54
  export async function PUT(req: Request) {
@@ -62,19 +60,64 @@ export async function PUT(req: Request) {
62
60
  if (!parsed.success) {
63
61
  return NextResponse.json({ error: 'Validation failed', details: parsed.error.flatten() }, { status: 400 })
64
62
  }
65
- const { resolve } = await createRequestContainer()
66
- const moduleConfigService = resolve('moduleConfigService') as ModuleConfigServiceLike
67
- await moduleConfigService.setValue(
63
+ const container = await createRequestContainer()
64
+ const { resolve } = container
65
+ const moduleConfigService = resolve('moduleConfigService') as ModuleConfigService
66
+
67
+ const currentRecord = await moduleConfigService.getRecord(
68
+ CONFIG_MODULE,
69
+ NEW_ENTITIES_RESTRICTED_KEY,
70
+ { tenantId: auth.tenantId ?? null },
71
+ )
72
+
73
+ const guard = await beginEntitiesMutationGuard({
74
+ container,
75
+ auth,
76
+ req,
77
+ resourceKind: 'entities.settings',
78
+ resourceId: NEW_ENTITIES_RESTRICTED_KEY,
79
+ operation: currentRecord ? 'update' : 'create',
80
+ mutationPayload: parsed.data as unknown as Record<string, unknown>,
81
+ })
82
+ if (guard.blockedResponse) return guard.blockedResponse
83
+
84
+ const ifMatchHeader = req.headers.get('If-Match') || req.headers.get('x-om-ext-optimistic-lock-expected-updated-at')
85
+ const expectedUpdatedAt = ifMatchHeader || parsed.data.expectedUpdatedAt
86
+
87
+ try {
88
+ await enforceCommandOptimisticLockWithGuards(container, {
89
+ resourceKind: 'entities.settings',
90
+ resourceId: NEW_ENTITIES_RESTRICTED_KEY,
91
+ current: currentRecord ? currentRecord.updatedAt : null,
92
+ expected: expectedUpdatedAt,
93
+ request: req,
94
+ })
95
+ } catch (lockError) {
96
+ if (isCrudHttpError(lockError)) {
97
+ return NextResponse.json(lockError.body, { status: lockError.status })
98
+ }
99
+ throw lockError
100
+ }
101
+
102
+ const updatedRecord = await moduleConfigService.setValue(
68
103
  CONFIG_MODULE,
69
104
  NEW_ENTITIES_RESTRICTED_KEY,
70
105
  parsed.data.newEntitiesRestrictedByDefault,
71
106
  { tenantId: auth.tenantId ?? null },
72
107
  )
73
- return NextResponse.json({ ok: true, newEntitiesRestrictedByDefault: parsed.data.newEntitiesRestrictedByDefault })
108
+
109
+ await guard.runAfterSuccess()
110
+
111
+ return NextResponse.json({
112
+ ok: true,
113
+ newEntitiesRestrictedByDefault: parsed.data.newEntitiesRestrictedByDefault,
114
+ updatedAt: updatedRecord ? updatedRecord.updatedAt : null,
115
+ })
74
116
  }
75
117
 
76
118
  const settingsResponseSchema = z.object({
77
119
  newEntitiesRestrictedByDefault: z.boolean(),
120
+ updatedAt: z.string().nullable().optional(),
78
121
  })
79
122
 
80
123
  export const openApi: OpenApiRouteDoc = {
@@ -94,9 +137,10 @@ export const openApi: OpenApiRouteDoc = {
94
137
  description: 'Sets the tenant-scoped default-restricted policy for new custom entities.',
95
138
  requestBody: { schema: putBodySchema },
96
139
  responses: [
97
- { status: 200, description: 'Updated settings', schema: z.object({ ok: z.boolean(), newEntitiesRestrictedByDefault: z.boolean() }) },
140
+ { status: 200, description: 'Updated settings', schema: z.object({ ok: z.boolean(), newEntitiesRestrictedByDefault: z.boolean(), updatedAt: z.string().nullable().optional() }) },
98
141
  { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },
99
142
  { status: 401, description: 'Missing authentication', schema: z.object({ error: z.string() }) },
143
+ { status: 409, description: 'Optimistic lock conflict', schema: z.object({ code: z.string(), error: z.string() }) },
100
144
  ],
101
145
  },
102
146
  },
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+
3
+ const settingsIcon = React.createElement(
4
+ 'svg',
5
+ {
6
+ width: 16,
7
+ height: 16,
8
+ viewBox: '0 0 24 24',
9
+ fill: 'none',
10
+ stroke: 'currentColor',
11
+ strokeWidth: 2,
12
+ strokeLinecap: 'round',
13
+ strokeLinejoin: 'round',
14
+ },
15
+ React.createElement('circle', { cx: 12, cy: 12, r: 3 }),
16
+ React.createElement('path', {
17
+ d: 'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z'
18
+ })
19
+ )
20
+
21
+ export const metadata = {
22
+ requireAuth: true,
23
+ requireFeatures: ['entities.definitions.manage'],
24
+ pageTitle: 'Custom Entities',
25
+ pageTitleKey: 'entities.settings.title',
26
+ pageGroup: 'Module Configs',
27
+ pageGroupKey: 'settings.sections.moduleConfigs',
28
+ pageOrder: 5,
29
+ icon: settingsIcon,
30
+ pageContext: 'settings' as const,
31
+ breadcrumb: [
32
+ { label: 'Custom Entities', labelKey: 'entities.settings.title' },
33
+ ],
34
+ } as const
@@ -0,0 +1,12 @@
1
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
2
+ import { EntitySettingsManager } from '../../../components/EntitySettingsManager'
3
+
4
+ export default function EntitySettingsPage() {
5
+ return (
6
+ <Page>
7
+ <PageBody className="space-y-8">
8
+ <EntitySettingsManager />
9
+ </PageBody>
10
+ </Page>
11
+ )
12
+ }
@@ -10,6 +10,7 @@ import { useRouter } from 'next/navigation'
10
10
  import { pushWithFlash } from '@open-mercato/ui/backend/utils/flash'
11
11
  import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
12
12
  import { LoadingMessage } from '@open-mercato/ui/backend/detail'
13
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
14
 
14
15
  const schema = upsertCustomEntitySchema
15
16
 
@@ -24,15 +25,23 @@ export default function CreateEntityPage() {
24
25
  React.useEffect(() => {
25
26
  let active = true
26
27
  ;(async () => {
27
- const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean }>(
28
- '/api/entities/entity-settings',
29
- undefined,
30
- { errorMessage: '[internal] Failed to load entity settings', fallback: { newEntitiesRestrictedByDefault: false } },
31
- ).catch(() => ({ newEntitiesRestrictedByDefault: false }))
32
- if (active) setDefaultRestricted(data?.newEntitiesRestrictedByDefault === true)
28
+ try {
29
+ const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean }>(
30
+ '/api/entities/entity-settings',
31
+ undefined,
32
+ {
33
+ errorMessage: t('entities.userEntities.errors.settingsLoadFailed', 'Could not load default restriction policy; using default'),
34
+ fallback: { newEntitiesRestrictedByDefault: false },
35
+ },
36
+ )
37
+ if (active) setDefaultRestricted(data?.newEntitiesRestrictedByDefault === true)
38
+ } catch {
39
+ flash(t('entities.userEntities.errors.settingsLoadFailed', 'Could not load default restriction policy; using default'), 'warning')
40
+ if (active) setDefaultRestricted(false)
41
+ }
33
42
  })()
34
43
  return () => { active = false }
35
- }, [])
44
+ }, [t])
36
45
  const fields = React.useMemo<CrudField[]>(() => ([
37
46
  {
38
47
  id: 'entityId',
@@ -0,0 +1,160 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Button } from '@open-mercato/ui/primitives/button'
5
+ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
6
+ import { Label } from '@open-mercato/ui/primitives/label'
7
+ import { Spinner } from '@open-mercato/ui/primitives/spinner'
8
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
9
+ import { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
10
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
11
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
12
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
13
+ import { buildOptimisticLockHeader, extractOptimisticLockConflict } from '@open-mercato/ui/backend/utils/optimisticLock'
14
+ import { withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
15
+
16
+ export function EntitySettingsManager(): React.ReactElement {
17
+ const t = useT()
18
+ const [loading, setLoading] = React.useState(true)
19
+ const [loadError, setLoadError] = React.useState(false)
20
+ const [saving, setSaving] = React.useState(false)
21
+ const [restricted, setRestricted] = React.useState(false)
22
+ const [updatedAt, setUpdatedAt] = React.useState<string | null>(null)
23
+
24
+ const { runMutation } = useGuardedMutation({
25
+ contextId: 'custom-entities-settings',
26
+ })
27
+
28
+ const loadSettings = React.useCallback(async () => {
29
+ setLoading(true)
30
+ setLoadError(false)
31
+ try {
32
+ const data = await readApiResultOrThrow<{ newEntitiesRestrictedByDefault?: boolean; updatedAt?: string | null }>(
33
+ '/api/entities/entity-settings',
34
+ undefined,
35
+ {
36
+ errorMessage: t('entities.settings.errors.loadFailed', 'Failed to load settings'),
37
+ }
38
+ )
39
+ setRestricted(data.newEntitiesRestrictedByDefault === true)
40
+ setUpdatedAt(data.updatedAt ?? null)
41
+ } catch {
42
+ setLoadError(true)
43
+ } finally {
44
+ setLoading(false)
45
+ }
46
+ }, [t])
47
+
48
+ React.useEffect(() => {
49
+ void loadSettings()
50
+ }, [loadSettings])
51
+
52
+ const handleSubmit = async (e: React.FormEvent) => {
53
+ e.preventDefault()
54
+ setSaving(true)
55
+ try {
56
+ const headers = buildOptimisticLockHeader(updatedAt)
57
+ await runMutation({
58
+ operation: () =>
59
+ withScopedApiRequestHeaders(headers, async () => {
60
+ const res = await apiCallOrThrow<{ ok: boolean; newEntitiesRestrictedByDefault: boolean; updatedAt: string | null }>(
61
+ '/api/entities/entity-settings',
62
+ {
63
+ method: 'PUT',
64
+ headers: { 'content-type': 'application/json' },
65
+ body: JSON.stringify({
66
+ newEntitiesRestrictedByDefault: restricted,
67
+ expectedUpdatedAt: updatedAt ?? undefined,
68
+ }),
69
+ },
70
+ {
71
+ errorMessage: t('entities.settings.errors.saveFailed', 'Failed to save settings'),
72
+ }
73
+ )
74
+
75
+ if (res.ok && res.result) {
76
+ setUpdatedAt(res.result.updatedAt)
77
+ flash(t('entities.settings.flash.saved', 'Settings saved'), 'success')
78
+ }
79
+ return res
80
+ }),
81
+ context: {},
82
+ mutationPayload: {
83
+ newEntitiesRestrictedByDefault: restricted,
84
+ },
85
+ })
86
+ } catch (err) {
87
+ const conflict = extractOptimisticLockConflict(err)
88
+ if (conflict) {
89
+ setLoading(true)
90
+ await loadSettings()
91
+ } else {
92
+ flash(t('entities.settings.errors.saveFailed', 'Failed to save settings'), 'error')
93
+ }
94
+ } finally {
95
+ setSaving(false)
96
+ }
97
+ }
98
+
99
+ if (loading) {
100
+ return (
101
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
102
+ <Spinner className="h-4 w-4" />
103
+ <span>{t('entities.userEntities.form.loading', 'Loading…')}</span>
104
+ </div>
105
+ )
106
+ }
107
+
108
+ if (loadError) {
109
+ return (
110
+ <ErrorMessage
111
+ label={t('entities.settings.errors.loadFailed', 'Failed to load settings')}
112
+ action={(
113
+ <Button type="button" variant="outline" size="sm" onClick={() => void loadSettings()}>
114
+ {t('common.retry', 'Retry')}
115
+ </Button>
116
+ )}
117
+ />
118
+ )
119
+ }
120
+
121
+ return (
122
+ <form onSubmit={(e) => void handleSubmit(e)} className="space-y-6 max-w-2xl">
123
+ <div className="space-y-1">
124
+ <h3 className="text-base font-semibold">
125
+ {t('entities.settings.title', 'Custom Entities')}
126
+ </h3>
127
+ <p className="text-sm text-muted-foreground">
128
+ {t('entities.settings.description', 'Manage global custom entity configurations.')}
129
+ </p>
130
+ </div>
131
+
132
+ <div className="rounded-lg border bg-card p-4 space-y-4">
133
+ <div className="flex items-start gap-3">
134
+ <Checkbox
135
+ id="newEntitiesRestrictedByDefault"
136
+ checked={restricted}
137
+ onCheckedChange={(checked) => setRestricted(checked === true)}
138
+ disabled={saving}
139
+ className="mt-1"
140
+ />
141
+ <div className="space-y-1">
142
+ <Label htmlFor="newEntitiesRestrictedByDefault" className="font-medium cursor-pointer">
143
+ {t('entities.settings.newEntitiesRestrictedByDefault.label', 'Restrict record access by default')}
144
+ </Label>
145
+ <p className="text-sm text-muted-foreground">
146
+ {t('entities.settings.newEntitiesRestrictedByDefault.description', 'Require explicit per-entity permissions for records of newly created custom entities by default.')}
147
+ </p>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <div className="flex items-center justify-end gap-3 border-t pt-4">
153
+ <Button type="submit" disabled={saving}>
154
+ {saving ? <Spinner className="mr-2 h-4 w-4" /> : null}
155
+ {t('common.save', 'Save')}
156
+ </Button>
157
+ </div>
158
+ </form>
159
+ )
160
+ }
@@ -196,6 +196,14 @@
196
196
  "entities.nav.systemEntities": "Systementitäten",
197
197
  "entities.nav.userEntities": "Benutzerentitäten",
198
198
  "entities.nav.userEntities.create": "Entität erstellen",
199
+ "entities.settings.description": "Globale Konfigurationen für benutzerdefinierte Entitäten verwalten.",
200
+ "entities.settings.errors.conflict": "Die Einstellungen wurden von einem anderen Benutzer geändert. Bitte neu laden.",
201
+ "entities.settings.errors.loadFailed": "Laden der Einstellungen fehlgeschlagen",
202
+ "entities.settings.errors.saveFailed": "Speichern der Einstellungen fehlgeschlagen",
203
+ "entities.settings.flash.saved": "Einstellungen gespeichert",
204
+ "entities.settings.newEntitiesRestrictedByDefault.description": "Für Datensätze neu erstellter benutzerdefinierter Entitäten standardmäßig explizite Berechtigungen pro Entität erfordern.",
205
+ "entities.settings.newEntitiesRestrictedByDefault.label": "Datensatzzugriff standardmäßig einschränken",
206
+ "entities.settings.title": "Benutzerdefinierte Entitäten",
199
207
  "entities.userEntities.edit.errors.deleteEntityFailed": "Entität konnte nicht gelöscht werden",
200
208
  "entities.userEntities.edit.errors.invalidEntityDescription": "Die angeforderte Entitäts-ID fehlt oder ist ungültig.",
201
209
  "entities.userEntities.edit.errors.invalidEntityId": "Ungültige Entitäts-ID",
@@ -221,6 +229,7 @@
221
229
  "entities.userEntities.errors.entityIdExists": "Entitäts-ID ist bereits vorhanden",
222
230
  "entities.userEntities.errors.entityIdRequired": "Entitäts-ID ist erforderlich",
223
231
  "entities.userEntities.errors.loadFailed": "Entitäten konnten nicht geladen werden",
232
+ "entities.userEntities.errors.settingsLoadFailed": "Standardeinschränkungsrichtlinie konnte nicht geladen werden; Standardwert wird verwendet",
224
233
  "entities.userEntities.flash.created": "Entität erstellt",
225
234
  "entities.userEntities.form.accessRestricted.help": "Erfordert eine explizite entitätsspezifische Berechtigung zum Anzeigen oder Verwalten der Datensätze dieser Entität. Deaktiviert lassen, um allen Benutzern mit der allgemeinen Datensatzberechtigung Zugriff zu gewähren.",
226
235
  "entities.userEntities.form.accessRestricted.label": "Datensatzzugriff einschränken",
@@ -196,6 +196,14 @@
196
196
  "entities.nav.systemEntities": "System Entities",
197
197
  "entities.nav.userEntities": "User Entities",
198
198
  "entities.nav.userEntities.create": "Create Entity",
199
+ "entities.settings.description": "Manage global custom entity configurations.",
200
+ "entities.settings.errors.conflict": "Settings were modified by another user. Please reload.",
201
+ "entities.settings.errors.loadFailed": "Failed to load settings",
202
+ "entities.settings.errors.saveFailed": "Failed to save settings",
203
+ "entities.settings.flash.saved": "Settings saved",
204
+ "entities.settings.newEntitiesRestrictedByDefault.description": "Require explicit per-entity permissions for records of newly created custom entities by default.",
205
+ "entities.settings.newEntitiesRestrictedByDefault.label": "Restrict record access by default",
206
+ "entities.settings.title": "Custom Entities",
199
207
  "entities.userEntities.edit.errors.deleteEntityFailed": "Failed to delete entity",
200
208
  "entities.userEntities.edit.errors.invalidEntityDescription": "The requested entity ID is missing or invalid.",
201
209
  "entities.userEntities.edit.errors.invalidEntityId": "Invalid entity ID",
@@ -221,6 +229,7 @@
221
229
  "entities.userEntities.errors.entityIdExists": "Entity ID already exists",
222
230
  "entities.userEntities.errors.entityIdRequired": "Entity ID is required",
223
231
  "entities.userEntities.errors.loadFailed": "Failed to load entities",
232
+ "entities.userEntities.errors.settingsLoadFailed": "Could not load default restriction policy; using default",
224
233
  "entities.userEntities.flash.created": "Entity created",
225
234
  "entities.userEntities.form.accessRestricted.help": "Require an explicit per-entity permission to view or manage this entity’s records. Leave off to allow anyone with the general records permission.",
226
235
  "entities.userEntities.form.accessRestricted.label": "Restrict record access",
@@ -196,6 +196,14 @@
196
196
  "entities.nav.systemEntities": "Entidades del sistema",
197
197
  "entities.nav.userEntities": "Entidades de usuario",
198
198
  "entities.nav.userEntities.create": "Crear entidad",
199
+ "entities.settings.description": "Administrar configuraciones globales de entidades personalizadas.",
200
+ "entities.settings.errors.conflict": "La configuración fue modificada por otro usuario. Por favor recarga.",
201
+ "entities.settings.errors.loadFailed": "Error al cargar la configuración",
202
+ "entities.settings.errors.saveFailed": "Error al guardar la configuración",
203
+ "entities.settings.flash.saved": "Configuración guardada",
204
+ "entities.settings.newEntitiesRestrictedByDefault.description": "Requerir permisos explícitos por entidad para registros de nuevas entidades creadas por defecto.",
205
+ "entities.settings.newEntitiesRestrictedByDefault.label": "Restringir acceso a registros por defecto",
206
+ "entities.settings.title": "Entidades personalizadas",
199
207
  "entities.userEntities.edit.errors.deleteEntityFailed": "No se pudo eliminar la entidad",
200
208
  "entities.userEntities.edit.errors.invalidEntityDescription": "Falta el ID de la entidad solicitada o no es válido.",
201
209
  "entities.userEntities.edit.errors.invalidEntityId": "ID de entidad no válido",
@@ -221,6 +229,7 @@
221
229
  "entities.userEntities.errors.entityIdExists": "El ID de la entidad ya existe",
222
230
  "entities.userEntities.errors.entityIdRequired": "El ID de la entidad es obligatorio",
223
231
  "entities.userEntities.errors.loadFailed": "No se pudieron cargar las entidades",
232
+ "entities.userEntities.errors.settingsLoadFailed": "No se pudo cargar la política de restricción por defecto; usando valor predeterminado",
224
233
  "entities.userEntities.flash.created": "Entidad creada",
225
234
  "entities.userEntities.form.accessRestricted.help": "Requiere un permiso explícito por entidad para ver o gestionar los registros de esta entidad. Déjalo desactivado para permitir el acceso a cualquiera con el permiso general de registros.",
226
235
  "entities.userEntities.form.accessRestricted.label": "Restringir acceso a registros",
@@ -196,6 +196,14 @@
196
196
  "entities.nav.systemEntities": "Encje systemowe",
197
197
  "entities.nav.userEntities": "Encje użytkownika",
198
198
  "entities.nav.userEntities.create": "Utwórz encję",
199
+ "entities.settings.description": "Zarządzaj globalnymi ustawieniami niestandardowych encji.",
200
+ "entities.settings.errors.conflict": "Ustawienia zostały zmodyfikowane przez innego użytkownika. Proszę przeładować.",
201
+ "entities.settings.errors.loadFailed": "Nie udało się załadować ustawień",
202
+ "entities.settings.errors.saveFailed": "Nie udało się zapisać ustawień",
203
+ "entities.settings.flash.saved": "Ustawienia zostały zapisane",
204
+ "entities.settings.newEntitiesRestrictedByDefault.description": "Domyślnie wymagaj jawnych uprawnień dla poszczególnych encji do rekordów nowo tworzonych niestandardowych encji.",
205
+ "entities.settings.newEntitiesRestrictedByDefault.label": "Domyślnie ograniczaj dostęp do rekordów",
206
+ "entities.settings.title": "Niestandardowe encje",
199
207
  "entities.userEntities.edit.errors.deleteEntityFailed": "Nie udało się usunąć encji",
200
208
  "entities.userEntities.edit.errors.invalidEntityDescription": "Brakuje identyfikatora encji albo jest on nieprawidłowy.",
201
209
  "entities.userEntities.edit.errors.invalidEntityId": "Nieprawidłowy identyfikator encji",
@@ -221,6 +229,7 @@
221
229
  "entities.userEntities.errors.entityIdExists": "Identyfikator encji już istnieje",
222
230
  "entities.userEntities.errors.entityIdRequired": "Identyfikator encji jest wymagany",
223
231
  "entities.userEntities.errors.loadFailed": "Nie udało się wczytać encji",
232
+ "entities.userEntities.errors.settingsLoadFailed": "Nie udało się załadować domyślnej polityki ograniczeń; używanie wartości domyślnej",
224
233
  "entities.userEntities.flash.created": "Encja została utworzona",
225
234
  "entities.userEntities.form.accessRestricted.help": "Wymaga jawnego uprawnienia dla tej encji, aby przeglądać lub zarządzać jej rekordami. Pozostaw wyłączone, aby zezwolić na dostęp każdemu z ogólnym uprawnieniem do rekordów.",
226
235
  "entities.userEntities.form.accessRestricted.label": "Ogranicz dostęp do rekordów",
@@ -13,6 +13,11 @@ import { parseInboundEmail } from '../../lib/emailParser'
13
13
  import { checkRateLimit } from '../../lib/rateLimiter'
14
14
  import { emitInboxOpsEvent } from '../../events'
15
15
  import { createLogger } from '@open-mercato/shared/lib/logger'
16
+ import {
17
+ readBoundedRequestBody,
18
+ resolveWebhookBodyLimitBytes,
19
+ WebhookBodyTooLargeError,
20
+ } from '@open-mercato/shared/lib/webhooks'
16
21
 
17
22
  const logger = createLogger('inbox_ops').child({ component: 'webhook' })
18
23
 
@@ -20,7 +25,10 @@ export const metadata = {
20
25
  POST: { requireAuth: false },
21
26
  }
22
27
 
23
- const MAX_PAYLOAD_SIZE = 2 * 1024 * 1024 // 2MB
28
+ const MAX_PAYLOAD_SIZE = resolveWebhookBodyLimitBytes(
29
+ process.env.INBOX_OPS_WEBHOOK_MAX_BODY_BYTES ?? process.env.OM_WEBHOOK_MAX_BODY_BYTES,
30
+ 2 * 1024 * 1024,
31
+ )
24
32
  const REPLAY_WINDOW_MS = 5 * 60 * 1000 // 5 minutes
25
33
 
26
34
  function isTimestampFresh(timestamp: string): boolean {
@@ -211,22 +219,16 @@ export async function POST(req: Request) {
211
219
  return NextResponse.json({ error: 'Service unavailable' }, { status: 503 })
212
220
  }
213
221
 
214
- const contentLength = parseInt(req.headers.get('content-length') || '0', 10)
215
- if (contentLength > MAX_PAYLOAD_SIZE) {
216
- return NextResponse.json({ error: 'Payload too large' }, { status: 413 })
217
- }
218
-
219
222
  let rawBody: string
220
223
  try {
221
- rawBody = await req.text()
222
- } catch {
224
+ rawBody = await readBoundedRequestBody(req, { maxBytes: MAX_PAYLOAD_SIZE })
225
+ } catch (error) {
226
+ if (error instanceof WebhookBodyTooLargeError) {
227
+ return NextResponse.json({ error: 'Payload too large' }, { status: 413 })
228
+ }
223
229
  return NextResponse.json({ error: 'Bad request' }, { status: 400 })
224
230
  }
225
231
 
226
- if (rawBody.length > MAX_PAYLOAD_SIZE) {
227
- return NextResponse.json({ error: 'Payload too large' }, { status: 413 })
228
- }
229
-
230
232
  const verified = await verifyAndParse(req, rawBody)
231
233
  if (!verified.ok) {
232
234
  return verified.response