import { parseAuth } from './auth.ts'
import { zendeskRequest } from './client.ts'
import { compactDefined, extractListItems, mapUser } from './models.ts'
import type {
DryRunResult,
JsonRecord,
MutationInput,
ZendeskAuthInput,
ZendeskPageInfo,
ZendeskUser,
} from './types.ts'
import {
clampInt,
optionalBoolean,
optionalNumber,
optionalString,
requireRecord,
requireString,
} from './types.ts'
export type ListUsersInput = ZendeskAuthInput & {
perPage?: number
page?: number
role?: string
}
export type GetUserInput = ZendeskAuthInput & {
id: number | string
}
export type SearchUsersInput = ZendeskAuthInput & {
query: string
perPage?: number
page?: number
}
export type CreateUserInput = ZendeskAuthInput &
MutationInput & {
name: string
email?: string
role?: string
organizationId?: number
externalId?: string
}
export type UpdateUserInput = ZendeskAuthInput &
MutationInput & {
id: number | string
name?: string
email?: string
role?: string
organizationId?: number
verified?: boolean
}
export type UserListResult = {
items: Array<ZendeskUser>
pageInfo: ZendeskPageInfo
}
function userId(value: number | string, label = 'id'): string {
if (typeof value === 'number' && Number.isInteger(value) && value > 0) return String(value)
return requireString(value, label)
}
/**
* List Zendesk users.
* @example
* import { listUsers } from 'kody:@kody/zendesk/users'
* const { items } = await listUsers({ perPage: 10, subdomain: 'acme' })
*/
export async function listUsers(input: ListUsersInput = {}): Promise<UserListResult> {
const result = await zendeskRequest<unknown>({
...input,
operation: 'users.read',
path: '/users',
query: compactDefined({
per_page: clampInt(input.perPage, 1, 100, 20),
page: input.page === undefined ? undefined : clampInt(input.page, 1, 10_000, 1, 'page'),
role: input.role,
}),
})
if ('dryRun' in result) throw new Error('listUsers is read-only.')
return {
items: extractListItems(result.data, ['users'])
.map(mapUser)
.filter((item): item is ZendeskUser => Boolean(item)),
pageInfo: result.pageInfo,
}
}
/**
* Get one Zendesk user by id.
* @example
* import { getUser } from 'kody:@kody/zendesk/users'
* const user = await getUser({ id, subdomain: 'acme' })
*/
export async function getUser(input: GetUserInput): Promise<ZendeskUser> {
const id = userId(input.id)
const result = await zendeskRequest<unknown>({
...input,
operation: 'users.read',
path: `/users/${encodeURIComponent(id)}`,
})
if ('dryRun' in result) throw new Error('getUser is read-only.')
const mapped = mapUser(result.data)
if (!mapped) throw new Error('Zendesk did not return a user id.')
return mapped
}
/**
* Search Zendesk users. GET `/users/search` is a read.
* @example
* import { searchUsers } from 'kody:@kody/zendesk/users'
* const { items } = await searchUsers({ query: 'pat@example.com', subdomain: 'acme' })
*/
export async function searchUsers(input: SearchUsersInput): Promise<UserListResult> {
const result = await zendeskRequest<unknown>({
...input,
operation: 'users.read',
path: '/users/search',
query: compactDefined({
query: requireString(input.query, 'query'),
per_page: clampInt(input.perPage, 1, 100, 20),
page: input.page === undefined ? undefined : clampInt(input.page, 1, 10_000, 1, 'page'),
}),
})
if ('dryRun' in result) throw new Error('searchUsers is read-only.')
return {
items: extractListItems(result.data, ['users'])
.map(mapUser)
.filter((item): item is ZendeskUser => Boolean(item)),
pageInfo: result.pageInfo,
}
}
function userBody(input: CreateUserInput | UpdateUserInput): JsonRecord {
return {
user: compactDefined({
name: 'name' in input ? input.name : undefined,
email: input.email,
role: input.role,
organization_id: input.organizationId,
external_id: 'externalId' in input ? input.externalId : undefined,
verified: 'verified' in input ? input.verified : undefined,
}),
}
}
/**
* Create a Zendesk user. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import { createUser } from 'kody:@kody/zendesk/users'
* const preview = await createUser({ name: 'Pat', email: 'pat@example.com', dryRun: true, subdomain: 'acme' })
*/
export async function createUser(input: CreateUserInput): Promise<ZendeskUser | DryRunResult> {
const result = await zendeskRequest<unknown>({
...input,
operation: 'users.write',
method: 'POST',
path: '/users',
body: userBody({ ...input, name: requireString(input.name, 'name') }),
})
if ('dryRun' in result) return result
const mapped = mapUser(result.data)
if (!mapped) throw new Error('Zendesk did not return a created user id.')
return mapped
}
/**
* Update a Zendesk user. Requires `confirm: true`, or use `dryRun: true`.
* @example
* import { updateUser } from 'kody:@kody/zendesk/users'
* const preview = await updateUser({ id, name: 'Pat Lee', dryRun: true, subdomain: 'acme' })
*/
export async function updateUser(input: UpdateUserInput): Promise<ZendeskUser | DryRunResult> {
const id = userId(input.id)
const result = await zendeskRequest<unknown>({
...input,
operation: 'users.write',
method: 'PUT',
path: `/users/${encodeURIComponent(id)}`,
body: userBody(input),
})
if ('dryRun' in result) return result
const mapped = mapUser(result.data)
if (!mapped) throw new Error('Zendesk did not return an updated user id.')
return mapped
}
export function parseListUsers(params: Record<string, unknown>): ListUsersInput {
const input = requireRecord(params, 'list-users')
return {
...parseAuth(input),
perPage: input.perPage as number | undefined,
page: input.page as number | undefined,
role: optionalString(input.role, 'role'),
}
}
export function parseGetUser(params: Record<string, unknown>): GetUserInput {
const input = requireRecord(params, 'get-user')
return { ...parseAuth(input), id: input.id as number | string }
}
export function parseSearchUsers(params: Record<string, unknown>): SearchUsersInput {
const input = requireRecord(params, 'search-users')
return {
...parseAuth(input),
query: requireString(input.query, 'query'),
perPage: input.perPage as number | undefined,
page: input.page as number | undefined,
}
}
export function parseCreateUser(params: Record<string, unknown>): CreateUserInput {
const input = requireRecord(params, 'create-user')
return {
...parseAuth(input),
name: requireString(input.name, 'name'),
email: optionalString(input.email, 'email'),
role: optionalString(input.role, 'role'),
organizationId: optionalNumber(input.organizationId, 'organizationId'),
externalId: optionalString(input.externalId, 'externalId'),
confirm: optionalBoolean(input.confirm, 'confirm'),
dryRun: optionalBoolean(input.dryRun, 'dryRun'),
}
}
export function parseUpdateUser(params: Record<string, unknown>): UpdateUserInput {
const input = requireRecord(params, 'update-user')
return {
...parseAuth(input),
id: input.id as number | string,
name: optionalString(input.name, 'name'),
email: optionalString(input.email, 'email'),
role: optionalString(input.role, 'role'),
organizationId: optionalNumber(input.organizationId, 'organizationId'),
verified: optionalBoolean(input.verified, 'verified'),
confirm: optionalBoolean(input.confirm, 'confirm'),
dryRun: optionalBoolean(input.dryRun, 'dryRun'),
}
}
/**
* Zendesk user helpers. Writes require `confirm: true` or `dryRun: true`.
* @example
* import { listUsers } from 'kody:@kody/zendesk/users'
* const { items } = await listUsers({ perPage: 10, subdomain: 'acme' })
*/
export default async function usersEntrypoint(params: Record<string, unknown> = {}) {
return listUsers(parseListUsers(params))
}