@ikatec/digisac-api-sdk 4.13.0 → 4.15.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 +7 -2
- package/dist/apis/authHistory/AuthHistoryApi.cjs +9 -2
- package/dist/apis/authHistory/AuthHistoryApi.cjs.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.d.ts +7 -2
- package/dist/apis/authHistory/AuthHistoryApi.d.ts.map +1 -1
- package/dist/apis/authHistory/AuthHistoryApi.mjs +9 -2
- package/dist/apis/authHistory/AuthHistoryApi.mjs.map +1 -1
- package/dist/apis/authHistory/types.d.ts +6 -1
- package/dist/apis/authHistory/types.d.ts.map +1 -1
- package/dist/apis/contacts/ContactsApi.cjs +10 -0
- package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
- package/dist/apis/contacts/ContactsApi.d.ts +10 -2
- package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
- package/dist/apis/contacts/ContactsApi.mjs +10 -0
- package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
- package/dist/apis/contacts/types.d.ts +76 -2
- package/dist/apis/contacts/types.d.ts.map +1 -1
- package/dist/apis/files/FilesApi.cjs +13 -1
- package/dist/apis/files/FilesApi.cjs.map +1 -1
- package/dist/apis/files/FilesApi.d.ts +12 -2
- package/dist/apis/files/FilesApi.d.ts.map +1 -1
- package/dist/apis/files/FilesApi.mjs +13 -1
- package/dist/apis/files/FilesApi.mjs.map +1 -1
- package/dist/apis/files/types.d.ts +19 -1
- package/dist/apis/files/types.d.ts.map +1 -1
- package/dist/apis/notifications/types.d.ts +6 -3
- package/dist/apis/notifications/types.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.cjs +10 -0
- package/dist/apis/services/ServicesApi.cjs.map +1 -1
- package/dist/apis/services/ServicesApi.d.ts +9 -1
- package/dist/apis/services/ServicesApi.d.ts.map +1 -1
- package/dist/apis/services/ServicesApi.mjs +10 -0
- package/dist/apis/services/ServicesApi.mjs.map +1 -1
- package/dist/apis/services/types.d.ts +10 -0
- package/dist/apis/services/types.d.ts.map +1 -1
- package/dist/apis/stickers/types.d.ts +3 -0
- package/dist/apis/stickers/types.d.ts.map +1 -1
- package/dist/core/BaseReadApi.cjs +2 -2
- package/dist/core/BaseReadApi.cjs.map +1 -1
- package/dist/core/BaseReadApi.d.ts +2 -2
- package/dist/core/BaseReadApi.mjs +2 -2
- package/dist/core/BaseReadApi.mjs.map +1 -1
- package/package.json +1 -1
- package/src/apis/authHistory/AuthHistoryApi.test.ts +16 -0
- package/src/apis/authHistory/AuthHistoryApi.ts +10 -2
- package/src/apis/authHistory/types.ts +6 -1
- package/src/apis/contacts/ContactsApi.test.ts +13 -0
- package/src/apis/contacts/ContactsApi.ts +23 -1
- package/src/apis/contacts/types.ts +83 -1
- package/src/apis/files/FilesApi.test.ts +21 -0
- package/src/apis/files/FilesApi.ts +19 -1
- package/src/apis/files/types.ts +20 -1
- package/src/apis/notifications/types.ts +6 -3
- package/src/apis/payloads.test-d.ts +60 -2
- package/src/apis/services/ServicesApi.test.ts +11 -0
- package/src/apis/services/ServicesApi.ts +21 -0
- package/src/apis/services/types.ts +12 -0
- package/src/apis/stickers/types.ts +3 -0
- package/src/core/BaseReadApi.ts +2 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { CustomField, CustomFieldType } from '../customFields/types'
|
|
1
2
|
import type { Department } from '../departments/types'
|
|
2
3
|
import type { Person } from '../people/types'
|
|
3
4
|
import type { Service } from '../services/types'
|
|
4
5
|
import type { Tag } from '../tags/types'
|
|
5
6
|
import type { Ticket } from '../tickets/types'
|
|
6
7
|
import type { User } from '../users/types'
|
|
7
|
-
import type { IncludeItem, ListQuery, WhereClause } from '../../core/types'
|
|
8
|
+
import type { GetByIdQuery, IncludeItem, ListQuery, WhereClause } from '../../core/types'
|
|
8
9
|
|
|
9
10
|
export type ContactStatus = 'online' | 'offline' | 'absent'
|
|
10
11
|
export type ContactOrigin = 'api' | 'app' | 'campaign' | 'driver' | 'import' | 'web'
|
|
@@ -95,6 +96,56 @@ export type ContactDataBlock = {
|
|
|
95
96
|
description: string
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* A value of a custom field of the contact, as `include: ['customFieldValues']` answers it (the
|
|
101
|
+
* stored row).
|
|
102
|
+
*/
|
|
103
|
+
export type CustomFieldValue = {
|
|
104
|
+
id: string
|
|
105
|
+
customFieldId: string
|
|
106
|
+
/** The contact. */
|
|
107
|
+
relatedId: string
|
|
108
|
+
relatedType: string | null
|
|
109
|
+
/**
|
|
110
|
+
* The value as stored: text, or JSON text for the composite types (`date-range`, `hour-range`,
|
|
111
|
+
* `numeric`, `monetary`, `phone`, `postal-code`).
|
|
112
|
+
*/
|
|
113
|
+
value: string
|
|
114
|
+
createdAt: string
|
|
115
|
+
updatedAt: string
|
|
116
|
+
deletedAt: string | null
|
|
117
|
+
// relationships
|
|
118
|
+
customField?: CustomField
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A custom field of the contact as `formatCustomFields: true` answers it, keyed by the name of the
|
|
123
|
+
* field in `Contact.customFields`.
|
|
124
|
+
*/
|
|
125
|
+
export type FormattedCustomField = {
|
|
126
|
+
name: string
|
|
127
|
+
/** The value, parsed from JSON for the composite types. */
|
|
128
|
+
value: unknown
|
|
129
|
+
/** Id of the value. */
|
|
130
|
+
id: string
|
|
131
|
+
type: CustomFieldType
|
|
132
|
+
/** Id of the field. */
|
|
133
|
+
fieldId: string
|
|
134
|
+
/** When the field was created. */
|
|
135
|
+
createdAt: string
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A custom field value sent in the create or update of a contact. */
|
|
139
|
+
export type ContactCustomFieldInput = {
|
|
140
|
+
/**
|
|
141
|
+
* Id of the custom field (`CustomField.id`, `FormattedCustomField.fieldId` or
|
|
142
|
+
* `CustomFieldValue.customFieldId`), not the id of the value.
|
|
143
|
+
*/
|
|
144
|
+
id: string
|
|
145
|
+
/** Text, or an object for the composite types (stored as JSON). */
|
|
146
|
+
value: unknown
|
|
147
|
+
}
|
|
148
|
+
|
|
98
149
|
export type Contact = {
|
|
99
150
|
id: string
|
|
100
151
|
idFromService: string | null
|
|
@@ -139,6 +190,13 @@ export type Contact = {
|
|
|
139
190
|
person?: Person | null
|
|
140
191
|
tags?: Tag[]
|
|
141
192
|
currentTicket?: Ticket | null
|
|
193
|
+
/** With `include: ['customFieldValues']`, unless `formatCustomFields` is set. */
|
|
194
|
+
customFieldValues?: CustomFieldValue[]
|
|
195
|
+
/**
|
|
196
|
+
* With `formatCustomFields: true` and the include
|
|
197
|
+
* `{ model: 'customFieldValues', include: ['customField'] }`: the values keyed by the field name.
|
|
198
|
+
*/
|
|
199
|
+
customFields?: Record<string, FormattedCustomField>
|
|
142
200
|
}
|
|
143
201
|
|
|
144
202
|
export type ContactRelationships =
|
|
@@ -148,6 +206,23 @@ export type ContactRelationships =
|
|
|
148
206
|
| 'person'
|
|
149
207
|
| 'tags'
|
|
150
208
|
| 'currentTicket'
|
|
209
|
+
| 'customFieldValues'
|
|
210
|
+
| 'customFields'
|
|
211
|
+
|
|
212
|
+
type FormatCustomFieldsOption = {
|
|
213
|
+
/**
|
|
214
|
+
* Answers `customFields` keyed by the field name instead of the raw `customFieldValues` (with the
|
|
215
|
+
* include `{ model: 'customFieldValues', include: ['customField'] }`; without the nested
|
|
216
|
+
* `customField`, `customFields` comes empty).
|
|
217
|
+
*/
|
|
218
|
+
formatCustomFields?: boolean
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Query of `ContactsApi.getById`. */
|
|
222
|
+
export type ContactGetByIdQuery = GetByIdQuery<Contact> & FormatCustomFieldsOption
|
|
223
|
+
|
|
224
|
+
/** Query of `ContactsApi.getMany` and `getOne`. */
|
|
225
|
+
export type ContactListQuery = ListQuery<Contact> & FormatCustomFieldsOption
|
|
151
226
|
|
|
152
227
|
export type CreateContactPayload = {
|
|
153
228
|
name?: string
|
|
@@ -160,6 +235,8 @@ export type CreateContactPayload = {
|
|
|
160
235
|
data?: ContactData
|
|
161
236
|
note?: string
|
|
162
237
|
origin?: ContactOrigin
|
|
238
|
+
/** Values of the custom fields of the account. An id that is not a custom field answers 400. */
|
|
239
|
+
customFields?: ContactCustomFieldInput[]
|
|
163
240
|
}
|
|
164
241
|
|
|
165
242
|
export type UpdateContactPayload = {
|
|
@@ -174,6 +251,11 @@ export type UpdateContactPayload = {
|
|
|
174
251
|
block?: boolean
|
|
175
252
|
/** Tag IDs to associate with the contact. */
|
|
176
253
|
tagIds?: string[]
|
|
254
|
+
/**
|
|
255
|
+
* Values of the custom fields of the account: a value of the same field is updated. An id that is
|
|
256
|
+
* not a custom field is skipped.
|
|
257
|
+
*/
|
|
258
|
+
customFields?: ContactCustomFieldInput[]
|
|
177
259
|
}
|
|
178
260
|
|
|
179
261
|
/** Result of `ContactsApi.summarizeTicket`. */
|
|
@@ -97,6 +97,27 @@ describe('FilesApi', () => {
|
|
|
97
97
|
})
|
|
98
98
|
})
|
|
99
99
|
|
|
100
|
+
describe('duplicateFile', () => {
|
|
101
|
+
it('calls client.post with /files/duplicate-file and resolves the id of the copy', async () => {
|
|
102
|
+
vi.mocked(client.post).mockResolvedValue('file-2')
|
|
103
|
+
const result = await api.duplicateFile({ fileId: 'file-1' })
|
|
104
|
+
expect(client.post).toHaveBeenCalledWith(
|
|
105
|
+
'/files/duplicate-file',
|
|
106
|
+
{ fileId: 'file-1' },
|
|
107
|
+
undefined,
|
|
108
|
+
)
|
|
109
|
+
expect(result).toBe('file-2')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('sends urlToUpload and serviceId in the body and forwards custom headers', async () => {
|
|
113
|
+
vi.mocked(client.post).mockResolvedValue('file-1')
|
|
114
|
+
const headers = { 'X-Req': 'r-1' }
|
|
115
|
+
const body = { fileId: 'file-1', urlToUpload: URL_TO_UPLOAD, serviceId: 'svc-1' }
|
|
116
|
+
await api.duplicateFile(body, headers)
|
|
117
|
+
expect(client.post).toHaveBeenCalledWith('/files/duplicate-file', body, headers)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
100
121
|
describe('deleteById', () => {
|
|
101
122
|
it('calls client.delete with /files/:id and resolves { success }', async () => {
|
|
102
123
|
vi.mocked(client.delete).mockResolvedValue({ success: true })
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
CreateToUploadPayload,
|
|
10
10
|
CreateToUploadResult,
|
|
11
11
|
DeleteFileResult,
|
|
12
|
+
DuplicateFilePayload,
|
|
12
13
|
} from './types'
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -85,7 +86,8 @@ export class FilesApi extends BaseCrudApi<
|
|
|
85
86
|
* Creates the record of a file and returns the signed URL to upload its content: send the
|
|
86
87
|
* content with {@link upload}. An audio that must be converted (a voice message for the
|
|
87
88
|
* connection) is sent in `base64` instead, and then no `urlToUpload` is returned. `name` and
|
|
88
|
-
* `mimetype` are required (500 without them; an audio without `name` is named `recording`).
|
|
89
|
+
* `mimetype` are required (500 without them; an audio without `name` is named `recording`). With
|
|
90
|
+
* `serviceId`, an extension the account blocks for the type of the connection answers 403.
|
|
89
91
|
*/
|
|
90
92
|
createToUpload(
|
|
91
93
|
body: CreateToUploadPayload,
|
|
@@ -98,6 +100,22 @@ export class FilesApi extends BaseCrudApi<
|
|
|
98
100
|
)
|
|
99
101
|
}
|
|
100
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Copies the content of a file. With `urlToUpload` (the signed URL of a record created by
|
|
105
|
+
* {@link createToUpload} with the same `mimetype` as the file copied), the content is sent to that
|
|
106
|
+
* record and the answer is the `fileId` given; without it, a new record (`attachedType`
|
|
107
|
+
* `'message.file'`) is created with the copied content and its id is the answer, and with
|
|
108
|
+
* `serviceId` an extension the account blocks for the type of the connection answers 403. An
|
|
109
|
+
* unknown `fileId` answers 500.
|
|
110
|
+
*/
|
|
111
|
+
duplicateFile(body: DuplicateFilePayload, headers?: Record<string, string>): Promise<string> {
|
|
112
|
+
return this.client.post<string, DuplicateFilePayload>(
|
|
113
|
+
`${this.basePath}/duplicate-file`,
|
|
114
|
+
body,
|
|
115
|
+
headers,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
/**
|
|
102
120
|
* Uploads the content of a file to the `urlToUpload` returned by {@link createToUpload}. The URL
|
|
103
121
|
* is signed by the storage, so the request goes straight to it, without the API token (the
|
package/src/apis/files/types.ts
CHANGED
|
@@ -74,7 +74,10 @@ export type CreateToUploadPayload = {
|
|
|
74
74
|
mimetype: string
|
|
75
75
|
/** What the file is attached to, e.g. `'message.file'` (default), `'campaign.file'`, `'quickreply.file'`. */
|
|
76
76
|
attachedType?: string
|
|
77
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Connection that will send the file: used to decide the audio conversion and to apply the
|
|
79
|
+
* extensions the account blocks for its type (403).
|
|
80
|
+
*/
|
|
78
81
|
serviceId?: string
|
|
79
82
|
/** Audio sent as a voice message (default `true`). */
|
|
80
83
|
isPtt?: boolean
|
|
@@ -93,6 +96,22 @@ export type CreateToUploadResult = {
|
|
|
93
96
|
mimetype: string
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
/** Body of `FilesApi.duplicateFile`. */
|
|
100
|
+
export type DuplicateFilePayload = {
|
|
101
|
+
/** The file whose content is copied. */
|
|
102
|
+
fileId: string
|
|
103
|
+
/**
|
|
104
|
+
* Signed URL of a record created by `FilesApi.createToUpload`, with the same `mimetype` as the file
|
|
105
|
+
* copied, to receive the content. Without it, a new record is created with the copied content.
|
|
106
|
+
*/
|
|
107
|
+
urlToUpload?: string
|
|
108
|
+
/**
|
|
109
|
+
* Connection that will send the new record (used only without `urlToUpload`): the extensions the
|
|
110
|
+
* account blocks for its type apply.
|
|
111
|
+
*/
|
|
112
|
+
serviceId?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
96
115
|
/** Result of `FilesApi.deleteById`. */
|
|
97
116
|
export type DeleteFileResult = {
|
|
98
117
|
success: boolean
|
|
@@ -78,8 +78,10 @@ export type Notification = {
|
|
|
78
78
|
label: string | null
|
|
79
79
|
config: NotificationConfig | null
|
|
80
80
|
attachedType: string | null
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
/** @deprecated Not answered: the model has no such column, so the transformer never sends it. */
|
|
82
|
+
attachedId?: string | null
|
|
83
|
+
/** @deprecated Not answered: the model has no such column, so the transformer never sends it. */
|
|
84
|
+
notificationReadId?: string | null
|
|
83
85
|
contactId: string | null
|
|
84
86
|
userId: string
|
|
85
87
|
accountId: string
|
|
@@ -93,7 +95,8 @@ export type Notification = {
|
|
|
93
95
|
schedule?: Schedule | null
|
|
94
96
|
/** @deprecated Not served: the model has no such association, so it never comes. */
|
|
95
97
|
contact?: Contact | null
|
|
96
|
-
|
|
98
|
+
/** The read receipts of the notification (a list: `[]` while it was never read). */
|
|
99
|
+
notificationRead?: NotificationRead[]
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
/** The backend serves `user`, `schedule` and `notificationRead` (`contact` is never answered). */
|
|
@@ -54,7 +54,14 @@ import { AcceptanceTermsApi } from './acceptanceTerms/AcceptanceTermsApi'
|
|
|
54
54
|
import { CategoriesApi } from './categories/CategoriesApi'
|
|
55
55
|
import { ContactBlockListsApi } from './contactBlockLists/ContactBlockListsApi'
|
|
56
56
|
import { ContactBlockListsControlsApi } from './contactBlockListsControls/ContactBlockListsControlsApi'
|
|
57
|
-
import type {
|
|
57
|
+
import type {
|
|
58
|
+
Contact,
|
|
59
|
+
CustomFieldValue,
|
|
60
|
+
FormattedCustomField,
|
|
61
|
+
SummarizeTicketResult,
|
|
62
|
+
} from './contacts/types'
|
|
63
|
+
import { AuthHistoryApi } from './authHistory/AuthHistoryApi'
|
|
64
|
+
import type { AuthHistoryEvent } from './authHistory/types'
|
|
58
65
|
import { CustomFieldsApi } from './customFields/CustomFieldsApi'
|
|
59
66
|
import { DepartmentsApi } from './departments/DepartmentsApi'
|
|
60
67
|
import type { Department } from './departments/types'
|
|
@@ -71,7 +78,11 @@ import { KnowledgeBaseApi } from './knowledgeBase/KnowledgeBaseApi'
|
|
|
71
78
|
import { KnowledgeBaseItemApi } from './knowledgeBaseItem/KnowledgeBaseItemApi'
|
|
72
79
|
import { MessagesApi } from './messages/MessagesApi'
|
|
73
80
|
import { NotificationsApi } from './notifications/NotificationsApi'
|
|
74
|
-
import type {
|
|
81
|
+
import type {
|
|
82
|
+
Notification,
|
|
83
|
+
NotificationCountsResponse,
|
|
84
|
+
NotificationRead,
|
|
85
|
+
} from './notifications/types'
|
|
75
86
|
import { AbsenceApi } from './absence/AbsenceApi'
|
|
76
87
|
import type { AbsenceReportItem, UnlockAbsenceResponse } from './absence/types'
|
|
77
88
|
import { ActivityLogApi } from './activityLog/ActivityLogApi'
|
|
@@ -749,3 +760,50 @@ expectType<Promise<Sticker>>(new StickersApi(client).create({ base64: 'iVBOR', n
|
|
|
749
760
|
expectError(new StickersApi(client).create({ type: 'created' }))
|
|
750
761
|
expectType<Promise<'OK' | false>>(new StickersApi(client).remove('s-1'))
|
|
751
762
|
expectAssignable<Parameters<StickersApi['getMany']>[0]>({ include: ['file'] })
|
|
763
|
+
|
|
764
|
+
// ── QARC-1010 (4.13.1): the shapes observed on qa-rc ─────────────────
|
|
765
|
+
|
|
766
|
+
declare const notification: Notification
|
|
767
|
+
expectType<NotificationRead[] | undefined>(notification.notificationRead)
|
|
768
|
+
expectType<string | null | undefined>(notification.attachedId)
|
|
769
|
+
declare const sticker: Sticker
|
|
770
|
+
expectAssignable<Sticker>({ ...sticker, originMessage: null })
|
|
771
|
+
expectAssignable<Promise<{ message: string }>>(
|
|
772
|
+
new ServicesApi(client).accessManagement('s-1', { userIds: ['u-1'], deny: true }),
|
|
773
|
+
)
|
|
774
|
+
expectAssignable<Awaited<ReturnType<ServicesApi['accessManagement']>>>({
|
|
775
|
+
message: '2 users allowed access.',
|
|
776
|
+
})
|
|
777
|
+
expectNotAssignable<Awaited<ReturnType<ServicesApi['accessManagement']>>>({ message: 'denied' })
|
|
778
|
+
expectError(new ServicesApi(client).accessManagement('s-1', { userIds: ['u-1'] }))
|
|
779
|
+
|
|
780
|
+
// ── QARC-1015 (4.15.0): custom fields of the contact, file copy, logout event ─────
|
|
781
|
+
|
|
782
|
+
expectAssignable<Parameters<ContactsApi['create']>[0]>({
|
|
783
|
+
serviceId: 's-1',
|
|
784
|
+
customFields: [
|
|
785
|
+
{ id: 'f-1', value: 'text' },
|
|
786
|
+
{ id: 'f-2', value: { value: '10,50', identifier: 'BRL', min: '', max: '' } },
|
|
787
|
+
],
|
|
788
|
+
})
|
|
789
|
+
expectAssignable<Parameters<ContactsApi['updateById']>[1]>({
|
|
790
|
+
customFields: [{ id: 'f-1', value: 'new' }],
|
|
791
|
+
})
|
|
792
|
+
expectError(
|
|
793
|
+
new ContactsApi(client).create({ serviceId: 's-1', customFields: [{ value: 'no id' }] }),
|
|
794
|
+
)
|
|
795
|
+
expectAssignable<Parameters<ContactsApi['getById']>[1]>({
|
|
796
|
+
include: [{ model: 'customFieldValues', include: ['customField'] }],
|
|
797
|
+
formatCustomFields: true,
|
|
798
|
+
})
|
|
799
|
+
declare const contactWithFields: Contact
|
|
800
|
+
expectType<CustomFieldValue[] | undefined>(contactWithFields.customFieldValues)
|
|
801
|
+
expectType<Record<string, FormattedCustomField> | undefined>(contactWithFields.customFields)
|
|
802
|
+
expectAssignable<Parameters<ContactsApi['getMany']>[0]>({
|
|
803
|
+
include: [{ model: 'customFieldValues', include: ['customField'] }],
|
|
804
|
+
formatCustomFields: true,
|
|
805
|
+
})
|
|
806
|
+
expectType<Promise<string>>(new FilesApi(client).duplicateFile({ fileId: 'file-1' }))
|
|
807
|
+
expectError(new FilesApi(client).duplicateFile({ urlToUpload: 'https://storage' }))
|
|
808
|
+
expectType<Promise<void>>(new AuthHistoryApi(client).registerLogout())
|
|
809
|
+
expectAssignable<AuthHistoryEvent>('2fa_disabled')
|
|
@@ -91,4 +91,15 @@ describe('ServicesApi', () => {
|
|
|
91
91
|
})
|
|
92
92
|
},
|
|
93
93
|
)
|
|
94
|
+
|
|
95
|
+
describe('accessManagement', () => {
|
|
96
|
+
it('posts the users and the deny flag to /services/:id/access-management', async () => {
|
|
97
|
+
vi.mocked(client.post).mockResolvedValue({ message: '1 users denied access.' })
|
|
98
|
+
const body = { userIds: ['u-1'], deny: true }
|
|
99
|
+
await expect(api.accessManagement('svc-1', body)).resolves.toEqual({
|
|
100
|
+
message: '1 users denied access.',
|
|
101
|
+
})
|
|
102
|
+
expect(client.post).toHaveBeenCalledWith('/services/svc-1/access-management', body, undefined)
|
|
103
|
+
})
|
|
104
|
+
})
|
|
94
105
|
})
|
|
@@ -6,6 +6,8 @@ import type {
|
|
|
6
6
|
ServiceLimits,
|
|
7
7
|
UpdateServicePayload,
|
|
8
8
|
ArchiveServicePayload,
|
|
9
|
+
ServiceAccessManagementPayload,
|
|
10
|
+
ServiceAccessManagementResponse,
|
|
9
11
|
} from './types'
|
|
10
12
|
|
|
11
13
|
export class ServicesApi extends ArchivableCrudApi<
|
|
@@ -109,4 +111,23 @@ export class ServicesApi extends ArchivableCrudApi<
|
|
|
109
111
|
restart(id: string, headers?: Record<string, string>): Promise<'OK'> {
|
|
110
112
|
return this.client.post<'OK'>(`${this.basePath}/${id}/restart`, undefined, headers)
|
|
111
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Denies the connection to users (`deny: true`) or gives it back (`deny: false`); the admins are skipped. Denying
|
|
117
|
+
* transfers the open tickets of those users in the connection. The rows are read with
|
|
118
|
+
* `ServiceAccessManagementApi.getMany`. An unknown connection answers 404 `Service not found.`; a missing
|
|
119
|
+
* `userIds` or a `deny` that is not a boolean answers 400.
|
|
120
|
+
* @permissions users.update AND services.operate
|
|
121
|
+
*/
|
|
122
|
+
accessManagement(
|
|
123
|
+
id: string,
|
|
124
|
+
body: ServiceAccessManagementPayload,
|
|
125
|
+
headers?: Record<string, string>,
|
|
126
|
+
): Promise<ServiceAccessManagementResponse> {
|
|
127
|
+
return this.client.post<ServiceAccessManagementResponse, ServiceAccessManagementPayload>(
|
|
128
|
+
`${this.basePath}/${id}/access-management`,
|
|
129
|
+
body,
|
|
130
|
+
headers,
|
|
131
|
+
)
|
|
132
|
+
}
|
|
112
133
|
}
|
|
@@ -292,3 +292,15 @@ export type ServiceLimit = {
|
|
|
292
292
|
|
|
293
293
|
/** Connections used and allowed by the account plan, by connection type. */
|
|
294
294
|
export type ServiceLimits = Partial<Record<ServiceType, ServiceLimit>>
|
|
295
|
+
|
|
296
|
+
/** The body of `accessManagement`: the users to deny (or give back) the connection to. */
|
|
297
|
+
export type ServiceAccessManagementPayload = {
|
|
298
|
+
userIds: string[]
|
|
299
|
+
/** `true` denies the connection to the users (their open tickets in it are transferred); `false` gives it back. */
|
|
300
|
+
deny: boolean
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** What `accessManagement` answers: how many users were denied or given back the connection (no admin is counted). */
|
|
304
|
+
export type ServiceAccessManagementResponse = {
|
|
305
|
+
message: `${number} users denied access.` | `${number} users allowed access.`
|
|
306
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { File } from '../files/types'
|
|
2
|
+
import type { Message } from '../messages/types'
|
|
2
3
|
import type { User } from '../users/types'
|
|
3
4
|
|
|
4
5
|
/** A sticker of the account (every user of the account sees it). */
|
|
@@ -19,6 +20,8 @@ export type Sticker = {
|
|
|
19
20
|
user?: User
|
|
20
21
|
/** The image (webp) of the sticker. */
|
|
21
22
|
file?: File
|
|
23
|
+
/** The message the sticker came from (`add`); `null` on a sticker uploaded with `create`. */
|
|
24
|
+
originMessage?: Message | null
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/** The backend serves only `file` (`user` is dropped). */
|
package/src/core/BaseReadApi.ts
CHANGED
|
@@ -4,8 +4,8 @@ import type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of
|
|
7
|
-
* `BaseCrudApi`, and of the resources the backend
|
|
8
|
-
* auth history).
|
|
7
|
+
* `BaseCrudApi`, and of the resources the backend serves for reading, plus their own actions (e.g.
|
|
8
|
+
* permissions, auth history).
|
|
9
9
|
*/
|
|
10
10
|
export class BaseReadApi<TResponse, TQuery extends ListQuery<TResponse> = ListQuery<TResponse>>
|
|
11
11
|
extends BaseApi
|