Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/trello

src/boards.ts

195 lines · 5.8 KB · TypeScript
import { isDryRunResult, trelloRequest } from './client.ts'
import { parseAuthInput } from './auth.ts'
import { BOARD_FIELDS, boardSummary, mapMany, requireMapped } from './models.ts'
import type {
	DryRunResult,
	MutationInput,
	TrelloAuthInput,
	TrelloBoardSummary,
} from './types.ts'
import {
	clampInt,
	compactRecord,
	optionalBoolean,
	optionalString,
	requireRecord,
	requireString,
} from './types.ts'

export type ListBoardsInput = TrelloAuthInput & {
	filter?: 'open' | 'closed' | 'all'
	limit?: number
}

export type GetBoardInput = TrelloAuthInput & {
	boardId: string
}

export type CreateBoardInput = TrelloAuthInput &
	MutationInput & {
		name: string
		desc?: string
		idOrganization?: string
		defaultLists?: boolean
	}

export type UpdateBoardInput = TrelloAuthInput &
	MutationInput & {
		boardId: string
		name?: string
		desc?: string
		closed?: boolean
	}

function boardFilter(value: unknown): 'open' | 'closed' | 'all' {
	if (value === undefined || value === null || value === '') return 'open'
	const filter = requireString(value, 'filter')
	if (filter !== 'open' && filter !== 'closed' && filter !== 'all') {
		throw new Error('filter must be "open", "closed", or "all".')
	}
	return filter
}

/**
 * List boards for the authenticated member. Caller supplies no default board id.
 * @example
 * import { listBoards } from 'kody:@kody/trello/boards'
 * const boards = await listBoards({ filter: 'open', limit: 25 })
 */
export async function listBoards(input: ListBoardsInput = {}): Promise<Array<TrelloBoardSummary>> {
	const limit = clampInt(input.limit, 1, 200, 50)
	const result = await trelloRequest<unknown>({
		...input,
		method: 'GET',
		path: '/members/me/boards',
		query: compactRecord({
			filter: boardFilter(input.filter),
			fields: BOARD_FIELDS,
		}),
	})
	if (isDryRunResult(result)) return []
	return mapMany(result.data, boardSummary).slice(0, limit)
}

/**
 * Get one Trello board by id supplied by the caller.
 * @example
 * import { getBoard } from 'kody:@kody/trello/boards'
 * const board = await getBoard({ boardId })
 */
export async function getBoard(input: GetBoardInput): Promise<TrelloBoardSummary> {
	const boardId = requireString(input.boardId, 'boardId')
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'GET',
		path: `/boards/${boardId}`,
		query: { fields: BOARD_FIELDS },
	})
	if (isDryRunResult(result)) {
		throw new Error('getBoard is read-only and does not support dryRun.')
	}
	return requireMapped(boardSummary(result.data), 'getBoard')
}

/**
 * Create a Trello board. Requires `confirm: true`, or use `dryRun: true`.
 * @example
 * import { createBoard } from 'kody:@kody/trello/boards'
 * const preview = await createBoard({ name: 'Sprint planning', dryRun: true })
 */
export async function createBoard(
	input: CreateBoardInput,
): Promise<TrelloBoardSummary | DryRunResult> {
	const body = compactRecord({
		name: requireString(input.name, 'name'),
		desc: optionalString(input.desc, 'desc'),
		idOrganization: optionalString(input.idOrganization, 'idOrganization'),
		defaultLists: optionalBoolean(input.defaultLists, 'defaultLists'),
	})
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'POST',
		path: '/boards',
		query: compactRecord({ ...body, fields: BOARD_FIELDS }),
		body,
	})
	if (isDryRunResult(result)) return result
	return requireMapped(boardSummary(result.data), 'createBoard')
}

/**
 * Update a Trello board. Requires `confirm: true`, or use `dryRun: true`.
 * @example
 * import { updateBoard } from 'kody:@kody/trello/boards'
 * const preview = await updateBoard({ boardId, name: 'Renamed', dryRun: true })
 */
export async function updateBoard(
	input: UpdateBoardInput,
): Promise<TrelloBoardSummary | DryRunResult> {
	const boardId = requireString(input.boardId, 'boardId')
	const body = compactRecord({
		name: optionalString(input.name, 'name'),
		desc: optionalString(input.desc, 'desc'),
		closed: optionalBoolean(input.closed, 'closed'),
	})
	if (Object.keys(body).length === 0) {
		throw new Error('updateBoard requires at least one of name, desc, or closed.')
	}
	const result = await trelloRequest<Record<string, unknown>>({
		...input,
		method: 'PUT',
		path: `/boards/${boardId}`,
		query: compactRecord({ ...body, fields: BOARD_FIELDS }),
		body,
	})
	if (isDryRunResult(result)) return result
	return requireMapped(boardSummary(result.data), 'updateBoard')
}

export { boardSummary }

/**
 * Board helpers dispatcher.
 * @example
 * import boards from 'kody:@kody/trello/boards'
 * const items = await boards({ action: 'list' })
 */
export default async function boardsEntrypoint(
	params: Record<string, unknown> = {},
) {
	const input = requireRecord(params, 'boards')
	const auth = parseAuthInput(input)
	const action = optionalString(input.action, 'action') ?? 'list'
	switch (action) {
		case 'list':
			return listBoards({
				...auth,
				filter: boardFilter(input.filter),
				limit: input.limit as number | undefined,
			})
		case 'get':
			return getBoard({ ...auth, boardId: requireString(input.boardId, 'boardId') })
		case 'create':
			return createBoard({
				...auth,
				name: requireString(input.name, 'name'),
				desc: optionalString(input.desc, 'desc'),
				idOrganization: optionalString(input.idOrganization, 'idOrganization'),
				defaultLists: optionalBoolean(input.defaultLists, 'defaultLists'),
				confirm: optionalBoolean(input.confirm, 'confirm'),
				dryRun: optionalBoolean(input.dryRun, 'dryRun'),
			})
		case 'update':
			return updateBoard({
				...auth,
				boardId: requireString(input.boardId, 'boardId'),
				name: optionalString(input.name, 'name'),
				desc: optionalString(input.desc, 'desc'),
				closed: optionalBoolean(input.closed, 'closed'),
				confirm: optionalBoolean(input.confirm, 'confirm'),
				dryRun: optionalBoolean(input.dryRun, 'dryRun'),
			})
		default:
			throw new Error('boards action must be one of: list, get, create, update.')
	}
}