@open-mercato/core 0.6.7-develop.6785.1.1dd7cfac55 → 0.6.7-develop.6795.1.8a3f27921c

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 (196) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities.ids.generated.js +1 -0
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/modules/auth/backend/settings/page.js +14 -9
  5. package/dist/modules/auth/backend/settings/page.js.map +2 -2
  6. package/dist/modules/customers/cli.js +17 -7
  7. package/dist/modules/customers/cli.js.map +2 -2
  8. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +229 -216
  9. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  10. package/dist/modules/customers/search.js +50 -29
  11. package/dist/modules/customers/search.js.map +2 -2
  12. package/dist/modules/data_sync/api/mappings/[id]/route.js +22 -9
  13. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  14. package/dist/modules/data_sync/api/mappings/route.js +13 -4
  15. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  16. package/dist/modules/data_sync/api/options.js +7 -2
  17. package/dist/modules/data_sync/api/options.js.map +2 -2
  18. package/dist/modules/data_sync/api/run.js +7 -2
  19. package/dist/modules/data_sync/api/run.js.map +2 -2
  20. package/dist/modules/data_sync/api/runs/[id]/cancel.js +8 -3
  21. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  22. package/dist/modules/data_sync/api/runs/[id]/retry.js +7 -2
  23. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  24. package/dist/modules/data_sync/api/runs/[id]/route.js +8 -3
  25. package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
  26. package/dist/modules/data_sync/api/runs.js +7 -2
  27. package/dist/modules/data_sync/api/runs.js.map +2 -2
  28. package/dist/modules/data_sync/api/schedules/[id]/route.js +19 -6
  29. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  30. package/dist/modules/data_sync/api/schedules/route.js +13 -4
  31. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  32. package/dist/modules/data_sync/api/validate.js +9 -4
  33. package/dist/modules/data_sync/api/validate.js.map +2 -2
  34. package/dist/modules/design_system/acl.js +9 -0
  35. package/dist/modules/design_system/acl.js.map +7 -0
  36. package/dist/modules/design_system/backend/design-system/page.js +11 -0
  37. package/dist/modules/design_system/backend/design-system/page.js.map +7 -0
  38. package/dist/modules/design_system/backend/design-system/page.meta.js +15 -0
  39. package/dist/modules/design_system/backend/design-system/page.meta.js.map +7 -0
  40. package/dist/modules/design_system/gallery/components/CodeSnippet.js +52 -0
  41. package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +7 -0
  42. package/dist/modules/design_system/gallery/components/EntryCard.js +63 -0
  43. package/dist/modules/design_system/gallery/components/EntryCard.js.map +7 -0
  44. package/dist/modules/design_system/gallery/components/GalleryShell.js +163 -0
  45. package/dist/modules/design_system/gallery/components/GalleryShell.js.map +7 -0
  46. package/dist/modules/design_system/gallery/components/VariantPreview.js +9 -0
  47. package/dist/modules/design_system/gallery/components/VariantPreview.js.map +7 -0
  48. package/dist/modules/design_system/gallery/entries/banners.js +186 -0
  49. package/dist/modules/design_system/gallery/entries/banners.js.map +7 -0
  50. package/dist/modules/design_system/gallery/entries/buttons.js +361 -0
  51. package/dist/modules/design_system/gallery/entries/buttons.js.map +7 -0
  52. package/dist/modules/design_system/gallery/entries/charts.js +457 -0
  53. package/dist/modules/design_system/gallery/entries/charts.js.map +7 -0
  54. package/dist/modules/design_system/gallery/entries/dates.js +306 -0
  55. package/dist/modules/design_system/gallery/entries/dates.js.map +7 -0
  56. package/dist/modules/design_system/gallery/entries/detail.js +358 -0
  57. package/dist/modules/design_system/gallery/entries/detail.js.map +7 -0
  58. package/dist/modules/design_system/gallery/entries/display.js +785 -0
  59. package/dist/modules/design_system/gallery/entries/display.js.map +7 -0
  60. package/dist/modules/design_system/gallery/entries/feedback.js +658 -0
  61. package/dist/modules/design_system/gallery/entries/feedback.js.map +7 -0
  62. package/dist/modules/design_system/gallery/entries/filters.js +505 -0
  63. package/dist/modules/design_system/gallery/entries/filters.js.map +7 -0
  64. package/dist/modules/design_system/gallery/entries/foundations.js +453 -0
  65. package/dist/modules/design_system/gallery/entries/foundations.js.map +7 -0
  66. package/dist/modules/design_system/gallery/entries/icons.js +140 -0
  67. package/dist/modules/design_system/gallery/entries/icons.js.map +7 -0
  68. package/dist/modules/design_system/gallery/entries/inputs.js +1217 -0
  69. package/dist/modules/design_system/gallery/entries/inputs.js.map +7 -0
  70. package/dist/modules/design_system/gallery/entries/messages.js +334 -0
  71. package/dist/modules/design_system/gallery/entries/messages.js.map +7 -0
  72. package/dist/modules/design_system/gallery/entries/navigation.js +475 -0
  73. package/dist/modules/design_system/gallery/entries/navigation.js.map +7 -0
  74. package/dist/modules/design_system/gallery/entries/notifications.js +257 -0
  75. package/dist/modules/design_system/gallery/entries/notifications.js.map +7 -0
  76. package/dist/modules/design_system/gallery/entries/overlays.js +576 -0
  77. package/dist/modules/design_system/gallery/entries/overlays.js.map +7 -0
  78. package/dist/modules/design_system/gallery/entries/scaffolding.js +378 -0
  79. package/dist/modules/design_system/gallery/entries/scaffolding.js.map +7 -0
  80. package/dist/modules/design_system/gallery/entries/schedule.js +297 -0
  81. package/dist/modules/design_system/gallery/entries/schedule.js.map +7 -0
  82. package/dist/modules/design_system/gallery/registry.js +139 -0
  83. package/dist/modules/design_system/gallery/registry.js.map +7 -0
  84. package/dist/modules/design_system/gallery/types.js +1 -0
  85. package/dist/modules/design_system/gallery/types.js.map +7 -0
  86. package/dist/modules/design_system/index.js +9 -0
  87. package/dist/modules/design_system/index.js.map +7 -0
  88. package/dist/modules/design_system/setup.js +14 -0
  89. package/dist/modules/design_system/setup.js.map +7 -0
  90. package/dist/modules/inbox_ops/search.js +15 -4
  91. package/dist/modules/inbox_ops/search.js.map +2 -2
  92. package/dist/modules/integrations/api/[id]/credentials/route.js +11 -5
  93. package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
  94. package/dist/modules/integrations/api/[id]/health/route.js +6 -3
  95. package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
  96. package/dist/modules/integrations/api/[id]/route.js +6 -3
  97. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  98. package/dist/modules/integrations/api/[id]/state/route.js +6 -3
  99. package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
  100. package/dist/modules/integrations/api/[id]/version/route.js +6 -3
  101. package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
  102. package/dist/modules/integrations/api/logs/route.js +6 -3
  103. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  104. package/dist/modules/integrations/api/route.js +6 -3
  105. package/dist/modules/integrations/api/route.js.map +2 -2
  106. package/dist/modules/integrations/api/umes-read.js +2 -2
  107. package/dist/modules/integrations/api/umes-read.js.map +2 -2
  108. package/dist/modules/integrations/lib/organization-scope.js +2 -9
  109. package/dist/modules/integrations/lib/organization-scope.js.map +2 -2
  110. package/dist/modules/messages/search.js +10 -5
  111. package/dist/modules/messages/search.js.map +2 -2
  112. package/dist/modules/query_index/data/entities.js +2 -1
  113. package/dist/modules/query_index/data/entities.js.map +2 -2
  114. package/dist/modules/query_index/lib/engine.js +32 -50
  115. package/dist/modules/query_index/lib/engine.js.map +2 -2
  116. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js +17 -0
  117. package/dist/modules/query_index/migrations/Migration20260731105052_query_index.js.map +7 -0
  118. package/generated/entities.ids.generated.ts +1 -0
  119. package/package.json +16 -16
  120. package/src/modules/auth/backend/settings/page.tsx +17 -8
  121. package/src/modules/customers/cli.ts +17 -7
  122. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +24 -13
  123. package/src/modules/customers/i18n/de.json +17 -0
  124. package/src/modules/customers/i18n/en.json +17 -0
  125. package/src/modules/customers/i18n/es.json +17 -0
  126. package/src/modules/customers/i18n/pl.json +17 -0
  127. package/src/modules/customers/search.ts +59 -27
  128. package/src/modules/data_sync/AGENTS.md +1 -0
  129. package/src/modules/data_sync/api/mappings/[id]/route.ts +22 -9
  130. package/src/modules/data_sync/api/mappings/route.ts +13 -4
  131. package/src/modules/data_sync/api/options.ts +7 -2
  132. package/src/modules/data_sync/api/run.ts +7 -2
  133. package/src/modules/data_sync/api/runs/[id]/cancel.ts +8 -3
  134. package/src/modules/data_sync/api/runs/[id]/retry.ts +7 -2
  135. package/src/modules/data_sync/api/runs/[id]/route.ts +8 -3
  136. package/src/modules/data_sync/api/runs.ts +7 -2
  137. package/src/modules/data_sync/api/schedules/[id]/route.ts +19 -6
  138. package/src/modules/data_sync/api/schedules/route.ts +13 -4
  139. package/src/modules/data_sync/api/validate.ts +9 -4
  140. package/src/modules/design_system/AGENTS.md +34 -0
  141. package/src/modules/design_system/acl.ts +5 -0
  142. package/src/modules/design_system/backend/design-system/page.meta.ts +11 -0
  143. package/src/modules/design_system/backend/design-system/page.tsx +14 -0
  144. package/src/modules/design_system/gallery/components/CodeSnippet.tsx +55 -0
  145. package/src/modules/design_system/gallery/components/EntryCard.tsx +95 -0
  146. package/src/modules/design_system/gallery/components/GalleryShell.tsx +206 -0
  147. package/src/modules/design_system/gallery/components/VariantPreview.tsx +15 -0
  148. package/src/modules/design_system/gallery/entries/banners.tsx +226 -0
  149. package/src/modules/design_system/gallery/entries/buttons.tsx +422 -0
  150. package/src/modules/design_system/gallery/entries/charts.tsx +518 -0
  151. package/src/modules/design_system/gallery/entries/dates.tsx +325 -0
  152. package/src/modules/design_system/gallery/entries/detail.tsx +403 -0
  153. package/src/modules/design_system/gallery/entries/display.tsx +893 -0
  154. package/src/modules/design_system/gallery/entries/feedback.tsx +746 -0
  155. package/src/modules/design_system/gallery/entries/filters.tsx +570 -0
  156. package/src/modules/design_system/gallery/entries/foundations.tsx +529 -0
  157. package/src/modules/design_system/gallery/entries/icons.tsx +168 -0
  158. package/src/modules/design_system/gallery/entries/inputs.tsx +1516 -0
  159. package/src/modules/design_system/gallery/entries/messages.tsx +339 -0
  160. package/src/modules/design_system/gallery/entries/navigation.tsx +573 -0
  161. package/src/modules/design_system/gallery/entries/notifications.tsx +285 -0
  162. package/src/modules/design_system/gallery/entries/overlays.tsx +677 -0
  163. package/src/modules/design_system/gallery/entries/scaffolding.tsx +430 -0
  164. package/src/modules/design_system/gallery/entries/schedule.tsx +307 -0
  165. package/src/modules/design_system/gallery/registry.ts +152 -0
  166. package/src/modules/design_system/gallery/types.ts +32 -0
  167. package/src/modules/design_system/i18n/.hardcoded-allowlist.json +71 -0
  168. package/src/modules/design_system/i18n/de.json +46 -0
  169. package/src/modules/design_system/i18n/en.json +46 -0
  170. package/src/modules/design_system/i18n/es.json +46 -0
  171. package/src/modules/design_system/i18n/pl.json +46 -0
  172. package/src/modules/design_system/index.ts +7 -0
  173. package/src/modules/design_system/setup.ts +12 -0
  174. package/src/modules/inbox_ops/i18n/de.json +3 -0
  175. package/src/modules/inbox_ops/i18n/en.json +3 -0
  176. package/src/modules/inbox_ops/i18n/es.json +3 -0
  177. package/src/modules/inbox_ops/i18n/pl.json +3 -0
  178. package/src/modules/inbox_ops/search.ts +20 -4
  179. package/src/modules/integrations/api/[id]/credentials/route.ts +11 -5
  180. package/src/modules/integrations/api/[id]/health/route.ts +6 -3
  181. package/src/modules/integrations/api/[id]/route.ts +6 -3
  182. package/src/modules/integrations/api/[id]/state/route.ts +6 -3
  183. package/src/modules/integrations/api/[id]/version/route.ts +6 -3
  184. package/src/modules/integrations/api/logs/route.ts +6 -3
  185. package/src/modules/integrations/api/route.ts +6 -3
  186. package/src/modules/integrations/api/umes-read.ts +2 -2
  187. package/src/modules/integrations/lib/organization-scope.ts +5 -25
  188. package/src/modules/messages/i18n/de.json +2 -0
  189. package/src/modules/messages/i18n/en.json +2 -0
  190. package/src/modules/messages/i18n/es.json +2 -0
  191. package/src/modules/messages/i18n/pl.json +2 -0
  192. package/src/modules/messages/search.ts +11 -5
  193. package/src/modules/query_index/data/entities.ts +7 -0
  194. package/src/modules/query_index/lib/engine.ts +40 -63
  195. package/src/modules/query_index/migrations/.snapshot-open-mercato.json +12 -0
  196. package/src/modules/query_index/migrations/Migration20260731105052_query_index.ts +35 -0
