@ikatec/digisac-api-sdk 4.3.0 → 4.4.1

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.
Files changed (65) hide show
  1. package/dist/apis/contacts/ContactsApi.cjs +70 -0
  2. package/dist/apis/contacts/ContactsApi.cjs.map +1 -1
  3. package/dist/apis/contacts/ContactsApi.d.ts +51 -1
  4. package/dist/apis/contacts/ContactsApi.d.ts.map +1 -1
  5. package/dist/apis/contacts/ContactsApi.mjs +70 -0
  6. package/dist/apis/contacts/ContactsApi.mjs.map +1 -1
  7. package/dist/apis/contacts/types.d.ts +141 -1
  8. package/dist/apis/contacts/types.d.ts.map +1 -1
  9. package/dist/apis/messages/MessagesApi.cjs +4 -1
  10. package/dist/apis/messages/MessagesApi.cjs.map +1 -1
  11. package/dist/apis/messages/MessagesApi.d.ts +5 -2
  12. package/dist/apis/messages/MessagesApi.d.ts.map +1 -1
  13. package/dist/apis/messages/MessagesApi.mjs +4 -1
  14. package/dist/apis/messages/MessagesApi.mjs.map +1 -1
  15. package/dist/apis/messages/types.d.ts +13 -0
  16. package/dist/apis/messages/types.d.ts.map +1 -1
  17. package/dist/apis/services/ServicesApi.cjs +8 -0
  18. package/dist/apis/services/ServicesApi.cjs.map +1 -1
  19. package/dist/apis/services/ServicesApi.d.ts +7 -1
  20. package/dist/apis/services/ServicesApi.d.ts.map +1 -1
  21. package/dist/apis/services/ServicesApi.mjs +8 -0
  22. package/dist/apis/services/ServicesApi.mjs.map +1 -1
  23. package/dist/apis/services/types.d.ts +7 -0
  24. package/dist/apis/services/types.d.ts.map +1 -1
  25. package/dist/apis/tags/TagsApi.cjs +24 -0
  26. package/dist/apis/tags/TagsApi.cjs.map +1 -1
  27. package/dist/apis/tags/TagsApi.d.ts +20 -1
  28. package/dist/apis/tags/TagsApi.d.ts.map +1 -1
  29. package/dist/apis/tags/TagsApi.mjs +24 -0
  30. package/dist/apis/tags/TagsApi.mjs.map +1 -1
  31. package/dist/apis/tags/types.d.ts +13 -0
  32. package/dist/apis/tags/types.d.ts.map +1 -1
  33. package/dist/apis/users/UsersApi.cjs +77 -0
  34. package/dist/apis/users/UsersApi.cjs.map +1 -1
  35. package/dist/apis/users/UsersApi.d.ts +59 -1
  36. package/dist/apis/users/UsersApi.d.ts.map +1 -1
  37. package/dist/apis/users/UsersApi.mjs +77 -0
  38. package/dist/apis/users/UsersApi.mjs.map +1 -1
  39. package/dist/apis/users/types.d.ts +123 -3
  40. package/dist/apis/users/types.d.ts.map +1 -1
  41. package/package.json +1 -1
  42. package/src/apis/acceptanceTerms/AcceptanceTermsApi.test.ts +1 -1
  43. package/src/apis/authHistory/AuthHistoryApi.test.ts +1 -1
  44. package/src/apis/campaigns/CampaignsApi.test.ts +1 -1
  45. package/src/apis/contacts/ContactsApi.test.ts +182 -5
  46. package/src/apis/contacts/ContactsApi.ts +128 -0
  47. package/src/apis/contacts/types.ts +154 -1
  48. package/src/apis/me/MeApi.test.ts +3 -3
  49. package/src/apis/messages/MessagesApi.test.ts +14 -3
  50. package/src/apis/messages/MessagesApi.ts +6 -3
  51. package/src/apis/messages/types.ts +13 -0
  52. package/src/apis/payloads.test-d.ts +26 -0
  53. package/src/apis/queryFormat.test.ts +27 -3
  54. package/src/apis/serviceAccessManagement/ServiceAccessManagementApi.test.ts +2 -2
  55. package/src/apis/services/ServicesApi.test.ts +49 -0
  56. package/src/apis/services/ServicesApi.ts +10 -1
  57. package/src/apis/services/types.ts +9 -0
  58. package/src/apis/stickerUsers/StickerUsersApi.test.ts +1 -1
  59. package/src/apis/tags/TagsApi.test.ts +84 -0
  60. package/src/apis/tags/TagsApi.ts +59 -1
  61. package/src/apis/tags/types.ts +15 -0
  62. package/src/apis/terms/TermsApi.test.ts +1 -1
  63. package/src/apis/users/UsersApi.test.ts +228 -0
  64. package/src/apis/users/UsersApi.ts +152 -1
  65. package/src/apis/users/types.ts +144 -3
