@open-mercato/core 0.6.6-develop.6089.1.a7ed560528 → 0.6.6-develop.6121.1.5797a901b3

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 (320) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/catalog_product/index.js +52 -0
  3. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  4. package/dist/generated/entities/catalog_product_variant/index.js +4 -0
  5. package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +28 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/api/products/route.js +103 -33
  9. package/dist/modules/catalog/api/products/route.js.map +2 -2
  10. package/dist/modules/catalog/api/variants/route.js +4 -0
  11. package/dist/modules/catalog/api/variants/route.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
  15. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  16. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
  17. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
  18. package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
  19. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +251 -14
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/variants.js +72 -1
  23. package/dist/modules/catalog/commands/variants.js.map +2 -2
  24. package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
  25. package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
  26. package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
  27. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  28. package/dist/modules/catalog/components/products/productForm.js +164 -2
  29. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  30. package/dist/modules/catalog/components/products/variantForm.js +2 -0
  31. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  32. package/dist/modules/catalog/data/entities.js +93 -0
  33. package/dist/modules/catalog/data/entities.js.map +2 -2
  34. package/dist/modules/catalog/data/types.js +28 -0
  35. package/dist/modules/catalog/data/types.js.map +2 -2
  36. package/dist/modules/catalog/data/validators.js +100 -5
  37. package/dist/modules/catalog/data/validators.js.map +2 -2
  38. package/dist/modules/catalog/lib/gtin.js +61 -0
  39. package/dist/modules/catalog/lib/gtin.js.map +7 -0
  40. package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
  41. package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
  42. package/dist/modules/catalog/translations.js +1 -1
  43. package/dist/modules/catalog/translations.js.map +2 -2
  44. package/dist/modules/configs/api/cache/route.js +60 -10
  45. package/dist/modules/configs/api/cache/route.js.map +3 -3
  46. package/dist/modules/configs/components/CachePanel.js +52 -29
  47. package/dist/modules/configs/components/CachePanel.js.map +2 -2
  48. package/dist/modules/currencies/api/currencies/options/route.js +18 -4
  49. package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
  50. package/dist/modules/currencies/services/rateFetchingService.js +35 -18
  51. package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
  55. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  56. package/dist/modules/customers/backend/calendar/page.js +10 -0
  57. package/dist/modules/customers/backend/calendar/page.js.map +7 -0
  58. package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
  59. package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
  60. package/dist/modules/customers/cli.js +1 -0
  61. package/dist/modules/customers/cli.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
  63. package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
  65. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
  66. package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
  67. package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
  68. package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
  69. package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
  70. package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
  71. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
  72. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
  73. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
  74. package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
  75. package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
  76. package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
  77. package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
  78. package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
  79. package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
  80. package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
  81. package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
  82. package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
  83. package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
  85. package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
  87. package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
  88. package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
  89. package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
  90. package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
  91. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
  92. package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
  93. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
  94. package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
  95. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
  96. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
  97. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
  98. package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
  99. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
  100. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
  101. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
  102. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
  103. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
  104. package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
  105. package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
  106. package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
  107. package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
  108. package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
  109. package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
  110. package/dist/modules/customers/components/calendar/types.js +30 -0
  111. package/dist/modules/customers/components/calendar/types.js.map +7 -0
  112. package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
  113. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
  114. package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
  115. package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/categories.js +28 -0
  117. package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
  119. package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
  120. package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
  121. package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
  122. package/dist/modules/customers/lib/calendar/grid.js +48 -0
  123. package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
  124. package/dist/modules/customers/lib/calendar/layout.js +39 -0
  125. package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
  126. package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
  127. package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
  128. package/dist/modules/customers/lib/calendar/preferences.js +71 -0
  129. package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
  130. package/dist/modules/customers/lib/calendar/range.js +46 -0
  131. package/dist/modules/customers/lib/calendar/range.js.map +7 -0
  132. package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
  133. package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
  134. package/dist/modules/dashboards/services/widgetDataService.js +5 -5
  135. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  136. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
  137. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  138. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
  139. package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
  140. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
  141. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  142. package/dist/modules/dictionaries/api/route.js +33 -1
  143. package/dist/modules/dictionaries/api/route.js.map +2 -2
  144. package/dist/modules/feature_toggles/lib/queries.js +36 -52
  145. package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
  146. package/dist/modules/messages/commands/actions.js +81 -9
  147. package/dist/modules/messages/commands/actions.js.map +2 -2
  148. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
  149. package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
  150. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
  151. package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
  152. package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
  153. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  154. package/dist/modules/perspectives/services/perspectiveService.js +5 -3
  155. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  156. package/dist/modules/planner/api/access.js +9 -0
  157. package/dist/modules/planner/api/access.js.map +2 -2
  158. package/dist/modules/planner/api/availability-date-specific.js +22 -1
  159. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  160. package/dist/modules/planner/api/availability-weekly.js +22 -1
  161. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  162. package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
  163. package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
  164. package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
  165. package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
  166. package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
  167. package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
  168. package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
  169. package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
  170. package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
  171. package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
  172. package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
  173. package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
  174. package/dist/modules/staff/api/team-members/self/route.js +40 -0
  175. package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
  176. package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
  177. package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
  178. package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
  179. package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
  180. package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
  181. package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
  182. package/dist/modules/translations/api/context.js +7 -0
  183. package/dist/modules/translations/api/context.js.map +2 -2
  184. package/dist/modules/translations/api/put/locales.js +33 -1
  185. package/dist/modules/translations/api/put/locales.js.map +2 -2
  186. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
  187. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  188. package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
  189. package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
  190. package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
  191. package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
  192. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
  193. package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
  194. package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
  195. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  196. package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
  197. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
  198. package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
  199. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
  200. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  201. package/dist/modules/workflows/lib/graph-utils.js +8 -0
  202. package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
  203. package/generated/entities/catalog_product/index.ts +26 -0
  204. package/generated/entities/catalog_product_variant/index.ts +2 -0
  205. package/generated/entity-fields-registry.ts +28 -0
  206. package/package.json +7 -7
  207. package/src/modules/catalog/api/products/route.ts +123 -39
  208. package/src/modules/catalog/api/variants/route.ts +4 -0
  209. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
  210. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
  211. package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
  212. package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
  213. package/src/modules/catalog/commands/products.ts +307 -2
  214. package/src/modules/catalog/commands/variants.ts +90 -1
  215. package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
  216. package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
  217. package/src/modules/catalog/components/products/productForm.ts +262 -0
  218. package/src/modules/catalog/components/products/variantForm.ts +4 -0
  219. package/src/modules/catalog/data/entities.ts +97 -0
  220. package/src/modules/catalog/data/types.ts +36 -0
  221. package/src/modules/catalog/data/validators.ts +159 -3
  222. package/src/modules/catalog/i18n/de.json +64 -0
  223. package/src/modules/catalog/i18n/en.json +64 -0
  224. package/src/modules/catalog/i18n/es.json +64 -0
  225. package/src/modules/catalog/i18n/pl.json +64 -0
  226. package/src/modules/catalog/lib/gtin.ts +66 -0
  227. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
  228. package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
  229. package/src/modules/catalog/translations.ts +1 -1
  230. package/src/modules/configs/api/cache/route.ts +72 -10
  231. package/src/modules/configs/components/CachePanel.tsx +59 -29
  232. package/src/modules/currencies/api/currencies/options/route.ts +22 -4
  233. package/src/modules/currencies/services/rateFetchingService.ts +63 -26
  234. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
  235. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
  236. package/src/modules/customer_accounts/i18n/de.json +1 -1
  237. package/src/modules/customer_accounts/i18n/en.json +1 -1
  238. package/src/modules/customer_accounts/i18n/es.json +1 -1
  239. package/src/modules/customer_accounts/i18n/pl.json +1 -1
  240. package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
  241. package/src/modules/customers/backend/calendar/page.tsx +12 -0
  242. package/src/modules/customers/cli.ts +1 -0
  243. package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
  244. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
  245. package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
  246. package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
  247. package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
  248. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
  249. package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
  250. package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
  251. package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
  252. package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
  253. package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
  254. package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
  255. package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
  256. package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
  257. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
  258. package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
  259. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
  260. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
  261. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
  262. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
  263. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
  264. package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
  265. package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
  266. package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
  267. package/src/modules/customers/components/calendar/types.ts +163 -0
  268. package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
  269. package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
  270. package/src/modules/customers/i18n/de.json +195 -0
  271. package/src/modules/customers/i18n/en.json +195 -0
  272. package/src/modules/customers/i18n/es.json +195 -0
  273. package/src/modules/customers/i18n/pl.json +195 -0
  274. package/src/modules/customers/lib/calendar/categories.ts +26 -0
  275. package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
  276. package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
  277. package/src/modules/customers/lib/calendar/grid.ts +50 -0
  278. package/src/modules/customers/lib/calendar/layout.ts +47 -0
  279. package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
  280. package/src/modules/customers/lib/calendar/preferences.ts +95 -0
  281. package/src/modules/customers/lib/calendar/range.ts +45 -0
  282. package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
  283. package/src/modules/dashboards/services/widgetDataService.ts +8 -6
  284. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
  285. package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
  286. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
  287. package/src/modules/dictionaries/api/route.ts +36 -1
  288. package/src/modules/feature_toggles/lib/queries.ts +41 -60
  289. package/src/modules/messages/commands/actions.ts +100 -7
  290. package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
  291. package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
  292. package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
  293. package/src/modules/perspectives/services/perspectiveService.ts +9 -5
  294. package/src/modules/planner/api/access.ts +9 -0
  295. package/src/modules/planner/api/availability-date-specific.ts +22 -1
  296. package/src/modules/planner/api/availability-weekly.ts +22 -1
  297. package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
  298. package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
  299. package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
  300. package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
  301. package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
  302. package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
  303. package/src/modules/staff/api/team-members/self/route.ts +43 -0
  304. package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
  305. package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
  306. package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
  307. package/src/modules/translations/api/context.ts +7 -0
  308. package/src/modules/translations/api/put/locales.ts +35 -1
  309. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
  310. package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
  311. package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
  312. package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
  313. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
  314. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
  315. package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
  316. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
  317. package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
  318. package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
  319. package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
  320. package/src/modules/workflows/lib/graph-utils.ts +21 -1
