// 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(); // Simple in-memory process tracker const processes = {}; async function fetchAllCollections(shop, accessToken) { const adminUrl = `https://${shop}/admin/api/2023-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 } = req.body; const procId = uuid(); processes[procId] = { status: 'started', detail: null }; log(shop, `🔔 [${procId}] ManageBrands initiated for ${selectedBrands.length} brands`); //console.log(`🔔 [${procId}] ManageBrands initiated for ${selectedBrands.length} 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/2023-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}`); // 2. Fetch existing collections // const fetchQuery = ` // { collections(first:100) { edges { node { id title } } } } // `; // const fetchResp = await axios.post(adminUrl, { query: fetchQuery }, { headers }); // const existing = fetchResp.data.data.collections.edges.map(e => e.node); // log(shop, `🔍 [${procId}] Fetched ${existing.length} collections`); const allCollections = await fetchAllCollections(shop, tokenRecord.accessToken); log(shop, `🔍 [${procId}] Fetchedd ${allCollections.length} existing collections`); // 3. Delete unselected processes[procId].status = 'deleting'; // const toDelete = existing.filter(c => { const toDelete = allCollections.filter(c => { return !selectedBrands.find(b => b.name.toLowerCase() === c.title.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 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(selectedBrands); 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;