@open-mercato/sync-akeneo 0.6.6-develop.6299.1.29224e2d86 → 0.6.6-develop.6304.1.4cf2b975cb
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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sync_akeneo/api/discovery/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CredentialsService } from '@open-mercato/core/modules/integrations/lib/credentials-service'\nimport { CatalogPriceKind } from '@open-mercato/core/modules/catalog/data/entities'\nimport { SalesChannel } from '@open-mercato/core/modules/sales/data/entities'\nimport { createAkeneoClient } from '../../lib/client'\nimport { akeneoDiscoveryQuerySchema, akeneoDiscoveryResponseSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['Akeneo'],\n summary: 'Load Akeneo discovery metadata for field mapping',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ ok: false, message: 'Unauthorized' }, { status: 401 })\n }\n\n const parsed = akeneoDiscoveryQuerySchema.safeParse({\n refresh: new URL(req.url).searchParams.get('refresh') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, message: 'Invalid query' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const em = container.resolve('em') as EntityManager\n const [localChannels, priceKinds] = await Promise.all([\n findWithDecryption(em, SalesChannel, {\n organizationId: auth.orgId,\n tenantId: auth.tenantId,\n deletedAt: null,\n isActive: true,\n }, {\n fields: ['code', 'name'],\n orderBy: { name: 'asc' },\n }, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }),\n findWithDecryption(em, CatalogPriceKind, {\n tenantId: auth.tenantId,\n deletedAt: null,\n isActive: true,\n }, {\n fields: ['code', 'title', 'displayMode'],\n orderBy: { title: 'asc' },\n }, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }),\n ])\n const credentials = await credentialsService.resolve('sync_akeneo', {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (!credentials) {\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: false,\n locales: [],\n channels: [],\n attributes: [],\n families: [],\n familyVariants: [],\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message: 'Save Akeneo credentials before loading remote fields.',\n })\n return NextResponse.json(payload, { status: 200 })\n }\n\n try {\n const client = createAkeneoClient(credentials)\n const discovery = await client.collectDiscoveryData()\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: true,\n locales: discovery.locales,\n channels: discovery.channels,\n attributes: discovery.attributes,\n families: discovery.families,\n familyVariants: discovery.familyVariants,\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message: discovery.version ? `Akeneo ${discovery.version}` : undefined,\n })\n return NextResponse.json(payload)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to load Akeneo metadata'\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: false,\n locales: [],\n channels: [],\n attributes: [],\n families: [],\n familyVariants: [],\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message,\n })\n return NextResponse.json(payload, { status: 200 })\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AAEnC,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,4BAA4B,qCAAqC;AAEnE,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,EAAE;AACrE;AAEO,MAAM,UAAU;AAAA,EACrB,MAAM,CAAC,QAAQ;AAAA,EACf,SAAS;AACX;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM,YAAY,CAAC,KAAK,OAAO;AAClC,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,SAAS,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClF;AAEA,QAAM,SAAS,2BAA2B,UAAU;AAAA,IAClD,SAAS,IAAI,IAAI,IAAI,GAAG,EAAE,aAAa,IAAI,SAAS,KAAK;AAAA,EAC3D,CAAC;AACD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,SAAS,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,qBAAqB,UAAU,QAAQ,+BAA+B;AAC5E,QAAM,KAAK,UAAU,QAAQ,IAAI;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { CredentialsService } from '@open-mercato/core/modules/integrations/lib/credentials-service'\nimport { CatalogPriceKind } from '@open-mercato/core/modules/catalog/data/entities'\nimport { SalesChannel } from '@open-mercato/core/modules/sales/data/entities'\nimport { createAkeneoClient } from '../../lib/client'\nimport { akeneoDiscoveryQuerySchema, akeneoDiscoveryResponseSchema } from '../../data/validators'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['data_sync.configure'] },\n}\n\nexport const openApi = {\n tags: ['Akeneo'],\n summary: 'Load Akeneo discovery metadata for field mapping',\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId || !auth.orgId) {\n return NextResponse.json({ ok: false, message: 'Unauthorized' }, { status: 401 })\n }\n\n const parsed = akeneoDiscoveryQuerySchema.safeParse({\n refresh: new URL(req.url).searchParams.get('refresh') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, message: 'Invalid query' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService\n const em = container.resolve('em') as EntityManager\n // Audited for #3386 rollout (P3): both orderBy fields are non-encrypted.\n // SalesChannel.name is absent from the sales:sales_channel encryption map\n // (only address/contact fields are encrypted). CatalogPriceKind has no\n // encryption map at all. This is a discovery endpoint with no pagination,\n // so the #3278 two-phase encrypted-sort does not apply. Covered by\n // __tests__/discovery-route.test.ts.\n const [localChannels, priceKinds] = await Promise.all([\n findWithDecryption(em, SalesChannel, {\n organizationId: auth.orgId,\n tenantId: auth.tenantId,\n deletedAt: null,\n isActive: true,\n }, {\n fields: ['code', 'name'],\n orderBy: { name: 'asc' },\n }, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }),\n findWithDecryption(em, CatalogPriceKind, {\n tenantId: auth.tenantId,\n deletedAt: null,\n isActive: true,\n }, {\n fields: ['code', 'title', 'displayMode'],\n orderBy: { title: 'asc' },\n }, {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n }),\n ])\n const credentials = await credentialsService.resolve('sync_akeneo', {\n organizationId: auth.orgId as string,\n tenantId: auth.tenantId,\n })\n\n if (!credentials) {\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: false,\n locales: [],\n channels: [],\n attributes: [],\n families: [],\n familyVariants: [],\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message: 'Save Akeneo credentials before loading remote fields.',\n })\n return NextResponse.json(payload, { status: 200 })\n }\n\n try {\n const client = createAkeneoClient(credentials)\n const discovery = await client.collectDiscoveryData()\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: true,\n locales: discovery.locales,\n channels: discovery.channels,\n attributes: discovery.attributes,\n families: discovery.families,\n familyVariants: discovery.familyVariants,\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message: discovery.version ? `Akeneo ${discovery.version}` : undefined,\n })\n return NextResponse.json(payload)\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Failed to load Akeneo metadata'\n const payload = akeneoDiscoveryResponseSchema.parse({\n ok: false,\n locales: [],\n channels: [],\n attributes: [],\n families: [],\n familyVariants: [],\n localChannels: localChannels\n .filter((channel) => typeof channel.code === 'string' && channel.code.trim().length > 0)\n .map((channel) => ({ code: String(channel.code), name: channel.name })),\n priceKinds: priceKinds.map((priceKind) => ({\n code: priceKind.code,\n title: priceKind.title,\n displayMode: priceKind.displayMode,\n })),\n message,\n })\n return NextResponse.json(payload, { status: 200 })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/sync-akeneo",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6304.1.4cf2b975cb",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,18 +53,18 @@
|
|
|
53
53
|
}
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@open-mercato/core": "0.6.6-develop.
|
|
57
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
56
|
+
"@open-mercato/core": "0.6.6-develop.6304.1.4cf2b975cb",
|
|
57
|
+
"@open-mercato/ui": "0.6.6-develop.6304.1.4cf2b975cb",
|
|
58
58
|
"node-html-markdown": "^2.0.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@mikro-orm/postgresql": "^7.0.14",
|
|
62
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
62
|
+
"@open-mercato/shared": "0.6.6-develop.6304.1.4cf2b975cb",
|
|
63
63
|
"react": "^19.0.0",
|
|
64
64
|
"react-dom": "^19.0.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
67
|
+
"@open-mercato/shared": "0.6.6-develop.6304.1.4cf2b975cb",
|
|
68
68
|
"@types/jest": "^30.0.0",
|
|
69
69
|
"@types/react": "^19.2.17",
|
|
70
70
|
"@types/react-dom": "^19.2.3",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** @jest-environment node */
|
|
2
|
+
|
|
3
|
+
// P3 audit coverage for #3386: confirms that both findWithDecryption calls in
|
|
4
|
+
// the discovery route sort on non-encrypted columns (SalesChannel.name,
|
|
5
|
+
// CatalogPriceKind.title), so the #3278 two-phase encrypted-sort path is not
|
|
6
|
+
// needed here.
|
|
7
|
+
|
|
8
|
+
const tenantId = '11111111-1111-4111-8111-111111111111'
|
|
9
|
+
const orgId = '22222222-2222-4222-8222-222222222222'
|
|
10
|
+
|
|
11
|
+
type FindWithDecryptionArgs = [unknown, unknown, unknown, Record<string, unknown>, unknown]
|
|
12
|
+
const findWithDecryptionMock = jest.fn<Promise<unknown[]>, FindWithDecryptionArgs>(async () => [])
|
|
13
|
+
|
|
14
|
+
jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
|
|
15
|
+
findWithDecryption: (...args: unknown[]) => findWithDecryptionMock(...args),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
jest.mock('@open-mercato/shared/lib/auth/server', () => ({
|
|
19
|
+
getAuthFromRequest: async () => ({ tenantId, orgId, sub: null }),
|
|
20
|
+
}))
|
|
21
|
+
|
|
22
|
+
jest.mock('@open-mercato/shared/lib/di/container', () => ({
|
|
23
|
+
createRequestContainer: async () => ({
|
|
24
|
+
resolve: (token: string) => {
|
|
25
|
+
if (token === 'em') return {}
|
|
26
|
+
if (token === 'integrationCredentialsService') {
|
|
27
|
+
return {
|
|
28
|
+
resolve: async () => null,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
}))
|
|
35
|
+
|
|
36
|
+
import { GET } from '../api/discovery/route'
|
|
37
|
+
import { SalesChannel } from '@open-mercato/core/modules/sales/data/entities'
|
|
38
|
+
import { CatalogPriceKind } from '@open-mercato/core/modules/catalog/data/entities'
|
|
39
|
+
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
findWithDecryptionMock.mockClear()
|
|
42
|
+
findWithDecryptionMock.mockResolvedValue([])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('discovery route — encrypted-sort audit (#3386 P3)', () => {
|
|
46
|
+
it('fetches SalesChannel with orderBy name, which is not an encrypted field', async () => {
|
|
47
|
+
const res = await GET(new Request('http://localhost/api/data-sync/akeneo/discovery'))
|
|
48
|
+
expect(res.status).toBe(200)
|
|
49
|
+
|
|
50
|
+
const salesChannelCall = findWithDecryptionMock.mock.calls.find(
|
|
51
|
+
([, entity]) => entity === SalesChannel,
|
|
52
|
+
)
|
|
53
|
+
expect(salesChannelCall).toBeDefined()
|
|
54
|
+
// arg[3] is the ORM find options (fields + orderBy); arg[2] is the where clause
|
|
55
|
+
expect(salesChannelCall![3]).toMatchObject({ orderBy: { name: 'asc' } })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('fetches CatalogPriceKind with orderBy title, which is not an encrypted field', async () => {
|
|
59
|
+
const res = await GET(new Request('http://localhost/api/data-sync/akeneo/discovery'))
|
|
60
|
+
expect(res.status).toBe(200)
|
|
61
|
+
|
|
62
|
+
const priceKindCall = findWithDecryptionMock.mock.calls.find(
|
|
63
|
+
([, entity]) => entity === CatalogPriceKind,
|
|
64
|
+
)
|
|
65
|
+
expect(priceKindCall).toBeDefined()
|
|
66
|
+
// arg[3] is the ORM find options (fields + orderBy); arg[2] is the where clause
|
|
67
|
+
expect(priceKindCall![3]).toMatchObject({ orderBy: { title: 'asc' } })
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('returns ok:false with empty discovery data when credentials are absent', async () => {
|
|
71
|
+
const res = await GET(new Request('http://localhost/api/data-sync/akeneo/discovery'))
|
|
72
|
+
expect(res.status).toBe(200)
|
|
73
|
+
const body = await res.json()
|
|
74
|
+
expect(body.ok).toBe(false)
|
|
75
|
+
expect(body.locales).toEqual([])
|
|
76
|
+
expect(body.channels).toEqual([])
|
|
77
|
+
})
|
|
78
|
+
})
|
|
@@ -34,6 +34,12 @@ export async function GET(req: Request) {
|
|
|
34
34
|
const container = await createRequestContainer()
|
|
35
35
|
const credentialsService = container.resolve('integrationCredentialsService') as CredentialsService
|
|
36
36
|
const em = container.resolve('em') as EntityManager
|
|
37
|
+
// Audited for #3386 rollout (P3): both orderBy fields are non-encrypted.
|
|
38
|
+
// SalesChannel.name is absent from the sales:sales_channel encryption map
|
|
39
|
+
// (only address/contact fields are encrypted). CatalogPriceKind has no
|
|
40
|
+
// encryption map at all. This is a discovery endpoint with no pagination,
|
|
41
|
+
// so the #3278 two-phase encrypted-sort does not apply. Covered by
|
|
42
|
+
// __tests__/discovery-route.test.ts.
|
|
37
43
|
const [localChannels, priceKinds] = await Promise.all([
|
|
38
44
|
findWithDecryption(em, SalesChannel, {
|
|
39
45
|
organizationId: auth.orgId,
|