link-stack/apps/metamigo-api/app/routes/voice/index.ts

124 lines
3.7 KiB
TypeScript

import * as Hapi from "@hapi/hapi";
import * as Joi from "joi";
import * as Boom from "@hapi/boom";
import * as R from "remeda";
import * as Helpers from "../helpers";
import Twilio from "twilio";
import { crudRoutesFor, CrudControllerBase } from "@digiresilience/metamigo-common";
import { VoiceLineRecord, SavedVoiceLine } from "db";
const TwilioHandlers = {
freeNumbers: async (provider, request: Hapi.Request) => {
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
const client = Twilio(apiKeySid, apiKeySecret, {
accountSid,
});
const numbers = R.pipe(
await client.incomingPhoneNumbers.list({ limit: 100 }),
R.filter((n) => n.capabilities.voice),
R.map(R.pick(["sid", "phoneNumber"]))
);
const numberSids = R.map(numbers, R.prop("sid"));
const voiceLineRepo = request.db().voiceLines;
const voiceLines: SavedVoiceLine[] =
await voiceLineRepo.findAllByProviderLineSids(numberSids);
const voiceLineSids = new Set(R.map(voiceLines, R.prop("providerLineSid")));
return R.pipe(
numbers,
R.reject((n) => voiceLineSids.has(n.sid)),
R.map((n) => ({ id: n.sid, name: n.phoneNumber }))
);
},
};
export const VoiceProviderRoutes = Helpers.withDefaults([
{
method: "GET",
path: "/api/voice/providers/{providerId}/freeNumbers",
options: {
description:
"get a list of the incoming numbers for a provider account that aren't assigned to a voice line",
validate: {
params: {
providerId: Joi.string().uuid().required(),
},
},
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
const { providerId } = request.params;
const voiceProvidersRepo = request.db().voiceProviders;
const provider = await voiceProvidersRepo.findById(providerId);
if (!provider) return Boom.notFound();
switch (provider.kind) {
case "TWILIO":
return TwilioHandlers.freeNumbers(provider, request);
default:
return Boom.badImplementation();
}
},
},
},
]);
class VoiceLineRecordController extends CrudControllerBase(VoiceLineRecord) { }
const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
create: {
payload: Joi.object({
providerType: Joi.string().required(),
providerId: Joi.string().required(),
number: Joi.string().required(),
language: Joi.string().required(),
voice: Joi.string().required(),
promptText: Joi.string().optional(),
promptRecording: Joi.binary()
.encoding("base64")
.max(50 * 1000 * 1000)
.optional(),
}).label("VoiceLineCreate"),
},
updateById: {
params: {
id: Joi.string().uuid().required(),
},
payload: Joi.object({
providerType: Joi.string().optional(),
providerId: Joi.string().optional(),
number: Joi.string().optional(),
language: Joi.string().optional(),
voice: Joi.string().optional(),
promptText: Joi.string().optional(),
promptRecording: Joi.binary()
.encoding("base64")
.max(50 * 1000 * 1000)
.optional(),
}).label("VoiceLineUpdate"),
},
deleteById: {
params: {
id: Joi.string().uuid().required(),
},
},
getById: {
params: {
id: Joi.string().uuid().required(),
},
},
});
export const VoiceLineRoutes = async (
_server: Hapi.Server
): Promise<Hapi.ServerRoute[]> => {
const controller = new VoiceLineRecordController("voiceLines", "id");
return Helpers.withDefaults(
crudRoutesFor(
"voice-line",
"/api/voice/voice-line",
controller,
"id",
validator()
)
);
};
export * from "./twilio";