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:
Ben Miller
2025-12-28 12:25:13 -07:00
parent 6e1222cec9
commit 7c35817313
14 changed files with 1299 additions and 505 deletions

View File

@ -12,11 +12,29 @@ export class MockDriveService implements IDriveService {
// Mock implementation finding by name "under" parent
const key = `${parentFolderId}/${folderName}`
if (!this.folders.has(key)) {
const id = `mock_folder_${folderName}_id`
const newFolder = {
getId: () => `mock_folder_${folderName}_id`,
getId: () => id,
getName: () => folderName,
getUrl: () => `https://mock.drive/folders/${folderName}`,
createFile: (blob) => this.saveFile(blob, `mock_folder_${folderName}_id`)
createFile: (blob) => this.saveFile(blob, id),
addFile: (file) => {
console.log(`[MockDrive] addFile: Adding ${file.getId()} to ${id}`)
// Remove from all other folders (simplification) or just 'root'
for (const [fId, files] of this.files.entries()) {
const idx = files.findIndex(f => f.getId() === file.getId())
if (idx !== -1) {
console.log(`[MockDrive] Removed ${file.getId()} from ${fId}`)
files.splice(idx, 1)
}
}
// Add to this folder
if (!this.files.has(id)) {
this.files.set(id, [])
}
this.files.get(id).push(file)
return newFolder
}
} as unknown as GoogleAppsScript.Drive.Folder;
this.folders.set(key, newFolder)
}
@ -24,20 +42,26 @@ export class MockDriveService implements IDriveService {
}
saveFile(blob: GoogleAppsScript.Base.Blob, folderId: string): GoogleAppsScript.Drive.File {
const id = `mock_file_${Date.now()}_${Math.floor(Math.random() * 1000)}`
const newFile = {
getId: () => `mock_file_${Date.now()}`,
getId: () => id,
getName: () => blob.getName(),
getBlob: () => blob,
getUrl: () => `https://mock.drive/files/${blob.getName()}`,
getLastUpdated: () => new Date(),
getThumbnail: () => ({ getBytes: () => [] })
getThumbnail: () => ({ getBytes: () => [] }),
getAppProperty: (key) => {
return (newFile as any)._properties?.[key]
}
} as unknown as GoogleAppsScript.Drive.File
// Initialize properties container
;(newFile as any)._properties = {}
if (!this.files.has(folderId)) {
this.files.set(folderId, [])
}
this.files.get(folderId).push(newFile)
console.log(`[MockDrive] Saved file ${newFile.getName()} to ${folderId}. Total files: ${this.files.get(folderId).length}`)
return newFile
}
@ -69,10 +93,25 @@ export class MockDriveService implements IDriveService {
updateFileProperties(fileId: string, properties: any): void {
console.log(`[MockDrive] Updating properties for ${fileId}`, properties)
const file = this.getFileById(fileId)
const mockFile = file as any
if (!mockFile._properties) {
mockFile._properties = {}
}
Object.assign(mockFile._properties, properties)
}
createFile(blob: GoogleAppsScript.Base.Blob): GoogleAppsScript.Drive.File {
// Create in "root" or similar
return this.saveFile(blob, "root")
}
getFileProperties(fileId: string): {[key: string]: string} {
try {
const file = this.getFileById(fileId)
return (file as any)._properties || {}
} catch (e) {
return {}
}
}
}