167 lines
5.9 KiB
JavaScript
Executable File
167 lines
5.9 KiB
JavaScript
Executable File
// 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;
|