@open-mercato/core 0.6.6-develop.6551.1.1511bfa890 → 0.6.6

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 (304) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities.ids.generated.js +0 -1
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +0 -12
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/api_keys/api/keys/route.js +13 -30
  7. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  8. package/dist/modules/api_keys/cli.js +1 -2
  9. package/dist/modules/api_keys/cli.js.map +2 -2
  10. package/dist/modules/attachments/api/file/[id]/route.js +7 -9
  11. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  12. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
  13. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  14. package/dist/modules/attachments/api/route.js +17 -21
  15. package/dist/modules/attachments/api/route.js.map +2 -2
  16. package/dist/modules/attachments/components/AttachmentContentPreview.js +35 -6
  17. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  18. package/dist/modules/audit_logs/backend/audit-logs/page.js +24 -14
  19. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  20. package/dist/modules/auth/api/users/route.js +2 -6
  21. package/dist/modules/auth/api/users/route.js.map +2 -2
  22. package/dist/modules/auth/cli.js +2 -3
  23. package/dist/modules/auth/cli.js.map +2 -2
  24. package/dist/modules/configs/lib/upgrade-actions.js +0 -22
  25. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  26. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +0 -1
  27. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  28. package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -17
  29. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  30. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +1 -6
  31. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  32. package/dist/modules/customer_accounts/api/portal/users-invite.js +1 -6
  33. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  34. package/dist/modules/customer_accounts/services/customerInvitationService.js +0 -1
  35. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  36. package/dist/modules/customer_accounts/services/customerRbacService.js +1 -6
  37. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  38. package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
  39. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  40. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
  41. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  42. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  43. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  44. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +43 -37
  45. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  46. package/dist/modules/customers/components/detail/DealDetailTabs.js +35 -23
  47. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  48. package/dist/modules/customers/components/detail/DetailTabsLayout.js +21 -6
  49. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  50. package/dist/modules/customers/components/detail/MobilePersonDetail.js +32 -26
  51. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  52. package/dist/modules/customers/components/detail/PersonDetailTabs.js +39 -29
  53. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  54. package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
  55. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  56. package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
  57. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  58. package/dist/modules/dashboards/cli.js +3 -4
  59. package/dist/modules/dashboards/cli.js.map +2 -2
  60. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
  61. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  62. package/dist/modules/dictionaries/commands/entries.js +14 -7
  63. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  64. package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
  65. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  66. package/dist/modules/dictionaries/commands/factory.js +3 -9
  67. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  68. package/dist/modules/dictionaries/fields/dictionary.js +41 -56
  69. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  70. package/dist/modules/directory/utils/organizationScope.js +3 -10
  71. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  72. package/dist/modules/entities/api/records.js +4 -5
  73. package/dist/modules/entities/api/records.js.map +2 -2
  74. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
  75. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  76. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  77. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  78. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
  79. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  80. package/dist/modules/entities/cli.js +1 -2
  81. package/dist/modules/entities/cli.js.map +2 -2
  82. package/dist/modules/feature_toggles/api/check/boolean/route.js +14 -0
  83. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  84. package/dist/modules/feature_toggles/defaults.json +0 -8
  85. package/dist/modules/integrations/backend/integrations/[id]/page.js +80 -9
  86. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  87. package/dist/modules/payment_gateways/data/entities.js +0 -45
  88. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  89. package/dist/modules/payment_gateways/lib/gateway-service.js +38 -173
  90. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  91. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +14 -16
  92. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  93. package/dist/modules/planner/components/AvailabilityRulesEditor.js +30 -18
  94. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  95. package/dist/modules/query_index/api/status.js +8 -2
  96. package/dist/modules/query_index/api/status.js.map +2 -2
  97. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  98. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  99. package/dist/modules/sales/backend/sales/channels/create/page.js +0 -6
  100. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  101. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +0 -2
  102. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  103. package/dist/modules/sales/backend/sales/channels/offers/page.js +0 -6
  104. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  105. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +0 -2
  106. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  107. package/dist/modules/sales/backend/sales/channels/page.js +0 -6
  108. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  109. package/dist/modules/sales/backend/sales/channels/page.meta.js +0 -2
  110. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  111. package/dist/modules/sales/backend/sales/documents/[id]/page.js +4 -6
  112. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  113. package/dist/modules/sales/commands/documents.js +0 -44
  114. package/dist/modules/sales/commands/documents.js.map +2 -2
  115. package/dist/modules/sales/commands/returns.js +27 -2
  116. package/dist/modules/sales/commands/returns.js.map +2 -2
  117. package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
  118. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  119. package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
  120. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  121. package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
  122. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  123. package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -51
  124. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  125. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +8 -10
  126. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  127. package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
  128. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  129. package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
  130. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  131. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +25 -22
  132. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  133. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +14 -12
  134. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  135. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +29 -10
  136. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  137. package/dist/modules/sync_excel/lib/adapters/customers.js +20 -19
  138. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  139. package/dist/modules/sync_excel/lib/parser.js +0 -185
  140. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  141. package/dist/modules/sync_excel/lib/upload-storage.js +1 -20
  142. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  143. package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
  144. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  145. package/dist/modules/workflows/lib/activity-executor.js +1 -1
  146. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  147. package/dist/modules/workflows/lib/find-definition.js +18 -2
  148. package/dist/modules/workflows/lib/find-definition.js.map +2 -2
  149. package/dist/modules/workflows/lib/signal-handler.js +3 -2
  150. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  151. package/dist/modules/workflows/lib/step-handler.js +4 -8
  152. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  153. package/dist/modules/workflows/lib/task-handler.js +3 -5
  154. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  155. package/dist/modules/workflows/lib/timer-handler.js +2 -1
  156. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  157. package/dist/modules/workflows/lib/transition-handler.js +5 -13
  158. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  159. package/dist/modules/workflows/lib/workflow-executor.js +11 -9
  160. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  161. package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
  162. package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
  163. package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
  164. package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
  165. package/dist/modules/workflows/workflows.js +2 -0
  166. package/dist/modules/workflows/workflows.js.map +2 -2
  167. package/generated/entities.ids.generated.ts +0 -1
  168. package/generated/entity-fields-registry.ts +0 -12
  169. package/package.json +8 -9
  170. package/src/modules/api_keys/api/keys/route.ts +13 -36
  171. package/src/modules/api_keys/cli.ts +3 -2
  172. package/src/modules/attachments/api/file/[id]/route.ts +7 -11
  173. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
  174. package/src/modules/attachments/api/route.ts +21 -26
  175. package/src/modules/attachments/components/AttachmentContentPreview.tsx +44 -9
  176. package/src/modules/audit_logs/backend/audit-logs/page.tsx +22 -12
  177. package/src/modules/auth/api/users/route.ts +2 -11
  178. package/src/modules/auth/cli.ts +2 -3
  179. package/src/modules/configs/i18n/de.json +1 -5
  180. package/src/modules/configs/i18n/en.json +1 -5
  181. package/src/modules/configs/i18n/es.json +1 -5
  182. package/src/modules/configs/i18n/pl.json +1 -5
  183. package/src/modules/configs/lib/upgrade-actions.ts +0 -24
  184. package/src/modules/customer_accounts/AGENTS.md +6 -8
  185. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +0 -1
  186. package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -17
  187. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +1 -6
  188. package/src/modules/customer_accounts/api/portal/users-invite.ts +1 -6
  189. package/src/modules/customer_accounts/services/customerInvitationService.ts +0 -1
  190. package/src/modules/customer_accounts/services/customerRbacService.ts +2 -9
  191. package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
  192. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
  193. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +2 -6
  194. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +55 -39
  195. package/src/modules/customers/components/detail/DealDetailTabs.tsx +49 -24
  196. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +24 -13
  197. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +31 -25
  198. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +50 -29
  199. package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
  200. package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
  201. package/src/modules/dashboards/cli.ts +9 -4
  202. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
  203. package/src/modules/dictionaries/commands/entries.ts +16 -7
  204. package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
  205. package/src/modules/dictionaries/commands/factory.ts +2 -8
  206. package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
  207. package/src/modules/dictionaries/i18n/de.json +0 -2
  208. package/src/modules/dictionaries/i18n/en.json +0 -2
  209. package/src/modules/dictionaries/i18n/es.json +0 -2
  210. package/src/modules/dictionaries/i18n/pl.json +0 -2
  211. package/src/modules/directory/utils/organizationScope.ts +2 -36
  212. package/src/modules/entities/api/records.ts +7 -5
  213. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
  214. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
  215. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
  216. package/src/modules/entities/cli.ts +1 -2
  217. package/src/modules/entities/i18n/de.json +0 -34
  218. package/src/modules/entities/i18n/en.json +0 -34
  219. package/src/modules/entities/i18n/es.json +0 -34
  220. package/src/modules/entities/i18n/pl.json +0 -34
  221. package/src/modules/feature_toggles/api/check/boolean/route.ts +15 -0
  222. package/src/modules/feature_toggles/defaults.json +0 -8
  223. package/src/modules/integrations/backend/integrations/[id]/page.tsx +59 -16
  224. package/src/modules/payment_gateways/data/entities.ts +0 -39
  225. package/src/modules/payment_gateways/lib/gateway-service.ts +40 -211
  226. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +0 -236
  227. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +20 -14
  228. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +34 -16
  229. package/src/modules/query_index/api/status.ts +8 -2
  230. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +37 -27
  231. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +0 -3
  232. package/src/modules/sales/backend/sales/channels/create/page.tsx +0 -7
  233. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +0 -3
  234. package/src/modules/sales/backend/sales/channels/offers/page.tsx +0 -7
  235. package/src/modules/sales/backend/sales/channels/page.meta.ts +0 -4
  236. package/src/modules/sales/backend/sales/channels/page.tsx +0 -7
  237. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +5 -7
  238. package/src/modules/sales/commands/documents.ts +0 -65
  239. package/src/modules/sales/commands/returns.ts +32 -2
  240. package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
  241. package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
  242. package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
  243. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +3 -53
  244. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +8 -10
  245. package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
  246. package/src/modules/sales/i18n/de.json +0 -5
  247. package/src/modules/sales/i18n/en.json +0 -5
  248. package/src/modules/sales/i18n/es.json +0 -5
  249. package/src/modules/sales/i18n/pl.json +0 -5
  250. package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
  251. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +34 -25
  252. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -11
  253. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +28 -11
  254. package/src/modules/sync_excel/lib/adapters/customers.ts +22 -20
  255. package/src/modules/sync_excel/lib/parser.ts +0 -245
  256. package/src/modules/sync_excel/lib/upload-storage.ts +1 -23
  257. package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
  258. package/src/modules/workflows/lib/activity-executor.ts +22 -2
  259. package/src/modules/workflows/lib/find-definition.ts +53 -1
  260. package/src/modules/workflows/lib/signal-handler.ts +5 -4
  261. package/src/modules/workflows/lib/step-handler.ts +4 -8
  262. package/src/modules/workflows/lib/task-handler.ts +2 -4
  263. package/src/modules/workflows/lib/timer-handler.ts +3 -2
  264. package/src/modules/workflows/lib/transition-handler.ts +5 -13
  265. package/src/modules/workflows/lib/workflow-executor.ts +11 -8
  266. package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
  267. package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
  268. package/src/modules/workflows/workflows.ts +2 -0
  269. package/dist/generated/entities/gateway_session_initialization/index.js +0 -23
  270. package/dist/generated/entities/gateway_session_initialization/index.js.map +0 -7
  271. package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
  272. package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
  273. package/dist/modules/attachments/lib/requestScope.js +0 -10
  274. package/dist/modules/attachments/lib/requestScope.js.map +0 -7
  275. package/dist/modules/auth/api/autologin.js +0 -85
  276. package/dist/modules/auth/api/autologin.js.map +0 -7
  277. package/dist/modules/auth/lib/autologin.js +0 -19
  278. package/dist/modules/auth/lib/autologin.js.map +0 -7
  279. package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
  280. package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
  281. package/dist/modules/payment_gateways/lib/session-idempotency.js +0 -104
  282. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +0 -7
  283. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +0 -11
  284. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +0 -7
  285. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +0 -15
  286. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +0 -7
  287. package/dist/modules/sales/components/useSalesChannelsEnabled.js +0 -44
  288. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +0 -7
  289. package/dist/modules/sales/lib/salesChannelsToggle.js +0 -18
  290. package/dist/modules/sales/lib/salesChannelsToggle.js.map +0 -7
  291. package/dist/modules/sales/lib/salesChannelsToggleId.js +0 -5
  292. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +0 -7
  293. package/generated/entities/gateway_session_initialization/index.ts +0 -10
  294. package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
  295. package/src/modules/attachments/lib/requestScope.ts +0 -27
  296. package/src/modules/auth/api/autologin.ts +0 -100
  297. package/src/modules/auth/lib/autologin.ts +0 -36
  298. package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
  299. package/src/modules/payment_gateways/lib/session-idempotency.ts +0 -142
  300. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +0 -10
  301. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +0 -21
  302. package/src/modules/sales/components/useSalesChannelsEnabled.ts +0 -59
  303. package/src/modules/sales/lib/salesChannelsToggle.ts +0 -26
  304. package/src/modules/sales/lib/salesChannelsToggleId.ts +0 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/api/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '../lib/imageUrls'\nimport { ensureDefaultPartitions, resolveDefaultPartitionCode, sanitizePartitionCode } from '../lib/partitions'\nimport { Attachment, AttachmentPartition } from '../data/entities'\nimport { extractAttachmentContent } from '../lib/textExtraction'\nimport { requestOcrProcessing } from '../lib/ocrQueue'\nimport { StorageDriverFactory } from '../lib/drivers'\nimport { OcrService, shouldUseLlmOcr } from '../lib/ocrService'\nimport { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'\nimport { assertAttachmentScopeInvariant } from '../lib/access'\nimport { resolveAttachmentOrganizationId } from '../lib/requestScope'\nimport {\n mergeAttachmentMetadata,\n normalizeAttachmentAssignments,\n normalizeAttachmentTags,\n readAttachmentMetadata,\n upsertAssignment,\n type AttachmentAssignment,\n} from '../lib/metadata'\nimport { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { emitCrudSideEffects, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '../lib/crud'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\nimport {\n detectAttachmentMimeType,\n hasDangerousExecutableExtension,\n isActiveContentAttachment,\n sanitizeUploadedFileName,\n} from '../lib/security'\nimport {\n isMultipartRequestWithinUploadLimit,\n resolveAttachmentMaxBytes,\n willExceedAttachmentTenantQuota,\n} from '../lib/upload-limits'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('attachments')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['attachments.view'] },\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nconst attachmentQuerySchema = z.object({\n entityId: z.string().min(1).describe('Entity identifier that owns the attachments'),\n recordId: z.string().min(1).describe('Record identifier within the entity'),\n page: z.coerce.number().min(1).optional(),\n pageSize: z.coerce.number().min(1).max(100).optional(),\n})\n\nconst attachmentAssignmentSchema = z.object({\n type: z.string().describe('Assignment type identifier'),\n id: z.string().describe('Assignment record identifier'),\n href: z.string().nullable().optional().describe('Optional link to the related record'),\n label: z.string().nullable().optional().describe('Optional label for the assignment'),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string().describe('Attachment identifier'),\n url: z.string().describe('Public path to the stored asset'),\n fileName: z.string().describe('Original filename'),\n fileSize: z.number().int().nonnegative().describe('File size in bytes'),\n createdAt: z.string().describe('Upload timestamp (ISO 8601)'),\n mimeType: z.string().nullable().optional().describe('MIME type of the file'),\n thumbnailUrl: z.string().optional().describe('Helper route that renders a thumbnail'),\n partitionCode: z.string().optional().describe('Partition identifier'),\n tags: z.array(z.string()).optional().describe('Tags assigned to the attachment'),\n content: z.string().nullable().optional().describe('Extracted text or markdown content'),\n assignments: z.array(attachmentAssignmentSchema).optional().describe('Records that reference this attachment'),\n})\n\nconst attachmentListResponseSchema = z.object({\n items: z.array(attachmentItemSchema),\n total: z.number().int().nonnegative().optional(),\n page: z.number().int().min(1).optional(),\n pageSize: z.number().int().min(1).optional(),\n totalPages: z.number().int().min(1).optional(),\n})\n\nconst attachmentUploadBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n fieldKey: z.string().optional(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n customFields: z\n .string()\n .optional()\n .describe('JSON encoded map of custom field values collected from the upload form.'),\n})\n\nconst attachmentDeleteQuerySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst uploadResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string(),\n url: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n thumbnailUrl: z.string().optional(),\n content: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n assignments: z.array(attachmentAssignmentSchema).optional(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n }),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nfunction parseCustomFieldsEntry(value: FormDataEntryValue | null): Record<string, unknown> {\n if (!value) return {}\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n const parsed = JSON.parse(trimmed)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n } catch {\n return {}\n }\n }\n if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n return { ...(value as Record<string, unknown>) }\n }\n return {}\n}\n\nfunction buildFormPayload(form: FormData): Record<string, unknown> {\n const payload: Record<string, unknown> = {}\n form.forEach((value, key) => {\n if (key === 'customFields') {\n payload.customFields = parseCustomFieldsEntry(value)\n return\n }\n payload[key] = value\n })\n return payload\n}\n\nfunction parseFormTags(value: FormDataEntryValue | null): string[] {\n if (!value) return []\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentTags(parsed)\n } catch {\n return normalizeAttachmentTags(value)\n }\n }\n return []\n}\n\nfunction parseFormAssignments(value: FormDataEntryValue | null): AttachmentAssignment[] {\n if (!value) return []\n if (typeof value !== 'string') return []\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentAssignments(parsed)\n } catch {\n return []\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsedQuery = attachmentQuerySchema.safeParse({\n entityId: url.searchParams.get('entityId') || '',\n recordId: url.searchParams.get('recordId') || '',\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'entityId and recordId are required' }, { status: 400 })\n }\n const { entityId, recordId, page, pageSize } = parsedQuery.data\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }\n if (orgId) filter.organizationId = orgId\n const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }\n const usePaging = typeof page === 'number' && typeof pageSize === 'number'\n const total = usePaging ? await em.count(Attachment, filter) : null\n const currentPage = usePaging ? Math.max(1, page) : null\n const currentPageSize = usePaging ? pageSize : null\n const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize!)) : null\n const pageOffset = usePaging ? (Math.min(currentPage!, totalPages!) - 1) * currentPageSize! : undefined\n const items = await findWithDecryption(\n em,\n Attachment,\n filter,\n {\n orderBy,\n ...(usePaging\n ? {\n limit: currentPageSize!,\n offset: pageOffset,\n }\n : {}),\n },\n {\n tenantId: auth.tenantId ?? null,\n organizationId: orgId ?? null,\n },\n )\n return NextResponse.json({\n items: items.map((a: any) => {\n const metadata = readAttachmentMetadata(a.storageMetadata)\n return {\n id: a.id,\n url: a.url,\n fileName: a.fileName,\n fileSize: a.fileSize,\n createdAt: a.createdAt,\n mimeType: a.mimeType ?? null,\n partitionCode: a.partitionCode,\n content: a.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(a.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(a.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n }\n }),\n ...(usePaging\n ? {\n total,\n page: Math.min(currentPage!, totalPages!),\n pageSize: currentPageSize,\n totalPages,\n }\n : {}),\n })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // A superadmin browsing with \"All organizations\" selected has no concrete\n // organization scope, but an attachment must be stored under exactly one\n // organization (the scope invariant forbids partial-null rows). Reject with a\n // clear, actionable error instead of a 401 \u2014 a 401 makes the client-side\n // fetch layer treat it as session expiry, show a misleading toast, and reset\n // the in-progress form (#3764).\n if (!auth.orgId) {\n return NextResponse.json({\n error: t('attachments.errors.selectOrganization', 'Select a specific organization before uploading an attachment.'),\n }, { status: 400 })\n }\n const tenantId = auth.tenantId\n\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n return NextResponse.json({ error: 'Attachment exceeds the maximum upload size.' }, { status: 413 })\n }\n\n const form = await req.formData()\n const formPayload = buildFormPayload(form)\n const customFieldValues = splitCustomFieldPayload(formPayload).custom\n const entityId = String(form.get('entityId') || '')\n const recordId = String(form.get('recordId') || '')\n const fieldKey = String(form.get('fieldKey') || '')\n const file = form.get('file') as unknown as File | null\n if (!entityId || !recordId || !file) return NextResponse.json({ error: 'entityId, recordId and file are required' }, { status: 400 })\n const partitionOverrideRaw = form.get('partitionCode')\n const partitionOverride =\n typeof partitionOverrideRaw === 'string' && partitionOverrideRaw.trim().length > 0\n ? sanitizePartitionCode(partitionOverrideRaw)\n : null\n const tags = parseFormTags(form.get('tags'))\n const assignmentsFromForm = parseFormAssignments(form.get('assignments'))\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n if (!orgId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n await ensureDefaultPartitions(em)\n // Optional per-field validations\n let partitionFromField: string | null = null\n let fieldMaxAttachmentSizeMb: number | null = null\n if (fieldKey) {\n try {\n const { CustomFieldDef } = await import('@open-mercato/core/modules/entities/data/entities')\n const def = await em.findOne(CustomFieldDef, {\n entityId,\n key: fieldKey,\n $and: [\n { $or: [ { tenantId: auth.tenantId }, { tenantId: null } ] },\n ],\n isActive: true,\n })\n const cfg = (def as any)?.configJson || {}\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n if (Array.isArray(cfg.acceptExtensions) && cfg.acceptExtensions.length) {\n const allowed = new Set((cfg.acceptExtensions as any[]).map((x: any) => String(x).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) return NextResponse.json({ error: 'File type not allowed' }, { status: 400 })\n }\n if (typeof cfg.maxAttachmentSizeMb === 'number' && cfg.maxAttachmentSizeMb > 0) {\n fieldMaxAttachmentSizeMb = cfg.maxAttachmentSizeMb\n }\n if (typeof cfg.partitionCode === 'string' && cfg.partitionCode.trim().length > 0) {\n partitionFromField = sanitizePartitionCode(cfg.partitionCode)\n }\n } catch {}\n }\n if (hasDangerousExecutableExtension(file.name)) {\n return NextResponse.json({\n error: t('attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'),\n }, { status: 400 })\n }\n const effectiveMaxBytes = resolveAttachmentMaxBytes(fieldMaxAttachmentSizeMb)\n if (file.size > effectiveMaxBytes) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n const tenantUsageBytes = await readTenantAttachmentUsageBytes(em, tenantId)\n if (willExceedAttachmentTenantQuota(tenantUsageBytes, file.size)) {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n const buf = Buffer.from(await file.arrayBuffer())\n const safeName = sanitizeUploadedFileName(file.name)\n const fileMimeType = detectAttachmentMimeType(buf, safeName, (file as any).type)\n if (isActiveContentAttachment(buf, safeName, fileMimeType)) {\n return NextResponse.json({ error: t('attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.') }, { status: 400 })\n }\n const defaultPartitionCode = resolveDefaultPartitionCode(entityId)\n const resolvedPartitionCode = partitionOverride ?? partitionFromField ?? defaultPartitionCode\n const partitionCodeCandidates = Array.from(\n new Set(\n [partitionOverride, partitionFromField, resolvedPartitionCode].filter(\n (code): code is string => typeof code === 'string' && code.length > 0,\n ),\n ),\n )\n let partition: AttachmentPartition | null = null\n for (const code of partitionCodeCandidates) {\n const record = await em.findOne(AttachmentPartition, { code })\n if (record) {\n partition = record\n break\n }\n }\n if (!partition) {\n partition = await em.findOne(AttachmentPartition, { code: defaultPartitionCode })\n }\n if (!partition) {\n return NextResponse.json({ error: 'Storage partition is not configured.' }, { status: 400 })\n }\n const requestedPublicOverride =\n typeof partitionOverride === 'string' &&\n partitionOverride.length > 0 &&\n partition.code === partitionOverride &&\n partition.isPublic === true &&\n partition.code !== defaultPartitionCode &&\n partition.code !== partitionFromField\n if (requestedPublicOverride) {\n return NextResponse.json({ error: t('attachments.errors.publicPartitionBlocked', 'Public storage partitions cannot be selected explicitly for this upload.') }, { status: 403 })\n }\n const uploadDriver = await storageDriverFactory.resolveForPartition(partition.code, { tenantId, organizationId: orgId })\n let storedPath: string\n try {\n const stored = await uploadDriver.store({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n buffer: buf,\n })\n storedPath = stored.storagePath\n } catch (error) {\n logger.error('Failed to persist file', { err: error })\n return NextResponse.json({ error: 'Failed to persist attachment.' }, { status: 500 })\n }\n\n const requiresOcr =\n typeof (partition as any).requiresOcr === 'boolean'\n ? Boolean((partition as any).requiresOcr)\n : resolveDefaultAttachmentOcrEnabled()\n let extractedContent: string | null = null\n const wantsLlmOcr = requiresOcr && shouldUseLlmOcr(fileMimeType, safeName)\n const ocrService = wantsLlmOcr ? new OcrService() : null\n const useLlmOcr = Boolean(wantsLlmOcr && ocrService?.available)\n\n if (requiresOcr && !useLlmOcr) {\n const { filePath: localPath, cleanup } = await uploadDriver.toLocalPath(partition.code, storedPath)\n try {\n extractedContent = await extractAttachmentContent({\n filePath: localPath,\n mimeType: fileMimeType,\n })\n } catch (error) {\n logger.error('Failed to extract attachment content', { err: error })\n } finally {\n await cleanup().catch(() => {})\n }\n }\n\n let assignments = assignmentsFromForm.slice()\n if (entityId !== LIBRARY_ENTITY_ID) {\n assignments = upsertAssignment(assignments, { type: entityId, id: recordId })\n }\n const metadata = mergeAttachmentMetadata(null, { assignments, tags })\n const attachmentId = randomUUID()\n assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: orgId })\n const att = em.create(Attachment, {\n id: attachmentId,\n entityId,\n recordId,\n organizationId: orgId,\n tenantId: auth.tenantId!,\n fileName: safeName,\n mimeType: fileMimeType,\n fileSize: buf.length,\n partitionCode: partition.code,\n storageDriver: partition.storageDriver || 'local',\n storagePath: storedPath,\n url: buildAttachmentFileUrl(attachmentId),\n content: extractedContent,\n storageMetadata: metadata,\n })\n // Persist the attachment row and its custom-field values atomically so a\n // custom-field failure cannot leave behind a committed orphan attachment.\n try {\n await em.transactional(async (tx) => {\n await tx.persist(att).flush()\n if (dataEngine) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.attachments.attachment,\n recordId: attachmentId,\n tenantId,\n organizationId: orgId,\n values: customFieldValues,\n })\n }\n })\n } catch (error) {\n logger.error('Failed to persist attachment with custom attributes', { err: error })\n return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })\n }\n\n if (useLlmOcr) {\n requestOcrProcessing(em, att, uploadDriver, storedPath).catch((error) => {\n logger.error('Failed to queue OCR processing', { err: error })\n })\n } else if (wantsLlmOcr) {\n logger.warn('OCR requested but OPENAI_API_KEY not configured, falling back to text extraction when available')\n }\n\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: att,\n identifiers: {\n id: att.id,\n organizationId: att.organizationId ?? null,\n tenantId: att.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n\n return NextResponse.json({\n ok: true,\n item: {\n id: attachmentId,\n url: att.url,\n fileName: safeName,\n fileSize: buf.length,\n partitionCode: partition.code,\n thumbnailUrl: buildAttachmentImageUrl(attachmentId, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(safeName),\n }),\n content: extractedContent ?? null,\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n customFields: Object.keys(customFieldValues).length ? customFieldValues : undefined,\n },\n })\n}\n\nasync function readTenantAttachmentUsageBytes(em: EntityManager, tenantId: string): Promise<number> {\n try {\n const db = em.getKysely<any>() as any\n const row = await db\n .selectFrom('attachments')\n .select(sql<string>`sum(file_size)`.as('total_size'))\n .where('tenant_id', '=', tenantId)\n .executeTakeFirst() as { total_size: string | number | null } | undefined\n const total = row?.total_size\n if (typeof total === 'number') return Number.isFinite(total) ? total : 0\n if (typeof total === 'string') {\n const parsed = Number(total)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n } catch {\n return 0\n }\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const id = url.searchParams.get('id') || ''\n if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const dataEngine = container.resolve('dataEngine')\n const storageDriverFactory =\n (container.resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n // Resolve the currently selected organization (#3765) so a multi-org admin who\n // switched the header org deletes within that org, not their pinned home org.\n // A superadmin browsing with \"All organizations\" selected has no concrete org,\n // so resolution returns null and the delete falls back to a tenant-only scope\n // (#3764) \u2014 letting them remove any attachment in the tenant.\n const orgId = await resolveAttachmentOrganizationId(container, auth, req)\n const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }\n if (orgId) deleteFilter.organizationId = orgId\n const record = await em.findOne(Attachment, deleteFilter)\n if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })\n await em.remove(record).flush()\n await clearAttachmentThumbnailCache(record.partitionCode, record.id).catch((error) => {\n logger.error('Failed to cleanup cached thumbnails', { err: error })\n })\n if (record.storagePath) {\n const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {\n tenantId: record.tenantId ?? auth.tenantId!,\n organizationId: record.organizationId ?? orgId ?? '',\n })\n await delDriver.delete(record.partitionCode, record.storagePath)\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId ?? null,\n tenantId: record.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage entity attachments',\n description: 'Upload and list attachments associated with module entities and records.',\n methods: {\n GET: {\n summary: 'List attachments for a record',\n description: 'Returns uploaded attachments for the given entity record, ordered by newest first.',\n query: attachmentQuerySchema,\n responses: [\n { status: 200, description: 'Attachments found for the record', schema: attachmentListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity or record identifiers', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Upload attachment',\n description: 'Uploads a new attachment using multipart form-data and stores metadata for later retrieval.',\n requestBody: {\n contentType: 'multipart/form-data',\n schema: attachmentUploadBodySchema,\n },\n responses: [\n { status: 200, description: 'Attachment stored successfully', schema: uploadResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Payload validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Attachment violates field constraints', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete attachment',\n description: 'Removes an uploaded attachment and deletes the stored asset.',\n query: attachmentDeleteQuerySchema,\n responses: [\n { status: 200, description: 'Attachment deleted', schema: z.object({ ok: z.literal(true) }) },\n { status: 404, description: 'Attachment not found', schema: errorSchema },\n ],\n errors: [\n { status: 400, description: 'Missing attachment identifier', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { z } from 'zod'\nimport { sql } from 'kysely'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildAttachmentFileUrl, buildAttachmentImageUrl, slugifyAttachmentFileName } from '../lib/imageUrls'\nimport { ensureDefaultPartitions, resolveDefaultPartitionCode, sanitizePartitionCode } from '../lib/partitions'\nimport { Attachment, AttachmentPartition } from '../data/entities'\nimport { extractAttachmentContent } from '../lib/textExtraction'\nimport { requestOcrProcessing } from '../lib/ocrQueue'\nimport { StorageDriverFactory } from '../lib/drivers'\nimport { OcrService, shouldUseLlmOcr } from '../lib/ocrService'\nimport { clearAttachmentThumbnailCache } from '../lib/thumbnailCache'\nimport { assertAttachmentScopeInvariant } from '../lib/access'\nimport {\n mergeAttachmentMetadata,\n normalizeAttachmentAssignments,\n normalizeAttachmentTags,\n readAttachmentMetadata,\n upsertAssignment,\n type AttachmentAssignment,\n} from '../lib/metadata'\nimport { randomUUID } from 'crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { emitCrudSideEffects, setCustomFieldsIfAny } from '@open-mercato/shared/lib/commands/helpers'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { attachmentCrudEvents, attachmentCrudIndexer } from '../lib/crud'\nimport { E } from '#generated/entities.ids.generated'\nimport { resolveDefaultAttachmentOcrEnabled } from '../lib/ocrConfig'\nimport {\n detectAttachmentMimeType,\n hasDangerousExecutableExtension,\n isActiveContentAttachment,\n sanitizeUploadedFileName,\n} from '../lib/security'\nimport {\n isMultipartRequestWithinUploadLimit,\n resolveAttachmentMaxBytes,\n willExceedAttachmentTenantQuota,\n} from '../lib/upload-limits'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('attachments')\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['attachments.view'] },\n POST: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['attachments.manage'] },\n}\n\nconst attachmentQuerySchema = z.object({\n entityId: z.string().min(1).describe('Entity identifier that owns the attachments'),\n recordId: z.string().min(1).describe('Record identifier within the entity'),\n page: z.coerce.number().min(1).optional(),\n pageSize: z.coerce.number().min(1).max(100).optional(),\n})\n\nconst attachmentAssignmentSchema = z.object({\n type: z.string().describe('Assignment type identifier'),\n id: z.string().describe('Assignment record identifier'),\n href: z.string().nullable().optional().describe('Optional link to the related record'),\n label: z.string().nullable().optional().describe('Optional label for the assignment'),\n})\n\nconst attachmentItemSchema = z.object({\n id: z.string().describe('Attachment identifier'),\n url: z.string().describe('Public path to the stored asset'),\n fileName: z.string().describe('Original filename'),\n fileSize: z.number().int().nonnegative().describe('File size in bytes'),\n createdAt: z.string().describe('Upload timestamp (ISO 8601)'),\n mimeType: z.string().nullable().optional().describe('MIME type of the file'),\n thumbnailUrl: z.string().optional().describe('Helper route that renders a thumbnail'),\n partitionCode: z.string().optional().describe('Partition identifier'),\n tags: z.array(z.string()).optional().describe('Tags assigned to the attachment'),\n content: z.string().nullable().optional().describe('Extracted text or markdown content'),\n assignments: z.array(attachmentAssignmentSchema).optional().describe('Records that reference this attachment'),\n})\n\nconst attachmentListResponseSchema = z.object({\n items: z.array(attachmentItemSchema),\n total: z.number().int().nonnegative().optional(),\n page: z.number().int().min(1).optional(),\n pageSize: z.number().int().min(1).optional(),\n totalPages: z.number().int().min(1).optional(),\n})\n\nconst attachmentUploadBodySchema = z.object({\n entityId: z.string().min(1),\n recordId: z.string().min(1),\n fieldKey: z.string().optional(),\n file: z.string().min(1).describe('Binary file payload; supplied as multipart form-data'),\n customFields: z\n .string()\n .optional()\n .describe('JSON encoded map of custom field values collected from the upload form.'),\n})\n\nconst attachmentDeleteQuerySchema = z.object({\n id: z.string().uuid(),\n})\n\nconst uploadResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string(),\n url: z.string(),\n fileName: z.string(),\n fileSize: z.number().int().nonnegative(),\n thumbnailUrl: z.string().optional(),\n content: z.string().nullable().optional(),\n tags: z.array(z.string()).optional(),\n assignments: z.array(attachmentAssignmentSchema).optional(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n }),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nfunction parseCustomFieldsEntry(value: FormDataEntryValue | null): Record<string, unknown> {\n if (!value) return {}\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n const parsed = JSON.parse(trimmed)\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n } catch {\n return {}\n }\n }\n if (typeof value === 'object' && !Array.isArray(value) && !(value instanceof File)) {\n return { ...(value as Record<string, unknown>) }\n }\n return {}\n}\n\nfunction buildFormPayload(form: FormData): Record<string, unknown> {\n const payload: Record<string, unknown> = {}\n form.forEach((value, key) => {\n if (key === 'customFields') {\n payload.customFields = parseCustomFieldsEntry(value)\n return\n }\n payload[key] = value\n })\n return payload\n}\n\nfunction parseFormTags(value: FormDataEntryValue | null): string[] {\n if (!value) return []\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentTags(parsed)\n } catch {\n return normalizeAttachmentTags(value)\n }\n }\n return []\n}\n\nfunction parseFormAssignments(value: FormDataEntryValue | null): AttachmentAssignment[] {\n if (!value) return []\n if (typeof value !== 'string') return []\n const trimmed = value.trim()\n if (!trimmed) return []\n try {\n const parsed = JSON.parse(trimmed)\n return normalizeAttachmentAssignments(parsed)\n } catch {\n return []\n }\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const parsedQuery = attachmentQuerySchema.safeParse({\n entityId: url.searchParams.get('entityId') || '',\n recordId: url.searchParams.get('recordId') || '',\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n })\n if (!parsedQuery.success) {\n return NextResponse.json({ error: 'entityId and recordId are required' }, { status: 400 })\n }\n const { entityId, recordId, page, pageSize } = parsedQuery.data\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const filter: Record<string, unknown> = { entityId, recordId, tenantId: auth.tenantId! }\n if (auth.orgId) filter.organizationId = auth.orgId\n const orderBy: Record<string, 'ASC' | 'DESC'> = { createdAt: 'DESC' }\n const usePaging = typeof page === 'number' && typeof pageSize === 'number'\n const total = usePaging ? await em.count(Attachment, filter) : null\n const currentPage = usePaging ? Math.max(1, page) : null\n const currentPageSize = usePaging ? pageSize : null\n const totalPages = usePaging && total !== null ? Math.max(1, Math.ceil(total / currentPageSize!)) : null\n const pageOffset = usePaging ? (Math.min(currentPage!, totalPages!) - 1) * currentPageSize! : undefined\n const items = await findWithDecryption(\n em,\n Attachment,\n filter,\n {\n orderBy,\n ...(usePaging\n ? {\n limit: currentPageSize!,\n offset: pageOffset,\n }\n : {}),\n },\n {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n },\n )\n return NextResponse.json({\n items: items.map((a: any) => {\n const metadata = readAttachmentMetadata(a.storageMetadata)\n return {\n id: a.id,\n url: a.url,\n fileName: a.fileName,\n fileSize: a.fileSize,\n createdAt: a.createdAt,\n mimeType: a.mimeType ?? null,\n partitionCode: a.partitionCode,\n content: a.content ?? null,\n thumbnailUrl: buildAttachmentImageUrl(a.id, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(a.fileName),\n }),\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n }\n }),\n ...(usePaging\n ? {\n total,\n page: Math.min(currentPage!, totalPages!),\n pageSize: currentPageSize,\n totalPages,\n }\n : {}),\n })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n // A superadmin browsing with \"All organizations\" selected has no concrete\n // organization scope, but an attachment must be stored under exactly one\n // organization (the scope invariant forbids partial-null rows). Reject with a\n // clear, actionable error instead of a 401 \u2014 a 401 makes the client-side\n // fetch layer treat it as session expiry, show a misleading toast, and reset\n // the in-progress form (#3764).\n if (!auth.orgId) {\n return NextResponse.json({\n error: t('attachments.errors.selectOrganization', 'Select a specific organization before uploading an attachment.'),\n }, { status: 400 })\n }\n const tenantId = auth.tenantId\n const orgId = auth.orgId\n\n const contentType = req.headers.get('content-type') || ''\n if (!contentType.toLowerCase().includes('multipart/form-data')) {\n return NextResponse.json({ error: 'Expected multipart/form-data' }, { status: 400 })\n }\n if (!isMultipartRequestWithinUploadLimit(req.headers.get('content-length'))) {\n return NextResponse.json({ error: 'Attachment exceeds the maximum upload size.' }, { status: 413 })\n }\n\n const form = await req.formData()\n const formPayload = buildFormPayload(form)\n const customFieldValues = splitCustomFieldPayload(formPayload).custom\n const entityId = String(form.get('entityId') || '')\n const recordId = String(form.get('recordId') || '')\n const fieldKey = String(form.get('fieldKey') || '')\n const file = form.get('file') as unknown as File | null\n if (!entityId || !recordId || !file) return NextResponse.json({ error: 'entityId, recordId and file are required' }, { status: 400 })\n const partitionOverrideRaw = form.get('partitionCode')\n const partitionOverride =\n typeof partitionOverrideRaw === 'string' && partitionOverrideRaw.trim().length > 0\n ? sanitizePartitionCode(partitionOverrideRaw)\n : null\n const tags = parseFormTags(form.get('tags'))\n const assignmentsFromForm = parseFormAssignments(form.get('assignments'))\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const dataEngine = resolve('dataEngine')\n const storageDriverFactory =\n (resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n await ensureDefaultPartitions(em)\n // Optional per-field validations\n let partitionFromField: string | null = null\n let fieldMaxAttachmentSizeMb: number | null = null\n if (fieldKey) {\n try {\n const { CustomFieldDef } = await import('@open-mercato/core/modules/entities/data/entities')\n const def = await em.findOne(CustomFieldDef, {\n entityId,\n key: fieldKey,\n $and: [\n { $or: [ { tenantId: auth.tenantId }, { tenantId: null } ] },\n ],\n isActive: true,\n })\n const cfg = (def as any)?.configJson || {}\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n if (Array.isArray(cfg.acceptExtensions) && cfg.acceptExtensions.length) {\n const allowed = new Set((cfg.acceptExtensions as any[]).map((x: any) => String(x).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) return NextResponse.json({ error: 'File type not allowed' }, { status: 400 })\n }\n if (typeof cfg.maxAttachmentSizeMb === 'number' && cfg.maxAttachmentSizeMb > 0) {\n fieldMaxAttachmentSizeMb = cfg.maxAttachmentSizeMb\n }\n if (typeof cfg.partitionCode === 'string' && cfg.partitionCode.trim().length > 0) {\n partitionFromField = sanitizePartitionCode(cfg.partitionCode)\n }\n } catch {}\n }\n if (hasDangerousExecutableExtension(file.name)) {\n return NextResponse.json({\n error: t('attachments.errors.dangerousExecutable', 'Executable file types are not allowed as attachments.'),\n }, { status: 400 })\n }\n const effectiveMaxBytes = resolveAttachmentMaxBytes(fieldMaxAttachmentSizeMb)\n if (file.size > effectiveMaxBytes) {\n return NextResponse.json({\n error: t('attachments.errors.maxUploadSize', 'Attachment exceeds the maximum upload size.'),\n }, { status: 413 })\n }\n const tenantUsageBytes = await readTenantAttachmentUsageBytes(em, tenantId)\n if (willExceedAttachmentTenantQuota(tenantUsageBytes, file.size)) {\n return NextResponse.json({\n error: t('attachments.errors.quotaExceeded', 'Attachment storage quota exceeded for this tenant.'),\n }, { status: 413 })\n }\n const buf = Buffer.from(await file.arrayBuffer())\n const safeName = sanitizeUploadedFileName(file.name)\n const fileMimeType = detectAttachmentMimeType(buf, safeName, (file as any).type)\n if (isActiveContentAttachment(buf, safeName, fileMimeType)) {\n return NextResponse.json({ error: t('attachments.errors.activeContentBlocked', 'Active content uploads are not allowed.') }, { status: 400 })\n }\n const defaultPartitionCode = resolveDefaultPartitionCode(entityId)\n const resolvedPartitionCode = partitionOverride ?? partitionFromField ?? defaultPartitionCode\n const partitionCodeCandidates = Array.from(\n new Set(\n [partitionOverride, partitionFromField, resolvedPartitionCode].filter(\n (code): code is string => typeof code === 'string' && code.length > 0,\n ),\n ),\n )\n let partition: AttachmentPartition | null = null\n for (const code of partitionCodeCandidates) {\n const record = await em.findOne(AttachmentPartition, { code })\n if (record) {\n partition = record\n break\n }\n }\n if (!partition) {\n partition = await em.findOne(AttachmentPartition, { code: defaultPartitionCode })\n }\n if (!partition) {\n return NextResponse.json({ error: 'Storage partition is not configured.' }, { status: 400 })\n }\n const requestedPublicOverride =\n typeof partitionOverride === 'string' &&\n partitionOverride.length > 0 &&\n partition.code === partitionOverride &&\n partition.isPublic === true &&\n partition.code !== defaultPartitionCode &&\n partition.code !== partitionFromField\n if (requestedPublicOverride) {\n return NextResponse.json({ error: t('attachments.errors.publicPartitionBlocked', 'Public storage partitions cannot be selected explicitly for this upload.') }, { status: 403 })\n }\n const uploadDriver = await storageDriverFactory.resolveForPartition(partition.code, { tenantId, organizationId: orgId })\n let storedPath: string\n try {\n const stored = await uploadDriver.store({\n partitionCode: partition.code,\n orgId,\n tenantId,\n fileName: safeName,\n buffer: buf,\n })\n storedPath = stored.storagePath\n } catch (error) {\n logger.error('Failed to persist file', { err: error })\n return NextResponse.json({ error: 'Failed to persist attachment.' }, { status: 500 })\n }\n\n const requiresOcr =\n typeof (partition as any).requiresOcr === 'boolean'\n ? Boolean((partition as any).requiresOcr)\n : resolveDefaultAttachmentOcrEnabled()\n let extractedContent: string | null = null\n const wantsLlmOcr = requiresOcr && shouldUseLlmOcr(fileMimeType, safeName)\n const ocrService = wantsLlmOcr ? new OcrService() : null\n const useLlmOcr = Boolean(wantsLlmOcr && ocrService?.available)\n\n if (requiresOcr && !useLlmOcr) {\n const { filePath: localPath, cleanup } = await uploadDriver.toLocalPath(partition.code, storedPath)\n try {\n extractedContent = await extractAttachmentContent({\n filePath: localPath,\n mimeType: fileMimeType,\n })\n } catch (error) {\n logger.error('Failed to extract attachment content', { err: error })\n } finally {\n await cleanup().catch(() => {})\n }\n }\n\n let assignments = assignmentsFromForm.slice()\n if (entityId !== LIBRARY_ENTITY_ID) {\n assignments = upsertAssignment(assignments, { type: entityId, id: recordId })\n }\n const metadata = mergeAttachmentMetadata(null, { assignments, tags })\n const attachmentId = randomUUID()\n assertAttachmentScopeInvariant({ tenantId: auth.tenantId, organizationId: auth.orgId })\n const att = em.create(Attachment, {\n id: attachmentId,\n entityId,\n recordId,\n organizationId: auth.orgId!,\n tenantId: auth.tenantId!,\n fileName: safeName,\n mimeType: fileMimeType,\n fileSize: buf.length,\n partitionCode: partition.code,\n storageDriver: partition.storageDriver || 'local',\n storagePath: storedPath,\n url: buildAttachmentFileUrl(attachmentId),\n content: extractedContent,\n storageMetadata: metadata,\n })\n // Persist the attachment row and its custom-field values atomically so a\n // custom-field failure cannot leave behind a committed orphan attachment.\n try {\n await em.transactional(async (tx) => {\n await tx.persist(att).flush()\n if (dataEngine) {\n await setCustomFieldsIfAny({\n dataEngine,\n entityId: E.attachments.attachment,\n recordId: attachmentId,\n tenantId,\n organizationId: orgId,\n values: customFieldValues,\n })\n }\n })\n } catch (error) {\n logger.error('Failed to persist attachment with custom attributes', { err: error })\n return NextResponse.json({ error: 'Failed to save attachment attributes.' }, { status: 500 })\n }\n\n if (useLlmOcr) {\n requestOcrProcessing(em, att, uploadDriver, storedPath).catch((error) => {\n logger.error('Failed to queue OCR processing', { err: error })\n })\n } else if (wantsLlmOcr) {\n logger.warn('OCR requested but OPENAI_API_KEY not configured, falling back to text extraction when available')\n }\n\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'created',\n entity: att,\n identifiers: {\n id: att.id,\n organizationId: att.organizationId ?? null,\n tenantId: att.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n\n return NextResponse.json({\n ok: true,\n item: {\n id: attachmentId,\n url: att.url,\n fileName: safeName,\n fileSize: buf.length,\n partitionCode: partition.code,\n thumbnailUrl: buildAttachmentImageUrl(attachmentId, {\n width: 320,\n height: 320,\n slug: slugifyAttachmentFileName(safeName),\n }),\n content: extractedContent ?? null,\n tags: metadata.tags ?? [],\n assignments: metadata.assignments ?? [],\n customFields: Object.keys(customFieldValues).length ? customFieldValues : undefined,\n },\n })\n}\n\nasync function readTenantAttachmentUsageBytes(em: EntityManager, tenantId: string): Promise<number> {\n try {\n const db = em.getKysely<any>() as any\n const row = await db\n .selectFrom('attachments')\n .select(sql<string>`sum(file_size)`.as('total_size'))\n .where('tenant_id', '=', tenantId)\n .executeTakeFirst() as { total_size: string | number | null } | undefined\n const total = row?.total_size\n if (typeof total === 'number') return Number.isFinite(total) ? total : 0\n if (typeof total === 'string') {\n const parsed = Number(total)\n return Number.isFinite(parsed) ? parsed : 0\n }\n return 0\n } catch {\n return 0\n }\n}\n\nexport async function DELETE(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId || (!auth.orgId && !auth.isSuperAdmin)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const url = new URL(req.url)\n const id = url.searchParams.get('id') || ''\n if (!id) return NextResponse.json({ error: 'Attachment id is required' }, { status: 400 })\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const dataEngine = resolve('dataEngine')\n const storageDriverFactory =\n (resolve('storageDriverFactory') as StorageDriverFactory | null) ?? new StorageDriverFactory(em)\n // Mirror the GET handler's superadmin bypass: a superadmin browsing with \"All\n // organizations\" selected has no concrete org, so scope the delete by tenant\n // only and let them remove any attachment in the tenant. Non-superadmins stay\n // scoped to their own organization (#3764).\n const deleteFilter: Record<string, unknown> = { id, tenantId: auth.tenantId! }\n if (auth.orgId) deleteFilter.organizationId = auth.orgId\n const record = await em.findOne(Attachment, deleteFilter)\n if (!record) return NextResponse.json({ error: 'Attachment not found' }, { status: 404 })\n await em.remove(record).flush()\n await clearAttachmentThumbnailCache(record.partitionCode, record.id).catch((error) => {\n logger.error('Failed to cleanup cached thumbnails', { err: error })\n })\n if (record.storagePath) {\n const delDriver = await storageDriverFactory.resolveForPartition(record.partitionCode, {\n tenantId: record.tenantId ?? auth.tenantId!,\n organizationId: record.organizationId ?? auth.orgId ?? '',\n })\n await delDriver.delete(record.partitionCode, record.storagePath)\n }\n if (dataEngine) {\n await emitCrudSideEffects({\n dataEngine,\n action: 'deleted',\n entity: record,\n identifiers: {\n id: record.id,\n organizationId: record.organizationId ?? null,\n tenantId: record.tenantId ?? null,\n },\n events: attachmentCrudEvents,\n indexer: attachmentCrudIndexer,\n })\n await dataEngine.flushOrmEntityChanges()\n }\n return NextResponse.json({ ok: true })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Manage entity attachments',\n description: 'Upload and list attachments associated with module entities and records.',\n methods: {\n GET: {\n summary: 'List attachments for a record',\n description: 'Returns uploaded attachments for the given entity record, ordered by newest first.',\n query: attachmentQuerySchema,\n responses: [\n { status: 200, description: 'Attachments found for the record', schema: attachmentListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing entity or record identifiers', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n POST: {\n summary: 'Upload attachment',\n description: 'Uploads a new attachment using multipart form-data and stores metadata for later retrieval.',\n requestBody: {\n contentType: 'multipart/form-data',\n schema: attachmentUploadBodySchema,\n },\n responses: [\n { status: 200, description: 'Attachment stored successfully', schema: uploadResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Payload validation error', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 403, description: 'Attachment violates field constraints', schema: errorSchema },\n ],\n },\n DELETE: {\n summary: 'Delete attachment',\n description: 'Removes an uploaded attachment and deletes the stored asset.',\n query: attachmentDeleteQuerySchema,\n responses: [\n { status: 200, description: 'Attachment deleted', schema: z.object({ ok: z.literal(true) }) },\n { status: 404, description: 'Attachment not found', schema: errorSchema },\n ],\n errors: [\n { status: 400, description: 'Missing attachment identifier', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["metadata"]
7
7
  }
@@ -2,7 +2,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { MarkdownContent } from "@open-mercato/ui/backend/markdown";
4
4
  import { Button } from "@open-mercato/ui/primitives/button";
5
- import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
6
5
  function AttachmentContentPreview({
7
6
  content,
8
7
  maxLength = 480,
@@ -15,21 +14,50 @@ function AttachmentContentPreview({
15
14
  const [expanded, setExpanded] = React.useState(false);
16
15
  const [tab, setTab] = React.useState("source");
17
16
  const text = (content ?? "").trim();
17
+ const sourceTabId = "attachment-content-preview-tab-source";
18
+ const previewTabId = "attachment-content-preview-tab-preview";
19
+ const sourcePanelId = "attachment-content-preview-panel-source";
20
+ const previewPanelId = "attachment-content-preview-panel-preview";
18
21
  if (!text) {
19
22
  return /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground italic", children: emptyLabel });
20
23
  }
21
24
  const shouldTruncate = !expanded && text.length > maxLength;
22
25
  const display = tab === "source" && shouldTruncate ? `${text.slice(0, maxLength)}\u2026` : text;
23
26
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
24
- /* @__PURE__ */ jsx(Tabs, { value: tab, onValueChange: (value) => setTab(value), variant: "underline", children: /* @__PURE__ */ jsxs(TabsList, { className: "w-full", children: [
25
- /* @__PURE__ */ jsx(TabsTrigger, { value: "source", children: sourceLabel }),
26
- /* @__PURE__ */ jsx(TabsTrigger, { value: "preview", children: previewLabel })
27
+ /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-4 text-xs", role: "tablist", "aria-label": "Content preview mode", children: [
28
+ /* @__PURE__ */ jsx(
29
+ "button",
30
+ {
31
+ type: "button",
32
+ id: sourceTabId,
33
+ role: "tab",
34
+ "aria-selected": tab === "source",
35
+ "aria-controls": sourcePanelId,
36
+ className: `-mb-px border-b-2 px-0 pb-2 font-medium transition-colors ${tab === "source" ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
37
+ onClick: () => setTab("source"),
38
+ children: sourceLabel
39
+ }
40
+ ),
41
+ /* @__PURE__ */ jsx(
42
+ "button",
43
+ {
44
+ type: "button",
45
+ id: previewTabId,
46
+ role: "tab",
47
+ "aria-selected": tab === "preview",
48
+ "aria-controls": previewPanelId,
49
+ className: `-mb-px border-b-2 px-0 pb-2 font-medium transition-colors ${tab === "preview" ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
50
+ onClick: () => setTab("preview"),
51
+ children: previewLabel
52
+ }
53
+ )
27
54
  ] }) }),
28
55
  tab === "source" ? /* @__PURE__ */ jsx(
29
56
  "div",
30
57
  {
31
58
  role: "tabpanel",
32
- "aria-label": sourceLabel,
59
+ id: sourcePanelId,
60
+ "aria-labelledby": sourceTabId,
33
61
  "data-testid": "attachment-content-preview",
34
62
  className: "whitespace-pre-wrap text-sm text-muted-foreground",
35
63
  children: display
@@ -38,7 +66,8 @@ function AttachmentContentPreview({
38
66
  "div",
39
67
  {
40
68
  role: "tabpanel",
41
- "aria-label": previewLabel,
69
+ id: previewPanelId,
70
+ "aria-labelledby": previewTabId,
42
71
  "data-testid": "markdown-preview",
43
72
  children: /* @__PURE__ */ jsx(
44
73
  MarkdownContent,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/components/AttachmentContentPreview.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport { MarkdownContent } from '@open-mercato/ui/backend/markdown'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\ntype Props = {\n content?: string | null\n maxLength?: number\n emptyLabel?: string\n showMoreLabel?: string\n showLessLabel?: string\n sourceLabel?: string\n previewLabel?: string\n}\n\nexport function AttachmentContentPreview({\n content,\n maxLength = 480,\n emptyLabel = 'No text extracted',\n showMoreLabel = 'Show more',\n showLessLabel = 'Show less',\n sourceLabel = 'Source',\n previewLabel = 'Preview',\n}: Props) {\n const [expanded, setExpanded] = React.useState(false)\n const [tab, setTab] = React.useState<'source' | 'preview'>('source')\n const text = (content ?? '').trim()\n\n if (!text) {\n return <div className=\"text-xs text-muted-foreground italic\">{emptyLabel}</div>\n }\n\n const shouldTruncate = !expanded && text.length > maxLength\n const display = tab === 'source' && shouldTruncate ? `${text.slice(0, maxLength)}\u2026` : text\n\n return (\n <div className=\"space-y-2\">\n {/* Tab Navigation */}\n <Tabs value={tab} onValueChange={(value) => setTab(value as 'source' | 'preview')} variant=\"underline\">\n <TabsList className=\"w-full\">\n <TabsTrigger value=\"source\">{sourceLabel}</TabsTrigger>\n <TabsTrigger value=\"preview\">{previewLabel}</TabsTrigger>\n </TabsList>\n </Tabs>\n\n {/* Tab Panels */}\n {tab === 'source' ? (\n <div\n role=\"tabpanel\"\n aria-label={sourceLabel}\n data-testid=\"attachment-content-preview\"\n className=\"whitespace-pre-wrap text-sm text-muted-foreground\"\n >\n {display}\n </div>\n ) : (\n <div\n role=\"tabpanel\"\n aria-label={previewLabel}\n data-testid=\"markdown-preview\"\n >\n <MarkdownContent\n body={text}\n format=\"markdown\"\n className=\"text-sm text-muted-foreground [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs\"\n />\n </div>\n )}\n\n {/* Show More/Less Button (only on source tab) */}\n {tab === 'source' && text.length > maxLength ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-1 text-xs\"\n onClick={() => setExpanded((prev) => !prev)}\n >\n {expanded ? showLessLabel : showMoreLabel}\n </Button>\n ) : null}\n </div>\n )\n}\n"],
5
- "mappings": "AA6BW,cAUH,YAVG;AA7BX,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,cAAc;AACvB,SAAS,MAAM,UAAU,mBAAmB;AAYrC,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,eAAe;AACjB,GAAU;AACR,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,KAAK,MAAM,IAAI,MAAM,SAA+B,QAAQ;AACnE,QAAM,QAAQ,WAAW,IAAI,KAAK;AAElC,MAAI,CAAC,MAAM;AACT,WAAO,oBAAC,SAAI,WAAU,wCAAwC,sBAAW;AAAA,EAC3E;AAEA,QAAM,iBAAiB,CAAC,YAAY,KAAK,SAAS;AAClD,QAAM,UAAU,QAAQ,YAAY,iBAAiB,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC,WAAM;AAEtF,SACE,qBAAC,SAAI,WAAU,aAEb;AAAA,wBAAC,QAAK,OAAO,KAAK,eAAe,CAAC,UAAU,OAAO,KAA6B,GAAG,SAAQ,aACzF,+BAAC,YAAS,WAAU,UAClB;AAAA,0BAAC,eAAY,OAAM,UAAU,uBAAY;AAAA,MACzC,oBAAC,eAAY,OAAM,WAAW,wBAAa;AAAA,OAC7C,GACF;AAAA,IAGC,QAAQ,WACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,eAAY;AAAA,QACZ,WAAU;AAAA,QAET;AAAA;AAAA,IACH,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ,eAAY;AAAA,QAEZ;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,QAAO;AAAA,YACP,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA,IACF;AAAA,IAID,QAAQ,YAAY,KAAK,SAAS,YACjC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI;AAAA,QAEzC,qBAAW,gBAAgB;AAAA;AAAA,IAC9B,IACE;AAAA,KACN;AAEJ;",
4
+ "sourcesContent": ["import * as React from 'react'\nimport { MarkdownContent } from '@open-mercato/ui/backend/markdown'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\ntype Props = {\n content?: string | null\n maxLength?: number\n emptyLabel?: string\n showMoreLabel?: string\n showLessLabel?: string\n sourceLabel?: string\n previewLabel?: string\n}\n\nexport function AttachmentContentPreview({\n content,\n maxLength = 480,\n emptyLabel = 'No text extracted',\n showMoreLabel = 'Show more',\n showLessLabel = 'Show less',\n sourceLabel = 'Source',\n previewLabel = 'Preview',\n}: Props) {\n const [expanded, setExpanded] = React.useState(false)\n const [tab, setTab] = React.useState<'source' | 'preview'>('source')\n const text = (content ?? '').trim()\n\n // ARIA IDs for accessibility\n const sourceTabId = 'attachment-content-preview-tab-source'\n const previewTabId = 'attachment-content-preview-tab-preview'\n const sourcePanelId = 'attachment-content-preview-panel-source'\n const previewPanelId = 'attachment-content-preview-panel-preview'\n\n if (!text) {\n return <div className=\"text-xs text-muted-foreground italic\">{emptyLabel}</div>\n }\n\n const shouldTruncate = !expanded && text.length > maxLength\n const display = tab === 'source' && shouldTruncate ? `${text.slice(0, maxLength)}\u2026` : text\n\n return (\n <div className=\"space-y-2\">\n {/* Tab Navigation */}\n <div className=\"border-b border-border\">\n <nav className=\"flex items-center gap-4 text-xs\" role=\"tablist\" aria-label=\"Content preview mode\">\n <button\n type=\"button\"\n id={sourceTabId}\n role=\"tab\"\n aria-selected={tab === 'source'}\n aria-controls={sourcePanelId}\n className={`-mb-px border-b-2 px-0 pb-2 font-medium transition-colors ${\n tab === 'source'\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setTab('source')}\n >\n {sourceLabel}\n </button>\n <button\n type=\"button\"\n id={previewTabId}\n role=\"tab\"\n aria-selected={tab === 'preview'}\n aria-controls={previewPanelId}\n className={`-mb-px border-b-2 px-0 pb-2 font-medium transition-colors ${\n tab === 'preview'\n ? 'border-accent-indigo text-foreground'\n : 'border-transparent text-muted-foreground hover:text-foreground'\n }`}\n onClick={() => setTab('preview')}\n >\n {previewLabel}\n </button>\n </nav>\n </div>\n\n {/* Tab Panels */}\n {tab === 'source' ? (\n <div\n role=\"tabpanel\"\n id={sourcePanelId}\n aria-labelledby={sourceTabId}\n data-testid=\"attachment-content-preview\"\n className=\"whitespace-pre-wrap text-sm text-muted-foreground\"\n >\n {display}\n </div>\n ) : (\n <div\n role=\"tabpanel\"\n id={previewPanelId}\n aria-labelledby={previewTabId}\n data-testid=\"markdown-preview\"\n >\n <MarkdownContent\n body={text}\n format=\"markdown\"\n className=\"text-sm text-muted-foreground [&>*]:mb-2 [&>*:last-child]:mb-0 [&_ul]:ml-4 [&_ul]:list-disc [&_ol]:ml-4 [&_ol]:list-decimal [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_pre]:rounded-md [&_pre]:bg-muted [&_pre]:p-3 [&_pre]:text-xs\"\n />\n </div>\n )}\n\n {/* Show More/Less Button (only on source tab) */}\n {tab === 'source' && text.length > maxLength ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto px-0 py-1 text-xs\"\n onClick={() => setExpanded((prev) => !prev)}\n >\n {expanded ? showLessLabel : showMoreLabel}\n </Button>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": "AAkCW,cAUH,YAVG;AAlCX,YAAY,WAAW;AACvB,SAAS,uBAAuB;AAChC,SAAS,cAAc;AAYhB,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,eAAe;AACjB,GAAU;AACR,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,KAAK,MAAM,IAAI,MAAM,SAA+B,QAAQ;AACnE,QAAM,QAAQ,WAAW,IAAI,KAAK;AAGlC,QAAM,cAAc;AACpB,QAAM,eAAe;AACrB,QAAM,gBAAgB;AACtB,QAAM,iBAAiB;AAEvB,MAAI,CAAC,MAAM;AACT,WAAO,oBAAC,SAAI,WAAU,wCAAwC,sBAAW;AAAA,EAC3E;AAEA,QAAM,iBAAiB,CAAC,YAAY,KAAK,SAAS;AAClD,QAAM,UAAU,QAAQ,YAAY,iBAAiB,GAAG,KAAK,MAAM,GAAG,SAAS,CAAC,WAAM;AAEtF,SACE,qBAAC,SAAI,WAAU,aAEb;AAAA,wBAAC,SAAI,WAAU,0BACb,+BAAC,SAAI,WAAU,mCAAkC,MAAK,WAAU,cAAW,wBACzE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,IAAI;AAAA,UACJ,MAAK;AAAA,UACL,iBAAe,QAAQ;AAAA,UACvB,iBAAe;AAAA,UACf,WAAW,6DACT,QAAQ,WACJ,yCACA,gEACN;AAAA,UACA,SAAS,MAAM,OAAO,QAAQ;AAAA,UAE7B;AAAA;AAAA,MACH;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,IAAI;AAAA,UACJ,MAAK;AAAA,UACL,iBAAe,QAAQ;AAAA,UACvB,iBAAe;AAAA,UACf,WAAW,6DACT,QAAQ,YACJ,yCACA,gEACN;AAAA,UACA,SAAS,MAAM,OAAO,SAAS;AAAA,UAE9B;AAAA;AAAA,MACH;AAAA,OACF,GACF;AAAA,IAGC,QAAQ,WACP;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,IAAI;AAAA,QACJ,mBAAiB;AAAA,QACjB,eAAY;AAAA,QACZ,WAAU;AAAA,QAET;AAAA;AAAA,IACH,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,IAAI;AAAA,QACJ,mBAAiB;AAAA,QACjB,eAAY;AAAA,QAEZ;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,QAAO;AAAA,YACP,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA,IACF;AAAA,IAID,QAAQ,YAAY,KAAK,SAAS,YACjC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,WAAU;AAAA,QACV,SAAS,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI;AAAA,QAEzC,qBAAW,gBAAgB;AAAA;AAAA,IAC9B,IACE;AAAA,KACN;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,6 @@ import React from "react";
4
4
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
5
5
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
6
6
  import { Button } from "@open-mercato/ui/primitives/button";
7
- import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
8
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
9
8
  import { AuditLogsActions } from "../../components/AuditLogsActions.js";
10
9
  import { AccessLogsTable } from "../../components/AccessLogsTable.js";
@@ -140,19 +139,30 @@ function AuditLogsPage() {
140
139
  ] })
141
140
  ] });
142
141
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { children: [
143
- /* @__PURE__ */ jsx(
144
- Tabs,
145
- {
146
- value: tab,
147
- onValueChange: (value) => setTab(value),
148
- variant: "underline",
149
- className: "mb-6",
150
- children: /* @__PURE__ */ jsxs(TabsList, { className: "w-full flex-wrap", "aria-label": t("audit_logs.tabs.label"), children: [
151
- /* @__PURE__ */ jsx(TabsTrigger, { value: "actions", children: t("audit_logs.actions.title") }),
152
- /* @__PURE__ */ jsx(TabsTrigger, { value: "access", children: t("audit_logs.access.title") })
153
- ] })
154
- }
155
- ),
142
+ /* @__PURE__ */ jsx("div", { className: "mb-6 border-b border-border", children: /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-6 text-sm", role: "tablist", "aria-label": t("audit_logs.tabs.label"), children: [
143
+ /* @__PURE__ */ jsx(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ role: "tab",
148
+ "aria-selected": tab === "actions",
149
+ className: `relative -mb-px border-b-2 px-0 pb-3 pt-2 font-medium transition-colors ${tab === "actions" ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
150
+ onClick: () => setTab("actions"),
151
+ children: t("audit_logs.actions.title")
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsx(
155
+ "button",
156
+ {
157
+ type: "button",
158
+ role: "tab",
159
+ "aria-selected": tab === "access",
160
+ className: `relative -mb-px border-b-2 px-0 pb-3 pt-2 font-medium transition-colors ${tab === "access" ? "border-accent-indigo text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
161
+ onClick: () => setTab("access"),
162
+ children: t("audit_logs.access.title")
163
+ }
164
+ )
165
+ ] }) }),
156
166
  error && /* @__PURE__ */ jsx("div", { className: "mb-4 rounded-md border border-status-error-border bg-status-error-bg p-3 text-sm text-status-error-text", children: error }),
157
167
  tab === "actions" && /* @__PURE__ */ jsx(
158
168
  AuditLogsActions,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/audit_logs/backend/audit-logs/page.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ActionLogItem } from '../../components/AuditLogsActions'\nimport { AuditLogsActions } from '../../components/AuditLogsActions'\nimport type { AccessLogItem } from '../../components/AccessLogsTable'\nimport { AccessLogsTable } from '../../components/AccessLogsTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('audit_logs').child({ component: 'audit-logs-page' })\n\ntype ActionLogResponse = {\n items: ActionLogItem[]\n canViewTenant: boolean\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype AccessLogResponse = {\n items: AccessLogItem[]\n canViewTenant: boolean\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype TabOption = 'actions' | 'access'\n\nconst DEFAULT_PAGE_SIZE = 50\n\nexport default function AuditLogsPage() {\n const t = useT()\n const [tab, setTab] = React.useState<TabOption>('actions')\n const [actions, setActions] = React.useState<ActionLogItem[]>([])\n const [accessLogs, setAccessLogs] = React.useState<AccessLogItem[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [undoableOnly, setUndoableOnly] = React.useState(false)\n const [actionsPage, setActionsPage] = React.useState(1)\n const [actionsPageSize, setActionsPageSize] = React.useState(DEFAULT_PAGE_SIZE)\n const [actionsTotal, setActionsTotal] = React.useState(0)\n const [actionsTotalPages, setActionsTotalPages] = React.useState(1)\n const actionsPageSizeRef = React.useRef(DEFAULT_PAGE_SIZE)\n const [accessPage, setAccessPage] = React.useState(1)\n const [accessPageSize, setAccessPageSize] = React.useState(DEFAULT_PAGE_SIZE)\n const [accessTotal, setAccessTotal] = React.useState(0)\n const [accessTotalPages, setAccessTotalPages] = React.useState(1)\n const accessPageSizeRef = React.useRef(DEFAULT_PAGE_SIZE)\n\n const fetchActions = React.useCallback(async (page: number, pageSize: number) => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (undoableOnly) params.set('undoableOnly', 'true')\n return readApiResultOrThrow<ActionLogResponse>(\n `/api/audit_logs/audit-logs/actions?${params.toString()}`,\n undefined,\n { errorMessage: t('audit_logs.error.load') },\n )\n }, [undoableOnly, t])\n\n const fetchAccess = React.useCallback(async (page: number, pageSize: number) => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n return readApiResultOrThrow<AccessLogResponse>(\n `/api/audit_logs/audit-logs/access?${params.toString()}`,\n undefined,\n { errorMessage: t('audit_logs.error.load') },\n )\n }, [t])\n\n const loadAll = React.useCallback(async (\n actionPage: number, actionPageSize: number,\n accessPageNum: number, accessPageSizeNum: number,\n ) => {\n const [actionsRes, accessRes] = await Promise.all([\n fetchActions(actionPage, actionPageSize),\n fetchAccess(accessPageNum, accessPageSizeNum),\n ])\n setActions(actionsRes.items ?? [])\n setActionsPage(actionsRes.page ?? actionPage)\n setActionsPageSize((prev) => {\n const resolved = actionsRes.pageSize ?? actionPageSize\n actionsPageSizeRef.current = resolved\n return resolved === prev ? prev : resolved\n })\n setActionsTotal(actionsRes.total ?? (actionsRes.items?.length ?? 0))\n setActionsTotalPages(actionsRes.totalPages ?? 1)\n setAccessLogs(accessRes.items ?? [])\n const resolvedPage = accessRes.page ?? accessPageNum\n const resolvedPageSize = accessRes.pageSize ?? accessPageSizeNum\n const resolvedTotal = accessRes.total ?? (accessRes.items?.length ?? 0)\n const resolvedTotalPages = accessRes.totalPages ?? Math.max(1, Math.ceil((resolvedTotal || 0) / (resolvedPageSize || 1)))\n setAccessPage(resolvedPage)\n setAccessPageSize((prev) => {\n if (resolvedPageSize === prev) {\n accessPageSizeRef.current = prev\n return prev\n }\n accessPageSizeRef.current = resolvedPageSize\n return resolvedPageSize\n })\n setAccessTotal(resolvedTotal)\n setAccessTotalPages(resolvedTotalPages)\n }, [fetchActions, fetchAccess])\n\n const loadWithState = React.useCallback(async (\n actionPage: number, actionPageSize: number,\n accessPageNum: number, accessPageSizeNum: number,\n ) => {\n setLoading(true)\n setError(null)\n try {\n await loadAll(actionPage, actionPageSize, accessPageNum, accessPageSizeNum)\n } catch (err) {\n logger.error('Failed to load audit logs', { err })\n setError(t('audit_logs.error.load'))\n } finally {\n setLoading(false)\n }\n }, [loadAll, t])\n\n React.useEffect(() => {\n setActionsPage(1)\n setAccessPage(1)\n void loadWithState(1, actionsPageSizeRef.current, 1, accessPageSizeRef.current)\n }, [loadWithState, undoableOnly])\n\n const renderRefreshButton = React.useCallback(() => (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void loadWithState(actionsPage, actionsPageSize, accessPage, accessPageSize)}\n disabled={loading}\n >\n {loading ? t('audit_logs.common.refreshing') : t('audit_logs.common.refresh')}\n </Button>\n ), [loadWithState, actionsPage, actionsPageSize, accessPage, accessPageSize, loading, t])\n\n const handleUndoError = React.useCallback(() => {\n setError(t('audit_logs.error.undo'))\n }, [t])\n const handleRedoError = React.useCallback(() => {\n setError(t('audit_logs.error.redo'))\n }, [t])\n\n const handleActionsPageChange = React.useCallback((nextPage: number) => {\n const totalPages = actionsTotalPages || 1\n const normalized = Math.max(1, Math.min(nextPage, totalPages))\n void loadWithState(normalized, actionsPageSizeRef.current, accessPage, accessPageSizeRef.current)\n }, [actionsTotalPages, loadWithState, accessPage])\n\n const handleAccessPageChange = React.useCallback((nextPage: number) => {\n const totalPages = accessTotalPages || 1\n const normalized = Math.max(1, Math.min(nextPage, totalPages))\n void loadWithState(actionsPage, actionsPageSizeRef.current, normalized, accessPageSizeRef.current)\n }, [accessTotalPages, loadWithState, actionsPage])\n\n const headerExtras = (\n <>\n {renderRefreshButton()}\n <label className=\"flex items-center gap-2 rounded border border-transparent px-2 py-1 text-sm text-muted-foreground\">\n <span>{t('audit_logs.filters.undoable_only')}</span>\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border border-input\"\n checked={undoableOnly}\n onChange={(e) => setUndoableOnly(e.target.checked)}\n />\n </label>\n </>\n )\n\n return (\n <Page>\n <PageBody>\n <Tabs\n value={tab}\n onValueChange={(value) => setTab(value as TabOption)}\n variant=\"underline\"\n className=\"mb-6\"\n >\n <TabsList className=\"w-full flex-wrap\" aria-label={t('audit_logs.tabs.label')}>\n <TabsTrigger value=\"actions\">{t('audit_logs.actions.title')}</TabsTrigger>\n <TabsTrigger value=\"access\">{t('audit_logs.access.title')}</TabsTrigger>\n </TabsList>\n </Tabs>\n\n {error && <div className=\"mb-4 rounded-md border border-status-error-border bg-status-error-bg p-3 text-sm text-status-error-text\">{error}</div>}\n\n {tab === 'actions' && (\n <AuditLogsActions\n items={actions}\n onRefresh={() => loadWithState(actionsPage, actionsPageSize, accessPage, accessPageSize)}\n isLoading={loading}\n headerExtras={headerExtras}\n onUndoError={handleUndoError}\n onRedoError={handleRedoError}\n pagination={{\n page: actionsPage,\n pageSize: actionsPageSize,\n total: actionsTotal,\n totalPages: actionsTotalPages,\n onPageChange: handleActionsPageChange,\n }}\n />\n )}\n\n {tab === 'access' && (\n <AccessLogsTable\n items={accessLogs}\n isLoading={loading}\n actions={renderRefreshButton()}\n pagination={{\n page: accessPage,\n pageSize: accessPageSize,\n total: accessTotal,\n totalPages: accessTotalPages,\n onPageChange: handleAccessPageChange,\n }}\n />\n )}\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ActionLogItem } from '../../components/AuditLogsActions'\nimport { AuditLogsActions } from '../../components/AuditLogsActions'\nimport type { AccessLogItem } from '../../components/AccessLogsTable'\nimport { AccessLogsTable } from '../../components/AccessLogsTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('audit_logs').child({ component: 'audit-logs-page' })\n\ntype ActionLogResponse = {\n items: ActionLogItem[]\n canViewTenant: boolean\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype AccessLogResponse = {\n items: AccessLogItem[]\n canViewTenant: boolean\n page: number\n pageSize: number\n total: number\n totalPages: number\n}\n\ntype TabOption = 'actions' | 'access'\n\nconst DEFAULT_PAGE_SIZE = 50\n\nexport default function AuditLogsPage() {\n const t = useT()\n const [tab, setTab] = React.useState<TabOption>('actions')\n const [actions, setActions] = React.useState<ActionLogItem[]>([])\n const [accessLogs, setAccessLogs] = React.useState<AccessLogItem[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [undoableOnly, setUndoableOnly] = React.useState(false)\n const [actionsPage, setActionsPage] = React.useState(1)\n const [actionsPageSize, setActionsPageSize] = React.useState(DEFAULT_PAGE_SIZE)\n const [actionsTotal, setActionsTotal] = React.useState(0)\n const [actionsTotalPages, setActionsTotalPages] = React.useState(1)\n const actionsPageSizeRef = React.useRef(DEFAULT_PAGE_SIZE)\n const [accessPage, setAccessPage] = React.useState(1)\n const [accessPageSize, setAccessPageSize] = React.useState(DEFAULT_PAGE_SIZE)\n const [accessTotal, setAccessTotal] = React.useState(0)\n const [accessTotalPages, setAccessTotalPages] = React.useState(1)\n const accessPageSizeRef = React.useRef(DEFAULT_PAGE_SIZE)\n\n const fetchActions = React.useCallback(async (page: number, pageSize: number) => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n if (undoableOnly) params.set('undoableOnly', 'true')\n return readApiResultOrThrow<ActionLogResponse>(\n `/api/audit_logs/audit-logs/actions?${params.toString()}`,\n undefined,\n { errorMessage: t('audit_logs.error.load') },\n )\n }, [undoableOnly, t])\n\n const fetchAccess = React.useCallback(async (page: number, pageSize: number) => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(pageSize))\n return readApiResultOrThrow<AccessLogResponse>(\n `/api/audit_logs/audit-logs/access?${params.toString()}`,\n undefined,\n { errorMessage: t('audit_logs.error.load') },\n )\n }, [t])\n\n const loadAll = React.useCallback(async (\n actionPage: number, actionPageSize: number,\n accessPageNum: number, accessPageSizeNum: number,\n ) => {\n const [actionsRes, accessRes] = await Promise.all([\n fetchActions(actionPage, actionPageSize),\n fetchAccess(accessPageNum, accessPageSizeNum),\n ])\n setActions(actionsRes.items ?? [])\n setActionsPage(actionsRes.page ?? actionPage)\n setActionsPageSize((prev) => {\n const resolved = actionsRes.pageSize ?? actionPageSize\n actionsPageSizeRef.current = resolved\n return resolved === prev ? prev : resolved\n })\n setActionsTotal(actionsRes.total ?? (actionsRes.items?.length ?? 0))\n setActionsTotalPages(actionsRes.totalPages ?? 1)\n setAccessLogs(accessRes.items ?? [])\n const resolvedPage = accessRes.page ?? accessPageNum\n const resolvedPageSize = accessRes.pageSize ?? accessPageSizeNum\n const resolvedTotal = accessRes.total ?? (accessRes.items?.length ?? 0)\n const resolvedTotalPages = accessRes.totalPages ?? Math.max(1, Math.ceil((resolvedTotal || 0) / (resolvedPageSize || 1)))\n setAccessPage(resolvedPage)\n setAccessPageSize((prev) => {\n if (resolvedPageSize === prev) {\n accessPageSizeRef.current = prev\n return prev\n }\n accessPageSizeRef.current = resolvedPageSize\n return resolvedPageSize\n })\n setAccessTotal(resolvedTotal)\n setAccessTotalPages(resolvedTotalPages)\n }, [fetchActions, fetchAccess])\n\n const loadWithState = React.useCallback(async (\n actionPage: number, actionPageSize: number,\n accessPageNum: number, accessPageSizeNum: number,\n ) => {\n setLoading(true)\n setError(null)\n try {\n await loadAll(actionPage, actionPageSize, accessPageNum, accessPageSizeNum)\n } catch (err) {\n logger.error('Failed to load audit logs', { err })\n setError(t('audit_logs.error.load'))\n } finally {\n setLoading(false)\n }\n }, [loadAll, t])\n\n React.useEffect(() => {\n setActionsPage(1)\n setAccessPage(1)\n void loadWithState(1, actionsPageSizeRef.current, 1, accessPageSizeRef.current)\n }, [loadWithState, undoableOnly])\n\n const renderRefreshButton = React.useCallback(() => (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void loadWithState(actionsPage, actionsPageSize, accessPage, accessPageSize)}\n disabled={loading}\n >\n {loading ? t('audit_logs.common.refreshing') : t('audit_logs.common.refresh')}\n </Button>\n ), [loadWithState, actionsPage, actionsPageSize, accessPage, accessPageSize, loading, t])\n\n const handleUndoError = React.useCallback(() => {\n setError(t('audit_logs.error.undo'))\n }, [t])\n const handleRedoError = React.useCallback(() => {\n setError(t('audit_logs.error.redo'))\n }, [t])\n\n const handleActionsPageChange = React.useCallback((nextPage: number) => {\n const totalPages = actionsTotalPages || 1\n const normalized = Math.max(1, Math.min(nextPage, totalPages))\n void loadWithState(normalized, actionsPageSizeRef.current, accessPage, accessPageSizeRef.current)\n }, [actionsTotalPages, loadWithState, accessPage])\n\n const handleAccessPageChange = React.useCallback((nextPage: number) => {\n const totalPages = accessTotalPages || 1\n const normalized = Math.max(1, Math.min(nextPage, totalPages))\n void loadWithState(actionsPage, actionsPageSizeRef.current, normalized, accessPageSizeRef.current)\n }, [accessTotalPages, loadWithState, actionsPage])\n\n const headerExtras = (\n <>\n {renderRefreshButton()}\n <label className=\"flex items-center gap-2 rounded border border-transparent px-2 py-1 text-sm text-muted-foreground\">\n <span>{t('audit_logs.filters.undoable_only')}</span>\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border border-input\"\n checked={undoableOnly}\n onChange={(e) => setUndoableOnly(e.target.checked)}\n />\n </label>\n </>\n )\n\n return (\n <Page>\n <PageBody>\n <div className=\"mb-6 border-b border-border\">\n <nav className=\"flex items-center gap-6 text-sm\" role=\"tablist\" aria-label={t('audit_logs.tabs.label')}>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={tab === 'actions'}\n className={`relative -mb-px border-b-2 px-0 pb-3 pt-2 font-medium transition-colors ${tab === 'actions' ? 'border-accent-indigo text-foreground' : 'border-transparent text-muted-foreground hover:text-foreground'}`}\n onClick={() => setTab('actions')}\n >\n {t('audit_logs.actions.title')}\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n aria-selected={tab === 'access'}\n className={`relative -mb-px border-b-2 px-0 pb-3 pt-2 font-medium transition-colors ${tab === 'access' ? 'border-accent-indigo text-foreground' : 'border-transparent text-muted-foreground hover:text-foreground'}`}\n onClick={() => setTab('access')}\n >\n {t('audit_logs.access.title')}\n </button>\n </nav>\n </div>\n\n {error && <div className=\"mb-4 rounded-md border border-status-error-border bg-status-error-bg p-3 text-sm text-status-error-text\">{error}</div>}\n\n {tab === 'actions' && (\n <AuditLogsActions\n items={actions}\n onRefresh={() => loadWithState(actionsPage, actionsPageSize, accessPage, accessPageSize)}\n isLoading={loading}\n headerExtras={headerExtras}\n onUndoError={handleUndoError}\n onRedoError={handleRedoError}\n pagination={{\n page: actionsPage,\n pageSize: actionsPageSize,\n total: actionsTotal,\n totalPages: actionsTotalPages,\n onPageChange: handleActionsPageChange,\n }}\n />\n )}\n\n {tab === 'access' && (\n <AccessLogsTable\n items={accessLogs}\n isLoading={loading}\n actions={renderRefreshButton()}\n pagination={{\n page: accessPage,\n pageSize: accessPageSize,\n total: accessTotal,\n totalPages: accessTotalPages,\n onPageChange: handleAccessPageChange,\n }}\n />\n )}\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -18,7 +18,6 @@ import {
18
18
  import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
19
19
  import { buildPasswordSchema } from "@open-mercato/shared/lib/auth/passwordPolicy";
20
20
  import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
21
- import { parseBooleanFlag } from "@open-mercato/shared/lib/boolean";
22
21
  import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
23
22
  import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
24
23
  import { sql } from "kysely";
@@ -36,7 +35,6 @@ const querySchema = z.object({
36
35
  search: z.string().optional(),
37
36
  name: z.string().optional(),
38
37
  organizationId: z.string().uuid().optional(),
39
- scopeToActiveOrganization: z.boolean().optional(),
40
38
  roleIds: z.array(z.string().uuid()).optional()
41
39
  }).passthrough();
42
40
  const rawBodySchema = z.object({}).passthrough();
@@ -160,7 +158,6 @@ async function GET(req) {
160
158
  search: url.searchParams.get("search") || void 0,
161
159
  name: url.searchParams.get("name") || void 0,
162
160
  organizationId: url.searchParams.get("organizationId") || void 0,
163
- scopeToActiveOrganization: parseBooleanFlag(url.searchParams.get("scopeToActiveOrganization") || void 0),
164
161
  roleIds: rawRoleIds.length ? rawRoleIds : void 0
165
162
  });
166
163
  if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 });
@@ -176,7 +173,7 @@ async function GET(req) {
176
173
  } catch (err) {
177
174
  logger.error("Failed to resolve rbac", { err });
178
175
  }
179
- const { id, page, pageSize, search, name, organizationId, scopeToActiveOrganization, roleIds } = parsed.data;
176
+ const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data;
180
177
  const filters = [{ deletedAt: null }];
181
178
  const actorTenantId = auth.tenantId ? String(auth.tenantId) : null;
182
179
  let effectiveTenantId = null;
@@ -223,7 +220,6 @@ async function GET(req) {
223
220
  }
224
221
  const scopeOrganizationId = usesSelectedTenantScope ? effectiveSelectedOrganizationId : auth.orgId ?? null;
225
222
  if (organizationId) filters.push({ organizationId });
226
- if (scopeToActiveOrganization) filters.push({ organizationId: auth.orgId ?? null });
227
223
  const trimmedName = typeof name === "string" ? name.trim() : "";
228
224
  if (trimmedName) {
229
225
  const searchPattern = `%${escapeLikePattern(trimmedName)}%`;
@@ -546,7 +542,7 @@ const openApi = {
546
542
  methods: {
547
543
  GET: {
548
544
  summary: "List users",
549
- description: "Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters. Pass scopeToActiveOrganization=1 to restrict results to the caller's active organization (used by recipient/assignee pickers so suggestions stay within the org that owns the resulting record).",
545
+ description: "Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.",
550
546
  query: querySchema,
551
547
  responses: [
552
548
  { status: 200, description: "User collection", schema: userListResponseSchema }