@@ -0,0 +1,228 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { UsersApi } from './UsersApi'
3
+ import type { ApiClient } from '../../core/ApiClient'
4
+ import type {
5
+ BulkUsersStatusPreview,
6
+ BulkUsersStatusResult,
7
+ ImportUsersResult,
8
+ User,
9
+ UserImportFile,
10
+ ValidateUserImportResult,
11
+ } from './types'
12
+
13
+ // ── Mock client factory ──────────────────────────────────────────────
14
+
15
+ function createMockClient(): ApiClient {
16
+ return {
17
+ setAccessToken: vi.fn().mockReturnThis(),
18
+ request: vi.fn(),
19
+ get: vi.fn(),
20
+ post: vi.fn(),
21
+ put: vi.fn(),
22
+ patch: vi.fn(),
23
+ delete: vi.fn(),
24
+ }
25
+ }
26
+
27
+ // ── Fixtures ─────────────────────────────────────────────────────────
28
+
29
+ const user: User = {
30
+ id: 'usr-1',
31
+ name: 'Alice',
32
+ email: 'alice@example.com',
33
+ phoneNumber: null,
34
+ branch: null,
35
+ isClientUser: false,
36
+ language: 'pt-BR',
37
+ isFirstLogin: false,
38
+ status: 'offline',
39
+ clientsStatus: { web: 'offline', app: 'offline' },
40
+ isActiveInternalChat: false,
41
+ internalChatToken: null,
42
+ noteReference: false,
43
+ data: {},
44
+ passwordExpiresAt: null,
45
+ otpAuthActive: false,
46
+ preferences: {},
47
+ archivedAt: null,
48
+ accountId: 'acc-1',
49
+ timetableId: null,
50
+ createdAt: '2024-01-01T00:00:00Z',
51
+ updatedAt: '2024-01-01T00:00:00Z',
52
+ deletedAt: null,
53
+ }
54
+
55
+ const file: UserImportFile = { id: 'f1', name: 'users.csv', mimetype: 'text/csv' }
56
+ const headers = { 'X-Custom': 'value' }
57
+
58
+ // ── Tests ────────────────────────────────────────────────────────────
59
+
60
+ describe('UsersApi', () => {
61
+ let client: ReturnType<typeof createMockClient>
62
+ let api: UsersApi
63
+
64
+ beforeEach(() => {
65
+ client = createMockClient()
66
+ api = new UsersApi(client)
67
+ })
68
+
69
+ describe('archive (ArchivableCrudApi)', () => {
70
+ it('keeps the plain PUT { archivedAt } used by the cleanup of the test suites', async () => {
71
+ await api.archive('usr-1')
72
+ const [url, body] = vi.mocked(client.put).mock.calls[0] ?? []
73
+ expect(url).toBe('/users/usr-1')
74
+ expect(body).toEqual({ archivedAt: expect.any(String) })
75
+ })
76
+ })
77
+
78
+ describe('archiveWithValidation', () => {
79
+ it('calls client.post with /users/:id/archive and { archive: true }', async () => {
80
+ const archived = { ...user, archivedAt: '2026-01-01T00:00:00Z' }
81
+ vi.mocked(client.post).mockResolvedValue(archived)
82
+ const result = await api.archiveWithValidation('usr-1', { archive: true })
83
+ expect(client.post).toHaveBeenCalledWith('/users/usr-1/archive', { archive: true }, undefined)
84
+ expect(result).toEqual(archived)
85
+ })
86
+
87
+ it('restores with { archive: false } and forwards custom headers', async () => {
88
+ await api.archiveWithValidation('usr-1', { archive: false }, headers)
89
+ expect(client.post).toHaveBeenCalledWith('/users/usr-1/archive', { archive: false }, headers)
90
+ })
91
+ })
92
+
93
+ describe('previewBulkStatus', () => {
94
+ it('calls client.post with /users/bulk-status/preview and body', async () => {
95
+ const preview: BulkUsersStatusPreview = {
96
+ scenario: 'allEligible',
97
+ totalSelected: 1,
98
+ noop: 0,
99
+ eligible: 1,
100
+ blocked: 0,
101
+ blockedSelf: false,
102
+ planSlotsAvailable: 10,
103
+ }
104
+ vi.mocked(client.post).mockResolvedValue(preview)
105
+ const body = { ids: ['usr-1'], archive: false }
106
+ const result = await api.previewBulkStatus(body)
107
+ expect(client.post).toHaveBeenCalledWith('/users/bulk-status/preview', body, undefined)
108
+ expect(result).toEqual(preview)
109
+ })
110
+ })
111
+
112
+ describe('bulkStatus', () => {
113
+ it('calls client.post with /users/bulk-status and body', async () => {
114
+ const response: BulkUsersStatusResult = {
115
+ changed: 1,
116
+ noop: 0,
117
+ blocked: 0,
118
+ failed: 0,
119
+ changedIds: ['usr-1'],
120
+ missingIds: [],
121
+ }
122
+ vi.mocked(client.post).mockResolvedValue(response)
123
+ const body = { ids: ['usr-1'], archive: true }
124
+ const result = await api.bulkStatus(body, headers)
125
+ expect(client.post).toHaveBeenCalledWith('/users/bulk-status', body, headers)
126
+ expect(result).toEqual(response)
127
+ })
128
+ })
129
+
130
+ describe('exportCsv', () => {
131
+ it('calls client.post with /users/export and resolves to the CSV text', async () => {
132
+ const csv = 'Nome,E-mail\nAlice,alice@example.com\n'
133
+ vi.mocked(client.post).mockResolvedValue(csv)
134
+ const result = await api.exportCsv({ filter: 'active' })
135
+ expect(client.post).toHaveBeenCalledWith('/users/export', { filter: 'active' }, undefined)
136
+ expect(result).toBe(csv)
137
+ })
138
+ })
139
+
140
+ describe('validateImport', () => {
141
+ it('calls client.post with /users/import/validate and the file', async () => {
142
+ const response: ValidateUserImportResult = {
143
+ licenses: { requestedRows: 1, available: 5, exceeded: false },
144
+ columns: {
145
+ required: [{ name: 'Nome', found: true }],
146
+ missingRequired: [],
147
+ optionalFound: [],
148
+ },
149
+ rows: { total: 1, valid: 1, invalid: 0, hasInvalid: false },
150
+ rowsWithOptionalColumns: { total: 1, valid: 1, invalid: 0, hasInvalid: false },
151
+ }
152
+ vi.mocked(client.post).mockResolvedValue(response)
153
+ const result = await api.validateImport({ file })
154
+ expect(client.post).toHaveBeenCalledWith('/users/import/validate', { file }, undefined)
155
+ expect(result).toEqual(response)
156
+ })
157
+ })
158
+
159
+ describe('startImport', () => {
160
+ it('calls client.post with /users/import and body', async () => {
161
+ const response: ImportUsersResult = { importId: 'imp-1' }
162
+ vi.mocked(client.post).mockResolvedValue(response)
163
+ const body = { file, useOptionalColumns: true }
164
+ const result = await api.startImport(body, headers)
165
+ expect(client.post).toHaveBeenCalledWith('/users/import', body, headers)
166
+ expect(result).toEqual(response)
167
+ })
168
+ })
169
+
170
+ describe('disableOtpAuth', () => {
171
+ it('calls client.post with /users/:id/disable-otp-auth and no body', async () => {
172
+ vi.mocked(client.post).mockResolvedValue(user)
173
+ const result = await api.disableOtpAuth('usr-1')
174
+ expect(client.post).toHaveBeenCalledWith(
175
+ '/users/usr-1/disable-otp-auth',
176
+ undefined,
177
+ undefined,
178
+ )
179
+ expect(result).toEqual(user)
180
+ })
181
+ })
182
+
183
+ describe('enableAccess', () => {
184
+ it('calls client.post with /users/:id/enable-access and no body', async () => {
185
+ const response = { success: true, message: 'User access enabled successfully' }
186
+ vi.mocked(client.post).mockResolvedValue(response)
187
+ const result = await api.enableAccess('usr-1', headers)
188
+ expect(client.post).toHaveBeenCalledWith('/users/usr-1/enable-access', undefined, headers)
189
+ expect(result).toEqual(response)
190
+ })
191
+ })
192
+
193
+ describe('getPinnedContacts', () => {
194
+ it('calls client.get with /users/pin', async () => {
195
+ vi.mocked(client.get).mockResolvedValue([])
196
+ const result = await api.getPinnedContacts()
197
+ expect(client.get).toHaveBeenCalledWith('/users/pin', undefined)
198
+ expect(result).toEqual([])
199
+ })
200
+ })
201
+
202
+ describe('togglePinContact', () => {
203
+ it('calls client.patch with /users/pin and resolves to the link rows when pinning', async () => {
204
+ const links = [
205
+ { userId: 'usr-1', contactId: 'c1', createdAt: '2026-01-01', updatedAt: '2026-01-01' },
206
+ ]
207
+ vi.mocked(client.patch).mockResolvedValue(links)
208
+ const result = await api.togglePinContact({ contactId: 'c1' })
209
+ expect(client.patch).toHaveBeenCalledWith('/users/pin', { contactId: 'c1' }, undefined)
210
+ expect(result).toEqual(links)
211
+ })
212
+
213
+ it('resolves to the number of removed links when unpinning', async () => {
214
+ vi.mocked(client.patch).mockResolvedValue(1)
215
+ await expect(api.togglePinContact({ contactId: 'c1' }, headers)).resolves.toBe(1)
216
+ expect(client.patch).toHaveBeenCalledWith('/users/pin', { contactId: 'c1' }, headers)
217
+ })
218
+ })
219
+
220
+ describe('error propagation', () => {
221
+ it('propagates errors from archiveWithValidation', async () => {
222
+ vi.mocked(client.post).mockRejectedValue(new Error('Bad request'))
223
+ await expect(api.archiveWithValidation('usr-1', { archive: true })).rejects.toThrow(
224
+ 'Bad request',
225
+ )
226
+ })
227
+ })
228
+ })
@@ -1,9 +1,160 @@
1
1
  import type { ApiClient } from '../../core/ApiClient'
