From ce34b20b75076dd38084c2a1cda4a15cf995a229 Mon Sep 17 00:00:00 2001 From: Joscha Feth Date: Fri, 26 Jul 2024 09:16:00 +0100 Subject: [PATCH] feat: persons endpoint; delete, create, update, fields --- src/v1/organizations.ts | 6 +- src/v1/persons.ts | 203 ++++++++++++++++-- .../tests/__snapshots__/persons_test.ts.snap | 62 ++++++ .../fixtures/persons/create.raw.response.json | 9 + .../persons/get_fields.raw.response.json | 23 ++ .../fixtures/persons/update.raw.response.json | 9 + src/v1/tests/persons_test.ts | 89 ++++---- 7 files changed, 335 insertions(+), 66 deletions(-) create mode 100644 src/v1/tests/fixtures/persons/create.raw.response.json create mode 100644 src/v1/tests/fixtures/persons/get_fields.raw.response.json create mode 100644 src/v1/tests/fixtures/persons/update.raw.response.json diff --git a/src/v1/organizations.ts b/src/v1/organizations.ts index c63942e..1c2abd4 100644 --- a/src/v1/organizations.ts +++ b/src/v1/organizations.ts @@ -283,7 +283,7 @@ export type OrganizationReference = { organization_id: number } -export type OrganizationField = Pick< +export type EntityField = Pick< Field, 'id' | 'name' | 'value_type' | 'allows_multiple' | 'dropdown_options' > @@ -505,9 +505,9 @@ export class Organizations { * console.log(organizationFields) * ``` */ - async getFields(): Promise { + async getFields(): Promise { const response = await this.axios.get< - OrganizationField[] + EntityField[] >( organizationFieldsUrl(), ) diff --git a/src/v1/persons.ts b/src/v1/persons.ts index c615b0d..f9bbf88 100644 --- a/src/v1/persons.ts +++ b/src/v1/persons.ts @@ -3,6 +3,7 @@ import { defaultTransformers } from './axios_default_transformers.ts' import { createSearchIteratorFn } from './create_search_iterator_fn.ts' import type { ListEntryReferenceRaw } from './list_entries.ts' import { + EntityField, InteractionDateResponse, type InteractionDateResponseRaw, InteractionDatesQueryParams, @@ -17,7 +18,7 @@ import type { PagedResponse } from './paged_response.ts' import { transformInteractionDateResponseRaw } from './transform_interaction_date_response_raw.ts' import { transformListEntryReference } from './transform_list_entry_reference.ts' import type { Replace } from './types.ts' -import { personsUrl } from './urls.ts' +import { personFieldsUrl, personsUrl } from './urls.ts' /** * The type of person. @@ -33,6 +34,23 @@ export enum PersonType { INTERNAL = 1, } +export type Person = { + /** The unique identifier of the person object. */ + id: number + /** The type of person. */ + type: PersonType + /** The first name of the person. */ + first_name: string + /** The last name of the person. */ + last_name: string + /** The email addresses of the person. */ + emails: string[] + /** The email (automatically computed) that is most likely to the current active email address of the person. */ + primary_email: string + /** An array of unique identifiers of organizations that the person is associated with. */ + organization_ids: number[] +} + /** * Each person resource is assigned a unique `id` and stores the name, type, and email addresses of the person. A person resource also has access to a smart attribute called `primary_email`. The value of `primary_email` is automatically computed by Affinity's proprietary algorithms and refers to the email that is most likely to be the current active email address of a person. * The person resource `organization_ids` is a collection of unique identifiers to the person's associated organizations. Note that a person can be associated with multiple organizations. For example, say your team has talked with organizations A and B. Person X used to work at A and was your point of contact, but then changed jobs and started emailing you from a new email address (corresponding to organization B). In this case, Affinity will automatically associate person X with both organization A and organization B. @@ -41,21 +59,6 @@ export enum PersonType { */ export type PersonResponseRaw = & { - /** The unique identifier of the person object. */ - id: number - /** The type of person. */ - type: PersonType - /** The first name of the person. */ - first_name: string - /** The last name of the person. */ - last_name: string - /** The email addresses of the person. */ - emails: string[] - /** The email (automatically computed) that is most likely to the current active email address of the person. */ - primary_email: string - /** An array of unique identifiers of organizations that the person is associated with. */ - organization_ids: number[] - /** An array of unique identifiers of organizations that the person is currently associated with according to the Affinity Data: Current Organization in-app column. * Only returned when `{@link WithCurrentOrganizatonParams.with_current_organizations}=true`. * @@ -63,6 +66,7 @@ export type PersonResponseRaw = */ current_organization_ids?: number[] } + & Person & InteractionDateResponseRaw & OpportunityIdResponseRaw @@ -126,6 +130,66 @@ export type PersonReference = { person_id: number } +/** + * The request object for creating an organization. + */ +export type CreatePersonRequest = { + /** + * The first name of the person. + */ + first_name: string + /** + * The last name of the person. + */ + last_name: string + /** + * The email addresses of the person. If there are no email addresses, please specify an empty array. + */ + emails: string[] + /** + * An array of unique identifiers of organizations that the person is associated with. + */ + organization_ids?: number[] +} + +/** + * The request object for updating an organization. + */ +export type UpdatePersonRequest = + & { + /** + * The first name of the person. + */ + first_name?: string + + /** + * The last name of the person. + */ + last_name?: string + + /** + * The email addresses of the person. If there are no email addresses, please specify an empty array. + * + * *Hint*: If you are trying to add a new email to a person, the existing values for `emails` must also be supplied as parameters. + */ + emails?: string[] + + /** + * An array of unique identifiers of organizations that the person is associated with. + * + * *Hint*: If you are trying to add a new organization to a person, the existing values for `organization_ids` must also be supplied as parameters. + */ + organization_ids?: number[] + } + & PersonReference + +export type SimplePersonResponse = + & Person + & Pick< + PersonResponse, + 'organization_ids' + > + /** * @module * The persons API allows you to manage all the contacts of your organization. @@ -207,10 +271,10 @@ export class Persons { * * @example * ```typescript - * const result = await affinity.persons.search({ - * term: 'ben' + * const { persons: allAlices } = await affinity.persons.search({ + * term: 'Alice' * }) - * console.log(result.primary_email) + * console.log(allAlices) * ``` */ async search( @@ -251,12 +315,109 @@ export class Persons { * ```typescript * let page = 0 * for await (const entries of affinity.persons.searchIterator({ - * term: 'ben', + * term: 'Alice', * page_size: 10 * })) { * console.log(`Page ${++page} of entries:`, entries) * } * ``` */ - searchIterator = createSearchIteratorFn(this.search.bind(this), 'persons') + searchIterator = createSearchIteratorFn( + this.search.bind(this), + 'persons', + ) + + /** + * Creates a new person with the supplied parameters. + * + * @param data - Object containing the data for creating a new person + * @returns The person resource that was just created. + * + * @example + * ```typescript + * const newPerson = await affinity.persons.create({ + * first_name: 'Alice', + * last_name: 'Doe', + * emails: ['alice@doe.com'], + * organization_ids: [123456] + * }) + * console.log(newPerson) + * ``` + */ + async create( + data: CreatePersonRequest, + ): Promise { + const response = await this.axios.post( + personsUrl(), + data, + ) + return response.data + } + + /** + * Updates an existing person with `person_id` with the supplied parameters. + * + * @param data - Object containing the data for updating an person + * @returns The person resource that was just updated. + * + * @example + * ```typescript + * const updatedPerson = await affinity.persons.update({ + * person_id: 12345, + * name: 'Acme Corp.', + * person_ids: [38706, 89734] + * }) + * console.log(updatedPerson) + * ``` + */ + async update( + data: UpdatePersonRequest, + ): Promise { + const { person_id, ...rest } = data + const response = await this.axios.put( + personsUrl(person_id), + rest, + ) + return response.data + } + + /** + * Deletes an person with a specified `person_id`. + * @returns true if the deletion was successful + * + * @example + * ```typescript + * const success = await affinity.persons.delete({ + * person_id: 12345 + * }) + * console.log(success ? 'Person deleted': 'Person not deleted') + * ``` + */ + async delete(request: PersonReference): Promise { + const { person_id } = request + const response = await this.axios.delete<{ success: boolean }>( + personsUrl(person_id), + ) + return response.data.success === true + } + + /** + * Fetches an array of all the global fields that exist on persons. + * + * @returns An array of the fields that exist on all persons for your team. + * + * @example + * ```typescript + * const personFields = await affinity.persons.getFields() + * console.log(personFields) + * ``` + */ + async getFields(): Promise { + const response = await this.axios.get< + EntityField[] + >( + personFieldsUrl(), + ) + return response.data + } } diff --git a/src/v1/tests/__snapshots__/persons_test.ts.snap b/src/v1/tests/__snapshots__/persons_test.ts.snap index f5c2ce2..5bfe43c 100644 --- a/src/v1/tests/__snapshots__/persons_test.ts.snap +++ b/src/v1/tests/__snapshots__/persons_test.ts.snap @@ -85,6 +85,68 @@ snapshot[`persons > can search for persons 1`] = ` } `; +snapshot[`persons > can create a new person 1`] = ` +{ + emails: [ + "alice@affinity.co", + ], + first_name: "Alice", + id: 860197, + last_name: "Doe", + organization_ids: [ + 1687449, + ], + primary_email: "alice@affinity.co", + type: 0, +} +`; + +snapshot[`persons > can update a person 1`] = ` +{ + emails: [ + "alice@affinity.co", + "allison@example.com", + "allison@gmail.com", + ], + first_name: "Allison", + id: 860197, + last_name: "Doe", + organization_ids: [ + 1687449, + ], + primary_email: "alice@affinity.co", + type: 0, +} +`; + +snapshot[`persons > can delete a person 1`] = `true`; + +snapshot[`persons > can get global person fields 1`] = ` +[ + { + allows_multiple: true, + dropdown_options: [], + id: 125, + name: "Use Case", + value_type: 2, + }, + { + allows_multiple: true, + dropdown_options: [], + id: 198, + name: "Referrers", + value_type: 0, + }, + { + allows_multiple: false, + dropdown_options: [], + id: 1615, + name: "Address", + value_type: 5, + }, +] +`; + snapshot[`page 1 of persons 1`] = ` [ { diff --git a/src/v1/tests/fixtures/persons/create.raw.response.json b/src/v1/tests/fixtures/persons/create.raw.response.json new file mode 100644 index 0000000..b99b4e1 --- /dev/null +++ b/src/v1/tests/fixtures/persons/create.raw.response.json @@ -0,0 +1,9 @@ +{ + "id": 860197, + "type": 0, + "first_name": "Alice", + "last_name": "Doe", + "primary_email": "alice@affinity.co", + "emails": ["alice@affinity.co"], + "organization_ids": [1687449] +} diff --git a/src/v1/tests/fixtures/persons/get_fields.raw.response.json b/src/v1/tests/fixtures/persons/get_fields.raw.response.json new file mode 100644 index 0000000..ffcf806 --- /dev/null +++ b/src/v1/tests/fixtures/persons/get_fields.raw.response.json @@ -0,0 +1,23 @@ +[ + { + "id": 125, + "name": "Use Case", + "value_type": 2, + "allows_multiple": true, + "dropdown_options": [] + }, + { + "id": 198, + "name": "Referrers", + "value_type": 0, + "allows_multiple": true, + "dropdown_options": [] + }, + { + "id": 1615, + "name": "Address", + "value_type": 5, + "allows_multiple": false, + "dropdown_options": [] + } +] diff --git a/src/v1/tests/fixtures/persons/update.raw.response.json b/src/v1/tests/fixtures/persons/update.raw.response.json new file mode 100644 index 0000000..5e761a3 --- /dev/null +++ b/src/v1/tests/fixtures/persons/update.raw.response.json @@ -0,0 +1,9 @@ +{ + "id": 860197, + "type": 0, + "first_name": "Allison", + "last_name": "Doe", + "primary_email": "alice@affinity.co", + "emails": ["alice@affinity.co", "allison@example.com", "allison@gmail.com"], + "organization_ids": [1687449] +} diff --git a/src/v1/tests/persons_test.ts b/src/v1/tests/persons_test.ts index 66ab276..aa911e5 100644 --- a/src/v1/tests/persons_test.ts +++ b/src/v1/tests/persons_test.ts @@ -62,51 +62,56 @@ describe('persons', () => { // await assertSnapshot(t, res) // }) - // it('can create a new person', async (t) => { - // const data = { - // name: 'Acme Corporation', - // domain: 'acme.co', - // person_ids: [38706], - // } - // mock?.onPost(personsUrl()).reply( - // 201, - // await getRawFixture('persons/create.raw.response.json'), - // ) - // const res = await affinity.persons.create(data) - // await assertSnapshot(t, res) - // }) + it('can create a new person', async (t) => { + const data = { + first_name: 'Alice', + last_name: 'Doe', + emails: ['alice@affinity.co'], + organization_ids: [1687449], + } + mock?.onPost(personsUrl()).reply( + 201, + await getRawFixture('persons/create.raw.response.json'), + ) + const res = await affinity.persons.create(data) + await assertSnapshot(t, res) + }) - // it('can update a person', async (t) => { - // const data = { - // person_id: 120611418, - // name: 'Acme Corp.', - // person_ids: [38706, 89734], - // } - // mock?.onPut(personsUrl(data.person_id)).reply( - // 200, - // await getRawFixture('persons/update.raw.response.json'), - // ) - // const res = await affinity.persons.update(data) - // await assertSnapshot(t, res) - // }) + it('can update a person', async (t) => { + const data = { + person_id: 860197, + first_name: 'Allison', + emails: [ + 'alice@affinity.co', + 'allison@example.com', + 'allison@gmail.com', + ], + } + mock?.onPut(personsUrl(data.person_id)).reply( + 200, + await getRawFixture('persons/update.raw.response.json'), + ) + const res = await affinity.persons.update(data) + await assertSnapshot(t, res) + }) - // it('can delete a person', async (t) => { - // const person_id = 120611418 - // mock?.onDelete(personsUrl(person_id)).reply(200, { - // success: true, - // }) - // const res = await affinity.persons.delete({ person_id }) - // await assertSnapshot(t, res) - // }) + it('can delete a person', async (t) => { + const person_id = 860197 + mock?.onDelete(personsUrl(person_id)).reply(200, { + success: true, + }) + const res = await affinity.persons.delete({ person_id }) + await assertSnapshot(t, res) + }) - // it('can get global person fields', async (t) => { - // mock?.onGet(personFieldsUrl()).reply( - // 200, - // await getRawFixture('persons/get_fields.raw.response.json'), - // ) - // const res = await affinity.persons.getFields() - // await assertSnapshot(t, res) - // }) + it('can get global person fields', async (t) => { + mock?.onGet(personFieldsUrl()).reply( + 200, + await getRawFixture('persons/get_fields.raw.response.json'), + ) + const res = await affinity.persons.getFields() + await assertSnapshot(t, res) + }) it('iterates over all persons', async (t) => { const params: SearchPersonsRequest = {