@@ -6,6 +6,8 @@ import type {
6
6
  SearchResultLink,
7
7
  SearchIndexSource,
8
8
  } from '@open-mercato/shared/modules/search'
9
+ import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
10
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
11
  import { CUSTOMER_INTERACTION_TASK_SOURCE, EXAMPLE_TODO_SOURCE } from './lib/interactionCompatibility'
10
12
  import { createLogger } from '@open-mercato/shared/lib/logger'
11
13
 
@@ -480,6 +482,7 @@ function ensureFallbackLines(lines: string[], record: Record<string, unknown>, o
480
482
  // =============================================================================
481
483
 
482
484
  function resolvePersonPresenter(
485
+ translate: TranslateFn,
483
486
  record: Record<string, unknown>,
484
487
  entity: Record<string, unknown> | null,
485
488
  customFields: Record<string, unknown>,
@@ -495,7 +498,7 @@ function resolvePersonPresenter(
495
498
  (nameParts.length ? nameParts : undefined) ??
496
499
  fallbackEntityId ??
497
500
  (record.id as string | undefined) ??
498
- 'Person'
501
+ translate('customers.search.fallback.person', 'Person')
499
502
  const subtitlePieces: string[] = []
500
503
  const jobTitle = record.job_title ?? record.jobTitle ?? customFields.job_title ?? customFields.jobTitle
501
504
  if (jobTitle) subtitlePieces.push(String(jobTitle))
@@ -515,11 +518,14 @@ function resolvePersonPresenter(
515
518
  title: String(title),
516
519
  subtitle: subtitlePieces.length ? subtitlePieces.join(' · ') : undefined,
517
520
  icon: 'user',
518
- badge: pickValue(entity, 'display_name', 'displayName') ? 'Person' : undefined,
521
+ badge: pickValue(entity, 'display_name', 'displayName')
522
+ ? translate('customers.search.badge.person', 'Person')
523
+ : undefined,
519
524
  }
520
525
  }
521
526
 
522
527
  function resolveCompanyPresenter(
528
+ translate: TranslateFn,
523
529
  record: Record<string, unknown>,
524
530
  entity: Record<string, unknown> | null,
525
531
  customFields: Record<string, unknown>,
@@ -537,7 +543,7 @@ function resolveCompanyPresenter(
537
543
  (entity?.id && entity?.display_name ? entity.display_name as string : undefined) ??
538
544
  fallbackEntityId ??
539
545
  (record.id as string | undefined) ??
540
- 'Company'
546
+ translate('customers.search.fallback.company', 'Company')
541
547
  const subtitlePieces: string[] = []
542
548
  const industry = record.industry
543
549
  if (industry) subtitlePieces.push(String(industry))
@@ -567,7 +573,9 @@ function resolveCompanyPresenter(
567
573
  title: String(title),
568
574
  subtitle: subtitlePieces.length ? subtitlePieces.join(' · ') : undefined,
569
575
  icon: 'building',
570
- badge: pickValue(entity, 'display_name', 'displayName') ? 'Company' : undefined,
576
+ badge: pickValue(entity, 'display_name', 'displayName')
577
+ ? translate('customers.search.badge.company', 'Company')
578
+ : undefined,
571
579
  }
572
580
  }
573
581
 
@@ -605,6 +613,7 @@ export const searchConfig: SearchModuleConfig = {
605
613
 
606
614
  buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
607
615
  assertTenantContext(ctx)
616
+ const { t } = await resolveTranslations()
608
617
  const lines: string[] = []
609
618
  const record = ctx.record
610
619
  appendLine(lines, 'Preferred name', record.preferred_name ?? record.preferredName ?? ctx.customFields.preferred_name)
@@ -643,9 +652,9 @@ export const searchConfig: SearchModuleConfig = {
643
652
  })
644
653
  }
645
654
 
646
- const presenter = resolvePersonPresenter(record, entity, ctx.customFields)
655
+ const presenter = resolvePersonPresenter(t, record, entity, ctx.customFields)
647
656
  logMissingPresenterTitle('person', record, entity, presenter)
648
- const presenterLabel = pickLabel(presenter.title) ?? 'Open person'
657
+ const presenterLabel = pickLabel(presenter.title) ?? t('customers.search.link.openPerson', 'Open person')
649
658
  const links: SearchResultLink[] = []
650
659
  if (entityId) {
651
660
  const href = buildCustomerUrl('person', entityId)
@@ -669,8 +678,9 @@ export const searchConfig: SearchModuleConfig = {
669
678
 
670
679
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
671
680
  assertTenantContext(ctx)
681
+ const { t } = await resolveTranslations()
672
682
  const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))
673
- return resolvePersonPresenter(ctx.record, entity, ctx.customFields)
683
+ return resolvePersonPresenter(t, ctx.record, entity, ctx.customFields)
674
684
  },
675
685
 
676
686
  resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {
@@ -679,11 +689,12 @@ export const searchConfig: SearchModuleConfig = {
679
689
  },
680
690
 
681
691
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
692
+ const { t } = await resolveTranslations()
682
693
  const entityId = resolveCustomerEntityId(ctx.record)
683
694
  if (!entityId) return null
684
695
  const href = buildCustomerUrl('person', entityId)
685
696
  if (!href) return null
686
- return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]
697
+ return [{ href: `${href}/edit`, label: t('customers.search.link.edit', 'Edit'), kind: 'secondary' }]
687
698
  },
