@open-mercato/core 0.6.7-develop.6606.1.3b1ec9b1ea → 0.6.7-develop.6622.1.4d29ddaca4
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/modules/auth/api/session/refresh.js +12 -11
- package/dist/modules/auth/api/session/refresh.js.map +2 -2
- package/dist/modules/auth/lib/rateLimitCheck.js +4 -4
- package/dist/modules/auth/lib/rateLimitCheck.js.map +2 -2
- package/dist/modules/catalog/api/categories/route.js +8 -14
- package/dist/modules/catalog/api/categories/route.js.map +2 -2
- package/dist/modules/catalog/api/tags/route.js +3 -10
- package/dist/modules/catalog/api/tags/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js +15 -17
- package/dist/modules/customers/api/interactions/[id]/visibility/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/email-threads/route.js +6 -4
- package/dist/modules/customers/api/people/[id]/email-threads/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/emails/route.js +19 -17
- package/dist/modules/customers/api/people/[id]/emails/route.js.map +2 -2
- package/dist/modules/customers/components/detail/CustomFieldValuesList.js +22 -5
- package/dist/modules/customers/components/detail/CustomFieldValuesList.js.map +2 -2
- package/dist/modules/customers/components/detail/create/CreateDealForm.js +5 -2
- package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/customFieldUtils.js +10 -3
- package/dist/modules/customers/components/detail/customFieldUtils.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js +4 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js +4 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.meta.js.map +2 -2
- package/dist/modules/integrations/api/[id]/credentials/route.js +10 -7
- package/dist/modules/integrations/api/[id]/credentials/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/health/route.js +6 -4
- package/dist/modules/integrations/api/[id]/health/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +4 -2
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/state/route.js +7 -5
- package/dist/modules/integrations/api/[id]/state/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/version/route.js +7 -5
- package/dist/modules/integrations/api/[id]/version/route.js.map +2 -2
- package/dist/modules/integrations/api/logs/route.js +4 -2
- package/dist/modules/integrations/api/logs/route.js.map +2 -2
- package/dist/modules/integrations/api/route.js +4 -2
- package/dist/modules/integrations/api/route.js.map +2 -2
- package/dist/modules/integrations/api/umes-read.js +2 -1
- package/dist/modules/integrations/api/umes-read.js.map +2 -2
- package/dist/modules/integrations/lib/organization-scope.js +12 -0
- package/dist/modules/integrations/lib/organization-scope.js.map +7 -0
- package/dist/modules/payment_gateways/lib/gateway-service.js +10 -1
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/sales/api/document-history/route.js +6 -9
- package/dist/modules/sales/api/document-history/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/accept/route.js +3 -3
- package/dist/modules/sales/api/quotes/accept/route.js.map +2 -2
- package/dist/modules/sales/api/returns/[id]/route.js +6 -9
- package/dist/modules/sales/api/returns/[id]/route.js.map +2 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -2
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +6 -4
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/workflows.js +1 -1
- package/dist/modules/sales/workflows.js.map +1 -1
- package/dist/modules/workflows/api/definitions/[id]/route.js +10 -1
- package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
- package/dist/modules/workflows/lib/event-trigger-service.js +44 -3
- package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/api/session/refresh.ts +19 -11
- package/src/modules/auth/lib/rateLimitCheck.ts +4 -4
- package/src/modules/catalog/api/categories/route.ts +14 -16
- package/src/modules/catalog/api/tags/route.ts +6 -10
- package/src/modules/catalog/i18n/pl.json +9 -9
- package/src/modules/customer_accounts/i18n/pl.json +129 -129
- package/src/modules/customers/api/interactions/[id]/visibility/route.ts +24 -20
- package/src/modules/customers/api/people/[id]/email-threads/route.ts +10 -6
- package/src/modules/customers/api/people/[id]/emails/route.ts +25 -21
- package/src/modules/customers/components/detail/CustomFieldValuesList.tsx +28 -4
- package/src/modules/customers/components/detail/create/CreateDealForm.tsx +7 -2
- package/src/modules/customers/components/detail/customFieldUtils.ts +15 -3
- package/src/modules/data_sync/i18n/pl.json +68 -68
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.meta.ts +4 -2
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.meta.ts +4 -2
- package/src/modules/integrations/api/[id]/credentials/route.ts +10 -7
- package/src/modules/integrations/api/[id]/health/route.ts +6 -4
- package/src/modules/integrations/api/[id]/route.ts +4 -2
- package/src/modules/integrations/api/[id]/state/route.ts +7 -5
- package/src/modules/integrations/api/[id]/version/route.ts +7 -5
- package/src/modules/integrations/api/logs/route.ts +4 -2
- package/src/modules/integrations/api/route.ts +4 -2
- package/src/modules/integrations/api/umes-read.ts +2 -1
- package/src/modules/integrations/i18n/de.json +0 -76
- package/src/modules/integrations/i18n/en.json +0 -76
- package/src/modules/integrations/i18n/es.json +0 -76
- package/src/modules/integrations/i18n/pl.json +0 -76
- package/src/modules/integrations/lib/organization-scope.ts +27 -0
- package/src/modules/payment_gateways/lib/gateway-service.ts +12 -1
- package/src/modules/sales/api/document-history/route.ts +10 -9
- package/src/modules/sales/api/quotes/accept/route.ts +3 -3
- package/src/modules/sales/api/returns/[id]/route.ts +10 -9
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +26 -19
- package/src/modules/sales/components/documents/AddressesSection.tsx +8 -4
- package/src/modules/sales/i18n/pl.json +24 -24
- package/src/modules/sales/workflows.ts +1 -1
- package/src/modules/staff/i18n/pl.json +178 -178
- package/src/modules/workflows/api/definitions/[id]/route.ts +14 -1
- package/src/modules/workflows/i18n/pl.json +128 -128
- package/src/modules/workflows/lib/event-trigger-service.ts +87 -8
|
@@ -52,37 +52,24 @@ export async function PATCH(req: Request, context: RouteContext): Promise<Respon
|
|
|
52
52
|
const container = await createRequestContainer()
|
|
53
53
|
const em = (container.resolve('em') as EntityManager).fork()
|
|
54
54
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
55
|
-
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
56
|
-
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
57
55
|
const userId = resolveAuthActorId(auth)
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
resourceKind: 'customers.interaction',
|
|
64
|
-
resourceId: id,
|
|
65
|
-
operation: 'custom',
|
|
66
|
-
requestMethod: req.method,
|
|
67
|
-
requestHeaders: req.headers,
|
|
68
|
-
})
|
|
69
|
-
if (guardResult && !guardResult.ok) {
|
|
70
|
-
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
71
|
-
}
|
|
72
|
-
|
|
57
|
+
// Load the interaction by its tenant-unique id (not a hand-rolled selected
|
|
58
|
+
// org) so this keeps working under the "All organizations" scope. The record's
|
|
59
|
+
// own organization then becomes the concrete org the guard, command, and audit
|
|
60
|
+
// event are attributed to.
|
|
73
61
|
const interaction = (await findOneWithDecryption(
|
|
74
62
|
em,
|
|
75
63
|
CustomerInteraction,
|
|
76
64
|
{
|
|
77
65
|
id,
|
|
78
66
|
tenantId: auth.tenantId,
|
|
79
|
-
organizationId,
|
|
80
67
|
deletedAt: null,
|
|
81
68
|
interactionType: 'email',
|
|
82
69
|
} as any,
|
|
83
70
|
undefined,
|
|
84
|
-
|
|
85
|
-
)) as { id: string; authorUserId?: string | null; visibility?: string | null } | null
|
|
71
|
+
{ tenantId: auth.tenantId as string, organizationId: scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null },
|
|
72
|
+
)) as { id: string; organizationId?: string | null; authorUserId?: string | null; visibility?: string | null } | null
|
|
86
73
|
|
|
87
74
|
if (!interaction) {
|
|
88
75
|
return NextResponse.json({ error: 'Email not found' }, { status: 404 })
|
|
@@ -91,12 +78,29 @@ export async function PATCH(req: Request, context: RouteContext): Promise<Respon
|
|
|
91
78
|
// Personal mailbox privacy (v1: strict owner-only): ONLY the author may flip
|
|
92
79
|
// their own email's visibility — no admin bypass. Return 404 (not 403) for
|
|
93
80
|
// everyone else so we don't leak the row's existence — this also covers
|
|
94
|
-
// non-authors who cannot see a private email in the first place.
|
|
81
|
+
// non-authors who cannot see a private email in the first place. This owner
|
|
82
|
+
// check is stricter than any org gate, so no separate org read guard is needed.
|
|
95
83
|
const isAuthor = !!interaction.authorUserId && interaction.authorUserId === auth.sub
|
|
96
84
|
if (!isAuthor) {
|
|
97
85
|
return NextResponse.json({ error: 'Email not found' }, { status: 404 })
|
|
98
86
|
}
|
|
99
87
|
|
|
88
|
+
const organizationId = interaction.organizationId ?? null
|
|
89
|
+
|
|
90
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
91
|
+
tenantId: auth.tenantId,
|
|
92
|
+
organizationId,
|
|
93
|
+
userId,
|
|
94
|
+
resourceKind: 'customers.interaction',
|
|
95
|
+
resourceId: id,
|
|
96
|
+
operation: 'custom',
|
|
97
|
+
requestMethod: req.method,
|
|
98
|
+
requestHeaders: req.headers,
|
|
99
|
+
})
|
|
100
|
+
if (guardResult && !guardResult.ok) {
|
|
101
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
// No-op: visibility is already at the requested value.
|
|
101
105
|
if (interaction.visibility === body.visibility) {
|
|
102
106
|
return NextResponse.json({ ok: true, changed: false })
|
|
@@ -5,6 +5,7 @@ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
|
5
5
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
6
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
7
7
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
8
|
+
import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
|
|
8
9
|
import { CustomerEntity } from '../../../../data/entities'
|
|
9
10
|
import { buildPersonEmailThreads } from '../../../../lib/personEmailThreads'
|
|
10
11
|
|
|
@@ -33,12 +34,12 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
|
|
|
33
34
|
|
|
34
35
|
const container = await createRequestContainer()
|
|
35
36
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
36
|
-
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
37
37
|
const em = (container.resolve('em') as EntityManager).fork()
|
|
38
|
-
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
39
38
|
|
|
40
|
-
// Verify the Person exists in the caller's tenant
|
|
41
|
-
// same pattern as the [id]
|
|
39
|
+
// Verify the Person exists in the caller's tenant, then fail-closed on the
|
|
40
|
+
// record's own organization — same pattern as the [id] detail route. Loading
|
|
41
|
+
// by tenant + id (not a hand-rolled selected org) keeps this working under the
|
|
42
|
+
// "All organizations" scope, where no concrete org is carried.
|
|
42
43
|
const person = await findOneWithDecryption(
|
|
43
44
|
em,
|
|
44
45
|
CustomerEntity,
|
|
@@ -46,15 +47,18 @@ export async function GET(req: Request, context: RouteContext): Promise<Response
|
|
|
46
47
|
id: personId,
|
|
47
48
|
kind: 'person',
|
|
48
49
|
tenantId: auth.tenantId,
|
|
49
|
-
organizationId,
|
|
50
50
|
deletedAt: null,
|
|
51
51
|
} as never,
|
|
52
52
|
undefined,
|
|
53
|
-
|
|
53
|
+
{ tenantId: auth.tenantId as string, organizationId: scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null },
|
|
54
54
|
)
|
|
55
55
|
if (!person) {
|
|
56
56
|
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
57
57
|
}
|
|
58
|
+
const organizationId = (person as { organizationId?: string | null }).organizationId ?? null
|
|
59
|
+
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {
|
|
60
|
+
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
61
|
+
}
|
|
58
62
|
|
|
59
63
|
const viewerUserId = auth.isApiKey ? null : auth.sub ?? null
|
|
60
64
|
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
runCrudMutationGuardAfterSuccess,
|
|
11
11
|
} from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
12
12
|
import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
13
|
+
import { isOrganizationReadAccessAllowed } from '@open-mercato/core/modules/directory/utils/organizationScopeGuard'
|
|
13
14
|
import { resolveAuthActorId } from '../../../../lib/interactionRequestContext'
|
|
14
15
|
import { CustomerEntity } from '../../../../data/entities'
|
|
15
16
|
import type { SendAsUserService } from '@open-mercato/core/modules/communication_channels/lib/send-as-user'
|
|
@@ -66,28 +67,14 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
66
67
|
const container = await createRequestContainer()
|
|
67
68
|
const userId = resolveAuthActorId(auth)
|
|
68
69
|
const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
|
|
69
|
-
const organizationId = scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null
|
|
70
|
-
|
|
71
|
-
const guardResult = await validateCrudMutationGuard(container, {
|
|
72
|
-
tenantId: auth.tenantId,
|
|
73
|
-
organizationId,
|
|
74
|
-
userId,
|
|
75
|
-
resourceKind: 'customers.person',
|
|
76
|
-
resourceId: personId,
|
|
77
|
-
operation: 'custom',
|
|
78
|
-
requestMethod: req.method,
|
|
79
|
-
requestHeaders: req.headers,
|
|
80
|
-
})
|
|
81
|
-
if (guardResult && !guardResult.ok) {
|
|
82
|
-
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
83
|
-
}
|
|
84
70
|
|
|
85
71
|
const em = (container.resolve('em') as EntityManager).fork()
|
|
86
|
-
const dscope = { tenantId: auth.tenantId as string, organizationId }
|
|
87
72
|
|
|
88
|
-
// 1. Verify the Person exists in the caller's tenant
|
|
89
|
-
//
|
|
90
|
-
//
|
|
73
|
+
// 1. Verify the Person exists in the caller's tenant, then fail-closed on the
|
|
74
|
+
// record's own organization — same pattern as the [id]/route.ts GET handler.
|
|
75
|
+
// Loading by tenant + id (not a hand-rolled selected org) keeps this working
|
|
76
|
+
// under the "All organizations" scope; the record's own org then becomes the
|
|
77
|
+
// concrete org the guard and the outbound message are attributed to.
|
|
91
78
|
const person = await findOneWithDecryption(
|
|
92
79
|
em,
|
|
93
80
|
CustomerEntity,
|
|
@@ -95,15 +82,32 @@ export async function POST(req: Request, context: RouteContext): Promise<Respons
|
|
|
95
82
|
id: personId,
|
|
96
83
|
kind: 'person',
|
|
97
84
|
tenantId: auth.tenantId,
|
|
98
|
-
organizationId,
|
|
99
85
|
deletedAt: null,
|
|
100
86
|
} as never,
|
|
101
87
|
undefined,
|
|
102
|
-
|
|
88
|
+
{ tenantId: auth.tenantId as string, organizationId: scope?.selectedId ?? (auth as { orgId?: string | null }).orgId ?? null },
|
|
103
89
|
)
|
|
104
90
|
if (!person) {
|
|
105
91
|
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
106
92
|
}
|
|
93
|
+
const organizationId = (person as { organizationId?: string | null }).organizationId ?? null
|
|
94
|
+
if (!isOrganizationReadAccessAllowed({ scope, auth, organizationId })) {
|
|
95
|
+
return NextResponse.json({ error: 'Person not found' }, { status: 404 })
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
99
|
+
tenantId: auth.tenantId,
|
|
100
|
+
organizationId,
|
|
101
|
+
userId,
|
|
102
|
+
resourceKind: 'customers.person',
|
|
103
|
+
resourceId: personId,
|
|
104
|
+
operation: 'custom',
|
|
105
|
+
requestMethod: req.method,
|
|
106
|
+
requestHeaders: req.headers,
|
|
107
|
+
})
|
|
108
|
+
if (guardResult && !guardResult.ok) {
|
|
109
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
110
|
+
}
|
|
107
111
|
|
|
108
112
|
// 2. Call the hub's send-as-user facade in-process (resolved via DI) so the
|
|
109
113
|
// customers module makes no HTTP self-call. `crmVisibility` and `crmPersonId`
|
|
@@ -30,6 +30,8 @@ type DisplayEntry = {
|
|
|
30
30
|
value: unknown
|
|
31
31
|
dictionaryMap: DictionaryMap | null
|
|
32
32
|
multi: boolean
|
|
33
|
+
kind?: string
|
|
34
|
+
options?: CustomFieldDefDto['options']
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
type CustomFieldValuesListProps = {
|
|
@@ -97,10 +99,23 @@ function renderDictionaryValues(
|
|
|
97
99
|
)
|
|
98
100
|
}
|
|
99
101
|
|
|
100
|
-
function
|
|
102
|
+
function resolveOptionLabel(value: unknown, options: CustomFieldDefDto['options']): unknown {
|
|
103
|
+
if (!options?.length || typeof value !== 'string') return value
|
|
104
|
+
const trimmed = value.trim()
|
|
105
|
+
const match = options.find((option) => option.value === trimmed)
|
|
106
|
+
return match ? match.label : value
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function applyOptionLabels(value: unknown, options: CustomFieldDefDto['options']): unknown {
|
|
110
|
+
if (!options?.length) return value
|
|
111
|
+
if (Array.isArray(value)) return value.map((entry) => resolveOptionLabel(entry, options))
|
|
112
|
+
return resolveOptionLabel(value, options)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function renderPrimitiveValues(value: unknown, kind?: string): React.ReactNode {
|
|
101
116
|
if (Array.isArray(value)) {
|
|
102
117
|
const parts = value
|
|
103
|
-
.map((entry) => stringifyCustomValue(entry))
|
|
118
|
+
.map((entry) => stringifyCustomValue(entry, { kind }))
|
|
104
119
|
.map((entry) => entry.trim())
|
|
105
120
|
.filter((entry) => entry.length > 0)
|
|
106
121
|
if (!parts.length) return null
|
|
@@ -117,7 +132,7 @@ function renderPrimitiveValues(value: unknown): React.ReactNode {
|
|
|
117
132
|
</div>
|
|
118
133
|
)
|
|
119
134
|
}
|
|
120
|
-
const label = stringifyCustomValue(value).trim()
|
|
135
|
+
const label = stringifyCustomValue(value, { kind }).trim()
|
|
121
136
|
if (!label.length) return null
|
|
122
137
|
return <span className="text-sm text-foreground">{label}</span>
|
|
123
138
|
}
|
|
@@ -179,6 +194,12 @@ function buildDisplayEntries(
|
|
|
179
194
|
definitions.forEach((def, index) => {
|
|
180
195
|
const normalizedKey = normalizeCustomFieldKey(def.key)
|
|
181
196
|
if (!normalizedKey) return
|
|
197
|
+
// Operational fields marked listVisible:false must not render — neither
|
|
198
|
+
// here nor as a definition-less "extra" below, so consume the key anyway.
|
|
199
|
+
if (def.listVisible === false) {
|
|
200
|
+
consumedKeys.add(normalizedKey)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
182
203
|
const entry = combined.get(normalizedKey)
|
|
183
204
|
if (!entry || isEmptyCustomValue(entry.value)) return
|
|
184
205
|
const label = resolveCustomFieldLabel(entry.label ?? def.label, entry.key)
|
|
@@ -191,6 +212,8 @@ function buildDisplayEntries(
|
|
|
191
212
|
value: entry.value,
|
|
192
213
|
dictionaryMap,
|
|
193
214
|
multi: def.multi ?? Array.isArray(entry.value),
|
|
215
|
+
kind: def.kind,
|
|
216
|
+
options: def.options,
|
|
194
217
|
})
|
|
195
218
|
consumedKeys.add(normalizedKey)
|
|
196
219
|
})
|
|
@@ -242,7 +265,8 @@ export function CustomFieldValuesList({
|
|
|
242
265
|
<div className={cn('grid gap-3 sm:grid-cols-2', className)}>
|
|
243
266
|
{displayEntries.map((entry, index) => {
|
|
244
267
|
const dictionaryContent = renderDictionaryValues(entry.value, entry.dictionaryMap, entry.multi)
|
|
245
|
-
const primitiveContent =
|
|
268
|
+
const primitiveContent =
|
|
269
|
+
dictionaryContent ?? renderPrimitiveValues(applyOptionLabels(entry.value, entry.options), entry.kind)
|
|
246
270
|
const content =
|
|
247
271
|
dictionaryContent ??
|
|
248
272
|
primitiveContent ??
|
|
@@ -27,9 +27,11 @@ const CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent
|
|
|
27
27
|
|
|
28
28
|
export type CreateDealFormProps = {
|
|
29
29
|
returnTo: string
|
|
30
|
+
/** Seed values merged over EMPTY_VALUES for the initial form state. Entries set to `undefined` are ignored (the EMPTY_VALUES default wins), so a sparse `Partial<BaseValues>` can never unset a required field. Additive: omitting it preserves current behavior. */
|
|
31
|
+
initialValues?: Partial<BaseValues>
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
export function CreateDealForm({ returnTo }: CreateDealFormProps) {
|
|
34
|
+
export function CreateDealForm({ returnTo, initialValues }: CreateDealFormProps) {
|
|
33
35
|
const t = useT()
|
|
34
36
|
const router = useRouter()
|
|
35
37
|
const tr = React.useCallback(
|
|
@@ -38,7 +40,10 @@ export function CreateDealForm({ returnTo }: CreateDealFormProps) {
|
|
|
38
40
|
[t],
|
|
39
41
|
)
|
|
40
42
|
|
|
41
|
-
const [values, setValues] = React.useState<BaseValues>(
|
|
43
|
+
const [values, setValues] = React.useState<BaseValues>(() => {
|
|
44
|
+
const definedSeedEntries = Object.entries(initialValues ?? {}).filter(([, seedValue]) => seedValue !== undefined)
|
|
45
|
+
return { ...EMPTY_VALUES, ...Object.fromEntries(definedSeedEntries) }
|
|
46
|
+
})
|
|
42
47
|
const [errors, setErrors] = React.useState<Record<string, string>>({})
|
|
43
48
|
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
|
44
49
|
|
|
@@ -53,6 +53,7 @@ export function resolveCustomFieldLabel(label: string | null | undefined, key: s
|
|
|
53
53
|
|
|
54
54
|
export function isEmptyCustomValue(value: unknown): boolean {
|
|
55
55
|
if (value === null || value === undefined) return true
|
|
56
|
+
if (value === false) return true
|
|
56
57
|
if (typeof value === 'string') return value.trim().length === 0
|
|
57
58
|
if (Array.isArray(value)) return value.length === 0 || value.every((entry) => isEmptyCustomValue(entry))
|
|
58
59
|
if (typeof value === 'object') {
|
|
@@ -65,11 +66,14 @@ export function isEmptyCustomValue(value: unknown): boolean {
|
|
|
65
66
|
return false
|
|
66
67
|
}
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
const DATE_FIELD_KINDS = new Set(['date', 'datetime'])
|
|
70
|
+
const ISO_DATE_PREFIX_RE = /^\d{4}-\d{2}-\d{2}([T ]|$)/
|
|
71
|
+
|
|
72
|
+
export function stringifyCustomValue(value: unknown, options?: { kind?: string }): string {
|
|
69
73
|
if (value === null || value === undefined) return ''
|
|
70
74
|
if (Array.isArray(value)) {
|
|
71
75
|
const parts = value
|
|
72
|
-
.map((entry) => stringifyCustomValue(entry))
|
|
76
|
+
.map((entry) => stringifyCustomValue(entry, options))
|
|
73
77
|
.map((entry) => entry.trim())
|
|
74
78
|
.filter((entry) => entry.length > 0)
|
|
75
79
|
return parts.join(', ')
|
|
@@ -81,7 +85,15 @@ export function stringifyCustomValue(value: unknown): string {
|
|
|
81
85
|
if (typeof value === 'string') {
|
|
82
86
|
const trimmed = value.trim()
|
|
83
87
|
if (!trimmed.length) return ''
|
|
84
|
-
|
|
88
|
+
// Date-format strings only when the definition says the field is a date,
|
|
89
|
+
// or (for definition-less extras) when the value is unambiguously ISO-like.
|
|
90
|
+
// `new Date(...)` parses far too loosely to run on every string — it used
|
|
91
|
+
// to mangle plain text such as "Report 2024" into a date.
|
|
92
|
+
const kind = options?.kind
|
|
93
|
+
if (kind ? DATE_FIELD_KINDS.has(kind) : ISO_DATE_PREFIX_RE.test(trimmed)) {
|
|
94
|
+
return formatDateTime(trimmed) ?? trimmed
|
|
95
|
+
}
|
|
96
|
+
return trimmed
|
|
85
97
|
}
|
|
86
98
|
if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean') {
|
|
87
99
|
return String(value)
|
|
@@ -16,56 +16,56 @@
|
|
|
16
16
|
"data_sync.dashboard.loadError": "Nie udało się załadować przebiegów",
|
|
17
17
|
"data_sync.dashboard.noRuns": "Brak przebiegów synchronizacji",
|
|
18
18
|
"data_sync.dashboard.schedule.cron": "Cron",
|
|
19
|
-
"data_sync.dashboard.schedule.cronHelp": "
|
|
20
|
-
"data_sync.dashboard.schedule.cronValue": "
|
|
21
|
-
"data_sync.dashboard.schedule.delete": "
|
|
22
|
-
"data_sync.dashboard.schedule.deleteError": "
|
|
23
|
-
"data_sync.dashboard.schedule.deleteSuccess": "
|
|
24
|
-
"data_sync.dashboard.schedule.deleting": "
|
|
25
|
-
"data_sync.dashboard.schedule.description": "
|
|
26
|
-
"data_sync.dashboard.schedule.enabled": "
|
|
27
|
-
"data_sync.dashboard.schedule.enabledHelp": "
|
|
28
|
-
"data_sync.dashboard.schedule.error": "
|
|
29
|
-
"data_sync.dashboard.schedule.fullSync": "
|
|
30
|
-
"data_sync.dashboard.schedule.fullSyncHelp": "
|
|
31
|
-
"data_sync.dashboard.schedule.interval": "
|
|
32
|
-
"data_sync.dashboard.schedule.intervalHelp": "
|
|
33
|
-
"data_sync.dashboard.schedule.intervalValue": "
|
|
34
|
-
"data_sync.dashboard.schedule.invalidValue": "
|
|
35
|
-
"data_sync.dashboard.schedule.lastRun": "
|
|
36
|
-
"data_sync.dashboard.schedule.neverRun": "
|
|
37
|
-
"data_sync.dashboard.schedule.none": "
|
|
38
|
-
"data_sync.dashboard.schedule.save": "
|
|
39
|
-
"data_sync.dashboard.schedule.saving": "
|
|
40
|
-
"data_sync.dashboard.schedule.status.disabled": "
|
|
41
|
-
"data_sync.dashboard.schedule.status.enabled": "
|
|
42
|
-
"data_sync.dashboard.schedule.status.none": "
|
|
43
|
-
"data_sync.dashboard.schedule.status.shortDisabled": "
|
|
44
|
-
"data_sync.dashboard.schedule.status.shortEnabled": "
|
|
45
|
-
"data_sync.dashboard.schedule.status.shortNone": "
|
|
46
|
-
"data_sync.dashboard.schedule.success": "
|
|
47
|
-
"data_sync.dashboard.schedule.timezone": "
|
|
48
|
-
"data_sync.dashboard.schedule.title": "
|
|
49
|
-
"data_sync.dashboard.schedule.type": "
|
|
19
|
+
"data_sync.dashboard.schedule.cronHelp": "Przykład: `0 * * * *` uruchamia zadanie na początku każdej godziny.",
|
|
20
|
+
"data_sync.dashboard.schedule.cronValue": "Wyrażenie cron",
|
|
21
|
+
"data_sync.dashboard.schedule.delete": "Usuń harmonogram",
|
|
22
|
+
"data_sync.dashboard.schedule.deleteError": "Nie udało się usunąć harmonogramu cyklicznego",
|
|
23
|
+
"data_sync.dashboard.schedule.deleteSuccess": "Usunięto harmonogram cykliczny",
|
|
24
|
+
"data_sync.dashboard.schedule.deleting": "Usuwanie...",
|
|
25
|
+
"data_sync.dashboard.schedule.description": "Zapisz powtarzalny harmonogram dla wybranej integracji, encji i kierunku bez opuszczania tego pulpitu.",
|
|
26
|
+
"data_sync.dashboard.schedule.enabled": "Harmonogram włączony",
|
|
27
|
+
"data_sync.dashboard.schedule.enabledHelp": "Wstrzymaj zadanie cykliczne bez usuwania definicji harmonogramu.",
|
|
28
|
+
"data_sync.dashboard.schedule.error": "Nie udało się zapisać harmonogramu cyklicznego",
|
|
29
|
+
"data_sync.dashboard.schedule.fullSync": "Uruchamiaj zaplanowane zadania jako pełną synchronizację",
|
|
30
|
+
"data_sync.dashboard.schedule.fullSyncHelp": "Gdy opcja jest włączona, każdy cykliczny przebieg zaczyna się od początku zamiast od zapisanego kursora.",
|
|
31
|
+
"data_sync.dashboard.schedule.interval": "Interwał",
|
|
32
|
+
"data_sync.dashboard.schedule.intervalHelp": "Przykład: `1h`, `6h` lub `24h` dla powtarzalnych interwałów.",
|
|
33
|
+
"data_sync.dashboard.schedule.intervalValue": "Interwał",
|
|
34
|
+
"data_sync.dashboard.schedule.invalidValue": "Podaj wartość harmonogramu przed zapisaniem.",
|
|
35
|
+
"data_sync.dashboard.schedule.lastRun": "Ostatni zaplanowany przebieg: {value}",
|
|
36
|
+
"data_sync.dashboard.schedule.neverRun": "Zapisano, ale żadne zaplanowane wykonanie jeszcze się nie zakończyło.",
|
|
37
|
+
"data_sync.dashboard.schedule.none": "Dla tego celu nie zapisano jeszcze harmonogramu cyklicznego.",
|
|
38
|
+
"data_sync.dashboard.schedule.save": "Zapisz harmonogram cykliczny",
|
|
39
|
+
"data_sync.dashboard.schedule.saving": "Zapisywanie...",
|
|
40
|
+
"data_sync.dashboard.schedule.status.disabled": "Harmonogram cykliczny wstrzymany",
|
|
41
|
+
"data_sync.dashboard.schedule.status.enabled": "Harmonogram cykliczny aktywny",
|
|
42
|
+
"data_sync.dashboard.schedule.status.none": "Brak harmonogramu cyklicznego",
|
|
43
|
+
"data_sync.dashboard.schedule.status.shortDisabled": "Wstrzymany",
|
|
44
|
+
"data_sync.dashboard.schedule.status.shortEnabled": "Zaplanowany",
|
|
45
|
+
"data_sync.dashboard.schedule.status.shortNone": "Tylko jednorazowo",
|
|
46
|
+
"data_sync.dashboard.schedule.success": "Zapisano harmonogram cykliczny",
|
|
47
|
+
"data_sync.dashboard.schedule.timezone": "Strefa czasowa",
|
|
48
|
+
"data_sync.dashboard.schedule.title": "Harmonogram cykliczny",
|
|
49
|
+
"data_sync.dashboard.schedule.type": "Typ harmonogramu",
|
|
50
50
|
"data_sync.dashboard.searchPlaceholder": "Szukaj po integracji, typie encji, statusie lub ID przebiegu",
|
|
51
|
-
"data_sync.dashboard.start.batchSize": "
|
|
52
|
-
"data_sync.dashboard.start.description": "
|
|
53
|
-
"data_sync.dashboard.start.error": "
|
|
54
|
-
"data_sync.dashboard.start.eyebrow": "
|
|
55
|
-
"data_sync.dashboard.start.fullSync": "
|
|
56
|
-
"data_sync.dashboard.start.fullSyncHelp": "
|
|
57
|
-
"data_sync.dashboard.start.invalidBatchSize": "
|
|
51
|
+
"data_sync.dashboard.start.batchSize": "Rozmiar partii",
|
|
52
|
+
"data_sync.dashboard.start.description": "Wybierz cel synchronizacji, uruchom przebieg doraźny albo zapisz harmonogram cykliczny dla tej samej encji i kierunku z poziomu tej strony.",
|
|
53
|
+
"data_sync.dashboard.start.error": "Nie udało się uruchomić przebiegu synchronizacji",
|
|
54
|
+
"data_sync.dashboard.start.eyebrow": "Uruchom raz lub ustaw cyklicznie",
|
|
55
|
+
"data_sync.dashboard.start.fullSync": "Uruchom jako pełną synchronizację",
|
|
56
|
+
"data_sync.dashboard.start.fullSyncHelp": "Zignoruj zapisany kursor i przetwórz w tym przebiegu całe źródło od nowa.",
|
|
57
|
+
"data_sync.dashboard.start.invalidBatchSize": "Rozmiar partii musi mieścić się w zakresie od 1 do 1000.",
|
|
58
58
|
"data_sync.dashboard.start.providerManaged": "Ta integracja uruchamia synchronizacje z własnego przepływu konfiguracji. Otwórz ustawienia integracji, aby kontynuować.",
|
|
59
|
-
"data_sync.dashboard.start.runNowDescription": "
|
|
60
|
-
"data_sync.dashboard.start.runNowFootnote": "
|
|
61
|
-
"data_sync.dashboard.start.runNowTitle": "
|
|
62
|
-
"data_sync.dashboard.start.status.credentialsMissing": "
|
|
63
|
-
"data_sync.dashboard.start.status.credentialsReady": "
|
|
64
|
-
"data_sync.dashboard.start.status.disabled": "
|
|
65
|
-
"data_sync.dashboard.start.status.enabled": "
|
|
66
|
-
"data_sync.dashboard.start.submit": "
|
|
67
|
-
"data_sync.dashboard.start.success": "
|
|
68
|
-
"data_sync.dashboard.start.title": "
|
|
59
|
+
"data_sync.dashboard.start.runNowDescription": "Użyj tej opcji dla najbliższej natychmiastowej synchronizacji. Rozmiar partii i tryb pełnej synchronizacji dotyczą wyłącznie tego ręcznego przebiegu.",
|
|
60
|
+
"data_sync.dashboard.start.runNowFootnote": "Ręczne przebiegi od razu pokazują postęp, a po uruchomieniu przenoszą na stronę szczegółów przebiegu.",
|
|
61
|
+
"data_sync.dashboard.start.runNowTitle": "Uruchom teraz jednorazowo",
|
|
62
|
+
"data_sync.dashboard.start.status.credentialsMissing": "Brak poświadczeń",
|
|
63
|
+
"data_sync.dashboard.start.status.credentialsReady": "Poświadczenia gotowe",
|
|
64
|
+
"data_sync.dashboard.start.status.disabled": "Integracja wyłączona",
|
|
65
|
+
"data_sync.dashboard.start.status.enabled": "Integracja włączona",
|
|
66
|
+
"data_sync.dashboard.start.submit": "Uruchom synchronizację",
|
|
67
|
+
"data_sync.dashboard.start.success": "Uruchomiono przebieg synchronizacji",
|
|
68
|
+
"data_sync.dashboard.start.title": "Uruchom lub zaplanuj synchronizację",
|
|
69
69
|
"data_sync.dashboard.status.cancelled": "Anulowany",
|
|
70
70
|
"data_sync.dashboard.status.completed": "Zakończony",
|
|
71
71
|
"data_sync.dashboard.status.failed": "Nieudany",
|
|
@@ -73,28 +73,28 @@
|
|
|
73
73
|
"data_sync.dashboard.status.pending": "Oczekujący",
|
|
74
74
|
"data_sync.dashboard.status.running": "W toku",
|
|
75
75
|
"data_sync.dashboard.title": "Przebiegi synchronizacji",
|
|
76
|
-
"data_sync.integrationTab.columns.actions": "
|
|
77
|
-
"data_sync.integrationTab.columns.lastRun": "
|
|
78
|
-
"data_sync.integrationTab.columns.value": "
|
|
79
|
-
"data_sync.integrationTab.credentialsMissing": "
|
|
80
|
-
"data_sync.integrationTab.credentialsMissingNotice": "
|
|
81
|
-
"data_sync.integrationTab.description": "
|
|
82
|
-
"data_sync.integrationTab.empty": "
|
|
83
|
-
"data_sync.integrationTab.fullSyncShort": "
|
|
84
|
-
"data_sync.integrationTab.integrationDisabled": "
|
|
85
|
-
"data_sync.integrationTab.integrationDisabledNotice": "
|
|
86
|
-
"data_sync.integrationTab.loadError": "
|
|
87
|
-
"data_sync.integrationTab.notAvailable": "
|
|
76
|
+
"data_sync.integrationTab.columns.actions": "Akcje",
|
|
77
|
+
"data_sync.integrationTab.columns.lastRun": "Ostatni przebieg",
|
|
78
|
+
"data_sync.integrationTab.columns.value": "Wartość",
|
|
79
|
+
"data_sync.integrationTab.credentialsMissing": "Skonfiguruj poświadczenia przed uruchomieniem synchronizacji.",
|
|
80
|
+
"data_sync.integrationTab.credentialsMissingNotice": "Wciąż brakuje poświadczeń. Możesz najpierw zapisać harmonogramy, ale ręczne i zaplanowane przebiegi będą kończyć się błędem, dopóki poświadczenia nie zostaną skonfigurowane.",
|
|
81
|
+
"data_sync.integrationTab.description": "Uruchamiaj jednorazowe synchronizacje albo zapisuj harmonogramy cykliczne dla każdej obsługiwanej encji bezpośrednio ze strony szczegółów integracji.",
|
|
82
|
+
"data_sync.integrationTab.empty": "Ten dostawca nie udostępnia jeszcze żadnych encji, które można zaplanować do synchronizacji.",
|
|
83
|
+
"data_sync.integrationTab.fullSyncShort": "Pełna",
|
|
84
|
+
"data_sync.integrationTab.integrationDisabled": "Włącz integrację przed uruchomieniem synchronizacji.",
|
|
85
|
+
"data_sync.integrationTab.integrationDisabledNotice": "Integracja jest wyłączona. Możesz teraz zapisać harmonogramy, ale przebiegi pozostaną zablokowane do czasu włączenia integracji.",
|
|
86
|
+
"data_sync.integrationTab.loadError": "Nie udało się załadować harmonogramów synchronizacji.",
|
|
87
|
+
"data_sync.integrationTab.notAvailable": "Ta integracja nie jest zarejestrowana jako dostawca synchronizacji danych.",
|
|
88
88
|
"data_sync.integrationTab.providerManaged": "Uruchom tę integrację z jej przepływu konfiguracji dostawcy.",
|
|
89
89
|
"data_sync.integrationTab.providerManagedNotice": "Ten dostawca wymaga własnego przepływu konfiguracji, zanim będzie można uruchomić synchronizację. Użyj karty dostawcy na tej stronie zamiast ogólnych harmonogramów.",
|
|
90
|
-
"data_sync.integrationTab.refresh": "
|
|
91
|
-
"data_sync.integrationTab.runError": "
|
|
92
|
-
"data_sync.integrationTab.runNowHelp": "
|
|
93
|
-
"data_sync.integrationTab.runNowTitle": "
|
|
94
|
-
"data_sync.integrationTab.runStarted": "
|
|
95
|
-
"data_sync.integrationTab.start": "
|
|
96
|
-
"data_sync.integrationTab.starting": "
|
|
97
|
-
"data_sync.integrationTab.title": "
|
|
90
|
+
"data_sync.integrationTab.refresh": "Odśwież",
|
|
91
|
+
"data_sync.integrationTab.runError": "Nie udało się uruchomić synchronizacji.",
|
|
92
|
+
"data_sync.integrationTab.runNowHelp": "Opcja Uruchom teraz korzysta z pola pełnej synchronizacji z tego samego wiersza i od razu rozpoczyna śledzenie postępu w Synchronizacji danych.",
|
|
93
|
+
"data_sync.integrationTab.runNowTitle": "Ręczne przebiegi",
|
|
94
|
+
"data_sync.integrationTab.runStarted": "Uruchomiono przebieg synchronizacji.",
|
|
95
|
+
"data_sync.integrationTab.start": "Uruchom teraz",
|
|
96
|
+
"data_sync.integrationTab.starting": "Uruchamianie...",
|
|
97
|
+
"data_sync.integrationTab.title": "Harmonogramy synchronizacji",
|
|
98
98
|
"data_sync.nav.title": "Synchronizacja danych",
|
|
99
99
|
"data_sync.runs.detail.back": "Powrót do przebiegów",
|
|
100
100
|
"data_sync.runs.detail.cancel": "Anuluj",
|
|
@@ -2,11 +2,13 @@ export const metadata = {
|
|
|
2
2
|
requireAuth: true,
|
|
3
3
|
requireFeatures: ['directory.organizations.manage'],
|
|
4
4
|
pageTitle: 'Edit Organization',
|
|
5
|
+
pageTitleKey: 'directory.organizations.form.title.edit',
|
|
5
6
|
pageGroup: 'Directory',
|
|
7
|
+
pageGroupKey: 'directory.nav.group',
|
|
6
8
|
navHidden: true,
|
|
7
9
|
breadcrumb: [
|
|
8
|
-
{ label: 'Organizations', href: '/backend/directory/organizations' },
|
|
9
|
-
{ label: 'Edit' },
|
|
10
|
+
{ label: 'Organizations', labelKey: 'directory.nav.organizations', href: '/backend/directory/organizations' },
|
|
11
|
+
{ label: 'Edit', labelKey: 'common.edit' },
|
|
10
12
|
],
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -2,11 +2,13 @@ export const metadata = {
|
|
|
2
2
|
requireAuth: true,
|
|
3
3
|
requireFeatures: ['directory.tenants.manage'],
|
|
4
4
|
pageTitle: 'Edit Tenant',
|
|
5
|
+
pageTitleKey: 'directory.tenants.form.title.edit',
|
|
5
6
|
pageGroup: 'Directory',
|
|
7
|
+
pageGroupKey: 'directory.nav.group',
|
|
6
8
|
navHidden: true,
|
|
7
9
|
breadcrumb: [
|
|
8
|
-
{ label: 'Tenants', href: '/backend/directory/tenants' },
|
|
9
|
-
{ label: 'Edit' },
|
|
10
|
+
{ label: 'Tenants', labelKey: 'directory.nav.tenants', href: '/backend/directory/tenants' },
|
|
11
|
+
{ label: 'Edit', labelKey: 'common.edit' },
|
|
10
12
|
],
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
runIntegrationMutationGuardAfterSuccess,
|
|
22
22
|
runIntegrationMutationGuards,
|
|
23
23
|
} from '../../guards'
|
|
24
|
+
import { resolveIntegrationsOrganizationId } from '../../../lib/organization-scope'
|
|
24
25
|
|
|
25
26
|
const idParamsSchema = z.object({ id: z.string().min(1) })
|
|
26
27
|
|
|
@@ -44,7 +45,8 @@ function resolveParams(ctx: { params?: Promise<{ id?: string }> | { id?: string
|
|
|
44
45
|
|
|
45
46
|
export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
46
47
|
const auth = await getAuthFromRequest(req)
|
|
47
|
-
|
|
48
|
+
const organizationId = resolveIntegrationsOrganizationId(auth)
|
|
49
|
+
if (!auth?.tenantId || !organizationId) {
|
|
48
50
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -61,7 +63,7 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
|
|
|
61
63
|
|
|
62
64
|
const container = await createRequestContainer()
|
|
63
65
|
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
64
|
-
const scope = { organizationId:
|
|
66
|
+
const scope = { organizationId: organizationId, tenantId: auth.tenantId }
|
|
65
67
|
|
|
66
68
|
let values: Record<string, unknown> | null
|
|
67
69
|
let updatedAt: Date | null
|
|
@@ -89,7 +91,8 @@ export async function GET(req: Request, ctx: { params?: Promise<{ id?: string }>
|
|
|
89
91
|
|
|
90
92
|
export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }> | { id?: string } }) {
|
|
91
93
|
const auth = await getAuthFromRequest(req)
|
|
92
|
-
|
|
94
|
+
const organizationId = resolveIntegrationsOrganizationId(auth)
|
|
95
|
+
if (!auth?.tenantId || !organizationId) {
|
|
93
96
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -115,7 +118,7 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
|
|
|
115
118
|
container,
|
|
116
119
|
{
|
|
117
120
|
tenantId: auth.tenantId,
|
|
118
|
-
organizationId
|
|
121
|
+
organizationId,
|
|
119
122
|
userId: auth.sub ?? '',
|
|
120
123
|
resourceKind: 'integrations.integration',
|
|
121
124
|
resourceId: integration.id,
|
|
@@ -141,7 +144,7 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
144
|
-
const scope = { organizationId:
|
|
147
|
+
const scope = { organizationId: organizationId, tenantId: auth.tenantId }
|
|
145
148
|
const schema = credentialsService.getSchema(integration.id)
|
|
146
149
|
|
|
147
150
|
try {
|
|
@@ -190,13 +193,13 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
|
|
|
190
193
|
await emitIntegrationsEvent('integrations.credentials.updated', {
|
|
191
194
|
integrationId: integration.id,
|
|
192
195
|
tenantId: auth.tenantId,
|
|
193
|
-
organizationId
|
|
196
|
+
organizationId,
|
|
194
197
|
userId: auth.sub,
|
|
195
198
|
})
|
|
196
199
|
|
|
197
200
|
await runIntegrationMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
|
|
198
201
|
tenantId: auth.tenantId,
|
|
199
|
-
organizationId
|
|
202
|
+
organizationId,
|
|
200
203
|
userId: auth.sub ?? '',
|
|
201
204
|
resourceKind: 'integrations.integration',
|
|
202
205
|
resourceId: integration.id,
|