@@ -10,6 +10,11 @@ import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
10
10
  import {
11
11
  staffTeamMemberTagAssignmentSchema
12
12
  } from "../../../../data/validators.js";
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards
17
+ } from "../../../guards.js";
13
18
  const metadata = {
14
19
  POST: { requireAuth: true, requireFeatures: ["staff.manage_team"] }
15
20
  };
@@ -34,11 +39,47 @@ async function POST(req) {
34
39
  const { ctx, translate } = await buildContext(req);
35
40
  const body = await req.json().catch(() => ({}));
36
41
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate);
42
+ const auth = ctx.auth;
43
+ const tenantId = auth?.tenantId ?? "";
44
+ const organizationId = ctx.selectedOrganizationId ?? null;
45
+ const guardResult = await runStaffMutationGuards(
46
+ ctx.container,
47
+ {
48
+ tenantId,
49
+ organizationId,
50
+ userId: auth?.sub ?? "",
51
+ resourceKind: "staff.team_member",
52
+ resourceId: input.memberId,
53
+ operation: "create",
54
+ requestMethod: req.method,
55
+ requestHeaders: req.headers,
56
+ mutationPayload: input
57
+ },
58
+ resolveUserFeatures(auth)
59
+ );
60
+ if (!guardResult.ok) {
61
+ return NextResponse.json(
62
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
63
+ { status: guardResult.errorStatus ?? 422 }
64
+ );
65
+ }
37
66
  const commandBus = ctx.container.resolve("commandBus");