2
2
  import { ArchivableCrudApi } from '../../core/ArchivableCrudApi'
3
- import type { User, CreateUserPayload, UpdateUserPayload } from './types'
3
+ import type { Contact } from '../contacts/types'
4
+ import type {
5
+ ArchiveUserPayload,
6
+ BulkUsersStatusPayload,
7
+ BulkUsersStatusPreview,
8
+ BulkUsersStatusResult,
9
+ CreateUserPayload,
10
+ EnableUserAccessResult,
11
+ ExportUsersPayload,
12
+ ImportUsersPayload,
13
+ ImportUsersResult,
14
+ TogglePinContactPayload,
15
+ TogglePinContactResult,
16
+ UpdateUserPayload,
17
+ User,
18
+ ValidateUserImportPayload,
19
+ ValidateUserImportResult,
20
+ } from './types'
4
21
 
5
22
  export class UsersApi extends ArchivableCrudApi<User, CreateUserPayload, UpdateUserPayload> {
6
23
  constructor(client: ApiClient) {
7
24
  super(client, '/users')
8
25
  }
26
+
27
+ /**
28
+ * Archives or restores a user through the dedicated route used by the UI, which also
29
+ * sets the user offline and ends its sessions. The route enforces the business rules that
30
+ * {@link archive}/{@link unarchive} (a plain `PUT { archivedAt }`) skip:
31
+ * - archiving refuses (403) a user referenced by a bot or with open tickets;
32
+ * - restoring refuses (402) when the plan has no free user slot.
33
+ * @permissions users.view OR users.archive
34
+ */
35
+ archiveWithValidation(
36
+ id: string,
37
+ body: ArchiveUserPayload,
38
+ headers?: Record<string, string>,
39
+ ): Promise<User> {
40
+ return this.client.post<User, ArchiveUserPayload>(
41
+ `${this.basePath}/${id}/archive`,
42
+ body,
43
+ headers,
44
+ )
45
+ }
46
+
47
+ /**
48
+ * Previews a bulk archive/unarchive without changing anything.
49
+ * @permissions users.archive
50
+ */
51
+ previewBulkStatus(
52
+ body: BulkUsersStatusPayload,
53
+ headers?: Record<string, string>,
54
+ ): Promise<BulkUsersStatusPreview> {
55
+ return this.client.post<BulkUsersStatusPreview, BulkUsersStatusPayload>(
56
+ `${this.basePath}/bulk-status/preview`,
57
+ body,
58
+ headers,
59
+ )
60
+ }
61
+
62
+ /**
63
+ * Archives or unarchives up to 100 users at once.
64
+ * @permissions users.archive
65
+ */
66
+ bulkStatus(
67
+ body: BulkUsersStatusPayload,
68
+ headers?: Record<string, string>,
69
+ ): Promise<BulkUsersStatusResult> {
70
+ return this.client.post<BulkUsersStatusResult, BulkUsersStatusPayload>(
71
+ `${this.basePath}/bulk-status`,
72
+ body,
73
+ headers,
74
+ )
75
+ }
76
+
77
+ /**
78
+ * Exports the users of the account as CSV. Resolves to the CSV text.
79
+ * @permissions users.export
80
+ */
81
+ exportCsv(body: ExportUsersPayload, headers?: Record<string, string>): Promise<string> {
82
+ return this.client.post<string, ExportUsersPayload>(`${this.basePath}/export`, body, headers)
83
+ }
84
+
85
+ /**
86
+ * Validates a CSV file previously uploaded through `FilesApi` before importing it.
87
+ * @permissions users.create
88
+ */
89
+ validateImport(
90
+ body: ValidateUserImportPayload,
91
+ headers?: Record<string, string>,
92
+ ): Promise<ValidateUserImportResult> {
93
+ return this.client.post<ValidateUserImportResult, ValidateUserImportPayload>(
94
+ `${this.basePath}/import/validate`,
95
+ body,
96
+ headers,
97
+ )
98
+ }
99
+
100
+ /**
101
+ * Starts the import of a CSV file previously uploaded through `FilesApi`. The import runs
102
+ * in the background (the route answers `202 Accepted`).
103
+ * @permissions users.create
104
+ */
105
+ startImport(
106
+ body: ImportUsersPayload,
107
+ headers?: Record<string, string>,
108
+ ): Promise<ImportUsersResult> {
109
+ return this.client.post<ImportUsersResult, ImportUsersPayload>(
110
+ `${this.basePath}/import`,
111
+ body,
112
+ headers,
113
+ )
114
+ }
115
+
116
+ /**
117
+ * Turns off the two-factor authentication (OTP) of a user.
118
+ * @permissions users.update AND myAccount.update
119
+ */
120
+ disableOtpAuth(id: string, headers?: Record<string, string>): Promise<User> {
121
+ return this.client.post<User>(`${this.basePath}/${id}/disable-otp-auth`, undefined, headers)
122
+ }
123
+
124
+ /**
125
+ * Clears the login throttling of a user blocked by too many failed attempts.
126
+ * @permissions users.update
127
+ */
128
+ enableAccess(id: string, headers?: Record<string, string>): Promise<EnableUserAccessResult> {
129
+ return this.client.post<EnableUserAccessResult>(
130
+ `${this.basePath}/${id}/enable-access`,
131
+ undefined,
132
+ headers,
133
+ )
134
+ }
135
+
136
+ /**
137
+ * Returns the contacts pinned by the logged user.
138
+ * @permissions contacts.pin
139
+ */
140
+ getPinnedContacts(headers?: Record<string, string>): Promise<Contact[]> {
141
+ return this.client.get<Contact[]>(`${this.basePath}/pin`, headers)
142
+ }
143
+
144
+ /**
145
+ * Pins the contact for the logged user, or unpins it when already pinned. A user can pin
146
+ * up to 3 contacts. Resolves to the created link rows when pinning, or to the number of
147
+ * removed links when unpinning.
148
+ * @permissions contacts.pin
149
+ */
150
+ togglePinContact(
151
+ body: TogglePinContactPayload,
152
+ headers?: Record<string, string>,
153
+ ): Promise<TogglePinContactResult> {
154
+ return this.client.patch<TogglePinContactResult, TogglePinContactPayload>(
155
+ `${this.basePath}/pin`,
156
+ body,
157
+ headers,
158
+ )
159
+ }
9
160
  }
