diff --git a/apps/api/package.json b/apps/api/package.json index e8d4488..a7c8876 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -24,6 +24,7 @@ "nodemailer": "^6.9.16", "stripe": "^22.0.0", "telnyx": "^1.23.0", + "uuid": "^11.0.5", "zod": "^4.3.6" }, @@ -31,6 +32,7 @@ "@types/node": "^22.10.7", "@types/node-cron": "^3.0.11", "@types/nodemailer": "^6.4.17", + "@types/uuid": "^10.0.0", "@vitest/coverage-v8": "^3.2.4", "eslint": "^9.18.0", "tsx": "^4.19.2", diff --git a/apps/api/src/routes/webhooks/telnyx.ts b/apps/api/src/routes/webhooks/telnyx.ts index 723663d..f8393a3 100644 --- a/apps/api/src/routes/webhooks/telnyx.ts +++ b/apps/api/src/routes/webhooks/telnyx.ts @@ -3,7 +3,6 @@ import { createHmac } from "crypto"; import { handleMessageReceived, handleMessageFinalized, - resolveBusinessIdByMessagingNumber, TelnyxMessageReceivedPayload, } from "../../services/messaging/inbound.js"; @@ -45,7 +44,7 @@ telnyxWebhooksRouter.post("/messaging", async (c) => { return c.json({ error: "Could not read body" }, 400); } - if (!validateTelnyxSignature(rawBody, signature)) { + if (!validateTelnyxSignature(rawBody, signature ?? null)) { return c.json({ error: "Invalid signature" }, 401); } diff --git a/apps/api/src/services/messaging/__tests__/inbound.test.ts b/apps/api/src/services/messaging/__tests__/inbound.test.ts index aab629d..76dfeaf 100644 --- a/apps/api/src/services/messaging/__tests__/inbound.test.ts +++ b/apps/api/src/services/messaging/__tests__/inbound.test.ts @@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { findOrCreateConversation, upsertMessage, - resolveBusinessIdByMessagingNumber, handleMessageReceived, handleMessageFinalized, TelnyxMessageReceivedPayload, @@ -54,7 +53,7 @@ const makePayload = ( describe("signature validation via route", () => { it("returns 401 when telnyx-signature header is missing", async () => { - const { telnyxWebhooksRouter } = await import("../../routes/webhooks/telnyx.js"); + const { telnyxWebhooksRouter } = await import("../../../routes/webhooks/telnyx.js"); const payload = JSON.stringify(makePayload("message.received", "msg-123", "+1555111", "+1555222")); const req = new Request("http://localhost/api/webhooks/telnyx/messaging", { method: "POST", @@ -67,7 +66,7 @@ describe("signature validation via route", () => { it("returns 401 when signature does not match", async () => { process.env.TELNYX_WEBHOOK_SECRET = "test-secret"; - const { telnyxWebhooksRouter } = await import("../../routes/webhooks/telnyx.js"); + const { telnyxWebhooksRouter } = await import("../../../routes/webhooks/telnyx.js"); const payload = JSON.stringify(makePayload("message.received", "msg-123", "+1555111", "+1555222")); const req = new Request("http://localhost/api/webhooks/telnyx/messaging", { method: "POST", diff --git a/apps/api/src/services/messaging/__tests__/outbound.test.ts b/apps/api/src/services/messaging/__tests__/outbound.test.ts index d051c29..0e8545f 100644 --- a/apps/api/src/services/messaging/__tests__/outbound.test.ts +++ b/apps/api/src/services/messaging/__tests__/outbound.test.ts @@ -4,7 +4,7 @@ const mockSendSms = vi.fn(); const mockGetDb = vi.fn(); const mockUuidv4 = vi.fn(); -vi.mock("../sms.js", () => ({ +vi.mock("../../sms.js", () => ({ sendSms: mockSendSms, })); @@ -24,9 +24,6 @@ vi.mock("uuid", () => ({ const { sendMessage, MissingTenantPhoneNumberError } = await import("../outbound.ts"); -const mockEq = (a: unknown, b: unknown) => [a, b]; -const mockAnd = (...args: unknown[]) => args; - describe("sendMessage", () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/apps/api/src/services/messaging/inbound.ts b/apps/api/src/services/messaging/inbound.ts index d6d683e..723afb0 100644 --- a/apps/api/src/services/messaging/inbound.ts +++ b/apps/api/src/services/messaging/inbound.ts @@ -1,5 +1,4 @@ import { getDb, conversations, messages, businessSettings, eq, and, sql } from "@groombook/db"; -import { messageDirectionEnum, messageStatusEnum } from "@groombook/db"; import { v4 as uuidv4 } from "uuid"; export interface TelnyxMessageReceivedPayload { @@ -20,14 +19,6 @@ export interface TelnyxMessageReceivedPayload { }; } -function buildFindOrCreateConversationParams(businessId: string, clientPhone: string, businessNumber: string) { - return { - businessId, - externalNumber: clientPhone, - businessNumber, - }; -} - export async function findOrCreateConversation( businessId: string, clientPhone: string, @@ -73,6 +64,8 @@ export async function findOrCreateConversation( }) .returning({ id: conversations.id, clientId: conversations.clientId }); + if (!created) throw new Error("Failed to create conversation"); + return { id: created.id, clientId: created.clientId }; } @@ -109,6 +102,8 @@ export async function upsertMessage( }) .returning({ id: messages.id }); + if (!inserted) throw new Error("Failed to insert message"); + return { id: inserted.id, isNew: true }; } @@ -184,4 +179,4 @@ export async function handleMessageFinalized(payload: TelnyxMessageReceivedPaylo } return { messageId: existing.id, newStatus }; -} \ No newline at end of file +} diff --git a/apps/api/src/services/messaging/outbound.ts b/apps/api/src/services/messaging/outbound.ts index 320d78b..9b2896e 100644 --- a/apps/api/src/services/messaging/outbound.ts +++ b/apps/api/src/services/messaging/outbound.ts @@ -66,6 +66,8 @@ async function findOrCreateConversation( }) .returning({ id: conversations.id }); + if (!created) throw new Error("Failed to create conversation"); + return { id: created.id }; } @@ -115,6 +117,8 @@ export async function sendMessage(opts: SendMessageOptions): Promise [ index("idx_messages_conversation_id_created_at").on(