Files
product_inventory/src/services/ShopifyMediaService.ts
Ben Miller 64ab548593 Fix Shopify video preview propagation on save
Updates logic to detect processing state (including READY-but-no-sources race condition) and propagates contentUrl updates to the frontend immediately.
2025-12-31 01:08:12 -07:00

160 lines
4.9 KiB
TypeScript

import { IShopifyMediaService } from "../interfaces/IShopifyMediaService"
import { IShop } from "../interfaces/IShop"
import { formatGqlForJSON, buildGqlQuery } from "../shopifyApi"
export class ShopifyMediaService implements IShopifyMediaService {
private shop: IShop
constructor(shop: IShop) {
this.shop = shop
}
stagedUploadsCreate(input: any[]): any {
const query = /* GraphQL */ `
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters {
name
value
}
}
userErrors {
field
message
}
}
}
`
const variables = { input }
const payload = buildGqlQuery(query, variables)
const response = this.shop.shopifyGraphQLAPI(payload)
return response.content.data.stagedUploadsCreate
}
productCreateMedia(productId: string, media: any[]): any {
const query = /* GraphQL */ `
mutation productCreateMedia($media: [CreateMediaInput!]!, $productId: ID!) {
productCreateMedia(media: $media, productId: $productId) {
media {
id
alt
mediaContentType
status
}
mediaUserErrors {
field
message
}
product {
id
title
}
}
}
`
const variables = {
productId,
media
}
const payload = buildGqlQuery(query, variables)
const response = this.shop.shopifyGraphQLAPI(payload)
return response.content.data.productCreateMedia
}
getProductMedia(productId: string): any[] {
const query = /* GraphQL */ `
query getProductMedia($productId: ID!) {
product(id: $productId) {
media(first: 250) {
edges {
node {
id
alt
mediaContentType
status
preview {
image {
originalSrc
}
}
... on Video {
sources {
url
mimeType
}
}
... on MediaImage {
image {
url
}
}
}
}
}
}
}
`
const variables = { productId }
const payload = buildGqlQuery(query, variables)
const response = this.shop.shopifyGraphQLAPI(payload)
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($mediaIds: [ID!]!, $productId: ID!) {
productDeleteMedia(mediaIds: $mediaIds, productId: $productId) {
deletedMediaIds
mediaUserErrors {
field
message
}
}
}
`
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
}
productReorderMedia(productId: string, moves: any[]): any {
const query = /* GraphQL */ `
mutation productReorderMedia($id: ID!, $moves: [MoveInput!]!) {
productReorderMedia(id: $id, moves: $moves) {
job {
id
done
}
userErrors {
field
message
}
}
}
`
const variables = { id: productId, moves }
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()
}
}