688
699
 
689
700
  fieldPolicy: {
@@ -714,6 +725,7 @@ export const searchConfig: SearchModuleConfig = {
714
725
 
715
726
  buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
716
727
  assertTenantContext(ctx)
728
+ const { t } = await resolveTranslations()
717
729
  const lines: string[] = []
718
730
  const record = ctx.record
719
731
  appendLine(lines, 'Legal name', record.legal_name ?? record.legalName ?? ctx.customFields.legal_name)
@@ -734,9 +746,9 @@ export const searchConfig: SearchModuleConfig = {
734
746
  ensureFallbackLines(lines, record)
735
747
  if (!lines.length) return null
736
748
 
737
- const presenter = resolveCompanyPresenter(record, entity, ctx.customFields)
749
+ const presenter = resolveCompanyPresenter(t, record, entity, ctx.customFields)
738
750
  logMissingPresenterTitle('company', record, entity, presenter)
739
- const primaryLabel = pickLabel(presenter.title) ?? 'Open company'
751
+ const primaryLabel = pickLabel(presenter.title) ?? t('customers.search.link.openCompany', 'Open company')
740
752
  const links: SearchResultLink[] = []
741
753
  if (entityId) {
742
754
  const href = buildCustomerUrl('company', entityId)
@@ -760,8 +772,9 @@ export const searchConfig: SearchModuleConfig = {
760
772
 
761
773
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
762
774
  assertTenantContext(ctx)
775
+ const { t } = await resolveTranslations()
763
776
  const entity = await getCustomerEntity(ctx, resolveCustomerEntityId(ctx.record))
764
- return resolveCompanyPresenter(ctx.record, entity, ctx.customFields)
777
+ return resolveCompanyPresenter(t, ctx.record, entity, ctx.customFields)
765
778
  },
766
779
 
767
780
  resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {
@@ -770,11 +783,12 @@ export const searchConfig: SearchModuleConfig = {
770
783
  },
771
784
 
772
785
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
786
+ const { t } = await resolveTranslations()
773
787
  const entityId = resolveCustomerEntityId(ctx.record)
774
788
  if (!entityId) return null
775
789
  const href = buildCustomerUrl('company', entityId)
776
790
  if (!href) return null
777
- return [{ href: `${href}/edit`, label: 'Edit', kind: 'secondary' }]
791
+ return [{ href: `${href}/edit`, label: t('customers.search.link.edit', 'Edit'), kind: 'secondary' }]
778
792
  },
779
793
 
780
794
  fieldPolicy: {
@@ -832,8 +846,9 @@ export const searchConfig: SearchModuleConfig = {
832
846
 
833
847
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
834
848
  assertTenantContext(ctx)
849
+ const { t } = await resolveTranslations()
835
850
  const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)
836
- const title = (parent?.display_name as string | undefined) ?? 'Customer note'
851
+ const title = (parent?.display_name as string | undefined) ?? t('customers.search.fallback.customerNote', 'Customer note')
837
852
  return {
838
853
  title,
839
854
  subtitle: snippet(ctx.record.body),
@@ -850,15 +865,16 @@ export const searchConfig: SearchModuleConfig = {
850
865
 
851
866
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
852
867
  assertTenantContext(ctx)
868
+ const { t } = await resolveTranslations()
853
869
  const links: SearchResultLink[] = []
854
870
  const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)
855
871
  const parentUrl = buildCustomerUrl(parent?.kind as string ?? null, (parent?.id ?? ctx.record.entity_id ?? ctx.record.entityId) as string)
856
872
  if (parentUrl) {
857
- links.push({ href: parentUrl, label: (parent?.display_name as string | undefined) ?? 'View customer', kind: 'primary' })
873
+ links.push({ href: parentUrl, label: (parent?.display_name as string | undefined) ?? t('customers.search.link.viewCustomer', 'View customer'), kind: 'primary' })
858
874
  }
859
875
  if (ctx.record.deal_id) {
860
876
  const dealUrl = `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`
861
- links.push({ href: dealUrl, label: 'Open deal', kind: 'secondary' })
877
+ links.push({ href: dealUrl, label: t('customers.search.link.openDeal', 'Open deal'), kind: 'secondary' })
862
878
  }
863
879
  return links.length ? links : null
864
880
  },
@@ -880,6 +896,7 @@ export const searchConfig: SearchModuleConfig = {
880
896
  priority: 8,
881
897
 
882
898
  buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
899
+ const { t } = await resolveTranslations()
883
900
  const lines: string[] = []
884
901
  const record = ctx.record
885
902
  appendLine(lines, 'Title', record.title)
@@ -898,10 +915,10 @@ export const searchConfig: SearchModuleConfig = {
898
915
  return {
899
916
  text: lines,
900
917
  presenter: {
901
- title: String(record.title ?? 'Deal'),
918
+ title: String(record.title ?? t('customers.search.fallback.deal', 'Deal')),
902
919
  subtitle: subtitleParts.join(' · ') || undefined,
903
920
  icon: 'briefcase',
904
- badge: 'Deal',
921
+ badge: t('customers.search.badge.deal', 'Deal'),
905
922
  },
906
923
  checksumSource: {
907
924
  title: record.title,
@@ -913,8 +930,9 @@ export const searchConfig: SearchModuleConfig = {
913
930
  },
914
931
 
915
932
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
933
+ const { t } = await resolveTranslations()
916
934
  const { record } = ctx
917
- const title = pickString(record.title as string, 'Deal')
935
+ const title = pickString(record.title as string, t('customers.search.fallback.deal', 'Deal'))
918
936
  const subtitleParts: string[] = []
919
937
  if (record.pipeline_stage) subtitleParts.push(String(record.pipeline_stage))
920
938
  if (record.status) subtitleParts.push(String(record.status))
@@ -925,10 +943,10 @@ export const searchConfig: SearchModuleConfig = {
925
943
  }
926
944
 
927
945
  return {
928
- title: title ?? 'Deal',
946
+ title: title ?? t('customers.search.fallback.deal', 'Deal'),
929
947
  subtitle: subtitleParts.length ? subtitleParts.join(' · ') : undefined,
930
948
  icon: 'briefcase',
931
- badge: 'Deal',
949
+ badge: t('customers.search.badge.deal', 'Deal'),
932
950
  }
933
951
  },
934
952
 
@@ -939,12 +957,13 @@ export const searchConfig: SearchModuleConfig = {
939
957
  },
940
958
 
941
959
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
960
+ const { t } = await resolveTranslations()
942
961
  const id = ctx.record.id
943
962
  if (!id) return null
944
963
  return [
945
964
  {
946
965
  href: `/backend/customers/deals/${encodeURIComponent(String(id))}/edit`,
947
- label: 'Edit',
966
+ label: t('customers.search.link.edit', 'Edit'),
948
967
  kind: 'secondary',
949
968
  },
950
969
  ]
@@ -968,6 +987,7 @@ export const searchConfig: SearchModuleConfig = {
968
987
 
969
988
  buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
970
989
  assertTenantContext(ctx)
990
+ const { t } = await resolveTranslations()
971
991
  const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)
972
992
  const lines: string[] = []
973
993
  if (parent?.display_name) lines.push(`Customer: ${parent.display_name}`)
@@ -976,7 +996,11 @@ export const searchConfig: SearchModuleConfig = {
976
996
  if (ctx.record.body) lines.push(`Body: ${ctx.record.body}`)
977
997
 
978
998
  const presenter: SearchResultPresenter = {
979
- title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,
999
+ title: ctx.record.subject
1000
+ ? String(ctx.record.subject)
1001
+ : t('customers.search.fallback.activity', 'Activity: {{type}}', {
1002
+ type: String(ctx.record.activity_type ?? t('customers.search.fallback.activityUpdate', 'update')),
1003
+ }),
980
1004
  subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),
981
1005
  icon: 'bolt',
982
1006
  }
@@ -995,12 +1019,17 @@ export const searchConfig: SearchModuleConfig = {
995
1019
 
996
1020
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
997
1021
  assertTenantContext(ctx)
1022
+ const { t } = await resolveTranslations()
998
1023
  const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)
999
1024
  return {
1000
- title: ctx.record.subject ? String(ctx.record.subject) : `Activity: ${ctx.record.activity_type ?? 'update'}`,
1025
+ title: ctx.record.subject
1026
+ ? String(ctx.record.subject)
1027
+ : t('customers.search.fallback.activity', 'Activity: {{type}}', {
1028
+ type: String(ctx.record.activity_type ?? t('customers.search.fallback.activityUpdate', 'update')),
1029
+ }),
1001
1030
  subtitle: (parent?.display_name as string | undefined) ?? snippet(ctx.record.body),
1002
1031
  icon: 'bolt',
1003
- badge: 'Activity',
1032
+ badge: t('customers.search.badge.activity', 'Activity'),
1004
1033
  }
1005
1034
  },
1006
1035
 
@@ -1012,11 +1041,12 @@ export const searchConfig: SearchModuleConfig = {
1012
1041
  },
1013
1042
 
1014
1043
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
1044
+ const { t } = await resolveTranslations()
1015
1045
  const links: SearchResultLink[] = []
1016
1046
  if (ctx.record.deal_id) {
1017
1047
  links.push({
1018
1048
  href: `/backend/customers/deals/${encodeURIComponent(ctx.record.deal_id as string)}`,
1019
- label: 'Open deal',
1049
+ label: t('customers.search.link.openDeal', 'Open deal'),
1020
1050
  kind: 'secondary',
1021
1051
  })
1022
1052
  }
@@ -1064,10 +1094,11 @@ export const searchConfig: SearchModuleConfig = {
1064
1094
 
1065
1095
  formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
1066
1096
  assertTenantContext(ctx)
1097
+ const { t } = await resolveTranslations()
1067
1098
  const todo = await getLinkedTodo(ctx) as Record<string, unknown> | null
1068
1099
  const parent = await getCustomerEntity(ctx, ctx.record.entity_id as string ?? ctx.record.entityId as string)
1069
1100
  return {
1070
- title: (todo?.title as string | undefined) ?? 'Customer task',
1101
+ title: (todo?.title as string | undefined) ?? t('customers.search.fallback.customerTask', 'Customer task'),
1071
1102
  subtitle: parent?.display_name as string | undefined,
1072
1103
  icon: 'check-square',
1073
1104
  }
@@ -1081,11 +1112,12 @@ export const searchConfig: SearchModuleConfig = {
1081
1112
  },
1082
1113
 
1083
1114
  resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
1115
+ const { t } = await resolveTranslations()
1084
1116
  const todoId = ctx.record.todo_id ?? ctx.record.todoId
1085
1117
  if (!todoId) return null
1086
1118
  return [{
1087
1119
  href: `/backend/todos/${encodeURIComponent(todoId as string)}/edit`,
1088
- label: 'Open todo',
1120
+ label: t('customers.search.link.openTodo', 'Open todo'),
1089
1121
  kind: 'secondary',
1090
1122
  }]
1091
1123
  },
@@ -9,6 +9,7 @@ The `data_sync` module provides a streaming data synchronization hub for import/
9
9
  ## Always
10
10
 
11
11
  - **Always scope by organizationId + tenantId** — every entity query
12
+ - **Resolve the organization in API routes with `resolveActiveOrganizationId(auth)`** from `@open-mercato/shared/lib/auth/organizationScope`, never with raw `auth.orgId` — a super-admin viewing "all organizations" has `auth.orgId === null`. The fallback is tenant-aware: it never pairs the actor organization with a foreign selected tenant. When it returns `null` for an authenticated caller, answer with `organizationScopeRequiredResponse()` (400) — never 401, which sends `apiFetch` into a session-refresh loop
12
13
  - **Use the queue system** — never run syncs inline in API handlers
13
14
  - **New sync providers MUST support provider-owned env preconfiguration** when fresh installs need credentials or default mappings/locales/channels from deployment env
14
15
  - **Persist cursor after each batch** — enables resume on failure
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
6
  import type { EntityManager } from '@mikro-orm/postgresql'
6
7
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -36,9 +37,13 @@ async function resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: s
36
37
 
37
38
  export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
38
39
  const auth = await getAuthFromRequest(req)
39
- if (!auth?.tenantId || !auth.orgId) {
40
+ if (!auth?.tenantId) {
40
41
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
41
42
  }
43
+ const organizationId = resolveActiveOrganizationId(auth)
44
+ if (!organizationId) {
45
+ return organizationScopeRequiredResponse()
46
+ }
42
47
 
43
48
  const parsedParams = await resolveParams(ctx)
44
49
  if (!parsedParams.success) {
@@ -47,14 +52,14 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
47
52
 
48
53
  const container = await createRequestContainer()
49
54
  const em = container.resolve('em') as EntityManager
50
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
55
+ const scope = { organizationId, tenantId: auth.tenantId }
51
56
 
52
57
  const mapping = await findOneWithDecryption(
53
58
  em,
54
59
  SyncMapping,
55
60
  {
56
61
  id: parsedParams.data.id,
57
- organizationId: auth.orgId as string,
62
+ organizationId,
58
63
  tenantId: auth.tenantId,
59
64
  },
60
65
  undefined,
@@ -77,9 +82,13 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
77
82
 
78
83
  export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
79
84
  const auth = await getAuthFromRequest(req)
80
- if (!auth?.tenantId || !auth.orgId) {
85
+ if (!auth?.tenantId) {
81
86
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
82
87
  }
88
+ const organizationId = resolveActiveOrganizationId(auth)
89
+ if (!organizationId) {
90
+ return organizationScopeRequiredResponse()
91
+ }
83
92
 
84
93
  const parsedParams = await resolveParams(ctx)
85
94
  if (!parsedParams.success) {
@@ -94,14 +103,14 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
94
103
 
95
104
  const container = await createRequestContainer()
96
105
  const em = container.resolve('em') as EntityManager
97
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
106
+ const scope = { organizationId, tenantId: auth.tenantId }
98
107
 
99
108
  const mapping = await findOneWithDecryption(
100
109
  em,
101
110
  SyncMapping,
102
111
  {
103
112
  id: parsedParams.data.id,
104
- organizationId: auth.orgId as string,
113
+ organizationId,
105
114
  tenantId: auth.tenantId,
106
115
  },
107
116
  undefined,
@@ -155,9 +164,13 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
155
164
 
156
165
  export async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
157
166
  const auth = await getAuthFromRequest(req)
158
- if (!auth?.tenantId || !auth.orgId) {
167
+ if (!auth?.tenantId) {
159
168
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
160
169
  }
170
+ const organizationId = resolveActiveOrganizationId(auth)
171
+ if (!organizationId) {
172
+ return organizationScopeRequiredResponse()
173
+ }
161
174
 
162
175
  const parsedParams = await resolveParams(ctx)
163
176
  if (!parsedParams.success) {
@@ -166,14 +179,14 @@ export async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string
166
179
 
167
180
  const container = await createRequestContainer()
168
181
  const em = container.resolve('em') as EntityManager
169
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
182
+ const scope = { organizationId, tenantId: auth.tenantId }
170
183
 
171
184
  const mapping = await findOneWithDecryption(
172
185
  em,
173
186
  SyncMapping,
174
187
  {
175
188
  id: parsedParams.data.id,
176
- organizationId: auth.orgId as string,
189
+ organizationId,
177
190
  tenantId: auth.tenantId,
178
191
  },
179
192
  undefined,
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
6
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
6
7
  import { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
@@ -35,9 +36,13 @@ export const openApi = {
35
36
 
36
37
  export async function GET(req: Request) {
37
38
  const auth = await getAuthFromRequest(req)
38
- if (!auth?.tenantId || !auth.orgId) {
39
+ if (!auth?.tenantId) {
39
40
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
40
41
  }
42
+ const organizationId = resolveActiveOrganizationId(auth)
43
+ if (!organizationId) {
44
+ return organizationScopeRequiredResponse()
45
+ }
41
46
 
42
47
  const url = new URL(req.url)
43
48
  const parsed = listMappingsQuerySchema.safeParse({
@@ -53,7 +58,7 @@ export async function GET(req: Request) {
53
58
 
54
59
  const container = await createRequestContainer()
55
60
  const em = container.resolve('em') as EntityManager
56
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
61
+ const scope = { organizationId, tenantId: auth.tenantId }
57
62
 
58
63
  const where: FilterQuery<SyncMapping> = {
59
64
  organizationId: scope.organizationId,
@@ -92,9 +97,13 @@ export async function GET(req: Request) {
92
97
 
93
98
  export async function POST(req: Request) {
94
99
  const auth = await getAuthFromRequest(req)
95
- if (!auth?.tenantId || !auth.orgId) {
100
+ if (!auth?.tenantId) {
96
101
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
97
102
  }
103
+ const organizationId = resolveActiveOrganizationId(auth)
104
+ if (!organizationId) {
105
+ return organizationScopeRequiredResponse()
106
+ }
98
107
 
99
108
  const payload = await req.json().catch(() => null)
100
109
  const parsed = createMappingSchema.safeParse(payload)
@@ -104,7 +113,7 @@ export async function POST(req: Request) {
104
113
 
105
114
  const container = await createRequestContainer()
106
115
  const em = container.resolve('em') as EntityManager
107
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
116
+ const scope = { organizationId, tenantId: auth.tenantId }
108
117
 
109
118
  const existing = await findOneWithDecryption(
110
119
  em,
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
5
  import { getAllIntegrations } from '@open-mercato/shared/modules/integrations/types'
5
6
  import type { CredentialsService } from '../../integrations/lib/credentials-service'
@@ -17,14 +18,18 @@ export const openApi = {
17
18
 
18
19
  export async function GET(req: Request) {
19
20
  const auth = await getAuthFromRequest(req)
20
- if (!auth?.tenantId || !auth.orgId) {
21
+ if (!auth?.tenantId) {
21
22
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
22
23
  }
24
+ const organizationId = resolveActiveOrganizationId(auth)
25
+ if (!organizationId) {
26
+ return organizationScopeRequiredResponse()
27
+ }
23
28
 
24
29
  const container = await createRequestContainer()
25
30
  const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
26
31
  const stateService = container.resolve('integrationStateService') as IntegrationStateService
27
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
32
+ const scope = { organizationId, tenantId: auth.tenantId }
28
33
 
29
34
  const items = await Promise.all(
30
35
  getAllIntegrations()
@@ -1,5 +1,6 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
5
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
6
  import { getIntegration } from '@open-mercato/shared/modules/integrations/types'
@@ -29,9 +30,13 @@ export const openApi = {
29
30
  export async function POST(req: Request) {
30
31
  try {
31
32
  const auth = await getAuthFromRequest(req)
32
- if (!auth?.tenantId || !auth.orgId) {
33
+ if (!auth?.tenantId) {
33
34
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
34
35
  }
36
+ const organizationId = resolveActiveOrganizationId(auth)
37
+ if (!organizationId) {
38
+ return organizationScopeRequiredResponse()
39
+ }
35
40
 
36
41
  const payload = await readJsonSafe(req)
37
42
  const parsed = runSyncSchema.safeParse(payload)
@@ -45,7 +50,7 @@ export async function POST(req: Request) {
45
50
  const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService
46
51
 
47
52
  const scope = {
48
- organizationId: auth.orgId as string,
53
+ organizationId,
49
54
  tenantId: auth.tenantId,
50
55
  }
51
56
 
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
6
  import type { IntegrationLogService } from '../../../../integrations/lib/log-service'
6
7
  import type { IntegrationStateService } from '../../../../integrations/lib/state-service'
@@ -24,9 +25,13 @@ export const openApi = {
24
25
 
25
26
  export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
26
27
  const auth = await getAuthFromRequest(req)
27
- if (!auth?.tenantId || !auth.orgId) {
28
+ if (!auth?.tenantId) {
28
29
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
29
30
  }
31
+ const organizationId = resolveActiveOrganizationId(auth)
32
+ if (!organizationId) {
33
+ return organizationScopeRequiredResponse()
34
+ }
30
35
 
31
36
  const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
32
37
  ? await (ctx.params as Promise<{ id?: string }>)
@@ -42,7 +47,7 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
42
47
  const progressService = container.resolve('progressService') as ProgressService
43
48
  const integrationLogService = container.resolve('integrationLogService') as IntegrationLogService
44
49
  const integrationStateService = container.resolve('integrationStateService') as IntegrationStateService
45
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
50
+ const scope = { organizationId, tenantId: auth.tenantId }
46
51
 
47
52
  const run = await syncRunService.getRun(parsed.data.id, scope)
48
53
  if (!run) {
@@ -69,7 +74,7 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
69
74
 
70
75
  const progressCtx = {
71
76
  tenantId: auth.tenantId,
72
- organizationId: auth.orgId,
77
+ organizationId,
73
78
  userId: auth.sub,
74
79
  }
75
80
 
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
6
  import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
6
7
  import type { ProgressService } from '../../../../progress/lib/progressService'
@@ -25,9 +26,13 @@ export const openApi = {
25
26
 
26
27
  export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
27
28
  const auth = await getAuthFromRequest(req)
28
- if (!auth?.tenantId || !auth.orgId) {
29
+ if (!auth?.tenantId) {
29
30
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
30
31
  }
32
+ const organizationId = resolveActiveOrganizationId(auth)
33
+ if (!organizationId) {
34
+ return organizationScopeRequiredResponse()
35
+ }
31
36
 
32
37
  const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
33
38
  ? await (ctx.params as Promise<{ id?: string }>)
@@ -47,7 +52,7 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
47
52
  const container = await createRequestContainer()
48
53
  const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
49
54
  const progressService = container.resolve('progressService') as ProgressService
50
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
55
+ const scope = { organizationId, tenantId: auth.tenantId }
51
56
 
52
57
  const previous = await syncRunService.getRun(parsedParams.data.id, scope)
53
58
  if (!previous) {
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { z } from 'zod'
3
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { organizationScopeRequiredResponse, resolveActiveOrganizationId } from '@open-mercato/shared/lib/auth/organizationScope'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
6
  import type { ProgressService } from '../../../../progress/lib/progressService'
6
7
  import type { SyncRunService } from '../../../lib/sync-run-service'
@@ -18,9 +19,13 @@ export const openApi = {
18
19
 
19
20
  export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
20
21
  const auth = await getAuthFromRequest(req)
21
- if (!auth?.tenantId || !auth.orgId) {
22
+ if (!auth?.tenantId) {
22
23
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
23
24
  }
25
+ const organizationId = resolveActiveOrganizationId(auth)
26
+ if (!organizationId) {
27
+ return organizationScopeRequiredResponse()
28
+ }
24
29
 
25
30
  const rawParams = (ctx.params && typeof (ctx.params as Promise<unknown>).then === 'function')
26
31
  ? await (ctx.params as Promise<{ id?: string }>)
@@ -34,7 +39,7 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
34
39
  const container = await createRequestContainer()
35
40
  const syncRunService = container.resolve('dataSyncRunService') as SyncRunService
36
41
  const progressService = container.resolve('progressService') as ProgressService
37
- const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
42
+ const scope = { organizationId, tenantId: auth.tenantId }
38
43
 
39
44
  const run = await syncRunService.getRun(parsed.data.id, scope)
40
45
  if (!run) {
@@ -44,7 +49,7 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
44
49
  const progressJob = run.progressJobId
45
50
  ? await progressService.getJob(run.progressJobId, {
46
51
  tenantId: auth.tenantId,
47
- organizationId: auth.orgId,
52
+ organizationId,
48
53
  userId: auth.sub,
49
54
  })
50
55
  : null