import { Api } from "../utils/api"; import { Paginator } from "../utils/paginator"; import { createParams } from "../utils/queryParams"; export class Contacts { private api: Api; /** * Creates a new Contact model. * @param api - The API client to use for making requests. */ constructor(api: Api) { this.api = api; } /** * Fetches a list of contacts with optional filtering. * @param parameters - Options for fetching contacts. * @param parameters.email - Optional email address to query on. * @param parameters.family_name - Optional last name or surname to query on. * @param parameters.given_name - Optional first name or forename to query on. * @param parameters.limit - Sets a total of items to return. * @param parameters.offset - Sets a beginning range of items to return. * @param parameters.optional_properties - List of Contact properties to include in the response. * @param parameters.order - Attribute to order items by. * @param parameters.order_direction - How to order the data (ascending or descending). * @param parameters.since - Date to start searching from on LastUpdated (ex. 2017-01-01T22:17:59.039Z). * @param parameters.until - Date to search to on LastUpdated (ex. 2017-01-01T22:17:59.039Z). * @example * const contacts = await contactsInstance.getContacts({ * limit: 5, * offset: 10, * order: "date_created", * order_direction: "DESCENDING" * }); * @returns A paginator containing the list of contacts. */ async getContacts(parameters?: { email?: string; family_name?: string; given_name?: string; limit?: number; offset?: number; optional_properties?: string[]; order?: | "id" | "date_created" | "last_updated" | "name" | "firstName" | "email"; order_direction?: "ASCENDING" | "DESCENDING"; since?: string; until?: string; }): Promise> { const params = parameters ? createParams(parameters, [ "email", "limit", "offset", "family_name", "given_name", "optional_properties", "order", "order_direction", "since", "until", ]).toString() : ""; const response = await this.api.get(`v1/contacts?${params}`); const responseObj = response as { contacts: IContact[] | null }; if ( !responseObj || !Object.prototype.hasOwnProperty.call(responseObj, "contacts") ) { throw new Error("Invalid response format: missing contacts property"); } if (responseObj.contacts === null) { throw new Error("No contacts found in response"); } const contacts = responseObj.contacts.map((contact: IContact) => { if (contact === null) { throw new Error("Contact data is null"); } return new Contact(this, contact); }); return Paginator.wrap(this.api, { ...response, contacts }, "contacts"); } /** * Creates a new contact in Keap. * Note: Contact must contain at least one item in email_addresses or phone_numbers, * and country_code is required if region is specified on the addresses. * @param contactData - The data to create a contact with. * @returns The newly created contact. * @example * const contact = await contactsInstance.createContact({ * given_name: 'John', * family_name: 'Doe', * email_addresses: [ * { * email: "john.doe@example.com", * field: "EMAIL1" * } * ] * }); */ async createContact(contactData: IContact): Promise { if (!contactData.email_addresses && !contactData.phone_numbers) { throw new Error( "Contact must contain at least one item in email_addresses or phone_numbers" ); } const addressesWithRegionAndNoCountryCode = contactData.addresses?.filter( (address) => address.region && !address.country_code ); if ( addressesWithRegionAndNoCountryCode && addressesWithRegionAndNoCountryCode.length > 0 ) { throw new Error( "Contact must contain a country code if a region is provided" ); } const response = await this.api.post("v1/contacts", contactData); return new Contact(this, response as IContact); } /** * Updates an existing contact. * @param contactId - The ID of the contact to update. * @param contactData {IContact} - The data to update the contact with. * @returns The updated contact if the API call was successful. * @throws Will throw an error if the API call fails. * @example * const contact = await contacts * .updateContact(123, { * given_name: "Jane", * family_name: "Doe" * }); */ async updateContact( contactId: number, contactData: IContact ): Promise { const r = await this.api.patch(`v1/contacts/${contactId}`, contactData); return new Contact(this, r as IContact); } /** * Deletes a contact by ID. * @param contactId - The ID of the contact to delete. * @returns True if the contact was successfully deleted. * @example * const result = await contactsInstance.deleteContact(123); * if (result) { * console.log("Contact deleted successfully"); * } */ async deleteContact(contactId: number): Promise { await this.api.delete(`v1/contacts/${contactId}`); return true; } /** * Fetches a single contact by ID. * @param contactId - The ID of the contact to fetch. * @returns The contact. * @example * const contact = await contactsInstance.getContact(123); * console.log(contact.given_name); // "Jane" * console.log(contact.family_name); // "Doe" */ async getContact(contactId: number): Promise { const response = await this.api.get(`v1/contacts/${contactId}`); return new Contact(this, response as IContact); } /** * Fetches the contact model schema. * @returns The contact model. * @example * const contactModel = await contactsInstance.getContactModel(); * console.log(contactModel); */ async getContactModel(): Promise { const response = await this.api.get("v1/contacts/model"); return response as ContactModel; } /** * Creates a new custom contact field. * @param customFieldData - The data to create a custom field with. * @returns The newly created custom field. * @example * const customField = await contactsInstance.createCustomField({ * field_type: "Text", * label: "Custom Field Name" * }); */ async createCustomField(customFieldData: CustomField): Promise { const response = await this.api.post( "v1/contacts/customFields", customFieldData ); return response as CustomField; } /** * Creates a new contact if the contact does not exist, or updates the contact if it does. * @param contactData - The data to create or update a contact with. * @returns The newly created or updated contact. * @example * const contact = await contactsInstance.createOrUpdate({ * given_name: "Jane", * family_name: "Doe" * }); * console.log(contact); // {given_name: "Jane", family_name: "Doe", id: 123} */ async createOrUpdate(contactData: IContact): Promise { const response = await this.api.put("v1/contacts", contactData); return new Contact(this, response as IContact); } /** * Fetches the credit cards associated with a contact. * @param contactId - The ID of the contact to fetch credit cards for. * @returns The credit cards associated with the contact if the API call was successful. */ async getCreditCards(contactId: number): Promise { const response = await this.api.get(`v1/contacts/${contactId}/creditCards`); return response as object; } /** * Creates a new credit card for a contact. * @param contactId - The ID of the contact to add the credit card to. * @param creditCardData - The data to create the credit card with. * @returns The newly created credit card. */ async createCreditCard( contactId: number, creditCardData: object ): Promise { const response = await this.api.post( `v1/contacts/${contactId}/creditCards`, creditCardData ); return response as CreditCard; } /** * Fetches the email addresses associated with a contact. * @param contactId - The ID of the contact to fetch email addresses for. * @returns The email addresses associated with the contact if the API call was successful. */ async listEmails( contactId: number, options?: { email?: string; limit?: number; offset?: number } ): Promise> { const params = options ? createParams(options, ["email", "limit", "offset"]) : ""; const r = await this.api.get( `v1/contacts/${contactId}/emails?${params?.toString()}` ); return Paginator.wrap(this.api, r, "emails"); } /** * Creates a new email address for a contact. * @param contactId - The ID of the contact to add the email address to. * @param emailData {EmailRecord} - The data to create the email address with. * @returns The newly created email address if the API call was successful. */ createEmail(contactId: number, emailData: EmailRecord): Promise { return this.api.post( `v1/contacts/${contactId}/emails`, emailData ) as Promise; } /** * Fetches the tags applied to a contact. * @param contactId - The ID of the contact to fetch tags for. * @param options - The options to use when fetching tags. * @returns The tags applied to the contact if the API call was successful. */ async listAppliedTags( contactId: number, options?: { limit?: number; offset?: number } ): Promise> { const params = options ? createParams(options, ["limit", "offset"]) : ""; const r = await this.api.get( `v1/contacts/${contactId}/tags?${params?.toString()}` ); return Paginator.wrap(this.api, r, "tags"); } /** * Applies a tag to a contact. * @param contactId {number} - The ID of the contact to apply the tag to. * @param tagIds {number[]} - The ID of the tag to apply. * @returns The result of the API call if it was successful. */ applyTags(contactId: number, tagIds: number[]): Promise { return this.api.post(`v1/contacts/${contactId}/tags`, { tagIds, }) as Promise; } /** * Removes a tag from a contact. * @param contactId - The ID of the contact to remove the tag from. * @param tagId - The ID of the tag to remove. * @returns The result of the API call if it was successful. */ removeTag(contactId: number, tagId: number): Promise { return this.api.delete( `v1/contacts/${contactId}/tags/${tagId}` ) as Promise; } /** * Removes multiple tags from a contact. * @param contactId - The ID of the contact to remove the tags from. * @param tagIds - The IDs of the tags to remove. * @param data - Data to be sent with the request. * @returns The result of the API call if it was successful. */ removeTags( contactId: number, tagIds: number[], data: object ): Promise { return this.api.delete( `v1/contacts/${contactId}/tags?tagIds=${tagIds.join(",")}`, data ) as Promise; } /** * Adds UTM tracking data to a contact. * @param contactId - The ID of the contact to add the UTM data to. * @param utmData - The UTM data to add. * @returns The result of the API call if it was successful. */ addUTM(contactId: number, utmData: UTM): Promise { return this.api.post( `v1/contacts/${contactId}/utms`, utmData ) as Promise; } } class Contact { ScoreValue: string | null = null; addresses: Array<{ country_code: string; field: string; line1: string; line2: string; locality: string; postal_code: string; region: string; zip_code: string; zip_four: string; }> | null = null; anniversary: Date | null = null; birthday: Date | null = null; company: { company_name: string; id: number; } | null = null; company_name: string | null = null; contact_type: string | null = null; custom_fields: Array<{ content: object; id: number; }> | null = null; date_created: Date | null = null; email_addresses: Array<{ email: string; field: string; }> | null = null; email_opted_in: boolean | null = null; email_status: string | null = null; family_name: string | null = null; fax_numbers: Array<{ field: string; number: string; type: string; }> | null = null; given_name: string | null = null; id: number; job_title: string | null = null; last_updated: Date | null = null; lead_source_id: number | null = null; middle_name: string | null = null; opt_in_reason: string | null = null; origin: { date: Date; ip_address: string; } | null = null; owner_id: number | null = null; phone_numbers: Array<{ extension: string; field: string; number: string; type: string; }> | null = null; preferred_locale: string | null = null; preferred_name: string | null = null; prefix: string | null = null; relationships: Array<{ id: number; linked_contact_id: number; relationship_type_id: number; }> | null = null; social_accounts: Array<{ name: string; type: string; }> | null = null; source_type: string | null = null; spouse_name: string | null = null; suffix: string | null = null; tag_ids: Array | null = null; time_zone: string | null = null; website: string | null = null; private contacts: Contacts; /** * Creates a new Contact instance from the given data and API client. * @param api - The API client to use for making requests. * @param data - The data to use to populate the Contact instance. */ constructor(contacts: Contacts, data: IContact) { this.contacts = contacts; Object.assign(this, data); if (!data.id) throw new Error("Contact ID is required"); this.id = data.id; } /** * Updates the contact with the given data. * @param data - The data to update the contact with. * @returns The updated contact if the API call was successful. */ async update(data: IContact): Promise { const r = await this.contacts.updateContact(this.id, data); return new Contact(this.contacts, r); } /** * Deletes the contact. * @returns The result of the API call if it was successful. * @throws Will throw an error if the API call fails. */ async delete(): Promise { return this.contacts.deleteContact(this.id); } /** * Refreshes the contact data from the API. * @returns The updated contact if the API call was successful. */ async refresh(): Promise { const contact = await this.contacts.getContact(this.id); Object.assign(this, contact); return contact; } /** * Fetches the credit cards associated with this contact. * @returns The credit cards associated with this contact if the API call was successful. */ getCreditCards(): Promise { return this.contacts.getCreditCards(this.id); } /** * Creates a new credit card for this contact. * @param data - The data to create the credit card with. * @returns The newly created credit card if the API call was successful. */ addCreditCard(data: object): Promise { return this.contacts.createCreditCard(this.id, data); } /** * Fetches the email addresses associated with this contact. * @param options - The options to use when fetching email addresses. * @returns The email addresses associated with this contact if the API call was successful. */ getEmails(options?: { email?: string; limit?: number; offset?: number; }): Promise> { return this.contacts.listEmails(this.id, options); } /** * Adds a new email address to this contact. * @param data - The data to create the email address with. * @returns The newly created email address if the API call was successful. */ addEmail(data: EmailRecord): Promise { return this.contacts.createEmail(this.id, data); } /** * Fetches the tags applied to this contact. * @param options - The options to use when fetching tags. * @returns The tags applied to this contact if the API call was successful. */ getAppliedTags(options?: { limit?: number; offset?: number; }): Promise> { return this.contacts.listAppliedTags(this.id, options); } /** * Applies the given tags to this contact. * @param tagIds - The IDs of the tags to apply. * @returns The result of the API call if it was successful. */ applyTags(tagIds: number[]): Promise { return this.contacts.applyTags(this.id, tagIds); } /** * Removes the given tag from this contact. * @param tagId - The ID of the tag to remove. * @returns The result of the API call if it was successful. */ removeTag(tagId: number): Promise { return this.contacts.removeTag(this.id, tagId); } /** * Removes multiple tags from this contact. * @param tagIds - The IDs of the tags to remove. * @param data - Data to be sent with the request. * @returns The result of the API call if it was successful. */ removeTags(tagIds: number[], data: object): Promise { return this.contacts.removeTags(this.id, tagIds, data); } /** * Adds UTM tracking data to this contact. * @param utmData - The UTM data to add. * @returns The result of the API call if it was successful. */ addUTM(utmData: UTM): Promise { return this.contacts.addUTM(this.id, utmData); } }