2026-04-13 05:23:25 +00:00

167 lines
5.9 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// routes/manageBrands.js
const express = require('express');
const axios = require('axios');
const { v4: uuid } = require('uuid');
const { getToken } = require('../tokenStore');
const { log } = require('../logger');
const router = express.Router();
const processes = {};
async function fetchAllCollections(shop, accessToken) {
const adminUrl = `https://${shop}/admin/api/2025-10/graphql.json`;
const headers = {
'X-Shopify-Access-Token': accessToken,
'Content-Type': 'application/json',
};
let allCollections = [];
let hasNextPage = true;
let endCursor = null;
const pageSize = 100;
while (hasNextPage) {
const fetchQuery = `
query GetCollections {
collections(first: ${pageSize}${endCursor ? `, after: "${endCursor}"` : ''}) {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
`;
const fetchResp = await axios.post(adminUrl, { query: fetchQuery }, { headers });
const collections = fetchResp.data.data.collections.edges.map(e => e.node);
allCollections = allCollections.concat(collections);
hasNextPage = fetchResp.data.data.collections.pageInfo.hasNextPage;
endCursor = fetchResp.data.data.collections.pageInfo.endCursor;
}
console.log(`Fetched ${allCollections.length} collections from ${shop}`);
return allCollections;
}
router.post('/', async (req, res) => {
const { shop, selectedBrands, selectedOldBrands } = req.body;
const selectedBrandsToUpdateMetaField = selectedBrands
console.log(`Selected brands: ${JSON.stringify(selectedBrandsToUpdateMetaField)}`);
const procId = uuid();
processes[procId] = { status: 'started', detail: null };
log(shop, `🔔 [${procId}] ManageBrands initiated for ${selectedBrands.length} new brands and ${selectedOldBrands.length} old brands`);
res.json({ processId: procId, status: 'started' });
(async () => {
try {
log(shop, `🔔 [${procId}] ManageBrands started`);
processes[procId].status = 'fetching_collections';
// 1. Get token
const tokenRecord = getToken(shop);
if (!tokenRecord) throw new Error('No token for shop');
const adminUrl = `https://${shop}/admin/api/2025-10/graphql.json`;
const headers = {
'X-Shopify-Access-Token': tokenRecord.accessToken,
'Content-Type': 'application/json',
};
console.log(`🔑 [${procId}] Using access token for shop ${shop} ${adminUrl} ${tokenRecord.accessToken}`);
const allCollections = await fetchAllCollections(shop, tokenRecord.accessToken);
log(shop, `🔍 [${procId}] Fetchedd ${allCollections.length} existing collections`);
// 3. Delete unselected
processes[procId].status = 'deleting';
// const toDelete = allCollections.filter(c => {
// return !selectedBrands.find(b => b.name.toLowerCase() === c.title.toLowerCase());
// });
// const toDelete = selectedOldBrands.filter(c => {
// return !selectedBrands.find(b => b.name.toLowerCase() === c.name.toLowerCase());
// });
// for (let i = 0; i < toDelete.length; i++) {
// const col = toDelete[i];
// processes[procId].detail = `deleting ${i + 1}/${toDelete.length}`;
// await axios.post(adminUrl, {
// query: `
// mutation { collectionDelete(input:{id:"${col.id}"}) { deletedCollectionId } }
// `
// }, { headers });
// log(shop, `🗑️ [${procId}] Deleted collection ${col.title}`);
// }
// 4. Create new
processes[procId].status = 'creating';
const existingTitles = allCollections.map(c => c.title.toLowerCase());
const toCreate = selectedBrands.filter(b => !existingTitles.includes(b.name.toLowerCase()));
for (let i = 0; i < toCreate.length; i++) {
const b = toCreate[i];
processes[procId].detail = `creating ${i + 1}/${toCreate.length}`;
const name = b.name.replace(/"/g, '\\"');
const logo = b.logo || '';
await axios.post(adminUrl, {
query: `
mutation {
collectionCreate(input:{
title:"${name}",
descriptionHtml:"Products from ${name}",
image:{altText:"${name} Logo",src:"${logo}"}
}) { collection { id } }
}
`
}, { headers });
log(shop, ` [${procId}] Created collection ${b.name}`);
}
// 5. Update metafield
console.log(`Updating metafield with ${selectedBrands.length} entries`);
console.log(`Updating metafield with ${selectedBrands} entries`);
console.log(`Updating metafield with ${selectedBrandsToUpdateMetaField} entries`);
processes[procId].status = 'updating_metafield';
const shopIdQuery = `{ shop { id } }`;
const shopIdResp = await axios.post(adminUrl, { query: shopIdQuery }, { headers });
const shopId = shopIdResp.data.data.shop.id;
const mfValue = JSON.stringify(selectedBrandsToUpdateMetaField);
await axios.post(adminUrl, {
query: `
mutation {
metafieldsSet(metafields:[{
namespace:"turn14",key:"selected_brands",type:"json",
ownerId:"${shopId}",value:${JSON.stringify(mfValue)}
}]) { metafields { id } }
}
`
}, { headers });
log(shop, `💾 [${procId}] Updated metafield with ${selectedBrands.length} entries`);
processes[procId].status = 'done';
processes[procId].detail = null;
log(shop, `✅ [${procId}] ManageBrands complete`);
} catch (err) {
processes[procId].status = 'error';
processes[procId].detail = err.message;
log(shop, `❌ [${procId}] Error: ${err.message}`);
}
})();
});
router.get('/status/:processId', (req, res) => {
const info = processes[req.params.processId];
if (!info) return res.status(404).json({ error: 'Not found' });
res.json(info);
});
module.exports = router;