From 1f37eb45d5ebcdf3e2a51b49e167ec621a224c0c Mon Sep 17 00:00:00 2001 From: m5r Date: Sun, 22 May 2022 01:17:43 +0200 Subject: [PATCH] add recipient field to messages and phone calls --- app/features/messages/loaders/messages.ts | 48 +++++++------------ app/features/settings/actions/phone.ts | 14 ++++-- app/queues/insert-messages.server.ts | 29 ++++++----- app/queues/insert-phone-calls.server.ts | 27 ++++++----- .../20220517184134_init/migration.sql | 10 +++- prisma/schema.prisma | 8 +++- 6 files changed, 78 insertions(+), 58 deletions(-) diff --git a/app/features/messages/loaders/messages.ts b/app/features/messages/loaders/messages.ts index 77d4bfe..a26c45c 100644 --- a/app/features/messages/loaders/messages.ts +++ b/app/features/messages/loaders/messages.ts @@ -1,14 +1,14 @@ import type { LoaderFunction } from "@remix-run/node"; import { json } from "superjson-remix"; import { parsePhoneNumber } from "awesome-phonenumber"; -import { type Message, Prisma, Direction } from "@prisma/client"; +import { type Message, Prisma } from "@prisma/client"; import db from "~/utils/db.server"; import { requireLoggedIn, type SessionData } from "~/utils/auth.server"; export type MessagesLoaderData = { user: { hasPhoneNumber: boolean }; - conversations: Record | undefined; + conversations: Conversations | undefined; }; type Conversation = { @@ -18,23 +18,23 @@ type Conversation = { }; const loader: LoaderFunction = async ({ request }) => { - const sessionData = await requireLoggedIn(request); + const { phoneNumber } = await requireLoggedIn(request); return json({ - user: { hasPhoneNumber: Boolean(sessionData.phoneNumber) }, - conversations: await getConversations(sessionData.phoneNumber), + user: { hasPhoneNumber: Boolean(phoneNumber) }, + conversations: await getConversations(phoneNumber), }); }; export default loader; +type Conversations = Record; + async function getConversations(sessionPhoneNumber: SessionData["phoneNumber"]) { if (!sessionPhoneNumber) { return; } - const phoneNumber = await db.phoneNumber.findUnique({ - where: { id: sessionPhoneNumber.id }, - }); + const phoneNumber = await db.phoneNumber.findUnique({ where: { id: sessionPhoneNumber.id } }); if (!phoneNumber || phoneNumber.isFetchingMessages) { return; } @@ -42,34 +42,22 @@ async function getConversations(sessionPhoneNumber: SessionData["phoneNumber"]) const messages = await db.message.findMany({ where: { phoneNumberId: phoneNumber.id }, orderBy: { sentAt: Prisma.SortOrder.desc }, + distinct: "recipient", }); - let conversations: Record = {}; - for (const message of messages) { - let recipient: string; - if (message.direction === Direction.Outbound) { - recipient = message.to; - } else { - recipient = message.from; - } + return messages.reduce((conversations, message) => { + const recipient = message.recipient; const formattedPhoneNumber = parsePhoneNumber(recipient).getNumber("international"); - if (!conversations[recipient]) { - conversations[recipient] = { - recipient, - formattedPhoneNumber, - lastMessage: message, - }; - } - - if (message.sentAt > conversations[recipient].lastMessage.sentAt) { - conversations[recipient].lastMessage = message; - } + conversations[recipient] = { + recipient, + formattedPhoneNumber, + lastMessage: message, + }; /*conversations[recipient]!.messages.push({ ...message, content: decrypt(message.content, organization.encryptionKey), });*/ - } - - return conversations; + return conversations; + }, {}); } diff --git a/app/features/settings/actions/phone.ts b/app/features/settings/actions/phone.ts index d147dd5..897fa3d 100644 --- a/app/features/settings/actions/phone.ts +++ b/app/features/settings/actions/phone.ts @@ -4,7 +4,8 @@ import { z } from "zod"; import db from "~/utils/db.server"; import { type FormError, validate } from "~/utils/validation.server"; -import { requireLoggedIn } from "~/utils/auth.server"; +import { refreshSessionData, requireLoggedIn } from "~/utils/auth.server"; +import { commitSession } from "~/utils/session.server"; import setTwilioWebhooksQueue from "~/queues/set-twilio-webhooks.server"; type SetPhoneNumberFailureActionData = { errors: FormError; submitted?: never }; @@ -39,9 +40,16 @@ const action: ActionFunction = async ({ request }) => { phoneNumberId: validation.data.phoneNumberSid, organizationId: organization.id, }); - console.log("queued"); + const { session } = await refreshSessionData(request); - return json({ submitted: true }); + return json( + { submitted: true }, + { + headers: { + "Set-Cookie": await commitSession(session), + }, + }, + ); }; export default action; diff --git a/app/queues/insert-messages.server.ts b/app/queues/insert-messages.server.ts index 26e87fd..b7ac612 100644 --- a/app/queues/insert-messages.server.ts +++ b/app/queues/insert-messages.server.ts @@ -1,5 +1,5 @@ import type { MessageInstance } from "twilio/lib/rest/api/v2010/account/message"; -import type { Message } from "@prisma/client"; +import { type Message, Direction } from "@prisma/client"; import { Queue } from "~/utils/queue.server"; import db from "~/utils/db.server"; @@ -22,20 +22,25 @@ export default Queue("insert messages", async ({ data }) => { } const sms = messages - .map((message) => ({ - id: message.sid, - phoneNumberId: phoneNumber.id, - content: message.body, - from: message.from, - to: message.to, - status: translateMessageStatus(message.status), - direction: translateMessageDirection(message.direction), - sentAt: new Date(message.dateCreated), - })) + .map((message) => { + const status = translateMessageStatus(message.status); + const direction = translateMessageDirection(message.direction); + return { + id: message.sid, + phoneNumberId: phoneNumber.id, + content: message.body, + recipient: direction === Direction.Outbound ? message.to : message.from, + from: message.from, + to: message.to, + status, + direction, + sentAt: new Date(message.dateCreated), + }; + }) .sort((a, b) => a.sentAt.getTime() - b.sentAt.getTime()); const { count } = await db.message.createMany({ data: sms, skipDuplicates: true }); - logger.info(`inserted ${count} new messages for phoneNumberId=${phoneNumberId}`) + logger.info(`inserted ${count} new messages for phoneNumberId=${phoneNumberId}`); if (!phoneNumber.isFetchingMessages) { return; diff --git a/app/queues/insert-phone-calls.server.ts b/app/queues/insert-phone-calls.server.ts index ac693b0..a225331 100644 --- a/app/queues/insert-phone-calls.server.ts +++ b/app/queues/insert-phone-calls.server.ts @@ -1,5 +1,5 @@ import type { CallInstance } from "twilio/lib/rest/api/v2010/account/call"; -import type { PhoneCall } from "@prisma/client"; +import { type PhoneCall, Direction } from "@prisma/client"; import { Queue } from "~/utils/queue.server"; import db from "~/utils/db.server"; @@ -22,16 +22,21 @@ export default Queue("insert phone calls", async ({ data }) => { } const phoneCalls = calls - .map((call) => ({ - phoneNumberId, - id: call.sid, - from: call.from, - to: call.to, - direction: translateCallDirection(call.direction), - status: translateCallStatus(call.status), - duration: call.duration, - createdAt: new Date(call.dateCreated), - })) + .map((call) => { + const direction = translateCallDirection(call.direction); + const status = translateCallStatus(call.status); + return { + phoneNumberId, + id: call.sid, + recipient: direction === Direction.Outbound ? call.to : call.from, + from: call.from, + to: call.to, + direction, + status, + duration: call.duration, + createdAt: new Date(call.dateCreated), + }; + }) .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()); const ddd = await db.phoneCall.createMany({ data: phoneCalls, skipDuplicates: true }); diff --git a/prisma/migrations/20220517184134_init/migration.sql b/prisma/migrations/20220517184134_init/migration.sql index 6749838..57023d0 100644 --- a/prisma/migrations/20220517184134_init/migration.sql +++ b/prisma/migrations/20220517184134_init/migration.sql @@ -118,6 +118,7 @@ CREATE TABLE "Message" ( "id" TEXT NOT NULL, "sentAt" TIMESTAMPTZ NOT NULL, "content" TEXT NOT NULL, + "recipient" TEXT NOT NULL, "from" TEXT NOT NULL, "to" TEXT NOT NULL, "direction" "Direction" NOT NULL, @@ -131,6 +132,7 @@ CREATE TABLE "Message" ( CREATE TABLE "PhoneCall" ( "id" TEXT NOT NULL, "createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + "recipient" TEXT NOT NULL, "from" TEXT NOT NULL, "to" TEXT NOT NULL, "status" "CallStatus" NOT NULL, @@ -172,11 +174,17 @@ CREATE UNIQUE INDEX "Token_membershipId_key" ON "Token"("membershipId"); -- CreateIndex CREATE UNIQUE INDEX "Token_hashedToken_type_key" ON "Token"("hashedToken", "type"); +-- CreateIndex +CREATE INDEX "Message_phoneNumberId_recipient_idx" ON "Message"("phoneNumberId", "recipient"); + +-- CreateIndex +CREATE INDEX "PhoneCall_phoneNumberId_recipient_idx" ON "PhoneCall"("phoneNumberId", "recipient"); + -- CreateIndex CREATE UNIQUE INDEX "PhoneNumber_organizationId_isCurrent_key" ON "PhoneNumber"("organizationId", "isCurrent") WHERE ("isCurrent" = true); -- AddForeignKey -ALTER TABLE "TwilioAccount" ADD CONSTRAINT "TwilioAccount_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE; +ALTER TABLE "TwilioAccount" ADD CONSTRAINT "TwilioAccount_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9df1e8a..133ba8e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -17,7 +17,7 @@ model TwilioAccount { twimlAppSid String? organizationId String @unique - organization Organization @relation(fields: [organizationId], references: [id]) + organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade) } model Organization { @@ -105,17 +105,21 @@ model Message { id String @id sentAt DateTime @db.Timestamptz(6) content String + recipient String from String to String direction Direction status MessageStatus phoneNumberId String phoneNumber PhoneNumber @relation(fields: [phoneNumberId], references: [id], onDelete: Cascade) + + @@index([phoneNumberId, recipient]) } model PhoneCall { id String @id createdAt DateTime @default(now()) @db.Timestamptz(6) + recipient String from String to String status CallStatus @@ -123,6 +127,8 @@ model PhoneCall { duration String phoneNumberId String phoneNumber PhoneNumber @relation(fields: [phoneNumberId], references: [id], onDelete: Cascade) + + @@index([phoneNumberId, recipient]) } model PhoneNumber {