|
|
|
@ -17,6 +17,31 @@ const axios = require('axios');
|
|
|
|
|
|
|
|
|
|
|
|
const MERCHANT_ID = () => process.env.MERCHANT_ID;
|
|
|
|
const MERCHANT_ID = () => process.env.MERCHANT_ID;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function normalizeScopeId(value) {
|
|
|
|
|
|
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getCompanyId(req) {
|
|
|
|
|
|
|
|
return normalizeScopeId(
|
|
|
|
|
|
|
|
req.get('x-company-id')
|
|
|
|
|
|
|
|
|| req.query?.companyId
|
|
|
|
|
|
|
|
|| req.query?.company_id
|
|
|
|
|
|
|
|
|| req.body?.companyId
|
|
|
|
|
|
|
|
|| req.body?.company_id
|
|
|
|
|
|
|
|
|| MERCHANT_ID()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function getApplicationId(req) {
|
|
|
|
|
|
|
|
return normalizeScopeId(
|
|
|
|
|
|
|
|
req.get('x-application-id')
|
|
|
|
|
|
|
|
|| req.query?.applicationId
|
|
|
|
|
|
|
|
|| req.query?.application_id
|
|
|
|
|
|
|
|
|| req.body?.applicationId
|
|
|
|
|
|
|
|
|| req.body?.application_id
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function slugify(text) {
|
|
|
|
function slugify(text) {
|
|
|
|
@ -40,6 +65,18 @@ async function saveIndex(merchantId, businesses) {
|
|
|
|
await uploadJSON(indexPath(merchantId), 'index', { businesses });
|
|
|
|
await uploadJSON(indexPath(merchantId), 'index', { businesses });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function findBusinessByApplicationId(merchantId, applicationId) {
|
|
|
|
|
|
|
|
const normalizedApplicationId = normalizeScopeId(applicationId);
|
|
|
|
|
|
|
|
if (!normalizedApplicationId) return null;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const businesses = await getIndex(merchantId);
|
|
|
|
|
|
|
|
return businesses.find((business) => {
|
|
|
|
|
|
|
|
const storedApplicationId = normalizeScopeId(business.applicationId);
|
|
|
|
|
|
|
|
const storedBusinessId = normalizeScopeId(business.businessId);
|
|
|
|
|
|
|
|
return storedApplicationId === normalizedApplicationId || storedBusinessId === normalizedApplicationId;
|
|
|
|
|
|
|
|
}) || null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const PROVIDER_FIELDS = ['providerName', 'senderId', 'dltEntityId', 'authKey'];
|
|
|
|
const PROVIDER_FIELDS = ['providerName', 'senderId', 'dltEntityId', 'authKey'];
|
|
|
|
|
|
|
|
|
|
|
|
function createHttpError(status, message, extra = {}) {
|
|
|
|
function createHttpError(status, message, extra = {}) {
|
|
|
|
@ -67,6 +104,10 @@ function normalizeSenderId(value) {
|
|
|
|
return normalizeText(value).toUpperCase();
|
|
|
|
return normalizeText(value).toUpperCase();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function renderTemplateWithUsername(template, username) {
|
|
|
|
|
|
|
|
return String(template || '').replace(/\{#var#\}/g, normalizeText(username));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function isValidCurlCommand(rawCurl) {
|
|
|
|
function isValidCurlCommand(rawCurl) {
|
|
|
|
return normalizeText(rawCurl).toLowerCase().startsWith('curl');
|
|
|
|
return normalizeText(rawCurl).toLowerCase().startsWith('curl');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -239,7 +280,7 @@ function getMissingMandatoryProviderFields(provider = {}) {
|
|
|
|
// GET /api/businesses
|
|
|
|
// GET /api/businesses
|
|
|
|
router.get('/', async (req, res) => {
|
|
|
|
router.get('/', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const businesses = await getIndex(MERCHANT_ID());
|
|
|
|
const businesses = await getIndex(getCompanyId(req));
|
|
|
|
res.json({ businesses });
|
|
|
|
res.json({ businesses });
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
@ -252,7 +293,14 @@ router.post('/', async (req, res) => {
|
|
|
|
const { websiteUrl } = req.body;
|
|
|
|
const { websiteUrl } = req.body;
|
|
|
|
if (!websiteUrl) return res.status(400).json({ error: 'websiteUrl is required' });
|
|
|
|
if (!websiteUrl) return res.status(400).json({ error: 'websiteUrl is required' });
|
|
|
|
|
|
|
|
|
|
|
|
const merchantId = MERCHANT_ID();
|
|
|
|
const merchantId = getCompanyId(req);
|
|
|
|
|
|
|
|
const applicationId = getApplicationId(req);
|
|
|
|
|
|
|
|
const businesses = await getIndex(merchantId);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (applicationId && businesses.some((business) => normalizeScopeId(business.applicationId) === applicationId)) {
|
|
|
|
|
|
|
|
return res.status(409).json({ error: 'A business is already configured for this applicationId' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const businessId = uuidv4();
|
|
|
|
const businessId = uuidv4();
|
|
|
|
const bizRoot = businessRoot(merchantId, businessId);
|
|
|
|
const bizRoot = businessRoot(merchantId, businessId);
|
|
|
|
const imagesFolder = `${bizRoot}/images`;
|
|
|
|
const imagesFolder = `${bizRoot}/images`;
|
|
|
|
@ -277,6 +325,8 @@ router.post('/', async (req, res) => {
|
|
|
|
const contextJson = {
|
|
|
|
const contextJson = {
|
|
|
|
businessId,
|
|
|
|
businessId,
|
|
|
|
merchantId,
|
|
|
|
merchantId,
|
|
|
|
|
|
|
|
companyId: merchantId,
|
|
|
|
|
|
|
|
applicationId,
|
|
|
|
domain,
|
|
|
|
domain,
|
|
|
|
brandName: brandContext.brandName || 'Unknown Brand',
|
|
|
|
brandName: brandContext.brandName || 'Unknown Brand',
|
|
|
|
tone: brandContext.tone || 'professional',
|
|
|
|
tone: brandContext.tone || 'professional',
|
|
|
|
@ -292,9 +342,10 @@ router.post('/', async (req, res) => {
|
|
|
|
await uploadJSON(bizRoot, 'events', { events: DEFAULT_EVENTS });
|
|
|
|
await uploadJSON(bizRoot, 'events', { events: DEFAULT_EVENTS });
|
|
|
|
|
|
|
|
|
|
|
|
// 6. Update index.json
|
|
|
|
// 6. Update index.json
|
|
|
|
const businesses = await getIndex(merchantId);
|
|
|
|
|
|
|
|
businesses.push({
|
|
|
|
businesses.push({
|
|
|
|
businessId,
|
|
|
|
businessId,
|
|
|
|
|
|
|
|
companyId: merchantId,
|
|
|
|
|
|
|
|
applicationId,
|
|
|
|
brandName: contextJson.brandName,
|
|
|
|
brandName: contextJson.brandName,
|
|
|
|
domain: contextJson.domain,
|
|
|
|
domain: contextJson.domain,
|
|
|
|
createdAt: contextJson.createdAt,
|
|
|
|
createdAt: contextJson.createdAt,
|
|
|
|
@ -313,7 +364,7 @@ router.post('/', async (req, res) => {
|
|
|
|
router.get('/:businessId', async (req, res) => {
|
|
|
|
router.get('/:businessId', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId } = req.params;
|
|
|
|
const { businessId } = req.params;
|
|
|
|
const context = await fetchJSON(businessRoot(MERCHANT_ID(), businessId), 'context');
|
|
|
|
const context = await fetchJSON(businessRoot(getCompanyId(req), businessId), 'context');
|
|
|
|
if (!context) return res.status(404).json({ error: 'Business not found' });
|
|
|
|
if (!context) return res.status(404).json({ error: 'Business not found' });
|
|
|
|
res.json(context);
|
|
|
|
res.json(context);
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
@ -324,7 +375,7 @@ router.get('/:businessId', async (req, res) => {
|
|
|
|
// DELETE /api/businesses/:businessId
|
|
|
|
// DELETE /api/businesses/:businessId
|
|
|
|
router.delete('/:businessId', async (req, res) => {
|
|
|
|
router.delete('/:businessId', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const merchantId = MERCHANT_ID();
|
|
|
|
const merchantId = getCompanyId(req);
|
|
|
|
const { businessId } = req.params;
|
|
|
|
const { businessId } = req.params;
|
|
|
|
|
|
|
|
|
|
|
|
await deleteBusinessFiles(merchantId, businessId);
|
|
|
|
await deleteBusinessFiles(merchantId, businessId);
|
|
|
|
@ -340,12 +391,52 @@ router.delete('/:businessId', async (req, res) => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// POST /api/businesses/resolve-template
|
|
|
|
|
|
|
|
router.post('/resolve-template', async (req, res) => {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const companyId = getCompanyId(req);
|
|
|
|
|
|
|
|
const applicationId = getApplicationId(req);
|
|
|
|
|
|
|
|
const event = normalizeText(req.body?.event);
|
|
|
|
|
|
|
|
const username = normalizeText(req.body?.username);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!companyId) return res.status(400).json({ error: 'companyId is required' });
|
|
|
|
|
|
|
|
if (!applicationId) return res.status(400).json({ error: 'applicationId is required' });
|
|
|
|
|
|
|
|
if (!event) return res.status(400).json({ error: 'event is required' });
|
|
|
|
|
|
|
|
if (!username) return res.status(400).json({ error: 'username is required' });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const business = await findBusinessByApplicationId(companyId, applicationId);
|
|
|
|
|
|
|
|
if (!business) {
|
|
|
|
|
|
|
|
return res.status(404).json({ error: 'Business not found for applicationId' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const eventSlug = slugify(event);
|
|
|
|
|
|
|
|
const folder = `${businessRoot(companyId, business.businessId)}/templates`;
|
|
|
|
|
|
|
|
const tmpl = await fetchJSON(folder, eventSlug);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!tmpl || tmpl.status !== 'whitelisted' || !normalizeText(tmpl.selectedTemplate)) {
|
|
|
|
|
|
|
|
return res.status(404).json({ error: 'Whitelisted template not found' });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
|
|
companyId,
|
|
|
|
|
|
|
|
applicationId,
|
|
|
|
|
|
|
|
event: eventSlug,
|
|
|
|
|
|
|
|
username,
|
|
|
|
|
|
|
|
templateId: normalizeText(tmpl.templateId),
|
|
|
|
|
|
|
|
template: renderTemplateWithUsername(tmpl.selectedTemplate, username),
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
|
|
sendRouteError(res, err);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ─── Providers ────────────────────────────────────────────────────────────────
|
|
|
|
// ─── Providers ────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
// GET /api/businesses/:businessId/providers
|
|
|
|
// GET /api/businesses/:businessId/providers
|
|
|
|
router.get('/:businessId/providers', async (req, res) => {
|
|
|
|
router.get('/:businessId/providers', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const activeProfile = await getActiveProfile(bizRoot);
|
|
|
|
const activeProfile = await getActiveProfile(bizRoot);
|
|
|
|
if (!activeProfile) {
|
|
|
|
if (!activeProfile) {
|
|
|
|
return res.status(400).json({ error: 'An active cURL profile is required before editing provider settings.' });
|
|
|
|
return res.status(400).json({ error: 'An active cURL profile is required before editing provider settings.' });
|
|
|
|
@ -360,7 +451,7 @@ router.get('/:businessId/providers', async (req, res) => {
|
|
|
|
// POST /api/businesses/:businessId/providers
|
|
|
|
// POST /api/businesses/:businessId/providers
|
|
|
|
router.post('/:businessId/providers', async (req, res) => {
|
|
|
|
router.post('/:businessId/providers', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const providerPatch = getProviderPatch(req.body);
|
|
|
|
const providerPatch = getProviderPatch(req.body);
|
|
|
|
const senderIdError = validateSenderId(providerPatch?.senderId || '');
|
|
|
|
const senderIdError = validateSenderId(providerPatch?.senderId || '');
|
|
|
|
if (senderIdError) {
|
|
|
|
if (senderIdError) {
|
|
|
|
@ -388,7 +479,7 @@ router.post('/:businessId/providers', async (req, res) => {
|
|
|
|
// GET /api/businesses/:businessId/global-sms
|
|
|
|
// GET /api/businesses/:businessId/global-sms
|
|
|
|
router.get('/:businessId/global-sms', async (req, res) => {
|
|
|
|
router.get('/:businessId/global-sms', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const activeProfile = await getActiveProfile(bizRoot);
|
|
|
|
const activeProfile = await getActiveProfile(bizRoot);
|
|
|
|
res.json(activeProfile ? { rawCurl: activeProfile.rawCurl, updatedAt: activeProfile.updatedAt } : {});
|
|
|
|
res.json(activeProfile ? { rawCurl: activeProfile.rawCurl, updatedAt: activeProfile.updatedAt } : {});
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
@ -408,7 +499,7 @@ router.post('/:businessId/global-sms', async (req, res) => {
|
|
|
|
return res.status(400).json({ error: 'rawCurl must be a valid cURL command' });
|
|
|
|
return res.status(400).json({ error: 'rawCurl must be a valid cURL command' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
const normalizedCurl = normalizeText(rawCurl);
|
|
|
|
const normalizedCurl = normalizeText(rawCurl);
|
|
|
|
@ -446,7 +537,7 @@ router.post('/:businessId/global-sms', async (req, res) => {
|
|
|
|
// GET /api/businesses/:businessId/global-sms/profiles
|
|
|
|
// GET /api/businesses/:businessId/global-sms/profiles
|
|
|
|
router.get('/:businessId/global-sms/profiles', async (req, res) => {
|
|
|
|
router.get('/:businessId/global-sms/profiles', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const { profileData, activeProfileId } = await getProfileState(bizRoot);
|
|
|
|
const { profileData, activeProfileId } = await getProfileState(bizRoot);
|
|
|
|
const profiles = profileData.profiles || [];
|
|
|
|
const profiles = profileData.profiles || [];
|
|
|
|
res.json({ profiles, activeProfileId });
|
|
|
|
res.json({ profiles, activeProfileId });
|
|
|
|
@ -469,7 +560,7 @@ router.post('/:businessId/global-sms/profiles', async (req, res) => {
|
|
|
|
return res.status(400).json({ error: 'rawCurl must be a valid cURL command' });
|
|
|
|
return res.status(400).json({ error: 'rawCurl must be a valid cURL command' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
const now = new Date().toISOString();
|
|
|
|
const normalizedCurl = normalizeText(rawCurl);
|
|
|
|
const normalizedCurl = normalizeText(rawCurl);
|
|
|
|
@ -519,7 +610,7 @@ router.patch('/:businessId/global-sms/profiles/:profileId', async (req, res) =>
|
|
|
|
return res.status(400).json({ error: senderIdError });
|
|
|
|
return res.status(400).json({ error: senderIdError });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const profile = profileData.profiles.find(p => p.id === profileId);
|
|
|
|
const profile = profileData.profiles.find(p => p.id === profileId);
|
|
|
|
if (!profile) return res.status(404).json({ error: 'Profile not found' });
|
|
|
|
if (!profile) return res.status(404).json({ error: 'Profile not found' });
|
|
|
|
@ -539,7 +630,7 @@ router.patch('/:businessId/global-sms/profiles/:profileId', async (req, res) =>
|
|
|
|
router.delete('/:businessId/global-sms/profiles/:profileId', async (req, res) => {
|
|
|
|
router.delete('/:businessId/global-sms/profiles/:profileId', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId, profileId } = req.params;
|
|
|
|
const { businessId, profileId } = req.params;
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
|
|
|
|
|
|
|
|
const idx = profileData.profiles.findIndex(p => p.id === profileId);
|
|
|
|
const idx = profileData.profiles.findIndex(p => p.id === profileId);
|
|
|
|
@ -567,7 +658,7 @@ router.delete('/:businessId/global-sms/profiles/:profileId', async (req, res) =>
|
|
|
|
router.post('/:businessId/global-sms/profiles/:profileId/activate', async (req, res) => {
|
|
|
|
router.post('/:businessId/global-sms/profiles/:profileId/activate', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId, profileId } = req.params;
|
|
|
|
const { businessId, profileId } = req.params;
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const { profileData } = await getProfileState(bizRoot);
|
|
|
|
const profile = profileData.profiles.find(p => p.id === profileId);
|
|
|
|
const profile = profileData.profiles.find(p => p.id === profileId);
|
|
|
|
if (!profile) return res.status(404).json({ error: 'Profile not found' });
|
|
|
|
if (!profile) return res.status(404).json({ error: 'Profile not found' });
|
|
|
|
@ -582,7 +673,7 @@ router.post('/:businessId/global-sms/profiles/:profileId/activate', async (req,
|
|
|
|
// GET /api/businesses/:businessId/global-sms/active
|
|
|
|
// GET /api/businesses/:businessId/global-sms/active
|
|
|
|
router.get('/:businessId/global-sms/active', async (req, res) => {
|
|
|
|
router.get('/:businessId/global-sms/active', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const { activeProfile, activeProfileId } = await getProfileState(bizRoot);
|
|
|
|
const { activeProfile, activeProfileId } = await getProfileState(bizRoot);
|
|
|
|
res.json({ activeProfile, activeProfileId });
|
|
|
|
res.json({ activeProfile, activeProfileId });
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
@ -595,7 +686,7 @@ router.get('/:businessId/global-sms/active', async (req, res) => {
|
|
|
|
// GET /api/businesses/:businessId/events
|
|
|
|
// GET /api/businesses/:businessId/events
|
|
|
|
router.get('/:businessId/events', async (req, res) => {
|
|
|
|
router.get('/:businessId/events', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const data = await fetchJSON(businessRoot(MERCHANT_ID(), req.params.businessId), 'events');
|
|
|
|
const data = await fetchJSON(businessRoot(getCompanyId(req), req.params.businessId), 'events');
|
|
|
|
res.json(data || { events: DEFAULT_EVENTS });
|
|
|
|
res.json(data || { events: DEFAULT_EVENTS });
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
@ -608,7 +699,7 @@ router.post('/:businessId/events', async (req, res) => {
|
|
|
|
const { label } = req.body;
|
|
|
|
const { label } = req.body;
|
|
|
|
if (!label) return res.status(400).json({ error: 'label is required' });
|
|
|
|
if (!label) return res.status(400).json({ error: 'label is required' });
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const data = await fetchJSON(bizRoot, 'events') || { events: [...DEFAULT_EVENTS] };
|
|
|
|
const data = await fetchJSON(bizRoot, 'events') || { events: [...DEFAULT_EVENTS] };
|
|
|
|
const slug = slugify(label);
|
|
|
|
const slug = slugify(label);
|
|
|
|
|
|
|
|
|
|
|
|
@ -629,7 +720,7 @@ router.post('/:businessId/events', async (req, res) => {
|
|
|
|
router.delete('/:businessId/events/:slug', async (req, res) => {
|
|
|
|
router.delete('/:businessId/events/:slug', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const data = await fetchJSON(bizRoot, 'events') || { events: [...DEFAULT_EVENTS] };
|
|
|
|
const data = await fetchJSON(bizRoot, 'events') || { events: [...DEFAULT_EVENTS] };
|
|
|
|
const event = data.events.find(e => e.slug === slug);
|
|
|
|
const event = data.events.find(e => e.slug === slug);
|
|
|
|
|
|
|
|
|
|
|
|
@ -648,7 +739,7 @@ router.delete('/:businessId/events/:slug', async (req, res) => {
|
|
|
|
router.post('/:businessId/events/:slug/generate', async (req, res) => {
|
|
|
|
router.post('/:businessId/events/:slug/generate', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
|
|
|
|
|
|
|
|
const context = await fetchJSON(bizRoot, 'context');
|
|
|
|
const context = await fetchJSON(bizRoot, 'context');
|
|
|
|
if (!context) return res.status(400).json({ error: 'Business context not found.' });
|
|
|
|
if (!context) return res.status(400).json({ error: 'Business context not found.' });
|
|
|
|
@ -692,7 +783,7 @@ router.post('/:businessId/events/:slug/generate', async (req, res) => {
|
|
|
|
// GET /api/businesses/:businessId/templates/images (must be before /:slug)
|
|
|
|
// GET /api/businesses/:businessId/templates/images (must be before /:slug)
|
|
|
|
router.get('/:businessId/templates/images', async (req, res) => {
|
|
|
|
router.get('/:businessId/templates/images', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const images = await listImages(`${businessRoot(MERCHANT_ID(), req.params.businessId)}/images`);
|
|
|
|
const images = await listImages(`${businessRoot(getCompanyId(req), req.params.businessId)}/images`);
|
|
|
|
res.json({ images });
|
|
|
|
res.json({ images });
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
res.status(500).json({ error: err.message });
|
|
|
|
@ -702,7 +793,7 @@ router.get('/:businessId/templates/images', async (req, res) => {
|
|
|
|
// GET /api/businesses/:businessId/templates
|
|
|
|
// GET /api/businesses/:businessId/templates
|
|
|
|
router.get('/:businessId/templates', async (req, res) => {
|
|
|
|
router.get('/:businessId/templates', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), req.params.businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), req.params.businessId);
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const slugs = await listTemplateFiles(folder);
|
|
|
|
const slugs = await listTemplateFiles(folder);
|
|
|
|
const templates = [];
|
|
|
|
const templates = [];
|
|
|
|
@ -720,7 +811,7 @@ router.get('/:businessId/templates', async (req, res) => {
|
|
|
|
router.get('/:businessId/templates/:slug', async (req, res) => {
|
|
|
|
router.get('/:businessId/templates/:slug', async (req, res) => {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const { businessId, slug } = req.params;
|
|
|
|
const tmpl = await fetchJSON(`${businessRoot(MERCHANT_ID(), businessId)}/templates`, slug);
|
|
|
|
const tmpl = await fetchJSON(`${businessRoot(getCompanyId(req), businessId)}/templates`, slug);
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
res.json(tmpl);
|
|
|
|
res.json(tmpl);
|
|
|
|
} catch (err) {
|
|
|
|
} catch (err) {
|
|
|
|
@ -735,7 +826,7 @@ router.post('/:businessId/templates/:slug/select', async (req, res) => {
|
|
|
|
const { selectedVariant } = req.body;
|
|
|
|
const { selectedVariant } = req.body;
|
|
|
|
if (!selectedVariant) return res.status(400).json({ error: 'selectedVariant is required' });
|
|
|
|
if (!selectedVariant) return res.status(400).json({ error: 'selectedVariant is required' });
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
|
|
|
|
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
@ -776,7 +867,7 @@ router.post('/:businessId/templates/:slug/whitelist', async (req, res) => {
|
|
|
|
return res.status(400).json({ error: 'templateId is required' });
|
|
|
|
return res.status(400).json({ error: 'templateId is required' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const folder = `${businessRoot(MERCHANT_ID(), businessId)}/templates`;
|
|
|
|
const folder = `${businessRoot(getCompanyId(req), businessId)}/templates`;
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
if (tmpl.status !== 'pending_whitelisting') {
|
|
|
|
if (tmpl.status !== 'pending_whitelisting') {
|
|
|
|
@ -809,7 +900,7 @@ router.post('/:businessId/templates/:slug/publish', async (req, res) => {
|
|
|
|
return res.status(400).json({ error: 'toNumber is required' });
|
|
|
|
return res.status(400).json({ error: 'toNumber is required' });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
|
|
|
|
|
|
|
|
// Load template
|
|
|
|
// Load template
|
|
|
|
@ -877,7 +968,7 @@ router.post('/:businessId/templates/:slug/test', async (req, res) => {
|
|
|
|
const { toNumber } = req.body;
|
|
|
|
const { toNumber } = req.body;
|
|
|
|
if (!normalizeText(toNumber)) return res.status(400).json({ error: 'toNumber is required' });
|
|
|
|
if (!normalizeText(toNumber)) return res.status(400).json({ error: 'toNumber is required' });
|
|
|
|
|
|
|
|
|
|
|
|
const bizRoot = businessRoot(MERCHANT_ID(), businessId);
|
|
|
|
const bizRoot = businessRoot(getCompanyId(req), businessId);
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const folder = `${bizRoot}/templates`;
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
const tmpl = await fetchJSON(folder, slug);
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
if (!tmpl) return res.status(404).json({ error: 'Template not found' });
|
|
|
|
|