Files
yunke/yunkes/src/api/index.ts
T

278 lines
6.9 KiB
TypeScript
Raw Normal View History

// Typed endpoint wrappers for the M5b browse experience.
//
// Mirrors the FastAPI routes in yunke-server/app/api/routers/. Each wrapper
// drops `undefined`/empty params so the query string only carries set filters
// (the backend treats missing as "no filter").
import { getJson } from './client'
import type {
Account,
CallChannel,
Channel,
Customer,
Department,
Group,
GroupMember,
Message,
Page,
PhoneCall,
Sms,
Staff,
SyncRun,
SyncState,
SyncStatus,
} from './types'
/** Drop entries whose value is `undefined`, `null`, or `''`. */
function clean(params: Record<string, unknown>): Record<string, unknown> {
const out: Record<string, unknown> = {}
for (const [k, v] of Object.entries(params)) {
if (v === undefined || v === null || v === '') continue
out[k] = v
}
return out
}
export interface CustomerQuery {
channel?: Channel | 'all'
page?: number
page_size?: number
owner_account_id?: string
department_id?: string
}
export function getCustomers(query: CustomerQuery = {}): Promise<Page<Customer>> {
return getJson<Page<Customer>>(
'/customers',
clean({
page: query.page ?? 1,
page_size: query.page_size ?? 20,
channel: query.channel === 'all' ? undefined : query.channel,
owner_account_id: query.owner_account_id,
department_id: query.department_id,
}),
)
}
export interface MessageQuery {
channel?: Channel
owner_account_id: string
talker_id?: string
room_id?: string
start?: number
end?: number
page?: number
page_size?: number
}
export function getMessages(query: MessageQuery): Promise<Page<Message>> {
return getJson<Page<Message>>(
'/messages',
clean({
channel: query.channel,
owner_account_id: query.owner_account_id,
talker_id: query.talker_id,
room_id: query.room_id,
start: query.start,
end: query.end,
page: query.page ?? 1,
page_size: query.page_size ?? 50,
}),
)
}
export function getDepartments(): Promise<Department[]> {
return getJson<Department[]>('/departments')
}
export interface StaffQuery {
department_id?: string
is_on_job?: boolean
page?: number
page_size?: number
}
export function getStaff(query: StaffQuery = {}): Promise<Page<Staff>> {
return getJson<Page<Staff>>(
'/staff',
clean({
department_id: query.department_id,
is_on_job: query.is_on_job,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
export interface AccountQuery {
/** channel filter; `'all'` (default) drops the param so both channels union. */
channel?: Channel | 'all'
page?: number
page_size?: number
}
export function getAccounts(query: AccountQuery = {}): Promise<Page<Account>> {
return getJson<Page<Account>>(
'/accounts',
clean({
channel: query.channel === 'all' ? undefined : query.channel,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
// ---------------------------------------------------------------------------
// Chat groups
// ---------------------------------------------------------------------------
export interface GroupQuery {
channel?: Channel | 'all'
owner_account_id?: string
page?: number
page_size?: number
}
export function getGroups(query: GroupQuery = {}): Promise<Page<Group>> {
return getJson<Page<Group>>(
'/groups',
clean({
channel: query.channel === 'all' ? undefined : query.channel,
owner_account_id: query.owner_account_id,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
export function getGroupMembers(roomId: string): Promise<GroupMember[]> {
// room_id can contain '@' — axios encodes params, but path segments need
// explicit encoding so the FastAPI `{room_id}` path param matches.
return getJson<GroupMember[]>(
`/groups/${encodeURIComponent(roomId)}/members`,
)
}
// ---------------------------------------------------------------------------
// Phone / voice calls
// ---------------------------------------------------------------------------
export interface CallQuery {
/** call-type filter; `'all'` (default) drops the param. */
channel?: CallChannel | 'all'
user_id?: string
/** ms-epoch inclusive window compared against start_call_time. */
start?: number
end?: number
page?: number
page_size?: number
}
export function getCalls(query: CallQuery = {}): Promise<Page<PhoneCall>> {
return getJson<Page<PhoneCall>>(
'/calls',
clean({
channel: query.channel === 'all' ? undefined : query.channel,
user_id: query.user_id,
start: query.start,
end: query.end,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
// ---------------------------------------------------------------------------
// SMS
// ---------------------------------------------------------------------------
export interface SmsQuery {
user_id?: string
/** ms-epoch inclusive window compared against create_time. */
start?: number
end?: number
page?: number
page_size?: number
}
export function getSms(query: SmsQuery = {}): Promise<Page<Sms>> {
return getJson<Page<Sms>>(
'/sms',
clean({
user_id: query.user_id,
start: query.start,
end: query.end,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
// ---------------------------------------------------------------------------
// Sync monitor
// ---------------------------------------------------------------------------
export function getSyncStatus(): Promise<SyncStatus> {
return getJson<SyncStatus>('/sync/status')
}
// ---------------------------------------------------------------------------
// Full-text search (messages + customers)
// ---------------------------------------------------------------------------
export type SearchType = 'messages' | 'customers' | 'all'
export interface SearchQuery {
q: string
/** which section(s) to query; defaults to `all`. */
type?: SearchType
channel?: Channel | 'all'
/** ms-epoch inclusive window compared against message.timestamp. */
start?: number
end?: number
page?: number
page_size?: number
}
/**
* Combined search payload returned by `/api/search`. Each requested section is
* present; unrequested sections are omitted entirely by the backend (so the
* caller can branch on `section in payload`).
*/
export interface SearchPayload {
messages?: Page<Message>
customers?: Page<Customer>
}
export function search(query: SearchQuery): Promise<SearchPayload> {
return getJson<SearchPayload>(
'/search',
clean({
q: query.q,
type: query.type ?? 'all',
channel: query.channel === 'all' ? undefined : query.channel,
start: query.start,
end: query.end,
page: query.page ?? 1,
page_size: query.page_size ?? 20,
}),
)
}
export type {
Account,
CallChannel,
Channel,
Customer,
Department,
Group,
GroupMember,
Message,
Page,
PhoneCall,
Sms,
Staff,
SyncRun,
SyncState,
SyncStatus,
}