Add "recordings" table, save raw audio blobs in it
Other changes: - Move Stored(Voicemail|Recording) interfaces into knex/types/tables module for reduced boilerplate. - Change updateStoredVoicemail to take Partial<StoredVoicemail>, allowing to only update only some columns
This commit is contained in:
parent
dfabc13e8e
commit
3452cd143a
13
src/db/migrations/20210311061107_create_audio_table.ts
Normal file
13
src/db/migrations/20210311061107_create_audio_table.ts
Normal file
@ -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");
|
||||
}
|
@ -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,
|
||||
|
@ -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,7 +232,8 @@ export function ticketize(
|
||||
recording: Recording,
|
||||
transcription: Transcription
|
||||
) {
|
||||
return db<StoredVoicemail>("voicemails").insert({
|
||||
await db.transaction(async (trx) => {
|
||||
await trx("voicemails").insert({
|
||||
messageId: message.id,
|
||||
extensionId: message.extensionId,
|
||||
received: message.creationTime,
|
||||
@ -242,16 +246,24 @@ export function ticketize(
|
||||
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<StoredVoicemail>("voicemails")
|
||||
.update({ ...voicemail })
|
||||
.where({ messageId: voicemail.messageId });
|
||||
async function updateStoredVoicemail(voicemail: Partial<StoredVoicemail>) {
|
||||
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<StoredVoicemail>("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<StoredVoicemail>("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<StoredVoicemail>("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,
|
||||
|
22
src/types.ts
22
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,16 +106,16 @@ export interface Transcription {
|
||||
text: string | null;
|
||||
}
|
||||
|
||||
export interface StoredVoicemail {
|
||||
declare module "knex/types/tables" {
|
||||
interface StoredVoicemail {
|
||||
messageId: number;
|
||||
extensionId: number;
|
||||
processed: boolean;
|
||||
received: string;
|
||||
toNumber: string;
|
||||
extensionNumber: string;
|
||||
extensionName: string;
|
||||
fromNumber: string;
|
||||
fromName?: string;
|
||||
fromName: string;
|
||||
duration: number;
|
||||
transcriptionStatus: TranscriptionStatus;
|
||||
transcription: string | null;
|
||||
@ -123,4 +123,16 @@ export interface StoredVoicemail {
|
||||
contactId?: number;
|
||||
contactableType?: string;
|
||||
contactableId?: number;
|
||||
}
|
||||
|
||||
interface StoredRecording {
|
||||
messageId: number;
|
||||
mimeType: string;
|
||||
audio: ArrayBuffer;
|
||||
}
|
||||
|
||||
interface Tables {
|
||||
voicemails: StoredVoicemail;
|
||||
recordings: StoredRecording;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user