const { PixelbinConfig, PixelbinClient } = require('@pixelbin/admin'); const { Readable } = require('stream'); const axios = require('axios'); function getPixelbinClient() { const apiToken = process.env.PIXELBIN_API_TOKEN; if (!apiToken) throw new Error('PIXELBIN_API_TOKEN not set'); const config = new PixelbinConfig({ domain: 'https://api.pixelbin.io', apiSecret: apiToken, }); return new PixelbinClient(config); } /** Upload a JSON object to Pixelbin. Overwrites if already exists. */ async function uploadJSON(folderPath, filename, jsonObject) { const pixelbin = getPixelbinClient(); const buffer = Buffer.from(JSON.stringify(jsonObject, null, 2), 'utf-8'); const stream = Readable.from(buffer); stream.path = `${filename}.json`; await pixelbin.assets.fileUpload({ file: stream, path: folderPath, name: filename, access: 'public-read', overwrite: true, }); } /** Fetch a JSON file from Pixelbin. Returns null if not found. */ async function fetchJSON(folderPath, filename) { const pixelbin = getPixelbinClient(); try { const result = await pixelbin.assets.listFiles({ path: folderPath }); const items = result.items || []; const file = items.find( f => f.type === 'file' && (f.name === filename || f.name === `${filename}.json`) ); if (!file?.url) return null; const bustUrl = `${file.url}?t=${Date.now()}`; const res = await axios.get(bustUrl, { timeout: 10000 }); return res.data; } catch { return null; } } /** Download an image URL and upload it to Pixelbin. Returns CDN URL or null. */ async function uploadImageFromUrl(imageUrl, folderPath, filename) { try { const pixelbin = getPixelbinClient(); const res = await axios.get(imageUrl, { responseType: 'arraybuffer', timeout: 15000 }); const buffer = Buffer.from(res.data); const rawExt = (imageUrl.split('?')[0].split('.').pop() || 'jpg').toLowerCase(); const ext = ['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(rawExt) ? rawExt : 'jpg'; const stream = Readable.from(buffer); stream.path = `${filename}.${ext}`; const uploaded = await pixelbin.assets.fileUpload({ file: stream, path: folderPath, name: filename, access: 'public-read', overwrite: true, }); return uploaded?.url || null; } catch (err) { console.error(`Image upload failed (${imageUrl}):`, err.message); return null; } } /** List all image files in a Pixelbin folder. Returns [{ name, url }] */ async function listImages(folderPath) { const pixelbin = getPixelbinClient(); try { const result = await pixelbin.assets.listFiles({ path: folderPath }); return (result.items || []) .filter(f => f.type === 'file') .map(f => ({ name: f.name, url: f.url })); } catch { return []; } } /** List template file names (slugs) in a Pixelbin folder. */ async function listTemplateFiles(folderPath) { const pixelbin = getPixelbinClient(); try { const result = await pixelbin.assets.listFiles({ path: folderPath }); return (result.items || []) .filter(f => f.type === 'file') .map(f => f.name); } catch { return []; } } /** Delete a single file from Pixelbin by its fileId. */ async function deleteFile(fileId) { const pixelbin = getPixelbinClient(); try { await pixelbin.assets.deleteFile({ fileId }); } catch (err) { console.error(`Failed to delete ${fileId}:`, err.message); } } /** List all file items (with fileId) from a folder. */ async function listFilesWithId(folderPath) { const pixelbin = getPixelbinClient(); try { const result = await pixelbin.assets.listFiles({ path: folderPath }); return (result.items || []).filter(f => f.type === 'file'); } catch { return []; } } /** * Delete all files belonging to a specific business. * Business root: {merchantId}/{businessId}/ */ async function deleteBusinessFiles(merchantId, businessId) { const root = `${merchantId}/${businessId}`; const [rootFiles, templateFiles, imageFiles] = await Promise.all([ listFilesWithId(root), listFilesWithId(`${root}/templates`), listFilesWithId(`${root}/images`), ]); const all = [...rootFiles, ...templateFiles, ...imageFiles]; await Promise.all(all.map(f => deleteFile(f.fileId))); } module.exports = { uploadJSON, fetchJSON, uploadImageFromUrl, listImages, listTemplateFiles, deleteFile, listFilesWithId, deleteBusinessFiles, };