This change modifies the validation/planning phase to skip the expensive thumbnail generation step in 'getUnifiedMediaState'. Since the planning phase primarily needs file IDs and names to calculate deletions, adoptions, and reorders, skipping the thumbnail verification/retrieval (including sidecar checks) significantly reduces the latency of the 'Save Changes' operation.
234 lines
7.1 KiB
TypeScript
234 lines
7.1 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)
|
|
}
|
|
|
|
getProduct(productId: string): any {
|
|
const query = /* GraphQL */ `
|
|
query getProduct($productId: ID!) {
|
|
product(id: $productId) {
|
|
id
|
|
title
|
|
handle
|
|
onlineStoreUrl
|
|
}
|
|
}
|
|
`
|
|
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.warn("getProduct: Product not found or access denied for ID:", productId);
|
|
return null;
|
|
}
|
|
return response.content.data.product
|
|
}
|
|
|
|
getProductWithMedia(productId: string): any {
|
|
const query = /* GraphQL */ `
|
|
query getProductWithMedia($productId: ID!) {
|
|
product(id: $productId) {
|
|
id
|
|
title
|
|
handle
|
|
onlineStoreUrl
|
|
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.warn("getProductWithMedia: Product not found or access denied for ID:", productId);
|
|
return null;
|
|
}
|
|
|
|
// Normalize return structure to match expectations
|
|
const p = response.content.data.product;
|
|
return {
|
|
product: { id: p.id, title: p.title, handle: p.handle, onlineStoreUrl: p.onlineStoreUrl },
|
|
media: p.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()
|
|
}
|
|
}
|