Skip to content

Commit

Permalink
DEVEXP-410: Add support for Calls
Browse files Browse the repository at this point in the history
  • Loading branch information
asein-sinch committed Apr 27, 2024
1 parent 441932c commit e800ea5
Show file tree
Hide file tree
Showing 17 changed files with 307 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/simple-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,4 @@ yarn run numbers:regions:list
| | [./src/elastic-sip-trunking/country-permissions/update.ts](./src/elastic-sip-trunking/country-permissions/update.ts) | |
| Phone Numbers | [./src/elastic-sip-trunking/phone-numbers/get.ts](./src/elastic-sip-trunking/phone-numbers/get.ts) | SIP_TRUNK_PHONE_NUMBER |
| | [./src/elastic-sip-trunking/phone-numbers/list.ts](./src/elastic-sip-trunking/phone-numbers/list.ts) | |
| Calls | [./src/elastic-sip-trunking/calls-history/find.ts](./src/elastic-sip-trunking/calls-history/find.ts) | SIP_TRUNK_ID |
1 change: 1 addition & 0 deletions examples/simple-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
"elasticSipTrunks:countryPermissions:update": "ts-node src/elastic-sip-trunking/country-permissions/update.ts",
"elasticSipTrunks:phoneNumbers:get": "ts-node src/elastic-sip-trunking/phone-numbers/get.ts",
"elasticSipTrunks:phoneNumbers:list": "ts-node src/elastic-sip-trunking/phone-numbers/list.ts",
"elasticSipTrunks:calls:find": "ts-node src/elastic-sip-trunking/calls-history/find.ts",
"fax:services:create": "ts-node src/fax/services/create.ts",
"fax:services:get": "ts-node src/fax/services/get.ts",
"fax:services:list": "ts-node src/fax/services/list.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ElasticSipTrunking, PageResult } from '@sinch/sdk-core';
import {
getPrintFormat,
getSipTrunkIdFromConfig,
initElasticSipTrunkingService,
printFullResponse,
} from '../../config';

const populateCallsList = (
callsPage: PageResult<ElasticSipTrunking.Call>,
callsList: ElasticSipTrunking.Call[],
callsDetailsList: string[],
) => {
callsPage.data.map((call: ElasticSipTrunking.Call) => {
callsList.push(call);
callsDetailsList.push(`${call.callId} - From: ${call.from} - To: ${call.to}`);
});
};

(async () => {
console.log('*************');
console.log('* findCalls *');
console.log('*************');

const trunkId = getSipTrunkIdFromConfig();

const requestData: ElasticSipTrunking.FindCallsRequestData = {
trunkId,
callResult: 'COMPLETED',
direction: 'INBOUND',
};

const elasticSipTrunkingService = initElasticSipTrunkingService();

// ----------------------------------------------
// Method 1: Fetch the data page by page manually
// ----------------------------------------------
let response = await elasticSipTrunkingService.calls.find(requestData);

const callsList: ElasticSipTrunking.Call[] = [];
const callsDetailsList: string[] = [];

// Loop on all the pages to get all the active numbers
let reachedEndOfPages = false;
while (!reachedEndOfPages) {
populateCallsList(response, callsList, callsDetailsList);
if (response.hasNextPage) {
response = await response.nextPage();
} else {
reachedEndOfPages = true;
}
}

const printFormat = getPrintFormat(process.argv);

if (printFormat === 'pretty') {
console.log(callsDetailsList.length > 0
? 'List of calls found:\n' + callsDetailsList.join('\n')
: 'Sorry, no calls were found.');
} else {
printFullResponse(callsList);
}

// ---------------------------------------------------------------------
// Method 2: Use the iterator and fetch data on more pages automatically
// ---------------------------------------------------------------------
for await (const call of elasticSipTrunkingService.calls.find(requestData)) {
if (printFormat === 'pretty') {
console.log(`${call.callId} - From: ${call.from} - To: ${call.to}`);
} else {
console.log(call);
}
}

})();
36 changes: 36 additions & 0 deletions packages/elastic-sip-trunking/src/models/v1/call/call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Money } from '../money';
import { CallResult, DirectionEnum } from '../enum';

/**
* The call resource represents an inbound or outbound connection between Sinch and a supported device
*/
export interface Call {
/** The unique identifier of the call. */
callId?: string;
/** For `INBOUND` calls, this is the Sinch number the phone dialed. For `OUTBOUND` calls, this is the phone number you want to make a call to. Formatted according e164 format. */
to?: string;
/** For `INBOUND` calls, this is the number of the person calling. When making an outbound call set this to the your Sinch number you want to show up as Caller Id. The call\'s origination, formatted according to the call\'s `callType`. For calls in the telephone network, this is a phone number in [E.164](/docs/voice/api-reference/call-types) format, including the leading `+`. More info see [Call types](/docs/voice/api-reference/call-types) */
from?: string;
/** Describes whether the call was `INBOUND` *to* your Sinch number or was `OUTBOUND` and made *from* your Sinch number. */
direction?: DirectionEnum;
/** Time when call was answered */
answerTime?: Date;
/** The time the call ended */
endTime?: Date;
/** The duration of the call in seconds. For inbound calls, this is the time from when the call started until the call ended. For outbound calls, this is the time from when the call was answered until the call ended. */
durationSeconds?: number;
/** The result of the call */
callResult?: CallResult;
/** @see Money */
pricePerMinute?: Money;
/** The duration of the call adjusted with the billing period. */
billingDurationSeconds?: number;
/** @see Money */
price?: Money;
/** The time the call was created. */
createTime?: Date;
/** The ID of the project that the call belongs to. */
projectId?: string;
/** The ID of the trunk that the call was made through. */
trunkId?: string;
}
1 change: 1 addition & 0 deletions packages/elastic-sip-trunking/src/models/v1/call/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { Call } from './call';
3 changes: 3 additions & 0 deletions packages/elastic-sip-trunking/src/models/v1/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type DirectionEnum = 'INBOUND' | 'OUTBOUND';

export type CallResult = 'COMPLETED' | 'NO_ANSWER' | 'CANCEL' | 'BUSY' | 'FAILED';
3 changes: 3 additions & 0 deletions packages/elastic-sip-trunking/src/models/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
export * from './access-control-list';
export * from './add-access-control-list-to-trunk';
export * from './call';
export * from './country-permission';
export * from './ip-range';
export * from './list-country-permissions-response';
export * from './money';
export * from './phoneNumber';
export * from './sip-endpoint';
export * from './sip-trunk';
export * from './update-access-control-list-request';
export * from './update-country-permission-request';
export * from './enum';
export * from './requests';
1 change: 1 addition & 0 deletions packages/elastic-sip-trunking/src/models/v1/money/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type { Money } from './money';
9 changes: 9 additions & 0 deletions packages/elastic-sip-trunking/src/models/v1/money/money.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* This is where we need description to be overridden by `$ref:` description
*/
export interface Money {
/** The 3-letter currency code defined in ISO 4217. */
currencyCode?: string;
/** The amount with 4 decimals and decimal delimiter `.`. */
amount?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CallResult, DirectionEnum } from '../../enum';

