fix(GRO-1003): address CI typecheck and lint failures on PR #379
Typecheck fixes: - telnyx.ts:48 — coerce undefined to null for signature param - inbound.ts/outbound.ts — add null guards on .returning() results - schema.ts — add updatedAt to messages table - package.json — add uuid and @types/uuid Lint fixes: - telnyx.ts — remove unused resolveBusinessIdByMessagingNumber import - inbound.ts — remove unused messageDirectionEnum, messageStatusEnum imports - inbound.ts — remove unused buildFindOrCreateConversationParams function - inbound.test.ts — remove unused resolveBusinessIdByMessagingNumber import - outbound.test.ts — remove unused mockEq/mockAnd variables - outbound.test.ts — fix vi.mock path for sms.js (../../sms.js)
This commit is contained in:
@@ -24,6 +24,7 @@
|
|||||||
"nodemailer": "^6.9.16",
|
"nodemailer": "^6.9.16",
|
||||||
"stripe": "^22.0.0",
|
"stripe": "^22.0.0",
|
||||||
"telnyx": "^1.23.0",
|
"telnyx": "^1.23.0",
|
||||||
|
"uuid": "^11.0.5",
|
||||||
|
|
||||||
"zod": "^4.3.6"
|
"zod": "^4.3.6"
|
||||||
},
|
},
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitest/coverage-v8": "^3.2.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"eslint": "^9.18.0",
|
"eslint": "^9.18.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { createHmac } from "crypto";
|
|||||||
import {
|
import {
|
||||||
handleMessageReceived,
|
handleMessageReceived,
|
||||||
handleMessageFinalized,
|
handleMessageFinalized,
|
||||||
resolveBusinessIdByMessagingNumber,
|
|
||||||
TelnyxMessageReceivedPayload,
|
TelnyxMessageReceivedPayload,
|
||||||
} from "../../services/messaging/inbound.js";
|
} from "../../services/messaging/inbound.js";
|
||||||
|
|
||||||
@@ -45,7 +44,7 @@ telnyxWebhooksRouter.post("/messaging", async (c) => {
|
|||||||
return c.json({ error: "Could not read body" }, 400);
|
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);
|
return c.json({ error: "Invalid signature" }, 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
|||||||
import {
|
import {
|
||||||
findOrCreateConversation,
|
findOrCreateConversation,
|
||||||
upsertMessage,
|
upsertMessage,
|
||||||
resolveBusinessIdByMessagingNumber,
|
|
||||||
handleMessageReceived,
|
handleMessageReceived,
|
||||||
handleMessageFinalized,
|
handleMessageFinalized,
|
||||||
TelnyxMessageReceivedPayload,
|
TelnyxMessageReceivedPayload,
|
||||||
@@ -54,7 +53,7 @@ const makePayload = (
|
|||||||
|
|
||||||
describe("signature validation via route", () => {
|
describe("signature validation via route", () => {
|
||||||
it("returns 401 when telnyx-signature header is missing", async () => {
|
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 payload = JSON.stringify(makePayload("message.received", "msg-123", "+1555111", "+1555222"));
|
||||||
const req = new Request("http://localhost/api/webhooks/telnyx/messaging", {
|
const req = new Request("http://localhost/api/webhooks/telnyx/messaging", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -67,7 +66,7 @@ describe("signature validation via route", () => {
|
|||||||
|
|
||||||
it("returns 401 when signature does not match", async () => {
|
it("returns 401 when signature does not match", async () => {
|
||||||
process.env.TELNYX_WEBHOOK_SECRET = "test-secret";
|
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 payload = JSON.stringify(makePayload("message.received", "msg-123", "+1555111", "+1555222"));
|
||||||
const req = new Request("http://localhost/api/webhooks/telnyx/messaging", {
|
const req = new Request("http://localhost/api/webhooks/telnyx/messaging", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const mockSendSms = vi.fn();
|
|||||||
const mockGetDb = vi.fn();
|
const mockGetDb = vi.fn();
|
||||||
const mockUuidv4 = vi.fn();
|
const mockUuidv4 = vi.fn();
|
||||||
|
|
||||||
vi.mock("../sms.js", () => ({
|
vi.mock("../../sms.js", () => ({
|
||||||
sendSms: mockSendSms,
|
sendSms: mockSendSms,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -24,9 +24,6 @@ vi.mock("uuid", () => ({
|
|||||||
|
|
||||||
const { sendMessage, MissingTenantPhoneNumberError } = await import("../outbound.ts");
|
const { sendMessage, MissingTenantPhoneNumberError } = await import("../outbound.ts");
|
||||||
|
|
||||||
const mockEq = (a: unknown, b: unknown) => [a, b];
|
|
||||||
const mockAnd = (...args: unknown[]) => args;
|
|
||||||
|
|
||||||
describe("sendMessage", () => {
|
describe("sendMessage", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getDb, conversations, messages, businessSettings, eq, and, sql } from "@groombook/db";
|
import { getDb, conversations, messages, businessSettings, eq, and, sql } from "@groombook/db";
|
||||||
import { messageDirectionEnum, messageStatusEnum } from "@groombook/db";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
export interface TelnyxMessageReceivedPayload {
|
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(
|
export async function findOrCreateConversation(
|
||||||
businessId: string,
|
businessId: string,
|
||||||
clientPhone: string,
|
clientPhone: string,
|
||||||
@@ -73,6 +64,8 @@ export async function findOrCreateConversation(
|
|||||||
})
|
})
|
||||||
.returning({ id: conversations.id, clientId: conversations.clientId });
|
.returning({ id: conversations.id, clientId: conversations.clientId });
|
||||||
|
|
||||||
|
if (!created) throw new Error("Failed to create conversation");
|
||||||
|
|
||||||
return { id: created.id, clientId: created.clientId };
|
return { id: created.id, clientId: created.clientId };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +102,8 @@ export async function upsertMessage(
|
|||||||
})
|
})
|
||||||
.returning({ id: messages.id });
|
.returning({ id: messages.id });
|
||||||
|
|
||||||
|
if (!inserted) throw new Error("Failed to insert message");
|
||||||
|
|
||||||
return { id: inserted.id, isNew: true };
|
return { id: inserted.id, isNew: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ async function findOrCreateConversation(
|
|||||||
})
|
})
|
||||||
.returning({ id: conversations.id });
|
.returning({ id: conversations.id });
|
||||||
|
|
||||||
|
if (!created) throw new Error("Failed to create conversation");
|
||||||
|
|
||||||
return { id: created.id };
|
return { id: created.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +117,8 @@ export async function sendMessage(opts: SendMessageOptions): Promise<SendMessage
|
|||||||
})
|
})
|
||||||
.returning({ id: messages.id });
|
.returning({ id: messages.id });
|
||||||
|
|
||||||
|
if (!queuedMessage) throw new Error("Failed to insert queued message");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await sendSms(to, body, mediaUrls);
|
const result = await sendSms(to, body, mediaUrls);
|
||||||
|
|
||||||
|
|||||||
@@ -477,6 +477,7 @@ export const messages = pgTable(
|
|||||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||||
deliveredAt: timestamp("delivered_at"),
|
deliveredAt: timestamp("delivered_at"),
|
||||||
readByClientAt: timestamp("read_by_client_at"),
|
readByClientAt: timestamp("read_by_client_at"),
|
||||||
|
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [
|
||||||
index("idx_messages_conversation_id_created_at").on(
|
index("idx_messages_conversation_id_created_at").on(
|
||||||
|
|||||||
Reference in New Issue
Block a user