@@ -9,21 +9,39 @@ export type UserPreferences = {
9
9
  isShowTagsChat?: boolean
10
10
  }
11
11
 
12
+ export type UserData = {
13
+ /** Push token of the mobile app; cleared (`null`) when the user is archived. */
14
+ expoPushToken?: string | null
15
+ [key: string]: unknown
16
+ }
17
+
18
+ /** A connection the user is restricted to (`GET /users/:id` of a non-admin user). */
19
+ export type UserRestrictedService = {
20
+ id: string
21
+ name: string
22
+ }
23
+
24
+ /**
25
+ * A user as the API answers it (`userTransformer`). `isSuperAdmin`, `active` and `offlineAt` exist in
26
+ * the database but are never answered.
27
+ */
12
28
  export type User = {
13
29
  id: string
14
30
  name: string
15
31
  email: string
16
32
  phoneNumber: string | null
17
33
  branch: string | null
18
- isSuperAdmin: boolean
19
34
  isClientUser: boolean
20
- active: boolean
21
35
  language: string
22
36
  isFirstLogin: boolean
23
37
  status: UserStatus
24
38
  clientsStatus: Record<string, string> | null
25
- offlineAt: string | null
26
39
  isActiveInternalChat: boolean
40
+ /** Token of the internal chat; `null` right after the creation, regenerated on every write of the user. */
41
+ internalChatToken: string | null
42
+ /** Set by the backend from an internal flag of the user. */
43
+ noteReference: boolean
44
+ data: UserData
27
45
  passwordExpiresAt: string | null
28
46
  otpAuthActive: boolean | null
29
47
  preferences: UserPreferences
@@ -37,6 +55,8 @@ export type User = {
37
55
  timetable?: Timetable | null
38
56
  roles?: Role[]
39
57
  departments?: Department[]
58
+ /** Only in `GET /users/:id`, for a user without an admin role. */
59
+ restrictedServices?: UserRestrictedService[]
40
60
  }
41
61
 
42
62
  export type UserRelationships = 'timetable' | 'roles' | 'departments'
@@ -69,3 +89,124 @@ export type UpdateUserPayload = {
69
89
  language?: string
70
90
  archivedAt?: string | null
71
91
  }
92
+
93
+ /** Body for `POST /users/:id/archive`. */
94
+ export type ArchiveUserPayload = {
95
+ /** `true` archives the user; `false` restores it. */
96
+ archive: boolean
97
+ }
98
+
99
+ /** Body for `POST /users/bulk-status` and `POST /users/bulk-status/preview` (1 to 100 IDs). */
100
+ export type BulkUsersStatusPayload = {
101
+ ids: string[]
102
+ archive: boolean
103
+ }
104
+
105
+ export type BulkUsersStatusScenario =
106
+ | 'emptySelection'
107
+ | 'noneEligible'
108
+ | 'allEligible'
109
+ | 'partiallyBlocked'
110
+ | 'onlySelfBlocked'
111
+ | 'planLimitExceeded'
112
+
113
+ /** Read-only preview of a bulk status change: nothing is written. */
114
+ export type BulkUsersStatusPreview = {
115
+ scenario: BulkUsersStatusScenario
116
+ totalSelected: number
117
+ /** Users already in the target status. */
118
+ noop: number
119
+ eligible: number
120
+ /** Users that cannot change (e.g. referenced by a bot, or the acting user). */
121
+ blocked: number
122
+ blockedSelf: boolean
123
+ /** Free plan slots when unarchiving; `null` when archiving. */
124
+ planSlotsAvailable: number | null
125
+ }
126
+
127
+ export type BulkUsersStatusResult = {
128
+ changed: number
129
+ noop: number
130
+ blocked: number
131
+ failed: number
132
+ changedIds: string[]
133
+ /** IDs of the batch that no longer exist in the account. */
134
+ missingIds: string[]
135
+ }
136
+
137
+ /** Body for `POST /users/export`. */
138
+ export type ExportUsersPayload = {
139
+ filter: 'all' | 'active' | 'archived'
140
+ }
141
+
142
+ /** A file previously uploaded through `FilesApi`. */
143
+ export type UserImportFile = {
144
+ id: string
145
+ name: string
146
+ mimetype: string
147
+ url?: string
148
+ urlToUpload?: string
149
+ }
150
+
151
+ /** Body for `POST /users/import/validate`. */
152
+ export type ValidateUserImportPayload = {
153
+ file: UserImportFile
154
+ }
155
+
156
+ export type UserImportRowsSummary = {
157
+ total: number
158
+ valid: number
159
+ invalid: number
160
+ hasInvalid: boolean
161
+ }
162
+
163
+ export type ValidateUserImportResult = {
164
+ licenses: {
165
+ requestedRows: number
166
+ available: number
167
+ exceeded: boolean
168
+ }
169
+ columns: {
170
+ required: { name: string; found: boolean }[]
171
+ missingRequired: string[]
172
+ optionalFound: string[]
173
+ }
174
+ rows: UserImportRowsSummary
175
+ /** Same rows validated with the optional columns applied. */
176
+ rowsWithOptionalColumns: UserImportRowsSummary
177
+ }
178
+
179
+ /** Body for `POST /users/import`. */
180
+ export type ImportUsersPayload = {
181
+ file: UserImportFile
182
+ useOptionalColumns?: boolean
183
+ }
184
+
185
+ /** The import runs in the background (the route answers `202 Accepted`). */
186
+ export type ImportUsersResult = {
187
+ importId: string
188
+ }
189
+
190
+ export type EnableUserAccessResult = {
191
+ success: boolean
192
+ message: string
193
+ }
194
+
195
+ /** Body for `PATCH /users/pin`: toggles the pin of a contact for the logged user (max. 3). */
196
+ export type TogglePinContactPayload = {
197
+ contactId: string
198
+ }
199
+
200
+ /** Link row returned by `PATCH /users/pin` when the contact gets pinned. */
201
+ export type UserPinnedContactLink = {
202
+ userId: string
203
+ contactId: string
204
+ createdAt: string
205
+ updatedAt: string
206
+ }
207
+
208
+ /**
209
+ * Result of `PATCH /users/pin`: the created link rows when the contact gets pinned, or the
210
+ * number of removed links when it gets unpinned.
211
+ */
212
+ export type TogglePinContactResult = UserPinnedContactLink[] | number