export interface FindCallsRequestData {
/** A phone number that you want to use to filter results. You can pass a partial number to get all calls sent to numbers that start with the number you passed. */
'from'?: string;
/** Only include calls made to this number or address. You can pass a partial number to get all calls sent to numbers that start with the number you passed. */
'to'?: string;
/** Only include calls made from this trunk. */
'trunkId'?: string;
/** Filter calls based on `createTime`. You make the query more precise, fewer results will be returned. For example, 2021-02-01 will return all calls from the first of February 2021, and 2021-02-01T14:00:00Z will return all calls after 14:00 on the first of February. This field also supports <= and >= to search for calls in a range ?createTime>=2021-10-01&createTime<=2021-10-30 to get a list if calls for october 2021 It is also possible to submit partial dates for example createTime=2021-02 will return all calls for February ***Defaults to 24 hours*** ***Internal notes*** If a customer submits = and not <> we should add min and max for the date range psueodo sql ``` createTime = 2021-02-01 select * from calls where createTime >= 2021-02-01 and createTime <= 2021-02-01T23:59:59Z createTime = 2021-02-01T08 select * from calls where createTime >= 2021-02-01T08:00:00 and createTime <= 2021-02-01T08:59:59Z ``` but if they submit < or > we should just use the value they submitted and parse it a complete date */
'createTime'?: string;
/** only include calls by on the callResult(s), example callResult=COMPLETED will return all calls which have completed normally. */
'callResult'?: CallResult;
/** only include calls by on the direction(s), example direction=INBOUND,OUTBOUND will return all calls that are inbound or outbound. */
'direction'?: DirectionEnum;
/** The page you want to fetch */
'page'?: string;
/** The maximum number of items to return per request. The default is 100 and the maximum is 1000. If you need to export larger amounts and pagination is not suitable for you can use the Export function in the dashboard. */
'pageSize'?: number;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './access-control-list/access-control-list-request-data';
export * from './calls-history/calls-history-request-data';
export * from './country-permissions/country-permissions-request-data';
export * from './phone-numbers/phone-numbers-request-data';
export * from './sip-endpoints/sip-endpoints-request-data';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CallsHistoryApi } from './calls-history-api';
import { Call, FindCallsRequestData } from '../../../models';
import { ApiListPromise } from '@sinch/sdk-client';

export class CallsHistoryApiFixture implements Partial<Readonly<CallsHistoryApi>> {

/**
* Fixture associated to function find
*/
public find: jest.Mock<ApiListPromise<Call>, [FindCallsRequestData]> = jest.fn();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Call, FindCallsRequestData } from '../../../models';
import {
RequestBody,
SinchClientParameters,
ApiListPromise,
PaginatedApiProperties,
PaginationEnum,
buildPageResultPromise,
createIteratorMethodsForPagination,
} from '@sinch/sdk-client';
import { ElasticSipTrunkingDomainApi } from '../elastic-sip-trunking-domain-api';

