@open-mercato/core 0.6.5-develop.5337.1.534b781eac → 0.6.5

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 (350) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -1
  3. package/dist/bootstrap.js +46 -6
  4. package/dist/bootstrap.js.map +2 -2
  5. package/dist/generated/entities/organization/index.js +2 -0
  6. package/dist/generated/entities/organization/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +1 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/crmFixtures.js +4 -0
  10. package/dist/helpers/integration/crmFixtures.js.map +2 -2
  11. package/dist/modules/attachments/api/library/route.js +2 -2
  12. package/dist/modules/attachments/api/library/route.js.map +2 -2
  13. package/dist/modules/attachments/api/route.js +2 -0
  14. package/dist/modules/attachments/api/route.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentContentPreview.js +9 -5
  16. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  17. package/dist/modules/attachments/lib/access.js +18 -0
  18. package/dist/modules/attachments/lib/access.js.map +2 -2
  19. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js +3 -2
  20. package/dist/modules/audit_logs/api/audit-logs/actions/redo/route.js.map +2 -2
  21. package/dist/modules/audit_logs/data/entities.js +2 -1
  22. package/dist/modules/audit_logs/data/entities.js.map +2 -2
  23. package/dist/modules/audit_logs/migrations/Migration20260611104500.js +13 -0
  24. package/dist/modules/audit_logs/migrations/Migration20260611104500.js.map +7 -0
  25. package/dist/modules/audit_logs/services/accessLogService.js +10 -0
  26. package/dist/modules/audit_logs/services/accessLogService.js.map +2 -2
  27. package/dist/modules/auth/api/admin/nav.js +9 -0
  28. package/dist/modules/auth/api/admin/nav.js.map +2 -2
  29. package/dist/modules/auth/api/login.js +4 -13
  30. package/dist/modules/auth/api/login.js.map +2 -2
  31. package/dist/modules/auth/commands/users.js +20 -14
  32. package/dist/modules/auth/commands/users.js.map +2 -2
  33. package/dist/modules/auth/data/entities.js +4 -2
  34. package/dist/modules/auth/data/entities.js.map +2 -2
  35. package/dist/modules/auth/lib/backendChrome.js +35 -2
  36. package/dist/modules/auth/lib/backendChrome.js.map +2 -2
  37. package/dist/modules/auth/lib/consentIntegrity.js +3 -3
  38. package/dist/modules/auth/lib/consentIntegrity.js.map +2 -2
  39. package/dist/modules/auth/migrations/Migration20260610120000.js +30 -0
  40. package/dist/modules/auth/migrations/Migration20260610120000.js.map +7 -0
  41. package/dist/modules/auth/migrations/Migration20260611103000.js +15 -0
  42. package/dist/modules/auth/migrations/Migration20260611103000.js.map +7 -0
  43. package/dist/modules/auth/services/authService.js +5 -3
  44. package/dist/modules/auth/services/authService.js.map +2 -2
  45. package/dist/modules/auth/services/rbacService.js +3 -2
  46. package/dist/modules/auth/services/rbacService.js.map +2 -2
  47. package/dist/modules/catalog/ai-tools/configuration-pack.js.map +1 -1
  48. package/dist/modules/catalog/ai-tools/prices-offers-pack.js.map +1 -1
  49. package/dist/modules/catalog/ai-tools/products-pack.js.map +1 -1
  50. package/dist/modules/catalog/ai-tools/variants-pack.js.map +1 -1
  51. package/dist/modules/communication_channels/data/entities.js.map +1 -1
  52. package/dist/modules/communication_channels/encryption.js.map +1 -1
  53. package/dist/modules/communication_channels/lib/thread-matcher.js.map +1 -1
  54. package/dist/modules/communication_channels/lib/thread-token.js.map +1 -1
  55. package/dist/modules/currencies/api/currencies/route.js +4 -3
  56. package/dist/modules/currencies/api/currencies/route.js.map +2 -2
  57. package/dist/modules/customer_accounts/api/admin/roles.js +2 -1
  58. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  59. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js +0 -3
  60. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.js.map +2 -2
  61. package/dist/modules/customer_accounts/events.js +1 -1
  62. package/dist/modules/customer_accounts/events.js.map +1 -1
  63. package/dist/modules/customer_accounts/lib/resolveTenantContext.js.map +1 -1
  64. package/dist/modules/customers/acl.js +1 -1
  65. package/dist/modules/customers/acl.js.map +1 -1
  66. package/dist/modules/customers/ai-tools/companies-pack.js.map +1 -1
  67. package/dist/modules/customers/ai-tools/deals-pack.js.map +1 -1
  68. package/dist/modules/customers/ai-tools/people-pack.js.map +1 -1
  69. package/dist/modules/customers/api/companies/route.js +4 -4
  70. package/dist/modules/customers/api/companies/route.js.map +2 -2
  71. package/dist/modules/customers/api/deals/route.js +43 -2
  72. package/dist/modules/customers/api/deals/route.js.map +2 -2
  73. package/dist/modules/customers/api/deals/summary/route.js +402 -0
  74. package/dist/modules/customers/api/deals/summary/route.js.map +7 -0
  75. package/dist/modules/customers/api/people/route.js +4 -4
  76. package/dist/modules/customers/api/people/route.js.map +2 -2
  77. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js +16 -5
  78. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.js.map +2 -2
  79. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js +22 -5
  80. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealData.js.map +2 -2
  81. package/dist/modules/customers/backend/customers/deals/[id]/page.js +12 -2
  82. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  83. package/dist/modules/customers/backend/customers/deals/page.js +221 -56
  84. package/dist/modules/customers/backend/customers/deals/page.js.map +3 -3
  85. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
  86. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  87. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +18 -0
  88. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  89. package/dist/modules/customers/cli.js +15 -9
  90. package/dist/modules/customers/cli.js.map +2 -2
  91. package/dist/modules/customers/commands/addresses.js +5 -5
  92. package/dist/modules/customers/commands/addresses.js.map +2 -2
  93. package/dist/modules/customers/commands/comments.js +5 -5
  94. package/dist/modules/customers/commands/comments.js.map +2 -2
  95. package/dist/modules/customers/commands/deals.js +2 -2
  96. package/dist/modules/customers/commands/deals.js.map +2 -2
  97. package/dist/modules/customers/commands/entity-roles.js +2 -1
  98. package/dist/modules/customers/commands/entity-roles.js.map +2 -2
  99. package/dist/modules/customers/commands/interactions.js +8 -5
  100. package/dist/modules/customers/commands/interactions.js.map +2 -2
  101. package/dist/modules/customers/commands/shared.js +21 -6
  102. package/dist/modules/customers/commands/shared.js.map +2 -2
  103. package/dist/modules/customers/commands/tags.js +3 -3
  104. package/dist/modules/customers/commands/tags.js.map +2 -2
  105. package/dist/modules/customers/components/DealsKpiStrip.js +282 -0
  106. package/dist/modules/customers/components/DealsKpiStrip.js.map +7 -0
  107. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +0 -1
  108. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  109. package/dist/modules/customers/components/detail/DealForm.js +100 -17
  110. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  111. package/dist/modules/customers/components/detail/PersonDetailTabs.js +11 -3
  112. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  113. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +1 -2
  114. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  115. package/dist/modules/customers/components/detail/assignableStaff.js +21 -8
  116. package/dist/modules/customers/components/detail/assignableStaff.js.map +2 -2
  117. package/dist/modules/customers/components/kpi/PipelineStageBar.js +63 -0
  118. package/dist/modules/customers/components/kpi/PipelineStageBar.js.map +7 -0
  119. package/dist/modules/customers/lib/dealsMetrics.js +82 -0
  120. package/dist/modules/customers/lib/dealsMetrics.js.map +7 -0
  121. package/dist/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.js.map +1 -1
  122. package/dist/modules/data_sync/api/run.js +1 -1
  123. package/dist/modules/data_sync/api/run.js.map +2 -2
  124. package/dist/modules/directory/api/organization-branding/route.js +214 -0
  125. package/dist/modules/directory/api/organization-branding/route.js.map +7 -0
  126. package/dist/modules/directory/api/organizations/route.js +7 -0
  127. package/dist/modules/directory/api/organizations/route.js.map +3 -3
  128. package/dist/modules/directory/backend/directory/branding/page.js +214 -0
  129. package/dist/modules/directory/backend/directory/branding/page.js.map +7 -0
  130. package/dist/modules/directory/backend/directory/branding/page.meta.js +26 -0
  131. package/dist/modules/directory/backend/directory/branding/page.meta.js.map +7 -0
  132. package/dist/modules/directory/commands/organizations.js +8 -1
  133. package/dist/modules/directory/commands/organizations.js.map +2 -2
  134. package/dist/modules/directory/data/entities.js +3 -0
  135. package/dist/modules/directory/data/entities.js.map +2 -2
  136. package/dist/modules/directory/data/validators.js +9 -0
  137. package/dist/modules/directory/data/validators.js.map +2 -2
  138. package/dist/modules/directory/migrations/Migration20260607222259_directory.js +13 -0
  139. package/dist/modules/directory/migrations/Migration20260607222259_directory.js.map +7 -0
  140. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js +2 -1
  141. package/dist/modules/directory/subscribers/invalidateOrgScopeCache.js.map +2 -2
  142. package/dist/modules/directory/utils/organizationScope.js +59 -27
  143. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  144. package/dist/modules/entities/api/definitions.batch.js +2 -1
  145. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  146. package/dist/modules/entities/api/entities.js +7 -0
  147. package/dist/modules/entities/api/entities.js.map +2 -2
  148. package/dist/modules/entities/api/records.js +26 -15
  149. package/dist/modules/entities/api/records.js.map +2 -2
  150. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +14 -0
  151. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  152. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js +14 -0
  153. package/dist/modules/entities/backend/entities/user/[entityId]/records/create/page.js.map +2 -2
  154. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +12 -0
  155. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  156. package/dist/modules/entities/components/useRecordsEntityGuard.js +30 -0
  157. package/dist/modules/entities/components/useRecordsEntityGuard.js.map +7 -0
  158. package/dist/modules/payment_gateways/api/transactions/route.js +2 -4
  159. package/dist/modules/payment_gateways/api/transactions/route.js.map +2 -2
  160. package/dist/modules/progress/api/jobs/[id]/route.js +7 -2
  161. package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
  162. package/dist/modules/progress/api/jobs/route.js +1 -1
  163. package/dist/modules/progress/api/jobs/route.js.map +2 -2
  164. package/dist/modules/progress/lib/progressServiceImpl.js +8 -2
  165. package/dist/modules/progress/lib/progressServiceImpl.js.map +2 -2
  166. package/dist/modules/query_index/data/entities.js +2 -1
  167. package/dist/modules/query_index/data/entities.js.map +2 -2
  168. package/dist/modules/query_index/lib/engine.js +4 -2
  169. package/dist/modules/query_index/lib/engine.js.map +2 -2
  170. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js +16 -0
  171. package/dist/modules/query_index/migrations/Migration20260611103000_query_index.js.map +7 -0
  172. package/dist/modules/resources/api/resources.js +2 -3
  173. package/dist/modules/resources/api/resources.js.map +2 -2
  174. package/dist/modules/sales/api/documents/factory.js +2 -2
  175. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  176. package/dist/modules/sales/commands/documents.js +7 -5
  177. package/dist/modules/sales/commands/documents.js.map +2 -2
  178. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +2 -1
  179. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  180. package/dist/modules/sales/components/documents/salesDocumentsColumns.js +10 -0
  181. package/dist/modules/sales/components/documents/salesDocumentsColumns.js.map +7 -0
  182. package/dist/modules/staff/api/team-members.js +9 -2
  183. package/dist/modules/staff/api/team-members.js.map +2 -2
  184. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +24 -1
  185. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +2 -2
  186. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +11 -6
  187. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  188. package/dist/modules/staff/commands/team-members.js +1 -1
  189. package/dist/modules/staff/commands/team-members.js.map +2 -2
  190. package/dist/modules/staff/components/TeamMemberForm.js +1 -1
  191. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  192. package/dist/modules/staff/lib/scheduleSwitch.js +23 -0
  193. package/dist/modules/staff/lib/scheduleSwitch.js.map +7 -0
  194. package/dist/modules/sync_excel/api/import/route.js +1 -1
  195. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  196. package/dist/modules/workflows/api/definitions/route.js +3 -2
  197. package/dist/modules/workflows/api/definitions/route.js.map +2 -2
  198. package/dist/modules/workflows/backend/definitions/create/page.js +1 -2
  199. package/dist/modules/workflows/backend/definitions/create/page.js.map +2 -2
  200. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +1 -2
  201. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  202. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -2
  203. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  204. package/dist/modules/workflows/components/NodeEditDialog.js +4 -13
  205. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  206. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js +4 -13
  207. package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +2 -2
  208. package/dist/modules/workflows/components/WorkflowGraphImpl.js +1 -4
  209. package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
  210. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +2 -5
  211. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +2 -2
  212. package/generated/entities/organization/index.ts +1 -0
  213. package/generated/entity-fields-registry.ts +1 -0
  214. package/package.json +11 -12
  215. package/src/bootstrap.ts +65 -7
  216. package/src/helpers/integration/crmFixtures.ts +21 -1
  217. package/src/modules/attachments/AGENTS.md +79 -0
  218. package/src/modules/attachments/api/library/route.ts +2 -2
  219. package/src/modules/attachments/api/route.ts +2 -0
  220. package/src/modules/attachments/components/AttachmentContentPreview.tsx +6 -6
  221. package/src/modules/attachments/lib/access.ts +36 -0
  222. package/src/modules/audit_logs/api/audit-logs/actions/redo/route.ts +14 -2
  223. package/src/modules/audit_logs/data/entities.ts +1 -0
  224. package/src/modules/audit_logs/migrations/.snapshot-open-mercato.json +10 -0
  225. package/src/modules/audit_logs/migrations/Migration20260611104500.ts +13 -0
  226. package/src/modules/audit_logs/services/accessLogService.ts +15 -0
  227. package/src/modules/auth/api/admin/nav.ts +9 -0
  228. package/src/modules/auth/api/login.ts +13 -13
  229. package/src/modules/auth/commands/users.ts +32 -15
  230. package/src/modules/auth/data/entities.ts +13 -1
  231. package/src/modules/auth/i18n/de.json +0 -1
  232. package/src/modules/auth/i18n/en.json +0 -1
  233. package/src/modules/auth/i18n/es.json +0 -1
  234. package/src/modules/auth/i18n/pl.json +0 -1
  235. package/src/modules/auth/lib/backendChrome.tsx +37 -1
  236. package/src/modules/auth/lib/consentIntegrity.ts +6 -3
  237. package/src/modules/auth/migrations/.snapshot-open-mercato.json +20 -10
  238. package/src/modules/auth/migrations/Migration20260610120000.ts +53 -0
  239. package/src/modules/auth/migrations/Migration20260611103000.ts +21 -0
  240. package/src/modules/auth/services/authService.ts +24 -4
  241. package/src/modules/auth/services/rbacService.ts +11 -2
  242. package/src/modules/catalog/ai-tools/configuration-pack.ts +1 -1
  243. package/src/modules/catalog/ai-tools/prices-offers-pack.ts +1 -1
  244. package/src/modules/catalog/ai-tools/products-pack.ts +1 -1
  245. package/src/modules/catalog/ai-tools/variants-pack.ts +1 -1
  246. package/src/modules/communication_channels/data/entities.ts +2 -2
  247. package/src/modules/communication_channels/encryption.ts +1 -1
  248. package/src/modules/communication_channels/lib/adapter.ts +1 -1
  249. package/src/modules/communication_channels/lib/thread-matcher.ts +1 -1
  250. package/src/modules/communication_channels/lib/thread-token.ts +1 -1
  251. package/src/modules/currencies/api/currencies/route.ts +4 -3
  252. package/src/modules/customer_accounts/api/admin/roles.ts +2 -1
  253. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/components/Diagnostics.tsx +0 -3
  254. package/src/modules/customer_accounts/events.ts +1 -1
  255. package/src/modules/customer_accounts/lib/resolveTenantContext.ts +2 -2
  256. package/src/modules/customers/acl.ts +1 -1
  257. package/src/modules/customers/ai-tools/companies-pack.ts +1 -1
  258. package/src/modules/customers/ai-tools/deals-pack.ts +1 -1
  259. package/src/modules/customers/ai-tools/people-pack.ts +1 -1
  260. package/src/modules/customers/api/companies/route.ts +4 -4
  261. package/src/modules/customers/api/deals/route.ts +51 -2
  262. package/src/modules/customers/api/deals/summary/route.ts +496 -0
  263. package/src/modules/customers/api/people/route.ts +4 -4
  264. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealActivities.ts +28 -6
  265. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealData.ts +33 -6
  266. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +17 -2
  267. package/src/modules/customers/backend/customers/deals/page.tsx +254 -66
  268. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +1 -2
  269. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +18 -0
  270. package/src/modules/customers/cli.ts +15 -15
  271. package/src/modules/customers/commands/addresses.ts +5 -5
  272. package/src/modules/customers/commands/comments.ts +5 -5
  273. package/src/modules/customers/commands/deals.ts +2 -2
  274. package/src/modules/customers/commands/entity-roles.ts +2 -1
  275. package/src/modules/customers/commands/interactions.ts +8 -5
  276. package/src/modules/customers/commands/shared.ts +26 -4
  277. package/src/modules/customers/commands/tags.ts +3 -3
  278. package/src/modules/customers/components/DealsKpiStrip.tsx +389 -0
  279. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +0 -1
  280. package/src/modules/customers/components/detail/DealForm.tsx +121 -19
  281. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +12 -2
  282. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +1 -2
  283. package/src/modules/customers/components/detail/assignableStaff.ts +32 -8
  284. package/src/modules/customers/components/kpi/PipelineStageBar.tsx +77 -0
  285. package/src/modules/customers/i18n/de.json +43 -0
  286. package/src/modules/customers/i18n/en.json +43 -0
  287. package/src/modules/customers/i18n/es.json +43 -0
  288. package/src/modules/customers/i18n/pl.json +43 -0
  289. package/src/modules/customers/lib/dealsMetrics.ts +159 -0
  290. package/src/modules/customers/migrations/Migration20260519120000_pipeline_stage_color_tones.ts +1 -1
  291. package/src/modules/data_sync/api/run.ts +1 -1
  292. package/src/modules/directory/api/organization-branding/route.ts +238 -0
  293. package/src/modules/directory/api/organizations/route.ts +7 -0
  294. package/src/modules/directory/backend/directory/branding/page.meta.ts +24 -0
  295. package/src/modules/directory/backend/directory/branding/page.tsx +248 -0
  296. package/src/modules/directory/commands/organizations.ts +9 -1
  297. package/src/modules/directory/data/entities.ts +3 -0
  298. package/src/modules/directory/data/validators.ts +12 -0
  299. package/src/modules/directory/i18n/de.json +21 -0
  300. package/src/modules/directory/i18n/en.json +21 -0
  301. package/src/modules/directory/i18n/es.json +21 -0
  302. package/src/modules/directory/i18n/pl.json +21 -0
  303. package/src/modules/directory/migrations/.snapshot-open-mercato.json +40 -0
  304. package/src/modules/directory/migrations/Migration20260607222259_directory.ts +13 -0
  305. package/src/modules/directory/subscribers/invalidateOrgScopeCache.ts +3 -1
  306. package/src/modules/directory/utils/organizationScope.ts +85 -30
  307. package/src/modules/entities/api/definitions.batch.ts +11 -7
  308. package/src/modules/entities/api/entities.ts +11 -0
  309. package/src/modules/entities/api/records.ts +46 -25
  310. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +15 -0
  311. package/src/modules/entities/backend/entities/user/[entityId]/records/create/page.tsx +15 -0
  312. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +23 -0
  313. package/src/modules/entities/components/useRecordsEntityGuard.ts +41 -0
  314. package/src/modules/entities/i18n/de.json +1 -0
  315. package/src/modules/entities/i18n/en.json +1 -0
  316. package/src/modules/entities/i18n/es.json +1 -0
  317. package/src/modules/entities/i18n/pl.json +1 -0
  318. package/src/modules/payment_gateways/api/transactions/route.ts +2 -5
  319. package/src/modules/progress/api/jobs/[id]/route.ts +6 -1
  320. package/src/modules/progress/api/jobs/route.ts +1 -1
  321. package/src/modules/progress/lib/progressServiceImpl.ts +7 -1
  322. package/src/modules/query_index/data/entities.ts +1 -0
  323. package/src/modules/query_index/lib/engine.ts +11 -5
  324. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +11 -0
  325. package/src/modules/query_index/migrations/Migration20260611103000_query_index.ts +29 -0
  326. package/src/modules/resources/api/resources.ts +2 -3
  327. package/src/modules/sales/api/documents/factory.ts +2 -2
  328. package/src/modules/sales/commands/documents.ts +7 -5
  329. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +2 -1
  330. package/src/modules/sales/components/documents/salesDocumentsColumns.ts +6 -0
  331. package/src/modules/staff/AGENTS.md +1 -1
  332. package/src/modules/staff/api/team-members.ts +9 -2
  333. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +31 -1
  334. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +18 -8
  335. package/src/modules/staff/commands/team-members.ts +5 -2
  336. package/src/modules/staff/components/TeamMemberForm.tsx +4 -1
  337. package/src/modules/staff/i18n/de.json +1 -0
  338. package/src/modules/staff/i18n/en.json +1 -0
  339. package/src/modules/staff/i18n/es.json +1 -0
  340. package/src/modules/staff/i18n/pl.json +1 -0
  341. package/src/modules/staff/lib/scheduleSwitch.ts +46 -0
  342. package/src/modules/sync_excel/api/import/route.ts +1 -1
  343. package/src/modules/workflows/api/definitions/route.ts +3 -2
  344. package/src/modules/workflows/backend/definitions/create/page.tsx +1 -2
  345. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +1 -2
  346. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -2
  347. package/src/modules/workflows/components/NodeEditDialog.tsx +1 -4
  348. package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +4 -7
  349. package/src/modules/workflows/components/WorkflowGraphImpl.tsx +1 -2
  350. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +2 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/audit_logs/api/audit-logs/actions/redo/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { CommandRuntimeContext, CommandLogMetadata } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { AwilixContainer } from 'awilix'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['audit_logs.redo_self'] },\n}\n\ntype RedoRequestBody = {\n logId?: string\n}\n\nconst redoRequestSchema = z.object({\n logId: z.string().min(1).describe('Identifier of the previously undone action log'),\n})\n\nconst redoResponseSchema = z.object({\n ok: z.literal(true),\n logId: z.string().nullable().describe('Identifier of the new redo log entry, if available'),\n undoToken: z.string().nullable().describe('New undo token associated with the redone action'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = (await req.json().catch(() => null)) as RedoRequestBody | null\n const logId = typeof body?.logId === 'string' ? body.logId.trim() : ''\n if (!logId) return NextResponse.json({ error: 'Invalid log id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const commandBus = (container.resolve('commandBus') as CommandBus)\n const logs = (container.resolve('actionLogService') as ActionLogService)\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n const { organizationId } = await resolveFeatureCheckContext({ container, auth, request: req })\n\n const canRedoTenant = rbac\n ? await rbac.userHasAllFeatures(auth.sub, ['audit_logs.redo_tenant'], {\n tenantId: auth.tenantId ?? null,\n organizationId,\n })\n : false\n\n const scopedOrgId = canRedoTenant ? organizationId ?? null : organizationId ?? auth.orgId ?? null\n const log = await logs.findById(logId)\n\n if (!log || log.executionState !== 'undone') {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.actorUserId && log.actorUserId !== auth.sub && !canRedoTenant) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.tenantId && auth.tenantId && log.tenantId !== auth.tenantId) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.organizationId && scopedOrgId && log.organizationId !== scopedOrgId) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n const lookupActorId = canRedoTenant ? (log.actorUserId ?? auth.sub) : auth.sub\n const latestUndoneOrganizationId = log.organizationId ?? null\n const latestUndone = await logs.latestUndoneForActor(lookupActorId, {\n tenantId: auth.tenantId ?? null,\n organizationId: latestUndoneOrganizationId,\n })\n if (!latestUndone || latestUndone.id !== log.id) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n try {\n const ctx = await createRuntimeContext(container, auth, req)\n const contextRecord = log.contextJson && typeof log.contextJson === 'object' ? (log.contextJson as Record<string, unknown>) : null\n const cacheAliasesRaw = Array.isArray(contextRecord?.cacheAliases as unknown[])\n ? (contextRecord!.cacheAliases as unknown[])\n : []\n const cacheAliases = cacheAliasesRaw\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .map((value) => value.trim())\n const metadataContext: Record<string, unknown> = {\n historyAction: 'redo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n if (cacheAliases.length) metadataContext.cacheAliases = cacheAliases\n const metadata: CommandLogMetadata = {\n tenantId: log.tenantId,\n organizationId: log.organizationId,\n actorUserId: auth.sub,\n actionLabel: log.actionLabel,\n resourceKind: log.resourceKind,\n resourceId: log.resourceId,\n context: metadataContext,\n }\n const resolvedInput = resolveRedoInput(log.commandPayload, log)\n if (!resolvedInput) {\n return NextResponse.json({ error: 'Redo data unavailable for this action' }, { status: 400 })\n }\n const commandInput = resolvedInput\n const { logEntry } = await commandBus.execute(log.commandId, {\n input: commandInput,\n ctx,\n metadata,\n redoLogEntry: log,\n })\n await logs.markRedone(log.id)\n const actionLog = asActionLog(logEntry)\n const response = NextResponse.json({\n ok: true,\n logId: actionLog?.id ?? null,\n undoToken: actionLog?.undoToken ?? null,\n })\n if (actionLog?.undoToken && actionLog.id) {\n const createdAt = actionLog.createdAt instanceof Date\n ? actionLog.createdAt.toISOString()\n : (typeof actionLog.createdAt === 'string' ? actionLog.createdAt : new Date().toISOString())\n response.headers.set('x-om-operation', serializeOperationMetadata({\n id: actionLog.id,\n undoToken: actionLog.undoToken,\n commandId: actionLog.commandId ?? log.commandId,\n actionLabel: actionLog.actionLabel ?? log.actionLabel ?? null,\n resourceKind: typeof actionLog.resourceKind === 'string' ? actionLog.resourceKind : log.resourceKind ?? null,\n resourceId: typeof actionLog.resourceId === 'string' ? actionLog.resourceId : log.resourceId ?? null,\n executedAt: createdAt,\n }))\n }\n return response\n } catch (err) {\n console.error('Redo failed', err)\n return NextResponse.json({ error: 'Redo failed' }, { status: 400 })\n }\n}\n\nasync function createRuntimeContext(container: AwilixContainer, auth: AuthContext, request: Request): Promise<CommandRuntimeContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n return {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope.selectedId,\n organizationIds: scope.filterIds,\n request,\n }\n}\n\nfunction asActionLog(entry: unknown): ActionLog | null {\n if (!entry || typeof entry !== 'object') return null\n if (typeof (entry as { id?: unknown }).id !== 'string') return null\n return entry as ActionLog\n}\n\nfunction resolveRedoInput(payload: unknown, log: ActionLog): unknown | null {\n if (payload && typeof payload === 'object' && !Array.isArray(payload) && '__redoInput' in payload) {\n const envelope = payload as { __redoInput?: unknown }\n return envelope.__redoInput ?? {}\n }\n const updateFallback = deriveUpdateInput(log)\n if (updateFallback) return updateFallback\n return null\n}\n\nfunction deriveUpdateInput(log: ActionLog): Record<string, unknown> | null {\n if (!log.commandId.endsWith('.update')) return null\n if (!log.resourceId) return null\n const changes = log.changesJson\n if (!changes || typeof changes !== 'object' || Array.isArray(changes)) return { id: log.resourceId }\n const payload: Record<string, unknown> = { id: log.resourceId }\n for (const [key, value] of Object.entries(changes)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && 'to' in value) {\n payload[key] = (value as Record<string, unknown>).to\n } else {\n payload[key] = value\n }\n }\n return payload\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Redo a previously undone action',\n description: 'Replays the command associated with a recently undone action, reapplying the change and issuing a fresh undo token.',\n methods: {\n POST: {\n summary: 'Redo by action log id',\n description:\n 'Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: redoRequestSchema,\n },\n responses: [\n { status: 200, description: 'Redo executed successfully', schema: redoResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Log not eligible for redo', schema: errorSchema },\n { status: 401, description: 'Authentication required', schema: errorSchema },\n { status: 403, description: 'Redo blocked by scope checks', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest, type AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { CommandBus } from '@open-mercato/shared/lib/commands/command-bus'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { CommandRuntimeContext, CommandLogMetadata } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { AwilixContainer } from 'awilix'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['audit_logs.redo_self'] },\n}\n\ntype RedoRequestBody = {\n logId?: string\n}\n\nconst redoRequestSchema = z.object({\n logId: z.string().min(1).describe('Identifier of the previously undone action log'),\n})\n\nconst redoResponseSchema = z.object({\n ok: z.literal(true),\n logId: z.string().nullable().describe('Identifier of the new redo log entry, if available'),\n undoToken: z.string().nullable().describe('New undo token associated with the redone action'),\n})\n\nconst errorSchema = z.object({\n error: z.string(),\n})\n\nexport async function POST(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = (await req.json().catch(() => null)) as RedoRequestBody | null\n const logId = typeof body?.logId === 'string' ? body.logId.trim() : ''\n if (!logId) return NextResponse.json({ error: 'Invalid log id' }, { status: 400 })\n\n const container = await createRequestContainer()\n const commandBus = (container.resolve('commandBus') as CommandBus)\n const logs = (container.resolve('actionLogService') as ActionLogService)\n let rbac: RbacService | null = null\n try {\n rbac = (container.resolve('rbacService') as RbacService)\n } catch {\n rbac = null\n }\n\n const { organizationId } = await resolveFeatureCheckContext({ container, auth, request: req })\n\n const canRedoTenant = rbac\n ? await rbac.userHasAllFeatures(auth.sub, ['audit_logs.redo_tenant'], {\n tenantId: auth.tenantId ?? null,\n organizationId,\n })\n : false\n\n const scopedOrgId = canRedoTenant ? organizationId ?? null : organizationId ?? auth.orgId ?? null\n const log = await logs.findById(logId)\n\n if (!log || log.executionState !== 'undone') {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n if (log.actorUserId && log.actorUserId !== auth.sub && !canRedoTenant) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Fail closed on tenant scope: `audit_logs.redo_tenant` only widens scope WITHIN a\n // tenant, never across tenants, so a tenant-scoped target always requires a caller\n // bound to that same tenant. A caller whose tenantId is null (tenant-less global\n // account or unscoped API key) must never redo a tenant-scoped row. Mirrors the\n // hardened undo route (issue #2685, ported in #2931).\n if (log.tenantId && log.tenantId !== (auth.tenantId ?? null)) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n // Tenant-level redoers may redo across organizations within the tenant, so an\n // unresolved (null) caller org is allowed and only an explicit mismatch is rejected.\n // Every other caller must resolve to the target's own organization \u2014 a null caller\n // org must not bypass an org-scoped target (issue #2685, ported in #2931).\n const orgScopeMismatch = canRedoTenant\n ? Boolean(log.organizationId && scopedOrgId && log.organizationId !== scopedOrgId)\n : Boolean(log.organizationId && log.organizationId !== scopedOrgId)\n if (orgScopeMismatch) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n const lookupActorId = canRedoTenant ? (log.actorUserId ?? auth.sub) : auth.sub\n const latestUndoneOrganizationId = log.organizationId ?? null\n const latestUndone = await logs.latestUndoneForActor(lookupActorId, {\n tenantId: auth.tenantId ?? null,\n organizationId: latestUndoneOrganizationId,\n })\n if (!latestUndone || latestUndone.id !== log.id) {\n return NextResponse.json({ error: 'Redo target not available' }, { status: 400 })\n }\n\n try {\n const ctx = await createRuntimeContext(container, auth, req)\n const contextRecord = log.contextJson && typeof log.contextJson === 'object' ? (log.contextJson as Record<string, unknown>) : null\n const cacheAliasesRaw = Array.isArray(contextRecord?.cacheAliases as unknown[])\n ? (contextRecord!.cacheAliases as unknown[])\n : []\n const cacheAliases = cacheAliasesRaw\n .filter((value): value is string => typeof value === 'string' && value.trim().length > 0)\n .map((value) => value.trim())\n const metadataContext: Record<string, unknown> = {\n historyAction: 'redo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n if (cacheAliases.length) metadataContext.cacheAliases = cacheAliases\n const metadata: CommandLogMetadata = {\n tenantId: log.tenantId,\n organizationId: log.organizationId,\n actorUserId: auth.sub,\n actionLabel: log.actionLabel,\n resourceKind: log.resourceKind,\n resourceId: log.resourceId,\n context: metadataContext,\n }\n const resolvedInput = resolveRedoInput(log.commandPayload, log)\n if (!resolvedInput) {\n return NextResponse.json({ error: 'Redo data unavailable for this action' }, { status: 400 })\n }\n const commandInput = resolvedInput\n const { logEntry } = await commandBus.execute(log.commandId, {\n input: commandInput,\n ctx,\n metadata,\n redoLogEntry: log,\n })\n await logs.markRedone(log.id)\n const actionLog = asActionLog(logEntry)\n const response = NextResponse.json({\n ok: true,\n logId: actionLog?.id ?? null,\n undoToken: actionLog?.undoToken ?? null,\n })\n if (actionLog?.undoToken && actionLog.id) {\n const createdAt = actionLog.createdAt instanceof Date\n ? actionLog.createdAt.toISOString()\n : (typeof actionLog.createdAt === 'string' ? actionLog.createdAt : new Date().toISOString())\n response.headers.set('x-om-operation', serializeOperationMetadata({\n id: actionLog.id,\n undoToken: actionLog.undoToken,\n commandId: actionLog.commandId ?? log.commandId,\n actionLabel: actionLog.actionLabel ?? log.actionLabel ?? null,\n resourceKind: typeof actionLog.resourceKind === 'string' ? actionLog.resourceKind : log.resourceKind ?? null,\n resourceId: typeof actionLog.resourceId === 'string' ? actionLog.resourceId : log.resourceId ?? null,\n executedAt: createdAt,\n }))\n }\n return response\n } catch (err) {\n console.error('Redo failed', err)\n return NextResponse.json({ error: 'Redo failed' }, { status: 400 })\n }\n}\n\nasync function createRuntimeContext(container: AwilixContainer, auth: AuthContext, request: Request): Promise<CommandRuntimeContext> {\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n return {\n container,\n auth,\n organizationScope: scope,\n selectedOrganizationId: scope.selectedId,\n organizationIds: scope.filterIds,\n request,\n }\n}\n\nfunction asActionLog(entry: unknown): ActionLog | null {\n if (!entry || typeof entry !== 'object') return null\n if (typeof (entry as { id?: unknown }).id !== 'string') return null\n return entry as ActionLog\n}\n\nfunction resolveRedoInput(payload: unknown, log: ActionLog): unknown | null {\n if (payload && typeof payload === 'object' && !Array.isArray(payload) && '__redoInput' in payload) {\n const envelope = payload as { __redoInput?: unknown }\n return envelope.__redoInput ?? {}\n }\n const updateFallback = deriveUpdateInput(log)\n if (updateFallback) return updateFallback\n return null\n}\n\nfunction deriveUpdateInput(log: ActionLog): Record<string, unknown> | null {\n if (!log.commandId.endsWith('.update')) return null\n if (!log.resourceId) return null\n const changes = log.changesJson\n if (!changes || typeof changes !== 'object' || Array.isArray(changes)) return { id: log.resourceId }\n const payload: Record<string, unknown> = { id: log.resourceId }\n for (const [key, value] of Object.entries(changes)) {\n if (value && typeof value === 'object' && !Array.isArray(value) && 'to' in value) {\n payload[key] = (value as Record<string, unknown>).to\n } else {\n payload[key] = value\n }\n }\n return payload\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Redo a previously undone action',\n description: 'Replays the command associated with a recently undone action, reapplying the change and issuing a fresh undo token.',\n methods: {\n POST: {\n summary: 'Redo by action log id',\n description:\n 'Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope.',\n requestBody: {\n contentType: 'application/json',\n schema: redoRequestSchema,\n },\n responses: [\n { status: 200, description: 'Redo executed successfully', schema: redoResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Log not eligible for redo', schema: errorSchema },\n { status: 401, description: 'Authentication required', schema: errorSchema },\n { status: 403, description: 'Redo blocked by scope checks', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["metadata"]
7
7
  }
@@ -174,7 +174,8 @@ __decorateClass([
174
174
  AccessLog = __decorateClass([
175
175
  Entity({ tableName: "access_logs" }),
176
176
  Index({ name: "access_logs_tenant_idx", properties: ["tenantId", "createdAt"] }),
177
- Index({ name: "access_logs_actor_idx", properties: ["actorUserId", "createdAt"] })
177
+ Index({ name: "access_logs_actor_idx", properties: ["actorUserId", "createdAt"] }),
178
+ Index({ name: "access_logs_created_at_idx", properties: ["createdAt"] })
178
179
  ], AccessLog);
179
180
  export {
180
181
  AccessLog,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/audit_logs/data/entities.ts"],
4
- "sourcesContent": ["import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\nimport type { ActionLogProjectionType, ActionLogSourceKey } from '@open-mercato/core/modules/audit_logs/lib/projections'\n\nexport type ActionLogExecutionState = 'done' | 'undoing' | 'undone' | 'failed' | 'redone'\n\n@Entity({ tableName: 'action_logs' })\n@Index({ name: 'action_logs_tenant_idx', properties: ['tenantId', 'createdAt'] })\n@Index({ name: 'action_logs_actor_idx', properties: ['actorUserId', 'createdAt'] })\n@Index({ name: 'action_logs_resource_idx', properties: ['tenantId', 'resourceKind', 'resourceId', 'createdAt'] })\n@Index({ name: 'action_logs_parent_resource_idx', properties: ['tenantId', 'parentResourceKind', 'parentResourceId', 'createdAt'] })\n@Index({ name: 'action_logs_action_type_idx', properties: ['tenantId', 'organizationId', 'actionType', 'createdAt'] })\n@Index({ name: 'action_logs_source_key_idx', properties: ['tenantId', 'organizationId', 'sourceKey', 'createdAt'] })\n@Index({ name: 'action_logs_primary_changed_field_idx', properties: ['tenantId', 'organizationId', 'primaryChangedField', 'createdAt'] })\n@Index({ name: 'action_logs_changed_fields_idx', properties: ['changedFields'], type: 'gin' })\n@Index({ name: 'action_logs_related_resource_idx', properties: ['tenantId', 'relatedResourceKind', 'relatedResourceId', 'createdAt'] })\nexport class ActionLog {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId: string | null = null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId: string | null = null\n\n @Property({ name: 'actor_user_id', type: 'uuid', nullable: true })\n actorUserId: string | null = null\n\n @Property({ name: 'command_id', type: 'text' })\n commandId!: string\n\n @Property({ name: 'action_label', type: 'text', nullable: true })\n actionLabel: string | null = null\n\n @Property({ name: 'action_type', type: 'text', nullable: true })\n actionType: ActionLogProjectionType | null = null\n\n @Property({ name: 'resource_kind', type: 'text', nullable: true })\n resourceKind: string | null = null\n\n @Property({ name: 'resource_id', type: 'text', nullable: true })\n resourceId: string | null = null\n\n @Property({ name: 'parent_resource_kind', type: 'text', nullable: true })\n parentResourceKind: string | null = null\n\n @Property({ name: 'parent_resource_id', type: 'text', nullable: true })\n parentResourceId: string | null = null\n\n @Property({ name: 'execution_state', type: 'text', default: 'done' })\n executionState: ActionLogExecutionState = 'done'\n\n @Property({ name: 'undo_token', type: 'text', nullable: true })\n undoToken: string | null = null\n\n @Property({ name: 'command_payload', type: 'jsonb', nullable: true })\n commandPayload: unknown | null = null\n\n @Property({ name: 'snapshot_before', type: 'jsonb', nullable: true })\n snapshotBefore: unknown | null = null\n\n @Property({ name: 'snapshot_after', type: 'jsonb', nullable: true })\n snapshotAfter: unknown | null = null\n\n @Property({ name: 'changes_json', type: 'jsonb', nullable: true })\n changesJson: Record<string, unknown> | null = null\n\n @Property({ name: 'changed_fields', type: 'text[]', nullable: true })\n changedFields: string[] | null = null\n\n @Property({ name: 'primary_changed_field', type: 'text', nullable: true })\n primaryChangedField: string | null = null\n\n @Property({ name: 'context_json', type: 'jsonb', nullable: true })\n contextJson: Record<string, unknown> | null = null\n\n @Property({ name: 'source_key', type: 'text', nullable: true })\n sourceKey: ActionLogSourceKey | null = null\n\n @Property({ name: 'related_resource_kind', type: 'text', nullable: true })\n relatedResourceKind: string | null = null\n\n @Property({ name: 'related_resource_id', type: 'text', nullable: true })\n relatedResourceId: string | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt: Date | null = null\n}\n\n@Entity({ tableName: 'access_logs' })\n@Index({ name: 'access_logs_tenant_idx', properties: ['tenantId', 'createdAt'] })\n@Index({ name: 'access_logs_actor_idx', properties: ['actorUserId', 'createdAt'] })\nexport class AccessLog {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId: string | null = null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId: string | null = null\n\n @Property({ name: 'actor_user_id', type: 'uuid', nullable: true })\n actorUserId: string | null = null\n\n @Property({ name: 'resource_kind', type: 'text' })\n resourceKind!: string\n\n @Property({ name: 'resource_id', type: 'text' })\n resourceId!: string\n\n @Property({ name: 'access_type', type: 'text' })\n accessType!: string\n\n @Property({ name: 'fields_json', type: 'jsonb', nullable: true })\n fieldsJson: string[] | null = null\n\n @Property({ name: 'context_json', type: 'jsonb', nullable: true })\n contextJson: Record<string, unknown> | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt: Date | null = null\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Entity, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'\nimport type { ActionLogProjectionType, ActionLogSourceKey } from '@open-mercato/core/modules/audit_logs/lib/projections'\n\nexport type ActionLogExecutionState = 'done' | 'undoing' | 'undone' | 'failed' | 'redone'\n\n@Entity({ tableName: 'action_logs' })\n@Index({ name: 'action_logs_tenant_idx', properties: ['tenantId', 'createdAt'] })\n@Index({ name: 'action_logs_actor_idx', properties: ['actorUserId', 'createdAt'] })\n@Index({ name: 'action_logs_resource_idx', properties: ['tenantId', 'resourceKind', 'resourceId', 'createdAt'] })\n@Index({ name: 'action_logs_parent_resource_idx', properties: ['tenantId', 'parentResourceKind', 'parentResourceId', 'createdAt'] })\n@Index({ name: 'action_logs_action_type_idx', properties: ['tenantId', 'organizationId', 'actionType', 'createdAt'] })\n@Index({ name: 'action_logs_source_key_idx', properties: ['tenantId', 'organizationId', 'sourceKey', 'createdAt'] })\n@Index({ name: 'action_logs_primary_changed_field_idx', properties: ['tenantId', 'organizationId', 'primaryChangedField', 'createdAt'] })\n@Index({ name: 'action_logs_changed_fields_idx', properties: ['changedFields'], type: 'gin' })\n@Index({ name: 'action_logs_related_resource_idx', properties: ['tenantId', 'relatedResourceKind', 'relatedResourceId', 'createdAt'] })\nexport class ActionLog {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId: string | null = null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId: string | null = null\n\n @Property({ name: 'actor_user_id', type: 'uuid', nullable: true })\n actorUserId: string | null = null\n\n @Property({ name: 'command_id', type: 'text' })\n commandId!: string\n\n @Property({ name: 'action_label', type: 'text', nullable: true })\n actionLabel: string | null = null\n\n @Property({ name: 'action_type', type: 'text', nullable: true })\n actionType: ActionLogProjectionType | null = null\n\n @Property({ name: 'resource_kind', type: 'text', nullable: true })\n resourceKind: string | null = null\n\n @Property({ name: 'resource_id', type: 'text', nullable: true })\n resourceId: string | null = null\n\n @Property({ name: 'parent_resource_kind', type: 'text', nullable: true })\n parentResourceKind: string | null = null\n\n @Property({ name: 'parent_resource_id', type: 'text', nullable: true })\n parentResourceId: string | null = null\n\n @Property({ name: 'execution_state', type: 'text', default: 'done' })\n executionState: ActionLogExecutionState = 'done'\n\n @Property({ name: 'undo_token', type: 'text', nullable: true })\n undoToken: string | null = null\n\n @Property({ name: 'command_payload', type: 'jsonb', nullable: true })\n commandPayload: unknown | null = null\n\n @Property({ name: 'snapshot_before', type: 'jsonb', nullable: true })\n snapshotBefore: unknown | null = null\n\n @Property({ name: 'snapshot_after', type: 'jsonb', nullable: true })\n snapshotAfter: unknown | null = null\n\n @Property({ name: 'changes_json', type: 'jsonb', nullable: true })\n changesJson: Record<string, unknown> | null = null\n\n @Property({ name: 'changed_fields', type: 'text[]', nullable: true })\n changedFields: string[] | null = null\n\n @Property({ name: 'primary_changed_field', type: 'text', nullable: true })\n primaryChangedField: string | null = null\n\n @Property({ name: 'context_json', type: 'jsonb', nullable: true })\n contextJson: Record<string, unknown> | null = null\n\n @Property({ name: 'source_key', type: 'text', nullable: true })\n sourceKey: ActionLogSourceKey | null = null\n\n @Property({ name: 'related_resource_kind', type: 'text', nullable: true })\n relatedResourceKind: string | null = null\n\n @Property({ name: 'related_resource_id', type: 'text', nullable: true })\n relatedResourceId: string | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })\n updatedAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt: Date | null = null\n}\n\n@Entity({ tableName: 'access_logs' })\n@Index({ name: 'access_logs_tenant_idx', properties: ['tenantId', 'createdAt'] })\n@Index({ name: 'access_logs_actor_idx', properties: ['actorUserId', 'createdAt'] })\n@Index({ name: 'access_logs_created_at_idx', properties: ['createdAt'] })\nexport class AccessLog {\n @PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })\n id!: string\n\n @Property({ name: 'tenant_id', type: 'uuid', nullable: true })\n tenantId: string | null = null\n\n @Property({ name: 'organization_id', type: 'uuid', nullable: true })\n organizationId: string | null = null\n\n @Property({ name: 'actor_user_id', type: 'uuid', nullable: true })\n actorUserId: string | null = null\n\n @Property({ name: 'resource_kind', type: 'text' })\n resourceKind!: string\n\n @Property({ name: 'resource_id', type: 'text' })\n resourceId!: string\n\n @Property({ name: 'access_type', type: 'text' })\n accessType!: string\n\n @Property({ name: 'fields_json', type: 'jsonb', nullable: true })\n fieldsJson: string[] | null = null\n\n @Property({ name: 'context_json', type: 'jsonb', nullable: true })\n contextJson: Record<string, unknown> | null = null\n\n @Property({ name: 'created_at', type: Date, onCreate: () => new Date() })\n createdAt: Date = new Date()\n\n @Property({ name: 'deleted_at', type: Date, nullable: true })\n deletedAt: Date | null = null\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,13 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260611104500 extends Migration {
3
+ up() {
4
+ this.addSql(`create index "access_logs_created_at_idx" on "access_logs" ("created_at");`);
5
+ }
6
+ down() {
7
+ this.addSql(`drop index "access_logs_created_at_idx";`);
8
+ }
9
+ }
10
+ export {
11
+ Migration20260611104500
12
+ };
13
+ //# sourceMappingURL=Migration20260611104500.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/audit_logs/migrations/Migration20260611104500.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260611104500 extends Migration {\n\n override up(): void | Promise<void> {\n this.addSql(`create index \"access_logs_created_at_idx\" on \"access_logs\" (\"created_at\");`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop index \"access_logs_created_at_idx\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,gCAAgC,UAAU;AAAA,EAE5C,KAA2B;AAClC,SAAK,OAAO,4EAA4E;AAAA,EAC1F;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,0CAA0C;AAAA,EACxD;AAEF;",
6
+ "names": []
7
+ }
@@ -13,10 +13,18 @@ function toPositiveNumber(value, fallback) {
13
13
  if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
14
14
  return parsed;
15
15
  }
16
+ function toNonNegativeNumber(value, fallback) {
17
+ if (value === void 0 || value === "") return fallback;
18
+ const parsed = Number(value);
19
+ if (!Number.isFinite(parsed) || parsed < 0) return fallback;
20
+ return parsed;
21
+ }
16
22
  const CORE_RETENTION_DAYS = toPositiveNumber(process.env.AUDIT_LOGS_CORE_RETENTION_DAYS, 7);
17
23
  const NON_CORE_RETENTION_HOURS = toPositiveNumber(process.env.AUDIT_LOGS_NON_CORE_RETENTION_HOURS, 8);
18
24
  const CORE_RETENTION_MS = CORE_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
19
25
  const NON_CORE_RETENTION_MS = NON_CORE_RETENTION_HOURS * 60 * 60 * 1e3;
26
+ const ROTATE_INTERVAL_MS = toNonNegativeNumber(process.env.AUDIT_LOGS_ROTATE_INTERVAL_MS, 6e4);
27
+ let lastRotatedAt = null;
20
28
  const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
21
29
  const MAX_BATCH_ROWS = 500;
22
30
  let validationWarningLogged = false;
@@ -294,6 +302,8 @@ class AccessLogService {
294
302
  }
295
303
  async rotate(fork) {
296
304
  const now = Date.now();
305
+ if (ROTATE_INTERVAL_MS > 0 && lastRotatedAt !== null && now - lastRotatedAt < ROTATE_INTERVAL_MS) return;
306
+ lastRotatedAt = now;
297
307
  const coreCutoff = new Date(now - CORE_RETENTION_MS);
298
308
  const nonCoreCutoff = new Date(now - NON_CORE_RETENTION_MS);
299
309
  try {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/audit_logs/services/accessLogService.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { AccessLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport {\n accessLogCreateSchema,\n accessLogListSchema,\n type AccessLogCreateInput,\n type AccessLogListQuery,\n} from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { E } from '#generated/entities.ids.generated'\n\nconst CORE_RESOURCE_KINDS = new Set<string>(['auth.user', 'auth.role'])\n\nfunction toPositiveNumber(value: string | undefined, fallback: number): number {\n if (!value) return fallback\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) return fallback\n return parsed\n}\n\nconst CORE_RETENTION_DAYS = toPositiveNumber(process.env.AUDIT_LOGS_CORE_RETENTION_DAYS, 7)\nconst NON_CORE_RETENTION_HOURS = toPositiveNumber(process.env.AUDIT_LOGS_NON_CORE_RETENTION_HOURS, 8)\nconst CORE_RETENTION_MS = CORE_RETENTION_DAYS * 24 * 60 * 60 * 1000\nconst NON_CORE_RETENTION_MS = NON_CORE_RETENTION_HOURS * 60 * 60 * 1000\nconst UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n// Postgres has a hard limit of 65k bind parameters per statement. Each access\n// log row uses 10 bind values (see INSERT below), so 500 rows \u00D7 10 = 5 000\n// parameters \u2014 well below the limit while keeping memory bounded.\nconst MAX_BATCH_ROWS = 500\n\nlet validationWarningLogged = false\nlet runtimeValidationAvailable: boolean | null = null\n\n// Module-level registry of in-flight access-log writes. Both `log` and\n// `logMany` opt every promise they kick off into this set so that\n// `flushAccessLog()` can drain them. This is what makes the new\n// fire-and-forget CRUD path safe for test code that asserts on `access_logs`\n// rows immediately after a response \u2014 the integration harness defaults to\n// blocking via `OM_CRUD_ACCESS_LOG_BLOCKING=1`, and direct callers can opt\n// in to draining explicitly via `flushAccessLog()`.\nconst pendingAccessLogWrites = new Set<Promise<unknown>>()\n\nfunction trackPendingAccessLogWrite<T>(promise: Promise<T>): Promise<T> {\n pendingAccessLogWrites.add(promise as unknown as Promise<unknown>)\n promise\n .catch(() => undefined)\n .finally(() => {\n pendingAccessLogWrites.delete(promise as unknown as Promise<unknown>)\n })\n return promise\n}\n\nexport async function flushAccessLog(): Promise<void> {\n while (pendingAccessLogWrites.size > 0) {\n const snapshot = Array.from(pendingAccessLogWrites)\n await Promise.allSettled(snapshot)\n }\n}\n\nconst isZodRuntimeMissing = (err: unknown) => err instanceof TypeError && typeof err.message === 'string' && err.message.includes('_zod')\n\ntype RawEncryptedFields = {\n resourceKind?: unknown\n resourceId?: unknown\n accessType?: unknown\n fieldsJson?: unknown\n contextJson?: unknown\n}\n\nfunction serializeJsonColumn(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') {\n try {\n JSON.parse(value)\n return value\n } catch {\n return JSON.stringify(value)\n }\n }\n try {\n return JSON.stringify(value)\n } catch {\n return null\n }\n}\n\nexport class AccessLogService {\n constructor(private readonly em: EntityManager) {}\n\n async log(input: AccessLogCreateInput): Promise<AccessLog | null> {\n const promise = this.logInternal(input)\n return trackPendingAccessLogWrite(promise)\n }\n\n async logMany(inputs: AccessLogCreateInput[]): Promise<number> {\n if (!Array.isArray(inputs) || inputs.length === 0) return 0\n const promise = this.logManyInternal(inputs)\n return trackPendingAccessLogWrite(promise)\n }\n\n flush(): Promise<void> {\n return flushAccessLog()\n }\n\n private async logManyInternal(inputs: AccessLogCreateInput[]): Promise<number> {\n // Parsing in parallel matches the legacy fan-out `Promise.all(map(...service.log()))`\n // path's wall-clock; the previous sequential loop made batched writes slower than\n // un-batched on tenants with encryption enabled and pushed UI integration tests\n // over their dialog-stability budget.\n const parsedResults = await Promise.all(inputs.map((input) => this.parseInput(input)))\n const normalized: AccessLogCreateInput[] = []\n for (const parsed of parsedResults) {\n if (parsed) normalized.push(parsed)\n }\n if (!normalized.length) return 0\n\n let written = 0\n for (let offset = 0; offset < normalized.length; offset += MAX_BATCH_ROWS) {\n const chunk = normalized.slice(offset, offset + MAX_BATCH_ROWS)\n written += await this.writeChunk(chunk)\n }\n if (written > 0) {\n const fork = this.em.fork({ useContext: true })\n await this.rotate(fork)\n }\n return written\n }\n\n private async writeChunk(chunk: AccessLogCreateInput[]): Promise<number> {\n if (!chunk.length) return 0\n const fork = this.em.fork({ useContext: true })\n const encryption = resolveTenantEncryptionService(fork as any)\n const createdAt = new Date()\n\n // Encrypt every row in parallel so encryption-enabled tenants do not pay\n // the N-rows \u00D7 per-row latency penalty that the previous sequential\n // for-of loop introduced. The legacy `service.log()` fan-out resolved\n // its 50 encryption calls concurrently via `Promise.all`; preserve that\n // characteristic here so the batched single-INSERT path is strictly\n // faster than the legacy parallel-INSERTs path.\n type PreparedRow = {\n tenantId: string | null\n organizationId: string | null\n data: AccessLogCreateInput\n fields: unknown[] | null\n context: Record<string, unknown> | null\n encrypted: RawEncryptedFields | null\n }\n const prepared: PreparedRow[] = await Promise.all(\n chunk.map(async (data) => {\n const fields = Array.isArray(data.fields) && data.fields.length ? data.fields : null\n const context = data.context && Object.keys(data.context).length ? data.context : null\n const tenantId = data.tenantId ?? null\n const organizationId = data.organizationId ?? null\n const encrypted = encryption\n ? ((await encryption.encryptEntityPayload(\n E.audit_logs.access_log,\n {\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n },\n tenantId,\n organizationId,\n )) as RawEncryptedFields)\n : null\n return { tenantId, organizationId, data, fields, context, encrypted }\n }),\n )\n\n const placeholders: string[] = []\n const params: unknown[] = []\n for (const row of prepared) {\n const { tenantId, organizationId, data, fields, context, encrypted } = row\n const resourceKindOut = encrypted?.resourceKind ?? data.resourceKind\n const resourceIdOut = encrypted?.resourceId ?? data.resourceId\n const accessTypeOut = encrypted?.accessType ?? data.accessType\n const fieldsOut = encrypted?.fieldsJson ?? fields\n const contextOut = encrypted?.contextJson ?? context\n placeholders.push('(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')\n params.push(\n tenantId,\n organizationId,\n data.actorUserId ?? null,\n resourceKindOut,\n resourceIdOut,\n accessTypeOut,\n serializeJsonColumn(fieldsOut),\n serializeJsonColumn(contextOut),\n createdAt,\n null,\n )\n }\n if (!placeholders.length) return 0\n const sql = `insert into \"access_logs\" (\"tenant_id\", \"organization_id\", \"actor_user_id\", \"resource_kind\", \"resource_id\", \"access_type\", \"fields_json\", \"context_json\", \"created_at\", \"deleted_at\") values ${placeholders.join(', ')}`\n await fork.getConnection().execute(sql, params)\n return chunk.length\n }\n\n private async parseInput(input: AccessLogCreateInput): Promise<AccessLogCreateInput | null> {\n const schema = accessLogCreateSchema as typeof accessLogCreateSchema & { _zod?: unknown }\n const canValidate = Boolean(schema && typeof schema.parse === 'function')\n const shouldValidate = canValidate && runtimeValidationAvailable !== false\n if (shouldValidate) {\n try {\n const data = schema.parse(input)\n runtimeValidationAvailable = true\n return data\n } catch (err) {\n if (!isZodRuntimeMissing(err) && !validationWarningLogged) {\n validationWarningLogged = true\n // eslint-disable-next-line no-console\n console.warn('[audit_logs] falling back to permissive access log payload parser', err)\n }\n if (isZodRuntimeMissing(err)) runtimeValidationAvailable = false\n return this.normalizeInput(input)\n }\n }\n return this.normalizeInput(input)\n }\n\n private async logInternal(input: AccessLogCreateInput): Promise<AccessLog | null> {\n const data = await this.parseInput(input)\n if (!data) return null\n const fork = this.em.fork({ useContext: true })\n const fields = Array.isArray(data.fields) && data.fields.length ? data.fields : null\n const context = data.context && Object.keys(data.context).length ? data.context : null\n const createdAt = new Date()\n const tenantId = data.tenantId ?? null\n const organizationId = data.organizationId ?? null\n\n const encryption = resolveTenantEncryptionService(fork as any)\n const encrypted = encryption\n ? ((await encryption.encryptEntityPayload(\n E.audit_logs.access_log,\n {\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n },\n tenantId,\n organizationId,\n )) as RawEncryptedFields)\n : null\n\n const payload = {\n resourceKind: encrypted?.resourceKind ?? data.resourceKind,\n resourceId: encrypted?.resourceId ?? data.resourceId,\n accessType: encrypted?.accessType ?? data.accessType,\n fieldsJson: encrypted?.fieldsJson ?? fields,\n contextJson: encrypted?.contextJson ?? context,\n }\n\n const rows = await fork.getConnection().execute(\n `insert into \"access_logs\" (\"tenant_id\", \"organization_id\", \"actor_user_id\", \"resource_kind\", \"resource_id\", \"access_type\", \"fields_json\", \"context_json\", \"created_at\", \"deleted_at\") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning \"id\"`,\n [\n tenantId,\n organizationId,\n data.actorUserId ?? null,\n payload.resourceKind,\n payload.resourceId,\n payload.accessType,\n serializeJsonColumn(payload.fieldsJson),\n serializeJsonColumn(payload.contextJson),\n createdAt,\n null,\n ],\n )\n await this.rotate(fork)\n const id = Array.isArray(rows) && rows.length > 0 ? rows[0]?.id ?? null : null\n if (!id) return null\n const entry = fork.create(AccessLog, {\n id,\n tenantId: data.tenantId ?? null,\n organizationId: data.organizationId ?? null,\n actorUserId: data.actorUserId ?? null,\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n createdAt,\n })\n return entry\n }\n\n private normalizeInput(input: Partial<AccessLogCreateInput> | null | undefined): AccessLogCreateInput {\n if (!input) {\n return {\n tenantId: null,\n organizationId: null,\n actorUserId: null,\n resourceKind: 'unknown',\n resourceId: 'unknown',\n accessType: 'unknown',\n fields: undefined,\n context: undefined,\n }\n }\n const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n const toNullableUuid = (value: unknown) => {\n if (typeof value !== 'string' || value.length === 0) return null\n const candidate = value.startsWith('api_key:') ? value.slice('api_key:'.length) : value\n return UUID_REGEX.test(candidate) ? candidate : null\n }\n const fields = Array.isArray(input.fields)\n ? input.fields.filter((f): f is string => typeof f === 'string' && f.length > 0)\n : undefined\n const context = typeof input.context === 'object' && input.context !== null\n ? input.context as Record<string, unknown>\n : undefined\n return {\n tenantId: toNullableUuid(input.tenantId),\n organizationId: toNullableUuid(input.organizationId),\n actorUserId: toNullableUuid(input.actorUserId),\n resourceKind: String(input.resourceKind || 'unknown'),\n resourceId: String(input.resourceId || 'unknown'),\n accessType: String(input.accessType || 'unknown'),\n fields,\n context,\n }\n }\n\n async list(query: Partial<AccessLogListQuery>) {\n const parsed = accessLogListSchema.parse({\n ...query,\n })\n\n const where: FilterQuery<AccessLog> = { deletedAt: null }\n if (parsed.tenantId) where.tenantId = parsed.tenantId\n if (parsed.organizationId) where.organizationId = parsed.organizationId\n if (parsed.actorUserId) where.actorUserId = parsed.actorUserId\n if (parsed.resourceKind) where.resourceKind = parsed.resourceKind\n if (parsed.accessType) where.accessType = parsed.accessType\n if (parsed.before) where.createdAt = { ...(where.createdAt as Record<string, any> | undefined), $lt: parsed.before } as any\n if (parsed.after) where.createdAt = { ...(where.createdAt as Record<string, any> | undefined), $gt: parsed.after } as any\n\n const pageSize = parsed.pageSize ?? parsed.limit ?? 50\n const page = parsed.page ?? 1\n const offset = (page - 1) * pageSize\n\n const [items, total] = await this.em.findAndCount(\n AccessLog,\n where,\n {\n orderBy: { createdAt: 'desc' },\n limit: pageSize,\n offset,\n },\n )\n\n // Encrypted jsonb columns (`fields_json`, `context_json`) come back as raw\n // JSON strings from the encryption subscriber after issue #1810 follow-up\n // (entity-field decryption no longer auto-parses). Restore the structured\n // shape on read so API consumers see typed objects/arrays.\n for (const item of items) {\n const rawFieldsJson = (item as { fieldsJson?: unknown }).fieldsJson\n if (typeof rawFieldsJson === 'string') {\n const parsed = parseDecryptedFieldValue(rawFieldsJson)\n item.fieldsJson = Array.isArray(parsed) ? (parsed as string[]) : null\n }\n const rawContextJson = (item as { contextJson?: unknown }).contextJson\n if (typeof rawContextJson === 'string') {\n const parsed = parseDecryptedFieldValue(rawContextJson)\n item.contextJson = parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null\n }\n }\n\n const totalPages = Math.max(1, Math.ceil((total || 0) / (pageSize || 1)))\n return { items, total, page, pageSize, totalPages }\n }\n\n private async rotate(fork: EntityManager) {\n const now = Date.now()\n const coreCutoff = new Date(now - CORE_RETENTION_MS)\n const nonCoreCutoff = new Date(now - NON_CORE_RETENTION_MS)\n try {\n if (CORE_RESOURCE_KINDS.size > 0) {\n await fork.nativeDelete(AccessLog, {\n resourceKind: { $in: Array.from(CORE_RESOURCE_KINDS) },\n createdAt: { $lt: coreCutoff },\n })\n }\n await fork.nativeDelete(AccessLog, {\n resourceKind: { $nin: Array.from(CORE_RESOURCE_KINDS) },\n createdAt: { $lt: nonCoreCutoff },\n })\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[audit_logs] failed to rotate access logs', err)\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { AccessLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport {\n accessLogCreateSchema,\n accessLogListSchema,\n type AccessLogCreateInput,\n type AccessLogListQuery,\n} from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { E } from '#generated/entities.ids.generated'\n\nconst CORE_RESOURCE_KINDS = new Set<string>(['auth.user', 'auth.role'])\n\nfunction toPositiveNumber(value: string | undefined, fallback: number): number {\n if (!value) return fallback\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed <= 0) return fallback\n return parsed\n}\n\nfunction toNonNegativeNumber(value: string | undefined, fallback: number): number {\n if (value === undefined || value === '') return fallback\n const parsed = Number(value)\n if (!Number.isFinite(parsed) || parsed < 0) return fallback\n return parsed\n}\n\nconst CORE_RETENTION_DAYS = toPositiveNumber(process.env.AUDIT_LOGS_CORE_RETENTION_DAYS, 7)\nconst NON_CORE_RETENTION_HOURS = toPositiveNumber(process.env.AUDIT_LOGS_NON_CORE_RETENTION_HOURS, 8)\nconst CORE_RETENTION_MS = CORE_RETENTION_DAYS * 24 * 60 * 60 * 1000\nconst NON_CORE_RETENTION_MS = NON_CORE_RETENTION_HOURS * 60 * 60 * 1000\n// Rotation runs after every successful write; without a gate that means two\n// DELETE statements per CRUD GET. Amortize to one rotation per interval per\n// process \u2014 `0` opts back into rotate-on-every-write (test harnesses).\nconst ROTATE_INTERVAL_MS = toNonNegativeNumber(process.env.AUDIT_LOGS_ROTATE_INTERVAL_MS, 60_000)\n\nlet lastRotatedAt: number | null = null\nconst UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n// Postgres has a hard limit of 65k bind parameters per statement. Each access\n// log row uses 10 bind values (see INSERT below), so 500 rows \u00D7 10 = 5 000\n// parameters \u2014 well below the limit while keeping memory bounded.\nconst MAX_BATCH_ROWS = 500\n\nlet validationWarningLogged = false\nlet runtimeValidationAvailable: boolean | null = null\n\n// Module-level registry of in-flight access-log writes. Both `log` and\n// `logMany` opt every promise they kick off into this set so that\n// `flushAccessLog()` can drain them. This is what makes the new\n// fire-and-forget CRUD path safe for test code that asserts on `access_logs`\n// rows immediately after a response \u2014 the integration harness defaults to\n// blocking via `OM_CRUD_ACCESS_LOG_BLOCKING=1`, and direct callers can opt\n// in to draining explicitly via `flushAccessLog()`.\nconst pendingAccessLogWrites = new Set<Promise<unknown>>()\n\nfunction trackPendingAccessLogWrite<T>(promise: Promise<T>): Promise<T> {\n pendingAccessLogWrites.add(promise as unknown as Promise<unknown>)\n promise\n .catch(() => undefined)\n .finally(() => {\n pendingAccessLogWrites.delete(promise as unknown as Promise<unknown>)\n })\n return promise\n}\n\nexport async function flushAccessLog(): Promise<void> {\n while (pendingAccessLogWrites.size > 0) {\n const snapshot = Array.from(pendingAccessLogWrites)\n await Promise.allSettled(snapshot)\n }\n}\n\nconst isZodRuntimeMissing = (err: unknown) => err instanceof TypeError && typeof err.message === 'string' && err.message.includes('_zod')\n\ntype RawEncryptedFields = {\n resourceKind?: unknown\n resourceId?: unknown\n accessType?: unknown\n fieldsJson?: unknown\n contextJson?: unknown\n}\n\nfunction serializeJsonColumn(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') {\n try {\n JSON.parse(value)\n return value\n } catch {\n return JSON.stringify(value)\n }\n }\n try {\n return JSON.stringify(value)\n } catch {\n return null\n }\n}\n\nexport class AccessLogService {\n constructor(private readonly em: EntityManager) {}\n\n async log(input: AccessLogCreateInput): Promise<AccessLog | null> {\n const promise = this.logInternal(input)\n return trackPendingAccessLogWrite(promise)\n }\n\n async logMany(inputs: AccessLogCreateInput[]): Promise<number> {\n if (!Array.isArray(inputs) || inputs.length === 0) return 0\n const promise = this.logManyInternal(inputs)\n return trackPendingAccessLogWrite(promise)\n }\n\n flush(): Promise<void> {\n return flushAccessLog()\n }\n\n private async logManyInternal(inputs: AccessLogCreateInput[]): Promise<number> {\n // Parsing in parallel matches the legacy fan-out `Promise.all(map(...service.log()))`\n // path's wall-clock; the previous sequential loop made batched writes slower than\n // un-batched on tenants with encryption enabled and pushed UI integration tests\n // over their dialog-stability budget.\n const parsedResults = await Promise.all(inputs.map((input) => this.parseInput(input)))\n const normalized: AccessLogCreateInput[] = []\n for (const parsed of parsedResults) {\n if (parsed) normalized.push(parsed)\n }\n if (!normalized.length) return 0\n\n let written = 0\n for (let offset = 0; offset < normalized.length; offset += MAX_BATCH_ROWS) {\n const chunk = normalized.slice(offset, offset + MAX_BATCH_ROWS)\n written += await this.writeChunk(chunk)\n }\n if (written > 0) {\n const fork = this.em.fork({ useContext: true })\n await this.rotate(fork)\n }\n return written\n }\n\n private async writeChunk(chunk: AccessLogCreateInput[]): Promise<number> {\n if (!chunk.length) return 0\n const fork = this.em.fork({ useContext: true })\n const encryption = resolveTenantEncryptionService(fork as any)\n const createdAt = new Date()\n\n // Encrypt every row in parallel so encryption-enabled tenants do not pay\n // the N-rows \u00D7 per-row latency penalty that the previous sequential\n // for-of loop introduced. The legacy `service.log()` fan-out resolved\n // its 50 encryption calls concurrently via `Promise.all`; preserve that\n // characteristic here so the batched single-INSERT path is strictly\n // faster than the legacy parallel-INSERTs path.\n type PreparedRow = {\n tenantId: string | null\n organizationId: string | null\n data: AccessLogCreateInput\n fields: unknown[] | null\n context: Record<string, unknown> | null\n encrypted: RawEncryptedFields | null\n }\n const prepared: PreparedRow[] = await Promise.all(\n chunk.map(async (data) => {\n const fields = Array.isArray(data.fields) && data.fields.length ? data.fields : null\n const context = data.context && Object.keys(data.context).length ? data.context : null\n const tenantId = data.tenantId ?? null\n const organizationId = data.organizationId ?? null\n const encrypted = encryption\n ? ((await encryption.encryptEntityPayload(\n E.audit_logs.access_log,\n {\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n },\n tenantId,\n organizationId,\n )) as RawEncryptedFields)\n : null\n return { tenantId, organizationId, data, fields, context, encrypted }\n }),\n )\n\n const placeholders: string[] = []\n const params: unknown[] = []\n for (const row of prepared) {\n const { tenantId, organizationId, data, fields, context, encrypted } = row\n const resourceKindOut = encrypted?.resourceKind ?? data.resourceKind\n const resourceIdOut = encrypted?.resourceId ?? data.resourceId\n const accessTypeOut = encrypted?.accessType ?? data.accessType\n const fieldsOut = encrypted?.fieldsJson ?? fields\n const contextOut = encrypted?.contextJson ?? context\n placeholders.push('(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')\n params.push(\n tenantId,\n organizationId,\n data.actorUserId ?? null,\n resourceKindOut,\n resourceIdOut,\n accessTypeOut,\n serializeJsonColumn(fieldsOut),\n serializeJsonColumn(contextOut),\n createdAt,\n null,\n )\n }\n if (!placeholders.length) return 0\n const sql = `insert into \"access_logs\" (\"tenant_id\", \"organization_id\", \"actor_user_id\", \"resource_kind\", \"resource_id\", \"access_type\", \"fields_json\", \"context_json\", \"created_at\", \"deleted_at\") values ${placeholders.join(', ')}`\n await fork.getConnection().execute(sql, params)\n return chunk.length\n }\n\n private async parseInput(input: AccessLogCreateInput): Promise<AccessLogCreateInput | null> {\n const schema = accessLogCreateSchema as typeof accessLogCreateSchema & { _zod?: unknown }\n const canValidate = Boolean(schema && typeof schema.parse === 'function')\n const shouldValidate = canValidate && runtimeValidationAvailable !== false\n if (shouldValidate) {\n try {\n const data = schema.parse(input)\n runtimeValidationAvailable = true\n return data\n } catch (err) {\n if (!isZodRuntimeMissing(err) && !validationWarningLogged) {\n validationWarningLogged = true\n // eslint-disable-next-line no-console\n console.warn('[audit_logs] falling back to permissive access log payload parser', err)\n }\n if (isZodRuntimeMissing(err)) runtimeValidationAvailable = false\n return this.normalizeInput(input)\n }\n }\n return this.normalizeInput(input)\n }\n\n private async logInternal(input: AccessLogCreateInput): Promise<AccessLog | null> {\n const data = await this.parseInput(input)\n if (!data) return null\n const fork = this.em.fork({ useContext: true })\n const fields = Array.isArray(data.fields) && data.fields.length ? data.fields : null\n const context = data.context && Object.keys(data.context).length ? data.context : null\n const createdAt = new Date()\n const tenantId = data.tenantId ?? null\n const organizationId = data.organizationId ?? null\n\n const encryption = resolveTenantEncryptionService(fork as any)\n const encrypted = encryption\n ? ((await encryption.encryptEntityPayload(\n E.audit_logs.access_log,\n {\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n },\n tenantId,\n organizationId,\n )) as RawEncryptedFields)\n : null\n\n const payload = {\n resourceKind: encrypted?.resourceKind ?? data.resourceKind,\n resourceId: encrypted?.resourceId ?? data.resourceId,\n accessType: encrypted?.accessType ?? data.accessType,\n fieldsJson: encrypted?.fieldsJson ?? fields,\n contextJson: encrypted?.contextJson ?? context,\n }\n\n const rows = await fork.getConnection().execute(\n `insert into \"access_logs\" (\"tenant_id\", \"organization_id\", \"actor_user_id\", \"resource_kind\", \"resource_id\", \"access_type\", \"fields_json\", \"context_json\", \"created_at\", \"deleted_at\") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning \"id\"`,\n [\n tenantId,\n organizationId,\n data.actorUserId ?? null,\n payload.resourceKind,\n payload.resourceId,\n payload.accessType,\n serializeJsonColumn(payload.fieldsJson),\n serializeJsonColumn(payload.contextJson),\n createdAt,\n null,\n ],\n )\n await this.rotate(fork)\n const id = Array.isArray(rows) && rows.length > 0 ? rows[0]?.id ?? null : null\n if (!id) return null\n const entry = fork.create(AccessLog, {\n id,\n tenantId: data.tenantId ?? null,\n organizationId: data.organizationId ?? null,\n actorUserId: data.actorUserId ?? null,\n resourceKind: data.resourceKind,\n resourceId: data.resourceId,\n accessType: data.accessType,\n fieldsJson: fields,\n contextJson: context,\n createdAt,\n })\n return entry\n }\n\n private normalizeInput(input: Partial<AccessLogCreateInput> | null | undefined): AccessLogCreateInput {\n if (!input) {\n return {\n tenantId: null,\n organizationId: null,\n actorUserId: null,\n resourceKind: 'unknown',\n resourceId: 'unknown',\n accessType: 'unknown',\n fields: undefined,\n context: undefined,\n }\n }\n const UUID_REGEX = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n const toNullableUuid = (value: unknown) => {\n if (typeof value !== 'string' || value.length === 0) return null\n const candidate = value.startsWith('api_key:') ? value.slice('api_key:'.length) : value\n return UUID_REGEX.test(candidate) ? candidate : null\n }\n const fields = Array.isArray(input.fields)\n ? input.fields.filter((f): f is string => typeof f === 'string' && f.length > 0)\n : undefined\n const context = typeof input.context === 'object' && input.context !== null\n ? input.context as Record<string, unknown>\n : undefined\n return {\n tenantId: toNullableUuid(input.tenantId),\n organizationId: toNullableUuid(input.organizationId),\n actorUserId: toNullableUuid(input.actorUserId),\n resourceKind: String(input.resourceKind || 'unknown'),\n resourceId: String(input.resourceId || 'unknown'),\n accessType: String(input.accessType || 'unknown'),\n fields,\n context,\n }\n }\n\n async list(query: Partial<AccessLogListQuery>) {\n const parsed = accessLogListSchema.parse({\n ...query,\n })\n\n const where: FilterQuery<AccessLog> = { deletedAt: null }\n if (parsed.tenantId) where.tenantId = parsed.tenantId\n if (parsed.organizationId) where.organizationId = parsed.organizationId\n if (parsed.actorUserId) where.actorUserId = parsed.actorUserId\n if (parsed.resourceKind) where.resourceKind = parsed.resourceKind\n if (parsed.accessType) where.accessType = parsed.accessType\n if (parsed.before) where.createdAt = { ...(where.createdAt as Record<string, any> | undefined), $lt: parsed.before } as any\n if (parsed.after) where.createdAt = { ...(where.createdAt as Record<string, any> | undefined), $gt: parsed.after } as any\n\n const pageSize = parsed.pageSize ?? parsed.limit ?? 50\n const page = parsed.page ?? 1\n const offset = (page - 1) * pageSize\n\n const [items, total] = await this.em.findAndCount(\n AccessLog,\n where,\n {\n orderBy: { createdAt: 'desc' },\n limit: pageSize,\n offset,\n },\n )\n\n // Encrypted jsonb columns (`fields_json`, `context_json`) come back as raw\n // JSON strings from the encryption subscriber after issue #1810 follow-up\n // (entity-field decryption no longer auto-parses). Restore the structured\n // shape on read so API consumers see typed objects/arrays.\n for (const item of items) {\n const rawFieldsJson = (item as { fieldsJson?: unknown }).fieldsJson\n if (typeof rawFieldsJson === 'string') {\n const parsed = parseDecryptedFieldValue(rawFieldsJson)\n item.fieldsJson = Array.isArray(parsed) ? (parsed as string[]) : null\n }\n const rawContextJson = (item as { contextJson?: unknown }).contextJson\n if (typeof rawContextJson === 'string') {\n const parsed = parseDecryptedFieldValue(rawContextJson)\n item.contextJson = parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null\n }\n }\n\n const totalPages = Math.max(1, Math.ceil((total || 0) / (pageSize || 1)))\n return { items, total, page, pageSize, totalPages }\n }\n\n private async rotate(fork: EntityManager) {\n const now = Date.now()\n if (ROTATE_INTERVAL_MS > 0 && lastRotatedAt !== null && now - lastRotatedAt < ROTATE_INTERVAL_MS) return\n lastRotatedAt = now\n const coreCutoff = new Date(now - CORE_RETENTION_MS)\n const nonCoreCutoff = new Date(now - NON_CORE_RETENTION_MS)\n try {\n if (CORE_RESOURCE_KINDS.size > 0) {\n await fork.nativeDelete(AccessLog, {\n resourceKind: { $in: Array.from(CORE_RESOURCE_KINDS) },\n createdAt: { $lt: coreCutoff },\n })\n }\n await fork.nativeDelete(AccessLog, {\n resourceKind: { $nin: Array.from(CORE_RESOURCE_KINDS) },\n createdAt: { $lt: nonCoreCutoff },\n })\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[audit_logs] failed to rotate access logs', err)\n }\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,sCAAsC;AAC/C,SAAS,gCAAgC;AACzC,SAAS,SAAS;AAElB,MAAM,sBAAsB,oBAAI,IAAY,CAAC,aAAa,WAAW,CAAC;AAEtE,SAAS,iBAAiB,OAA2B,UAA0B;AAC7E,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO;AACT;AAEA,SAAS,oBAAoB,OAA2B,UAA0B;AAChF,MAAI,UAAU,UAAa,UAAU,GAAI,QAAO;AAChD,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,SAAS,EAAG,QAAO;AACnD,SAAO;AACT;AAEA,MAAM,sBAAsB,iBAAiB,QAAQ,IAAI,gCAAgC,CAAC;AAC1F,MAAM,2BAA2B,iBAAiB,QAAQ,IAAI,qCAAqC,CAAC;AACpG,MAAM,oBAAoB,sBAAsB,KAAK,KAAK,KAAK;AAC/D,MAAM,wBAAwB,2BAA2B,KAAK,KAAK;AAInE,MAAM,qBAAqB,oBAAoB,QAAQ,IAAI,+BAA+B,GAAM;AAEhG,IAAI,gBAA+B;AACnC,MAAM,aAAa;AAInB,MAAM,iBAAiB;AAEvB,IAAI,0BAA0B;AAC9B,IAAI,6BAA6C;AASjD,MAAM,yBAAyB,oBAAI,IAAsB;AAEzD,SAAS,2BAA8B,SAAiC;AACtE,yBAAuB,IAAI,OAAsC;AACjE,UACG,MAAM,MAAM,MAAS,EACrB,QAAQ,MAAM;AACb,2BAAuB,OAAO,OAAsC;AAAA,EACtE,CAAC;AACH,SAAO;AACT;AAEA,eAAsB,iBAAgC;AACpD,SAAO,uBAAuB,OAAO,GAAG;AACtC,UAAM,WAAW,MAAM,KAAK,sBAAsB;AAClD,UAAM,QAAQ,WAAW,QAAQ;AAAA,EACnC;AACF;AAEA,MAAM,sBAAsB,CAAC,QAAiB,eAAe,aAAa,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,SAAS,MAAM;AAUxI,SAAS,oBAAoB,OAA+B;AAC1D,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,WAAK,MAAM,KAAK;AAChB,aAAO;AAAA,IACT,QAAQ;AACN,aAAO,KAAK,UAAU,KAAK;AAAA,IAC7B;AAAA,EACF;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,MAAM,iBAAiB;AAAA,EAC5B,YAA6B,IAAmB;AAAnB;AAAA,EAAoB;AAAA,EAEjD,MAAM,IAAI,OAAwD;AAChE,UAAM,UAAU,KAAK,YAAY,KAAK;AACtC,WAAO,2BAA2B,OAAO;AAAA,EAC3C;AAAA,EAEA,MAAM,QAAQ,QAAiD;AAC7D,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,EAAG,QAAO;AAC1D,UAAM,UAAU,KAAK,gBAAgB,MAAM;AAC3C,WAAO,2BAA2B,OAAO;AAAA,EAC3C;AAAA,EAEA,QAAuB;AACrB,WAAO,eAAe;AAAA,EACxB;AAAA,EAEA,MAAc,gBAAgB,QAAiD;AAK7E,UAAM,gBAAgB,MAAM,QAAQ,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,WAAW,KAAK,CAAC,CAAC;AACrF,UAAM,aAAqC,CAAC;AAC5C,eAAW,UAAU,eAAe;AAClC,UAAI,OAAQ,YAAW,KAAK,MAAM;AAAA,IACpC;AACA,QAAI,CAAC,WAAW,OAAQ,QAAO;AAE/B,QAAI,UAAU;AACd,aAAS,SAAS,GAAG,SAAS,WAAW,QAAQ,UAAU,gBAAgB;AACzE,YAAM,QAAQ,WAAW,MAAM,QAAQ,SAAS,cAAc;AAC9D,iBAAW,MAAM,KAAK,WAAW,KAAK;AAAA,IACxC;AACA,QAAI,UAAU,GAAG;AACf,YAAM,OAAO,KAAK,GAAG,KAAK,EAAE,YAAY,KAAK,CAAC;AAC9C,YAAM,KAAK,OAAO,IAAI;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,WAAW,OAAgD;AACvE,QAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,UAAM,OAAO,KAAK,GAAG,KAAK,EAAE,YAAY,KAAK,CAAC;AAC9C,UAAM,aAAa,+BAA+B,IAAW;AAC7D,UAAM,YAAY,oBAAI,KAAK;AAgB3B,UAAM,WAA0B,MAAM,QAAQ;AAAA,MAC5C,MAAM,IAAI,OAAO,SAAS;AACxB,cAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,OAAO,SAAS,KAAK,SAAS;AAChF,cAAM,UAAU,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,EAAE,SAAS,KAAK,UAAU;AAClF,cAAM,WAAW,KAAK,YAAY;AAClC,cAAM,iBAAiB,KAAK,kBAAkB;AAC9C,cAAM,YAAY,aACZ,MAAM,WAAW;AAAA,UACjB,EAAE,WAAW;AAAA,UACb;AAAA,YACE,cAAc,KAAK;AAAA,YACnB,YAAY,KAAK;AAAA,YACjB,YAAY,KAAK;AAAA,YACjB,YAAY;AAAA,YACZ,aAAa;AAAA,UACf;AAAA,UACA;AAAA,UACA;AAAA,QACF,IACA;AACJ,eAAO,EAAE,UAAU,gBAAgB,MAAM,QAAQ,SAAS,UAAU;AAAA,MACtE,CAAC;AAAA,IACH;AAEA,UAAM,eAAyB,CAAC;AAChC,UAAM,SAAoB,CAAC;AAC3B,eAAW,OAAO,UAAU;AAC1B,YAAM,EAAE,UAAU,gBAAgB,MAAM,QAAQ,SAAS,UAAU,IAAI;AACvE,YAAM,kBAAkB,WAAW,gBAAgB,KAAK;AACxD,YAAM,gBAAgB,WAAW,cAAc,KAAK;AACpD,YAAM,gBAAgB,WAAW,cAAc,KAAK;AACpD,YAAM,YAAY,WAAW,cAAc;AAC3C,YAAM,aAAa,WAAW,eAAe;AAC7C,mBAAa,KAAK,gCAAgC;AAClD,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA,KAAK,eAAe;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA,oBAAoB,SAAS;AAAA,QAC7B,oBAAoB,UAAU;AAAA,QAC9B;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,aAAa,OAAQ,QAAO;AACjC,UAAM,MAAM,gMAAgM,aAAa,KAAK,IAAI,CAAC;AACnO,UAAM,KAAK,cAAc,EAAE,QAAQ,KAAK,MAAM;AAC9C,WAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAc,WAAW,OAAmE;AAC1F,UAAM,SAAS;AACf,UAAM,cAAc,QAAQ,UAAU,OAAO,OAAO,UAAU,UAAU;AACxE,UAAM,iBAAiB,eAAe,+BAA+B;AACrE,QAAI,gBAAgB;AAClB,UAAI;AACF,cAAM,OAAO,OAAO,MAAM,KAAK;AAC/B,qCAA6B;AAC7B,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,YAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC,yBAAyB;AACzD,oCAA0B;AAE1B,kBAAQ,KAAK,qEAAqE,GAAG;AAAA,QACvF;AACA,YAAI,oBAAoB,GAAG,EAAG,8BAA6B;AAC3D,eAAO,KAAK,eAAe,KAAK;AAAA,MAClC;AAAA,IACF;AACA,WAAO,KAAK,eAAe,KAAK;AAAA,EAClC;AAAA,EAEA,MAAc,YAAY,OAAwD;AAChF,UAAM,OAAO,MAAM,KAAK,WAAW,KAAK;AACxC,QAAI,CAAC,KAAM,QAAO;AAClB,UAAM,OAAO,KAAK,GAAG,KAAK,EAAE,YAAY,KAAK,CAAC;AAC9C,UAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,KAAK,KAAK,OAAO,SAAS,KAAK,SAAS;AAChF,UAAM,UAAU,KAAK,WAAW,OAAO,KAAK,KAAK,OAAO,EAAE,SAAS,KAAK,UAAU;AAClF,UAAM,YAAY,oBAAI,KAAK;AAC3B,UAAM,WAAW,KAAK,YAAY;AAClC,UAAM,iBAAiB,KAAK,kBAAkB;AAE9C,UAAM,aAAa,+BAA+B,IAAW;AAC7D,UAAM,YAAY,aACZ,MAAM,WAAW;AAAA,MACjB,EAAE,WAAW;AAAA,MACb;AAAA,QACE,cAAc,KAAK;AAAA,QACnB,YAAY,KAAK;AAAA,QACjB,YAAY,KAAK;AAAA,QACjB,YAAY;AAAA,QACZ,aAAa;AAAA,MACf;AAAA,MACA;AAAA,MACA;AAAA,IACF,IACA;AAEJ,UAAM,UAAU;AAAA,MACd,cAAc,WAAW,gBAAgB,KAAK;AAAA,MAC9C,YAAY,WAAW,cAAc,KAAK;AAAA,MAC1C,YAAY,WAAW,cAAc,KAAK;AAAA,MAC1C,YAAY,WAAW,cAAc;AAAA,MACrC,aAAa,WAAW,eAAe;AAAA,IACzC;AAEA,UAAM,OAAO,MAAM,KAAK,cAAc,EAAE;AAAA,MACtC;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA,KAAK,eAAe;AAAA,QACpB,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,oBAAoB,QAAQ,UAAU;AAAA,QACtC,oBAAoB,QAAQ,WAAW;AAAA,QACvC;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,UAAM,KAAK,OAAO,IAAI;AACtB,UAAM,KAAK,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,GAAG,MAAM,OAAO;AAC1E,QAAI,CAAC,GAAI,QAAO;AAChB,UAAM,QAAQ,KAAK,OAAO,WAAW;AAAA,MACnC;AAAA,MACA,UAAU,KAAK,YAAY;AAAA,MAC3B,gBAAgB,KAAK,kBAAkB;AAAA,MACvC,aAAa,KAAK,eAAe;AAAA,MACjC,cAAc,KAAK;AAAA,MACnB,YAAY,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,MACjB,YAAY;AAAA,MACZ,aAAa;AAAA,MACb;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEQ,eAAe,OAA+E;AACpG,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,QACL,UAAU;AAAA,QACV,gBAAgB;AAAA,QAChB,aAAa;AAAA,QACb,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,QAAQ;AAAA,QACR,SAAS;AAAA,MACX;AAAA,IACF;AACA,UAAMA,cAAa;AACnB,UAAM,iBAAiB,CAAC,UAAmB;AACzC,UAAI,OAAO,UAAU,YAAY,MAAM,WAAW,EAAG,QAAO;AAC5D,YAAM,YAAY,MAAM,WAAW,UAAU,IAAI,MAAM,MAAM,WAAW,MAAM,IAAI;AAClF,aAAOA,YAAW,KAAK,SAAS,IAAI,YAAY;AAAA,IAClD;AACA,UAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,IACrC,MAAM,OAAO,OAAO,CAAC,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,CAAC,IAC7E;AACJ,UAAM,UAAU,OAAO,MAAM,YAAY,YAAY,MAAM,YAAY,OACnE,MAAM,UACN;AACJ,WAAO;AAAA,MACL,UAAU,eAAe,MAAM,QAAQ;AAAA,MACvC,gBAAgB,eAAe,MAAM,cAAc;AAAA,MACnD,aAAa,eAAe,MAAM,WAAW;AAAA,MAC7C,cAAc,OAAO,MAAM,gBAAgB,SAAS;AAAA,MACpD,YAAY,OAAO,MAAM,cAAc,SAAS;AAAA,MAChD,YAAY,OAAO,MAAM,cAAc,SAAS;AAAA,MAChD;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,OAAoC;AAC7C,UAAM,SAAS,oBAAoB,MAAM;AAAA,MACvC,GAAG;AAAA,IACL,CAAC;AAED,UAAM,QAAgC,EAAE,WAAW,KAAK;AACxD,QAAI,OAAO,SAAU,OAAM,WAAW,OAAO;AAC7C,QAAI,OAAO,eAAgB,OAAM,iBAAiB,OAAO;AACzD,QAAI,OAAO,YAAa,OAAM,cAAc,OAAO;AACnD,QAAI,OAAO,aAAc,OAAM,eAAe,OAAO;AACrD,QAAI,OAAO,WAAY,OAAM,aAAa,OAAO;AACjD,QAAI,OAAO,OAAQ,OAAM,YAAY,EAAE,GAAI,MAAM,WAA+C,KAAK,OAAO,OAAO;AACnH,QAAI,OAAO,MAAO,OAAM,YAAY,EAAE,GAAI,MAAM,WAA+C,KAAK,OAAO,MAAM;AAEjH,UAAM,WAAW,OAAO,YAAY,OAAO,SAAS;AACpD,UAAM,OAAO,OAAO,QAAQ;AAC5B,UAAM,UAAU,OAAO,KAAK;AAE5B,UAAM,CAAC,OAAO,KAAK,IAAI,MAAM,KAAK,GAAG;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,QACE,SAAS,EAAE,WAAW,OAAO;AAAA,QAC7B,OAAO;AAAA,QACP;AAAA,MACF;AAAA,IACF;AAMA,eAAW,QAAQ,OAAO;AACxB,YAAM,gBAAiB,KAAkC;AACzD,UAAI,OAAO,kBAAkB,UAAU;AACrC,cAAMC,UAAS,yBAAyB,aAAa;AACrD,aAAK,aAAa,MAAM,QAAQA,OAAM,IAAKA,UAAsB;AAAA,MACnE;AACA,YAAM,iBAAkB,KAAmC;AAC3D,UAAI,OAAO,mBAAmB,UAAU;AACtC,cAAMA,UAAS,yBAAyB,cAAc;AACtD,aAAK,cAAcA,WAAU,OAAOA,YAAW,YAAY,CAAC,MAAM,QAAQA,OAAM,IAC3EA,UACD;AAAA,MACN;AAAA,IACF;AAEA,UAAM,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,MAAM,YAAY,EAAE,CAAC;AACxE,WAAO,EAAE,OAAO,OAAO,MAAM,UAAU,WAAW;AAAA,EACpD;AAAA,EAEA,MAAc,OAAO,MAAqB;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,qBAAqB,KAAK,kBAAkB,QAAQ,MAAM,gBAAgB,mBAAoB;AAClG,oBAAgB;AAChB,UAAM,aAAa,IAAI,KAAK,MAAM,iBAAiB;AACnD,UAAM,gBAAgB,IAAI,KAAK,MAAM,qBAAqB;AAC1D,QAAI;AACF,UAAI,oBAAoB,OAAO,GAAG;AAChC,cAAM,KAAK,aAAa,WAAW;AAAA,UACjC,cAAc,EAAE,KAAK,MAAM,KAAK,mBAAmB,EAAE;AAAA,UACrD,WAAW,EAAE,KAAK,WAAW;AAAA,QAC/B,CAAC;AAAA,MACH;AACA,YAAM,KAAK,aAAa,WAAW;AAAA,QACjC,cAAc,EAAE,MAAM,MAAM,KAAK,mBAAmB,EAAE;AAAA,QACtD,WAAW,EAAE,KAAK,cAAc;AAAA,MAClC,CAAC;AAAA,IACH,SAAS,KAAK;AAEZ,cAAQ,KAAK,6CAA6C,GAAG;AAAA,IAC/D;AAAA,EACF;AACF;",
6
6
  "names": ["UUID_REGEX", "parsed"]
7
7
  }
@@ -43,6 +43,13 @@ const sectionGroupSchema = z.object({
43
43
  items: z.array(sectionItemSchema)
44
44
  });
45
45
  const adminNavResponseSchema = z.object({
46
+ brand: z.object({
47
+ name: z.string().optional(),
48
+ logo: z.object({
49
+ src: z.string(),
50
+ alt: z.string().optional()
51
+ }).nullable().optional()
52
+ }).nullable().optional(),
46
53
  groups: z.array(
47
54
  z.object({
48
55
  id: z.string().optional(),
@@ -123,6 +130,8 @@ async function GET(req) {
123
130
  `nav:entities:${cacheScopeTenantId || "null"}`,
124
131
  `nav:locale:${locale}`,
125
132
  `nav:sidebar:user:${auth.sub}`,
133
+ cacheScopeTenantId ? `nav:sidebar:tenant:${cacheScopeTenantId}` : void 0,
134
+ cacheScopeOrganizationId ? `nav:sidebar:organization:${cacheScopeOrganizationId}` : void 0,
126
135
  `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || "null"}:${cacheScopeOrganizationId || "null"}:${locale}`,
127
136
  ...(Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)
128
137
  ].filter(Boolean);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/auth/api/admin/nav.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBackendRouteManifests } from '@open-mercato/shared/modules/registry'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { groupBackendRoutesByModule, resolveBackendChromePayload } from '../../lib/backendChrome'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{\n id?: string\n href: string\n title: string\n defaultTitle?: string\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string().optional(),\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string().optional(),\n enabled: z.boolean().optional(),\n hidden: z.boolean().optional(),\n pageContext: z.enum(['main', 'admin', 'settings', 'profile']).optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n }),\n)\n\nconst sectionItemSchema: z.ZodType<{\n id: string\n label: string\n labelKey?: string\n href: string\n order?: number\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n href: z.string(),\n order: z.number().optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sectionItemSchema).optional(),\n }),\n)\n\nconst sectionGroupSchema = z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n order: z.number().optional(),\n items: z.array(sectionItemSchema),\n})\n\nconst adminNavResponseSchema = z.object({\n groups: z.array(\n z.object({\n id: z.string().optional(),\n name: z.string(),\n defaultName: z.string().optional(),\n items: z.array(sidebarNavItemSchema),\n }),\n ),\n settingsSections: z.array(sectionGroupSchema),\n settingsPathPrefixes: z.array(z.string()),\n profileSections: z.array(sectionGroupSchema),\n profilePathPrefixes: z.array(z.string()),\n grantedFeatures: z.array(z.string()),\n roles: z.array(z.string()),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { translate, locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const cache = container.resolve('cache') as {\n get?: (key: string) => Promise<unknown>\n set?: (key: string, value: unknown, options?: { tags?: string[] }) => Promise<void>\n } | null\n let selectedOrganizationId: string | null | undefined\n let selectedTenantId: string | null | undefined\n try {\n const url = new URL(req.url)\n const orgParam = url.searchParams.get('orgId')\n const tenantParam = url.searchParams.get('tenantId')\n selectedOrganizationId = orgParam === null ? undefined : orgParam || null\n selectedTenantId = tenantParam === null ? undefined : tenantParam || null\n } catch {\n selectedOrganizationId = undefined\n selectedTenantId = undefined\n }\n\n let cacheScopeTenantId = auth.tenantId ?? null\n let cacheScopeOrganizationId = auth.orgId ?? null\n try {\n const { organizationId, scope } = await resolveFeatureCheckContext({\n container,\n auth,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n request: req,\n })\n cacheScopeOrganizationId = organizationId\n cacheScopeTenantId = scope.tenantId ?? auth.tenantId ?? null\n } catch {\n cacheScopeOrganizationId = auth.orgId ?? null\n cacheScopeTenantId = auth.tenantId ?? null\n selectedOrganizationId = auth.orgId ?? null\n selectedTenantId = auth.tenantId ?? null\n }\n\n const cacheVersion = 'v2'\n const cacheKey = `nav:sidebar:${cacheVersion}:${locale}:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}`\n try {\n if (cache?.get) {\n const cached = await cache.get(cacheKey)\n if (cached) return NextResponse.json(cached)\n }\n } catch {\n // ignore cache read failures\n }\n\n const payload = await resolveBackendChromePayload({\n auth,\n locale,\n modules: groupBackendRoutesByModule(getBackendRouteManifests()),\n translate: (key, fallback) => (key ? translate(key, fallback) : fallback),\n request: req,\n selectedOrganizationId,\n selectedTenantId,\n })\n\n try {\n if (cache?.set) {\n const tags = [\n `rbac:user:${auth.sub}`,\n cacheScopeTenantId ? `rbac:tenant:${cacheScopeTenantId}` : undefined,\n `nav:entities:${cacheScopeTenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${locale}`,\n ...((Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {\n // ignore cache write failures\n }\n\n return NextResponse.json(payload)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve backend chrome bootstrap payload',\n description:\n 'Returns the backend chrome payload available to the authenticated administrator after applying scope, RBAC, role defaults, and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Backend chrome payload', schema: adminNavResponseSchema },\n { status: 401, description: 'Unauthorized', schema: adminNavErrorSchema },\n ],\n },\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { z } from 'zod'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getBackendRouteManifests } from '@open-mercato/shared/modules/registry'\nimport { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { groupBackendRoutesByModule, resolveBackendChromePayload } from '../../lib/backendChrome'\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst sidebarNavItemSchema: z.ZodType<{\n id?: string\n href: string\n title: string\n defaultTitle?: string\n enabled?: boolean\n hidden?: boolean\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string().optional(),\n href: z.string(),\n title: z.string(),\n defaultTitle: z.string().optional(),\n enabled: z.boolean().optional(),\n hidden: z.boolean().optional(),\n pageContext: z.enum(['main', 'admin', 'settings', 'profile']).optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sidebarNavItemSchema).optional(),\n }),\n)\n\nconst sectionItemSchema: z.ZodType<{\n id: string\n label: string\n labelKey?: string\n href: string\n order?: number\n iconName?: string\n iconMarkup?: string\n children?: any[]\n}> = z.lazy(() =>\n z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n href: z.string(),\n order: z.number().optional(),\n iconName: z.string().optional(),\n iconMarkup: z.string().optional(),\n children: z.array(sectionItemSchema).optional(),\n }),\n)\n\nconst sectionGroupSchema = z.object({\n id: z.string(),\n label: z.string(),\n labelKey: z.string().optional(),\n order: z.number().optional(),\n items: z.array(sectionItemSchema),\n})\n\nconst adminNavResponseSchema = z.object({\n brand: z.object({\n name: z.string().optional(),\n logo: z.object({\n src: z.string(),\n alt: z.string().optional(),\n }).nullable().optional(),\n }).nullable().optional(),\n groups: z.array(\n z.object({\n id: z.string().optional(),\n name: z.string(),\n defaultName: z.string().optional(),\n items: z.array(sidebarNavItemSchema),\n }),\n ),\n settingsSections: z.array(sectionGroupSchema),\n settingsPathPrefixes: z.array(z.string()),\n profileSections: z.array(sectionGroupSchema),\n profilePathPrefixes: z.array(z.string()),\n grantedFeatures: z.array(z.string()),\n roles: z.array(z.string()),\n})\n\nconst adminNavErrorSchema = z.object({\n error: z.string(),\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { translate, locale } = await resolveTranslations()\n const container = await createRequestContainer()\n const cache = container.resolve('cache') as {\n get?: (key: string) => Promise<unknown>\n set?: (key: string, value: unknown, options?: { tags?: string[] }) => Promise<void>\n } | null\n let selectedOrganizationId: string | null | undefined\n let selectedTenantId: string | null | undefined\n try {\n const url = new URL(req.url)\n const orgParam = url.searchParams.get('orgId')\n const tenantParam = url.searchParams.get('tenantId')\n selectedOrganizationId = orgParam === null ? undefined : orgParam || null\n selectedTenantId = tenantParam === null ? undefined : tenantParam || null\n } catch {\n selectedOrganizationId = undefined\n selectedTenantId = undefined\n }\n\n let cacheScopeTenantId = auth.tenantId ?? null\n let cacheScopeOrganizationId = auth.orgId ?? null\n try {\n const { organizationId, scope } = await resolveFeatureCheckContext({\n container,\n auth,\n selectedId: selectedOrganizationId,\n tenantId: selectedTenantId,\n request: req,\n })\n cacheScopeOrganizationId = organizationId\n cacheScopeTenantId = scope.tenantId ?? auth.tenantId ?? null\n } catch {\n cacheScopeOrganizationId = auth.orgId ?? null\n cacheScopeTenantId = auth.tenantId ?? null\n selectedOrganizationId = auth.orgId ?? null\n selectedTenantId = auth.tenantId ?? null\n }\n\n const cacheVersion = 'v2'\n const cacheKey = `nav:sidebar:${cacheVersion}:${locale}:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}`\n try {\n if (cache?.get) {\n const cached = await cache.get(cacheKey)\n if (cached) return NextResponse.json(cached)\n }\n } catch {\n // ignore cache read failures\n }\n\n const payload = await resolveBackendChromePayload({\n auth,\n locale,\n modules: groupBackendRoutesByModule(getBackendRouteManifests()),\n translate: (key, fallback) => (key ? translate(key, fallback) : fallback),\n request: req,\n selectedOrganizationId,\n selectedTenantId,\n })\n\n try {\n if (cache?.set) {\n const tags = [\n `rbac:user:${auth.sub}`,\n cacheScopeTenantId ? `rbac:tenant:${cacheScopeTenantId}` : undefined,\n `nav:entities:${cacheScopeTenantId || 'null'}`,\n `nav:locale:${locale}`,\n `nav:sidebar:user:${auth.sub}`,\n cacheScopeTenantId ? `nav:sidebar:tenant:${cacheScopeTenantId}` : undefined,\n cacheScopeOrganizationId ? `nav:sidebar:organization:${cacheScopeOrganizationId}` : undefined,\n `nav:sidebar:scope:${auth.sub}:${cacheScopeTenantId || 'null'}:${cacheScopeOrganizationId || 'null'}:${locale}`,\n ...((Array.isArray(auth.roles) ? auth.roles : []).map((role) => `nav:sidebar:role:${role}`)),\n ].filter(Boolean) as string[]\n await cache.set(cacheKey, payload, { tags })\n }\n } catch {\n // ignore cache write failures\n }\n\n return NextResponse.json(payload)\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'Admin sidebar navigation',\n methods: {\n GET: {\n summary: 'Resolve backend chrome bootstrap payload',\n description:\n 'Returns the backend chrome payload available to the authenticated administrator after applying scope, RBAC, role defaults, and personal sidebar preferences.',\n responses: [\n { status: 200, description: 'Backend chrome payload', schema: adminNavResponseSchema },\n { status: 401, description: 'Unauthorized', schema: adminNavErrorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -92,21 +92,12 @@ async function POST(req) {
92
92
  user = await auth.findUserByEmailAndTenant(parsed.data.email, tenantId);
93
93
  } else {
94
94
  const users = await auth.findUsersByEmail(parsed.data.email);
95
- if (users.length > 1) {
96
- return NextResponse.json({
97
- ok: false,
98
- error: translate("auth.login.errors.tenantRequired", "Use the login link provided with your tenant activation to continue.")
99
- }, { status: 400 });
100
- }
101
- user = users[0] ?? null;
102
- }
103
- if (!user || !user.passwordHash) {
104
- void emitAuthEvent("auth.login.failed", { email: parsed.data.email, reason: "invalid_credentials" }).catch(() => void 0);
105
- return NextResponse.json({ ok: false, error: translate("auth.login.errors.invalidCredentials", "Invalid email or password") }, { status: 401 });
95
+ user = users.length === 1 ? users[0] : null;
106
96
  }
107
97
  const ok = await auth.verifyPassword(user, parsed.data.password);
108
- if (!ok) {
109
- void emitAuthEvent("auth.login.failed", { email: parsed.data.email, reason: "invalid_password" }).catch(() => void 0);
98
+ if (!user || !ok) {
99
+ const reason = user?.passwordHash ? "invalid_password" : "invalid_credentials";
100
+ void emitAuthEvent("auth.login.failed", { email: parsed.data.email, reason }).catch(() => void 0);
110
101
  return NextResponse.json({ ok: false, error: translate("auth.login.errors.invalidCredentials", "Invalid email or password") }, { status: 401 });
111
102
  }
112
103
  if (requiredRoles.length) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/api/login.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { userLoginSchema } from '@open-mercato/core/modules/auth/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { AuthService } from '@open-mercato/core/modules/auth/services/authService'\nimport { signJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EventBus } from '@open-mercato/events/types'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { emitAuthEvent } from '@open-mercato/core/modules/auth/events'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { checkAuthRateLimit, resetAuthRateLimit } from '@open-mercato/core/modules/auth/lib/rateLimitCheck'\nimport { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'\nimport { sanitizeRedirectPath } from '@open-mercato/core/modules/auth/lib/safeRedirect'\nimport { getAppBaseUrl } from '@open-mercato/shared/lib/url'\n\nconst loginRateLimitConfig = readEndpointRateLimitConfig('LOGIN', {\n points: 5, duration: 60, blockDuration: 60, keyPrefix: 'login',\n})\nconst loginIpRateLimitConfig = readEndpointRateLimitConfig('LOGIN_IP', {\n points: 20, duration: 60, blockDuration: 60, keyPrefix: 'login-ip',\n})\n\nexport const metadata = { requireAuth: false }\n\n// validation comes from userLoginSchema\n\ntype ParsedLoginForm = {\n email: string\n password: string\n remember: boolean\n tenantIdRaw: string\n requiredRoles: string[]\n redirectTo: string\n}\n\nfunction parseRequiredRoles(rawValue: string): string[] {\n return rawValue\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n}\n\nasync function parseLoginForm(req: Request): Promise<ParsedLoginForm> {\n const rawContentType = req.headers.get('content-type') ?? ''\n const contentType = rawContentType.split(';')[0].trim().toLowerCase()\n\n try {\n if (contentType === 'application/x-www-form-urlencoded') {\n const body = await req.text()\n const params = new URLSearchParams(body)\n const requireRoleRaw = String(params.get('requireRole') ?? params.get('role') ?? '').trim()\n return {\n email: String(params.get('email') ?? ''),\n password: String(params.get('password') ?? ''),\n remember: parseBooleanToken(params.get('remember')) === true,\n tenantIdRaw: String(params.get('tenantId') ?? params.get('tenant') ?? '').trim(),\n requiredRoles: requireRoleRaw ? parseRequiredRoles(requireRoleRaw) : [],\n redirectTo: String(params.get('redirect') ?? ''),\n }\n }\n\n const form = await req.formData()\n const requireRoleRaw = String(form.get('requireRole') ?? form.get('role') ?? '').trim()\n return {\n email: String(form.get('email') ?? ''),\n password: String(form.get('password') ?? ''),\n remember: parseBooleanToken(form.get('remember')?.toString()) === true,\n tenantIdRaw: String(form.get('tenantId') ?? form.get('tenant') ?? '').trim(),\n requiredRoles: requireRoleRaw ? parseRequiredRoles(requireRoleRaw) : [],\n redirectTo: String(form.get('redirect') ?? ''),\n }\n } catch {\n return {\n email: '',\n password: '',\n remember: false,\n tenantIdRaw: '',\n requiredRoles: [],\n redirectTo: '',\n }\n }\n}\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n const { email, password, remember, tenantIdRaw, requiredRoles, redirectTo } = await parseLoginForm(req)\n // Rate limit \u2014 two layers, both checked before validation and DB work\n const { error: rateLimitError, compoundKey: rateLimitCompoundKey } = await checkAuthRateLimit({\n req, ipConfig: loginIpRateLimitConfig, compoundConfig: loginRateLimitConfig, compoundIdentifier: email,\n })\n if (rateLimitError) return rateLimitError\n const parsed = userLoginSchema.pick({ email: true, password: true, tenantId: true }).safeParse({\n email,\n password,\n tenantId: tenantIdRaw || undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.invalidCredentials', 'Invalid credentials') }, { status: 400 })\n }\n const container = await createRequestContainer()\n const auth = (container.resolve('authService') as AuthService)\n const tenantId = parsed.data.tenantId ?? null\n let user = null\n if (tenantId) {\n user = await auth.findUserByEmailAndTenant(parsed.data.email, tenantId)\n } else {\n const users = await auth.findUsersByEmail(parsed.data.email)\n if (users.length > 1) {\n return NextResponse.json({\n ok: false,\n error: translate('auth.login.errors.tenantRequired', 'Use the login link provided with your tenant activation to continue.'),\n }, { status: 400 })\n }\n user = users[0] ?? null\n }\n if (!user || !user.passwordHash) {\n void emitAuthEvent('auth.login.failed', { email: parsed.data.email, reason: 'invalid_credentials' }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.invalidCredentials', 'Invalid email or password') }, { status: 401 })\n }\n const ok = await auth.verifyPassword(user, parsed.data.password)\n if (!ok) {\n void emitAuthEvent('auth.login.failed', { email: parsed.data.email, reason: 'invalid_password' }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.invalidCredentials', 'Invalid email or password') }, { status: 401 })\n }\n // Optional role requirement\n if (requiredRoles.length) {\n const userRoleNames = await auth.getUserRoles(user, tenantId ?? (user.tenantId ? String(user.tenantId) : null))\n const authorized = requiredRoles.some(r => userRoleNames.includes(r))\n if (!authorized) {\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.permissionDenied', 'Not authorized for this area') }, { status: 403 })\n }\n }\n await auth.updateLastLoginAt(user)\n // Reset rate limit counter on successful login so legitimate users aren't penalized for prior typos\n if (rateLimitCompoundKey) {\n await resetAuthRateLimit(rateLimitCompoundKey, loginRateLimitConfig)\n }\n const resolvedTenantId = tenantId ?? (user.tenantId ? String(user.tenantId) : null)\n const userRoleNames = await auth.getUserRoles(user, resolvedTenantId)\n try {\n const eventBus = (container.resolve('eventBus') as EventBus)\n void eventBus.emitEvent('query_index.coverage.warmup', {\n tenantId: resolvedTenantId,\n }).catch(() => undefined)\n } catch {\n // optional warmup\n }\n const rememberMeDays = Number(process.env.REMEMBER_ME_DAYS || '30')\n const accessTokenMaxAgeSeconds = 60 * 60 * 8\n const sessionExpiresAt = remember\n ? new Date(Date.now() + rememberMeDays * 24 * 60 * 60 * 1000)\n : new Date(Date.now() + accessTokenMaxAgeSeconds * 1000)\n const { session: loginSession, token: sessionRefreshToken } = await auth.createSession(user, sessionExpiresAt)\n const token = signJwt({\n sub: String(user.id),\n sid: String(loginSession.id),\n tenantId: resolvedTenantId,\n orgId: user.organizationId ? String(user.organizationId) : null,\n email: user.email,\n roles: userRoleNames\n })\n void emitAuthEvent('auth.login.success', { id: String(user.id), email: user.email, tenantId: resolvedTenantId, organizationId: user.organizationId ? String(user.organizationId) : null }).catch(() => undefined)\n const responseData: { ok: true; token: string; redirect: string; refreshToken?: string } = {\n ok: true,\n token,\n redirect: sanitizeRedirectPath(redirectTo, getAppBaseUrl(req), '/backend'),\n }\n if (remember) {\n responseData.refreshToken = sessionRefreshToken\n }\n const em = container.resolve('em')\n const interceptedResponse = await runCustomRouteAfterInterceptors({\n routePath: 'auth/login',\n method: 'POST',\n request: {\n method: 'POST',\n url: req.url,\n body: {\n email: parsed.data.email,\n tenantId: parsed.data.tenantId ?? undefined,\n remember,\n requireRole: requiredRoles.length > 0 ? requiredRoles : undefined,\n },\n headers: Object.fromEntries(req.headers.entries()),\n },\n response: {\n statusCode: 200,\n body: responseData,\n headers: {},\n },\n context: {\n em,\n container,\n },\n })\n if (!interceptedResponse.ok) {\n return NextResponse.json(interceptedResponse.body, { status: interceptedResponse.statusCode })\n }\n\n const interceptedBody = interceptedResponse.body\n const authTokenForCookie = typeof interceptedBody.token === 'string' && interceptedBody.token.length > 0\n ? interceptedBody.token\n : token\n const refreshTokenForCookie = typeof interceptedBody.refreshToken === 'string'\n ? interceptedBody.refreshToken\n : undefined\n\n const res = NextResponse.json(interceptedBody, { status: interceptedResponse.statusCode })\n res.cookies.set('auth_token', authTokenForCookie, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: accessTokenMaxAgeSeconds })\n if (remember && refreshTokenForCookie) {\n const expiresAt = new Date(Date.now() + rememberMeDays * 24 * 60 * 60 * 1000)\n res.cookies.set('session_token', refreshTokenForCookie, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', expires: expiresAt })\n } else if (!remember && authTokenForCookie === token) {\n res.cookies.set('session_token', sessionRefreshToken, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: accessTokenMaxAgeSeconds })\n }\n return res\n}\n\nconst loginRequestSchema = userLoginSchema.extend({\n password: z.string().min(6).describe('User password'),\n remember: z.enum(['on', '1', 'true']).optional().describe('Persist the session (submit `on`, `1`, or `true`).'),\n}).describe('Login form payload')\n\nconst loginSuccessSchema = z.object({\n ok: z.literal(true),\n token: z.string().describe('JWT token issued for subsequent API calls'),\n redirect: z.string().nullable().describe('Next location the client should navigate to'),\n refreshToken: z.string().optional().describe('Long-lived refresh token for obtaining new access tokens (only present when remember=true)'),\n})\n\nconst loginErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst loginMethodDoc: OpenApiMethodDoc = {\n summary: 'Authenticate user credentials',\n description: 'Validates the submitted credentials and issues a bearer token cookie for subsequent API calls.',\n tags: ['Authentication & Accounts'],\n requestBody: {\n contentType: 'application/x-www-form-urlencoded',\n schema: loginRequestSchema,\n description: 'Form-encoded payload captured from the login form.',\n },\n responses: [\n {\n status: 200,\n description: 'Authentication succeeded',\n schema: loginSuccessSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: loginErrorSchema },\n { status: 401, description: 'Invalid credentials', schema: loginErrorSchema },\n { status: 403, description: 'User lacks required role', schema: loginErrorSchema },\n { status: 429, description: 'Too many login attempts', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Authenticate user credentials',\n description: 'Accepts login form submissions and manages cookie/session issuance.',\n methods: {\n POST: loginMethodDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { userLoginSchema } from '@open-mercato/core/modules/auth/data/validators'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { AuthService } from '@open-mercato/core/modules/auth/services/authService'\nimport { signJwt } from '@open-mercato/shared/lib/auth/jwt'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { EventBus } from '@open-mercato/events/types'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { emitAuthEvent } from '@open-mercato/core/modules/auth/events'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport { readEndpointRateLimitConfig } from '@open-mercato/shared/lib/ratelimit/config'\nimport { checkAuthRateLimit, resetAuthRateLimit } from '@open-mercato/core/modules/auth/lib/rateLimitCheck'\nimport { runCustomRouteAfterInterceptors } from '@open-mercato/shared/lib/crud/custom-route-interceptor'\nimport { sanitizeRedirectPath } from '@open-mercato/core/modules/auth/lib/safeRedirect'\nimport { getAppBaseUrl } from '@open-mercato/shared/lib/url'\n\nconst loginRateLimitConfig = readEndpointRateLimitConfig('LOGIN', {\n points: 5, duration: 60, blockDuration: 60, keyPrefix: 'login',\n})\nconst loginIpRateLimitConfig = readEndpointRateLimitConfig('LOGIN_IP', {\n points: 20, duration: 60, blockDuration: 60, keyPrefix: 'login-ip',\n})\n\nexport const metadata = { requireAuth: false }\n\n// validation comes from userLoginSchema\n\ntype ParsedLoginForm = {\n email: string\n password: string\n remember: boolean\n tenantIdRaw: string\n requiredRoles: string[]\n redirectTo: string\n}\n\nfunction parseRequiredRoles(rawValue: string): string[] {\n return rawValue\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n}\n\nasync function parseLoginForm(req: Request): Promise<ParsedLoginForm> {\n const rawContentType = req.headers.get('content-type') ?? ''\n const contentType = rawContentType.split(';')[0].trim().toLowerCase()\n\n try {\n if (contentType === 'application/x-www-form-urlencoded') {\n const body = await req.text()\n const params = new URLSearchParams(body)\n const requireRoleRaw = String(params.get('requireRole') ?? params.get('role') ?? '').trim()\n return {\n email: String(params.get('email') ?? ''),\n password: String(params.get('password') ?? ''),\n remember: parseBooleanToken(params.get('remember')) === true,\n tenantIdRaw: String(params.get('tenantId') ?? params.get('tenant') ?? '').trim(),\n requiredRoles: requireRoleRaw ? parseRequiredRoles(requireRoleRaw) : [],\n redirectTo: String(params.get('redirect') ?? ''),\n }\n }\n\n const form = await req.formData()\n const requireRoleRaw = String(form.get('requireRole') ?? form.get('role') ?? '').trim()\n return {\n email: String(form.get('email') ?? ''),\n password: String(form.get('password') ?? ''),\n remember: parseBooleanToken(form.get('remember')?.toString()) === true,\n tenantIdRaw: String(form.get('tenantId') ?? form.get('tenant') ?? '').trim(),\n requiredRoles: requireRoleRaw ? parseRequiredRoles(requireRoleRaw) : [],\n redirectTo: String(form.get('redirect') ?? ''),\n }\n } catch {\n return {\n email: '',\n password: '',\n remember: false,\n tenantIdRaw: '',\n requiredRoles: [],\n redirectTo: '',\n }\n }\n}\n\nexport async function POST(req: Request) {\n const { translate } = await resolveTranslations()\n const { email, password, remember, tenantIdRaw, requiredRoles, redirectTo } = await parseLoginForm(req)\n // Rate limit \u2014 two layers, both checked before validation and DB work\n const { error: rateLimitError, compoundKey: rateLimitCompoundKey } = await checkAuthRateLimit({\n req, ipConfig: loginIpRateLimitConfig, compoundConfig: loginRateLimitConfig, compoundIdentifier: email,\n })\n if (rateLimitError) return rateLimitError\n const parsed = userLoginSchema.pick({ email: true, password: true, tenantId: true }).safeParse({\n email,\n password,\n tenantId: tenantIdRaw || undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.invalidCredentials', 'Invalid credentials') }, { status: 400 })\n }\n const container = await createRequestContainer()\n const auth = (container.resolve('authService') as AuthService)\n const tenantId = parsed.data.tenantId ?? null\n let user = null\n if (tenantId) {\n user = await auth.findUserByEmailAndTenant(parsed.data.email, tenantId)\n } else {\n const users = await auth.findUsersByEmail(parsed.data.email)\n // Never disclose that an email is registered across multiple tenants \u2014 a\n // password-independent 400-vs-401 response is an account/topology oracle\n // (issue #2242). Treat an ambiguous match as no resolvable user and fall\n // through to the uniform invalid-credentials path; tenant-selection\n // guidance is delivered out-of-band via the activation/login link.\n user = users.length === 1 ? users[0] : null\n }\n // Always verify the password \u2014 verifyPassword runs a constant-time bcrypt\n // comparison even when the user is missing or has no hash \u2014 so unknown-email,\n // wrong-password, and multi-tenant cases return an identical 401 with\n // identical latency.\n const ok = await auth.verifyPassword(user, parsed.data.password)\n if (!user || !ok) {\n const reason = user?.passwordHash ? 'invalid_password' : 'invalid_credentials'\n void emitAuthEvent('auth.login.failed', { email: parsed.data.email, reason }).catch(() => undefined)\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.invalidCredentials', 'Invalid email or password') }, { status: 401 })\n }\n // Optional role requirement\n if (requiredRoles.length) {\n const userRoleNames = await auth.getUserRoles(user, tenantId ?? (user.tenantId ? String(user.tenantId) : null))\n const authorized = requiredRoles.some(r => userRoleNames.includes(r))\n if (!authorized) {\n return NextResponse.json({ ok: false, error: translate('auth.login.errors.permissionDenied', 'Not authorized for this area') }, { status: 403 })\n }\n }\n await auth.updateLastLoginAt(user)\n // Reset rate limit counter on successful login so legitimate users aren't penalized for prior typos\n if (rateLimitCompoundKey) {\n await resetAuthRateLimit(rateLimitCompoundKey, loginRateLimitConfig)\n }\n const resolvedTenantId = tenantId ?? (user.tenantId ? String(user.tenantId) : null)\n const userRoleNames = await auth.getUserRoles(user, resolvedTenantId)\n try {\n const eventBus = (container.resolve('eventBus') as EventBus)\n void eventBus.emitEvent('query_index.coverage.warmup', {\n tenantId: resolvedTenantId,\n }).catch(() => undefined)\n } catch {\n // optional warmup\n }\n const rememberMeDays = Number(process.env.REMEMBER_ME_DAYS || '30')\n const accessTokenMaxAgeSeconds = 60 * 60 * 8\n const sessionExpiresAt = remember\n ? new Date(Date.now() + rememberMeDays * 24 * 60 * 60 * 1000)\n : new Date(Date.now() + accessTokenMaxAgeSeconds * 1000)\n const { session: loginSession, token: sessionRefreshToken } = await auth.createSession(user, sessionExpiresAt)\n const token = signJwt({\n sub: String(user.id),\n sid: String(loginSession.id),\n tenantId: resolvedTenantId,\n orgId: user.organizationId ? String(user.organizationId) : null,\n email: user.email,\n roles: userRoleNames\n })\n void emitAuthEvent('auth.login.success', { id: String(user.id), email: user.email, tenantId: resolvedTenantId, organizationId: user.organizationId ? String(user.organizationId) : null }).catch(() => undefined)\n const responseData: { ok: true; token: string; redirect: string; refreshToken?: string } = {\n ok: true,\n token,\n redirect: sanitizeRedirectPath(redirectTo, getAppBaseUrl(req), '/backend'),\n }\n if (remember) {\n responseData.refreshToken = sessionRefreshToken\n }\n const em = container.resolve('em')\n const interceptedResponse = await runCustomRouteAfterInterceptors({\n routePath: 'auth/login',\n method: 'POST',\n request: {\n method: 'POST',\n url: req.url,\n body: {\n email: parsed.data.email,\n tenantId: parsed.data.tenantId ?? undefined,\n remember,\n requireRole: requiredRoles.length > 0 ? requiredRoles : undefined,\n },\n headers: Object.fromEntries(req.headers.entries()),\n },\n response: {\n statusCode: 200,\n body: responseData,\n headers: {},\n },\n context: {\n em,\n container,\n },\n })\n if (!interceptedResponse.ok) {\n return NextResponse.json(interceptedResponse.body, { status: interceptedResponse.statusCode })\n }\n\n const interceptedBody = interceptedResponse.body\n const authTokenForCookie = typeof interceptedBody.token === 'string' && interceptedBody.token.length > 0\n ? interceptedBody.token\n : token\n const refreshTokenForCookie = typeof interceptedBody.refreshToken === 'string'\n ? interceptedBody.refreshToken\n : undefined\n\n const res = NextResponse.json(interceptedBody, { status: interceptedResponse.statusCode })\n res.cookies.set('auth_token', authTokenForCookie, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: accessTokenMaxAgeSeconds })\n if (remember && refreshTokenForCookie) {\n const expiresAt = new Date(Date.now() + rememberMeDays * 24 * 60 * 60 * 1000)\n res.cookies.set('session_token', refreshTokenForCookie, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', expires: expiresAt })\n } else if (!remember && authTokenForCookie === token) {\n res.cookies.set('session_token', sessionRefreshToken, { httpOnly: true, path: '/', sameSite: 'lax', secure: process.env.NODE_ENV === 'production', maxAge: accessTokenMaxAgeSeconds })\n }\n return res\n}\n\nconst loginRequestSchema = userLoginSchema.extend({\n password: z.string().min(6).describe('User password'),\n remember: z.enum(['on', '1', 'true']).optional().describe('Persist the session (submit `on`, `1`, or `true`).'),\n}).describe('Login form payload')\n\nconst loginSuccessSchema = z.object({\n ok: z.literal(true),\n token: z.string().describe('JWT token issued for subsequent API calls'),\n redirect: z.string().nullable().describe('Next location the client should navigate to'),\n refreshToken: z.string().optional().describe('Long-lived refresh token for obtaining new access tokens (only present when remember=true)'),\n})\n\nconst loginErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst loginMethodDoc: OpenApiMethodDoc = {\n summary: 'Authenticate user credentials',\n description: 'Validates the submitted credentials and issues a bearer token cookie for subsequent API calls.',\n tags: ['Authentication & Accounts'],\n requestBody: {\n contentType: 'application/x-www-form-urlencoded',\n schema: loginRequestSchema,\n description: 'Form-encoded payload captured from the login form.',\n },\n responses: [\n {\n status: 200,\n description: 'Authentication succeeded',\n schema: loginSuccessSchema,\n },\n ],\n errors: [\n { status: 400, description: 'Validation failed', schema: loginErrorSchema },\n { status: 401, description: 'Invalid credentials', schema: loginErrorSchema },\n { status: 403, description: 'User lacks required role', schema: loginErrorSchema },\n { status: 429, description: 'Too many login attempts', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Authenticate user credentials',\n description: 'Accepts login form submissions and manages cookie/session issuance.',\n methods: {\n POST: loginMethodDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["requireRoleRaw", "userRoleNames"]
7
7
  }