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 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[]> {
|
||||
const { baseUrl, headers } = getConfig()
|
||||
const res = await fetch(`${baseUrl}/metafield_definitions.json?owner_type=PRODUCT&limit=250`, { headers })
|
||||
if (!res.ok) throw new Error(`Shopify metafield definitions error: ${res.status}`)
|
||||
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 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()
|
||||
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),
|
||||
namespace: d.namespace,
|
||||
key: d.key,
|
||||
name: d.name,
|
||||
type: typeof d.type === 'string' ? d.type : d.type?.name ?? 'single_line_text_field',
|
||||
id: edge.node.id,
|
||||
namespace: edge.node.namespace,
|
||||
key: edge.node.key,
|
||||
name: edge.node.name,
|
||||
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> {
|
||||
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',
|
||||
headers,
|
||||
headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
metafield_definition: {
|
||||
namespace: resolution.namespace,
|
||||
key: resolution.key,
|
||||
name: resolution.columnHeader,
|
||||
type: 'single_line_text_field',
|
||||
owner_type: 'PRODUCT',
|
||||
query: mutation,
|
||||
variables: {
|
||||
definition: {
|
||||
namespace: resolution.namespace,
|
||||
key: resolution.key,
|
||||
name: resolution.columnHeader,
|
||||
type: 'single_line_text_field',
|
||||
ownerType: 'PRODUCT',
|
||||
},
|
||||
},
|
||||
}),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const err = await res.text()
|
||||
if (!err.includes('taken') && !err.includes('already')) {
|
||||
throw new Error(`Shopify create metafield definition error: ${res.status} — ${err}`)
|
||||
}
|
||||
|
||||
if (!res.ok) throw new Error(`Shopify GraphQL metafield create error: ${res.status}`)
|
||||
const data = await res.json()
|
||||
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