Bridge worker updates

This commit is contained in:
Darren Clarke 2024-04-21 16:59:50 +02:00
parent a445762a37
commit f93c4ad317
33 changed files with 17584 additions and 161 deletions

View file

@ -0,0 +1,71 @@
/* eslint-disable camelcase */
// import { SavedVoiceProvider } from "@digiresilience/bridge-db";
import Twilio from "twilio";
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
import { Zammad, getOrCreateUser } from "./zammad";
type SavedVoiceProvider = any;
export const twilioClientFor = (
provider: SavedVoiceProvider,
): Twilio.Twilio => {
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
if (!accountSid || !apiKeySid || !apiKeySecret)
throw new Error(
`twilio provider ${provider.name} does not have credentials`,
);
return Twilio(apiKeySid, apiKeySecret, {
accountSid,
});
};
export const createZammadTicket = async (
call: CallInstance,
mp3: Buffer,
): Promise<void> => {
const title = `Call from ${call.fromFormatted} at ${call.startTime}`;
const body = `<ul>
<li>Caller: ${call.fromFormatted}</li>
<li>Service Number: ${call.toFormatted}</li>
<li>Call Duration: ${call.duration} seconds</li>
<li>Start Time: ${call.startTime}</li>
<li>End Time: ${call.endTime}</li>
</ul>
<p>See the attached recording.</p>`;
const filename = `${call.sid}-${call.startTime}.mp3`;
const zammad = Zammad(
{
token: "EviH_WL0p6YUlCoIER7noAZEAPsYA_fVU4FZCKdpq525Vmzzvl8d7dNuP_8d-Amb",
},
"https://demo.digiresilience.org",
);
try {
const customer = await getOrCreateUser(zammad, call.fromFormatted);
await zammad.ticket.create({
title,
group: "Finances",
note: "This ticket was created automaticaly from a recorded phone call.",
customer_id: customer.id,
article: {
body,
subject: title,
content_type: "text/html",
type: "note",
attachments: [
{
filename,
data: mp3.toString("base64"),
"mime-type": "audio/mpeg",
},
],
},
});
} catch (error: any) {
console.log(Object.keys(error));
if (error.isBoom) {
console.log(error.output);
throw new Error("Failed to create zamamd ticket");
}
}
};

View file

@ -0,0 +1,56 @@
/*
import pgPromise from "pg-promise";
import * as pgMonitor from "pg-monitor";
import {
dbInitOptions,
IRepositories,
AppDatabase,
} from "@digiresilience/bridge-db";
import config from "@digiresilience/bridge-config";
import type { IInitOptions } from "pg-promise";
export const initDiagnostics = (
logSql: boolean,
initOpts: IInitOptions<IRepositories>,
): void => {
if (logSql) {
pgMonitor.attach(initOpts);
} else {
pgMonitor.attach(initOpts, ["error"]);
}
};
export const stopDiagnostics = (): void => pgMonitor.detach();
let pgp: any;
let pgpInitOptions: any;
export const initPgp = (): void => {
pgpInitOptions = dbInitOptions(config);
pgp = pgPromise(pgpInitOptions);
};
const initDb = (): AppDatabase => {
const db = pgp(config.db.connection);
return db;
};
export const stopDb = async (db: AppDatabase): Promise<void> => {
return db.$pool.end();
};
*/
export type AppDatabase = any;
export const withDb = <T>(f: (db: AppDatabase) => Promise<T>): Promise<T> => {
/*
const db = initDb();
initDiagnostics(config.logging.sql, pgpInitOptions);
try {
return f(db);
} finally {
stopDiagnostics();
}
*/
return f(null);
};

View file

View file

@ -0,0 +1,11 @@
//import { defState } from "@digiresilience/montar";
//import { configureLogger } from "@digiresilience/bridge-common";
// import config from "@digiresilience/bridge-config";
//export const logger = defState("workerLogger", {
// start: async () => configureLogger(config),
//});
//export default logger;
export const logger = {};
export default logger;

View file

@ -0,0 +1,26 @@
import * as Worker from "graphile-worker";
// import { defState } from "@digiresilience/montar";
//import config from "@digiresilience/bridge-config";
/*
const startWorkerUtils = async (): Promise<Worker.WorkerUtils> => {
const workerUtils = await Worker.makeWorkerUtils({
connectionString: config.worker.connection,
});
return workerUtils;
};
const stopWorkerUtils = async (): Promise<void> => {
return workerUtils.release();
};
const workerUtils = defState("workerUtils", {
start: startWorkerUtils,
stop: stopWorkerUtils,
});
*/
export const workerUtils: any = {};
export default workerUtils;

View file

View file

View file

@ -0,0 +1,106 @@
/* eslint-disable camelcase,@typescript-eslint/explicit-module-boundary-types,@typescript-eslint/no-explicit-any */
import querystring from "querystring";
import Wreck from "@hapi/wreck";
export interface User {
id: number;
firstname?: string;
lastname?: string;
email?: string;
phone?: string;
}
export interface Ticket {
id: number;
title?: string;
group_id?: number;
customer_id?: number;
}
export interface ZammadClient {
ticket: {
create: (data: any) => Promise<Ticket>;
};
user: {
search: (data: any) => Promise<User[]>;
create: (data: any) => Promise<User>;
};
}
export type ZammadCredentials =
| { username: string; password: string }
| { token: string };
export interface ZammadClientOpts {
headers?: Record<string, any>;
}
const formatAuth = (credentials: any) => {
if (credentials.username) {
return (
"Basic " +
Buffer.from(`${credentials.username}:${credentials.password}`).toString(
"base64"
)
);
}
if (credentials.token) {
return `Token ${credentials.token}`;
}
throw new Error("invalid zammad credentials type");
};
export const Zammad = (
credentials: ZammadCredentials,
host: string,
opts?: ZammadClientOpts
): ZammadClient => {
const extraHeaders = (opts && opts.headers) || {};
const wreck = Wreck.defaults({
baseUrl: `${host}/api/v1/`,
headers: {
authorization: formatAuth(credentials),
...extraHeaders,
},
json: true,
});
return {
ticket: {
create: async (payload) => {
const { payload: result } = await wreck.post("tickets", { payload });
return result as Ticket;
},
},
user: {
search: async (query) => {
const qp = querystring.stringify({ query });
const { payload: result } = await wreck.get(`users/search?${qp}`);
return result as User[];
},
create: async (payload) => {
const { payload: result } = await wreck.post("users", { payload });
return result as User;
},
},
};
};
export const getUser = async (zammad: ZammadClient, phoneNumber: string) => {
const mungedNumber = phoneNumber.replace("+", "");
const results = await zammad.user.search(`phone:${mungedNumber}`);
if (results.length > 0) return results[0];
return undefined;
};
export const getOrCreateUser = async (zammad: ZammadClient, phoneNumber: string) => {
const customer = await getUser(zammad, phoneNumber);
if (customer) return customer;
return zammad.user.create({
phone: phoneNumber,
note: "User created by Grabadora from incoming voice call",
});
};