@open-mercato/core 0.9.0 → 0.9.1-develop.7462.1.94c09f158f
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/attachments/api/route.js +29 -3
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/lib/ocrLimits.js +49 -0
- package/dist/modules/attachments/lib/ocrLimits.js.map +7 -0
- package/dist/modules/attachments/lib/ocrQueue.js +76 -2
- package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
- package/dist/modules/attachments/lib/ocrService.js +4 -0
- package/dist/modules/attachments/lib/ocrService.js.map +2 -2
- package/dist/modules/attachments/lib/pdfProcessing.js +14 -2
- package/dist/modules/attachments/lib/pdfProcessing.js.map +2 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js +4 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/textExtraction.js +13 -1
- package/dist/modules/attachments/lib/textExtraction.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/search.js +3 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +2 -1
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +10 -7
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-result.js +12 -0
- package/dist/modules/communication_channels/lib/oauth-result.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/attachments/api/route.ts +36 -3
- package/src/modules/attachments/lib/ocrLimits.ts +62 -0
- package/src/modules/attachments/lib/ocrQueue.ts +95 -2
- package/src/modules/attachments/lib/ocrService.ts +4 -0
- package/src/modules/attachments/lib/pdfProcessing.ts +14 -2
- package/src/modules/attachments/lib/scoped-upload-service.ts +4 -2
- package/src/modules/attachments/lib/textExtraction.ts +14 -1
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/catalog/search.ts +5 -1
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +2 -1
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +12 -9
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -1
- package/src/modules/communication_channels/i18n/en.json +4 -1
- package/src/modules/communication_channels/i18n/es.json +4 -1
- package/src/modules/communication_channels/i18n/ko.json +4 -1
- package/src/modules/communication_channels/i18n/pl.json +4 -1
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-result.ts +13 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -131,10 +131,14 @@ function buildProductPresenter(
|
|
|
131
131
|
) ?? label
|
|
132
132
|
const isActive = record.is_active ?? record.isActive
|
|
133
133
|
const statusText = isActive === false ? translate('catalog.search.status.inactive', 'Inactive') : null
|
|
134
|
+
const productType = readRecordText(record, 'product_type', 'productType')
|
|
135
|
+
const productTypeLabel = productType
|
|
136
|
+
? translate(`catalog.products.types.${productType}`, productType)
|
|
137
|
+
: null
|
|
134
138
|
const subtitle = formatSubtitle(
|
|
135
139
|
readRecordText(record, 'subtitle'),
|
|
136
140
|
readRecordText(record, 'sku'),
|
|
137
|
-
|
|
141
|
+
productTypeLabel,
|
|
138
142
|
statusText,
|
|
139
143
|
) ?? snippet(readRecordText(record, 'description'))
|
|
140
144
|
return { title, subtitle, icon: 'package', badge: label }
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
verifyOAuthState,
|
|
16
16
|
type OAuthStateConsumeStore,
|
|
17
17
|
} from '../../../../../lib/oauth-state'
|
|
18
|
+
import { OAUTH_RESULT_QUERY_PARAM } from '../../../../../lib/oauth-result'
|
|
18
19
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
19
20
|
|
|
20
21
|
const logger = createLogger('communication_channels').child({ component: 'oauth-callback' })
|
|
@@ -51,7 +52,7 @@ function redirectWithFlash(
|
|
|
51
52
|
normalizeOAuthReturnUrl(returnUrl, DEFAULT_OAUTH_RETURN_URL),
|
|
52
53
|
getAppBaseUrl(req),
|
|
53
54
|
)
|
|
54
|
-
base.searchParams.set(
|
|
55
|
+
base.searchParams.set(OAUTH_RESULT_QUERY_PARAM, flash.type)
|
|
55
56
|
if (flash.code) base.searchParams.set('code', flash.code)
|
|
56
57
|
if (flash.provider) base.searchParams.set('provider', flash.provider)
|
|
57
58
|
if (flash.channelId) base.searchParams.set('channelId', flash.channelId)
|
|
@@ -33,16 +33,14 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
33
33
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const json = await readJsonSafe(req, null)
|
|
39
|
-
body = bodySchema.parse(json)
|
|
40
|
-
} catch (err) {
|
|
36
|
+
const parsed = bodySchema.safeParse(await readJsonSafe(req, null))
|
|
37
|
+
if (!parsed.success) {
|
|
41
38
|
return NextResponse.json(
|
|
42
|
-
{ error:
|
|
39
|
+
{ error: 'Invalid request body', details: parsed.error.issues },
|
|
43
40
|
{ status: 422 },
|
|
44
41
|
)
|
|
45
42
|
}
|
|
43
|
+
const body = parsed.data
|
|
46
44
|
|
|
47
45
|
const container = await createRequestContainer()
|
|
48
46
|
|
package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts
CHANGED
|
@@ -33,16 +33,14 @@ export async function POST(req: Request): Promise<Response> {
|
|
|
33
33
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const json = await readJsonSafe(req, null)
|
|
39
|
-
body = bodySchema.parse(json)
|
|
40
|
-
} catch (err) {
|
|
36
|
+
const parsed = bodySchema.safeParse(await readJsonSafe(req, null))
|
|
37
|
+
if (!parsed.success) {
|
|
41
38
|
return NextResponse.json(
|
|
42
|
-
{ error:
|
|
39
|
+
{ error: 'Invalid request body', details: parsed.error.issues },
|
|
43
40
|
{ status: 422 },
|
|
44
41
|
)
|
|
45
42
|
}
|
|
43
|
+
const body = parsed.data
|
|
46
44
|
|
|
47
45
|
const container = await createRequestContainer()
|
|
48
46
|
|
|
@@ -4,6 +4,8 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
4
4
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
5
5
|
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
6
6
|
import { toAbsoluteUrl } from '@open-mercato/shared/lib/url'
|
|
7
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
8
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
7
9
|
import { getChannelAdapter } from '../../../../../lib/adapter-registry-singleton'
|
|
8
10
|
import { resolveOAuthClientCredentials } from '../../../../../lib/oauth-client-config'
|
|
9
11
|
import {
|
|
@@ -16,6 +18,8 @@ import {
|
|
|
16
18
|
OAuthStateError,
|
|
17
19
|
} from '../../../../../lib/oauth-state'
|
|
18
20
|
|
|
21
|
+
const logger = createLogger('communication_channels').child({ component: 'oauth-initiate' })
|
|
22
|
+
|
|
19
23
|
export const metadata = {
|
|
20
24
|
path: '/communication_channels/oauth/[provider]/initiate',
|
|
21
25
|
POST: {
|
|
@@ -56,6 +60,21 @@ function defaultRedirectUri(req: Request, providerKey: string): string {
|
|
|
56
60
|
return toAbsoluteUrl(req, `/api/communication_channels/oauth/${providerKey}/callback`)
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
function oauthStateErrorResponse(err: unknown): Response | null {
|
|
64
|
+
if (err instanceof OAuthStateError) {
|
|
65
|
+
if (err.code === 'missing_secret') {
|
|
66
|
+
logger.error('OAuth state secret is not configured', { err })
|
|
67
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
68
|
+
module: 'communication_channels',
|
|
69
|
+
code: 'communication_channels.missing_secret',
|
|
70
|
+
})
|
|
71
|
+
return NextResponse.json({ error: 'OAuth is not available', code: err.code }, { status: 500 })
|
|
72
|
+
}
|
|
73
|
+
return NextResponse.json({ error: err.message, code: err.code }, { status: 500 })
|
|
74
|
+
}
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
export async function POST(req: Request, context: RouteContext): Promise<Response> {
|
|
60
79
|
const { provider } = await context.params
|
|
61
80
|
if (!/^[a-z0-9_-]+$/i.test(provider)) {
|
|
@@ -121,13 +140,20 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
const redirectUri = defaultRedirectUri(req, provider)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
143
|
+
let stateEnvelope: ReturnType<typeof createOAuthState>
|
|
144
|
+
try {
|
|
145
|
+
stateEnvelope = createOAuthState({
|
|
146
|
+
userId: auth.sub as string,
|
|
147
|
+
tenantId: auth.tenantId as string,
|
|
148
|
+
organizationId: (auth as { orgId?: string | null }).orgId ?? null,
|
|
149
|
+
providerKey: provider,
|
|
150
|
+
returnUrl: normalizeOAuthReturnUrl(body.returnUrl, DEFAULT_OAUTH_RETURN_URL),
|
|
151
|
+
})
|
|
152
|
+
} catch (err) {
|
|
153
|
+
const response = oauthStateErrorResponse(err)
|
|
154
|
+
if (response) return response
|
|
155
|
+
throw err
|
|
156
|
+
}
|
|
131
157
|
|
|
132
158
|
let result
|
|
133
159
|
try {
|
|
@@ -153,12 +179,21 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
153
179
|
|
|
154
180
|
// If the adapter packed extras (PKCE verifier, scopes), bake them into the
|
|
155
181
|
// state cookie now so the callback handler can pass them to exchangeOAuthCode.
|
|
156
|
-
|
|
157
|
-
|
|
182
|
+
let finalCookie: string
|
|
183
|
+
if (result.extra) {
|
|
184
|
+
try {
|
|
185
|
+
finalCookie = (await import('../../../../../lib/oauth-state')).encryptOAuthState({
|
|
158
186
|
...stateEnvelope.payload,
|
|
159
187
|
extra: { ...(stateEnvelope.payload.extra ?? {}), ...result.extra },
|
|
160
188
|
})
|
|
161
|
-
|
|
189
|
+
} catch (err) {
|
|
190
|
+
const response = oauthStateErrorResponse(err)
|
|
191
|
+
if (response) return response
|
|
192
|
+
throw err
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
finalCookie = stateEnvelope.cookie
|
|
196
|
+
}
|
|
162
197
|
|
|
163
198
|
const response = NextResponse.json({ authorizeUrl: result.authorizeUrl })
|
|
164
199
|
response.cookies.set({
|
|
@@ -184,6 +219,7 @@ export const openApi = {
|
|
|
184
219
|
{ status: 400, description: 'Invalid provider or unsupported (no OAuth)' },
|
|
185
220
|
{ status: 401, description: 'Unauthorized' },
|
|
186
221
|
{ status: 422, description: 'Invalid request body' },
|
|
222
|
+
{ status: 500, description: 'OAuth state secret missing or state encryption failed' },
|
|
187
223
|
{ status: 502, description: 'Adapter failed to build authorize URL' },
|
|
188
224
|
],
|
|
189
225
|
},
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { extensionPoints } from '@open-mercato/core/modules/communication_channels/extension-points'
|
|
5
5
|
import { getImportHistoryLimits } from '@open-mercato/core/modules/communication_channels/lib/import-history-limits'
|
|
6
|
+
import { OAUTH_RESULT_QUERY_PARAM, stripOAuthResultParams } from '@open-mercato/core/modules/communication_channels/lib/oauth-result'
|
|
6
7
|
import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
|
|
7
8
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
8
9
|
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
@@ -76,7 +77,7 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
76
77
|
const t = useT()
|
|
77
78
|
const router = useRouter()
|
|
78
79
|
const searchParams = useSearchParams()
|
|
79
|
-
const flashType = searchParams?.get(
|
|
80
|
+
const flashType = searchParams?.get(OAUTH_RESULT_QUERY_PARAM)
|
|
80
81
|
const flashCode = searchParams?.get('code')
|
|
81
82
|
const flashProvider = searchParams?.get('provider')
|
|
82
83
|
|
|
@@ -96,10 +97,13 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
96
97
|
|
|
97
98
|
React.useEffect(() => {
|
|
98
99
|
if (flashType === 'connected') {
|
|
100
|
+
const providerLabel = flashProvider
|
|
101
|
+
? t(`communication_channels.channel.providers.${flashProvider}`, '')
|
|
102
|
+
: ''
|
|
99
103
|
flash(
|
|
100
|
-
|
|
104
|
+
providerLabel
|
|
101
105
|
? t('communication_channels.profile.flash.connectedWithProvider', 'Channel connected ({provider}).', {
|
|
102
|
-
provider:
|
|
106
|
+
provider: providerLabel,
|
|
103
107
|
})
|
|
104
108
|
: t('communication_channels.profile.flash.connected', 'Channel connected.'),
|
|
105
109
|
'success',
|
|
@@ -126,15 +130,14 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
126
130
|
'communication_channels.profile.flash.stateStoreUnavailable',
|
|
127
131
|
'A server error prevented completing the connection. Please try again in a moment.',
|
|
128
132
|
)
|
|
129
|
-
:
|
|
130
|
-
? t('communication_channels.profile.flash.errorWithCode', 'Failed to connect channel — {code}.', {
|
|
131
|
-
code: flashCode,
|
|
132
|
-
})
|
|
133
|
-
: t('communication_channels.profile.flash.error', 'Failed to connect channel.'),
|
|
133
|
+
: t('communication_channels.profile.flash.error', 'Failed to connect channel.'),
|
|
134
134
|
'error',
|
|
135
135
|
)
|
|
136
|
+
} else {
|
|
137
|
+
return
|
|
136
138
|
}
|
|
137
|
-
|
|
139
|
+
router.replace(stripOAuthResultParams(window.location.href), { scroll: false })
|
|
140
|
+
}, [flashType, flashCode, flashProvider, router, t])
|
|
138
141
|
|
|
139
142
|
React.useEffect(() => {
|
|
140
143
|
let cancelled = false
|
|
@@ -76,6 +76,11 @@ export type DeliverOutboundMessageResult =
|
|
|
76
76
|
export const COMMUNICATION_CHANNELS_DELIVER_OUTBOUND_COMMAND_ID =
|
|
77
77
|
'communication_channels.message.deliver_outbound'
|
|
78
78
|
|
|
79
|
+
function omitRawMessage(metadata: Record<string, unknown> | undefined): Record<string, unknown> {
|
|
80
|
+
if (!metadata) return {}
|
|
81
|
+
return Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== 'rawMessage'))
|
|
82
|
+
}
|
|
83
|
+
|
|
79
84
|
type CredentialsServiceLike = {
|
|
80
85
|
resolve: (
|
|
81
86
|
integrationId: string,
|
|
@@ -500,7 +505,7 @@ const deliverOutboundMessageCommand: CommandHandler<
|
|
|
500
505
|
link.externalMessageId = externalMessageRowId
|
|
501
506
|
link.channelMetadata = {
|
|
502
507
|
...((link.channelMetadata as Record<string, unknown> | undefined) ?? {}),
|
|
503
|
-
...(converted.metadata
|
|
508
|
+
...omitRawMessage(converted.metadata),
|
|
504
509
|
externalMessageId: sendResult.externalMessageId,
|
|
505
510
|
// Always persist the RFC2822 Message-ID so inbound reply matching (JWZ
|
|
506
511
|
// strategy in lib/thread-matcher) and sent-folder dedup can resolve this
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"communication_channels.profile.connect.connected": "Kanal verbunden.",
|
|
72
72
|
"communication_channels.profile.connect.connecting": "Wird verbunden...",
|
|
73
73
|
"communication_channels.profile.connect.credentialsFailed": "Postfach konnte nicht verbunden werden.",
|
|
74
|
+
"communication_channels.profile.connect.errors.invalidEmail": "Geben Sie eine gültige E-Mail-Adresse ein.",
|
|
75
|
+
"communication_channels.profile.connect.errors.invalidPort": "Geben Sie einen Port zwischen 1 und 65535 ein.",
|
|
76
|
+
"communication_channels.profile.connect.errors.required": "Dieses Feld ist erforderlich.",
|
|
74
77
|
"communication_channels.profile.connect.fields.displayName": "Anzeigename",
|
|
75
78
|
"communication_channels.profile.connect.fields.fromAddress": "Absenderadresse",
|
|
76
79
|
"communication_channels.profile.connect.fields.imapHost": "IMAP-Host",
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
"communication_channels.profile.connect.imapTitle": "IMAP-Postfach verbinden",
|
|
89
92
|
"communication_channels.profile.connect.mailboxAlreadyConnected": "Dieses Postfach ist bereits über einen anderen Anbieter verbunden. Trennen Sie es zuerst, um es mit einem anderen Anbieter erneut zu verbinden.",
|
|
90
93
|
"communication_channels.profile.connect.menu": "Kanal verbinden",
|
|
94
|
+
"communication_channels.profile.connect.notAvailable": "Die Verbindung mit einem Postfach ist nicht verfügbar. Bitten Sie einen Administrator, den OAuth-Zustandsschlüssel zu konfigurieren.",
|
|
91
95
|
"communication_channels.profile.connect.notConfigured": "Dieser Anbieter ist noch nicht konfiguriert. Bitten Sie einen Administrator, die OAuth-Client-ID und das Secret unter Integrationen zu hinterlegen, bevor ein Postfach verbunden wird.",
|
|
92
96
|
"communication_channels.profile.connect.oauthFailed": "OAuth-Verbindung konnte nicht gestartet werden.",
|
|
93
97
|
"communication_channels.profile.connect.save": "Verbinden",
|
|
@@ -103,7 +107,6 @@
|
|
|
103
107
|
"communication_channels.profile.flash.connected": "Kanal verbunden.",
|
|
104
108
|
"communication_channels.profile.flash.connectedWithProvider": "Kanal verbunden ({provider}).",
|
|
105
109
|
"communication_channels.profile.flash.error": "Kanal konnte nicht verbunden werden.",
|
|
106
|
-
"communication_channels.profile.flash.errorWithCode": "Kanal konnte nicht verbunden werden — {code}.",
|
|
107
110
|
"communication_channels.profile.flash.replayedState": "Dieser Verbindungslink wurde bereits verwendet — bitte starten Sie die Verbindung erneut.",
|
|
108
111
|
"communication_channels.profile.flash.stateStoreUnavailable": "Ein Serverfehler hat die Verbindung verhindert. Bitte versuchen Sie es in Kürze erneut.",
|
|
109
112
|
"communication_channels.profile.group": "Profil",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"communication_channels.profile.connect.connected": "Channel connected.",
|
|
72
72
|
"communication_channels.profile.connect.connecting": "Connecting...",
|
|
73
73
|
"communication_channels.profile.connect.credentialsFailed": "Could not connect mailbox.",
|
|
74
|
+
"communication_channels.profile.connect.errors.invalidEmail": "Enter a valid email address.",
|
|
75
|
+
"communication_channels.profile.connect.errors.invalidPort": "Enter a port between 1 and 65535.",
|
|
76
|
+
"communication_channels.profile.connect.errors.required": "This field is required.",
|
|
74
77
|
"communication_channels.profile.connect.fields.displayName": "Display name",
|
|
75
78
|
"communication_channels.profile.connect.fields.fromAddress": "From address",
|
|
76
79
|
"communication_channels.profile.connect.fields.imapHost": "IMAP host",
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
"communication_channels.profile.connect.imapTitle": "Connect IMAP mailbox",
|
|
89
92
|
"communication_channels.profile.connect.mailboxAlreadyConnected": "This mailbox is already connected through another provider. Disconnect it first to reconnect it with a different one.",
|
|
90
93
|
"communication_channels.profile.connect.menu": "Connect channel",
|
|
94
|
+
"communication_channels.profile.connect.notAvailable": "Mailbox connection is not available. Ask an administrator to configure the OAuth state key.",
|
|
91
95
|
"communication_channels.profile.connect.notConfigured": "This provider is not configured yet. Ask an administrator to add the OAuth Client ID and Secret under Integrations before connecting a mailbox.",
|
|
92
96
|
"communication_channels.profile.connect.oauthFailed": "Could not start OAuth connection.",
|
|
93
97
|
"communication_channels.profile.connect.save": "Connect",
|
|
@@ -103,7 +107,6 @@
|
|
|
103
107
|
"communication_channels.profile.flash.connected": "Channel connected.",
|
|
104
108
|
"communication_channels.profile.flash.connectedWithProvider": "Channel connected ({provider}).",
|
|
105
109
|
"communication_channels.profile.flash.error": "Failed to connect channel.",
|
|
106
|
-
"communication_channels.profile.flash.errorWithCode": "Failed to connect channel — {code}.",
|
|
107
110
|
"communication_channels.profile.flash.replayedState": "This connection link was already used — please start the connection again.",
|
|
108
111
|
"communication_channels.profile.flash.stateStoreUnavailable": "A server error prevented completing the connection. Please try again in a moment.",
|
|
109
112
|
"communication_channels.profile.group": "Profile",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"communication_channels.profile.connect.connected": "Canal conectado.",
|
|
72
72
|
"communication_channels.profile.connect.connecting": "Conectando...",
|
|
73
73
|
"communication_channels.profile.connect.credentialsFailed": "No se pudo conectar el buzón.",
|
|
74
|
+
"communication_channels.profile.connect.errors.invalidEmail": "Introduce una dirección de correo electrónico válida.",
|
|
75
|
+
"communication_channels.profile.connect.errors.invalidPort": "Introduce un puerto entre 1 y 65535.",
|
|
76
|
+
"communication_channels.profile.connect.errors.required": "Este campo es obligatorio.",
|
|
74
77
|
"communication_channels.profile.connect.fields.displayName": "Nombre visible",
|
|
75
78
|
"communication_channels.profile.connect.fields.fromAddress": "Dirección de remitente",
|
|
76
79
|
"communication_channels.profile.connect.fields.imapHost": "Host IMAP",
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
"communication_channels.profile.connect.imapTitle": "Conectar buzón IMAP",
|
|
89
92
|
"communication_channels.profile.connect.mailboxAlreadyConnected": "Este buzón ya está conectado a través de otro proveedor. Desconéctalo primero para volver a conectarlo con uno diferente.",
|
|
90
93
|
"communication_channels.profile.connect.menu": "Conectar canal",
|
|
94
|
+
"communication_channels.profile.connect.notAvailable": "La conexión del buzón no está disponible. Pide a un administrador que configure la clave de estado de OAuth.",
|
|
91
95
|
"communication_channels.profile.connect.notConfigured": "Este proveedor aún no está configurado. Pide a un administrador que añada el ID de cliente y el secreto de OAuth en Integraciones antes de conectar un buzón.",
|
|
92
96
|
"communication_channels.profile.connect.oauthFailed": "No se pudo iniciar la conexión OAuth.",
|
|
93
97
|
"communication_channels.profile.connect.save": "Conectar",
|
|
@@ -103,7 +107,6 @@
|
|
|
103
107
|
"communication_channels.profile.flash.connected": "Canal conectado.",
|
|
104
108
|
"communication_channels.profile.flash.connectedWithProvider": "Canal conectado ({provider}).",
|
|
105
109
|
"communication_channels.profile.flash.error": "No se pudo conectar el canal.",
|
|
106
|
-
"communication_channels.profile.flash.errorWithCode": "No se pudo conectar el canal — {code}.",
|
|
107
110
|
"communication_channels.profile.flash.replayedState": "Este enlace de conexión ya se utilizó — inicia la conexión de nuevo.",
|
|
108
111
|
"communication_channels.profile.flash.stateStoreUnavailable": "Un error del servidor impidió completar la conexión. Inténtalo de nuevo en un momento.",
|
|
109
112
|
"communication_channels.profile.group": "Perfil",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"communication_channels.profile.connect.connected": "채널이 연결되었습니다.",
|
|
72
72
|
"communication_channels.profile.connect.connecting": "연결 중...",
|
|
73
73
|
"communication_channels.profile.connect.credentialsFailed": "메일함에 연결할 수 없습니다.",
|
|
74
|
+
"communication_channels.profile.connect.errors.invalidEmail": "유효한 이메일 주소를 입력하세요.",
|
|
75
|
+
"communication_channels.profile.connect.errors.invalidPort": "1에서 65535 사이의 포트를 입력하세요.",
|
|
76
|
+
"communication_channels.profile.connect.errors.required": "필수 입력 항목입니다.",
|
|
74
77
|
"communication_channels.profile.connect.fields.displayName": "표시 이름",
|
|
75
78
|
"communication_channels.profile.connect.fields.fromAddress": "보내는 주소",
|
|
76
79
|
"communication_channels.profile.connect.fields.imapHost": "IMAP 호스트",
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
"communication_channels.profile.connect.imapTitle": "IMAP 메일함 연결",
|
|
89
92
|
"communication_channels.profile.connect.mailboxAlreadyConnected": "이 메일함은 이미 다른 제공업체를 통해 연결되어 있습니다. 다른 제공업체로 다시 연결하려면 먼저 연결을 해제하세요.",
|
|
90
93
|
"communication_channels.profile.connect.menu": "채널 연결",
|
|
94
|
+
"communication_channels.profile.connect.notAvailable": "메일함 연결을 사용할 수 없습니다. 관리자에게 OAuth 상태 키를 구성하도록 요청하세요.",
|
|
91
95
|
"communication_channels.profile.connect.notConfigured": "이 제공업체는 아직 구성되지 않았습니다. 메일함을 연결하기 전에 관리자에게 통합에서 OAuth 클라이언트 ID와 시크릿을 추가하도록 요청하세요.",
|
|
92
96
|
"communication_channels.profile.connect.oauthFailed": "OAuth 연결을 시작할 수 없습니다.",
|
|
93
97
|
"communication_channels.profile.connect.save": "연결",
|
|
@@ -103,7 +107,6 @@
|
|
|
103
107
|
"communication_channels.profile.flash.connected": "채널이 연결되었습니다.",
|
|
104
108
|
"communication_channels.profile.flash.connectedWithProvider": "채널이 연결되었습니다 ({provider}).",
|
|
105
109
|
"communication_channels.profile.flash.error": "채널 연결에 실패했습니다.",
|
|
106
|
-
"communication_channels.profile.flash.errorWithCode": "채널 연결에 실패했습니다 — {code}.",
|
|
107
110
|
"communication_channels.profile.flash.replayedState": "이 연결 링크는 이미 사용되었습니다. 연결을 다시 시작해 주세요.",
|
|
108
111
|
"communication_channels.profile.flash.stateStoreUnavailable": "서버 오류로 연결을 완료할 수 없습니다. 잠시 후 다시 시도해 주세요.",
|
|
109
112
|
"communication_channels.profile.group": "프로필",
|
|
@@ -71,6 +71,9 @@
|
|
|
71
71
|
"communication_channels.profile.connect.connected": "Kanał połączony.",
|
|
72
72
|
"communication_channels.profile.connect.connecting": "Łączenie...",
|
|
73
73
|
"communication_channels.profile.connect.credentialsFailed": "Nie udało się połączyć skrzynki pocztowej.",
|
|
74
|
+
"communication_channels.profile.connect.errors.invalidEmail": "Wprowadź prawidłowy adres e-mail.",
|
|
75
|
+
"communication_channels.profile.connect.errors.invalidPort": "Wprowadź port z zakresu od 1 do 65535.",
|
|
76
|
+
"communication_channels.profile.connect.errors.required": "To pole jest wymagane.",
|
|
74
77
|
"communication_channels.profile.connect.fields.displayName": "Nazwa wyświetlana",
|
|
75
78
|
"communication_channels.profile.connect.fields.fromAddress": "Adres nadawcy",
|
|
76
79
|
"communication_channels.profile.connect.fields.imapHost": "Host IMAP",
|
|
@@ -88,6 +91,7 @@
|
|
|
88
91
|
"communication_channels.profile.connect.imapTitle": "Połącz skrzynkę IMAP",
|
|
89
92
|
"communication_channels.profile.connect.mailboxAlreadyConnected": "Ta skrzynka jest już połączona przez innego dostawcę. Najpierw ją rozłącz, aby połączyć ją z innym dostawcą.",
|
|
90
93
|
"communication_channels.profile.connect.menu": "Połącz kanał",
|
|
94
|
+
"communication_channels.profile.connect.notAvailable": "Połączenie skrzynki pocztowej jest niedostępne. Poproś administratora o skonfigurowanie klucza stanu OAuth.",
|
|
91
95
|
"communication_channels.profile.connect.notConfigured": "Ten dostawca nie został jeszcze skonfigurowany. Poproś administratora o dodanie identyfikatora klienta i sekretu OAuth w sekcji Integracje przed połączeniem skrzynki.",
|
|
92
96
|
"communication_channels.profile.connect.oauthFailed": "Nie udało się rozpocząć połączenia OAuth.",
|
|
93
97
|
"communication_channels.profile.connect.save": "Połącz",
|
|
@@ -103,7 +107,6 @@
|
|
|
103
107
|
"communication_channels.profile.flash.connected": "Kanał połączony.",
|
|
104
108
|
"communication_channels.profile.flash.connectedWithProvider": "Kanał połączony ({provider}).",
|
|
105
109
|
"communication_channels.profile.flash.error": "Nie udało się połączyć kanału.",
|
|
106
|
-
"communication_channels.profile.flash.errorWithCode": "Nie udało się połączyć kanału — {code}.",
|
|
107
110
|
"communication_channels.profile.flash.replayedState": "Ten link połączenia został już użyty — rozpocznij połączenie ponownie.",
|
|
108
111
|
"communication_channels.profile.flash.stateStoreUnavailable": "Błąd serwera uniemożliwił dokończenie połączenia. Spróbuj ponownie za chwilę.",
|
|
109
112
|
"communication_channels.profile.group": "Profil",
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { resolveCommunicationChannelsSystemUserId } from './system-user'
|
|
3
|
+
import { EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE } from '../../messages/lib/messageSourceEntityTypes'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
* Entity type a channel-backed conversation is exposed under on a message's
|
|
6
|
-
* `sourceEntityType`. Written by `commands/ingest-inbound-message.ts` and
|
|
7
|
-
* inherited by every operator message composed on the same conversation.
|
|
8
|
-
*/
|
|
9
|
-
export const EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE = 'communication_channels.external_conversation'
|
|
5
|
+
export { EXTERNAL_CONVERSATION_SOURCE_ENTITY_TYPE }
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* Dedup key `ingest-inbound-message` stamps on the platform message it composes,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The OAuth callback reports its outcome to the return page through this query
|
|
2
|
+
// param. It must not be `flash`: the global <FlashMessages> host reads `?flash=`
|
|
3
|
+
// as the message text and would render the raw outcome ("error"/"connected") as
|
|
4
|
+
// a success toast (#6402).
|
|
5
|
+
export const OAUTH_RESULT_QUERY_PARAM = 'oauth'
|
|
6
|
+
|
|
7
|
+
const OAUTH_RESULT_QUERY_PARAMS = [OAUTH_RESULT_QUERY_PARAM, 'code', 'provider', 'channelId']
|
|
8
|
+
|
|
9
|
+
export function stripOAuthResultParams(href: string): string {
|
|
10
|
+
const url = new URL(href)
|
|
11
|
+
for (const param of OAUTH_RESULT_QUERY_PARAMS) url.searchParams.delete(param)
|
|
12
|
+
return `${url.pathname}${url.search}${url.hash}`
|
|
13
|
+
}
|
|
@@ -138,7 +138,10 @@ function getSecret(): string {
|
|
|
138
138
|
// state cookies, bypassing the userId/tenant binding. In non-production we fall
|
|
139
139
|
// back to JWT_SECRET so dev/test envs that only configure one secret still work.
|
|
140
140
|
if (process.env.NODE_ENV === 'production') {
|
|
141
|
-
throw new
|
|
141
|
+
throw new OAuthStateError(
|
|
142
|
+
'[internal] OM_HUB_OAUTH_STATE_KEY or KMS_MASTER_KEY required in production',
|
|
143
|
+
'missing_secret',
|
|
144
|
+
)
|
|
142
145
|
}
|
|
143
146
|
const fallback = process.env.JWT_SECRET
|
|
144
147
|
if (!fallback) {
|
|
@@ -46,6 +46,16 @@ export function useConnectChannel(options: {
|
|
|
46
46
|
})
|
|
47
47
|
const body = response.result as InitiateResponse | undefined
|
|
48
48
|
if (!response.ok || !body?.authorizeUrl) {
|
|
49
|
+
if (body?.code === 'missing_secret') {
|
|
50
|
+
flash(
|
|
51
|
+
t(
|
|
52
|
+
'communication_channels.profile.connect.notAvailable',
|
|
53
|
+
'Mailbox connection is not available. Ask an administrator to configure the OAuth state key.',
|
|
54
|
+
),
|
|
55
|
+
'error',
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
49
59
|
if (body?.code === 'oauth_client_not_configured') {
|
|
50
60
|
flash(
|
|
51
61
|
t(
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
|
+
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
6
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
|
+
import type { OrganizationHierarchyService } from '@open-mercato/shared/lib/auth/principal-service'
|
|
8
|
+
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
9
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
10
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
11
|
+
import { resolveDictionaryRouteContext } from '../dictionaries/context'
|
|
12
|
+
import { createDictionaryCacheTags, DICTIONARY_CACHE_TTL_MS } from '../dictionaries/cache'
|
|
13
|
+
import { getInjectionRegistryVersion } from '@open-mercato/shared/modules/widgets/injection-loader'
|
|
14
|
+
import {
|
|
15
|
+
resolveScopedCalendarEventTypes,
|
|
16
|
+
} from '../../lib/calendar/eventTypeResolver'
|
|
17
|
+
import { calendarEventTypeBehaviorSchema } from '../../calendar-event-types'
|
|
18
|
+
import { getCalendarEventTypeRegistryVersion } from '../../calendar-event-types'
|
|
19
|
+
|
|
20
|
+
const logger = createLogger('customers')
|
|
21
|
+
|
|
22
|
+
function reportActivityTypeError(error: unknown, code: string, attributes?: Record<string, string>): void {
|
|
23
|
+
try {
|
|
24
|
+
getTelemetryRuntime()?.reportError(error, { module: 'customers', code, attributes })
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const querySchema = z.object({ organizationId: z.string().uuid().optional() })
|
|
29
|
+
const responseSchema = z.object({
|
|
30
|
+
fallbackKey: z.literal('meeting'),
|
|
31
|
+
items: z.array(z.object({
|
|
32
|
+
key: z.string(),
|
|
33
|
+
label: z.string(),
|
|
34
|
+
labelKey: z.string().optional(),
|
|
35
|
+
icon: z.string().nullable().optional(),
|
|
36
|
+
color: z.string().nullable().optional(),
|
|
37
|
+
behavior: calendarEventTypeBehaviorSchema,
|
|
38
|
+
selectable: z.boolean(),
|
|
39
|
+
panelKey: z.string().optional(),
|
|
40
|
+
source: z.string(),
|
|
41
|
+
provenance: z.record(z.string(), z.unknown()),
|
|
42
|
+
historical: z.boolean(),
|
|
43
|
+
fallbackReason: z.enum(['tombstoned', 'module-unavailable', 'unknown']).optional(),
|
|
44
|
+
adminConfigurable: z.boolean(),
|
|
45
|
+
isInherited: z.boolean(),
|
|
46
|
+
isLocalOverride: z.boolean(),
|
|
47
|
+
updatedAt: z.string().nullable(),
|
|
48
|
+
missingCustomFieldsetIds: z.array(z.string()),
|
|
49
|
+
inactiveDictionaryOverride: z.boolean().optional(),
|
|
50
|
+
})),
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
export const metadata = {
|
|
54
|
+
GET: { requireAuth: true },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function GET(req: Request) {
|
|
58
|
+
try {
|
|
59
|
+
const url = new URL(req.url)
|
|
60
|
+
const query = querySchema.parse({ organizationId: url.searchParams.get('organizationId') ?? undefined })
|
|
61
|
+
const context = await resolveDictionaryRouteContext(req, { selectedId: query.organizationId })
|
|
62
|
+
if (!context.organizationId) {
|
|
63
|
+
return NextResponse.json({ error: context.translate('customers.errors.organization_required', 'Organization context is required') }, { status: 400 })
|
|
64
|
+
}
|
|
65
|
+
const organizationId = context.organizationId
|
|
66
|
+
const rbac = context.container.resolve('rbacService') as RbacService | undefined
|
|
67
|
+
const actorId = context.auth?.sub
|
|
68
|
+
if (!rbac || !actorId) {
|
|
69
|
+
throw new CrudHttpError(403, { error: context.translate('customers.calendar.activityTypes.errors.forbidden', 'You do not have permission to view activity types.') })
|
|
70
|
+
}
|
|
71
|
+
const permissionScope = { tenantId: context.tenantId, organizationId }
|
|
72
|
+
const canViewInteractions = await rbac.userHasAllFeatures(actorId, ['customers.interactions.view'], permissionScope)
|
|
73
|
+
const canManageSettings = canViewInteractions || await rbac.userHasAllFeatures(actorId, ['customers.settings.manage'], permissionScope)
|
|
74
|
+
if (!canManageSettings) {
|
|
75
|
+
throw new CrudHttpError(403, { error: context.translate('customers.calendar.activityTypes.errors.forbidden', 'You do not have permission to view activity types.') })
|
|
76
|
+
}
|
|
77
|
+
const hierarchy = context.container.resolve('organizationHierarchyService') as OrganizationHierarchyService
|
|
78
|
+
const ancestors = await hierarchy.resolveAncestorIds({ tenantId: context.tenantId, organizationId })
|
|
79
|
+
if (ancestors === null) {
|
|
80
|
+
throw new CrudHttpError(403, { error: context.translate('customers.errors.organization_forbidden', 'Organization not accessible') })
|
|
81
|
+
}
|
|
82
|
+
const scopeIds = Array.from(new Set([organizationId, ...ancestors.slice().reverse()]))
|
|
83
|
+
const cacheKey = `customers:activity-types:${context.tenantId}:org=${organizationId}:scope=${scopeIds.join('|')}:widgets=${getInjectionRegistryVersion()}:registry=${getCalendarEventTypeRegistryVersion()}`
|
|
84
|
+
const cached = await context.cache?.get(cacheKey)
|
|
85
|
+
if (cached) return NextResponse.json(cached)
|
|
86
|
+
|
|
87
|
+
let catalog
|
|
88
|
+
try {
|
|
89
|
+
catalog = await resolveScopedCalendarEventTypes({
|
|
90
|
+
em: context.em,
|
|
91
|
+
tenantId: context.tenantId,
|
|
92
|
+
organizationId,
|
|
93
|
+
readableOrganizationIds: scopeIds,
|
|
94
|
+
})
|
|
95
|
+
} catch (err) {
|
|
96
|
+
logger.error('customers.activity_types.resolve failed', {
|
|
97
|
+
err,
|
|
98
|
+
tenantId: context.tenantId,
|
|
99
|
+
organizationId,
|
|
100
|
+
})
|
|
101
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_resolution_failed', {
|
|
102
|
+
tenantId: context.tenantId,
|
|
103
|
+
organizationId,
|
|
104
|
+
})
|
|
105
|
+
return NextResponse.json({
|
|
106
|
+
error: context.translate('customers.calendar.activityTypes.errors.loadFailed', 'Failed to load activity types'),
|
|
107
|
+
code: 'activity_type_catalog_unavailable',
|
|
108
|
+
}, { status: 503 })
|
|
109
|
+
}
|
|
110
|
+
if (context.cache) {
|
|
111
|
+
await context.cache.set(cacheKey, catalog, {
|
|
112
|
+
ttl: DICTIONARY_CACHE_TTL_MS,
|
|
113
|
+
tags: createDictionaryCacheTags({
|
|
114
|
+
tenantId: context.tenantId,
|
|
115
|
+
mappedKind: 'activity_type',
|
|
116
|
+
organizationIds: scopeIds,
|
|
117
|
+
}),
|
|
118
|
+
}).catch((err) => {
|
|
119
|
+
logger.warn('customers.activity_types cache write failed', { err })
|
|
120
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_cache_write_failed', {
|
|
121
|
+
tenantId: context.tenantId,
|
|
122
|
+
organizationId,
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
return NextResponse.json(catalog)
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (isCrudHttpError(err)) return NextResponse.json(err.body, { status: err.status })
|
|
129
|
+
logger.error('customers.activity_types request failed', { err })
|
|
130
|
+
reportActivityTypeError(err, 'customers.activity_type_catalog_request_failed')
|
|
131
|
+
const { translate } = await resolveTranslations()
|
|
132
|
+
return NextResponse.json({
|
|
133
|
+
error: translate('customers.calendar.activityTypes.errors.loadFailed', 'Failed to load activity types'),
|
|
134
|
+
}, { status: err instanceof z.ZodError ? 400 : 500 })
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export const openApi: OpenApiRouteDoc = {
|
|
139
|
+
tag: 'Customers',
|
|
140
|
+
summary: 'Effective calendar activity types',
|
|
141
|
+
methods: {
|
|
142
|
+
GET: {
|
|
143
|
+
summary: 'List effective calendar activity types',
|
|
144
|
+
description: 'Returns the tenant and organization scoped calendar activity-type catalog. Requires customers.interactions.view or customers.settings.manage.',
|
|
145
|
+
responses: [{ status: 200, description: 'Effective activity-type catalog', schema: responseSchema }],
|
|
146
|
+
errors: [
|
|
147
|
+
{ status: 400, description: 'Organization scope is required', schema: z.object({ error: z.string() }) },
|
|
148
|
+
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
149
|
+
{ status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
|
|
150
|
+
{ status: 500, description: 'Unexpected server error', schema: z.object({ error: z.string() }) },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|