feat: update Twilio configuration to include primary and secondary phone numbers

This commit is contained in:
Simon Larsen
2025-03-14 10:45:52 +00:00
parent c60ebfa962
commit 8f057c11ed
11 changed files with 172 additions and 54 deletions

View File

@@ -72,9 +72,9 @@ const Settings: FunctionComponent = (): ReactElement => {
},
{
field: {
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
},
title: "Twilio Phone Number",
title: "Primary Twilio Phone Number",
fieldType: FormFieldSchemaType.Phone,
required: true,
description: "You can find this in your Twilio console.",
@@ -83,6 +83,19 @@ const Settings: FunctionComponent = (): ReactElement => {
minLength: 2,
},
},
{
field: {
twilioSecondaryPhoneNumbers: true,
},
title: "Secondary Twilio Phone Number",
fieldType: FormFieldSchemaType.LongText,
required: true,
description: "If you have bought more phone numbers from Twilio for specific countries, you can add them here.",
placeholder: "+1234567890, +4444444444",
validation: {
minLength: 2,
},
},
]}
modelDetailProps={{
modelType: GlobalConfig,
@@ -97,12 +110,20 @@ const Settings: FunctionComponent = (): ReactElement => {
},
{
field: {
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
},
title: "Twilio Phone Number",
title: "Primary Twilio Phone Number",
fieldType: FieldType.Phone,
placeholder: "None",
},
{
field: {
twilioSecondaryPhoneNumbers: true,
},
title: "Secondary Twilio Phone Numbers",
fieldType: FieldType.LongText,
placeholder: "None",
},
],
modelId: ObjectID.getZeroObjectID(),
}}

View File

@@ -54,7 +54,8 @@ router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
@@ -97,11 +98,11 @@ router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
);
}
if (!config.twilioPhoneNumber) {
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPhoneNumber is required"),
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}

View File

@@ -53,7 +53,8 @@ router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
projectId: true,
},
});
@@ -96,11 +97,11 @@ router.post("/test", async (req: ExpressRequest, res: ExpressResponse) => {
);
}
if (!config.twilioPhoneNumber) {
if (!config.twilioPrimaryPhoneNumber) {
return Response.sendErrorResponse(
req,
res,
new BadDataException("twilioPhoneNumber is required"),
new BadDataException("twilioPrimaryPhoneNumber is required"),
);
}

View File

@@ -10,6 +10,7 @@ import GlobalConfigService from "Common/Server/Services/GlobalConfigService";
import GlobalConfig, {
EmailServerType,
} from "Common/Models/DatabaseModels/GlobalConfig";
import Phone from "Common/Types/Phone";
export const InternalSmtpPassword: string =
process.env["INTERNAL_SMTP_PASSWORD"] || "";
@@ -196,7 +197,8 @@ export const getTwilioConfig: GetTwilioConfigFunction =
select: {
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
},
});
@@ -207,7 +209,7 @@ export const getTwilioConfig: GetTwilioConfigFunction =
if (
!globalConfig.twilioAccountSID ||
!globalConfig.twilioAuthToken ||
!globalConfig.twilioPhoneNumber
!globalConfig.twilioPrimaryPhoneNumber
) {
return null;
}
@@ -215,7 +217,10 @@ export const getTwilioConfig: GetTwilioConfigFunction =
return {
accountSid: globalConfig.twilioAccountSID,
authToken: globalConfig.twilioAuthToken,
phoneNumber: globalConfig.twilioPhoneNumber,
primaryPhoneNumber: globalConfig.twilioPrimaryPhoneNumber,
secondaryPhoneNumbers: globalConfig.twilioSecondaryPhoneNumbers && globalConfig.twilioSecondaryPhoneNumbers.length > 0 ? globalConfig.twilioSecondaryPhoneNumbers.split(",").map((phoneNumber: string)=>{
return new Phone(phoneNumber.trim());
}) : [],
};
};

View File

