[object Object]

← back to Designer Wallcoverings

Host China Seas mailer vendor logos on Shopify CDN; swap 6 img src to permanent cdn.shopify.com URLs (unblocks logo-hosting check)

ec18eb1cda46d55aaf00a443bbdb78ba90a038cf · 2026-06-24 12:43:34 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit ec18eb1cda46d55aaf00a443bbdb78ba90a038cf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jun 24 12:43:34 2026 -0700

    Host China Seas mailer vendor logos on Shopify CDN; swap 6 img src to permanent cdn.shopify.com URLs (unblocks logo-hosting check)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 mailers/china-seas-launch.html |  12 ++---
 mailers/upload-vendor-logos.js | 102 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 108 insertions(+), 6 deletions(-)

diff --git a/mailers/china-seas-launch.html b/mailers/china-seas-launch.html
index b0fcec96..cddf3cd3 100644
--- a/mailers/china-seas-launch.html
+++ b/mailers/china-seas-launch.html
@@ -161,34 +161,34 @@
         <tr>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/china-seas?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/china-seas.png" alt="China Seas" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/china-seas.webp?v=1782330141" alt="China Seas" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
             </a>
           </td>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/quadrille-wallcoverings?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/quadrille.png" alt="Quadrille" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/quadrille.png?v=1782330144" alt="Quadrille" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
             </a>
           </td>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/nina-campbell?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/nina-campbell-shopify.jpg" alt="Nina Campbell" width="120" style="width:100%; max-width:120px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/nina-campbell-shopify.jpg?v=1771978096" alt="Nina Campbell" width="120" style="width:100%; max-width:120px; height:auto; margin:0 auto;">
             </a>
           </td>
         </tr>
         <tr>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/schumacher-wallpaper?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/schumacher-shopify.png" alt="Schumacher" width="130" style="width:100%; max-width:130px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/schumacher-shopify.png?v=1771978111" alt="Schumacher" width="130" style="width:100%; max-width:130px; height:auto; margin:0 auto;">
             </a>
           </td>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/thibaut-wallcoverings?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/thibaut.png" alt="Thibaut" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/thibaut.png?v=1771978235" alt="Thibaut" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
             </a>
           </td>
           <td class="stack" width="33%" valign="middle" align="center" style="padding: 14px 8px;">
             <a href="https://designerwallcoverings.com/collections/cole-sons?utm_source=email&utm_medium=mailer&utm_campaign=china-seas-launch" target="_blank">
-              <img src="https://designerwallcoverings.com/vendor-logos/cole-son.png" alt="Cole &amp; Son" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
+              <img src="https://cdn.shopify.com/s/files/1/0015/4117/7456/files/cole-son_3259d3af-8870-4ffe-aaaf-0288dea61a73.png?v=1782330147" alt="Cole &amp; Son" width="150" style="width:100%; max-width:150px; height:auto; margin:0 auto;">
             </a>
           </td>
         </tr>
