Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { getTrackedImages, findTrackedImage } = require('./imageStore');
const { getTemplateInfo, applyEdit } = require('./expressApi');
const { uploadImageToMeta } = require('./metaUpload');

function formatUnknownImageMessage(phoneNumber) {
const images = getTrackedImages(phoneNumber);
const list = images.map((image) => `- ${image.name}`).join('\n');
return `I couldn't find that image. Here's what I have:\n${list}`;
}

async function actionListCampaignGraphics() {
// TODO: fetch from campaign API
return 'Graphics in your current campaign:\n1. Diwali Offer Banner\n2. Summer Sale Flyer\n3. Croma Earbuds';
}

async function actionCheckAllowedEdits(phoneNumber, imageId) {
const image = findTrackedImage(phoneNumber, imageId);
if (!image) {
return formatUnknownImageMessage(phoneNumber);
}
const { unlockedLayers } = getTemplateInfo(image.templateId);
const layerList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
return `Edits allowed on "${image.name}":\n${layerList}`;
}

async function actionEditGraphic(phoneNumber, imageId, edits, { sendImage }) {
const image = findTrackedImage(phoneNumber, imageId);
if (!image) {
return formatUnknownImageMessage(phoneNumber);
}

const { unlockedLayers } = getTemplateInfo(image.templateId);
const requestedKeys = Object.keys(edits || {});
const disallowedKeys = requestedKeys.filter((key) => !unlockedLayers.includes(key));

if (disallowedKeys.length > 0) {
const allowedList = unlockedLayers.map((layer) => `- ${layer}`).join('\n');
return `I can't edit ${disallowedKeys.join(', ')} on "${image.name}". Allowed edits:\n${allowedList}`;
}

const { mergedEdits, renderedImageUrl } = applyEdit(image.templateId, image.currentEdits, edits);
image.currentEdits = mergedEdits;

const uploadedUrl = await uploadImageToMeta(renderedImageUrl);
await sendImage(phoneNumber, uploadedUrl);

const summary = Object.entries(edits).map(([key, value]) => `• ${key}: ${value}`).join('\n');
return `Updated "${image.name}":\n${summary}`;
}

async function actionGenerateBulkGraphics(filename) {
// TODO: parse CSV/Excel and call Adobe Express API per row
return `Bulk generation complete! Graphics created from ${filename || 'your uploaded file'}.`;
}

module.exports = {
actionListCampaignGraphics,
actionCheckAllowedEdits,
actionEditGraphic,
actionGenerateBulkGraphics,
};
66 changes: 66 additions & 0 deletions actions.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const test = require('node:test');
const assert = require('node:assert/strict');
const { actionCheckAllowedEdits, actionEditGraphic } = require('./actions');
const { getTrackedImages } = require('./imageStore');

test('actionCheckAllowedEdits lists the unlocked layers for a known image', async () => {
const reply = await actionCheckAllowedEdits('phone-1', 'img_1');
assert.match(reply, /Diwali Offer Banner/);
assert.match(reply, /discount_text/);
});

test('actionCheckAllowedEdits lists Price, Address, Product Image, Partner Logo for the Croma earbuds image', async () => {
const reply = await actionCheckAllowedEdits('phone-croma', 'img_3');
assert.match(reply, /Croma Earbuds/);
assert.match(reply, /- Price/);
assert.match(reply, /- Address/);
assert.match(reply, /- Product Image/);
assert.match(reply, /- Partner Logo/);
});

test('actionCheckAllowedEdits reports unknown images without throwing', async () => {
const reply = await actionCheckAllowedEdits('phone-2', 'img_nope');
assert.match(reply, /couldn't find that image/);
});

test('actionEditGraphic rejects edits outside the unlocked layers and sends nothing', async () => {
let sendImageCalled = false;
const sendImage = async () => {
sendImageCalled = true;
};

const reply = await actionEditGraphic('phone-3', 'img_3', { background_color: 'red' }, { sendImage });

assert.match(reply, /can't edit background_color/);
assert.equal(sendImageCalled, false);
});

test('actionEditGraphic sends the fixed updated Croma earbuds image for any allowed edit', async () => {
const sentCalls = [];
const sendImage = async (to, link) => {
sentCalls.push({ to, link });
};

const reply = await actionEditGraphic('phone-5', 'img_3', { Price: '999' }, { sendImage });

assert.match(reply, /Updated "Croma Earbuds"/);
assert.equal(sentCalls.length, 1);
assert.equal(sentCalls[0].link, 'https://s7ap1.scene7.com/is/image/varun/croma1-earbuds-updated');
});

test('actionEditGraphic applies an allowed edit, sends the updated image, and remembers the edit', async () => {
const sentCalls = [];
const sendImage = async (to, link) => {
sentCalls.push({ to, link });
};

const reply = await actionEditGraphic('phone-4', 'img_2', { headline: 'Flash Sale' }, { sendImage });

assert.match(reply, /Updated "Summer Sale Flyer"/);
assert.equal(sentCalls.length, 1);
assert.equal(sentCalls[0].to, 'phone-4');
assert.match(sentCalls[0].link, /^https:\/\/mock-meta-cdn\.local\//);

const image = getTrackedImages('phone-4').find((img) => img.id === 'img_2');
assert.equal(image.currentEdits.headline, 'Flash Sale');
});
Loading