Bridge worker updates
This commit is contained in:
parent
a445762a37
commit
f93c4ad317
33 changed files with 17584 additions and 161 deletions
71
apps/bridge-worker/lib/common.ts
Normal file
71
apps/bridge-worker/lib/common.ts
Normal 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");
|
||||
}
|
||||
}
|
||||
};
|
||||
56
apps/bridge-worker/lib/db.ts
Normal file
56
apps/bridge-worker/lib/db.ts
Normal 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);
|
||||
};
|
||||
0
apps/bridge-worker/lib/facebook.ts
Normal file
0
apps/bridge-worker/lib/facebook.ts
Normal file
11
apps/bridge-worker/lib/logger.ts
Normal file
11
apps/bridge-worker/lib/logger.ts
Normal 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;
|
||||
26
apps/bridge-worker/lib/utils.ts
Normal file
26
apps/bridge-worker/lib/utils.ts
Normal 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;
|
||||
0
apps/bridge-worker/lib/voice.ts
Normal file
0
apps/bridge-worker/lib/voice.ts
Normal file
0
apps/bridge-worker/lib/whatsapp.ts
Normal file
0
apps/bridge-worker/lib/whatsapp.ts
Normal file
106
apps/bridge-worker/lib/zammad.ts
Normal file
106
apps/bridge-worker/lib/zammad.ts
Normal 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",
|
||||
});
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue