yaveon.ecm.d3dwebhooks 0.7.0 → 0.7.2
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/js/Yaveon.ECM.d3d.Client.d.ts +0 -48
- package/js/Yaveon.ECM.d3d.Client.js +4 -73
- package/js/Yaveon.ECM.d3d.Webhook.Client.d.ts +0 -41
- package/js/Yaveon.ECM.d3d.Webhook.Client.js +2 -44
- package/js/Yaveon.ECM.d3d.Webhook.Constants.js +0 -7
- package/js/Yaveon.ECM.d3d.Webhook.SetNumberId.d.ts +1 -0
- package/js/{Yaveon.ECM.d3d.Webhook.SetNumberId.Test.js → Yaveon.ECM.d3d.Webhook.SetNumberId.js} +4 -4
- package/package.json +1 -1
- package/js/Yaveon.ECM.d3d.Webhook.SetNumberId.Test.d.ts +0 -1
|
@@ -3,60 +3,12 @@ export declare class D3dClient {
|
|
|
3
3
|
protected repositoryId: string;
|
|
4
4
|
private defaultHeaders;
|
|
5
5
|
constructor(baseUrl: string, repositoryId: string, accessToken: string);
|
|
6
|
-
/**
|
|
7
|
-
* creates a copy of the default header and sets content-type to 'octet-stream'
|
|
8
|
-
*
|
|
9
|
-
* @returns a copy of the default header and with content-type set to 'octet-stream'
|
|
10
|
-
*/
|
|
11
6
|
private getUploadHeader;
|
|
12
|
-
/**
|
|
13
|
-
* uploads a binary chunk of data to d3 documents
|
|
14
|
-
*
|
|
15
|
-
* @param binaryData - this parameter takes in the upload body as byte array
|
|
16
|
-
* @returns The response object of the webservice call
|
|
17
|
-
*/
|
|
18
7
|
uploadChunk(binaryData: Uint8Array): Promise<any>;
|
|
19
|
-
/**
|
|
20
|
-
* updates a document with properties in the updateBody
|
|
21
|
-
*
|
|
22
|
-
* @param d3dObjectId - string id of the documents object
|
|
23
|
-
* @param updateBody - contains the properties which are supposed to be updated
|
|
24
|
-
* @returns The response object of the webservice call
|
|
25
|
-
*/
|
|
26
8
|
updateDocumentProperties(d3dObjectId: string, updateBody: Object): Promise<any>;
|
|
27
|
-
/**
|
|
28
|
-
* returns the content of a d3 documents file
|
|
29
|
-
*
|
|
30
|
-
* @param d3dDocumentId - string id of the documents object
|
|
31
|
-
* @returns the retrieved documents file content
|
|
32
|
-
*/
|
|
33
9
|
getDocumentContent(d3dDocumentId: string): Promise<any>;
|
|
34
|
-
/**
|
|
35
|
-
*
|
|
36
|
-
* @param d3dObjectId - string id of the documents object
|
|
37
|
-
* @param ownerUpn - upn of the desired owner
|
|
38
|
-
* @param stateValue - string representation of a DmsObjectState
|
|
39
|
-
* @param alterationText - optional: value that contains the alteration reason for documents system
|
|
40
|
-
* @returns the d3dObject update response object
|
|
41
|
-
*/
|
|
42
10
|
setObjectState(d3dObjectId: string, ownerUpn: string, stateValue: string, alterationText?: string): Promise<any>;
|
|
43
|
-
/**
|
|
44
|
-
* returns a valid eTag value
|
|
45
|
-
*
|
|
46
|
-
* @param d3dObjectId - string id of the documents object
|
|
47
|
-
* @returns a valid eTag value string
|
|
48
|
-
*/
|
|
49
11
|
getETag(d3dObjectId: string): Promise<string>;
|
|
50
|
-
/**
|
|
51
|
-
* retrieves an object that represents the current user
|
|
52
|
-
* @returns the current user object
|
|
53
|
-
*/
|
|
54
12
|
getCurrentUserObject(): Promise<any>;
|
|
55
|
-
/**
|
|
56
|
-
* retrieves search results for the given search url
|
|
57
|
-
*
|
|
58
|
-
* @param searchUrlPart - contains the search query
|
|
59
|
-
* @returns the search result object
|
|
60
|
-
*/
|
|
61
13
|
getSearchResults(searchUrlPart: string): Promise<any>;
|
|
62
14
|
}
|
|
@@ -29,7 +29,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
29
29
|
exports.D3dClient = void 0;
|
|
30
30
|
const Constants = __importStar(require("./Yaveon.ECM.d3d.Webhook.Constants"));
|
|
31
31
|
const axios_1 = __importDefault(require("axios"));
|
|
32
|
-
//const axios = require('axios');
|
|
33
32
|
class D3dClient {
|
|
34
33
|
constructor(baseUrl, repositoryId, accessToken) {
|
|
35
34
|
this.baseUrl = baseUrl;
|
|
@@ -39,46 +38,22 @@ class D3dClient {
|
|
|
39
38
|
this.defaultHeaders.Origin = baseUrl;
|
|
40
39
|
axios_1.default.defaults.headers.common = this.defaultHeaders;
|
|
41
40
|
}
|
|
42
|
-
/**
|
|
43
|
-
* creates a copy of the default header and sets content-type to 'octet-stream'
|
|
44
|
-
*
|
|
45
|
-
* @returns a copy of the default header and with content-type set to 'octet-stream'
|
|
46
|
-
*/
|
|
47
41
|
getUploadHeader() {
|
|
48
42
|
let headerCopy = JSON.parse(JSON.stringify(this.defaultHeaders));
|
|
49
43
|
headerCopy['Content-Type'] = "application/octet-stream";
|
|
50
44
|
return headerCopy;
|
|
51
45
|
}
|
|
52
|
-
/**
|
|
53
|
-
* uploads a binary chunk of data to d3 documents
|
|
54
|
-
*
|
|
55
|
-
* @param binaryData - this parameter takes in the upload body as byte array
|
|
56
|
-
* @returns The response object of the webservice call
|
|
57
|
-
*/
|
|
58
46
|
async uploadChunk(binaryData) {
|
|
59
47
|
let uploadHeader = this.getUploadHeader();
|
|
60
48
|
let uploadUrl = this.baseUrl + "/dms/r/" + this.repositoryId + "/blob/chunk/";
|
|
61
49
|
let uploadResponse = await axios_1.default.post(uploadUrl, binaryData, { headers: uploadHeader });
|
|
62
50
|
return uploadResponse;
|
|
63
51
|
}
|
|
64
|
-
/**
|
|
65
|
-
* updates a document with properties in the updateBody
|
|
66
|
-
*
|
|
67
|
-
* @param d3dObjectId - string id of the documents object
|
|
68
|
-
* @param updateBody - contains the properties which are supposed to be updated
|
|
69
|
-
* @returns The response object of the webservice call
|
|
70
|
-
*/
|
|
71
52
|
async updateDocumentProperties(d3dObjectId, updateBody) {
|
|
72
53
|
const updateUrl = this.baseUrl + '/dms/r/' + this.repositoryId + '/o2/' + d3dObjectId;
|
|
73
54
|
let updateResponse = await axios_1.default.put(updateUrl, updateBody);
|
|
74
55
|
return updateResponse;
|
|
75
56
|
}
|
|
76
|
-
/**
|
|
77
|
-
* returns the content of a d3 documents file
|
|
78
|
-
*
|
|
79
|
-
* @param d3dDocumentId - string id of the documents object
|
|
80
|
-
* @returns the retrieved documents file content
|
|
81
|
-
*/
|
|
82
57
|
async getDocumentContent(d3dDocumentId) {
|
|
83
58
|
const fileUrl = this.baseUrl + '/dms/r/' + this.repositoryId + '/o2m/' + d3dDocumentId;
|
|
84
59
|
const getFileResponse = await axios_1.default.get(fileUrl);
|
|
@@ -86,14 +61,6 @@ class D3dClient {
|
|
|
86
61
|
const getFileContentResponse = await axios_1.default.get(fileContentUrl);
|
|
87
62
|
return getFileContentResponse.data;
|
|
88
63
|
}
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
* @param d3dObjectId - string id of the documents object
|
|
92
|
-
* @param ownerUpn - upn of the desired owner
|
|
93
|
-
* @param stateValue - string representation of a DmsObjectState
|
|
94
|
-
* @param alterationText - optional: value that contains the alteration reason for documents system
|
|
95
|
-
* @returns the d3dObject update response object
|
|
96
|
-
*/
|
|
97
64
|
async setObjectState(d3dObjectId, ownerUpn, stateValue, alterationText = '') {
|
|
98
65
|
if (!Constants.DmsObjectStates.isValid(stateValue)) {
|
|
99
66
|
throw stateValue + " is not a valid document state. Valid states are " + Object.values(Constants.DmsObjectStates.States).join(", ");
|
|
@@ -102,11 +69,6 @@ class D3dClient {
|
|
|
102
69
|
stateValue = Constants.FILE_STATUS_RELEASED;
|
|
103
70
|
}
|
|
104
71
|
const updateUrl = this.baseUrl + '/dms/r/' + this.repositoryId + '/o2/' + d3dObjectId + '/v/current';
|
|
105
|
-
/* let updateBody: Map <string,string> = new Map <string,string>([
|
|
106
|
-
['eTag', await this.getETag(d3dObjectId)],
|
|
107
|
-
['state', stateValue],
|
|
108
|
-
['destionationUserOrGroup', ownerUpn]
|
|
109
|
-
]); */
|
|
110
72
|
let updateBody = {
|
|
111
73
|
"eTag": await this.getETag(d3dObjectId),
|
|
112
74
|
"state": stateValue,
|
|
@@ -114,51 +76,20 @@ class D3dClient {
|
|
|
114
76
|
"alterationText": alterationText
|
|
115
77
|
};
|
|
116
78
|
let updateResponse = null;
|
|
117
|
-
|
|
118
|
-
updateResponse = await axios_1.default.put(updateUrl, updateBody);
|
|
119
|
-
}
|
|
120
|
-
catch (ex) {
|
|
121
|
-
debugger;
|
|
122
|
-
}
|
|
79
|
+
updateResponse = await axios_1.default.put(updateUrl, updateBody);
|
|
123
80
|
return updateResponse;
|
|
124
81
|
}
|
|
125
|
-
/**
|
|
126
|
-
* returns a valid eTag value
|
|
127
|
-
*
|
|
128
|
-
* @param d3dObjectId - string id of the documents object
|
|
129
|
-
* @returns a valid eTag value string
|
|
130
|
-
*/
|
|
131
82
|
async getETag(d3dObjectId) {
|
|
132
83
|
const eTagRequestUrl = this.baseUrl + '/dms/r/' + this.repositoryId + '/o2/' + d3dObjectId;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
return eTagResponse.data.eTag;
|
|
136
|
-
}
|
|
137
|
-
catch (ex) {
|
|
138
|
-
throw ex.Message;
|
|
139
|
-
}
|
|
84
|
+
const eTagResponse = await axios_1.default.get(eTagRequestUrl);
|
|
85
|
+
return eTagResponse.data.eTag;
|
|
140
86
|
}
|
|
141
|
-
/**
|
|
142
|
-
* retrieves an object that represents the current user
|
|
143
|
-
* @returns the current user object
|
|
144
|
-
*/
|
|
145
87
|
async getCurrentUserObject() {
|
|
146
88
|
let requestUrl = this.baseUrl + "/identityprovider/validate";
|
|
147
89
|
let response;
|
|
148
|
-
|
|
149
|
-
response = await axios_1.default.get(requestUrl);
|
|
150
|
-
}
|
|
151
|
-
catch (ex) {
|
|
152
|
-
throw ex.Message;
|
|
153
|
-
}
|
|
90
|
+
response = await axios_1.default.get(requestUrl);
|
|
154
91
|
return response.data;
|
|
155
92
|
}
|
|
156
|
-
/**
|
|
157
|
-
* retrieves search results for the given search url
|
|
158
|
-
*
|
|
159
|
-
* @param searchUrlPart - contains the search query
|
|
160
|
-
* @returns the search result object
|
|
161
|
-
*/
|
|
162
93
|
async getSearchResults(searchUrlPart) {
|
|
163
94
|
let resultItems;
|
|
164
95
|
let searchUrl = this.baseUrl + '/dms/r/' + this.repositoryId + '/sr' + searchUrlPart;
|
|
@@ -4,52 +4,11 @@ export declare class D3dWebhookClient extends D3dClient {
|
|
|
4
4
|
private configFileId;
|
|
5
5
|
private currentUserName;
|
|
6
6
|
constructor(baseUrl: string, repositoryId: string, accessToken: string);
|
|
7
|
-
/**
|
|
8
|
-
* updates the config file in d3 documents with the given config object
|
|
9
|
-
*
|
|
10
|
-
* @param configObject - config object that shall be written to d3d documents
|
|
11
|
-
* @param updateBody - contains the necessary metadata
|
|
12
|
-
* @returns {string} contentLocationUri
|
|
13
|
-
*/
|
|
14
7
|
updateConfiguration(configObject: Object, alterationText: string): Promise<any>;
|
|
15
|
-
/**
|
|
16
|
-
* ensures that configuration file id is present. Throws if file can't be found.
|
|
17
|
-
*
|
|
18
|
-
* @returns void
|
|
19
|
-
*/
|
|
20
8
|
private ensureConfigurationFileId;
|
|
21
|
-
/**
|
|
22
|
-
* retrieves the webhook configuration from d3 documents
|
|
23
|
-
*
|
|
24
|
-
* @returns webhook configuration object
|
|
25
|
-
*/
|
|
26
9
|
getConfiguration(): Promise<any>;
|
|
27
|
-
/**
|
|
28
|
-
* sets the config file to state 'processing'
|
|
29
|
-
*
|
|
30
|
-
* @param configFileId - string id of the config file document
|
|
31
|
-
* @returns void
|
|
32
|
-
*/
|
|
33
10
|
blockConfiguration(): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* sets the config file to state 'released'
|
|
36
|
-
*
|
|
37
|
-
* @param configFileId - string id of the config file document
|
|
38
|
-
* @returns void
|
|
39
|
-
*/
|
|
40
11
|
releaseConfiguration(alterationText?: string): Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
* retrieves the current user name
|
|
43
|
-
*
|
|
44
|
-
* @returns the current user name as string
|
|
45
|
-
*/
|
|
46
12
|
getCurrentUserName(): Promise<string>;
|
|
47
|
-
/**
|
|
48
|
-
* returns the document categroy specific number range information from config
|
|
49
|
-
*
|
|
50
|
-
* @param doc - the current document object
|
|
51
|
-
* @param config - the current config object
|
|
52
|
-
* @returns the document categroy's' number range information from config
|
|
53
|
-
*/
|
|
54
13
|
getNumberRangeInformation(doc: any, config: any): INumberRangePropertyObject[];
|
|
55
14
|
}
|
|
@@ -32,13 +32,6 @@ class D3dWebhookClient extends Yaveon_ECM_d3d_Client_1.D3dClient {
|
|
|
32
32
|
this.configFileId = '';
|
|
33
33
|
this.currentUserName = '';
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
36
|
-
* updates the config file in d3 documents with the given config object
|
|
37
|
-
*
|
|
38
|
-
* @param configObject - config object that shall be written to d3d documents
|
|
39
|
-
* @param updateBody - contains the necessary metadata
|
|
40
|
-
* @returns {string} contentLocationUri
|
|
41
|
-
*/
|
|
42
35
|
async updateConfiguration(configObject, alterationText) {
|
|
43
36
|
await this.ensureConfigurationFileId();
|
|
44
37
|
let uploadResponse = await this.uploadChunk((new TextEncoder).encode(JSON.stringify(configObject)));
|
|
@@ -52,34 +45,23 @@ class D3dWebhookClient extends Yaveon_ECM_d3d_Client_1.D3dClient {
|
|
|
52
45
|
let updateResponse = await this.updateDocumentProperties(this.configFileId, updateBody);
|
|
53
46
|
return updateResponse;
|
|
54
47
|
}
|
|
55
|
-
/**
|
|
56
|
-
* ensures that configuration file id is present. Throws if file can't be found.
|
|
57
|
-
*
|
|
58
|
-
* @returns void
|
|
59
|
-
*/
|
|
60
48
|
async ensureConfigurationFileId() {
|
|
61
49
|
if (this.configFileId) {
|
|
62
50
|
return;
|
|
63
51
|
}
|
|
64
52
|
let searchResults = await this.getSearchResults('?properties={"property_filename":["' + Constants.ConfigFileName + '"],"property_filetype":["' + Constants.ConfigFileExtension + '"]}');
|
|
65
53
|
if (searchResults.count === 0) {
|
|
66
|
-
|
|
54
|
+
let err = new Error("Configuration file with filname '" + Constants.ConfigFileName + "' and filetype '" + Constants.ConfigFileExtension + "' could not be found.");
|
|
55
|
+
throw err;
|
|
67
56
|
}
|
|
68
57
|
this.configFileId = searchResults[0].id;
|
|
69
58
|
}
|
|
70
|
-
/**
|
|
71
|
-
* retrieves the webhook configuration from d3 documents
|
|
72
|
-
*
|
|
73
|
-
* @returns webhook configuration object
|
|
74
|
-
*/
|
|
75
59
|
async getConfiguration() {
|
|
76
60
|
let searchResults = await this.getSearchResults('?properties={"property_filename":["' + Constants.ConfigFileName + '"],"property_filetype":["' + Constants.ConfigFileExtension + '"]}');
|
|
77
61
|
let configFileState = searchResults[0].displayProperties.find((p) => p.id === Constants.FILE_STATUS_PROPERTY_NAME).value.toLowerCase();
|
|
78
62
|
this.configFileId = searchResults[0].id;
|
|
79
|
-
// make sure to not use a configuration that is about to get changed
|
|
80
63
|
let counter = 0;
|
|
81
64
|
while (configFileState !== Constants.DmsObjectStates.States.Released) {
|
|
82
|
-
// go to sleep
|
|
83
65
|
await new Promise(resolve => setTimeout(resolve, Constants.ConfigFileLockedTimeout));
|
|
84
66
|
if (counter >= 2) {
|
|
85
67
|
await this.releaseConfiguration(this.configFileId);
|
|
@@ -90,44 +72,20 @@ class D3dWebhookClient extends Yaveon_ECM_d3d_Client_1.D3dClient {
|
|
|
90
72
|
}
|
|
91
73
|
return await this.getDocumentContent(this.configFileId);
|
|
92
74
|
}
|
|
93
|
-
/**
|
|
94
|
-
* sets the config file to state 'processing'
|
|
95
|
-
*
|
|
96
|
-
* @param configFileId - string id of the config file document
|
|
97
|
-
* @returns void
|
|
98
|
-
*/
|
|
99
75
|
async blockConfiguration() {
|
|
100
76
|
await this.ensureConfigurationFileId();
|
|
101
77
|
await this.setObjectState(this.configFileId, (await this.getCurrentUserName()), Constants.DmsObjectStates.States.Processing);
|
|
102
78
|
}
|
|
103
|
-
/**
|
|
104
|
-
* sets the config file to state 'released'
|
|
105
|
-
*
|
|
106
|
-
* @param configFileId - string id of the config file document
|
|
107
|
-
* @returns void
|
|
108
|
-
*/
|
|
109
79
|
async releaseConfiguration(alterationText = '') {
|
|
110
80
|
await this.ensureConfigurationFileId();
|
|
111
81
|
await this.setObjectState(this.configFileId, (await this.getCurrentUserName()), Constants.DmsObjectStates.States.Released, alterationText);
|
|
112
82
|
}
|
|
113
|
-
/**
|
|
114
|
-
* retrieves the current user name
|
|
115
|
-
*
|
|
116
|
-
* @returns the current user name as string
|
|
117
|
-
*/
|
|
118
83
|
async getCurrentUserName() {
|
|
119
84
|
if (this.currentUserName == null) {
|
|
120
85
|
this.currentUserName = (await this.getCurrentUserObject()).userName;
|
|
121
86
|
}
|
|
122
87
|
return this.currentUserName;
|
|
123
88
|
}
|
|
124
|
-
/**
|
|
125
|
-
* returns the document categroy specific number range information from config
|
|
126
|
-
*
|
|
127
|
-
* @param doc - the current document object
|
|
128
|
-
* @param config - the current config object
|
|
129
|
-
* @returns the document categroy's' number range information from config
|
|
130
|
-
*/
|
|
131
89
|
getNumberRangeInformation(doc, config) {
|
|
132
90
|
let tenantInfo = config.numberRanges.tenants.find((cp) => {
|
|
133
91
|
let tenantProperty = doc.properties.find((p) => p.id.toLowerCase() === cp.tenantPropertyGuid.toLowerCase());
|
|
@@ -13,7 +13,6 @@ var _dmsObjectStates;
|
|
|
13
13
|
class DmsObjectStates {
|
|
14
14
|
static isValid(stateValue) {
|
|
15
15
|
return Object.values(_dmsObjectStates).includes(stateValue);
|
|
16
|
-
// return true;
|
|
17
16
|
}
|
|
18
17
|
;
|
|
19
18
|
}
|
|
@@ -25,12 +24,6 @@ exports.ConfigFileDisplayName = 'Webhook Konfiguration';
|
|
|
25
24
|
exports.FILE_STATUS_PROPERTY_NAME = 'property_state';
|
|
26
25
|
exports.FILE_STATUS_RELEASED = 'release';
|
|
27
26
|
exports.ConfigFileLockedTimeout = 3000;
|
|
28
|
-
// export const defaultHeaders: Map <string,string> = new Map <string,string> ([
|
|
29
|
-
// ['Accept', 'application/json+hal'],
|
|
30
|
-
// ['Authorization', ''],
|
|
31
|
-
// ['Content-Type', 'application/json;charset=utf-8'],
|
|
32
|
-
// ['Origin', '']
|
|
33
|
-
// ]);
|
|
34
27
|
exports.defaultHeaders = {
|
|
35
28
|
Accept: 'application/json+hal',
|
|
36
29
|
Authorization: '',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare let SetNumberId: (req: any, res: any) => Promise<void>;
|
package/js/{Yaveon.ECM.d3d.Webhook.SetNumberId.Test.js → Yaveon.ECM.d3d.Webhook.SetNumberId.js}
RENAMED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SetNumberId = void 0;
|
|
3
4
|
const Yaveon_ECM_d3d_Webhook_Client_1 = require("./Yaveon.ECM.d3d.Webhook.Client");
|
|
4
5
|
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '1';
|
|
5
|
-
|
|
6
|
+
let SetNumberId = async (req, res) => {
|
|
6
7
|
let reqBody = req.json();
|
|
7
8
|
let doc = reqBody.doc;
|
|
8
9
|
let resultMsg = '';
|
|
9
10
|
let resultState = 200;
|
|
10
11
|
let whClient = new Yaveon_ECM_d3d_Webhook_Client_1.D3dWebhookClient(req.get('x-dv-baseuri'), req.get('x-dv-repository-id'), 'Bearer ' + req.get('Authorization').replace('Bearer ', ''));
|
|
11
|
-
// obtain configuration file
|
|
12
12
|
let config = await whClient.getConfiguration();
|
|
13
|
-
// block configuration to prevent concurrent updates
|
|
14
13
|
await whClient.blockConfiguration();
|
|
15
14
|
let alterationText = "";
|
|
16
15
|
try {
|
|
@@ -47,4 +46,5 @@ module.exports = async (req, res) => {
|
|
|
47
46
|
}
|
|
48
47
|
res.status(resultState).set(req.get()).send((resultState === 200 ? Buffer.from(JSON.stringify(reqBody)) : resultMsg));
|
|
49
48
|
};
|
|
50
|
-
|
|
49
|
+
exports.SetNumberId = SetNumberId;
|
|
50
|
+
//# sourceMappingURL=Yaveon.ECM.d3d.Webhook.SetNumberId.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|