@@ -232,8 +232,8 @@ export default class GlobalConfig extends GlobalConfigModel {
})
@TableColumn({
type: TableColumnType.Phone,
title: "Twilio Phone Number",
description: "Phone Number for your Twilio account",
title: "Twilio Primary Phone Number",
description: "Secondary Phone Number for your Twilio account",
})
@Column({
type: ColumnType.Phone,
@@ -242,7 +242,26 @@ export default class GlobalConfig extends GlobalConfigModel {
unique: true,
transformer: Phone.getDatabaseTransformer(),
})
public twilioPhoneNumber?: Phone = undefined;
public twilioPrimaryPhoneNumber?: Phone = undefined;
@ColumnAccessControl({
create: [],
read: [],
update: [],
})
@TableColumn({
type: TableColumnType.LongText,
title: "Twilio Secondary Phone Numbers",
description: "Secondary Phone Number for your Twilio account",
})
@Column({
type: ColumnType.LongText,
length: ColumnLength.LongText,
nullable: true,
unique: false,
})
public twilioSecondaryPhoneNumbers?: string = undefined; // phone numbers seperated by comma
@ColumnAccessControl({
create: [],

View File

@@ -426,5 +426,37 @@ export default class ProjectCallSMSConfig extends BaseModel {
unique: true,
transformer: Phone.getDatabaseTransformer(),
})
public twilioPhoneNumber?: Phone = undefined;
public twilioPrimaryPhoneNumber?: Phone = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.CreateProjectCallSMSConfig,
],
read: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.ProjectMember,
Permission.ReadProjectCallSMSConfig,
],
update: [
Permission.ProjectOwner,
Permission.ProjectAdmin,
Permission.EditProjectCallSMSConfig,
],
})
@TableColumn({
type: TableColumnType.LongText,
title: "Twilio Secondary Phone Numbers",
description: "Secondary Phone Number for your Twilio account",
})
@Column({
type: ColumnType.LongText,
length: ColumnLength.LongText,
nullable: true,
unique: false,
})
public twilioSecondaryPhoneNumbers?: string = undefined; // phone numbers seperated by comma
}

View File