diff --git a/mailers/upload-vendor-logos.js b/mailers/upload-vendor-logos.js
new file mode 100644
index 00000000..dc9c5984
--- /dev/null
+++ b/mailers/upload-vendor-logos.js
@@ -0,0 +1,102 @@
+#!/usr/bin/env node
+// One-shot: upload vendor logos to Shopify Files via stagedUploadsCreate -> PUT -> fileCreate -> poll READY.
+// Uses SHOPIFY_DRAFT_TOKEN (has write_files). Live store designer-laboratory-sandbox.
+const fs = require('fs');
+const path = require('path');
+
+const DOMAIN = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+const TOKEN = process.env.SHOPIFY_DRAFT_TOKEN;
+if (!TOKEN) { console.error('Missing SHOPIFY_DRAFT_TOKEN'); process.exit(1); }
+
+const DIR = path.join(__dirname, '..', 'vendor-logos');
+
+// Files that need fresh upload (the other 3 already exist in Files and are reused).
+// china-seas.png is actually a WebP container -> declare true mimeType.
+const FILES = [
+  { file: 'china-seas.png', mime: 'image/webp' },
+  { file: 'quadrille.png',  mime: 'image/png'  },
+  { file: 'cole-son.png',   mime: 'image/png'  },
+];
+
+async function gql(query, variables) {
+  const res = await fetch(`https://${DOMAIN}/admin/api/${API}/graphql.json`, {
+    method: 'POST',
+    headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+    body: JSON.stringify({ query, variables }),
+  });
+  const j = await res.json();
+  if (j.errors) throw new Error('GraphQL errors: ' + JSON.stringify(j.errors));
+  return j.data;
+}
+
+async function stagedUpload(filename, mime, sizeBytes) {
+  const q = `mutation stagedUploadsCreate($input:[StagedUploadInput!]!){
+    stagedUploadsCreate(input:$input){
+      stagedTargets{ url resourceUrl parameters{ name value } }
+      userErrors{ field message }
+    }
+  }`;
+  const d = await gql(q, { input: [{
+    resource: 'FILE', filename, mimeType: mime, httpMethod: 'POST', fileSize: String(sizeBytes),
+  }]});
+  const ue = d.stagedUploadsCreate.userErrors;
+  if (ue && ue.length) throw new Error('stagedUploadsCreate: ' + JSON.stringify(ue));
+  return d.stagedUploadsCreate.stagedTargets[0];
+}
+
+async function putBytes(target, buf, mime) {
+  // Google Cloud Storage staged target uses multipart POST with the provided params.
+  const fd = new FormData();
+  for (const p of target.parameters) fd.append(p.name, p.value);
+  fd.append('file', new Blob([buf], { type: mime }));
+  const res = await fetch(target.url, { method: 'POST', body: fd });
+  if (!(res.status >= 200 && res.status < 300)) {
+    const t = await res.text();
+    throw new Error(`Upload PUT/POST failed ${res.status}: ${t.slice(0,300)}`);
+  }
+}
+
+async function fileCreate(resourceUrl, filename, alt) {
+  const q = `mutation fileCreate($files:[FileCreateInput!]!){
+    fileCreate(files:$files){
+      files{ id fileStatus alt ... on MediaImage { image { url } } }
+      userErrors{ field message }
+    }
+  }`;
+  const d = await gql(q, { files: [{
+    originalSource: resourceUrl, contentType: 'IMAGE', alt, filename,
+  }]});
+  const ue = d.fileCreate.userErrors;
+  if (ue && ue.length) throw new Error('fileCreate: ' + JSON.stringify(ue));
+  return d.fileCreate.files[0];
+}
+
+async function poll(id) {
+  const q = `query($id:ID!){ node(id:$id){ ... on MediaImage { id fileStatus image { url } } } }`;
+  for (let i = 0; i < 30; i++) {
+    const d = await gql(q, { id });
+    const n = d.node;
+    if (n && n.fileStatus === 'READY' && n.image && n.image.url) return n.image.url;
+    if (n && n.fileStatus === 'FAILED') throw new Error('file FAILED: ' + id);
+    await new Promise(r => setTimeout(r, 2000));
+  }
+  throw new Error('timeout waiting READY: ' + id);
+}
+
+(async () => {
+  const out = {};
+  for (const { file, mime } of FILES) {
+    const fp = path.join(DIR, file);
+    const buf = fs.readFileSync(fp);
+    const alt = file.replace(/\.[a-z]+$/i, '').replace(/-/g, ' ') + ' logo';
+    process.stdout.write(`Uploading ${file} (${mime}, ${buf.length}B)... `);
+    const target = await stagedUpload(file, mime, buf.length);
+    await putBytes(target, buf, mime);
+    const created = await fileCreate(target.resourceUrl, file, alt);
+    const url = await poll(created.id);
+    out[file] = url;
+    console.log('READY ->', url);
+  }
+  console.log('\nRESULT_JSON ' + JSON.stringify(out));
+})().catch(e => { console.error('FATAL', e.message); process.exit(1); });

← dbcd3125 Add Constant Contact v3 API scaffold for China Seas mailer (  ·  back to Designer Wallcoverings  ·  China Seas mailer: confirmed DW postal address + phone in fo 4c4f363f →