diff --git a/src/models/contacts.ts b/src/models/contacts.ts new file mode 100644 index 00000000..38a20a4b --- /dev/null +++ b/src/models/contacts.ts @@ -0,0 +1,160 @@ +import { ListQueryParams } from './listQueryParams.js'; + +/** + * Interface representing a Nylas Contact object. + */ +export interface Contact { + id: string; + grantId: string; + object: 'contact'; + birthday?: string; + companyName?: string; + displayName: string; + emails: Email[]; + imAddresses: InstantMessagingAddress[]; + givenName?: string; + jobTitle?: string; + managerName?: string; + middleName?: string; + nickname?: string; + notes?: string; + officeLocation?: string; + pictureUrl?: string; + picture?: string; + suffix?: string; + surname?: string; + source?: SourceType; + phoneNumbers: PhoneNumber[]; + physicalAddresses: PhysicalAddress[]; + webPages: WebPage[]; + groups: ContactGroup[]; +} + +/** + * Custom Types. + */ +export type ContactType = 'work' | 'home' | 'other'; +export type SourceType = 'address_book' | 'inbox' | 'domain'; +export type GroupType = 'user' | 'system' | 'other'; + +/** + * Interface for email addresses in a contact. + */ +export interface Email { + email?: string; + type?: ContactType; +} + +/** + * Interface for IM addresses in a contact. + */ +export interface InstantMessagingAddress { + type?: string; + imAddress?: string; +} + +/** + * Interface for phone numbers in a contact. + */ +export interface PhoneNumber { + number?: string; + type?: ContactType; +} + +/** + * Interface for physical addresses in a contact. + */ +export interface PhysicalAddress { + format?: string; + streetAddress?: string; + city?: string; + postalCode?: string; + state?: string; + country?: string; + type?: ContactType; +} + +/** + * Interface for web pages in a contact. + */ +export interface WebPage { + url?: string; + type?: ContactType; +} + +/** + * Interface representing a contact group. + */ +export interface ContactGroup { + id: string; + object: 'contact_group'; + grantId?: string; + groupType?: GroupType; + name?: string; + path?: string; +} + +/** + * Interface representing the query parameters for listing contacts. + */ +export interface ListContactQueryParams extends ListQueryParams { + /** + * Returns the contacts matching the exact contact's email. + */ + email?: string; + /** + * Returns the contacts matching the contact's exact phone number + */ + phoneNumber?: string; + /** + * Returns the contacts matching from the address book or auto-generated contacts from emails. + * For example of contacts only from the address book: /contacts?source=address_bookor for only autogenerated contacts:/contacts?source=inbox` + */ + source?: string; + /** + * Returns the contacts belonging to the Contact Group matching this ID + */ + group?: string; + /** + * When set to true, returns the contacts also within the specified Contact Group subgroups, if the group parameter is set. + */ + recurse?: boolean; +} + +/** + * Interface representing the query parameters for retrieving a single contact. + */ +export interface FindContactQueryParams { + profilePicture?: boolean; +} + +/** + * Interface for creating a contact. + */ +export type CreateContactRequest = { + displayName?: string; + birthday?: string; + companyName?: string; + emails?: Email[]; + givenName?: string; + imAddresses?: InstantMessagingAddress[]; + jobTitle?: string; + managerName?: string; + middleName?: string; + nickname?: string; + notes?: string; + officeLocation?: string; + phoneNumbers?: PhoneNumber[]; + physicalAddresses?: PhysicalAddress[]; + suffix?: string; + surname?: string; + webPages?: WebPage[]; + picture?: string; + source?: SourceType; + groups?: ContactGroup[]; +}; + +/** + * Interface for updating a contact. + */ +export type UpdateContactRequest = CreateContactRequest; diff --git a/src/nylas.ts b/src/nylas.ts index 731206c9..e433a4d3 100644 --- a/src/nylas.ts +++ b/src/nylas.ts @@ -10,6 +10,7 @@ import { Drafts } from './resources/drafts.js'; import { Threads } from './resources/threads.js'; import { Connectors } from './resources/connectors.js'; import { Folders } from './resources/folders.js'; +import { Contacts } from './resources/contacts.js'; /** * The entry point to the Node SDK @@ -58,6 +59,10 @@ export default class Nylas { * Access the Folders API */ public folders: Folders; + /** + * Access the Contacts API + */ + public contacts: Contacts; /** * The configured API client @@ -85,6 +90,7 @@ export default class Nylas { this.threads = new Threads(this.apiClient); this.webhooks = new Webhooks(this.apiClient); this.folders = new Folders(this.apiClient); + this.contacts = new Contacts(this.apiClient); return this; } diff --git a/src/resources/contacts.ts b/src/resources/contacts.ts new file mode 100644 index 00000000..67a52ecc --- /dev/null +++ b/src/resources/contacts.ts @@ -0,0 +1,177 @@ +import { Overrides } from '../config.js'; +import { + CreateContactRequest, + Contact, + ListContactQueryParams, + FindContactQueryParams, + UpdateContactRequest, + ContactGroup, +} from '../models/contacts.js'; +import { + NylasResponse, + NylasListResponse, + NylasDeleteResponse, +} from '../models/response.js'; +import { AsyncListResponse, Resource } from './resource.js'; + +/** + * @property contactId The id of the Contact to retrieve. + * @property identifier The identifier of the grant to act upon + * @property queryParams The query parameters to include in the request + */ +interface FindContactParams { + identifier: string; + contactId: string; + queryParams: FindContactQueryParams; +} + +/** + * @property identifier The identifier of the grant to act upon + * @property queryParams The query parameters to include in the request + */ +interface ListContactParams { + identifier: string; + queryParams: ListContactQueryParams; +} + +/** + * @property identifier The identifier of the grant to act upon + * @property requestBody The values to create the Contact with + */ +interface CreateContactParams { + identifier: string; + requestBody: CreateContactRequest; +} + +/** + * @property identifier The identifier of the grant to act upon + * @property contactId The id of the Contact to retrieve. + * @property requestBody The values to update the Contact with + */ +interface UpdateContactParams { + identifier: string; + contactId: string; + requestBody: UpdateContactRequest; +} + +/** + * @property identifier The identifier of the grant to act upon + * @property contactId The id of the Contact to retrieve. + */ +interface DestroyContactParams { + identifier: string; + contactId: string; +} + +/** + * @property identifier The identifier of the grant to act upon + */ +interface ListContactGroupParams { + identifier: string; +} + +/** + * Nylas Contacts API + * + * The Nylas Contacts API allows you to create, update, and delete contacts. + */ +export class Contacts extends Resource { + /** + * Return all Contacts + * @return The list of Contacts + */ + public list({ + identifier, + queryParams, + overrides, + }: ListContactParams & Overrides): AsyncListResponse< + NylasListResponse + > { + return super._list({ + queryParams, + path: `/v3/grants/${identifier}/contacts`, + overrides, + }); + } + + /** + * Return a Contact + * @return The Contact + */ + public find({ + identifier, + contactId, + queryParams, + overrides, + }: FindContactParams & Overrides): Promise> { + return super._find({ + path: `/v3/grants/${identifier}/contacts/${contactId}`, + queryParams, + overrides, + }); + } + + /** + * Create a Contact + * @return The created Contact + */ + public create({ + identifier, + requestBody, + overrides, + }: CreateContactParams & Overrides): Promise> { + return super._create({ + path: `/v3/grants/${identifier}/contacts`, + requestBody, + overrides, + }); + } + + /** + * Update a Contact + * @return The updated Contact + */ + public update({ + identifier, + contactId, + requestBody, + overrides, + }: UpdateContactParams & Overrides): Promise> { + return super._update({ + path: `/v3/grants/${identifier}/contacts/${contactId}`, + requestBody, + overrides, + }); + } + + /** + * Delete a Contact + * @return The deletion response + */ + public destroy({ + identifier, + contactId, + overrides, + }: DestroyContactParams & Overrides): Promise { + return super._destroy({ + path: `/v3/grants/${identifier}/contacts/${contactId}`, + overrides, + }); + } + + /** + * Return a Contact Group + * @return The list of Contact Groups + */ + public groups({ + identifier, + overrides, + }: ListContactGroupParams & Overrides): Promise< + NylasListResponse + > { + return super._list({ + path: `/v3/grants/${identifier}/contacts/groups`, + overrides, + }); + } +} diff --git a/tests/resources/contacts.spec.ts b/tests/resources/contacts.spec.ts new file mode 100644 index 00000000..b634fee0 --- /dev/null +++ b/tests/resources/contacts.spec.ts @@ -0,0 +1,258 @@ +import APIClient from '../../src/apiClient'; +import { Contacts } from '../../src/resources/contacts'; +jest.mock('../src/apiClient'); + +describe('Contacts', () => { + let apiClient: jest.Mocked; + let contacts: Contacts; + + beforeAll(() => { + apiClient = new APIClient({ + apiKey: 'apiKey', + apiUri: 'https://test.api.nylas.com', + timeout: 30, + }) as jest.Mocked; + + contacts = new Contacts(apiClient); + apiClient.request.mockResolvedValue({}); + }); + + describe('list', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.list({ + identifier: 'id123', + queryParams: { + email: 'test@email.com', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/contacts', + queryParams: { + email: 'test@email.com', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + + it('should paginate correctly if a nextCursor is present', async () => { + apiClient.request.mockResolvedValueOnce({ + requestId: 'request123', + data: [ + { + id: 'id', + name: 'name', + }, + ], + nextCursor: 'cursor123', + }); + const contactList = await contacts.list({ + identifier: 'id123', + queryParams: { + email: 'test@email.com', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + apiClient.request.mockResolvedValueOnce({ + requestId: 'request123', + data: [ + { + id: 'id', + name: 'name', + }, + ], + }); + await contactList.next(); + + expect(apiClient.request).toBeCalledTimes(2); + expect(apiClient.request).toHaveBeenLastCalledWith({ + method: 'GET', + path: '/v3/grants/id123/contacts', + queryParams: { + email: 'test@email.com', + pageToken: 'cursor123', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + + it('should not paginate if nextCursor is not present', async () => { + apiClient.request.mockResolvedValueOnce({ + requestId: 'request123', + data: [ + { + id: 'id', + name: 'name', + }, + ], + }); + const contactList = await contacts.list({ + identifier: 'id123', + queryParams: { + email: 'test@email.com', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + apiClient.request.mockResolvedValueOnce({ + requestId: 'request123', + data: [ + { + id: 'id', + name: 'name', + }, + ], + }); + await contactList.next(); + + expect(apiClient.request).toBeCalledTimes(1); + }); + + //TODO::More iterator tests + }); + + describe('find', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.find({ + identifier: 'id123', + contactId: 'contact123', + queryParams: { + profilePicture: true, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/contacts/contact123', + queryParams: { + profilePicture: true, + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + }); + + describe('create', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.create({ + identifier: 'id123', + requestBody: { + displayName: 'Test', + birthday: '1960-12-31', + companyName: 'Nylas', + emails: [ + { + email: 'test@gmail.com', + type: 'home', + }, + ], + givenName: 'Test', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'POST', + path: '/v3/grants/id123/contacts', + body: { + displayName: 'Test', + birthday: '1960-12-31', + companyName: 'Nylas', + emails: [ + { + email: 'test@gmail.com', + type: 'home', + }, + ], + givenName: 'Test', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + }); + + describe('update', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.update({ + identifier: 'id123', + contactId: 'contact123', + requestBody: { + birthday: '1960-12-31', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'PUT', + path: '/v3/grants/id123/contacts/contact123', + body: { + birthday: '1960-12-31', + }, + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + }); + + describe('destroy', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.destroy({ + identifier: 'id123', + contactId: 'contact123', + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'DELETE', + path: '/v3/grants/id123/contacts/contact123', + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + }); + + describe('contact group', () => { + it('should call apiClient.request with the correct params', async () => { + await contacts.groups({ + identifier: 'id123', + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + + expect(apiClient.request).toHaveBeenCalledWith({ + method: 'GET', + path: '/v3/grants/id123/contacts/groups', + overrides: { + apiUri: 'https://test.api.nylas.com', + }, + }); + }); + }); +});