@@ -2,6 +2,7 @@ import DatabaseService from "./DatabaseService";
import TwilioConfig from "../../Types/CallAndSMS/TwilioConfig";
import BadDataException from "../../Types/Exception/BadDataException";
import Model from "Common/Models/DatabaseModels/ProjectCallSMSConfig";
import Phone from "../../Types/Phone";
export class Service extends DatabaseService<Model> {
public constructor() {
@@ -25,7 +26,7 @@ export class Service extends DatabaseService<Model> {
);
}
if (!projectCallSmsConfig.twilioPhoneNumber) {
if (!projectCallSmsConfig.twilioPrimaryPhoneNumber) {
throw new BadDataException(
"Project Call and SMS Config twilio phone number is not set",
);
@@ -40,7 +41,10 @@ export class Service extends DatabaseService<Model> {
return {
accountSid: projectCallSmsConfig.twilioAccountSID.toString(),
authToken: projectCallSmsConfig.twilioAuthToken.toString(),
phoneNumber: projectCallSmsConfig.twilioPhoneNumber,
primaryPhoneNumber: projectCallSmsConfig.twilioPrimaryPhoneNumber,
secondaryPhoneNumbers: projectCallSmsConfig.twilioSecondaryPhoneNumbers && projectCallSmsConfig.twilioSecondaryPhoneNumbers.length > 0 ? projectCallSmsConfig.twilioSecondaryPhoneNumbers.split(",").map((phone: string)=>{
return new Phone(phone);
}) : [],
};
}
}

View File

@@ -262,7 +262,9 @@ export class Service extends DatabaseService<Model> {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
},
},
props: {
@@ -795,7 +797,8 @@ export class Service extends DatabaseService<Model> {
_id: true,
twilioAccountSID: true,
twilioAuthToken: true,
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
twilioSecondaryPhoneNumbers: true,
},
subscriberTimezones: true,
reportDataInDays: true,

View File

@@ -3,5 +3,6 @@ import Phone from "../Phone";
export default interface TwilioConfig {
accountSid: string;
authToken: string;
phoneNumber: Phone;
primaryPhoneNumber: Phone;
secondaryPhoneNumbers: Phone[];
}

View File

@@ -170,9 +170,9 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
},
{
field: {
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
},
title: "Twilio Phone Number",
title: "Twilio Primary Phone Number",
stepId: "twilio-info",
fieldType: FormFieldSchemaType.Phone,
required: true,
@@ -182,6 +182,23 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
minLength: 2,
},
},
// add twilioSecondaryPhoneNumbers
{
field: {
twilioSecondaryPhoneNumbers: true,
},
title: "Twilio Secondary Phone Numbers",
stepId: "twilio-info",
fieldType: FormFieldSchemaType.LongText,
required: false,
description:
"If you have multiple phone numbers, add them here. These numbers will be used to send SMS and make calls if the country code matches instead of primary phone number. If the country code does not match, then primary phone number will be used.",
placeholder: "+441234567890, +461234567890",
validation: {
minLength: 2,
},
},
]}
showRefreshButton={true}
viewPageRoute={Navigation.getCurrentRoute()}
@@ -208,10 +225,17 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
},
},
{
title: "Twilio Phone Number",
title: "Twilio Primary Phone Number",
type: FieldType.Phone,
field: {
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
},
},
{
title: "Twilio Secondary Primary Phone Numbers",
type: FieldType.LongText,
field: {
twilioSecondaryPhoneNumbers: true,
},
},
]}
@@ -240,11 +264,18 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
},
{
field: {
twilioPhoneNumber: true,
twilioPrimaryPhoneNumber: true,
},
title: "Twilio Phone Number",
type: FieldType.Phone,
},
{
field: {
twilioSecondaryPhoneNumbers: true,
},
title: "Twilio Phone Number",
type: FieldType.LongText,
},
]}
/>
@@ -284,19 +315,19 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
const response:
| HTTPResponse<EmptyResponseData>
| HTTPErrorResponse = await API.post(
URL.fromString(NOTIFICATION_URL.toString()).addRoute(
`/sms/test`,
),
URL.fromString(NOTIFICATION_URL.toString()).addRoute(
`/sms/test`,
),
{
toPhone: values["toPhone"],
callSMSConfigId: new ObjectID(
currentCallSMSTestConfig["_id"]
? currentCallSMSTestConfig["_id"].toString()
: "",
).toString(),
},
);
{
toPhone: values["toPhone"],
callSMSConfigId: new ObjectID(
currentCallSMSTestConfig["_id"]
? currentCallSMSTestConfig["_id"].toString()
: "",
).toString(),
},
);
if (response.isSuccess()) {
setIsCallSMSTestLoading(false);
setShowSMSTestModal(false);
@@ -321,7 +352,7 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
title={`SMS Sent`}
error={
error ===
"Error connecting to server. Please try again in few minutes."
"Error connecting to server. Please try again in few minutes."
? "Request timed out. Please check your twilio credentials and make sure they are correct."
: error
}
@@ -373,19 +404,19 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
const response:
| HTTPResponse<EmptyResponseData>
| HTTPErrorResponse = await API.post(
URL.fromString(NOTIFICATION_URL.toString()).addRoute(
`/call/test`,
),
URL.fromString(NOTIFICATION_URL.toString()).addRoute(
`/call/test`,
),
{
toPhone: values["toPhone"],
callSMSConfigId: new ObjectID(
currentCallSMSTestConfig["_id"]
? currentCallSMSTestConfig["_id"].toString()
: "",
).toString(),
},
);
{
toPhone: values["toPhone"],
callSMSConfigId: new ObjectID(
currentCallSMSTestConfig["_id"]
? currentCallSMSTestConfig["_id"].toString()
: "",
).toString(),
},
);
if (response.isSuccess()) {
setIsCallSMSTestLoading(false);
setShowCallTestModal(false);
@@ -410,7 +441,7 @@ const CustomCallSMSTable: FunctionComponent = (): ReactElement => {
title={`Call Sent`}
error={
error ===
"Error connecting to server. Please try again in few minutes."
"Error connecting to server. Please try again in few minutes."
? "Request timed out. Please check your twilio credentials and make sure they are correct."
: error
}

View File

@@ -30,7 +30,7 @@ export default class UpdateGlobalConfigFromEnv extends DataMigrationBase {
twilioAccountSID: process.env["TWILIO_ACCOUNT_SID"] || "",
twilioAuthToken: process.env["TWILIO_AUTH_TOKEN"] || "",
twilioPhoneNumber: process.env["TWILIO_PHONE_NUMBER"] || "",
twilioPrimaryPhoneNumber: process.env["TWILIO_PHONE_NUMBER"] || "",
// Update SMTP
smtpUsername: process.env["SMTP_USERNAME"] || "",