@ikatec/digisac-api-sdk 4.5.1 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/apis/answers/AnswersApi.cjs +38 -0
- package/dist/apis/answers/AnswersApi.cjs.map +1 -1
- package/dist/apis/answers/AnswersApi.d.ts +33 -2
- package/dist/apis/answers/AnswersApi.d.ts.map +1 -1
- package/dist/apis/answers/AnswersApi.mjs +38 -0
- package/dist/apis/answers/AnswersApi.mjs.map +1 -1
- package/dist/apis/answers/types.d.ts +64 -1
- package/dist/apis/answers/types.d.ts.map +1 -1
- package/dist/apis/botSimulators/BotSimulatorsApi.cjs +42 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.cjs.map +1 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.d.ts +31 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.d.ts.map +1 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.mjs +41 -0
- package/dist/apis/botSimulators/BotSimulatorsApi.mjs.map +1 -0
- package/dist/apis/botSimulators/index.cjs +4 -0
- package/dist/apis/botSimulators/index.d.ts +3 -0
- package/dist/apis/botSimulators/index.d.ts.map +1 -0
- package/dist/apis/botSimulators/index.mjs +3 -0
- package/dist/apis/botSimulators/types.cjs +0 -0
- package/dist/apis/botSimulators/types.d.ts +73 -0
- package/dist/apis/botSimulators/types.d.ts.map +1 -0
- package/dist/apis/botSimulators/types.mjs +0 -0
- package/dist/apis/botVersions/BotVersionsApi.cjs +25 -0
- package/dist/apis/botVersions/BotVersionsApi.cjs.map +1 -1
- package/dist/apis/botVersions/BotVersionsApi.d.ts +18 -0
- package/dist/apis/botVersions/BotVersionsApi.d.ts.map +1 -1
- package/dist/apis/botVersions/BotVersionsApi.mjs +25 -0
- package/dist/apis/botVersions/BotVersionsApi.mjs.map +1 -1
- package/dist/apis/botVersions/types.d.ts +4 -0
- package/dist/apis/botVersions/types.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.cjs +99 -4
- package/dist/apis/bots/BotsApi.cjs.map +1 -1
- package/dist/apis/bots/BotsApi.d.ts +78 -5
- package/dist/apis/bots/BotsApi.d.ts.map +1 -1
- package/dist/apis/bots/BotsApi.mjs +99 -4
- package/dist/apis/bots/BotsApi.mjs.map +1 -1
- package/dist/apis/bots/types.d.ts +266 -16
- package/dist/apis/bots/types.d.ts.map +1 -1
- package/dist/apis/index.cjs +3 -0
- package/dist/apis/index.d.ts +1 -0
- package/dist/apis/index.d.ts.map +1 -1
- package/dist/apis/index.mjs +3 -1
- package/dist/apis/questions/QuestionsApi.cjs +15 -0
- package/dist/apis/questions/QuestionsApi.cjs.map +1 -1
- package/dist/apis/questions/QuestionsApi.d.ts +15 -1
- package/dist/apis/questions/QuestionsApi.d.ts.map +1 -1
- package/dist/apis/questions/QuestionsApi.mjs +15 -0
- package/dist/apis/questions/QuestionsApi.mjs.map +1 -1
- package/dist/apis/questions/types.d.ts +25 -5
- package/dist/apis/questions/types.d.ts.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.cjs +16 -0
- package/dist/apis/quickReplies/QuickRepliesApi.cjs.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.d.ts +15 -2
- package/dist/apis/quickReplies/QuickRepliesApi.d.ts.map +1 -1
- package/dist/apis/quickReplies/QuickRepliesApi.mjs +16 -0
- package/dist/apis/quickReplies/QuickRepliesApi.mjs.map +1 -1
- package/dist/apis/quickReplies/types.d.ts +30 -2
- package/dist/apis/quickReplies/types.d.ts.map +1 -1
- package/dist/apis/schedule/ScheduleApi.cjs +37 -2
- package/dist/apis/schedule/ScheduleApi.cjs.map +1 -1
- package/dist/apis/schedule/ScheduleApi.d.ts +30 -3
- package/dist/apis/schedule/ScheduleApi.d.ts.map +1 -1
- package/dist/apis/schedule/ScheduleApi.mjs +37 -2
- package/dist/apis/schedule/ScheduleApi.mjs.map +1 -1
- package/dist/apis/schedule/types.d.ts +58 -11
- package/dist/apis/schedule/types.d.ts.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.cjs +25 -2
- package/dist/apis/ticketTopics/TicketTopicsApi.cjs.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.d.ts +18 -4
- package/dist/apis/ticketTopics/TicketTopicsApi.d.ts.map +1 -1
- package/dist/apis/ticketTopics/TicketTopicsApi.mjs +25 -2
- package/dist/apis/ticketTopics/TicketTopicsApi.mjs.map +1 -1
- package/dist/apis/ticketTopics/types.d.ts +22 -1
- package/dist/apis/ticketTopics/types.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.cjs +5 -2
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +7 -4
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs +5 -2
- package/dist/core/BaseCrudApi.mjs.map +1 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/incommingWebhooks/index.d.ts +7 -7
- package/dist/incommingWebhooks/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/apis/answers/AnswersApi.test.ts +67 -0
- package/src/apis/answers/AnswersApi.ts +66 -2
- package/src/apis/answers/types.ts +64 -2
- package/src/apis/botSimulators/BotSimulatorsApi.test.ts +58 -0
- package/src/apis/botSimulators/BotSimulatorsApi.ts +57 -0
- package/src/apis/botSimulators/index.ts +2 -0
- package/src/apis/botSimulators/types.ts +72 -0
- package/src/apis/botVersions/BotVersionsApi.test.ts +51 -0
- package/src/apis/botVersions/BotVersionsApi.ts +41 -0
- package/src/apis/botVersions/types.ts +4 -0
- package/src/apis/bots/BotsApi.test.ts +177 -0
- package/src/apis/bots/BotsApi.ts +162 -6
- package/src/apis/bots/types.ts +267 -15
- package/src/apis/index.ts +1 -0
- package/src/apis/payloads.test-d.ts +148 -0
- package/src/apis/questions/QuestionsApi.test.ts +49 -0
- package/src/apis/questions/QuestionsApi.ts +20 -1
- package/src/apis/questions/types.ts +25 -6
- package/src/apis/quickReplies/QuickRepliesApi.test.ts +69 -0
- package/src/apis/quickReplies/QuickRepliesApi.ts +35 -2
- package/src/apis/quickReplies/types.ts +34 -2
- package/src/apis/schedule/ScheduleApi.test.ts +80 -0
- package/src/apis/schedule/ScheduleApi.ts +59 -6
- package/src/apis/schedule/types.ts +66 -11
- package/src/apis/ticketTopics/TicketTopicsApi.test.ts +40 -0
- package/src/apis/ticketTopics/TicketTopicsApi.ts +35 -4
- package/src/apis/ticketTopics/types.ts +26 -1
- package/src/core/BaseCrudApi.ts +9 -5
- package/src/core/types.ts +2 -1
- package/src/incommingWebhooks/index.test-d.ts +9 -0
- package/src/incommingWebhooks/index.ts +7 -7
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
3
|
+
import { QuickRepliesApi } from './QuickRepliesApi'
|
|
4
|
+
import type { QuickRepliesLoadOnDemandQuery } from './types'
|
|
5
|
+
|
|
6
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
function createMockClient(): ApiClient {
|
|
9
|
+
return {
|
|
10
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
11
|
+
request: vi.fn(),
|
|
12
|
+
get: vi.fn(),
|
|
13
|
+
post: vi.fn(),
|
|
14
|
+
put: vi.fn(),
|
|
15
|
+
patch: vi.fn(),
|
|
16
|
+
delete: vi.fn(),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const headers = { 'X-Custom': 'value' }
|
|
21
|
+
|
|
22
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
describe('QuickRepliesApi', () => {
|
|
25
|
+
let client: ReturnType<typeof createMockClient>
|
|
26
|
+
let api: QuickRepliesApi
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
client = createMockClient()
|
|
30
|
+
api = new QuickRepliesApi(client)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('loadOnDemand calls client.get with /quick-replies/load-on-demand and the query', async () => {
|
|
34
|
+
const query: QuickRepliesLoadOnDemandQuery = { order: [['title', 'ASC']], limit: 20, offset: 0 }
|
|
35
|
+
vi.mocked(client.get).mockResolvedValue({ data: [], total: 0 })
|
|
36
|
+
expect(await api.loadOnDemand(query, headers)).toEqual({
|
|
37
|
+
data: [],
|
|
38
|
+
total: 0,
|
|
39
|
+
})
|
|
40
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
41
|
+
`/quick-replies/load-on-demand?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
42
|
+
headers,
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('create sends the departments, categories and files', async () => {
|
|
47
|
+
const body = {
|
|
48
|
+
title: 'Saudação',
|
|
49
|
+
text: 'Olá!',
|
|
50
|
+
departmentIds: ['d1'],
|
|
51
|
+
categoryIds: ['c1'],
|
|
52
|
+
files: [{ id: 'f1' }],
|
|
53
|
+
}
|
|
54
|
+
await api.create(body)
|
|
55
|
+
expect(client.post).toHaveBeenCalledWith('/quick-replies', body, undefined)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('updateById sends the whole reply', async () => {
|
|
59
|
+
const body = { title: 'Saudação', text: 'Oi!', files: [] }
|
|
60
|
+
await api.updateById('q1', body, headers)
|
|
61
|
+
expect(client.put).toHaveBeenCalledWith('/quick-replies/q1', body, headers)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('deleteById resolves to the OK of the route', async () => {
|
|
65
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
66
|
+
expect(await api.deleteById('q1')).toBe('OK')
|
|
67
|
+
expect(client.delete).toHaveBeenCalledWith('/quick-replies/q1', undefined)
|
|
68
|
+
})
|
|
69
|
+
})
|
|
@@ -1,13 +1,46 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type { ListQuery } from '../../core/types'
|
|
4
|
+
import type {
|
|
5
|
+
QuickReply,
|
|
6
|
+
QuickRepliesLoadOnDemandQuery,
|
|
7
|
+
QuickRepliesLoadOnDemandResult,
|
|
8
|
+
CreateQuickReplyPayload,
|
|
9
|
+
UpdateQuickReplyPayload,
|
|
10
|
+
} from './types'
|
|
4
11
|
|
|
5
12
|
export class QuickRepliesApi extends BaseCrudApi<
|
|
6
13
|
QuickReply,
|
|
7
14
|
CreateQuickReplyPayload,
|
|
8
|
-
UpdateQuickReplyPayload
|
|
15
|
+
UpdateQuickReplyPayload,
|
|
16
|
+
ListQuery<QuickReply>,
|
|
17
|
+
'OK'
|
|
9
18
|
> {
|
|
10
19
|
constructor(client: ApiClient) {
|
|
11
20
|
super(client, '/quick-replies')
|
|
12
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Deletes a quick reply (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
25
|
+
* @permissions quickReplies.view OR quickReplies.destroy
|
|
26
|
+
*/
|
|
27
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
28
|
+
return super.deleteById(id, headers)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Lists the replies the user can use (no department, or one of the departments of the user),
|
|
33
|
+
* the way the chat loads them: `limit`/`offset` instead of pages, and `{ data, total }`
|
|
34
|
+
* instead of the pagination envelope.
|
|
35
|
+
* @permissions quickReplies.view
|
|
36
|
+
*/
|
|
37
|
+
loadOnDemand(
|
|
38
|
+
query?: QuickRepliesLoadOnDemandQuery,
|
|
39
|
+
headers?: Record<string, string>,
|
|
40
|
+
): Promise<QuickRepliesLoadOnDemandResult> {
|
|
41
|
+
return this.client.get<QuickRepliesLoadOnDemandResult>(
|
|
42
|
+
`${this.basePath}/load-on-demand${this.toQueryString(query)}`,
|
|
43
|
+
headers,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
13
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ListQuery } from '../../core/types'
|
|
1
2
|
import type { Category } from '../categories/types'
|
|
2
3
|
import type { Department } from '../departments/types'
|
|
3
4
|
import type { File } from '../files/types'
|
|
@@ -11,6 +12,7 @@ export type QuickReply = {
|
|
|
11
12
|
updatedAt: string
|
|
12
13
|
deletedAt: string | null
|
|
13
14
|
// relationships
|
|
15
|
+
/** Empty = all departments. */
|
|
14
16
|
departments?: Department[]
|
|
15
17
|
categories?: Category[]
|
|
16
18
|
files?: File[]
|
|
@@ -18,12 +20,42 @@ export type QuickReply = {
|
|
|
18
20
|
|
|
19
21
|
export type QuickReplyRelationships = 'departments' | 'categories' | 'files'
|
|
20
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Files already uploaded through `FilesApi` with `attachedType: 'quickreply.file'` — uploaded
|
|
25
|
+
* with another type they get the `attachedId` but never show in `files`.
|
|
26
|
+
*/
|
|
27
|
+
export type QuickReplyFilePayload = { id: string }
|
|
28
|
+
|
|
29
|
+
/** `title` and `text` are each unique in the account (409, unless the account allows duplicate names). */
|
|
21
30
|
export type CreateQuickReplyPayload = {
|
|
22
31
|
title: string
|
|
23
32
|
text: string
|
|
33
|
+
/** Department ids; omitted = all departments. */
|
|
34
|
+
departmentIds?: string[]
|
|
35
|
+
categoryIds?: string[]
|
|
36
|
+
files?: QuickReplyFilePayload[]
|
|
24
37
|
}
|
|
25
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Not partial: `title` and `text` are required, the departments and categories are replaced
|
|
41
|
+
* (omitted = none), and so are the files.
|
|
42
|
+
*/
|
|
26
43
|
export type UpdateQuickReplyPayload = {
|
|
27
|
-
title
|
|
28
|
-
text
|
|
44
|
+
title: string
|
|
45
|
+
text: string
|
|
46
|
+
departmentIds?: string[]
|
|
47
|
+
categoryIds?: string[]
|
|
48
|
+
/** The files to keep or attach; `[]` removes all. Required: omitted, the update fails (500). */
|
|
49
|
+
files: QuickReplyFilePayload[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The query of `loadOnDemand`, passed to the database as is. */
|
|
53
|
+
export type QuickRepliesLoadOnDemandQuery = Pick<
|
|
54
|
+
ListQuery<QuickReply>,
|
|
55
|
+
'where' | 'include' | 'order' | 'attributes' | 'limit' | 'offset'
|
|
56
|
+
>
|
|
57
|
+
|
|
58
|
+
export type QuickRepliesLoadOnDemandResult = {
|
|
59
|
+
data: QuickReply[]
|
|
60
|
+
total: number
|
|
29
61
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
3
|
+
import { ScheduleApi } from './ScheduleApi'
|
|
4
|
+
|
|
5
|
+
// ── Mock client factory ──────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
function createMockClient(): ApiClient {
|
|
8
|
+
return {
|
|
9
|
+
setAccessToken: vi.fn().mockReturnThis(),
|
|
10
|
+
request: vi.fn(),
|
|
11
|
+
get: vi.fn(),
|
|
12
|
+
post: vi.fn(),
|
|
13
|
+
put: vi.fn(),
|
|
14
|
+
patch: vi.fn(),
|
|
15
|
+
delete: vi.fn(),
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const headers = { 'X-Custom': 'value' }
|
|
20
|
+
const body = {
|
|
21
|
+
scheduledAt: '2026-10-02T12:00:00.000Z',
|
|
22
|
+
contactId: 'c1',
|
|
23
|
+
departmentId: 'd1',
|
|
24
|
+
message: 'Lembrete',
|
|
25
|
+
notes: '',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
describe('ScheduleApi', () => {
|
|
31
|
+
let client: ReturnType<typeof createMockClient>
|
|
32
|
+
let api: ScheduleApi
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
client = createMockClient()
|
|
36
|
+
api = new ScheduleApi(client)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('create calls client.post with /schedule and resolves with no content', async () => {
|
|
40
|
+
vi.mocked(client.post).mockResolvedValue(undefined)
|
|
41
|
+
expect(await api.create(body, headers)).toBeUndefined()
|
|
42
|
+
expect(client.post).toHaveBeenCalledWith('/schedule', body, headers)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('updateById calls client.put with /schedule/:id', async () => {
|
|
46
|
+
await api.updateById('s1', { ...body, files: [{ id: 'f1' }] })
|
|
47
|
+
expect(client.put).toHaveBeenCalledWith(
|
|
48
|
+
'/schedule/s1',
|
|
49
|
+
{ ...body, files: [{ id: 'f1' }] },
|
|
50
|
+
undefined,
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('deleteById calls client.delete with /schedule/:id', async () => {
|
|
55
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
56
|
+
expect(await api.deleteById('s1', headers)).toBe('OK')
|
|
57
|
+
expect(client.delete).toHaveBeenCalledWith('/schedule/s1', headers)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('deleteMany sends the ids in where.id of a DELETE body', async () => {
|
|
61
|
+
vi.mocked(client.request).mockResolvedValue('OK')
|
|
62
|
+
expect(await api.deleteMany(['s1', 's2'], headers)).toBe('OK')
|
|
63
|
+
expect(client.request).toHaveBeenCalledWith(
|
|
64
|
+
'DELETE',
|
|
65
|
+
'/schedule/many',
|
|
66
|
+
{ where: { id: ['s1', 's2'] } },
|
|
67
|
+
headers,
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('reads through the inherited getMany', async () => {
|
|
72
|
+
await api.getMany({ where: { contactId: 'c1' }, paginate: false })
|
|
73
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
74
|
+
`/schedule?query=${encodeURIComponent(
|
|
75
|
+
JSON.stringify({ where: { contactId: 'c1' }, paginate: false }),
|
|
76
|
+
)}`,
|
|
77
|
+
undefined,
|
|
78
|
+
)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -1,13 +1,66 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
export class ScheduleApi extends BaseCrudApi<
|
|
2
|
+
import { BaseReadApi } from '../../core/BaseReadApi'
|
|
3
|
+
import type {
|
|
6
4
|
Schedule,
|
|
5
|
+
ScheduleRecord,
|
|
7
6
|
CreateSchedulePayload,
|
|
8
|
-
UpdateSchedulePayload
|
|
9
|
-
|
|
7
|
+
UpdateSchedulePayload,
|
|
8
|
+
} from './types'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Scheduled messages. Create answers no body and update answers the stored record, so this
|
|
12
|
+
* class extends `BaseReadApi` and writes its own create/update/delete.
|
|
13
|
+
*/
|
|
14
|
+
export class ScheduleApi extends BaseReadApi<Schedule> {
|
|
10
15
|
constructor(client: ApiClient) {
|
|
11
16
|
super(client, '/schedule')
|
|
12
17
|
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a schedule. Resolves with NO CONTENT: find it back with `getOne` (e.g. by
|
|
21
|
+
* `contactId` and `scheduledAt`).
|
|
22
|
+
* @permissions schedule.create
|
|
23
|
+
*/
|
|
24
|
+
create(body: CreateSchedulePayload, headers?: Record<string, string>): Promise<void> {
|
|
25
|
+
return this.client.post<void, CreateSchedulePayload>(this.basePath, body, headers)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Updates a schedule. Resolves to the stored record, without relationships. An unknown id
|
|
30
|
+
* answers 500.
|
|
31
|
+
* @permissions schedule.view OR schedule.update
|
|
32
|
+
*/
|
|
33
|
+
updateById(
|
|
34
|
+
id: string,
|
|
35
|
+
body: UpdateSchedulePayload,
|
|
36
|
+
headers?: Record<string, string>,
|
|
37
|
+
): Promise<ScheduleRecord> {
|
|
38
|
+
return this.client.put<ScheduleRecord, UpdateSchedulePayload>(
|
|
39
|
+
`${this.basePath}/${id}`,
|
|
40
|
+
body,
|
|
41
|
+
headers,
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Deletes a schedule (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
47
|
+
* @permissions schedule.view OR schedule.destroy
|
|
48
|
+
*/
|
|
49
|
+
deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
50
|
+
return this.client.delete<'OK'>(`${this.basePath}/${id}`, headers)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deletes several schedules of the account. Ids that are not UUIDs, or of another account, are
|
|
55
|
+
* skipped in silence; always resolves to `'OK'`.
|
|
56
|
+
* @permissions schedule.destroy
|
|
57
|
+
*/
|
|
58
|
+
deleteMany(ids: string[], headers?: Record<string, string>): Promise<'OK'> {
|
|
59
|
+
return this.client.request<'OK', { where: { id: string[] } }>(
|
|
60
|
+
'DELETE',
|
|
61
|
+
`${this.basePath}/many`,
|
|
62
|
+
{ where: { id: ids } },
|
|
63
|
+
headers,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
13
66
|
}
|
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import type { Contact } from '../contacts/types'
|
|
2
2
|
import type { Department } from '../departments/types'
|
|
3
|
+
import type { File } from '../files/types'
|
|
3
4
|
import type { User } from '../users/types'
|
|
4
5
|
|
|
6
|
+
/** `scheduled` until the worker runs it; `error` and `not_sent` come with the `reason`. */
|
|
7
|
+
export type ScheduleStatus = 'scheduled' | 'done' | 'error' | 'not_sent'
|
|
8
|
+
|
|
5
9
|
export type Schedule = {
|
|
6
10
|
id: string
|
|
7
|
-
status:
|
|
11
|
+
status: ScheduleStatus
|
|
12
|
+
/**
|
|
13
|
+
* Why it was not sent: `ALREADY_TICKET_OPENED`, `DDD_BLOCKED`, `SERVICE_IS_NOT_ENABLED`,
|
|
14
|
+
* `CONTACT_IS_BLOCKED`, `CONTACT_NOT_EXISTS`, `HSM_LIMIT_EXCEEDED`, `HSM_IS_BLOCKED`,
|
|
15
|
+
* `SERVICE_IS_BLOCKED`, `INVALID_PAYLOAD`, a WhatsApp Business error code or
|
|
16
|
+
* `SCHEDULE_UNKNOWN_REASON`.
|
|
17
|
+
*/
|
|
8
18
|
reason: string | null
|
|
9
|
-
message
|
|
19
|
+
/** For a WhatsApp Business template (`hsmId`), the message the server built from it. */
|
|
20
|
+
message: string | null
|
|
10
21
|
notes: string
|
|
11
|
-
openTicket: boolean
|
|
12
|
-
notificateUser: boolean
|
|
22
|
+
openTicket: boolean | null
|
|
23
|
+
notificateUser: boolean | null
|
|
24
|
+
/** Sends the message even when the contact already has an open ticket. */
|
|
25
|
+
sendWithOpenTicket: boolean | null
|
|
13
26
|
extraOptions: Record<string, unknown> | null
|
|
14
27
|
hsmId: string | null
|
|
15
28
|
hsmFileId: string | null
|
|
@@ -20,39 +33,81 @@ export type Schedule = {
|
|
|
20
33
|
accountId: string
|
|
21
34
|
createdAt: string
|
|
22
35
|
updatedAt: string
|
|
23
|
-
deletedAt: string | null
|
|
24
36
|
// relationships
|
|
25
37
|
contact?: Contact
|
|
26
38
|
department?: Department
|
|
27
39
|
user?: User | null
|
|
40
|
+
files?: File[]
|
|
41
|
+
/** The header media of the WhatsApp Business template. */
|
|
42
|
+
hsmFile?: File
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The schedule as stored, without relationships: what `updateById` resolves to. */
|
|
46
|
+
export type ScheduleRecord = Omit<
|
|
47
|
+
Schedule,
|
|
48
|
+
'contact' | 'department' | 'user' | 'files' | 'hsmFile'
|
|
49
|
+
> & {
|
|
50
|
+
deletedAt: string | null
|
|
28
51
|
}
|
|
29
52
|
|
|
30
|
-
export type ScheduleRelationships =
|
|
53
|
+
export type ScheduleRelationships =
|
|
54
|
+
| 'contact'
|
|
55
|
+
| 'department'
|
|
56
|
+
| 'user'
|
|
57
|
+
| 'files'
|
|
58
|
+
| 'hsmFile'
|
|
59
|
+
| 'contact.service'
|
|
60
|
+
| 'contact.person'
|
|
61
|
+
|
|
62
|
+
/** A new attachment. */
|
|
63
|
+
export type ScheduleNewFilePayload = { base64: string; name: string; mimetype: string }
|
|
31
64
|
|
|
65
|
+
/** On update: a new attachment, or one to keep (`id`). */
|
|
66
|
+
export type ScheduleFilePayload = ScheduleNewFilePayload | { id: string }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Another schedule of the connection less than 5 minutes apart answers 402
|
|
70
|
+
* (`Schedule time is already exist.`). A contact of another account answers 500.
|
|
71
|
+
*/
|
|
32
72
|
export type CreateSchedulePayload = {
|
|
33
73
|
scheduledAt: string
|
|
34
74
|
contactId: string
|
|
35
75
|
departmentId: string
|
|
36
|
-
|
|
76
|
+
/** Up to 1024 characters. Optional for a WhatsApp Business template, built from `hsmId`. */
|
|
77
|
+
message?: string
|
|
78
|
+
/**
|
|
79
|
+
* Up to 1024 characters. Required by the route only with `openTicket: true`, but the column is
|
|
80
|
+
* NOT NULL: send it always (an empty string is fine).
|
|
81
|
+
*/
|
|
37
82
|
notes: string
|
|
38
|
-
openTicket
|
|
83
|
+
openTicket?: boolean
|
|
39
84
|
notificateUser?: boolean
|
|
85
|
+
sendWithOpenTicket?: boolean
|
|
40
86
|
userId?: string
|
|
41
87
|
hsmId?: string
|
|
42
88
|
hsmFileId?: string
|
|
43
89
|
extraOptions?: Record<string, unknown>
|
|
90
|
+
/** New attachments only: the create drops `{ id }` items. */
|
|
91
|
+
files?: ScheduleNewFilePayload[]
|
|
44
92
|
}
|
|
45
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Not partial: `scheduledAt`, `contactId` and `departmentId` are required on update too. The
|
|
96
|
+
* 5-minute conflict (402) is checked against the whole account.
|
|
97
|
+
*/
|
|
46
98
|
export type UpdateSchedulePayload = {
|
|
47
|
-
scheduledAt
|
|
48
|
-
contactId
|
|
49
|
-
departmentId
|
|
99
|
+
scheduledAt: string
|
|
100
|
+
contactId: string
|
|
101
|
+
departmentId: string
|
|
50
102
|
userId?: string | null
|
|
51
103
|
message?: string
|
|
52
104
|
notes?: string
|
|
53
105
|
openTicket?: boolean
|
|
54
106
|
notificateUser?: boolean
|
|
107
|
+
sendWithOpenTicket?: boolean
|
|
55
108
|
hsmId?: string | null
|
|
56
109
|
hsmFileId?: string | null
|
|
57
110
|
extraOptions?: Record<string, unknown> | null
|
|
111
|
+
/** Replaces the attachments: `{ id }` keeps one, `{ base64, ... }` adds one; omitted or `[]` removes all. */
|
|
112
|
+
files?: ScheduleFilePayload[]
|
|
58
113
|
}
|
|
@@ -55,4 +55,44 @@ describe('TicketTopicsApi', () => {
|
|
|
55
55
|
await unarchive('t1')
|
|
56
56
|
expect(client.post).toHaveBeenCalledTimes(2)
|
|
57
57
|
})
|
|
58
|
+
|
|
59
|
+
it('create sends the department ids', async () => {
|
|
60
|
+
await api.create({ name: 'Dúvida', departments: ['d1'] })
|
|
61
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
62
|
+
'/ticket-topics',
|
|
63
|
+
{ name: 'Dúvida', departments: ['d1'] },
|
|
64
|
+
undefined,
|
|
65
|
+
)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('getMany sends the department filters at the top of the query', async () => {
|
|
69
|
+
const query = { departmentIds: ['d1'], includeNoDepartment: true }
|
|
70
|
+
await api.getMany(query)
|
|
71
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
72
|
+
`/ticket-topics?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
73
|
+
undefined,
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('deleteById resolves to the OK of the route', async () => {
|
|
78
|
+
vi.mocked(client.delete).mockResolvedValue('OK')
|
|
79
|
+
expect(await api.deleteById('t1')).toBe('OK')
|
|
80
|
+
expect(client.delete).toHaveBeenCalledWith('/ticket-topics/t1', undefined)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('getOne reads one item of the paginated list, where the department filters apply', async () => {
|
|
84
|
+
vi.mocked(client.get).mockResolvedValue({ data: [{ id: 't1' }], total: 1 })
|
|
85
|
+
expect(await api.getOne({ departmentIds: ['d1'] })).toEqual({ id: 't1' })
|
|
86
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
87
|
+
`/ticket-topics?query=${encodeURIComponent(
|
|
88
|
+
JSON.stringify({ departmentIds: ['d1'], paginate: true, perPage: 1, page: 1 }),
|
|
89
|
+
)}`,
|
|
90
|
+
undefined,
|
|
91
|
+
)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('getOne resolves null on an empty page', async () => {
|
|
95
|
+
vi.mocked(client.get).mockResolvedValue({ data: [], total: 0 })
|
|
96
|
+
expect(await api.getOne()).toBeNull()
|
|
97
|
+
})
|
|
58
98
|
})
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
TicketTopic,
|
|
5
|
+
TicketTopicListQuery,
|
|
6
|
+
CreateTicketTopicPayload,
|
|
7
|
+
UpdateTicketTopicPayload,
|
|
8
|
+
} from './types'
|
|
4
9
|
|
|
5
10
|
export class TicketTopicsApi extends BaseCrudApi<
|
|
6
11
|
TicketTopic,
|
|
7
12
|
CreateTicketTopicPayload,
|
|
8
|
-
UpdateTicketTopicPayload
|
|
13
|
+
UpdateTicketTopicPayload,
|
|
14
|
+
TicketTopicListQuery,
|
|
15
|
+
'OK'
|
|
9
16
|
> {
|
|
10
17
|
constructor(client: ApiClient) {
|
|
11
18
|
super(client, '/ticket-topics')
|
|
@@ -13,10 +20,34 @@ export class TicketTopicsApi extends BaseCrudApi<
|
|
|
13
20
|
this.unarchive = this.unarchive.bind(this)
|
|
14
21
|
}
|
|
15
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Returns the first topic matching the query, or `null` when none match. Reads one item of the
|
|
25
|
+
* paginated list (instead of `paginate: false`), the only path where `departmentIds` and
|
|
26
|
+
* `includeNoDepartment` apply.
|
|
27
|
+
* @permissions ticketTopics.view
|
|
28
|
+
*/
|
|
29
|
+
override async getOne(
|
|
30
|
+
query?: TicketTopicListQuery,
|
|
31
|
+
headers?: Record<string, string>,
|
|
32
|
+
): Promise<TicketTopic | null> {
|
|
33
|
+
const page = await this.getMany({ ...query, paginate: true, perPage: 1, page: 1 }, headers)
|
|
34
|
+
return page?.data?.[0] ?? null
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Deletes a ticket topic (soft delete). Resolves to `'OK'`; an unknown id answers 500.
|
|
39
|
+
* @permissions ticketTopics.view OR ticketTopics.destroy
|
|
40
|
+
*/
|
|
41
|
+
override deleteById(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
42
|
+
return super.deleteById(id, headers)
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
/**
|
|
17
46
|
* Archives a ticket topic. Unlike most resources, ticket-topics archive through a
|
|
18
47
|
* dedicated endpoint (POST `${basePath}/${id}/archive`) rather than `updateById`.
|
|
19
|
-
*
|
|
48
|
+
* Resolves to the stored topic, without `departments`. A topic used in a bot answers 403;
|
|
49
|
+
* an unknown id answers 500.
|
|
50
|
+
* @permissions ticketTopics.archive
|
|
20
51
|
*/
|
|
21
52
|
archive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
|
|
22
53
|
return this.client.post<TicketTopic>(
|
|
@@ -28,7 +59,7 @@ export class TicketTopicsApi extends BaseCrudApi<
|
|
|
28
59
|
|
|
29
60
|
/**
|
|
30
61
|
* Restores a previously archived ticket topic via the same dedicated endpoint.
|
|
31
|
-
* @permissions
|
|
62
|
+
* @permissions ticketTopics.archive
|
|
32
63
|
*/
|
|
33
64
|
unarchive(id: string, headers?: Record<string, string>): Promise<TicketTopic> {
|
|
34
65
|
return this.client.post<TicketTopic>(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { ListQuery } from '../../core/types'
|
|
2
|
+
import type { Department } from '../departments/types'
|
|
3
|
+
|
|
1
4
|
export type TicketTopic = {
|
|
2
5
|
id: string
|
|
3
6
|
name: string
|
|
@@ -6,12 +9,34 @@ export type TicketTopic = {
|
|
|
6
9
|
createdAt: string
|
|
7
10
|
updatedAt: string
|
|
8
11
|
deletedAt: string | null
|
|
12
|
+
// relationships
|
|
13
|
+
/** Always comes in the paginated `getMany`; elsewhere only with `include`. Empty = all departments. */
|
|
14
|
+
departments?: Department[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type TicketTopicRelationships = 'departments'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Department filters of the paginated `getMany` and of `getOne`. `getMany({ paginate: false })`
|
|
21
|
+
* ignores them in silence (the route reads them only on the paginated path).
|
|
22
|
+
*/
|
|
23
|
+
export type TicketTopicListQuery = ListQuery<TicketTopic> & {
|
|
24
|
+
/** Topics linked to any of these departments. */
|
|
25
|
+
departmentIds?: string | string[]
|
|
26
|
+
/** Also (or only) the topics linked to no department. Only with the default `'json'` query format. */
|
|
27
|
+
includeNoDepartment?: boolean
|
|
9
28
|
}
|
|
10
29
|
|
|
11
30
|
export type CreateTicketTopicPayload = {
|
|
31
|
+
/** Unique in the account (409, unless the account allows duplicate names); trimmed. */
|
|
12
32
|
name: string
|
|
33
|
+
/** Department ids; `[]` = all departments. Required: a missing list answers 500 after creating the topic. */
|
|
34
|
+
departments: string[]
|
|
13
35
|
}
|
|
14
36
|
|
|
15
37
|
export type UpdateTicketTopicPayload = {
|
|
16
|
-
|
|
38
|
+
/** Required on update too. */
|
|
39
|
+
name: string
|
|
40
|
+
/** Department ids, replacing the current ones; omitted keeps them. */
|
|
41
|
+
departments?: string[]
|
|
17
42
|
}
|
package/src/core/BaseCrudApi.ts
CHANGED
|
@@ -14,15 +14,19 @@ export type {
|
|
|
14
14
|
CrudApi,
|
|
15
15
|
} from './types'
|
|
16
16
|
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* Generic CRUD resource: the reads of `BaseReadApi` plus `create`, `updateById` and `deleteById`.
|
|
19
|
+
* `TDeleteResponse` is what `deleteById` resolves to (most routes answer the text `'OK'`).
|
|
20
|
+
*/
|
|
18
21
|
export class BaseCrudApi<
|
|
19
22
|
TResponse,
|
|
20
23
|
TCreate,
|
|
21
24
|
TUpdate = TCreate,
|
|
22
25
|
TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
|
|
26
|
+
TDeleteResponse = TResponse,
|
|
23
27
|
>
|
|
24
28
|
extends BaseReadApi<TResponse, TQuery>
|
|
25
|
-
implements CrudApi<TResponse, TCreate, TUpdate, TQuery>
|
|
29
|
+
implements CrudApi<TResponse, TCreate, TUpdate, TQuery, TDeleteResponse>
|
|
26
30
|
{
|
|
27
31
|
/**
|
|
28
32
|
* Creates a new resource.
|
|
@@ -42,9 +46,9 @@ export class BaseCrudApi<
|
|
|
42
46
|
|
|
43
47
|
/**
|
|
44
48
|
* Deletes a resource by ID.
|
|
45
|
-
* @permissions $resourceName.
|
|
49
|
+
* @permissions $resourceName.destroy
|
|
46
50
|
*/
|
|
47
|
-
deleteById(id: string, headers?: Record<string, string>): Promise<
|
|
48
|
-
return this.client.delete<
|
|
51
|
+
deleteById(id: string, headers?: Record<string, string>): Promise<TDeleteResponse> {
|
|
52
|
+
return this.client.delete<TDeleteResponse>(`${this.basePath}/${id}`, headers)
|
|
49
53
|
}
|
|
50
54
|
}
|
package/src/core/types.ts
CHANGED
|
@@ -159,8 +159,9 @@ export type CrudApi<
|
|
|
159
159
|
TCreate,
|
|
160
160
|
TUpdate = TCreate,
|
|
161
161
|
TQuery extends ListQuery<TResponse> = ListQuery<TResponse>,
|
|
162
|
+
TDeleteResponse = TResponse,
|
|
162
163
|
> = HasGetMany<TResponse, TQuery> &
|
|
163
164
|
HasGetById<TResponse> &
|
|
164
165
|
HasCreate<TResponse, TCreate> &
|
|
165
166
|
HasUpdateById<TResponse, TUpdate> &
|
|
166
|
-
HasDeleteById<
|
|
167
|
+
HasDeleteById<TDeleteResponse>
|
|
@@ -2,6 +2,7 @@ import { expectAssignable, expectNotAssignable, expectType } from 'tsd'
|
|
|
2
2
|
import type { WebhookPayload, WebhookEvent, WebhookEventPayloadMap, BotCommandData } from './index'
|
|
3
3
|
import type { Message, MessageRelationships } from '../apis/messages'
|
|
4
4
|
import type { Ticket, TicketRelationships } from '../apis/tickets'
|
|
5
|
+
import type { BotApiSignalData, BotContexts } from '../apis/bots'
|
|
5
6
|
|
|
6
7
|
// ── WebhookPayload (generic) ─────────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -37,6 +38,14 @@ declare const botPayload: WebhookPayload<'bot.command'>
|
|
|
37
38
|
expectType<BotCommandData>(botPayload.data)
|
|
38
39
|
expectAssignable<{ id: string; command: string }>(botPayload.data)
|
|
39
40
|
|
|
41
|
+
// ── bot.* deliver the stored bot, with the contexts in the map form ──────────
|
|
42
|
+
|
|
43
|
+
declare const botCreated: WebhookPayload<'bot.created'>
|
|
44
|
+
expectType<BotContexts>(botCreated.data.contexts)
|
|
45
|
+
|
|
46
|
+
declare const apiSignal: WebhookPayload<'bot.api-signal'>
|
|
47
|
+
expectType<BotApiSignalData>(apiSignal.data)
|
|
48
|
+
|
|
40
49
|
// ── invalid events are rejected ──────────────────────────────────────────────
|
|
41
50
|
|
|
42
51
|
expectNotAssignable<WebhookEvent>('message.deleted')
|