import * as z from "zod" import { defineAction } from "../../../automation/actions" import { TRELLO_READ_SCOPE, TRELLO_WRITE_SCOPE } from "./scopes" import { getTrelloApi, normalizeTrelloBoardId, normalizeTrelloCardId, } from "@automate.ax/integration-contracts/trello" import { TRELLO_CUSTOM_FIELD_ITEM_SCHEMA, TRELLO_CUSTOM_FIELD_OPTION_SCHEMA, TRELLO_CUSTOM_FIELD_SCHEMA, TRELLO_CUSTOM_FIELD_VALUE_SCHEMA, TRELLO_PROVIDER_CUSTOM_FIELD_ITEM_SCHEMA, TRELLO_PROVIDER_CUSTOM_FIELD_OPTION_SCHEMA, TRELLO_PROVIDER_CUSTOM_FIELD_SCHEMA, toTrelloCustomField, toTrelloCustomFieldItem, toTrelloCustomFieldOption, } from "@automate.ax/integration-contracts/trello" const TRELLO_ACCOUNT = "trello" const POSITION_SCHEMA = z.union([ z.enum(["top", "bottom"]), z.number().nonnegative(), ]) const CUSTOM_FIELD_TYPE_SCHEMA = z.enum([ "checkbox", "date", "list", "number", "text", ]) const CUSTOM_FIELD_OPTION_COLOR_SCHEMA = z.enum([ "black", "blue", "green", "lime", "none", "orange", "pink", "purple", "red", "sky", "yellow", ]) const DATE_INPUT_SCHEMA = z.union([ z.date(), z.iso.datetime({ offset: true }).transform((value) => new Date(value)), ]) const CUSTOM_FIELD_VALUE_INPUT_SCHEMA = z.discriminatedUnion("type", [ z.object({ checked: z.boolean(), type: z.literal("checkbox") }), z.object({ date: DATE_INPUT_SCHEMA, type: z.literal("date") }), z.object({ optionId: z.string().trim().min(1), type: z.literal("list") }), z.object({ number: z.string().trim().min(1), type: z.literal("number") }), z.object({ text: z.string(), type: z.literal("text") }), ]) /** Lists Custom Field definitions on a Trello board. */ export const listTrelloBoardCustomFields = defineAction( "List Trello board Custom Fields", ) .describe("Lists Custom Field definitions on a Trello board.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input(z.object({ board: z.string().trim().min(1) })) .output(TRELLO_CUSTOM_FIELD_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ( await getTrelloApi(account.secret).request( `boards/${encodeURIComponent(normalizeTrelloBoardId(input.board))}/customFields`, { responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_SCHEMA.array() }, ) ).map(toTrelloCustomField), ) /** Retrieves one Trello Custom Field definition. */ export const getTrelloCustomField = defineAction("Get Trello Custom Field") .describe("Retrieves one Trello Custom Field definition.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input(z.object({ customFieldId: z.string().trim().min(1) })) .output(TRELLO_CUSTOM_FIELD_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => toTrelloCustomField( await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}`, { responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_SCHEMA }, ), ), ) /** Creates a Trello Custom Field definition on a board. */ export const createTrelloCustomField = defineAction( "Create Trello Custom Field", ) .describe("Creates a typed Custom Field definition on a Trello board.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ /** Board ID, short link, or standard Trello board URL. */ board: z.string().trim().min(1), /** Whether the field appears on card fronts. */ displayOnCardFront: z.boolean().prefault(true), /** Human-readable Custom Field name. */ name: z.string().trim().min(1), /** Initial Custom Field position. */ position: POSITION_SCHEMA.prefault("bottom"), /** Custom Field value type. */ type: CUSTOM_FIELD_TYPE_SCHEMA, }), ) .output(TRELLO_CUSTOM_FIELD_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloCustomField( await getTrelloApi(account.secret).request("customFields", { body: { display_cardFront: input.displayOnCardFront, idModel: normalizeTrelloBoardId(input.board), modelType: "board", name: input.name, pos: input.position, type: input.type, }, method: "POST", responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_SCHEMA, }), ), ) /** Updates a Trello Custom Field definition. */ export const updateTrelloCustomField = defineAction( "Update Trello Custom Field", ) .describe("Updates a Trello Custom Field's name, position, or display.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z .object({ /** Stable Trello Custom Field ID. */ customFieldId: z.string().trim().min(1), /** Whether the field appears on card fronts. */ displayOnCardFront: z.boolean().optional(), /** Replacement Custom Field name. */ name: z.string().trim().min(1).optional(), /** Replacement Custom Field position. */ position: POSITION_SCHEMA.optional(), }) .refine( ({ displayOnCardFront, name, position }) => displayOnCardFront !== undefined || name !== undefined || position !== undefined, "Provide at least one Custom Field property to update.", ), ) .output(TRELLO_CUSTOM_FIELD_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloCustomField( await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}`, { body: { "display/cardFront": input.displayOnCardFront, name: input.name, pos: input.position, }, method: "PUT", responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_SCHEMA, }, ), ), ) /** Permanently deletes a Trello Custom Field definition and its card values. */ export const deleteTrelloCustomField = defineAction( "Delete Trello Custom Field", ) .describe("Deletes a Custom Field definition and every card value using it.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input(z.object({ customFieldId: z.string().trim().min(1) })) .output(z.object({ customFieldId: z.string(), deleted: z.literal(true) })) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}`, { method: "DELETE", responseSchema: z.unknown() }, ) return { customFieldId: input.customFieldId, deleted: true as const } }) /** Lists dropdown options on a Trello Custom Field. */ export const listTrelloCustomFieldOptions = defineAction( "List Trello Custom Field options", ) .describe("Lists dropdown options on a Trello Custom Field.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input(z.object({ customFieldId: z.string().trim().min(1) })) .output(TRELLO_CUSTOM_FIELD_OPTION_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ( await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}/options`, { responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_OPTION_SCHEMA.array() }, ) ).map(toTrelloCustomFieldOption), ) /** Retrieves one dropdown option on a Trello Custom Field. */ export const getTrelloCustomFieldOption = defineAction( "Get Trello Custom Field option", ) .describe("Retrieves one dropdown option on a Trello Custom Field.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input( z.object({ customFieldId: z.string().trim().min(1), optionId: z.string().trim().min(1), }), ) .output(TRELLO_CUSTOM_FIELD_OPTION_SCHEMA) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => toTrelloCustomFieldOption( await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}/options/${encodeURIComponent(input.optionId)}`, { responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_OPTION_SCHEMA }, ), ), ) /** Creates a dropdown option on a Trello Custom Field. */ export const createTrelloCustomFieldOption = defineAction( "Create Trello Custom Field option", ) .describe("Creates a dropdown option on a Trello Custom Field.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ color: CUSTOM_FIELD_OPTION_COLOR_SCHEMA.prefault("none"), customFieldId: z.string().trim().min(1), position: POSITION_SCHEMA.prefault("bottom"), text: z.string().trim().min(1), }), ) .output(TRELLO_CUSTOM_FIELD_OPTION_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloCustomFieldOption( await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}/options`, { body: { color: input.color, pos: input.position, value: { text: input.text }, }, method: "POST", responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_OPTION_SCHEMA, }, ), ), ) /** Updates a dropdown option on a Trello Custom Field. */ export const updateTrelloCustomFieldOption = defineAction( "Update Trello Custom Field option", ) .describe("Updates a dropdown option on a Trello Custom Field.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z .object({ color: CUSTOM_FIELD_OPTION_COLOR_SCHEMA.optional(), customFieldId: z.string().trim().min(1), optionId: z.string().trim().min(1), position: POSITION_SCHEMA.optional(), text: z.string().trim().min(1).optional(), }) .refine( ({ color, position, text }) => color !== undefined || position !== undefined || text !== undefined, "Provide at least one Custom Field option property to update.", ), ) .output(TRELLO_CUSTOM_FIELD_OPTION_SCHEMA) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => toTrelloCustomFieldOption( await getTrelloApi(account.secret).request( `customField/${encodeURIComponent(input.customFieldId)}/options/${encodeURIComponent(input.optionId)}`, { body: { color: input.color, pos: input.position, ...(input.text !== undefined && { value: { text: input.text } }), }, method: "PUT", responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_OPTION_SCHEMA, }, ), ), ) /** Deletes a dropdown option from a Trello Custom Field. */ export const deleteTrelloCustomFieldOption = defineAction( "Delete Trello Custom Field option", ) .describe("Deletes a dropdown option from a Trello Custom Field.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ customFieldId: z.string().trim().min(1), optionId: z.string().trim().min(1), }), ) .output( z.object({ customFieldId: z.string(), deleted: z.literal(true), optionId: z.string(), }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { await getTrelloApi(account.secret).request( `customFields/${encodeURIComponent(input.customFieldId)}/options/${encodeURIComponent(input.optionId)}`, { method: "DELETE", responseSchema: z.unknown() }, ) return { customFieldId: input.customFieldId, deleted: true as const, optionId: input.optionId, } }) /** Lists typed Custom Field values stored on a Trello card. */ export const listTrelloCardCustomFieldValues = defineAction( "List Trello card Custom Field values", ) .describe("Lists typed Custom Field values stored on a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_READ_SCOPE) .input(z.object({ card: z.string().trim().min(1) })) .output(TRELLO_CUSTOM_FIELD_ITEM_SCHEMA.array()) .retry({ replaySafety: "safe" }) .handler(async ({ account, input }) => ( await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(normalizeTrelloCardId(input.card))}/customFieldItems`, { responseSchema: TRELLO_PROVIDER_CUSTOM_FIELD_ITEM_SCHEMA.array() }, ) ).map(toTrelloCustomFieldItem), ) /** Sets one typed Custom Field value on a Trello card. */ export const setTrelloCardCustomFieldValue = defineAction( "Set Trello card Custom Field value", ) .describe("Sets one typed Custom Field value on a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ card: z.string().trim().min(1), customFieldId: z.string().trim().min(1), value: CUSTOM_FIELD_VALUE_INPUT_SCHEMA, }), ) .output( z.object({ cardId: z.string(), customFieldId: z.string(), value: TRELLO_CUSTOM_FIELD_VALUE_SCHEMA, }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const cardId = normalizeTrelloCardId(input.card) await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(cardId)}/customField/${encodeURIComponent(input.customFieldId)}/item`, { body: toProviderCustomFieldValue(input.value), method: "PUT", responseSchema: z.unknown(), }, ) return { cardId, customFieldId: input.customFieldId, value: input.value } }) /** Clears one Custom Field value from a Trello card. */ export const clearTrelloCardCustomFieldValue = defineAction( "Clear Trello card Custom Field value", ) .describe("Clears one Custom Field value from a Trello card.") .account(TRELLO_ACCOUNT, TRELLO_WRITE_SCOPE) .input( z.object({ card: z.string().trim().min(1), customFieldId: z.string().trim().min(1), }), ) .output( z.object({ cardId: z.string(), cleared: z.literal(true), customFieldId: z.string(), }), ) .retry({ replaySafety: "unsafe" }) .handler(async ({ account, input }) => { const cardId = normalizeTrelloCardId(input.card) await getTrelloApi(account.secret).request( `cards/${encodeURIComponent(cardId)}/customField/${encodeURIComponent(input.customFieldId)}/item`, { body: { idValue: "", value: "" }, method: "PUT", responseSchema: z.unknown(), }, ) return { cardId, cleared: true as const, customFieldId: input.customFieldId, } }) /** * Converts a public typed Custom Field value into Trello's key-selected body. * * @param value - Public typed Custom Field value. */ function toProviderCustomFieldValue( value: z.output, ) { switch (value.type) { case "checkbox": return { value: { checked: String(value.checked) } } case "date": return { value: { date: value.date.toISOString() } } case "list": return { idValue: value.optionId } case "number": return { value: { number: value.number } } case "text": return { value: { text: value.text } } } }