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.')
}
}