diff --git a/examples/simple-examples/README.md b/examples/simple-examples/README.md index 6fed6474..a3945688 100644 --- a/examples/simple-examples/README.md +++ b/examples/simple-examples/README.md @@ -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 | diff --git a/examples/simple-examples/package.json b/examples/simple-examples/package.json index 9580665e..4b442faa 100644 --- a/examples/simple-examples/package.json +++ b/examples/simple-examples/package.json @@ -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", diff --git a/examples/simple-examples/src/elastic-sip-trunking/calls-history/find.ts b/examples/simple-examples/src/elastic-sip-trunking/calls-history/find.ts new file mode 100644 index 00000000..10dcddfe --- /dev/null +++ b/examples/simple-examples/src/elastic-sip-trunking/calls-history/find.ts @@ -0,0 +1,75 @@ +import { ElasticSipTrunking, PageResult } from '@sinch/sdk-core'; +import { + getPrintFormat, + getSipTrunkIdFromConfig, + initElasticSipTrunkingService, + printFullResponse, +} from '../../config'; + +const populateCallsList = ( + callsPage: PageResult, + 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); + } + } + +})(); diff --git a/packages/elastic-sip-trunking/src/models/v1/call/call.ts b/packages/elastic-sip-trunking/src/models/v1/call/call.ts new file mode 100644 index 00000000..7fd6425a --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/call/call.ts @@ -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; +} diff --git a/packages/elastic-sip-trunking/src/models/v1/call/index.ts b/packages/elastic-sip-trunking/src/models/v1/call/index.ts new file mode 100644 index 00000000..48ba5cae --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/call/index.ts @@ -0,0 +1 @@ +export type { Call } from './call'; diff --git a/packages/elastic-sip-trunking/src/models/v1/enum.ts b/packages/elastic-sip-trunking/src/models/v1/enum.ts new file mode 100644 index 00000000..312f600f --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/enum.ts @@ -0,0 +1,3 @@ +export type DirectionEnum = 'INBOUND' | 'OUTBOUND'; + +export type CallResult = 'COMPLETED' | 'NO_ANSWER' | 'CANCEL' | 'BUSY' | 'FAILED'; diff --git a/packages/elastic-sip-trunking/src/models/v1/index.ts b/packages/elastic-sip-trunking/src/models/v1/index.ts index fa0a29a4..4f57738f 100644 --- a/packages/elastic-sip-trunking/src/models/v1/index.ts +++ b/packages/elastic-sip-trunking/src/models/v1/index.ts @@ -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'; diff --git a/packages/elastic-sip-trunking/src/models/v1/money/index.ts b/packages/elastic-sip-trunking/src/models/v1/money/index.ts new file mode 100644 index 00000000..6b156e03 --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/money/index.ts @@ -0,0 +1 @@ +export type { Money } from './money'; diff --git a/packages/elastic-sip-trunking/src/models/v1/money/money.ts b/packages/elastic-sip-trunking/src/models/v1/money/money.ts new file mode 100644 index 00000000..bc45fbfb --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/money/money.ts @@ -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; +} diff --git a/packages/elastic-sip-trunking/src/models/v1/requests/calls-history/calls-history-request-data.ts b/packages/elastic-sip-trunking/src/models/v1/requests/calls-history/calls-history-request-data.ts new file mode 100644 index 00000000..f22dc84a --- /dev/null +++ b/packages/elastic-sip-trunking/src/models/v1/requests/calls-history/calls-history-request-data.ts @@ -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; +} diff --git a/packages/elastic-sip-trunking/src/models/v1/requests/index.ts b/packages/elastic-sip-trunking/src/models/v1/requests/index.ts index 5c12fb07..0dec0e40 100644 --- a/packages/elastic-sip-trunking/src/models/v1/requests/index.ts +++ b/packages/elastic-sip-trunking/src/models/v1/requests/index.ts @@ -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'; diff --git a/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.jest.fixture.ts b/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.jest.fixture.ts new file mode 100644 index 00000000..98322470 --- /dev/null +++ b/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.jest.fixture.ts @@ -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> { + + /** + * Fixture associated to function find + */ + public find: jest.Mock, [FindCallsRequestData]> = jest.fn(); +} + diff --git a/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.ts b/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.ts new file mode 100644 index 00000000..6c42951b --- /dev/null +++ b/packages/elastic-sip-trunking/src/rest/v1/calls-history/calls-history-api.ts @@ -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 } + */ + public find(data: FindCallsRequestData): ApiListPromise { + this.client = this.getSinchClient(); + data['createTime'] = data['createTime'] !== undefined ? data['createTime'] : 'now-24h'; + const getParams = this.client.extractQueryParams(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( + this.client, + requestOptionsPromise, + operationProperties, + ); + + // Add properties to the Promise to offer the possibility to use it as an iterator + Object.assign( + listPromise, + createIteratorMethodsForPagination( + this.client, requestOptionsPromise, listPromise, operationProperties), + ); + + return listPromise as ApiListPromise; + } + +} diff --git a/packages/elastic-sip-trunking/src/rest/v1/calls-history/index.ts b/packages/elastic-sip-trunking/src/rest/v1/calls-history/index.ts new file mode 100644 index 00000000..2e3d0744 --- /dev/null +++ b/packages/elastic-sip-trunking/src/rest/v1/calls-history/index.ts @@ -0,0 +1,2 @@ +export * from './calls-history-api'; +export * from './calls-history-api.jest.fixture'; diff --git a/packages/elastic-sip-trunking/src/rest/v1/elastic-sip-trunking-service.ts b/packages/elastic-sip-trunking/src/rest/v1/elastic-sip-trunking-service.ts index 9f149286..7e583e0f 100644 --- a/packages/elastic-sip-trunking/src/rest/v1/elastic-sip-trunking-service.ts +++ b/packages/elastic-sip-trunking/src/rest/v1/elastic-sip-trunking-service.ts @@ -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 { @@ -12,6 +13,7 @@ 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); @@ -19,6 +21,7 @@ export class ElasticSipTrunkingService { this.accessControlList = new AccessControlListApi(params); this.countryPermissions = new CountryPermissionsApi(params); this.phoneNumbers = new PhoneNumbersApi(params); + this.calls = new CallsHistoryApi(params); } /** @@ -32,5 +35,6 @@ export class ElasticSipTrunkingService { this.accessControlList.setHostname(hostname); this.countryPermissions.setHostname(hostname); this.phoneNumbers.setHostname(hostname); + this.calls.setHostname(hostname); } } diff --git a/packages/elastic-sip-trunking/src/rest/v1/index.ts b/packages/elastic-sip-trunking/src/rest/v1/index.ts index 48bd19cd..5bd03355 100644 --- a/packages/elastic-sip-trunking/src/rest/v1/index.ts +++ b/packages/elastic-sip-trunking/src/rest/v1/index.ts @@ -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'; diff --git a/packages/elastic-sip-trunking/tests/rest/v1/calls-history/calls-history-api.test.ts b/packages/elastic-sip-trunking/tests/rest/v1/calls-history/calls-history-api.test.ts new file mode 100644 index 00000000..5801b35f --- /dev/null +++ b/packages/elastic-sip-trunking/tests/rest/v1/calls-history/calls-history-api.test.ts @@ -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); + }); + }); +});