shellphone.app/app/messages/api/webhook/incoming-message.ts
m5r 2cf53533a3 replace early returns with NotFoundError
test /api/webhook/incoming-message
2021-08-02 21:44:04 +08:00

128 lines
3.1 KiB
TypeScript

import type { BlitzApiRequest, BlitzApiResponse } from "blitz";
import { getConfig } from "blitz";
import twilio from "twilio";
import type { ApiError } from "../../../api/_types";
import appLogger from "../../../../integrations/logger";
import db from "../../../../db";
import insertIncomingMessageQueue from "../queue/insert-incoming-message";
import notifyIncomingMessageQueue from "../queue/notify-incoming-message";
const logger = appLogger.child({ route: "/api/webhook/incoming-message" });
const { serverRuntimeConfig } = getConfig();
export default async function incomingMessageHandler(req: BlitzApiRequest, res: BlitzApiResponse) {
if (req.method !== "POST") {
const statusCode = 405;
const apiError: ApiError = {
statusCode,
errorMessage: `Method ${req.method} Not Allowed`,
};
logger.error(apiError);
res.setHeader("Allow", ["POST"]);
res.status(statusCode).send(apiError);
return;
}
const twilioSignature = req.headers["X-Twilio-Signature"] || req.headers["x-twilio-signature"];
if (!twilioSignature || Array.isArray(twilioSignature)) {
const statusCode = 400;
const apiError: ApiError = {
statusCode,
errorMessage: "Invalid header X-Twilio-Signature",
};
logger.error(apiError);
res.status(statusCode).send(apiError);
return;
}
const body: Body = req.body;
try {
const customerPhoneNumber = await db.phoneNumber.findFirst({
where: { phoneNumber: body.To },
});
if (!customerPhoneNumber) {
// phone number is not registered by any of our customer
res.status(500).end();
return;
}
const customer = await db.customer.findFirst({
where: { id: customerPhoneNumber.customerId },
});
if (!customer || !customer.authToken) {
res.status(500).end();
return;
}
const url = `https://${serverRuntimeConfig.app.baseUrl}/api/webhook/incoming-message`;
const isRequestValid = twilio.validateRequest(customer.authToken, twilioSignature, url, req.body);
if (!isRequestValid) {
const statusCode = 400;
const apiError: ApiError = {
statusCode,
errorMessage: "Invalid webhook",
};
logger.error(apiError);
res.status(statusCode).send(apiError);
return;
}
const messageSid = body.MessageSid;
const customerId = customer.id;
await Promise.all([
notifyIncomingMessageQueue.enqueue(
{
messageSid,
customerId,
},
{ id: `notify-${messageSid}` },
),
insertIncomingMessageQueue.enqueue(
{
messageSid,
customerId,
},
{ id: `insert-${messageSid}` },
),
]);
res.setHeader("content-type", "text/html");
res.status(200).send("<Response></Response>");
} catch (error) {
const statusCode = error.statusCode ?? 500;
const apiError: ApiError = {
statusCode,
errorMessage: error.message,
};
logger.error(error);
res.status(statusCode).send(apiError);
}
}
type Body = {
ToCountry: string;
ToState: string;
SmsMessageSid: string;
NumMedia: string;
ToCity: string;
FromZip: string;
SmsSid: string;
FromState: string;
SmsStatus: string;
FromCity: string;
Body: string;
FromCountry: string;
To: string;
ToZip: string;
NumSegments: string;
MessageSid: string;
AccountSid: string;
From: string;
ApiVersion: string;
};