import * as z from "zod" import { defineAction } from "../../../automation/actions" import { apolloAccountOptions, getApolloApi } from "../lib/api" const FIELD_MODALITY_SCHEMA = z.enum(["account", "contact", "opportunity"]) const FIELD_TYPE_SCHEMA = z.enum([ "boolean", "date", "datetime", "number", "string", "textarea", ]) const PICKLIST_VALUE_SCHEMA = z.object({ id: z.string().optional(), mappedCrmId: z.string().optional(), name: z.string().trim().min(1), reassignFrom: z.string().array().optional(), }) const FIELD_SCHEMA = z.object({ fieldId: z.string(), label: z.string(), maxLength: z.number().optional(), modality: FIELD_MODALITY_SCHEMA, type: z.string().optional(), }) const CREATE_FIELD_RESPONSE_SCHEMA = z.looseObject({ typed_custom_fields: z .looseObject({ id: z.string(), modality: FIELD_MODALITY_SCHEMA, name: z.string(), text_field_max_length: z.number().nullish(), }) .array() .min(1), }) const UPDATE_FIELD_RESPONSE_SCHEMA = z.looseObject({ fields: z .looseObject({ id: z.string(), label: z.string(), meta: z.looseObject({ max_length: z.number().nullish() }).nullish(), modality: FIELD_MODALITY_SCHEMA, type: z.string().nullish(), }) .array() .min(1), }) const UPDATE_FIELD_INPUT_SCHEMA = z .object({ autorunDownstreamEnrichment: z.boolean().optional(), fieldGroupId: z.string().optional(), fieldId: z.string().trim().min(1), label: z.string().trim().min(1).optional(), maxLength: z.number().int().positive().optional(), picklistValueSetId: z.string().optional(), picklistValues: PICKLIST_VALUE_SCHEMA.array().min(1).optional(), schedule: z .object({ dayOfMonth: z.number().int().min(1).max(31).optional(), dayOfWeek: z.number().int().min(0).max(6).optional(), enabled: z.boolean().optional(), endDate: z.iso.date().optional(), hour: z.number().int().min(0).max(23).optional(), interval: z.number().int().positive().optional(), maxExecutions: z.number().int().positive().optional(), minute: z.number().int().min(0).max(59).optional(), resetExecutionCount: z.boolean().optional(), runLimit: z.number().int().positive().optional(), type: z.string().trim().min(1).optional(), timezone: z.string().trim().min(1).optional(), }) .optional(), }) .refine( (input) => input.autorunDownstreamEnrichment !== undefined || input.fieldGroupId !== undefined || input.label !== undefined || input.maxLength !== undefined || input.picklistValueSetId !== undefined || input.picklistValues !== undefined || input.schedule !== undefined, "Provide at least one custom field change.", ) /** Creates one custom field in Apollo. */ export const createApolloCustomField = defineAction( "Create Apollo custom field", ) .describe("Creates a custom field for contacts, accounts, or deals.") .account("apollo", apolloAccountOptions("custom_field_write")) .input( z.object({ label: z.string().trim().min(1), maxLength: z.number().int().positive().optional(), modality: FIELD_MODALITY_SCHEMA, type: FIELD_TYPE_SCHEMA, }), ) .output(FIELD_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const { typed_custom_fields: fields } = await getApolloApi(account).request( "fields", { body: { label: input.label, meta: input.maxLength === undefined ? undefined : { max_length: input.maxLength }, modality: input.modality, type: input.type, }, responseSchema: CREATE_FIELD_RESPONSE_SCHEMA, }, ) const field = fields[0] if (!field) throw new Error("Apollo did not return the created field.") return { fieldId: `${field.modality}.${field.id}`, label: field.name, maxLength: field.text_field_max_length ?? undefined, modality: field.modality, type: input.type, } }) /** Updates one custom field in Apollo. */ export const updateApolloCustomField = defineAction( "Update Apollo custom field", ) .describe( "Updates one custom field. Picklist values replace the complete option set.", ) .account("apollo", apolloAccountOptions("custom_field_write")) .input(UPDATE_FIELD_INPUT_SCHEMA) .output(FIELD_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const field = ( await getApolloApi(account).request("fields", { body: { fields: [ { autorun_downstream_enrichment: input.autorunDownstreamEnrichment, field_group_id: input.fieldGroupId, id: input.fieldId, label: input.label, meta: input.maxLength === undefined && input.picklistValues === undefined && input.picklistValueSetId === undefined ? undefined : { max_length: input.maxLength, picklist_value_set_id: input.picklistValueSetId, picklist_values: input.picklistValues?.map((value) => ({ id: value.id, mapped_crm_id: value.mappedCrmId, name: value.name, reassign_from: value.reassignFrom, })), }, schedule: input.schedule === undefined ? undefined : { enabled: input.schedule.enabled, end_date: input.schedule.endDate, max_executions: input.schedule.maxExecutions, reset_execution_count: input.schedule.resetExecutionCount, run_limit: input.schedule.runLimit, schedule_config: { day_of_month: input.schedule.dayOfMonth, day_of_week: input.schedule.dayOfWeek, hour: input.schedule.hour, interval: input.schedule.interval, minute: input.schedule.minute, }, schedule_timezone: input.schedule.timezone, schedule_type: input.schedule.type, }, }, ], }, method: "PATCH", responseSchema: UPDATE_FIELD_RESPONSE_SCHEMA, }) ).fields[0] if (!field) throw new Error("Apollo did not return the updated field.") return { fieldId: field.id, label: field.label, maxLength: field.meta?.max_length ?? undefined, modality: field.modality, type: field.type ?? undefined, } })