38
67
  const { result, logEntry } = await commandBus.execute(
39
68
  "staff.team-members.tags.assign",
40
69
  { input, ctx }
41
70
  );
71
+ if (guardResult.afterSuccessCallbacks.length) {
72
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
73
+ tenantId,
74
+ organizationId,
75
+ userId: auth?.sub ?? "",
76
+ resourceKind: "staff.team_member",
77
+ resourceId: result?.memberId ?? input.memberId,
78
+ operation: "create",
79
+ requestMethod: req.method,
80
+ requestHeaders: req.headers
81
+ });
82
+ }
42
83
  const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 });
43
84
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
44
85
  response.headers.set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/staff/api/team-members/tags/assign/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.assign',\n { input, ctx },\n )\n const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('staff.teamMembers.tags.assign failed', err)\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Assign team member tag',\n methods: {\n POST: {\n summary: 'Assign team member tag',\n description: 'Assigns a tag to a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 201, description: 'Tag assignment created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.assign',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? input.memberId,\n operation: 'create',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? null }, { status: 201 })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('staff.teamMembers.tags.assign failed', err)\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Assign team member tag',\n methods: {\n POST: {\n summary: 'Assign team member tag',\n description: 'Assigns a tag to a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 201, description: 'Tag assignment created', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -10,6 +10,11 @@ import { parseScopedCommandInput } from "@open-mercato/shared/lib/api/scoped";
10
10
  import {
11
11
  staffTeamMemberTagAssignmentSchema
12
12
  } from "../../../../data/validators.js";
13
+ import {
14
+ resolveUserFeatures,
15
+ runStaffMutationGuardAfterSuccess,
16
+ runStaffMutationGuards
17
+ } from "../../../guards.js";
13
18
  const metadata = {
14
19
  POST: { requireAuth: true, requireFeatures: ["staff.manage_team"] }
15
20
  };
@@ -34,11 +39,47 @@ async function POST(req) {
34
39
  const { ctx, translate } = await buildContext(req);
35
40
  const body = await req.json().catch(() => ({}));
36
41
  const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate);
42
+ const auth = ctx.auth;
43
+ const tenantId = auth?.tenantId ?? "";
44
+ const organizationId = ctx.selectedOrganizationId ?? null;
45
+ const guardResult = await runStaffMutationGuards(
46
+ ctx.container,
47
+ {
48
+ tenantId,
49
+ organizationId,
50
+ userId: auth?.sub ?? "",
51
+ resourceKind: "staff.team_member",
52
+ resourceId: input.memberId,
53
+ operation: "delete",
54
+ requestMethod: req.method,
55
+ requestHeaders: req.headers,
56
+ mutationPayload: input
57
+ },
58
+ resolveUserFeatures(auth)
59
+ );
60
+ if (!guardResult.ok) {
61
+ return NextResponse.json(
62
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
63
+ { status: guardResult.errorStatus ?? 422 }
64
+ );
65
+ }
37
66
  const commandBus = ctx.container.resolve("commandBus");
38
67
  const { result, logEntry } = await commandBus.execute(
39
68
  "staff.team-members.tags.unassign",
40
69
  { input, ctx }
41
70
  );
71
+ if (guardResult.afterSuccessCallbacks.length) {
72
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
73
+ tenantId,
74
+ organizationId,
75
+ userId: auth?.sub ?? "",
76
+ resourceKind: "staff.team_member",
77
+ resourceId: result?.memberId ?? input.memberId,
78
+ operation: "delete",
79
+ requestMethod: req.method,
80
+ requestHeaders: req.headers
81
+ });
82
+ }
42
83
  const response = NextResponse.json({ id: result?.memberId ?? null });
43
84
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
44
85
  response.headers.set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/staff/api/team-members/tags/unassign/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.unassign',\n { input, ctx },\n )\n const response = NextResponse.json({ id: result?.memberId ?? null })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('staff.teamMembers.tags.unassign failed', err)\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Unassign team member tag',\n methods: {\n POST: {\n summary: 'Unassign team member tag',\n description: 'Removes a tag from a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 200, description: 'Tag assignment removed', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CommandRuntimeContext, CommandBus } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n staffTeamMemberTagAssignmentSchema,\n type StaffTeamMemberTagAssignmentInput,\n} from '../../../../data/validators'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../guards'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['staff.manage_team'] },\n}\n\nasync function buildContext(\n req: Request\n): Promise<{ ctx: CommandRuntimeContext; translate: (key: string, fallback?: string) => string }> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const ctx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope?.selectedId ?? auth.orgId ?? null,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n return { ctx, translate }\n}\n\nexport async function POST(req: Request) {\n try {\n const { ctx, translate } = await buildContext(req)\n const body = await req.json().catch(() => ({}))\n const input = parseScopedCommandInput(staffTeamMemberTagAssignmentSchema, body, ctx, translate)\n\n const auth = ctx.auth\n const tenantId = auth?.tenantId ?? ''\n const organizationId = ctx.selectedOrganizationId ?? null\n const guardResult = await runStaffMutationGuards(\n ctx.container,\n {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: input.memberId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: input,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n const commandBus = (ctx.container.resolve('commandBus') as CommandBus)\n const { result, logEntry } = await commandBus.execute<StaffTeamMemberTagAssignmentInput, { memberId: string }>(\n 'staff.team-members.tags.unassign',\n { input, ctx },\n )\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth?.sub ?? '',\n resourceKind: 'staff.team_member',\n resourceId: result?.memberId ?? input.memberId,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n const response = NextResponse.json({ id: result?.memberId ?? null })\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'staff.teamMemberTagAssignment',\n resourceId: logEntry.resourceId ?? result?.memberId ?? null,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : undefined,\n }),\n )\n }\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n const { translate } = await resolveTranslations()\n console.error('staff.teamMembers.tags.unassign failed', err)\n return NextResponse.json({ error: translate('staff.teamMembers.tags.updateError', 'Failed to update tags.') }, { status: 400 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Unassign team member tag',\n methods: {\n POST: {\n summary: 'Unassign team member tag',\n description: 'Removes a tag from a staff team member.',\n requestBody: {\n contentType: 'application/json',\n schema: staffTeamMemberTagAssignmentSchema,\n },\n responses: [\n { status: 200, description: 'Tag assignment removed', schema: z.object({ id: z.string().uuid().nullable() }) },\n { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,13 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { sql } from "kysely";
4
- import { resolveTranslationsRouteContext, requireTranslationFeatures } from "@open-mercato/core/modules/translations/api/context";
4
+ import { resolveTranslationsRouteContext, requireTranslationFeatures, resolveTranslationsActorId } from "@open-mercato/core/modules/translations/api/context";
5
5
  import { translationBodySchema, entityTypeParamSchema, entityIdParamSchema } from "@open-mercato/core/modules/translations/data/validators";
6
6
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import {
8
+ runCrudMutationGuardAfterSuccess,
9
+ validateCrudMutationGuard
10
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
7
11
  import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
8
12
  const paramsSchema = z.object({
9
13
  entityType: entityTypeParamSchema,
@@ -62,6 +66,21 @@ async function PUT(req, ctx) {
62
66
  }
63
67
  }
64
68
  const translations = translationBodySchema.parse(rawBody);
69
+ const guardUserId = resolveTranslationsActorId(context.auth);
70
+ const guardResult = await validateCrudMutationGuard(context.container, {
71
+ tenantId: context.tenantId,
72
+ organizationId: context.organizationId,
73
+ userId: guardUserId,
74
+ resourceKind: "translations.translation",
75
+ resourceId: `${entityType}:${entityId}`,
76
+ operation: "update",
77
+ requestMethod: req.method,
78
+ requestHeaders: req.headers,
79
+ mutationPayload: { entityType, entityId, translations }
80
+ });
81
+ if (guardResult && !guardResult.ok) {
82
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
83
+ }
65
84
  const commandBus = context.container.resolve("commandBus");
66
85
  const { result, logEntry } = await commandBus.execute("translations.translation.save", {
67
86
  input: {
@@ -73,6 +92,19 @@ async function PUT(req, ctx) {
73
92
  },
74
93
  ctx: context.commandCtx
75
94
  });
95
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
96
+ await runCrudMutationGuardAfterSuccess(context.container, {
97
+ tenantId: context.tenantId,
98
+ organizationId: context.organizationId,
99
+ userId: guardUserId,
100
+ resourceKind: "translations.translation",
101
+ resourceId: `${entityType}:${entityId}`,
102
+ operation: "update",
103
+ requestMethod: req.method,
104
+ requestHeaders: req.headers,
105
+ metadata: guardResult.metadata ?? null
106
+ });
107
+ }
76
108
  const row = await context.db.selectFrom("entity_translations").selectAll().where("id", "=", result.rowId).executeTakeFirst();
77
109
  const response = NextResponse.json({
78
110
  entityType: row.entity_type,
@@ -115,6 +147,21 @@ async function DELETE(req, ctx) {
115
147
  entityType: ctx.params?.entityType,
116
148
  entityId: ctx.params?.entityId
117
149
  });
150
+ const guardUserId = resolveTranslationsActorId(context.auth);
151
+ const guardResult = await validateCrudMutationGuard(context.container, {
152
+ tenantId: context.tenantId,
153
+ organizationId: context.organizationId,
154
+ userId: guardUserId,
155
+ resourceKind: "translations.translation",
156
+ resourceId: `${entityType}:${entityId}`,
157
+ operation: "delete",
158
+ requestMethod: req.method,
159
+ requestHeaders: req.headers,
160
+ mutationPayload: null
161
+ });
162
+ if (guardResult && !guardResult.ok) {
163
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
164
+ }
118
165
  const commandBus = context.container.resolve("commandBus");
119
166
  const { logEntry } = await commandBus.execute("translations.translation.delete", {
120
167
  input: {
@@ -125,6 +172,19 @@ async function DELETE(req, ctx) {
125
172
  },
126
173
  ctx: context.commandCtx
127
174
  });
175
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
176
+ await runCrudMutationGuardAfterSuccess(context.container, {
177
+ tenantId: context.tenantId,
178
+ organizationId: context.organizationId,
179
+ userId: guardUserId,
180
+ resourceKind: "translations.translation",
181
+ resourceId: `${entityType}:${entityId}`,
182
+ operation: "delete",
183
+ requestMethod: req.method,
184
+ requestHeaders: req.headers,
185
+ metadata: guardResult.metadata ?? null
186
+ });
187
+ }
128
188
  const response = new NextResponse(null, { status: 204 });
129
189
  if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {
130
190
  response.headers.set(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/translations/api/%5BentityType%5D/%5BentityId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport { resolveTranslationsRouteContext, requireTranslationFeatures } from '@open-mercato/core/modules/translations/api/context'\nimport { translationBodySchema, entityTypeParamSchema, entityIdParamSchema } from '@open-mercato/core/modules/translations/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst paramsSchema = z.object({\n entityType: entityTypeParamSchema,\n entityId: entityIdParamSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['translations.view'] },\n PUT: { requireAuth: true, requireFeatures: ['translations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['translations.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.view'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const row = await (context.db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('entity_type', '=', entityType)\n .where('entity_id', '=', entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${context.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${context.organizationId}`)\n .executeTakeFirst() as Record<string, any> | undefined\n\n if (!row) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.manage'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const rawText = await req.text()\n let rawBody: unknown = {}\n if (rawText.trim().length > 0) {\n try {\n rawBody = JSON.parse(rawText)\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n }\n const translations = translationBodySchema.parse(rawBody)\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<\n { entityType: string; entityId: string; translations: typeof translations; organizationId: string | null; tenantId: string },\n { rowId: string }\n >('translations.translation.save', {\n input: {\n entityType,\n entityId,\n translations,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n },\n ctx: context.commandCtx,\n })\n\n const row = await (context.db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('id', '=', result.rowId)\n .executeTakeFirst() as Record<string, any>\n\n const response = NextResponse.json({\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'translations.translation',\n resourceId: logEntry.resourceId ?? result.rowId,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n }),\n )\n }\n\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.PUT] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.manage'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<\n { entityType: string; entityId: string; organizationId: string | null; tenantId: string },\n { deleted: boolean }\n >('translations.translation.delete', {\n input: {\n entityType,\n entityId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n },\n ctx: context.commandCtx,\n })\n\n const response = new NextResponse(null, { status: 204 })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'translations.translation',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n }),\n )\n }\n\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst translationsTag = 'Translations'\n\nconst getDoc: OpenApiMethodDoc = {\n summary: 'Get entity translations',\n description: 'Returns the full translation record for a single entity.',\n tags: [translationsTag],\n responses: [\n { status: 200, description: 'Translation record found.' },\n ],\n errors: [\n { status: 401, description: 'Authentication required' },\n { status: 404, description: 'No translations found for this entity' },\n ],\n}\n\nconst putDoc: OpenApiMethodDoc = {\n summary: 'Create or update entity translations',\n description: 'Full replacement of translations JSONB for an entity.',\n tags: [translationsTag],\n responses: [\n { status: 200, description: 'Translations saved.' },\n ],\n errors: [\n { status: 400, description: 'Validation failed' },\n { status: 401, description: 'Authentication required' },\n ],\n}\n\nconst deleteDoc: OpenApiMethodDoc = {\n summary: 'Delete entity translations',\n description: 'Removes all translations for an entity.',\n tags: [translationsTag],\n responses: [\n { status: 204, description: 'Translations deleted.' },\n ],\n errors: [\n { status: 401, description: 'Authentication required' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: translationsTag,\n summary: 'Entity translation resource',\n pathParams: paramsSchema,\n methods: {\n GET: getDoc,\n PUT: putDoc,\n DELETE: deleteDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport { resolveTranslationsRouteContext, requireTranslationFeatures, resolveTranslationsActorId } from '@open-mercato/core/modules/translations/api/context'\nimport { translationBodySchema, entityTypeParamSchema, entityIdParamSchema } from '@open-mercato/core/modules/translations/data/validators'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst paramsSchema = z.object({\n entityType: entityTypeParamSchema,\n entityId: entityIdParamSchema,\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['translations.view'] },\n PUT: { requireAuth: true, requireFeatures: ['translations.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['translations.manage'] },\n}\n\nexport async function GET(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.view'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const row = await (context.db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('entity_type', '=', entityType)\n .where('entity_id', '=', entityId)\n .where(sql<boolean>`tenant_id is not distinct from ${context.tenantId}`)\n .where(sql<boolean>`organization_id is not distinct from ${context.organizationId}`)\n .executeTakeFirst() as Record<string, any> | undefined\n\n if (!row) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.GET] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport async function PUT(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.manage'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const rawText = await req.text()\n let rawBody: unknown = {}\n if (rawText.trim().length > 0) {\n try {\n rawBody = JSON.parse(rawText)\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n }\n const translations = translationBodySchema.parse(rawBody)\n\n const guardUserId = resolveTranslationsActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.translation',\n resourceId: `${entityType}:${entityId}`,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { entityType, entityId, translations },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { result, logEntry } = await commandBus.execute<\n { entityType: string; entityId: string; translations: typeof translations; organizationId: string | null; tenantId: string },\n { rowId: string }\n >('translations.translation.save', {\n input: {\n entityType,\n entityId,\n translations,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n },\n ctx: context.commandCtx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.translation',\n resourceId: `${entityType}:${entityId}`,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const row = await (context.db as any)\n .selectFrom('entity_translations')\n .selectAll()\n .where('id', '=', result.rowId)\n .executeTakeFirst() as Record<string, any>\n\n const response = NextResponse.json({\n entityType: row.entity_type,\n entityId: row.entity_id,\n translations: row.translations,\n createdAt: row.created_at,\n updatedAt: row.updated_at,\n })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'translations.translation',\n resourceId: logEntry.resourceId ?? result.rowId,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n }),\n )\n }\n\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation failed', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.PUT] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, ctx: { params?: { entityType?: string; entityId?: string } }) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n await requireTranslationFeatures(context, ['translations.manage'])\n const { entityType, entityId } = paramsSchema.parse({\n entityType: ctx.params?.entityType,\n entityId: ctx.params?.entityId,\n })\n\n const guardUserId = resolveTranslationsActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.translation',\n resourceId: `${entityType}:${entityId}`,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: null,\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const commandBus = context.container.resolve('commandBus') as CommandBus\n const { logEntry } = await commandBus.execute<\n { entityType: string; entityId: string; organizationId: string | null; tenantId: string },\n { deleted: boolean }\n >('translations.translation.delete', {\n input: {\n entityType,\n entityId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n },\n ctx: context.commandCtx,\n })\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.translation',\n resourceId: `${entityType}:${entityId}`,\n operation: 'delete',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n const response = new NextResponse(null, { status: 204 })\n\n if (logEntry?.undoToken && logEntry?.id && logEntry?.commandId) {\n response.headers.set(\n 'x-om-operation',\n serializeOperationMetadata({\n id: logEntry.id,\n undoToken: logEntry.undoToken,\n commandId: logEntry.commandId,\n actionLabel: logEntry.actionLabel ?? null,\n resourceKind: logEntry.resourceKind ?? 'translations.translation',\n resourceId: logEntry.resourceId ?? null,\n executedAt: logEntry.createdAt instanceof Date\n ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string'\n ? logEntry.createdAt\n : new Date().toISOString(),\n }),\n )\n }\n\n return response\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid parameters', details: err.issues }, { status: 400 })\n }\n console.error('[translations/:entityType/:entityId.DELETE] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst translationsTag = 'Translations'\n\nconst getDoc: OpenApiMethodDoc = {\n summary: 'Get entity translations',\n description: 'Returns the full translation record for a single entity.',\n tags: [translationsTag],\n responses: [\n { status: 200, description: 'Translation record found.' },\n ],\n errors: [\n { status: 401, description: 'Authentication required' },\n { status: 404, description: 'No translations found for this entity' },\n ],\n}\n\nconst putDoc: OpenApiMethodDoc = {\n summary: 'Create or update entity translations',\n description: 'Full replacement of translations JSONB for an entity.',\n tags: [translationsTag],\n responses: [\n { status: 200, description: 'Translations saved.' },\n ],\n errors: [\n { status: 400, description: 'Validation failed' },\n { status: 401, description: 'Authentication required' },\n ],\n}\n\nconst deleteDoc: OpenApiMethodDoc = {\n summary: 'Delete entity translations',\n description: 'Removes all translations for an entity.',\n tags: [translationsTag],\n responses: [\n { status: 204, description: 'Translations deleted.' },\n ],\n errors: [\n { status: 401, description: 'Authentication required' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: translationsTag,\n summary: 'Entity translation resource',\n pathParams: paramsSchema,\n methods: {\n GET: getDoc,\n PUT: putDoc,\n DELETE: deleteDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,12 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
2
  import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
3
3
  import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
4
4
  import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
+ function resolveTranslationsActorId(auth) {
6
+ if (typeof auth.sub === "string" && auth.sub.trim().length > 0) return auth.sub;
7
+ if (typeof auth.userId === "string" && auth.userId.trim().length > 0) return auth.userId;
8
+ if (typeof auth.keyId === "string" && auth.keyId.trim().length > 0) return auth.keyId;
9
+ return "system";
10
+ }
5
11
  async function resolveTranslationsRouteContext(req) {
6
12
  const container = await createRequestContainer();
7
13
  const auth = await getAuthFromRequest(req);
@@ -48,6 +54,7 @@ async function requireTranslationFeatures(context, requiredFeatures) {
48
54
  }
49
55
  export {
50
56
  requireTranslationFeatures,
57
+ resolveTranslationsActorId,
51
58
  resolveTranslationsRouteContext
52
59
  };
53
60
  //# sourceMappingURL=context.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/translations/api/context.ts"],
4
- "sourcesContent": ["import { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { Kysely } from 'kysely'\n\nexport type TranslationsRouteContext = {\n container: AwilixContainer\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n em: EntityManager\n db: Kysely<any>\n organizationId: string | null\n tenantId: string\n commandCtx: CommandRuntimeContext\n}\n\nexport async function resolveTranslationsRouteContext(req: Request): Promise<TranslationsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = container.resolve('em') as EntityManager\n const db = em.getKysely<any>()\n const tenantId: string = scope?.tenantId ?? auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n\n const commandCtx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return {\n container,\n auth,\n em,\n db,\n organizationId,\n tenantId,\n commandCtx,\n }\n}\n\nexport async function requireTranslationFeatures(\n context: TranslationsRouteContext,\n requiredFeatures: string[],\n): Promise<void> {\n if (!requiredFeatures.length) return\n const subject = context.auth.sub\n if (!subject) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n const rbacService = context.container.resolve('rbacService') as {\n userHasAllFeatures(\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ): Promise<boolean>\n }\n const hasFeatures = await rbacService.userHasAllFeatures(subject, requiredFeatures, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n if (!hasFeatures) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,qBAAqB;AAgB9B,eAAsB,gCAAgC,KAAiD;AACrG,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EACxD;AAEA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,KAAK,GAAG,UAAe;AAC7B,QAAM,WAAmB,OAAO,YAAY,KAAK;AACjD,QAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAE1D,QAAM,aAAoC;AAAA,IACxC;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,wBAAwB;AAAA,IACxB,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,IAClE,SAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,2BACpB,SACA,kBACe;AACf,MAAI,CAAC,iBAAiB,OAAQ;AAC9B,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EACxD;AACA,QAAM,cAAc,QAAQ,UAAU,QAAQ,aAAa;AAO3D,QAAM,cAAc,MAAM,YAAY,mBAAmB,SAAS,kBAAkB;AAAA,IAClF,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,EAC1B,CAAC;AACD,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AACF;",
4
+ "sourcesContent": ["import { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { AwilixContainer } from 'awilix'\nimport type { Kysely } from 'kysely'\n\nexport type TranslationsRouteContext = {\n container: AwilixContainer\n auth: NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n em: EntityManager\n db: Kysely<any>\n organizationId: string | null\n tenantId: string\n commandCtx: CommandRuntimeContext\n}\n\nexport function resolveTranslationsActorId(auth: TranslationsRouteContext['auth']): string {\n if (typeof auth.sub === 'string' && auth.sub.trim().length > 0) return auth.sub\n if (typeof auth.userId === 'string' && auth.userId.trim().length > 0) return auth.userId\n if (typeof auth.keyId === 'string' && auth.keyId.trim().length > 0) return auth.keyId\n return 'system'\n}\n\nexport async function resolveTranslationsRouteContext(req: Request): Promise<TranslationsRouteContext> {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const em = container.resolve('em') as EntityManager\n const db = em.getKysely<any>()\n const tenantId: string = scope?.tenantId ?? auth.tenantId\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n\n const commandCtx: CommandRuntimeContext = {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: organizationId,\n organizationIds: scope?.filterIds ?? (auth.orgId ? [auth.orgId] : null),\n request: req,\n }\n\n return {\n container,\n auth,\n em,\n db,\n organizationId,\n tenantId,\n commandCtx,\n }\n}\n\nexport async function requireTranslationFeatures(\n context: TranslationsRouteContext,\n requiredFeatures: string[],\n): Promise<void> {\n if (!requiredFeatures.length) return\n const subject = context.auth.sub\n if (!subject) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n const rbacService = context.container.resolve('rbacService') as {\n userHasAllFeatures(\n userId: string,\n required: string[],\n scope: { tenantId: string | null; organizationId: string | null },\n ): Promise<boolean>\n }\n const hasFeatures = await rbacService.userHasAllFeatures(subject, requiredFeatures, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n })\n if (!hasFeatures) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,qBAAqB;AAgBvB,SAAS,2BAA2B,MAAgD;AACzF,MAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,IAAI,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAC5E,MAAI,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAClF,MAAI,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,KAAK;AAChF,SAAO;AACT;AAEA,eAAsB,gCAAgC,KAAiD;AACrG,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EACxD;AAEA,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,KAAK,GAAG,UAAe;AAC7B,QAAM,WAAmB,OAAO,YAAY,KAAK;AACjD,QAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAE1D,QAAM,aAAoC;AAAA,IACxC;AAAA,IACA;AAAA,IACA,mBAAmB;AAAA,IACnB,wBAAwB;AAAA,IACxB,iBAAiB,OAAO,cAAc,KAAK,QAAQ,CAAC,KAAK,KAAK,IAAI;AAAA,IAClE,SAAS;AAAA,EACX;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAsB,2BACpB,SACA,kBACe;AACf,MAAI,CAAC,iBAAiB,OAAQ;AAC9B,QAAM,UAAU,QAAQ,KAAK;AAC7B,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EACxD;AACA,QAAM,cAAc,QAAQ,UAAU,QAAQ,aAAa;AAO3D,QAAM,cAAc,MAAM,YAAY,mBAAmB,SAAS,kBAAkB;AAAA,IAClF,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ;AAAA,EAC1B,CAAC;AACD,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
- import { resolveTranslationsRouteContext } from "@open-mercato/core/modules/translations/api/context";
3
+ import { resolveTranslationsRouteContext, resolveTranslationsActorId } from "@open-mercato/core/modules/translations/api/context";
4
4
  import { isCrudHttpError } from "@open-mercato/shared/lib/crud/errors";
5
+ import {
6
+ runCrudMutationGuardAfterSuccess,
7
+ validateCrudMutationGuard
8
+ } from "@open-mercato/shared/lib/crud/mutation-guard";
5
9
  import { isValidIso639 } from "@open-mercato/shared/lib/i18n/iso639";
6
10
  const bodySchema = z.object({
7
11
  locales: z.array(
@@ -17,8 +21,36 @@ async function PUT(req) {
17
21
  const context = await resolveTranslationsRouteContext(req);
18
22
  const body = bodySchema.parse(await req.json());
19
23
  const uniqueLocales = [...new Set(body.locales.map((l) => l.toLowerCase().trim()))];
24
+ const guardUserId = resolveTranslationsActorId(context.auth);
25
+ const guardResult = await validateCrudMutationGuard(context.container, {
26
+ tenantId: context.tenantId,
27
+ organizationId: context.organizationId,
28
+ userId: guardUserId,
29
+ resourceKind: "translations.locales",
30
+ resourceId: "supported_locales",
31
+ operation: "custom",
32
+ requestMethod: req.method,
33
+ requestHeaders: req.headers,
34
+ mutationPayload: { locales: uniqueLocales }
35
+ });
36
+ if (guardResult && !guardResult.ok) {
37
+ return NextResponse.json(guardResult.body, { status: guardResult.status });
38
+ }
20
39
  const configService = context.container.resolve("moduleConfigService");
21
40
  await configService.setValue("translations", "supported_locales", uniqueLocales);
41
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
42
+ await runCrudMutationGuardAfterSuccess(context.container, {
43
+ tenantId: context.tenantId,
44
+ organizationId: context.organizationId,
45
+ userId: guardUserId,
46
+ resourceKind: "translations.locales",
47
+ resourceId: "supported_locales",
48
+ operation: "custom",
49
+ requestMethod: req.method,
50
+ requestHeaders: req.headers,
51
+ metadata: guardResult.metadata ?? null
52
+ });
53
+ }
22
54
  return NextResponse.json({ locales: uniqueLocales });
23
55
  } catch (err) {
24
56
  if (isCrudHttpError(err)) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/translations/api/put/locales.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveTranslationsRouteContext } from '@open-mercato/core/modules/translations/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { isValidIso639 } from '@open-mercato/shared/lib/i18n/iso639'\nimport type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst bodySchema = z.object({\n locales: z.array(\n z.string().min(2).max(10).refine(isValidIso639, { message: 'Invalid ISO 639-1 language code' }),\n ).min(1).max(50),\n})\n\nexport const metadata = {\n path: '/translations/locales',\n PUT: { requireAuth: true, requireFeatures: ['translations.manage_locales'] },\n}\n\nasync function PUT(req: Request) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n const body = bodySchema.parse(await req.json())\n const uniqueLocales = [...new Set(body.locales.map((l) => l.toLowerCase().trim()))]\n\n const configService = context.container.resolve('moduleConfigService') as ModuleConfigService\n await configService.setValue('translations', 'supported_locales', uniqueLocales)\n\n return NextResponse.json({ locales: uniqueLocales })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid request', details: err.issues }, { status: 400 })\n }\n console.error('[translations/locales.PUT] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst responseSchema = z.object({\n locales: z.array(z.string()),\n})\n\nconst putDoc: OpenApiMethodDoc = {\n summary: 'Update supported translation locales',\n tags: ['Translations'],\n requestBody: {\n schema: bodySchema,\n },\n responses: [\n { status: 200, description: 'Updated locales list', schema: responseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid request body' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Translations',\n summary: 'Update supported translation locales',\n methods: {\n PUT: putDoc,\n },\n}\n\nexport default PUT\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,uCAAuC;AAChD,SAAwB,uBAAuB;AAC/C,SAAS,qBAAqB;AAI9B,MAAM,aAAa,EAAE,OAAO;AAAA,EAC1B,SAAS,EAAE;AAAA,IACT,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,OAAO,eAAe,EAAE,SAAS,kCAAkC,CAAC;AAAA,EAChG,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AACjB,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAC7E;AAEA,eAAe,IAAI,KAAc;AAC/B,MAAI;AACF,UAAM,UAAU,MAAM,gCAAgC,GAAG;AACzD,UAAM,OAAO,WAAW,MAAM,MAAM,IAAI,KAAK,CAAC;AAC9C,UAAM,gBAAgB,CAAC,GAAG,IAAI,IAAI,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;AAElF,UAAM,gBAAgB,QAAQ,UAAU,QAAQ,qBAAqB;AACrE,UAAM,cAAc,SAAS,gBAAgB,qBAAqB,aAAa;AAE/E,WAAO,aAAa,KAAK,EAAE,SAAS,cAAc,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC7F;AACA,YAAQ,MAAM,+CAA+C,GAAG;AAChE,WAAO,aAAa,KAAK,EAAE,OAAO,wBAAwB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9E;AACF;AAEA,MAAM,iBAAiB,EAAE,OAAO;AAAA,EAC9B,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC;AAC7B,CAAC;AAED,MAAM,SAA2B;AAAA,EAC/B,SAAS;AAAA,EACT,MAAM,CAAC,cAAc;AAAA,EACrB,aAAa;AAAA,IACX,QAAQ;AAAA,EACV;AAAA,EACA,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,wBAAwB,QAAQ,eAAe;AAAA,EAC7E;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,uBAAuB;AAAA,EACrD;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;AAEA,IAAO,kBAAQ;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveTranslationsRouteContext, resolveTranslationsActorId } from '@open-mercato/core/modules/translations/api/context'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport { isValidIso639 } from '@open-mercato/shared/lib/i18n/iso639'\nimport type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst bodySchema = z.object({\n locales: z.array(\n z.string().min(2).max(10).refine(isValidIso639, { message: 'Invalid ISO 639-1 language code' }),\n ).min(1).max(50),\n})\n\nexport const metadata = {\n path: '/translations/locales',\n PUT: { requireAuth: true, requireFeatures: ['translations.manage_locales'] },\n}\n\nasync function PUT(req: Request) {\n try {\n const context = await resolveTranslationsRouteContext(req)\n const body = bodySchema.parse(await req.json())\n const uniqueLocales = [...new Set(body.locales.map((l) => l.toLowerCase().trim()))]\n\n const guardUserId = resolveTranslationsActorId(context.auth)\n const guardResult = await validateCrudMutationGuard(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.locales',\n resourceId: 'supported_locales',\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { locales: uniqueLocales },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n const configService = context.container.resolve('moduleConfigService') as ModuleConfigService\n await configService.setValue('translations', 'supported_locales', uniqueLocales)\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(context.container, {\n tenantId: context.tenantId,\n organizationId: context.organizationId,\n userId: guardUserId,\n resourceKind: 'translations.locales',\n resourceId: 'supported_locales',\n operation: 'custom',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ locales: uniqueLocales })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Invalid request', details: err.issues }, { status: 400 })\n }\n console.error('[translations/locales.PUT] Unexpected error', err)\n return NextResponse.json({ error: 'Internal server error' }, { status: 500 })\n }\n}\n\nconst responseSchema = z.object({\n locales: z.array(z.string()),\n})\n\nconst putDoc: OpenApiMethodDoc = {\n summary: 'Update supported translation locales',\n tags: ['Translations'],\n requestBody: {\n schema: bodySchema,\n },\n responses: [\n { status: 200, description: 'Updated locales list', schema: responseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid request body' },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Translations',\n summary: 'Update supported translation locales',\n methods: {\n PUT: putDoc,\n },\n}\n\nexport default PUT\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,10 +5,9 @@ import { NodeEditDialog } from "../../../components/NodeEditDialog.js";
5
5
  import { EdgeEditDialog } from "../../../components/EdgeEditDialog.js";
6
6
  import { NodeEditDialogCrudForm } from "../../../components/NodeEditDialogCrudForm.js";
7
7
  import { EdgeEditDialogCrudForm } from "../../../components/EdgeEditDialogCrudForm.js";
8
- import { addEdge, applyNodeChanges, applyEdgeChanges } from "@xyflow/react";
9
8
  import { useState, useCallback, useEffect } from "react";
10
9
  import { useRouter, useSearchParams } from "next/navigation";
11
- import { graphToDefinition, definitionToGraph, validateWorkflowGraph, generateStepId, generateTransitionId } from "../../../lib/graph-utils.js";
10
+ import { graphToDefinition, definitionToGraph, validateWorkflowGraph, generateStepId, generateTransitionId, appendWorkflowEdge } from "../../../lib/graph-utils.js";
12
11
  import { performDeleteEdgeFlow, performDeleteNodeFlow } from "../../../lib/visual-editor-delete-flow.js";
13
12
  import { workflowDefinitionDataSchema } from "../../../data/validators.js";
14
13
  import { Page } from "@open-mercato/ui/backend/Page";
@@ -126,13 +125,13 @@ function VisualEditorPage() {
126
125
  };
127
126
  loadDefinition();
128
127
  }, [definitionId]);
129
- const handleNodesChange = useCallback((changes) => {
128
+ const handleNodesChange = useCallback((nextNodes) => {
130
129
  if (isCodeOnly) return;
131
- setNodes((nds) => applyNodeChanges(changes, nds));
130
+ setNodes(nextNodes);
132
131
  }, [isCodeOnly]);
133
- const handleEdgesChange = useCallback((changes) => {
132
+ const handleEdgesChange = useCallback((nextEdges) => {
134
133
  if (isCodeOnly) return;
135
- setEdges((eds) => applyEdgeChanges(changes, eds));
134
+ setEdges(nextEdges);
136
135
  }, [isCodeOnly]);
137
136
  const handleAddNode = useCallback((nodeType) => {
138
137
  if (isCodeOnly) return;
@@ -216,7 +215,7 @@ function VisualEditorPage() {
216
215
  label: ""
217
216
  }
218
217
  };
219
- setEdges((eds) => addEdge(newEdge, eds));
218
+ setEdges((eds) => appendWorkflowEdge(eds, newEdge));
220
219
  }, []);
221
220
  const handleValidate = useCallback(() => {
222
221
  const graphErrors = validateWorkflowGraph(nodes, edges);