@ikatec/digisac-api-sdk 4.14.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/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/payloads.test-d.ts +39 -1
- package/src/core/BaseReadApi.ts +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilesApi.d.ts","sourceRoot":"","sources":["../../../src/apis/files/FilesApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,KAAK,EACV,IAAI,EACJ,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"FilesApi.d.ts","sourceRoot":"","sources":["../../../src/apis/files/FilesApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpD,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC1E,OAAO,KAAK,EACV,IAAI,EACJ,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACrB,MAAM,SAAS,CAAA;AAEhB;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,WAAW,CACvC,IAAI,EACJ,iBAAiB,EACjB,iBAAiB,EACjB,SAAS,CAAC,IAAI,CAAC,EACf,gBAAgB,CACjB;gBACa,MAAM,EAAE,SAAS;IAI7B,wFAAwF;IAC/E,OAAO,CACd,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG;QAAE,QAAQ,EAAE,KAAK,CAAA;KAAE,EAC5C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,EAAE,CAAC;IAClB,wFAAwF;IAC/E,OAAO,CACd,KAAK,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG;QAAE,QAAQ,CAAC,EAAE,IAAI,CAAA;KAAE,EAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC3B,wFAAwF;IAC/E,OAAO,CACd,KAAK,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,EACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC;IAQpC,wFAAwF;IAC/E,MAAM,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAIhG,4FAA4F;IACnF,OAAO,CACd,EAAE,EAAE,MAAM,EACV,KAAK,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,EAC1B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC;IAIhB,4FAA4F;IACnF,UAAU,CACjB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,iBAAiB,EACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC;IAIhB;;;OAGG;IACM,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAI5F;;;;;;OAMG;IACH,cAAc,CACZ,IAAI,EAAE,qBAAqB,EAC3B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,oBAAoB,CAAC;IAQhC;;;;;;;OAOG;IACH,aAAa,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAQ5F;;;;;;OAMG;IACG,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAMtF"}
|
|
@@ -38,12 +38,24 @@ var FilesApi = class extends BaseCrudApi {
|
|
|
38
38
|
* Creates the record of a file and returns the signed URL to upload its content: send the
|
|
39
39
|
* content with {@link upload}. An audio that must be converted (a voice message for the
|
|
40
40
|
* connection) is sent in `base64` instead, and then no `urlToUpload` is returned. `name` and
|
|
41
|
-
* `mimetype` are required (500 without them; an audio without `name` is named `recording`).
|
|
41
|
+
* `mimetype` are required (500 without them; an audio without `name` is named `recording`). With
|
|
42
|
+
* `serviceId`, an extension the account blocks for the type of the connection answers 403.
|
|
42
43
|
*/
|
|
43
44
|
createToUpload(body, headers) {
|
|
44
45
|
return this.client.post(`${this.basePath}/create-to-upload`, body, headers);
|
|
45
46
|
}
|
|
46
47
|
/**
|
|
48
|
+
* Copies the content of a file. With `urlToUpload` (the signed URL of a record created by
|
|
49
|
+
* {@link createToUpload} with the same `mimetype` as the file copied), the content is sent to that
|
|
50
|
+
* record and the answer is the `fileId` given; without it, a new record (`attachedType`
|
|
51
|
+
* `'message.file'`) is created with the copied content and its id is the answer, and with
|
|
52
|
+
* `serviceId` an extension the account blocks for the type of the connection answers 403. An
|
|
53
|
+
* unknown `fileId` answers 500.
|
|
54
|
+
*/
|
|
55
|
+
duplicateFile(body, headers) {
|
|
56
|
+
return this.client.post(`${this.basePath}/duplicate-file`, body, headers);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
47
59
|
* Uploads the content of a file to the `urlToUpload` returned by {@link createToUpload}. The URL
|
|
48
60
|
* is signed by the storage, so the request goes straight to it, without the API token (the
|
|
49
61
|
* storage refuses an `Authorization` header). `mimetype` must be the one sent to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilesApi.mjs","names":[],"sources":["../../../src/apis/files/FilesApi.ts"],"sourcesContent":["import type { ApiClient } from '../../core/ApiClient'\nimport { BaseCrudApi } from '../../core/BaseCrudApi'\nimport { requestExternal } from '../../core/requestExternal'\nimport type { GetByIdQuery, ListQuery, Paginated } from '../../core/types'\nimport type {\n File,\n CreateFilePayload,\n UpdateFilePayload,\n CreateToUploadPayload,\n CreateToUploadResult,\n DeleteFileResult,\n} from './types'\n\n/**\n * Files of the account. The backend serves `POST /files` (the record only, no content),\n * `POST /files/create-to-upload` (record + signed URL to upload the content), `POST\n * /files/duplicate-file`, `DELETE /files/:id` and the signed download `GET\n * /files/:accountId/:filename`; there is no list, read by id nor update.\n */\nexport class FilesApi extends BaseCrudApi<\n File,\n CreateFilePayload,\n UpdateFilePayload,\n ListQuery<File>,\n DeleteFileResult\n> {\n constructor(client: ApiClient) {\n super(client, '/files')\n }\n\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query: ListQuery<File> & { paginate: false },\n headers?: Record<string, string>,\n ): Promise<File[]>\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query?: ListQuery<File> & { paginate?: true },\n headers?: Record<string, string>,\n ): Promise<Paginated<File>>\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query?: ListQuery<File>,\n headers?: Record<string, string>,\n ): Promise<Paginated<File> | File[]>\n override getMany(\n query?: ListQuery<File>,\n headers?: Record<string, string>,\n ): Promise<Paginated<File> | File[]> {\n return super.getMany(query, headers)\n }\n\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getOne(query?: ListQuery<File>, headers?: Record<string, string>): Promise<File | null> {\n return super.getOne(query, headers)\n }\n\n /** @deprecated Not served: the backend has no GET /files/:id, so this route answers 404. */\n override getById(\n id: string,\n query?: GetByIdQuery<File>,\n headers?: Record<string, string>,\n ): Promise<File> {\n return super.getById(id, query, headers)\n }\n\n /** @deprecated Not served: the backend has no PUT /files/:id, so this route answers 404. */\n override updateById(\n id: string,\n body: UpdateFilePayload,\n headers?: Record<string, string>,\n ): Promise<File> {\n return super.updateById(id, body, headers)\n }\n\n /**\n * Deletes a file. Resolves `{ success: true }`, also for an unknown id; a file of another\n * account answers 403.\n */\n override deleteById(id: string, headers?: Record<string, string>): Promise<DeleteFileResult> {\n return super.deleteById(id, headers)\n }\n\n /**\n * Creates the record of a file and returns the signed URL to upload its content: send the\n * content with {@link upload}. An audio that must be converted (a voice message for the\n * connection) is sent in `base64` instead, and then no `urlToUpload` is returned. `name` and\n * `mimetype` are required (500 without them; an audio without `name` is named `recording`).\n */\n createToUpload(\n body: CreateToUploadPayload,\n headers?: Record<string, string>,\n ): Promise<CreateToUploadResult> {\n return this.client.post<CreateToUploadResult, CreateToUploadPayload>(\n `${this.basePath}/create-to-upload`,\n body,\n headers,\n )\n }\n\n /**\n * Uploads the content of a file to the `urlToUpload` returned by {@link createToUpload}. The URL\n * is signed by the storage, so the request goes straight to it, without the API token (the\n * storage refuses an `Authorization` header). `mimetype` must be the one sent to\n * `createToUpload`. Sent through `client.requestExternal` (the global `fetch` for a client\n * without it). A non-2xx answer of the storage throws `ApiError`.\n */\n async upload(urlToUpload: string, content: BodyInit, mimetype: string): Promise<void> {\n const headers = { 'Content-Type': mimetype }\n await (this.client.requestExternal\n ? this.client.requestExternal('PUT', urlToUpload, content, headers)\n : requestExternal('PUT', urlToUpload, content, headers))\n }\n}\n"],"mappings":";;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"FilesApi.mjs","names":[],"sources":["../../../src/apis/files/FilesApi.ts"],"sourcesContent":["import type { ApiClient } from '../../core/ApiClient'\nimport { BaseCrudApi } from '../../core/BaseCrudApi'\nimport { requestExternal } from '../../core/requestExternal'\nimport type { GetByIdQuery, ListQuery, Paginated } from '../../core/types'\nimport type {\n File,\n CreateFilePayload,\n UpdateFilePayload,\n CreateToUploadPayload,\n CreateToUploadResult,\n DeleteFileResult,\n DuplicateFilePayload,\n} from './types'\n\n/**\n * Files of the account. The backend serves `POST /files` (the record only, no content),\n * `POST /files/create-to-upload` (record + signed URL to upload the content), `POST\n * /files/duplicate-file`, `DELETE /files/:id` and the signed download `GET\n * /files/:accountId/:filename`; there is no list, read by id nor update.\n */\nexport class FilesApi extends BaseCrudApi<\n File,\n CreateFilePayload,\n UpdateFilePayload,\n ListQuery<File>,\n DeleteFileResult\n> {\n constructor(client: ApiClient) {\n super(client, '/files')\n }\n\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query: ListQuery<File> & { paginate: false },\n headers?: Record<string, string>,\n ): Promise<File[]>\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query?: ListQuery<File> & { paginate?: true },\n headers?: Record<string, string>,\n ): Promise<Paginated<File>>\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getMany(\n query?: ListQuery<File>,\n headers?: Record<string, string>,\n ): Promise<Paginated<File> | File[]>\n override getMany(\n query?: ListQuery<File>,\n headers?: Record<string, string>,\n ): Promise<Paginated<File> | File[]> {\n return super.getMany(query, headers)\n }\n\n /** @deprecated Not served: the backend has no GET /files, so this route answers 404. */\n override getOne(query?: ListQuery<File>, headers?: Record<string, string>): Promise<File | null> {\n return super.getOne(query, headers)\n }\n\n /** @deprecated Not served: the backend has no GET /files/:id, so this route answers 404. */\n override getById(\n id: string,\n query?: GetByIdQuery<File>,\n headers?: Record<string, string>,\n ): Promise<File> {\n return super.getById(id, query, headers)\n }\n\n /** @deprecated Not served: the backend has no PUT /files/:id, so this route answers 404. */\n override updateById(\n id: string,\n body: UpdateFilePayload,\n headers?: Record<string, string>,\n ): Promise<File> {\n return super.updateById(id, body, headers)\n }\n\n /**\n * Deletes a file. Resolves `{ success: true }`, also for an unknown id; a file of another\n * account answers 403.\n */\n override deleteById(id: string, headers?: Record<string, string>): Promise<DeleteFileResult> {\n return super.deleteById(id, headers)\n }\n\n /**\n * Creates the record of a file and returns the signed URL to upload its content: send the\n * content with {@link upload}. An audio that must be converted (a voice message for the\n * connection) is sent in `base64` instead, and then no `urlToUpload` is returned. `name` and\n * `mimetype` are required (500 without them; an audio without `name` is named `recording`). With\n * `serviceId`, an extension the account blocks for the type of the connection answers 403.\n */\n createToUpload(\n body: CreateToUploadPayload,\n headers?: Record<string, string>,\n ): Promise<CreateToUploadResult> {\n return this.client.post<CreateToUploadResult, CreateToUploadPayload>(\n `${this.basePath}/create-to-upload`,\n body,\n headers,\n )\n }\n\n /**\n * Copies the content of a file. With `urlToUpload` (the signed URL of a record created by\n * {@link createToUpload} with the same `mimetype` as the file copied), the content is sent to that\n * record and the answer is the `fileId` given; without it, a new record (`attachedType`\n * `'message.file'`) is created with the copied content and its id is the answer, and with\n * `serviceId` an extension the account blocks for the type of the connection answers 403. An\n * unknown `fileId` answers 500.\n */\n duplicateFile(body: DuplicateFilePayload, headers?: Record<string, string>): Promise<string> {\n return this.client.post<string, DuplicateFilePayload>(\n `${this.basePath}/duplicate-file`,\n body,\n headers,\n )\n }\n\n /**\n * Uploads the content of a file to the `urlToUpload` returned by {@link createToUpload}. The URL\n * is signed by the storage, so the request goes straight to it, without the API token (the\n * storage refuses an `Authorization` header). `mimetype` must be the one sent to\n * `createToUpload`. Sent through `client.requestExternal` (the global `fetch` for a client\n * without it). A non-2xx answer of the storage throws `ApiError`.\n */\n async upload(urlToUpload: string, content: BodyInit, mimetype: string): Promise<void> {\n const headers = { 'Content-Type': mimetype }\n await (this.client.requestExternal\n ? this.client.requestExternal('PUT', urlToUpload, content, headers)\n : requestExternal('PUT', urlToUpload, content, headers))\n }\n}\n"],"mappings":";;;;;;;;;;AAoBA,IAAa,WAAb,cAA8B,YAM5B;CACA,YAAY,QAAmB;AAC7B,QAAM,QAAQ,SAAS;;CAkBzB,AAAS,QACP,OACA,SACmC;AACnC,SAAO,MAAM,QAAQ,OAAO,QAAQ;;;CAItC,AAAS,OAAO,OAAyB,SAAwD;AAC/F,SAAO,MAAM,OAAO,OAAO,QAAQ;;;CAIrC,AAAS,QACP,IACA,OACA,SACe;AACf,SAAO,MAAM,QAAQ,IAAI,OAAO,QAAQ;;;CAI1C,AAAS,WACP,IACA,MACA,SACe;AACf,SAAO,MAAM,WAAW,IAAI,MAAM,QAAQ;;;;;;CAO5C,AAAS,WAAW,IAAY,SAA6D;AAC3F,SAAO,MAAM,WAAW,IAAI,QAAQ;;;;;;;;;CAUtC,eACE,MACA,SAC+B;AAC/B,SAAO,KAAK,OAAO,KACjB,GAAG,KAAK,SAAS,oBACjB,MACA,QACD;;;;;;;;;;CAWH,cAAc,MAA4B,SAAmD;AAC3F,SAAO,KAAK,OAAO,KACjB,GAAG,KAAK,SAAS,kBACjB,MACA,QACD;;;;;;;;;CAUH,MAAM,OAAO,aAAqB,SAAmB,UAAiC;EACpF,MAAM,UAAU,EAAE,gBAAgB,UAAU;AAC5C,SAAO,KAAK,OAAO,kBACf,KAAK,OAAO,gBAAgB,OAAO,aAAa,SAAS,QAAQ,GACjE,gBAAgB,OAAO,aAAa,SAAS,QAAQ"}
|
|
@@ -60,7 +60,10 @@ export type CreateToUploadPayload = {
|
|
|
60
60
|
mimetype: string;
|
|
61
61
|
/** What the file is attached to, e.g. `'message.file'` (default), `'campaign.file'`, `'quickreply.file'`. */
|
|
62
62
|
attachedType?: string;
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Connection that will send the file: used to decide the audio conversion and to apply the
|
|
65
|
+
* extensions the account blocks for its type (403).
|
|
66
|
+
*/
|
|
64
67
|
serviceId?: string;
|
|
65
68
|
/** Audio sent as a voice message (default `true`). */
|
|
66
69
|
isPtt?: boolean;
|
|
@@ -77,6 +80,21 @@ export type CreateToUploadResult = {
|
|
|
77
80
|
name: string;
|
|
78
81
|
mimetype: string;
|
|
79
82
|
};
|
|
83
|
+
/** Body of `FilesApi.duplicateFile`. */
|
|
84
|
+
export type DuplicateFilePayload = {
|
|
85
|
+
/** The file whose content is copied. */
|
|
86
|
+
fileId: string;
|
|
87
|
+
/**
|
|
88
|
+
* Signed URL of a record created by `FilesApi.createToUpload`, with the same `mimetype` as the file
|
|
89
|
+
* copied, to receive the content. Without it, a new record is created with the copied content.
|
|
90
|
+
*/
|
|
91
|
+
urlToUpload?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Connection that will send the new record (used only without `urlToUpload`): the extensions the
|
|
94
|
+
* account blocks for its type apply.
|
|
95
|
+
*/
|
|
96
|
+
serviceId?: string;
|
|
97
|
+
};
|
|
80
98
|
/** Result of `FilesApi.deleteById`. */
|
|
81
99
|
export type DeleteFileResult = {
|
|
82
100
|
success: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/files/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,QAAQ,GACR,IAAI,GACJ,gBAAgB,GAChB,QAAQ,GACR,iBAAiB,GACjB,WAAW,GACX,MAAM,CAAA;AAEV,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,sEAAsE;IACtE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,sEAAsE;IACtE,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,yCAAyC;AACzC,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,6GAA6G;IAC7G,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/apis/files/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,aAAa,CAAC,EAAE,iBAAiB,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,WAAW,GACnB,IAAI,GACJ,QAAQ,GACR,IAAI,GACJ,gBAAgB,GAChB,QAAQ,GACR,iBAAiB,GACjB,WAAW,GACX,MAAM,CAAA;AAEV,MAAM,MAAM,IAAI,GAAG;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,4DAA4D;IAC5D,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,sEAAsE;IACtE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,sEAAsE;IACtE,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAED,yCAAyC;AACzC,MAAM,MAAM,qBAAqB,GAAG;IAClC,gFAAgF;IAChF,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,6GAA6G;IAC7G,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sDAAsD;IACtD,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,sEAAsE;IACtE,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,2CAA2C;AAC3C,MAAM,MAAM,oBAAoB,GAAG;IACjC,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG;IACjC,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,uCAAuC;AACvC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA"}
|
|
@@ -4,8 +4,8 @@ const require_core_BaseApi = require('./BaseApi.cjs');
|
|
|
4
4
|
//#region src/core/BaseReadApi.ts
|
|
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
|
var BaseReadApi = class extends require_core_BaseApi.BaseApi {
|
|
11
11
|
basePath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseReadApi.cjs","names":["BaseApi"],"sources":["../../src/core/BaseReadApi.ts"],"sourcesContent":["import type { ApiClient } from './ApiClient'\nimport { BaseApi } from './BaseApi'\nimport type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from './types'\n\n/**\n * Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of\n * `BaseCrudApi`, and of the resources the backend
|
|
1
|
+
{"version":3,"file":"BaseReadApi.cjs","names":["BaseApi"],"sources":["../../src/core/BaseReadApi.ts"],"sourcesContent":["import type { ApiClient } from './ApiClient'\nimport { BaseApi } from './BaseApi'\nimport type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from './types'\n\n/**\n * Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of\n * `BaseCrudApi`, and of the resources the backend serves for reading, plus their own actions (e.g.\n * permissions, auth history).\n */\nexport class BaseReadApi<TResponse, TQuery extends ListQuery<TResponse> = ListQuery<TResponse>>\n extends BaseApi\n implements HasGetMany<TResponse, TQuery>, HasGetById<TResponse>\n{\n public readonly basePath: string\n\n constructor(client: ApiClient, basePath: string) {\n super(client)\n this.basePath = basePath.replace(/\\/$/, '')\n }\n\n getMany(\n query: TQuery & { paginate: false },\n headers?: Record<string, string>,\n ): Promise<TResponse[]>\n getMany(\n query?: TQuery & { paginate?: true },\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse>>\n getMany(\n query?: TQuery,\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse> | TResponse[]>\n getMany(\n query?: TQuery,\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse> | TResponse[]> {\n const queryString = this.toQueryString(query)\n return this.client.get<Paginated<TResponse> | TResponse[]>(\n `${this.basePath}${queryString}`,\n headers,\n )\n }\n\n /**\n * Returns the first resource matching the query, or `null` when none match.\n * Convenience over `getMany` with `limit: 1`.\n * @permissions $resourceName.view\n */\n async getOne(query?: TQuery, headers?: Record<string, string>): Promise<TResponse | null> {\n const results = await this.getMany(\n { ...query, limit: 1, paginate: false } as TQuery & { paginate: false },\n headers,\n )\n // An empty response body resolves to `undefined`.\n return results?.[0] ?? null\n }\n\n /**\n * Returns a single resource by ID. When it does not exist most routes answer 200 with an empty\n * body, which resolves to `undefined`.\n * @permissions $resourceName.view\n */\n getById(\n id: string,\n query?: GetByIdQuery<TResponse>,\n headers?: Record<string, string>,\n ): Promise<TResponse> {\n return this.client.get<TResponse>(`${this.basePath}/${id}${this.toQueryString(query)}`, headers)\n }\n}\n"],"mappings":";;;;;;;;;AASA,IAAa,cAAb,cACUA,6BAEV;CACE,AAAgB;CAEhB,YAAY,QAAmB,UAAkB;AAC/C,QAAM,OAAO;AACb,OAAK,WAAW,SAAS,QAAQ,OAAO,GAAG;;CAe7C,QACE,OACA,SAC6C;EAC7C,MAAM,cAAc,KAAK,cAAc,MAAM;AAC7C,SAAO,KAAK,OAAO,IACjB,GAAG,KAAK,WAAW,eACnB,QACD;;;;;;;CAQH,MAAM,OAAO,OAAgB,SAA6D;AAMxF,UALgB,MAAM,KAAK,QACzB;GAAE,GAAG;GAAO,OAAO;GAAG,UAAU;GAAO,EACvC,QACD,IAEgB,MAAM;;;;;;;CAQzB,QACE,IACA,OACA,SACoB;AACpB,SAAO,KAAK,OAAO,IAAe,GAAG,KAAK,SAAS,GAAG,KAAK,KAAK,cAAc,MAAM,IAAI,QAAQ"}
|
|
@@ -3,8 +3,8 @@ import { BaseApi } from './BaseApi';
|
|
|
3
3
|
import type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from './types';
|
|
4
4
|
/**
|
|
5
5
|
* Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of
|
|
6
|
-
* `BaseCrudApi`, and of the resources the backend
|
|
7
|
-
* auth history).
|
|
6
|
+
* `BaseCrudApi`, and of the resources the backend serves for reading, plus their own actions (e.g.
|
|
7
|
+
* permissions, auth history).
|
|
8
8
|
*/
|
|
9
9
|
export declare class BaseReadApi<TResponse, TQuery extends ListQuery<TResponse> = ListQuery<TResponse>> extends BaseApi implements HasGetMany<TResponse, TQuery>, HasGetById<TResponse> {
|
|
10
10
|
readonly basePath: string;
|
|
@@ -3,8 +3,8 @@ import { BaseApi } from "./BaseApi.mjs";
|
|
|
3
3
|
//#region src/core/BaseReadApi.ts
|
|
4
4
|
/**
|
|
5
5
|
* Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of
|
|
6
|
-
* `BaseCrudApi`, and of the resources the backend
|
|
7
|
-
* auth history).
|
|
6
|
+
* `BaseCrudApi`, and of the resources the backend serves for reading, plus their own actions (e.g.
|
|
7
|
+
* permissions, auth history).
|
|
8
8
|
*/
|
|
9
9
|
var BaseReadApi = class extends BaseApi {
|
|
10
10
|
basePath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseReadApi.mjs","names":[],"sources":["../../src/core/BaseReadApi.ts"],"sourcesContent":["import type { ApiClient } from './ApiClient'\nimport { BaseApi } from './BaseApi'\nimport type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from './types'\n\n/**\n * Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of\n * `BaseCrudApi`, and of the resources the backend
|
|
1
|
+
{"version":3,"file":"BaseReadApi.mjs","names":[],"sources":["../../src/core/BaseReadApi.ts"],"sourcesContent":["import type { ApiClient } from './ApiClient'\nimport { BaseApi } from './BaseApi'\nimport type { GetByIdQuery, HasGetById, HasGetMany, ListQuery, Paginated } from './types'\n\n/**\n * Generic read-only resource: `getMany`, `getOne` and `getById` under `basePath`. Base of\n * `BaseCrudApi`, and of the resources the backend serves for reading, plus their own actions (e.g.\n * permissions, auth history).\n */\nexport class BaseReadApi<TResponse, TQuery extends ListQuery<TResponse> = ListQuery<TResponse>>\n extends BaseApi\n implements HasGetMany<TResponse, TQuery>, HasGetById<TResponse>\n{\n public readonly basePath: string\n\n constructor(client: ApiClient, basePath: string) {\n super(client)\n this.basePath = basePath.replace(/\\/$/, '')\n }\n\n getMany(\n query: TQuery & { paginate: false },\n headers?: Record<string, string>,\n ): Promise<TResponse[]>\n getMany(\n query?: TQuery & { paginate?: true },\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse>>\n getMany(\n query?: TQuery,\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse> | TResponse[]>\n getMany(\n query?: TQuery,\n headers?: Record<string, string>,\n ): Promise<Paginated<TResponse> | TResponse[]> {\n const queryString = this.toQueryString(query)\n return this.client.get<Paginated<TResponse> | TResponse[]>(\n `${this.basePath}${queryString}`,\n headers,\n )\n }\n\n /**\n * Returns the first resource matching the query, or `null` when none match.\n * Convenience over `getMany` with `limit: 1`.\n * @permissions $resourceName.view\n */\n async getOne(query?: TQuery, headers?: Record<string, string>): Promise<TResponse | null> {\n const results = await this.getMany(\n { ...query, limit: 1, paginate: false } as TQuery & { paginate: false },\n headers,\n )\n // An empty response body resolves to `undefined`.\n return results?.[0] ?? null\n }\n\n /**\n * Returns a single resource by ID. When it does not exist most routes answer 200 with an empty\n * body, which resolves to `undefined`.\n * @permissions $resourceName.view\n */\n getById(\n id: string,\n query?: GetByIdQuery<TResponse>,\n headers?: Record<string, string>,\n ): Promise<TResponse> {\n return this.client.get<TResponse>(`${this.basePath}/${id}${this.toQueryString(query)}`, headers)\n }\n}\n"],"mappings":";;;;;;;;AASA,IAAa,cAAb,cACU,QAEV;CACE,AAAgB;CAEhB,YAAY,QAAmB,UAAkB;AAC/C,QAAM,OAAO;AACb,OAAK,WAAW,SAAS,QAAQ,OAAO,GAAG;;CAe7C,QACE,OACA,SAC6C;EAC7C,MAAM,cAAc,KAAK,cAAc,MAAM;AAC7C,SAAO,KAAK,OAAO,IACjB,GAAG,KAAK,WAAW,eACnB,QACD;;;;;;;CAQH,MAAM,OAAO,OAAgB,SAA6D;AAMxF,UALgB,MAAM,KAAK,QACzB;GAAE,GAAG;GAAO,OAAO;GAAG,UAAU;GAAO,EACvC,QACD,IAEgB,MAAM;;;;;;;CAQzB,QACE,IACA,OACA,SACoB;AACpB,SAAO,KAAK,OAAO,IAAe,GAAG,KAAK,SAAS,GAAG,KAAK,KAAK,cAAc,MAAM,IAAI,QAAQ"}
|
package/package.json
CHANGED
|
@@ -115,4 +115,20 @@ describe('AuthHistoryApi', () => {
|
|
|
115
115
|
await expect(api.getById('missing')).rejects.toThrow('Not found')
|
|
116
116
|
})
|
|
117
117
|
})
|
|
118
|
+
|
|
119
|
+
describe('registerLogout', () => {
|
|
120
|
+
it('calls client.post with /auth-history and an empty body', async () => {
|
|
121
|
+
vi.mocked(client.post).mockResolvedValue(undefined)
|
|
122
|
+
const result = await api.registerLogout()
|
|
123
|
+
expect(client.post).toHaveBeenCalledWith('/auth-history', {}, undefined)
|
|
124
|
+
expect(result).toBeUndefined()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('forwards custom headers', async () => {
|
|
128
|
+
vi.mocked(client.post).mockResolvedValue(undefined)
|
|
129
|
+
const headers = { 'X-Req': 'r-1' }
|
|
130
|
+
await api.registerLogout(headers)
|
|
131
|
+
expect(client.post).toHaveBeenCalledWith('/auth-history', {}, headers)
|
|
132
|
+
})
|
|
133
|
+
})
|
|
118
134
|
})
|
|
@@ -3,8 +3,8 @@ import { BaseReadApi } from '../../core/BaseReadApi'
|
|
|
3
3
|
import type { AuthHistory } from './types'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Authentication events (auth, password_change, logout), read
|
|
7
|
-
* `authHistory.view
|
|
6
|
+
* Authentication events (auth, password_change, logout, 2fa_disabled), read with
|
|
7
|
+
* `authHistory.view`. Non-admin users can only see and access their own records.
|
|
8
8
|
*
|
|
9
9
|
* Since 4.2 `getById` has the base signature `(id, query?, headers?)`: a call written as
|
|
10
10
|
* `getById(id, headers)` with the headers in a variable still compiles, but the headers are sent
|
|
@@ -14,4 +14,12 @@ export class AuthHistoryApi extends BaseReadApi<AuthHistory> {
|
|
|
14
14
|
constructor(client: ApiClient) {
|
|
15
15
|
super(client, '/auth-history')
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Records the `logout` event of the current session, as the web app does before revoking its
|
|
20
|
+
* token (revoke the token apart: this call does not). Resolves with no body.
|
|
21
|
+
*/
|
|
22
|
+
registerLogout(headers?: Record<string, string>): Promise<void> {
|
|
23
|
+
return this.client.post<void, Record<string, never>>(this.basePath, {}, headers)
|
|
24
|
+
}
|
|
17
25
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Account } from '../accounts/types'
|
|
2
2
|
import type { User } from '../users/types'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* `auth`: a login (after the second factor, when active); `password_change`: by the user, by an
|
|
6
|
+
* admin or by the password reset; `logout`: recorded by `AuthHistoryApi.registerLogout`;
|
|
7
|
+
* `2fa_disabled`: the second factor turned off, by the user or by an admin.
|
|
8
|
+
*/
|
|
9
|
+
export type AuthHistoryEvent = 'auth' | 'password_change' | 'logout' | '2fa_disabled'
|
|
5
10
|
|
|
6
11
|
export type AuthHistory = {
|
|
7
12
|
id: string
|
|
@@ -138,6 +138,19 @@ describe('ContactsApi', () => {
|
|
|
138
138
|
await api.getById('cnt-1', undefined, headers)
|
|
139
139
|
expect(client.get).toHaveBeenCalledWith('/contacts/cnt-1', headers)
|
|
140
140
|
})
|
|
141
|
+
|
|
142
|
+
it('sends formatCustomFields and the include of the custom fields in the query', async () => {
|
|
143
|
+
vi.mocked(client.get).mockResolvedValue(contact)
|
|
144
|
+
const query = {
|
|
145
|
+
include: [{ model: 'customFieldValues' as const, include: ['customField' as const] }],
|
|
146
|
+
formatCustomFields: true,
|
|
147
|
+
}
|
|
148
|
+
await api.getById('cnt-1', query)
|
|
149
|
+
expect(client.get).toHaveBeenCalledWith(
|
|
150
|
+
`/contacts/cnt-1?query=${encodeURIComponent(JSON.stringify(query))}`,
|
|
151
|
+
undefined,
|
|
152
|
+
)
|
|
153
|
+
})
|
|
141
154
|
})
|
|
142
155
|
|
|
143
156
|
// ── create ───────────────────────────────────────────────────────
|
|
@@ -7,6 +7,8 @@ import type {
|
|
|
7
7
|
BulkTransferTicketsPayload,
|
|
8
8
|
BulkTransferTicketsResult,
|
|
9
9
|
Contact,
|
|
10
|
+
ContactGetByIdQuery,
|
|
11
|
+
ContactListQuery,
|
|
10
12
|
ContactMediaPage,
|
|
11
13
|
ContactMediaQuery,
|
|
12
14
|
ContactsSelection,
|
|
@@ -24,11 +26,31 @@ import type {
|
|
|
24
26
|
SummarizeTicketResult,
|
|
25
27
|
} from './types'
|
|
26
28
|
|
|
27
|
-
export class ContactsApi extends BaseCrudApi<
|
|
29
|
+
export class ContactsApi extends BaseCrudApi<
|
|
30
|
+
Contact,
|
|
31
|
+
CreateContactPayload,
|
|
32
|
+
UpdateContactPayload,
|
|
33
|
+
ContactListQuery
|
|
34
|
+
> {
|
|
28
35
|
constructor(client: ApiClient) {
|
|
29
36
|
super(client, '/contacts')
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Returns a contact by ID. When it does not exist the route answers 200 with an empty body, which
|
|
41
|
+
* resolves to `undefined`. With `formatCustomFields: true` (and the include
|
|
42
|
+
* `{ model: 'customFieldValues', include: ['customField'] }`), the custom fields come keyed by name
|
|
43
|
+
* in `customFields`.
|
|
44
|
+
* @permissions contacts.view
|
|
45
|
+
*/
|
|
46
|
+
override getById(
|
|
47
|
+
id: string,
|
|
48
|
+
query?: ContactGetByIdQuery,
|
|
49
|
+
headers?: Record<string, string>,
|
|
50
|
+
): Promise<Contact> {
|
|
51
|
+
return super.getById(id, query, headers)
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
/**
|
|
33
55
|
* Downloads the CSV template (`;`-separated header line, in the language of the user) for
|
|
34
56
|
* importing contacts, with columns based on the service type. Answered with no Content-Type.
|
|
@@ -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
|
|
@@ -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'
|
|
@@ -769,3 +776,34 @@ expectAssignable<Awaited<ReturnType<ServicesApi['accessManagement']>>>({
|
|
|
769
776
|
})
|
|
770
777
|
expectNotAssignable<Awaited<ReturnType<ServicesApi['accessManagement']>>>({ message: 'denied' })
|
|
771
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')
|
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
|