Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added search customer by email api #124

Merged
merged 4 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ cio.destroy(1);

- **id**: String or number (required)

#### Attention!
#### Attention!

This method will only delete a person and not suppress them. This means they can be readded.
This method will only delete a person and not suppress them. This means they can be readded.
If you need to suppress a person, please use [`cio.suppress`](https://github.com/customerio/customerio-node#ciosuppressid).

---
Expand Down Expand Up @@ -173,7 +173,7 @@ cio.trackAnonymous(anonymous_id, {

#### Anonymous invite events

If you previously sent [invite events](https://customer.io/docs/anonymous-invite-emails/), you can achieve the same functionality by sending an anonymous event with an empty string for the anonymous identifier. To send anonymous invites, your event *must* include a `recipient` attribute.
If you previously sent [invite events](https://customer.io/docs/anonymous-invite-emails/), you can achieve the same functionality by sending an anonymous event with an empty string for the anonymous identifier. To send anonymous invites, your event *must* include a `recipient` attribute.

```javascript
cio.trackAnonymous("", {
Expand Down Expand Up @@ -347,6 +347,20 @@ api.triggerBroadcast(1, { name: "foo" }, { emails: ["[email protected]"], email
- **data**: Object (optional)
- **recipients**: Object (optional)

### api.getCustomersByEmail(email)

Returns customer object with given email.

```javascript
api.getCustomersByEmail("[email protected]")
```

[You can learn more about the available recipient fields here](https://customer.io/docs/api/#operation/getPeopleEmail).

#### Options

- **email**: String (required)

## Further examples

We've included functional examples in the [examples/ directory](https://github.com/customerio/customerio-node/tree/main/examples) of the repo to further assist in demonstrating how to use this library to integrate with Customer.io
Expand Down
11 changes: 11 additions & 0 deletions examples/getCustomerByEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { APIClient, RegionUS, RegionEU } = require('../dist/index');
// In actual use require the node module:
// const { TrackClient, RegionUS, RegionEU } = require('customerio-node');
const appKey = require('./config').appKey;
const campaignId = require('./config').campaignId;
const segmentId = require('./config').segmentId;
const cio = new APIClient(appKey, { region: RegionUS });

const email = "[email protected]"

cio.getCustomersByEmail(email);
9 changes: 9 additions & 0 deletions lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { RequestOptions } from 'https';
import Request, { BearerAuth, RequestData } from './request';
import { Region, RegionUS } from './regions';
import { SendEmailRequest } from './api/requests';
import { cleanEmail, isEmpty } from './utils';

type APIDefaults = RequestOptions & { region: Region; url?: string };

Expand Down Expand Up @@ -51,6 +52,14 @@ export class APIClient {
return this.request.post(`${this.apiRoot}/send/email`, req.message);
}

getCustomersByEmail(email: string) {
if (typeof email !== 'string' || isEmpty(email)) {
throw new Error('"email" must be a string');
}

return this.request.get(`${this.apiRoot}/customers?email=${cleanEmail(email)}`);
}

triggerBroadcast(id: string | number, data: RequestData, recipients: Recipients) {
let payload = {};
let customRecipientField = (
Expand Down
4 changes: 4 additions & 0 deletions lib/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export default class CIORequest {
return this.handler(this.options(uri, 'PUT', data));
}

get(uri: string) {
return this.handler(this.options(uri, 'GET'));
}

destroy(uri: string) {
return this.handler(this.options(uri, 'DELETE'));
}
Expand Down
4 changes: 4 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export const isEmpty = (value: unknown) => {
return value === null || value === undefined || (typeof value === 'string' && value.trim() === '');
};

export const cleanEmail = (email: string) => {
return email.split('@').map(encodeURIComponent).join('@');
};

export class CustomerIORequestError extends Error {
statusCode: number;
response: IncomingMessage;
Expand Down
29 changes: 29 additions & 0 deletions test/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,35 @@ test('#sendEmail: override body: success', (t) => {
t.is(req.message.transactional_message_id, 1);
});

test('#getCustomersByEmail: searching for a customer email (default)', (t) => {
sinon.stub(t.context.client.request, 'get');

const email = '[email protected]';
t.context.client.getCustomersByEmail(email);
t.truthy((t.context.client.request.get as SinonStub).calledWith(`${RegionUS.apiUrl}/customers?email=${email}`));
})

test('#getCustomersByEmail: should throw error when email is empty', (t) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another test should be added for cases where null/undefined or a non-string object are used for non-typescript clients

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

const email = '';
t.throws(() => t.context.client.getCustomersByEmail(email));
})


test('#getCustomersByEmail: should throw error when email is null', (t) => {
const email: unknown = null;
t.throws(() => t.context.client.getCustomersByEmail(email as string));
})

test('#getCustomersByEmail: should throw error when email is undefined', (t) => {
const email: unknown = undefined;
t.throws(() => t.context.client.getCustomersByEmail(email as string));
})

test('#getCustomersByEmail: should throw error when email is not a string object', (t) => {
const email: unknown = { "object": "test" };
t.throws(() => t.context.client.getCustomersByEmail(email as string));
})

test('#sendEmail: adding attachments with encoding (default)', (t) => {
sinon.stub(t.context.client.request, 'post');
let req = new SendEmailRequest({ to: '[email protected]', identifiers: { id: '2' }, transactional_message_id: 1 });
Expand Down
6 changes: 6 additions & 0 deletions test/request-track-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,12 @@ test('#put returns the promise generated by the handler', (t) => {
t.is(promise.constructor.name, 'Promise');
});

test('#get returns the promise generated by the handler', (t) => {
createMockRequest(t.context.httpsReq, 200);
const promise = t.context.req.get(uri);
t.is(promise.constructor.name, 'Promise');
});

const deleteOptions = Object.assign({}, baseOptions, { method: 'DELETE', body: null });

test('#destroy calls the handler, makes a DELETE request with the correct args', (t) => {
Expand Down
21 changes: 21 additions & 0 deletions test/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import avaTest, { TestInterface } from 'ava';
import { cleanEmail } from '../lib/utils';

type TestContext = {};

const test = avaTest as TestInterface<TestContext>;


test('#cleanEmail correctly formats email (default)', (t) => {
let email = "[email protected]";
let result = cleanEmail(email);
t.is(result, 'hello%[email protected]');

email = "[email protected]";
result = cleanEmail(email);
t.is(result, '[email protected]');

email = "";
result = cleanEmail(email);
t.is(result, '');
});