wbxcallprov/src/flows/migrateStore.js
jmcqueen 93b060bc8b Modernize bot to Node.js 22 with modular architecture and remote SIW agent
- Refactor monolithic index.js (2646 lines) into src/{webex,integrations,
  cards,flows,commands,services} modules; replace node-fetch/form-data
  with native fetch/FormData; move all secrets to .env via dotenv
- Add dockerized remote SIW agent (docker/remote-agent/) with cross-arch
  buildx packaging (arm64 Mac -> linux/amd64), idempotent install.sh
  deploy bundle, and docker-free ZIP inspector for arch verification
- Bot hosts a WebSocket server; agent proxies SIW requests with a
  per-request insecure:true flag, replacing the process-wide
  NODE_TLS_REJECT_UNAUTHORIZED bypass
- Add ESLint flat config + Prettier, rewrite Dockerfile as non-root
  multi-stage node:22-alpine build, README covering setup / deploy /
  remote agent workflow
- Fix parseStoreArg to read trigger.prompt correctly (was indexing past
  the framework's post-match slice); register /help as regex (string
  matcher only compares the first token); switch catch-all to /.+/
  (previous /.*/gim was stateful due to the g flag); remove
  /fixDisplayNames command and its flow/card

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 14:48:51 -04:00

75 lines
2.8 KiB
JavaScript

import {
addPhoneNumbersToLocation,
findWebexLocation,
updateLocationCallingIdentity,
} from '../webex/locations.js';
import { findLocationAnnouncements } from '../webex/announcements.js';
import { createStoreAutoAttendant } from '../webex/autoAttendants.js';
import { normalizeStoreUserLicenses } from '../webex/licensing.js';
import {
updateUserCallExperience,
updateUserCallerId,
updateUserExtension,
updateUserVoicemailSettings,
} from '../webex/users.js';
import { CALLER_ID } from '../constants.js';
import { logger } from '../logger.js';
import { runStep } from './stepRunner.js';
/**
* "Migrate store" — finish the cut-over for a store that was previously staged.
* Attach the phone number, set caller ID, create the auto-attendant, and clean
* up the user's licensing/voicemail.
*/
export async function migrateStoreLocation(bot, locationInfo, userInfo) {
logger.info(`Migrating location ${locationInfo.name}`);
const matches = await findWebexLocation(locationInfo.name);
if (!matches.length) {
throw new Error(
`No existing Webex location found for ${locationInfo.name}. Did you stage first?`,
);
}
const location = matches[0];
await runStep(bot, 'Added phone numbers to location', () =>
addPhoneNumbersToLocation(location, locationInfo.phoneNumber),
);
await runStep(bot, 'Updated location Webex Calling Details', () =>
updateLocationCallingIdentity(location, locationInfo.phoneNumber),
);
await runStep(bot, 'Updated user extension', () =>
updateUserExtension(userInfo, locationInfo.extension),
);
await runStep(bot, 'Updated user call application experience', () =>
updateUserCallExperience(userInfo),
);
await runStep(bot, 'Updated user Caller ID Information', () =>
updateUserCallerId(userInfo, locationInfo.extension, locationInfo.phoneNumber, {
locationFallbackNumber: CALLER_ID.locationFallbackNumber,
customExternalCallerIdName: CALLER_ID.customExternalCallerIdName,
}),
);
await runStep(bot, 'Updated user voicemail settings', () =>
updateUserVoicemailSettings(userInfo),
);
const announcements = await runStep(bot, 'Located store announcement', () =>
findLocationAnnouncements(location),
);
if (announcements && announcements[0]) {
location.announcementId = announcements[0].id;
await runStep(bot, 'Created auto attendant', () =>
createStoreAutoAttendant(location, locationInfo),
);
} else {
bot.say(
'markdown',
"<blockquote class='failure'>Skipping auto attendant: no location announcement found.</blockquote>",
);
}
await runStep(bot, 'Fixed Licenses', () => normalizeStoreUserLicenses(userInfo));
bot.say('Migration Complete!');
return location;
}