Refactor Media Manager sync logic and fix duplication bugs
This major refactor addresses improper image matching and duplication: - Implemented strict ID-based matching in 'MediaService', removing the greedy filename matching fallback. - Redesigned synchronization pipeline to treat Google Drive as the Source of Truth, supporting orphan adoption (Shopify -> Drive) and secure uploads. - Implemented 'gallery_order' using Drive file properties (supporting both v2 and v3 APIs) for stable, drag-and-drop global ordering. - Added conditional file renaming using timestamps to enforce '_' naming convention without unnecessary renames. - Fixed runtime errors in 'MediaService' loops and updated 'ShopifyMediaService' GraphQL mutations to match correctly schema. - Rewrote 'MediaService.test.ts' with robust test cases for strict matching, adoption, sorting, and reordering.
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
import { IShopifyMediaService } from "../interfaces/IShopifyMediaService"
|
||||
import { IShop } from "../interfaces/IShop"
|
||||
import { formatGqlForJSON } from "../shopifyApi"
|
||||
import { formatGqlForJSON, buildGqlQuery } from "../shopifyApi"
|
||||
|
||||
export class ShopifyMediaService implements IShopifyMediaService {
|
||||
private shop: IShop
|
||||
@ -29,10 +29,7 @@ export class ShopifyMediaService implements IShopifyMediaService {
|
||||
}
|
||||
`
|
||||
const variables = { input }
|
||||
const payload = {
|
||||
query: formatGqlForJSON(query),
|
||||
variables: variables
|
||||
}
|
||||
const payload = buildGqlQuery(query, variables)
|
||||
const response = this.shop.shopifyGraphQLAPI(payload)
|
||||
return response.content.data.stagedUploadsCreate
|
||||
}
|
||||
@ -62,10 +59,7 @@ export class ShopifyMediaService implements IShopifyMediaService {
|
||||
productId,
|
||||
media
|
||||
}
|
||||
const payload = {
|
||||
query: formatGqlForJSON(query),
|
||||
variables: variables
|
||||
}
|
||||
const payload = buildGqlQuery(query, variables)
|
||||
const response = this.shop.shopifyGraphQLAPI(payload)
|
||||
return response.content.data.productCreateMedia
|
||||
}
|
||||
@ -91,33 +85,37 @@ export class ShopifyMediaService implements IShopifyMediaService {
|
||||
}
|
||||
`
|
||||
const variables = { productId }
|
||||
const payload = {
|
||||
query: formatGqlForJSON(query),
|
||||
variables: variables
|
||||
}
|
||||
const payload = buildGqlQuery(query, variables)
|
||||
const response = this.shop.shopifyGraphQLAPI(payload)
|
||||
if (!response.content.data.product) return []
|
||||
if (!response || !response.content || !response.content.data || !response.content.data.product) {
|
||||
console.error("getProductMedia: Invalid response or product not found. Raw Response:", JSON.stringify(response));
|
||||
throw new Error(`Product not found or access denied for ID: ${productId}. See Logs for details.`);
|
||||
}
|
||||
return response.content.data.product.media.edges.map((edge: any) => edge.node)
|
||||
}
|
||||
|
||||
productDeleteMedia(productId: string, mediaId: string): any {
|
||||
const query = /* GraphQL */ `
|
||||
mutation productDeleteMedia($mediaId: ID!, $productId: ID!) {
|
||||
productDeleteMedia(mediaId: $mediaId, productId: $productId) {
|
||||
deletedMediaId
|
||||
userErrors {
|
||||
mutation productDeleteMedia($mediaIds: [ID!]!, $productId: ID!) {
|
||||
productDeleteMedia(mediaIds: $mediaIds, productId: $productId) {
|
||||
deletedMediaIds
|
||||
mediaUserErrors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const variables = { productId, mediaId }
|
||||
const payload = {
|
||||
query: formatGqlForJSON(query),
|
||||
variables: variables
|
||||
}
|
||||
const variables = { productId, mediaIds: [mediaId] }
|
||||
const payload = buildGqlQuery(query, variables)
|
||||
const response = this.shop.shopifyGraphQLAPI(payload)
|
||||
if (!response || !response.content || !response.content.data) {
|
||||
console.error("productDeleteMedia failed. Response:", JSON.stringify(response))
|
||||
if (response && response.content && response.content.errors) {
|
||||
console.error("GraphQL Errors:", JSON.stringify(response.content.errors))
|
||||
}
|
||||
throw new Error(`Shopify API failed for productDeleteMedia: ${response ? 'Invalid Response' : 'No Response'}`)
|
||||
}
|
||||
return response.content.data.productDeleteMedia
|
||||
}
|
||||
|
||||
@ -137,11 +135,13 @@ export class ShopifyMediaService implements IShopifyMediaService {
|
||||
}
|
||||
`
|
||||
const variables = { id: productId, moves }
|
||||
const payload = {
|
||||
query: formatGqlForJSON(query),
|
||||
variables: variables
|
||||
}
|
||||
const payload = buildGqlQuery(query, variables)
|
||||
const response = this.shop.shopifyGraphQLAPI(payload)
|
||||
return response.content.data.productReorderMedia
|
||||
return response.content.data.productReorderMedia
|
||||
}
|
||||
|
||||
getShopDomain(): string {
|
||||
return this.shop.getShopDomain()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user