ServiceChannel webhook processor, proposal approval cards, attachment auto-post, CollabSupport commands, and Docker deployment configuration. Co-authored-by: Cursor <cursoragent@cursor.com>
101 lines
No EOL
4.3 KiB
JavaScript
101 lines
No EOL
4.3 KiB
JavaScript
/**
|
|
* src/services/webexService.js
|
|
*
|
|
* Thin service layer over the Webex bot client.
|
|
*
|
|
* Purpose:
|
|
* - Provides a clean, ServChan-specific interface for Webex operations
|
|
* - Improves testability (easy to mock the entire service)
|
|
* - Central place for any future Webex business logic related to work orders
|
|
* - Better isolation between webhookProcessor and raw botClient calls
|
|
*
|
|
* Created as part of the 2026-05-28 refactoring (step 4).
|
|
*/
|
|
|
|
import botClient from '../integrations/webex/botClient.js'; // default singleton for convenience
|
|
import { logger } from '../utils/logger.js';
|
|
|
|
export class WebexService {
|
|
/**
|
|
* @param {object} [client] - Optional bot client instance.
|
|
* Defaults to the shared botClient singleton.
|
|
*/
|
|
constructor(client = botClient) {
|
|
this.client = client;
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Low-level passthroughs (kept for compatibility with current processor)
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
async createRoom(title, teamId = null) {
|
|
return this.client.createRoom(title, teamId);
|
|
}
|
|
|
|
async addMember(roomId, personEmail) {
|
|
return this.client.addMember(roomId, personEmail);
|
|
}
|
|
|
|
async sendMarkdown(roomId, markdown, textFallback = null) {
|
|
return this.client.sendMarkdown(roomId, markdown, textFallback);
|
|
}
|
|
|
|
async sendWithAttachment(roomId, buffer, fileName, contentType, text, fileUrl = null) {
|
|
return this.client.sendWithAttachment(roomId, buffer, fileName, contentType, text, fileUrl);
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Higher-level ServChan-specific helpers (recommended for new code)
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Creates a work order room with the standard ServChan title format.
|
|
*/
|
|
async createWorkOrderRoom(workOrder, teamId) {
|
|
const title = `ServChan WO-${workOrder.Number} | Store ${workOrder.LocationStoreId} | ${workOrder.LocationName}`;
|
|
const room = await this.createRoom(title, teamId);
|
|
logger('webexService', `Created room for WO-${workOrder.Number}: ${room.id}`);
|
|
return room;
|
|
}
|
|
|
|
/**
|
|
* Adds the standard default members to a work order room.
|
|
*/
|
|
async addDefaultMembers(roomId, members = []) {
|
|
if (!members.length) return;
|
|
|
|
const results = await Promise.allSettled(
|
|
members.map(email => this.addMember(roomId, email))
|
|
);
|
|
|
|
results.forEach((result, i) => {
|
|
if (result.status === 'rejected') {
|
|
logger('webexService:addDefaultMembers', `Failed to add ${members[i]}: ${result.reason?.message || result.reason}`, 'warn');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Posts a message to a work order room.
|
|
*/
|
|
async postWorkOrderMessage(roomId, markdown) {
|
|
return this.sendMarkdown(roomId, markdown);
|
|
}
|
|
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
// Adaptive Card support (for proposal approval cards)
|
|
// ────────────────────────────────────────────────────────────────────────────
|
|
|
|
async sendAdaptiveCard(roomId, card, fallbackText) {
|
|
return this.client.sendAdaptiveCard(roomId, card, fallbackText);
|
|
}
|
|
|
|
async deleteMessage(messageId) {
|
|
return this.client.deleteMessage(messageId);
|
|
}
|
|
}
|
|
|
|
// Convenience singleton (matches the pattern used elsewhere)
|
|
export const webexService = new WebexService();
|
|
|
|
export default webexService; |