@open-mercato/core 0.4.6-develop-bbfd75b1c8 → 0.4.6-develop-2ba4e02ffb

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/modules/data_sync/api/mappings/[id]/route.js +2 -2
  2. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  3. package/dist/modules/data_sync/api/mappings/route.js +2 -2
  4. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  5. package/dist/modules/integrations/api/[id]/route.js +9 -1
  6. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  7. package/dist/modules/integrations/api/logs/route.js +2 -2
  8. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  9. package/dist/modules/integrations/backend/integrations/[id]/page.js +5 -1
  10. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  11. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +5 -1
  12. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  13. package/dist/modules/integrations/lib/credentials-service.js +5 -1
  14. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  15. package/dist/modules/integrations/lib/health-service.js.map +2 -2
  16. package/dist/modules/integrations/lib/log-service.js.map +2 -2
  17. package/dist/modules/integrations/lib/state-service.js.map +2 -2
  18. package/dist/modules/sales/api/quotes/convert/route.js +31 -14
  19. package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
  20. package/dist/modules/sales/api/quotes/send/route.js +31 -14
  21. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  22. package/package.json +2 -2
  23. package/src/modules/data_sync/api/mappings/[id]/route.ts +2 -2
  24. package/src/modules/data_sync/api/mappings/route.ts +2 -2
  25. package/src/modules/integrations/api/[id]/route.ts +9 -1
  26. package/src/modules/integrations/api/logs/route.ts +3 -3
  27. package/src/modules/integrations/backend/integrations/[id]/page.tsx +8 -2
  28. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +8 -2
  29. package/src/modules/integrations/lib/credentials-service.ts +6 -2
  30. package/src/modules/integrations/lib/health-service.ts +1 -2
  31. package/src/modules/integrations/lib/log-service.ts +1 -1
  32. package/src/modules/integrations/lib/state-service.ts +1 -1
  33. package/src/modules/sales/api/quotes/convert/route.ts +55 -14
  34. package/src/modules/sales/api/quotes/send/route.ts +55 -14
  35. package/dist/modules/integrations/lib/types.js +0 -1
  36. package/dist/modules/integrations/lib/types.js.map +0 -7
  37. package/src/modules/integrations/lib/types.ts +0 -4
@@ -12,12 +12,18 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
12
12
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
13
13
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
14
14
  import { useT } from '@open-mercato/shared/lib/i18n/context'