export class CallsHistoryApi extends ElasticSipTrunkingDomainApi {

/**
* Initialize your interface
*
* @param {SinchClientParameters} sinchClientParameters - The parameters used to initialize the API Client.
*/
constructor(sinchClientParameters: SinchClientParameters) {
super(sinchClientParameters, 'CallsHistoryApi');
}

/**
* Find calls
* Find calls by query parameters.
* @param { FindCallsRequestData } data - The data to provide to the API call.
* @return { ApiListPromise<Call> }
*/
public find(data: FindCallsRequestData): ApiListPromise<Call> {
this.client = this.getSinchClient();
data['createTime'] = data['createTime'] !== undefined ? data['createTime'] : 'now-24h';
const getParams = this.client.extractQueryParams<FindCallsRequestData>(data, [
'from', 'to', 'trunkId', 'createTime', 'callResult', 'direction', 'page', 'pageSize']);
const headers: { [key: string]: string | undefined } = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};

const body: RequestBody = '';
const basePathUrl = `${this.client.apiClientOptions.hostname}/v1/projects/${this.client.apiClientOptions.projectId}/calls`;

const requestOptionsPromise = this.client.prepareOptions(basePathUrl, 'GET', getParams, headers, body || undefined);

const operationProperties: PaginatedApiProperties = {
pagination: PaginationEnum.PAGE2,
apiName: this.apiName,
operationId: 'FindCalls',
dataKey: 'calls',
};

// Create the promise containing the response wrapped as a PageResult
const listPromise = buildPageResultPromise<Call>(
this.client,
requestOptionsPromise,
operationProperties,
);

// Add properties to the Promise to offer the possibility to use it as an iterator
Object.assign(
listPromise,
createIteratorMethodsForPagination<Call>(
this.client, requestOptionsPromise, listPromise, operationProperties),
);

return listPromise as ApiListPromise<Call>;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './calls-history-api';
export * from './calls-history-api.jest.fixture';
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { AccessControlListApi } from './access-control-list';
import { SipEndpointsApi } from './sip-endpoints';
import { CountryPermissionsApi } from './country-permissions';
import { PhoneNumbersApi } from './phone-numbers';
import { CallsHistoryApi } from './calls-history';

export class ElasticSipTrunkingService {

Expand All @@ -12,13 +13,15 @@ export class ElasticSipTrunkingService {
public readonly accessControlList: AccessControlListApi;
public readonly countryPermissions: CountryPermissionsApi;
public readonly phoneNumbers: PhoneNumbersApi;
public readonly calls: CallsHistoryApi;

constructor(params: SinchClientParameters) {
this.sipTrunks = new SipTrunksApi(params);
this.sipEndpoints = new SipEndpointsApi(params);
this.accessControlList = new AccessControlListApi(params);
this.countryPermissions = new CountryPermissionsApi(params);
this.phoneNumbers = new PhoneNumbersApi(params);
this.calls = new CallsHistoryApi(params);
}

/**
Expand All @@ -32,5 +35,6 @@ export class ElasticSipTrunkingService {
this.accessControlList.setHostname(hostname);
this.countryPermissions.setHostname(hostname);
this.phoneNumbers.setHostname(hostname);
this.calls.setHostname(hostname);
}
}
1 change: 1 addition & 0 deletions packages/elastic-sip-trunking/src/rest/v1/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './access-control-list';
export * from './calls-history';
export * from './country-permissions';
export * from './phone-numbers';
export * from './sip-endpoints';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { SinchClientParameters } from '@sinch/sdk-client';
import { CallsHistoryApi, CallsHistoryApiFixture, ElasticSipTrunking } from '../../../../src';

describe('CallsApi', () => {
let callsApi: CallsHistoryApi;
let fixture: CallsHistoryApiFixture;
let credentials: SinchClientParameters;

beforeEach(() => {
fixture = new CallsHistoryApiFixture();
credentials = {
projectId: 'PROJECT_ID',
keyId: 'KEY_ID',
keySecret: 'KEY_SECRET',
};
callsApi = new CallsHistoryApi(credentials);
});


describe ('findCalls', () => {
it('should make a GET request to find calls by query parameters', async () => {
// Given
const requestData: ElasticSipTrunking.FindCallsRequestData = {
trunkId: 'dFeDe67-09d5-49d5-b469-e1fc2cb163c7',
};
const mockData: ElasticSipTrunking.Call[] = [
{
callId: '01AQ3D80ZKSSK35TZFKM3JG9CT',
to: '+15551239898',
from: '+14155553434',
direction: 'INBOUND',
answerTime: new Date('2021-11-01T23:26:50Z'),
endTime: new Date('2021-11-01T23:27:35Z'),
durationSeconds: 45,
callResult: 'COMPLETED',
pricePerMinute: {
currencyCode: 'USD',
amount: '0.0040',
},
billingDurationSeconds: 60,
price: {
currencyCode: 'USD',
amount: '0.0040',
},
createTime: new Date('2021-11-01T23:20:50Z'),
projectId: '1bf62742-7b84-4666-9cbe-8e5734fd57d0',
trunkId: 'dFeDe67-09d5-49d5-b469-e1fc2cb163c7',
},
];
const expectedResponse = {
data: mockData,
hasNextPage: false,
nextPageValue: '',
nextPage: jest.fn(),
};

// When
fixture.find.mockResolvedValue(expectedResponse);
callsApi.find = fixture.find;
const response = await callsApi.find(requestData);

// Then
expect(response).toEqual(expectedResponse);
expect(response.data).toBeDefined();
expect(fixture.find).toHaveBeenCalledWith(requestData);
});
});
});

0 comments on commit e800ea5

Please sign in to comment.