diff --git a/src/db/migrations/20210311061107_create_audio_table.ts b/src/db/migrations/20210311061107_create_audio_table.ts new file mode 100644 index 0000000..dbff1f6 --- /dev/null +++ b/src/db/migrations/20210311061107_create_audio_table.ts @@ -0,0 +1,13 @@ +import { Knex } from "knex"; + +export async function up(knex: Knex) { + await knex.schema.createTable("recordings", (table) => { + table.bigInteger("messageId").primary().references("voicemails.messageId"); + table.string("mimeType", 32); + table.binary("audio"); + }); +} + +export async function down(knex: Knex) { + await knex.schema.dropTable("recordings"); +} diff --git a/src/template.tsx b/src/template.tsx index c60605e..21aca0c 100644 --- a/src/template.tsx +++ b/src/template.tsx @@ -1,8 +1,9 @@ import React from "react"; import ReactDOMServer from "react-dom/server"; -import type { Contact, StoredVoicemail } from "./types"; import { getNationalNumber, formatSeconds } from "./util"; import { DateTime } from "luxon"; +import type { Contact } from "./types"; +import type { StoredVoicemail } from "knex/types/tables"; export function getTicketSubject( voicemail: StoredVoicemail, diff --git a/src/ticketize.ts b/src/ticketize.ts index 47e97b6..826873f 100644 --- a/src/ticketize.ts +++ b/src/ticketize.ts @@ -11,8 +11,8 @@ import type { RCAudioAttachment, Recording, Transcription, - StoredVoicemail, } from "./types"; +import type { StoredVoicemail, StoredRecording } from "knex/types/tables"; const SEARCH_CONTACT_BY_PHONE_NUMBER_QUERY = gql` query getContactByPhoneNumber($phoneNumber: String!) { @@ -168,8 +168,8 @@ export function ticketize( const response = await rcsdk.get(audio.uri); const result = { duration: audio.vmDuration, - mimetype: audio.contentType, - audio: await response.blob(), + mimeType: audio.contentType, + audio: await response.arrayBuffer(), }; return result; } @@ -193,7 +193,10 @@ export function ticketize( * @param voicemail * @param contact */ - async function createTicket(voicemail: StoredVoicemail, contact?: Contact) { + async function createTicket( + voicemail: StoredVoicemail & StoredRecording, + contact?: Contact + ) { const input: any = { subject: getTicketSubject(voicemail, contact), description: getTicketBody(voicemail, contact), @@ -229,29 +232,38 @@ export function ticketize( recording: Recording, transcription: Transcription ) { - return db("voicemails").insert({ - messageId: message.id, - extensionId: message.extensionId, - received: message.creationTime, - toNumber: message.to[0].phoneNumber, - extensionNumber: extension.extensionNumber, - extensionName: extension.name, - fromNumber: message.from.phoneNumber, - fromName: message.from.name, - duration: recording.duration, - transcriptionStatus: transcription.status, - transcription: transcription.text, + await db.transaction(async (trx) => { + await trx("voicemails").insert({ + messageId: message.id, + extensionId: message.extensionId, + received: message.creationTime, + toNumber: message.to[0].phoneNumber, + extensionNumber: extension.extensionNumber, + extensionName: extension.name, + fromNumber: message.from.phoneNumber, + fromName: message.from.name, + duration: recording.duration, + transcriptionStatus: transcription.status, + transcription: transcription.text, + }); + await trx("recordings").insert({ + messageId: message.id, + mimeType: recording.mimeType, + audio: new Uint8Array(recording.audio), + }); }); } /** - * Updates a stored voicemail using its current properties + * Updates a stored voicemail * @param voicemail the voicemail to update */ - async function updateStoredVoicemail(voicemail: StoredVoicemail) { - await db("voicemails") - .update({ ...voicemail }) - .where({ messageId: voicemail.messageId }); + async function updateStoredVoicemail(voicemail: Partial) { + const messageId = voicemail.messageId; + if (!messageId) { + throw new Error("Missing required messageId property"); + } + await db("voicemails").update(voicemail).where({ messageId }); } /** @@ -259,9 +271,7 @@ export function ticketize( * @returns whether the message by the given ID has been stored */ async function isMessageStored(messageId: number) { - const result = await db("voicemails") - .where({ messageId }) - .first(); + const result = await db("voicemails").where({ messageId }).first(); return result !== undefined; } @@ -269,7 +279,8 @@ export function ticketize( * @returns stored voicemails that haven't had tickets created for them yet */ async function getUnprocessedVoicemails() { - return await db("voicemails") + return await db("voicemails") + .join("recordings", "voicemails.messageId", "recordings.messageId") .whereNull("ticketId") .whereIn("transcriptionStatus", [ "Completed", @@ -283,7 +294,7 @@ export function ticketize( * @returns stored voicemails whose trranscriptions may still be in progress */ async function getMissingTranscriptionVoicemails() { - return await db("voicemails") + return await db("voicemails") .whereNotNull("transcription") .whereNotIn("transcriptionStatus", [ // Don't include those whose transcriptions have failed or will not @@ -366,7 +377,11 @@ export function ticketize( // else we do nothing return; } - return updateStoredVoicemail(message); + return updateStoredVoicemail({ + messageId: message.messageId, + transcriptionStatus: message.transcriptionStatus, + transcription: message.transcription, + }); }) ); } @@ -385,7 +400,7 @@ export function ticketize( `Created ticket ${ticketId} from voicemail ${voicemail.messageId}` ); return updateStoredVoicemail({ - ...voicemail, + messageId: voicemail.messageId, ticketId, contactId: contact?.id, contactableType: contact?.contactable.__typename, diff --git a/src/types.ts b/src/types.ts index a0ed5ff..fdcfa1a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -97,8 +97,8 @@ export interface RCMessage { export interface Recording { duration: number; - mimetype: string; - audio: Blob; + mimeType: string; + audio: ArrayBuffer; } export interface Transcription { @@ -106,21 +106,33 @@ export interface Transcription { text: string | null; } -export interface StoredVoicemail { - messageId: number; - extensionId: number; - processed: boolean; - received: string; - toNumber: string; - extensionNumber: string; - extensionName: string; - fromNumber: string; - fromName?: string; - duration: number; - transcriptionStatus: TranscriptionStatus; - transcription: string | null; - ticketId?: number; - contactId?: number; - contactableType?: string; - contactableId?: number; +declare module "knex/types/tables" { + interface StoredVoicemail { + messageId: number; + extensionId: number; + received: string; + toNumber: string; + extensionNumber: string; + extensionName: string; + fromNumber: string; + fromName: string; + duration: number; + transcriptionStatus: TranscriptionStatus; + transcription: string | null; + ticketId?: number; + contactId?: number; + contactableType?: string; + contactableId?: number; + } + + interface StoredRecording { + messageId: number; + mimeType: string; + audio: ArrayBuffer; + } + + interface Tables { + voicemails: StoredVoicemail; + recordings: StoredRecording; + } }