15
- import type { IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'
15
+ import type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'
16
16
  import { LoadingMessage } from '@open-mercato/ui/backend/detail'
17
17
  import { ErrorMessage } from '@open-mercato/ui/backend/detail'
18
18
 
19
19
  type CredentialField = IntegrationCredentialField
20
20
 
21
+ const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])
22
+
23
+ function isEditableCredentialField(field: CredentialField): boolean {
24
+ return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)
25
+ }
26
+
21
27
  type BundleIntegration = {
22
28
  id: string
23
29
  title: string
@@ -131,7 +137,7 @@ export default function BundleConfigPage() {
131
137
  if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>
132
138
  if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>
133
139
 
134
- const credFields = detail.bundle.credentials?.fields ?? []
140
+ const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)
135
141
 
136
142
  return (
137
143
  <Page>
@@ -3,10 +3,14 @@ import type { EntityManager } from '@mikro-orm/postgresql'
3
3
  import { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
4
4
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
5
5
  import { createKmsService } from '@open-mercato/shared/lib/encryption/kms'
6
- import { getBundle, getIntegration, resolveIntegrationCredentialsSchema } from '@open-mercato/shared/modules/integrations/types'
6
+ import {
7
+ getBundle,
8
+ getIntegration,
9
+ resolveIntegrationCredentialsSchema,
10
+ type IntegrationScope,
11
+ } from '@open-mercato/shared/modules/integrations/types'
7
12
  import { EncryptionMap } from '../../entities/data/entities'
8
13
  import { IntegrationCredentials } from '../data/entities'
9
- import type { IntegrationScope } from './types'
10
14
 
11
15
  const ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'
12
16
  const DERIVED_KEY_CONTEXT = 'integrations.credentials'
@@ -1,8 +1,7 @@
1
1
  import type { AwilixContainer } from 'awilix'
2
2
  import type { IntegrationStateService } from './state-service'
3
3
  import type { IntegrationLogService } from './log-service'
4
- import { getIntegration, getBundle } from '@open-mercato/shared/modules/integrations/types'
5
- import type { IntegrationScope } from './types'
4
+ import { getIntegration, getBundle, type IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
6
5
 
7
6
  type HealthCheckResult = {
8
7
  status: 'healthy' | 'degraded' | 'unhealthy'
@@ -1,8 +1,8 @@
1
1
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
2
2
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
3
4
  import type { ListIntegrationLogsQuery } from '../data/validators'
4
5
  import { IntegrationLog } from '../data/entities'
5
- import type { IntegrationScope } from './types'
6
6
 
7
7
  type LogInput = {
8
8
  integrationId: string
@@ -1,7 +1,7 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
3
4
  import { IntegrationState } from '../data/entities'
4
- import type { IntegrationScope } from './types'
5
5
 
6
6
  export function createIntegrationStateService(em: EntityManager) {
7
7
  return {
@@ -9,10 +9,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
9
9
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
10
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
11
11
  import {
12
- runCrudMutationGuardAfterSuccess,
13
- validateCrudMutationGuard,
14
- type CrudMutationGuardValidationResult,
15
- } from '@open-mercato/shared/lib/crud/mutation-guard'
12
+ bridgeLegacyGuard,
13
+ runMutationGuards,
14
+ type MutationGuard,
15
+ type MutationGuardInput,
16
+ } from '@open-mercato/shared/lib/crud/mutation-guard-registry'
16
17
  import { withScopedPayload } from '../../utils'
17
18
 
18
19
  const convertSchema = z.object({
@@ -29,9 +30,51 @@ type RequestContext = {
29
30
  ctx: CommandRuntimeContext
30
31
  }
31
32
 
32
- function buildMutationGuardErrorResponse(validation: CrudMutationGuardValidationResult): NextResponse | null {
33
- if (validation.ok) return null
34
- return NextResponse.json(validation.body, { status: validation.status })
33
+ function resolveUserFeatures(auth: unknown): string[] {
34
+ const features = (auth as { features?: unknown })?.features
35
+ if (!Array.isArray(features)) return []
36
+ return features.filter((value): value is string => typeof value === 'string')
37
+ }
38
+
39
+ async function runGuards(
40
+ ctx: CommandRuntimeContext,
41
+ input: MutationGuardInput,
42
+ ): Promise<{
43
+ ok: boolean
44
+ errorBody?: Record<string, unknown>
45
+ errorStatus?: number
46
+ afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
47
+ }> {
48
+ const legacyGuard = bridgeLegacyGuard(ctx.container)
49
+ if (!legacyGuard) {
50
+ return { ok: true, afterSuccessCallbacks: [] }
51
+ }
52
+
53
+ return runMutationGuards([legacyGuard], input, {
54
+ userFeatures: resolveUserFeatures(ctx.auth),
55
+ })
56
+ }
57
+
58
+ async function runGuardAfterSuccessCallbacks(
59
+ callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
60
+ input: {
61
+ tenantId: string
62
+ organizationId: string | null
63
+ userId: string
64
+ resourceKind: string
65
+ resourceId: string
66
+ operation: 'create' | 'update' | 'delete'
67
+ requestMethod: string
68
+ requestHeaders: Headers
69
+ },
70
+ ): Promise<void> {
71
+ for (const callback of callbacks) {
72
+ if (!callback.guard.afterSuccess) continue
73
+ await callback.guard.afterSuccess({
74
+ ...input,
75
+ metadata: callback.metadata ?? null,
76
+ })
77
+ }
35
78
  }
36
79
 
37
80
  async function resolveRequestContext(req: Request): Promise<RequestContext> {
@@ -70,7 +113,7 @@ export async function POST(req: Request) {
70
113
  const payload = await req.json().catch(() => ({}))
71
114
  const scoped = withScopedPayload(payload ?? {}, ctx, translate)
72
115
  const input = convertSchema.parse(scoped)
73
- const mutationGuardValidation = await validateCrudMutationGuard(ctx.container, {
116
+ const guardResult = await runGuards(ctx, {
74
117
  tenantId: ctx.auth?.tenantId ?? '',
75
118
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
76
119
  userId: ctx.auth?.sub ?? '',
@@ -80,9 +123,8 @@ export async function POST(req: Request) {
80
123
  requestMethod: req.method,
81
124
  requestHeaders: req.headers,
82
125
  })
83
- if (mutationGuardValidation) {
84
- const lockErrorResponse = buildMutationGuardErrorResponse(mutationGuardValidation)
85
- if (lockErrorResponse) return lockErrorResponse
126
+ if (!guardResult.ok) {
127
+ return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
86
128
  }
87
129
  const commandBus = ctx.container.resolve('commandBus') as CommandBus
88
130
  const { result, logEntry } = await commandBus.execute<
@@ -112,8 +154,8 @@ export async function POST(req: Request) {
112
154
  )
113
155
  }
114
156
 
115
- if (mutationGuardValidation?.ok && mutationGuardValidation.shouldRunAfterSuccess) {
116
- await runCrudMutationGuardAfterSuccess(ctx.container, {
157
+ if (guardResult.afterSuccessCallbacks.length) {
158
+ await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
117
159
  tenantId: ctx.auth?.tenantId ?? '',
118
160
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
119
161
  userId: ctx.auth?.sub ?? '',
@@ -122,7 +164,6 @@ export async function POST(req: Request) {
122
164
  operation: 'update',
123
165
  requestMethod: req.method,
124
166
  requestHeaders: req.headers,
125
- metadata: mutationGuardValidation.metadata ?? null,
126
167
  })
127
168
  }
128
169
 
@@ -8,10 +8,11 @@ import { resolveTranslations, detectLocale } from '@open-mercato/shared/lib/i18n
8
8
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
9
9
  import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
10
10
  import {
11
- runCrudMutationGuardAfterSuccess,
12
- validateCrudMutationGuard,
13
- type CrudMutationGuardValidationResult,
14
- } from '@open-mercato/shared/lib/crud/mutation-guard'
11
+ bridgeLegacyGuard,
12
+ runMutationGuards,
13
+ type MutationGuard,
14
+ type MutationGuardInput,
15
+ } from '@open-mercato/shared/lib/crud/mutation-guard-registry'
15
16
  import type { EntityManager } from '@mikro-orm/postgresql'
16
17
  import crypto from 'node:crypto'
17
18
  import { withScopedPayload } from '../../utils'
@@ -29,9 +30,51 @@ type RequestContext = {
29
30
  ctx: CommandRuntimeContext
30
31
  }
31
32
 
32
- function buildMutationGuardErrorResponse(validation: CrudMutationGuardValidationResult): NextResponse | null {
33
- if (validation.ok) return null
34
- return NextResponse.json(validation.body, { status: validation.status })
33
+ function resolveUserFeatures(auth: unknown): string[] {
34
+ const features = (auth as { features?: unknown })?.features
35
+ if (!Array.isArray(features)) return []
36
+ return features.filter((value): value is string => typeof value === 'string')
37
+ }
38
+
39
+ async function runGuards(
40
+ ctx: CommandRuntimeContext,
41
+ input: MutationGuardInput,
42
+ ): Promise<{
43
+ ok: boolean
44
+ errorBody?: Record<string, unknown>
45
+ errorStatus?: number
46
+ afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
47
+ }> {
48
+ const legacyGuard = bridgeLegacyGuard(ctx.container)
49
+ if (!legacyGuard) {
50
+ return { ok: true, afterSuccessCallbacks: [] }
51
+ }
52
+
53
+ return runMutationGuards([legacyGuard], input, {
54
+ userFeatures: resolveUserFeatures(ctx.auth),
55
+ })
56
+ }
57
+
58
+ async function runGuardAfterSuccessCallbacks(
59
+ callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
60
+ input: {
61
+ tenantId: string
62
+ organizationId: string | null
63
+ userId: string
64
+ resourceKind: string
65
+ resourceId: string
66
+ operation: 'create' | 'update' | 'delete'
67
+ requestMethod: string
68
+ requestHeaders: Headers
69
+ },
70
+ ): Promise<void> {
71
+ for (const callback of callbacks) {
72
+ if (!callback.guard.afterSuccess) continue
73
+ await callback.guard.afterSuccess({
74
+ ...input,
75
+ metadata: callback.metadata ?? null,
76
+ })
77
+ }
35
78
  }
36
79
 
37
80
  async function resolveRequestContext(req: Request): Promise<RequestContext> {
@@ -85,7 +128,7 @@ export async function POST(req: Request) {
85
128
  const payload = await req.json().catch(() => ({}))
86
129
  const scoped = withScopedPayload(payload ?? {}, ctx, translate)
87
130
  const input = quoteSendSchema.parse(scoped)
88
- const mutationGuardValidation = await validateCrudMutationGuard(ctx.container, {
131
+ const guardResult = await runGuards(ctx, {
89
132
  tenantId: ctx.auth?.tenantId ?? '',
90
133
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
91
134
  userId: ctx.auth?.sub ?? '',
@@ -95,9 +138,8 @@ export async function POST(req: Request) {
95
138
  requestMethod: req.method,
96
139
  requestHeaders: req.headers,
97
140
  })
98
- if (mutationGuardValidation) {
99
- const lockErrorResponse = buildMutationGuardErrorResponse(mutationGuardValidation)
100
- if (lockErrorResponse) return lockErrorResponse
141
+ if (!guardResult.ok) {
142
+ return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
101
143
  }
102
144
 
103
145
  const em = (ctx.container.resolve('em') as EntityManager).fork()
@@ -163,8 +205,8 @@ export async function POST(req: Request) {
163
205
  react: QuoteSentEmail({ url, copy }),
164
206
  })
165
207
 
166
- if (mutationGuardValidation?.ok && mutationGuardValidation.shouldRunAfterSuccess) {
167
- await runCrudMutationGuardAfterSuccess(ctx.container, {
208
+ if (guardResult.afterSuccessCallbacks.length) {
209
+ await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
168
210
  tenantId: ctx.auth?.tenantId ?? '',
169
211
  organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
170
212
  userId: ctx.auth?.sub ?? '',
@@ -173,7 +215,6 @@ export async function POST(req: Request) {
173
215
  operation: 'update',
174
216
  requestMethod: req.method,
175
217
  requestHeaders: req.headers,
176
- metadata: mutationGuardValidation.metadata ?? null,
177
218
  })
178
219
  }
179
220
 
@@ -1 +0,0 @@
1
- //# sourceMappingURL=types.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }
@@ -1,4 +0,0 @@
1
- export type IntegrationScope = {
2
- organizationId: string
3
- tenantId: string
4
- }