fix: fetchMetafieldDefinitions + ensureMetafieldDefinition via GraphQL (REST n'expose pas cet endpoint)
This commit is contained in:
@@ -43,43 +43,98 @@ export function matchMetafields(columnHeaders: string[], definitions: MetafieldD
|
|||||||
return { matched, unmatched }
|
return { matched, unmatched }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupère toutes les définitions de métachamps produit via l'API GraphQL Admin.
|
||||||
|
* L'API REST ne dispose pas de cet endpoint — seul GraphQL l'expose.
|
||||||
|
*/
|
||||||
export async function fetchMetafieldDefinitions(): Promise<MetafieldDefinition[]> {
|
export async function fetchMetafieldDefinitions(): Promise<MetafieldDefinition[]> {
|
||||||
const { baseUrl, headers } = getConfig()
|
const domain = process.env.SHOPIFY_STORE_DOMAIN
|
||||||
const res = await fetch(`${baseUrl}/metafield_definitions.json?owner_type=PRODUCT&limit=250`, { headers })
|
const token = process.env.SHOPIFY_ADMIN_API_TOKEN
|
||||||
if (!res.ok) throw new Error(`Shopify metafield definitions error: ${res.status}`)
|
if (!domain || !token) throw new Error('SHOPIFY_STORE_DOMAIN et SHOPIFY_ADMIN_API_TOKEN requis')
|
||||||
|
|
||||||
|
const query = `{
|
||||||
|
metafieldDefinitions(ownerType: PRODUCT, first: 250) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
namespace
|
||||||
|
key
|
||||||
|
name
|
||||||
|
type { name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const res = await fetch(`https://${domain}/admin/api/2024-01/graphql.json`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ query }),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.ok) throw new Error(`Shopify GraphQL error: ${res.status}`)
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
return (data.metafield_definitions ?? []).map((d: {
|
|
||||||
id: string | number; namespace: string; key: string; name: string; type: { name: string } | string
|
if (data.errors) {
|
||||||
|
// Si les définitions ne sont pas accessibles, on retourne une liste vide
|
||||||
|
// plutôt que de bloquer — l'utilisateur verra toutes les colonnes comme "non matchées"
|
||||||
|
console.warn('[shopifyMetafields] GraphQL errors:', data.errors)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return (data.data?.metafieldDefinitions?.edges ?? []).map((edge: {
|
||||||
|
node: { id: string; namespace: string; key: string; name: string; type: { name: string } }
|
||||||
}) => ({
|
}) => ({
|
||||||
id: String(d.id),
|
id: edge.node.id,
|
||||||
namespace: d.namespace,
|
namespace: edge.node.namespace,
|
||||||
key: d.key,
|
key: edge.node.key,
|
||||||
name: d.name,
|
name: edge.node.name,
|
||||||
type: typeof d.type === 'string' ? d.type : d.type?.name ?? 'single_line_text_field',
|
type: edge.node.type?.name ?? 'single_line_text_field',
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Crée une définition de métachamp produit via GraphQL si elle n'existe pas encore.
|
||||||
|
*/
|
||||||
export async function ensureMetafieldDefinition(resolution: MetafieldResolution): Promise<void> {
|
export async function ensureMetafieldDefinition(resolution: MetafieldResolution): Promise<void> {
|
||||||
if (resolution.action !== 'create') return
|
if (resolution.action !== 'create') return
|
||||||
const { baseUrl, headers } = getConfig()
|
|
||||||
const res = await fetch(`${baseUrl}/metafield_definitions.json`, {
|
const domain = process.env.SHOPIFY_STORE_DOMAIN
|
||||||
|
const token = process.env.SHOPIFY_ADMIN_API_TOKEN
|
||||||
|
if (!domain || !token) throw new Error('SHOPIFY_STORE_DOMAIN et SHOPIFY_ADMIN_API_TOKEN requis')
|
||||||
|
|
||||||
|
const mutation = `mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
|
||||||
|
metafieldDefinitionCreate(definition: $definition) {
|
||||||
|
createdDefinition { id key namespace }
|
||||||
|
userErrors { field message }
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
const res = await fetch(`https://${domain}/admin/api/2024-01/graphql.json`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
metafield_definition: {
|
query: mutation,
|
||||||
namespace: resolution.namespace,
|
variables: {
|
||||||
key: resolution.key,
|
definition: {
|
||||||
name: resolution.columnHeader,
|
namespace: resolution.namespace,
|
||||||
type: 'single_line_text_field',
|
key: resolution.key,
|
||||||
owner_type: 'PRODUCT',
|
name: resolution.columnHeader,
|
||||||
|
type: 'single_line_text_field',
|
||||||
|
ownerType: 'PRODUCT',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
if (!res.ok) {
|
|
||||||
const err = await res.text()
|
if (!res.ok) throw new Error(`Shopify GraphQL metafield create error: ${res.status}`)
|
||||||
if (!err.includes('taken') && !err.includes('already')) {
|
const data = await res.json()
|
||||||
throw new Error(`Shopify create metafield definition error: ${res.status} — ${err}`)
|
const errors = data.data?.metafieldDefinitionCreate?.userErrors ?? []
|
||||||
}
|
const isAlreadyTaken = errors.some((e: { message: string }) =>
|
||||||
|
e.message.toLowerCase().includes('taken') || e.message.toLowerCase().includes('already'),
|
||||||
|
)
|
||||||
|
if (errors.length > 0 && !isAlreadyTaken) {
|
||||||
|
throw new Error(`Shopify metafield definition error: ${errors.map((e: { message: string }) => e.message).join(', ')}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user