@ikatec/digisac-api-sdk 4.10.0 → 4.12.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/dist/apis/categories/types.d.ts +1 -0
- package/dist/apis/categories/types.d.ts.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs +28 -0
- package/dist/apis/contactBlockLists/ContactBlockListsApi.cjs.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts +24 -0
- package/dist/apis/contactBlockLists/ContactBlockListsApi.d.ts.map +1 -1
- package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs +28 -0
- package/dist/apis/contactBlockLists/ContactBlockListsApi.mjs.map +1 -1
- package/dist/apis/contactBlockLists/types.d.ts +6 -1
- package/dist/apis/contactBlockLists/types.d.ts.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs +31 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.cjs.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts +28 -2
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.d.ts.map +1 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs +31 -1
- package/dist/apis/contactBlockListsControls/ContactBlockListsControlsApi.mjs.map +1 -1
- package/dist/apis/contactBlockListsControls/types.d.ts +7 -0
- package/dist/apis/contactBlockListsControls/types.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.cjs +25 -0
- package/dist/apis/distribution/DistributionApi.cjs.map +1 -1
- package/dist/apis/distribution/DistributionApi.d.ts +20 -1
- package/dist/apis/distribution/DistributionApi.d.ts.map +1 -1
- package/dist/apis/distribution/DistributionApi.mjs +25 -0
- package/dist/apis/distribution/DistributionApi.mjs.map +1 -1
- package/dist/apis/distribution/types.d.ts +18 -1
- package/dist/apis/distribution/types.d.ts.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.cjs +17 -0
- package/dist/apis/integrations/IntegrationsApi.cjs.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.d.ts +15 -2
- package/dist/apis/integrations/IntegrationsApi.d.ts.map +1 -1
- package/dist/apis/integrations/IntegrationsApi.mjs +17 -0
- package/dist/apis/integrations/IntegrationsApi.mjs.map +1 -1
- package/dist/apis/integrations/types.d.ts +15 -6
- package/dist/apis/integrations/types.d.ts.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs +15 -0
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.cjs.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts +13 -0
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.d.ts.map +1 -1
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs +15 -0
- package/dist/apis/interactiveMessages/InteractiveMessagesApi.mjs.map +1 -1
- package/dist/apis/interactiveMessages/types.d.ts +8 -2
- package/dist/apis/interactiveMessages/types.d.ts.map +1 -1
- package/dist/apis/me/MeApi.cjs +36 -29
- package/dist/apis/me/MeApi.cjs.map +1 -1
- package/dist/apis/me/MeApi.d.ts +33 -22
- package/dist/apis/me/MeApi.d.ts.map +1 -1
- package/dist/apis/me/MeApi.mjs +36 -29
- package/dist/apis/me/MeApi.mjs.map +1 -1
- package/dist/apis/me/types.d.ts +67 -4
- package/dist/apis/me/types.d.ts.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.cjs +12 -2
- package/dist/apis/transcripts/TranscriptsApi.cjs.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.d.ts +11 -5
- package/dist/apis/transcripts/TranscriptsApi.d.ts.map +1 -1
- package/dist/apis/transcripts/TranscriptsApi.mjs +12 -2
- package/dist/apis/transcripts/TranscriptsApi.mjs.map +1 -1
- package/dist/apis/transcripts/types.d.ts +4 -0
- package/dist/apis/transcripts/types.d.ts.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs +52 -0
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.cjs.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts +45 -2
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.d.ts.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs +52 -0
- package/dist/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.mjs.map +1 -1
- package/dist/apis/whatsappBusinessTemplates/types.d.ts +34 -1
- package/dist/apis/whatsappBusinessTemplates/types.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.cjs +3 -1
- package/dist/core/BaseCrudApi.cjs.map +1 -1
- package/dist/core/BaseCrudApi.d.ts +6 -4
- package/dist/core/BaseCrudApi.d.ts.map +1 -1
- package/dist/core/BaseCrudApi.mjs +3 -1
- 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/package.json +1 -1
- package/src/apis/categories/types.ts +1 -0
- package/src/apis/contactBlockLists/ContactBlockListsApi.test.ts +57 -0
- package/src/apis/contactBlockLists/ContactBlockListsApi.ts +44 -0
- package/src/apis/contactBlockLists/types.ts +6 -1
- package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.test.ts +48 -0
- package/src/apis/contactBlockListsControls/ContactBlockListsControlsApi.ts +49 -2
- package/src/apis/contactBlockListsControls/types.ts +7 -0
- package/src/apis/distribution/DistributionApi.test.ts +65 -0
- package/src/apis/distribution/DistributionApi.ts +39 -1
- package/src/apis/distribution/types.ts +17 -1
- package/src/apis/integrations/IntegrationsApi.test.ts +42 -0
- package/src/apis/integrations/IntegrationsApi.ts +35 -2
- package/src/apis/integrations/types.ts +25 -6
- package/src/apis/interactiveMessages/InteractiveMessagesApi.test.ts +59 -0
- package/src/apis/interactiveMessages/InteractiveMessagesApi.ts +26 -0
- package/src/apis/interactiveMessages/types.ts +8 -2
- package/src/apis/me/MeApi.test.ts +44 -0
- package/src/apis/me/MeApi.ts +83 -28
- package/src/apis/me/types.ts +75 -4
- package/src/apis/payloads.test-d.ts +94 -2
- package/src/apis/transcripts/TranscriptsApi.test.ts +14 -21
- package/src/apis/transcripts/TranscriptsApi.ts +15 -6
- package/src/apis/transcripts/types.ts +4 -0
- package/src/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.test.ts +86 -0
- package/src/apis/whatsappBusinessTemplates/WhatsappBusinessTemplatesApi.ts +75 -1
- package/src/apis/whatsappBusinessTemplates/types.ts +38 -1
- package/src/core/BaseCrudApi.test-d.ts +11 -1
- package/src/core/BaseCrudApi.ts +15 -6
- package/src/core/types.ts +4 -2
|
@@ -11,7 +11,8 @@ export type Distribution = {
|
|
|
11
11
|
distributeQueue: boolean
|
|
12
12
|
accountId: string
|
|
13
13
|
archivedAt: string | null
|
|
14
|
-
|
|
14
|
+
/** Only in the answer of `updateById` (the stored model, not transformed). */
|
|
15
|
+
deletedAt?: string | null
|
|
15
16
|
createdAt: string
|
|
16
17
|
updatedAt: string
|
|
17
18
|
// relationships
|
|
@@ -21,6 +22,9 @@ export type Distribution = {
|
|
|
21
22
|
|
|
22
23
|
export type DistributionRelationships = 'departments' | 'roles'
|
|
23
24
|
|
|
25
|
+
/** A department or a role linked to a distribution (only the id is read). */
|
|
26
|
+
export type DistributionLink = { id: string }
|
|
27
|
+
|
|
24
28
|
export type CreateDistributionPayload = {
|
|
25
29
|
name: string
|
|
26
30
|
maxNum: number
|
|
@@ -28,8 +32,17 @@ export type CreateDistributionPayload = {
|
|
|
28
32
|
timeToRedistribute?: number
|
|
29
33
|
redistributeAssignedTickets?: boolean
|
|
30
34
|
distributeQueue?: boolean
|
|
35
|
+
/** An archived distribution is skipped by the distribution crons. */
|
|
36
|
+
archivedAt?: string | null
|
|
37
|
+
/** Departments to distribute; a department has at most one distribution. */
|
|
38
|
+
departments?: DistributionLink[]
|
|
39
|
+
roles?: DistributionLink[]
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
/**
|
|
43
|
+
* The update replaces the links: send `departments` and `roles` again to keep them — omitting them
|
|
44
|
+
* clears them.
|
|
45
|
+
*/
|
|
33
46
|
export type UpdateDistributionPayload = {
|
|
34
47
|
name?: string
|
|
35
48
|
maxNum?: number
|
|
@@ -37,4 +50,7 @@ export type UpdateDistributionPayload = {
|
|
|
37
50
|
timeToRedistribute?: number
|
|
38
51
|
redistributeAssignedTickets?: boolean
|
|
39
52
|
distributeQueue?: boolean
|
|
53
|
+
archivedAt?: string | null
|
|
54
|
+
departments?: DistributionLink[]
|
|
55
|
+
roles?: DistributionLink[]
|
|
40
56
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { IntegrationsApi } from './IntegrationsApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
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
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe('IntegrationsApi', () => {
|
|
22
|
+
let client: ReturnType<typeof createMockClient>
|
|
23
|
+
let api: IntegrationsApi
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
client = createMockClient()
|
|
27
|
+
api = new IntegrationsApi(client)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('create posts to /integrations and resolves the empty object the route answers', async () => {
|
|
31
|
+
vi.mocked(client.post).mockResolvedValue({})
|
|
32
|
+
const body = { type: 'menu_button', url: 'https://example.com', icon: '', text: 'QA' }
|
|
33
|
+
await expect(api.create(body)).resolves.toEqual({})
|
|
34
|
+
expect(client.post).toHaveBeenCalledWith('/integrations', body, undefined)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('updateById puts to /integrations/:id and resolves the empty object', async () => {
|
|
38
|
+
vi.mocked(client.put).mockResolvedValue({})
|
|
39
|
+
await expect(api.updateById('int-1', { text: 'QA 2' })).resolves.toEqual({})
|
|
40
|
+
expect(client.put).toHaveBeenCalledWith('/integrations/int-1', { text: 'QA 2' }, undefined)
|
|
41
|
+
})
|
|
42
|
+
})
|
|
@@ -1,19 +1,52 @@
|
|
|
1
1
|
import type { ApiClient } from '../../core/ApiClient'
|
|
2
2
|
import type { ListQuery } from '../../core/types'
|
|
3
3
|
import { BaseCrudApi } from '../../core/BaseCrudApi'
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
Integration,
|
|
6
|
+
CreateIntegrationPayload,
|
|
7
|
+
UpdateIntegrationPayload,
|
|
8
|
+
IntegrationWriteResponse,
|
|
9
|
+
} from './types'
|
|
5
10
|
|
|
11
|
+
/** Integrations of the account (buttons and events of the UI). `getById` answers `null` for an unknown id. */
|
|
6
12
|
export class IntegrationsApi extends BaseCrudApi<
|
|
7
13
|
Integration,
|
|
8
14
|
CreateIntegrationPayload,
|
|
9
15
|
UpdateIntegrationPayload,
|
|
10
16
|
ListQuery<Integration>,
|
|
11
|
-
'OK'
|
|
17
|
+
'OK',
|
|
18
|
+
IntegrationWriteResponse,
|
|
19
|
+
IntegrationWriteResponse
|
|
12
20
|
> {
|
|
13
21
|
constructor(client: ApiClient) {
|
|
14
22
|
super(client, '/integrations')
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Creates an integration. Resolves `{}` — also when the type is unique (`event_ticket_transfer`,
|
|
27
|
+
* `event_ticket_closed`), already exists in the account and nothing is created. Find the integration with
|
|
28
|
+
* `getMany` (e.g. by `text`).
|
|
29
|
+
* @permissions integrations.create
|
|
30
|
+
*/
|
|
31
|
+
override create(
|
|
32
|
+
body: CreateIntegrationPayload,
|
|
33
|
+
headers?: Record<string, string>,
|
|
34
|
+
): Promise<IntegrationWriteResponse> {
|
|
35
|
+
return super.create(body, headers)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Updates an integration. Resolves `{}`; read it again with `getById`. An unknown id answers 500.
|
|
40
|
+
* @permissions integrations.view OR integrations.update
|
|
41
|
+
*/
|
|
42
|
+
override updateById(
|
|
43
|
+
id: string,
|
|
44
|
+
body: UpdateIntegrationPayload,
|
|
45
|
+
headers?: Record<string, string>,
|
|
46
|
+
): Promise<IntegrationWriteResponse> {
|
|
47
|
+
return super.updateById(id, body, headers)
|
|
48
|
+
}
|
|
49
|
+
|
|
17
50
|
/**
|
|
18
51
|
* Deletes an integration. Resolves the text `'OK'`; an unknown id answers 500.
|
|
19
52
|
* @permissions integrations.view OR integrations.destroy
|
|
@@ -1,27 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The types the UI shows: buttons (`menu_button` in the header, `context_menu_button_*` in the contact and
|
|
3
|
+
* message menus) and events that open the integration when a ticket is transferred or closed (unique per
|
|
4
|
+
* account). The backend accepts any string.
|
|
5
|
+
*/
|
|
6
|
+
export type IntegrationType =
|
|
7
|
+
| 'menu_button'
|
|
8
|
+
| 'context_menu_button_contact_modal'
|
|
9
|
+
| 'context_menu_button_contact_side_list'
|
|
10
|
+
| 'context_menu_button_message'
|
|
11
|
+
| 'event_ticket_transfer'
|
|
12
|
+
| 'event_ticket_closed'
|
|
13
|
+
| (string & {})
|
|
14
|
+
|
|
15
|
+
export type IntegrationSize = 'sm' | 'md' | 'lg' | (string & {})
|
|
16
|
+
|
|
1
17
|
export type Integration = {
|
|
2
18
|
id: string
|
|
3
|
-
type:
|
|
19
|
+
type: IntegrationType
|
|
4
20
|
url: string | null
|
|
5
21
|
icon: string | null
|
|
6
22
|
text: string | null
|
|
7
|
-
size:
|
|
23
|
+
size: IntegrationSize | null
|
|
8
24
|
accountId: string
|
|
9
25
|
createdAt: string
|
|
10
26
|
updatedAt: string
|
|
11
27
|
}
|
|
12
28
|
|
|
29
|
+
/** What `create` and `updateById` resolve to: an empty object. */
|
|
30
|
+
export type IntegrationWriteResponse = Record<string, never>
|
|
31
|
+
|
|
13
32
|
export type CreateIntegrationPayload = {
|
|
14
|
-
type:
|
|
33
|
+
type: IntegrationType
|
|
15
34
|
url?: string
|
|
16
35
|
icon?: string
|
|
17
36
|
text?: string
|
|
18
|
-
size?:
|
|
37
|
+
size?: IntegrationSize
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
export type UpdateIntegrationPayload = {
|
|
22
|
-
type?:
|
|
41
|
+
type?: IntegrationType
|
|
23
42
|
url?: string | null
|
|
24
43
|
icon?: string | null
|
|
25
44
|
text?: string | null
|
|
26
|
-
size?:
|
|
45
|
+
size?: IntegrationSize | null
|
|
27
46
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { InteractiveMessagesApi } from './InteractiveMessagesApi'
|
|
3
|
+
import type { ApiClient } from '../../core/ApiClient'
|
|
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
|
+
// ── Tests ────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
describe('InteractiveMessagesApi', () => {
|
|
22
|
+
let client: ReturnType<typeof createMockClient>
|
|
23
|
+
let api: InteractiveMessagesApi
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
client = createMockClient()
|
|
27
|
+
api = new InteractiveMessagesApi(client)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('count', () => {
|
|
31
|
+
it('calls client.get with /interactive-messages/count when no query is provided', async () => {
|
|
32
|
+
vi.mocked(client.get).mockResolvedValue({ count: 4 })
|
|
33
|
+
await expect(api.count()).resolves.toEqual({ count: 4 })
|
|
34
|
+
expect(client.get).toHaveBeenCalledWith('/interactive-messages/count', undefined)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('serializes the query', async () => {
|
|
38
|
+
vi.mocked(client.get).mockResolvedValue({ count: 1 })
|
|
39
|
+
await api.count({ where: { name: 'QA' } })
|
|
40
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
41
|
+
expect(url).toContain('/interactive-messages/count?')
|
|
42
|
+
expect(url).toContain('where')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('create posts the message to /interactive-messages', async () => {
|
|
47
|
+
const body = {
|
|
48
|
+
name: 'QA',
|
|
49
|
+
interactive: {
|
|
50
|
+
type: 'button' as const,
|
|
51
|
+
body: 'Oi',
|
|
52
|
+
action: { buttons: [{ type: 'reply' as const, reply: { id: '1', title: 'Sim' } }] },
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
vi.mocked(client.post).mockResolvedValue({ id: 'im-1' })
|
|
56
|
+
await api.create(body)
|
|
57
|
+
expect(client.post).toHaveBeenCalledWith('/interactive-messages', body, undefined)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
@@ -18,6 +18,32 @@ export class InteractiveMessagesApi extends BaseCrudApi<
|
|
|
18
18
|
super(client, '/interactive-messages')
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Counts the interactive messages matching the query.
|
|
23
|
+
* @permissions interactive-messages.view OR interactive-messages.management.all.departments
|
|
24
|
+
*/
|
|
25
|
+
count(
|
|
26
|
+
query?: ListQuery<InteractiveMessage>,
|
|
27
|
+
headers?: Record<string, string>,
|
|
28
|
+
): Promise<{ count: number }> {
|
|
29
|
+
return this.client.get<{ count: number }>(
|
|
30
|
+
`${this.basePath}/count${this.toQueryString(query)}`,
|
|
31
|
+
headers,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates an interactive message. Only `cta_url` is validated by the backend (the other types are stored as
|
|
37
|
+
* sent). The answer has no `departments` nor `file`: read them with `getById` and `include`.
|
|
38
|
+
* @permissions interactive-messages.create OR interactive-messages.management.all.departments
|
|
39
|
+
*/
|
|
40
|
+
override create(
|
|
41
|
+
body: CreateInteractiveMessagePayload,
|
|
42
|
+
headers?: Record<string, string>,
|
|
43
|
+
): Promise<InteractiveMessage> {
|
|
44
|
+
return super.create(body, headers)
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
/**
|
|
22
48
|
* Deletes an interactive message. Resolves the text `'OK'`; an unknown id answers 500.
|
|
23
49
|
* @permissions interactive-messages.destroy OR interactive-messages.management.all.departments
|
|
@@ -169,7 +169,8 @@ type InteractiveMessageFilePayload =
|
|
|
169
169
|
export type InteractiveMessage = {
|
|
170
170
|
id: string
|
|
171
171
|
name: string
|
|
172
|
-
|
|
172
|
+
/** @deprecated Not a field of the backend (no such column): the type is `interactive.type`. */
|
|
173
|
+
type?: 'list' | 'button' | 'cta_url' | 'request_contact_info'
|
|
173
174
|
interactive: InteractiveMessageContent
|
|
174
175
|
archivedAt: string | null
|
|
175
176
|
accountId: string
|
|
@@ -179,9 +180,10 @@ export type InteractiveMessage = {
|
|
|
179
180
|
// relationships
|
|
180
181
|
file?: File | null
|
|
181
182
|
departments?: Department[]
|
|
183
|
+
account?: Record<string, unknown>
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
export type InteractiveMessageRelationships = 'file' | 'departments'
|
|
186
|
+
export type InteractiveMessageRelationships = 'file' | 'departments' | 'account'
|
|
185
187
|
|
|
186
188
|
export type CreateInteractiveMessagePayload = {
|
|
187
189
|
name: string
|
|
@@ -191,6 +193,10 @@ export type CreateInteractiveMessagePayload = {
|
|
|
191
193
|
file?: InteractiveMessageFilePayload
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
/**
|
|
197
|
+
* The backend requires `name` and `interactive` on the update too. It replaces the departments: omitting
|
|
198
|
+
* `departmentIds`/`departments` clears them. Only `file.base64Url` is read (a `file.id` is ignored).
|
|
199
|
+
*/
|
|
194
200
|
export type UpdateInteractiveMessagePayload = {
|
|
195
201
|
name?: string
|
|
196
202
|
interactive?: InteractiveMessageContentInput
|
|
@@ -110,6 +110,50 @@ describe('MeApi', () => {
|
|
|
110
110
|
})
|
|
111
111
|
})
|
|
112
112
|
|
|
113
|
+
describe('getMe with a query', () => {
|
|
114
|
+
it('serializes the include and keeps the headers', async () => {
|
|
115
|
+
vi.mocked(client.get).mockResolvedValue(user)
|
|
116
|
+
const headers = { 'X-Custom': 'h' }
|
|
117
|
+
await api.getMe({ include: ['roles', 'account'] }, headers)
|
|
118
|
+
const [url, sent] = vi.mocked(client.get).mock.calls[0] ?? []
|
|
119
|
+
expect(url).toMatch(/^\/me\?/)
|
|
120
|
+
expect(url).toContain('include')
|
|
121
|
+
expect(sent).toBe(headers)
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('getAccount with where.id', () => {
|
|
126
|
+
it('serializes the id of the account', async () => {
|
|
127
|
+
vi.mocked(client.get).mockResolvedValue(account)
|
|
128
|
+
await api.getAccount({ where: { id: 'acc-1' } })
|
|
129
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
130
|
+
expect(url).toMatch(/^\/me\/account\?/)
|
|
131
|
+
expect(url).toContain('acc-1')
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('getAccountAmounts with a filter', () => {
|
|
136
|
+
it('sends the filter as a comma-separated list', async () => {
|
|
137
|
+
vi.mocked(client.get).mockResolvedValue({ accountId: 'acc-1' })
|
|
138
|
+
await api.getAccountAmounts({ filter: ['services.amounts', 'users.amounts'] })
|
|
139
|
+
const url = vi.mocked(client.get).mock.calls[0]?.[0] as string
|
|
140
|
+
expect(url).toMatch(/^\/me\/get-account-amounts\?/)
|
|
141
|
+
expect(decodeURIComponent(url)).toContain('services.amounts,users.amounts')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('keeps the headers-only call', async () => {
|
|
145
|
+
const headers = { 'X-Custom': 'h' }
|
|
146
|
+
await api.getAccountAmounts(headers)
|
|
147
|
+
expect(client.get).toHaveBeenCalledWith('/me/get-account-amounts', headers)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('keeps a header that comes as undefined at runtime as a header', async () => {
|
|
151
|
+
const headers = { Authorization: undefined } as unknown as Record<string, string>
|
|
152
|
+
await api.getAccountAmounts(headers)
|
|
153
|
+
expect(client.get).toHaveBeenCalledWith('/me/get-account-amounts', headers)
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
|
|
113
157
|
// ── getAccount ───────────────────────────────────────────────────
|
|
114
158
|
|
|
115
159
|
describe('getAccount', () => {
|
package/src/apis/me/MeApi.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { BaseApi } from '../../core/BaseApi'
|
|
|
2
2
|
import type { Account } from '../accounts/types'
|
|
3
3
|
import type { User } from '../users/types'
|
|
4
4
|
import type {
|
|
5
|
+
AccountAmounts,
|
|
6
|
+
AccountAmountsQuery,
|
|
7
|
+
MeAccountQuery,
|
|
8
|
+
MeQuery,
|
|
9
|
+
MeStatusResponse,
|
|
5
10
|
RegisterTokenPayload,
|
|
6
11
|
SetOtpSecretKeyPayload,
|
|
7
12
|
TakeoverPayload,
|
|
@@ -10,30 +15,74 @@ import type {
|
|
|
10
15
|
UpdateMeStatusPayload,
|
|
11
16
|
} from './types'
|
|
12
17
|
|
|
18
|
+
/**
|
|
19
|
+
* A query object always has an array or an object among its values; the headers only have strings (a header that
|
|
20
|
+
* comes as `undefined` at runtime stays a header). That tells the two apart in the methods that took only the
|
|
21
|
+
* headers before and now take a query first.
|
|
22
|
+
*/
|
|
23
|
+
function splitQueryAndHeaders<TQuery extends object>(
|
|
24
|
+
queryOrHeaders: TQuery | Record<string, string> | undefined,
|
|
25
|
+
headers: Record<string, string> | undefined,
|
|
26
|
+
): [TQuery | undefined, Record<string, string> | undefined] {
|
|
27
|
+
const isQuery =
|
|
28
|
+
!!queryOrHeaders &&
|
|
29
|
+
Object.values(queryOrHeaders).some((value) => typeof value === 'object' && value !== null)
|
|
30
|
+
return isQuery
|
|
31
|
+
? [queryOrHeaders as TQuery, headers]
|
|
32
|
+
: [undefined, queryOrHeaders as Record<string, string> | undefined]
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
export class MeApi extends BaseApi {
|
|
14
36
|
/**
|
|
15
|
-
* Returns the currently authenticated user
|
|
37
|
+
* Returns the currently authenticated user. Without `include`, the user comes without relations (roles,
|
|
38
|
+
* departments, timetable, account).
|
|
16
39
|
* @permissions users.view
|
|
17
40
|
*/
|
|
18
|
-
getMe(headers?: Record<string, string>): Promise<User>
|
|
19
|
-
|
|
41
|
+
getMe(headers?: Record<string, string>): Promise<User>
|
|
42
|
+
getMe(query: MeQuery, headers?: Record<string, string>): Promise<User>
|
|
43
|
+
getMe(
|
|
44
|
+
queryOrHeaders?: MeQuery | Record<string, string>,
|
|
45
|
+
headers?: Record<string, string>,
|
|
46
|
+
): Promise<User> {
|
|
47
|
+
const [query, sent] = splitQueryAndHeaders<MeQuery>(queryOrHeaders, headers)
|
|
48
|
+
return this.client.get<User>(`/me${this.toQueryString(query)}`, sent)
|
|
20
49
|
}
|
|
21
50
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
24
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Returns the account of the current user. Send `where.id` with the `accountId` of the user (what the web app
|
|
53
|
+
* does): without it, the route answers 500.
|
|
54
|
+
*/
|
|
55
|
+
getAccount(headers?: Record<string, string>): Promise<Account>
|
|
56
|
+
getAccount(query: MeAccountQuery, headers?: Record<string, string>): Promise<Account>
|
|
57
|
+
getAccount(
|
|
58
|
+
queryOrHeaders?: MeAccountQuery | Record<string, string>,
|
|
59
|
+
headers?: Record<string, string>,
|
|
60
|
+
): Promise<Account> {
|
|
61
|
+
const [query, sent] = splitQueryAndHeaders<MeAccountQuery>(queryOrHeaders, headers)
|
|
62
|
+
return this.client.get<Account>(`/me/account${this.toQueryString(query)}`, sent)
|
|
25
63
|
}
|
|
26
64
|
|
|
27
65
|
/**
|
|
28
|
-
* Returns
|
|
29
|
-
*
|
|
66
|
+
* Returns the amounts of the current account named in `filter` (services and/or users). The credit amounts are
|
|
67
|
+
* never answered here. An account without credits control answers 400.
|
|
30
68
|
*/
|
|
31
|
-
getAccountAmounts(headers?: Record<string, string>): Promise<
|
|
32
|
-
|
|
69
|
+
getAccountAmounts(headers?: Record<string, string>): Promise<AccountAmounts>
|
|
70
|
+
getAccountAmounts(
|
|
71
|
+
query: AccountAmountsQuery,
|
|
72
|
+
headers?: Record<string, string>,
|
|
73
|
+
): Promise<AccountAmounts>
|
|
74
|
+
getAccountAmounts(
|
|
75
|
+
queryOrHeaders?: AccountAmountsQuery | Record<string, string>,
|
|
76
|
+
headers?: Record<string, string>,
|
|
77
|
+
): Promise<AccountAmounts> {
|
|
78
|
+
const [query, sent] = splitQueryAndHeaders<AccountAmountsQuery>(queryOrHeaders, headers)
|
|
79
|
+
const queryString = query ? this.toQueryString({ filter: query.filter.join(',') }) : ''
|
|
80
|
+
return this.client.get<AccountAmounts>(`/me/get-account-amounts${queryString}`, sent)
|
|
33
81
|
}
|
|
34
82
|
|
|
35
83
|
/**
|
|
36
|
-
* Returns a URL to the Agnus billing/plan management page for the current account.
|
|
84
|
+
* Returns a URL to the Agnus billing/plan management page for the current account. Calls Agnus (an external
|
|
85
|
+
* service) and, for accounts without credits control, reports the consumption to it first.
|
|
37
86
|
* @permissions myAccount.myPlan
|
|
38
87
|
*/
|
|
39
88
|
getAgnusMyPlanUrl(headers?: Record<string, string>): Promise<{ url: string }> {
|
|
@@ -41,39 +90,45 @@ export class MeApi extends BaseApi {
|
|
|
41
90
|
}
|
|
42
91
|
|
|
43
92
|
/**
|
|
44
|
-
* Updates the current user's profile fields such as name, language, and preferences
|
|
45
|
-
*
|
|
46
|
-
* and
|
|
93
|
+
* Updates the current user's profile fields such as name, language, and preferences (`data` and `preferences`
|
|
94
|
+
* replace the stored objects). A new `password` needs `currentPassword` and a recent login, is validated against
|
|
95
|
+
* the last 6 passwords, and revokes the access tokens with an expiration — including the one of this request.
|
|
47
96
|
*/
|
|
48
97
|
updateMe(body: UpdateMePayload, headers?: Record<string, string>): Promise<User> {
|
|
49
98
|
return this.client.put<User, UpdateMePayload>('/me', body, headers)
|
|
50
99
|
}
|
|
51
100
|
|
|
52
|
-
/**
|
|
101
|
+
/**
|
|
102
|
+
* Updates account-level settings such as name, settings object, wizard progress, and default department. It
|
|
103
|
+
* applies to the whole account; the default department is required while the tickets are enabled.
|
|
104
|
+
*/
|
|
53
105
|
updateAccount(body: UpdateMeAccountPayload, headers?: Record<string, string>): Promise<Account> {
|
|
54
106
|
return this.client.put<Account, UpdateMeAccountPayload>('/me/account', body, headers)
|
|
55
107
|
}
|
|
56
108
|
|
|
57
109
|
/**
|
|
58
|
-
* Updates the presence status of the current user (`online`, `offline`, or `away`)
|
|
110
|
+
* Updates the presence status of the current user (`online`, `offline`, or `away`) for a `client`.
|
|
59
111
|
* If setting to `online`, validates the user's timetable first — if outside working hours,
|
|
60
|
-
* the status is set to `offline` instead.
|
|
112
|
+
* the status is set to `offline` instead. Resolves the status fields of the user.
|
|
61
113
|
*/
|
|
62
|
-
updateStatus(
|
|
63
|
-
|
|
114
|
+
updateStatus(
|
|
115
|
+
body: UpdateMeStatusPayload,
|
|
116
|
+
headers?: Record<string, string>,
|
|
117
|
+
): Promise<MeStatusResponse> {
|
|
118
|
+
return this.client.put<MeStatusResponse, UpdateMeStatusPayload>('/me/status', body, headers)
|
|
64
119
|
}
|
|
65
120
|
|
|
66
121
|
/**
|
|
67
|
-
* Extends the grace period for the current account when it has expired or is about to expire
|
|
68
|
-
* Returns the updated account.
|
|
122
|
+
* Extends the grace period for the current account when it has expired or is about to expire, and notifies the
|
|
123
|
+
* billing webhook. Returns the updated account; an account not in a grace period answers 400.
|
|
69
124
|
*/
|
|
70
125
|
extendAccountGracePeriod(headers?: Record<string, string>): Promise<Account> {
|
|
71
126
|
return this.client.post<Account>('/me/account/extend-grace-period', undefined, headers)
|
|
72
127
|
}
|
|
73
128
|
|
|
74
129
|
/**
|
|
75
|
-
* Registers
|
|
76
|
-
*
|
|
130
|
+
* Registers the push notification tokens of the current user (`data` replaces the whole `data` of the user).
|
|
131
|
+
* Resolves with no body.
|
|
77
132
|
*/
|
|
78
133
|
registerToken(body: RegisterTokenPayload, headers?: Record<string, string>): Promise<void> {
|
|
79
134
|
return this.client.post<void, RegisterTokenPayload>(
|
|
@@ -89,8 +144,8 @@ export class MeApi extends BaseApi {
|
|
|
89
144
|
}
|
|
90
145
|
|
|
91
146
|
/**
|
|
92
|
-
* Sets the current user's status to `online` for the given client and emits a takeover
|
|
93
|
-
* which signals other connected sessions to yield control to this one.
|
|
147
|
+
* Sets the current user's status to `online` for the given client (`'web'` by default) and emits a takeover
|
|
148
|
+
* event, which signals other connected sessions to yield control to this one. Resolves with no body.
|
|
94
149
|
*/
|
|
95
150
|
takeover(body?: TakeoverPayload, headers?: Record<string, string>): Promise<void> {
|
|
96
151
|
return this.client.post<void, TakeoverPayload>('/me/takeover', body, headers)
|
|
@@ -107,9 +162,9 @@ export class MeApi extends BaseApi {
|
|
|
107
162
|
/**
|
|
108
163
|
* Activates OTP (two-factor) authentication for the current user.
|
|
109
164
|
* Requires the `x-digisac-otp` header with a valid TOTP token generated from the secret key
|
|
110
|
-
* previously set via `setOtpSecretKey`.
|
|
165
|
+
* previously set via `setOtpSecretKey`. Resolves the text `'OK'`.
|
|
111
166
|
*/
|
|
112
|
-
activateOtpAuth(headers?: Record<string, string>): Promise<
|
|
113
|
-
return this.client.post<
|
|
167
|
+
activateOtpAuth(headers?: Record<string, string>): Promise<'OK'> {
|
|
168
|
+
return this.client.post<'OK'>('/me/activate-otp-auth', undefined, headers)
|
|
114
169
|
}
|
|
115
170
|
}
|
package/src/apis/me/types.ts
CHANGED
|
@@ -2,9 +2,68 @@ import type { AccountSettings } from '../accounts/types'
|
|
|
2
2
|
import type { UserPreferences } from '../users/types'
|
|
3
3
|
import type { UserStatus } from '../users/types'
|
|
4
4
|
|
|
5
|
+
/** The client of the session that sets the status: the web app or the mobile app. */
|
|
6
|
+
export type MeClient = 'web' | 'app'
|
|
7
|
+
|
|
8
|
+
/** Query of `GET /me`: without `include`, the user comes without relations. */
|
|
9
|
+
export type MeQuery = {
|
|
10
|
+
include?: (
|
|
11
|
+
| 'roles'
|
|
12
|
+
| 'departments'
|
|
13
|
+
| 'timetable'
|
|
14
|
+
| 'account'
|
|
15
|
+
| 'permissions'
|
|
16
|
+
| 'organizations'
|
|
17
|
+
| (string & {})
|
|
18
|
+
)[]
|
|
19
|
+
attributes?: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Query of `GET /me/account`: `where.id` is the id of the account of the user (`accountId` of `getMe`). */
|
|
23
|
+
export type MeAccountQuery = {
|
|
24
|
+
where: { id: string }
|
|
25
|
+
include?: string[]
|
|
26
|
+
attributes?: string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type AccountAmountsFilter = 'services.amounts' | 'users.amounts'
|
|
30
|
+
|
|
31
|
+
/** Query of `GET /me/get-account-amounts`: which amounts to answer (the credit amounts are never answered). */
|
|
32
|
+
export type AccountAmountsQuery = {
|
|
33
|
+
filter: AccountAmountsFilter[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type UsersAmounts = {
|
|
37
|
+
planBase: number
|
|
38
|
+
additional: number
|
|
39
|
+
totalContracted: number
|
|
40
|
+
planBaseConsumed: number
|
|
41
|
+
additionalConsumed: number
|
|
42
|
+
totalConsumed: number
|
|
43
|
+
available: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type AccountAmounts = {
|
|
47
|
+
accountId: string
|
|
48
|
+
servicesAmounts?: Record<string, unknown>
|
|
49
|
+
usersAmounts?: UsersAmounts
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** What `updateStatus` answers: the status fields of the user, not the whole user. */
|
|
53
|
+
export type MeStatusResponse = {
|
|
54
|
+
id: string
|
|
55
|
+
updatedAt: string
|
|
56
|
+
status: UserStatus
|
|
57
|
+
clientsStatus: Partial<Record<MeClient, UserStatus>>
|
|
58
|
+
language: string
|
|
59
|
+
account?: unknown
|
|
60
|
+
}
|
|
61
|
+
|
|
5
62
|
export type UpdateMePayload = {
|
|
6
63
|
name?: string
|
|
7
64
|
password?: string
|
|
65
|
+
/** Required with `password`: the current password of the user. */
|
|
66
|
+
currentPassword?: string
|
|
8
67
|
language?: string
|
|
9
68
|
preferences?: UserPreferences
|
|
10
69
|
otpAuthActive?: boolean
|
|
@@ -14,22 +73,34 @@ export type UpdateMePayload = {
|
|
|
14
73
|
|
|
15
74
|
export type UpdateMeStatusPayload = {
|
|
16
75
|
status: UserStatus
|
|
17
|
-
|
|
76
|
+
/** Required by the backend (400 without it). */
|
|
77
|
+
client?: MeClient
|
|
18
78
|
}
|
|
19
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Merged over the account. The default department is required while the tickets are enabled
|
|
82
|
+
* (`settings.ticketsEnabled`; 400 `DEFAULT_DEPARTMENT_REQUIRED`).
|
|
83
|
+
*/
|
|
20
84
|
export type UpdateMeAccountPayload = {
|
|
21
85
|
name?: string
|
|
22
86
|
settings?: Partial<AccountSettings>
|
|
23
|
-
wizardProgress?: string
|
|
87
|
+
wizardProgress?: 'notStarted' | 'started' | 'finished' | (string & {})
|
|
24
88
|
defaultDepartmentId?: string
|
|
89
|
+
defaultDepartment?: { id: string }
|
|
25
90
|
}
|
|
26
91
|
|
|
27
92
|
export type TakeoverPayload = {
|
|
28
|
-
|
|
93
|
+
/** Defaults to `'web'`. */
|
|
94
|
+
client?: MeClient
|
|
29
95
|
}
|
|
30
96
|
|
|
31
97
|
export type SetOtpSecretKeyPayload = {
|
|
32
98
|
secretKey: string
|
|
33
99
|
}
|
|
34
100
|
|
|
35
|
-
|
|
101
|
+
/** The push tokens of the user. `data` REPLACES the whole `data` of the user (it is not merged). */
|
|
102
|
+
export type RegisterTokenPayload = {
|
|
103
|
+
data?: { oneSignalToken?: string; expoPushToken?: string | null; fromApp?: boolean }
|
|
104
|
+
/** The backend ignores the call when it is `true`. */
|
|
105
|
+
isClientUser?: boolean
|
|
106
|
+
}
|