@crowdin/app-project-module 0.88.1 → 0.90.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/out/index.js +4 -6
- package/out/middlewares/api-call.d.ts +2 -0
- package/out/middlewares/api-call.js +7 -0
- package/out/middlewares/crowdin-client.js +8 -1
- package/out/middlewares/integration-credentials.js +19 -1
- package/out/modules/api/api.js +19 -26
- package/out/modules/api/components.d.ts +0 -3
- package/out/modules/api/components.js +0 -3
- package/out/modules/integration/handlers/crowdin-file-progress.js +7 -0
- package/out/modules/integration/handlers/crowdin-update.js +7 -0
- package/out/modules/integration/handlers/integration-login.js +27 -2
- package/out/modules/integration/handlers/integration-update.js +11 -2
- package/out/modules/integration/handlers/job-cancel.d.ts +1 -2
- package/out/modules/integration/handlers/job-cancel.js +12 -10
- package/out/modules/integration/handlers/job-info.js +50 -32
- package/out/modules/integration/handlers/main.js +1 -0
- package/out/modules/integration/handlers/settings-save.js +9 -2
- package/out/modules/integration/handlers/sync-settings-save.js +17 -0
- package/out/modules/integration/handlers/sync-settings.js +7 -0
- package/out/modules/integration/index.js +1 -1
- package/out/modules/integration/types.d.ts +4 -0
- package/out/modules/integration/util/defaults.js +46 -39
- package/out/modules/integration/util/job.d.ts +2 -1
- package/out/modules/integration/util/job.js +2 -2
- package/out/modules/webhooks/handlers/webhook-handler.js +10 -1
- package/out/modules/webhooks/types.d.ts +7 -0
- package/out/storage/index.d.ts +2 -2
- package/out/storage/mysql.d.ts +6 -1
- package/out/storage/mysql.js +48 -0
- package/out/storage/postgre.d.ts +1 -1
- package/out/storage/postgre.js +1 -1
- package/out/storage/sqlite.d.ts +2 -2
- package/out/types.d.ts +2 -1
- package/out/util/index.d.ts +0 -1
- package/out/util/index.js +1 -6
- package/out/views/main.handlebars +18 -5
- package/package.json +1 -1
package/out/index.js
CHANGED
|
@@ -98,13 +98,11 @@ exports.metadataStore = {
|
|
|
98
98
|
},
|
|
99
99
|
saveMetadata: (id, metadata, crowdinId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
100
100
|
if (!crowdinId) {
|
|
101
|
-
|
|
101
|
+
throw new Error('The crowdinId parameter is required.');
|
|
102
102
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
console.error('Invalid crowdinId parameter: You can get your crowdinId from the JWT payload');
|
|
107
|
-
}
|
|
103
|
+
const crowdinCredentials = yield storage.getStorage().getCrowdinCredentials(crowdinId);
|
|
104
|
+
if (!crowdinCredentials) {
|
|
105
|
+
throw new Error('Invalid crowdinId parameter: You can get your crowdinId from the JWT payload.');
|
|
108
106
|
}
|
|
109
107
|
const existing = yield storage.getStorage().getMetadata(id);
|
|
110
108
|
if (existing) {
|
|
@@ -65,7 +65,7 @@ function prepareCrowdinRequest({ jwtToken, config, optional = false, checkSubscr
|
|
|
65
65
|
exports.prepareCrowdinRequest = prepareCrowdinRequest;
|
|
66
66
|
function handle({ config, optional = false, checkSubscriptionExpiration = true, moduleKey, }) {
|
|
67
67
|
return (0, util_1.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
68
|
-
var _a;
|
|
68
|
+
var _a, _b;
|
|
69
69
|
const jwtToken = getToken(req);
|
|
70
70
|
if (!jwtToken) {
|
|
71
71
|
(0, logger_1.temporaryErrorDebug)('Access denied: crowdin-client', req);
|
|
@@ -80,6 +80,13 @@ function handle({ config, optional = false, checkSubscriptionExpiration = true,
|
|
|
80
80
|
moduleKey,
|
|
81
81
|
});
|
|
82
82
|
if ((_a = config.api) === null || _a === void 0 ? void 0 : _a.default) {
|
|
83
|
+
if (req.isApiCall && !((_b = req.body) === null || _b === void 0 ? void 0 : _b.projectId)) {
|
|
84
|
+
return res.status(400).json({
|
|
85
|
+
error: {
|
|
86
|
+
message: 'Missing required parameter: projectId',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
83
90
|
data.context = (0, api_1.updateCrowdinContext)(req, data.context);
|
|
84
91
|
}
|
|
85
92
|
req.crowdinContext = data.context;
|
|
@@ -40,6 +40,7 @@ const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-function
|
|
|
40
40
|
function handle(config, integration, optional = false) {
|
|
41
41
|
return (0, util_1.runAsyncWrapper)((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
42
42
|
let clientId = req.crowdinContext.clientId;
|
|
43
|
+
const isApiCall = req === null || req === void 0 ? void 0 : req.isApiCall;
|
|
43
44
|
const { organization, projectId, userId } = crowdinAppFunctions.parseCrowdinId(clientId);
|
|
44
45
|
req.logInfo(`Loading integration credentials for client ${clientId}`);
|
|
45
46
|
let integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(clientId);
|
|
@@ -73,6 +74,13 @@ function handle(config, integration, optional = false) {
|
|
|
73
74
|
owners: null,
|
|
74
75
|
hideActions: false,
|
|
75
76
|
};
|
|
77
|
+
if (isApiCall) {
|
|
78
|
+
return res.status(errorOptions.code).json({
|
|
79
|
+
error: {
|
|
80
|
+
message: errorOptions.message,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
76
84
|
if (owners) {
|
|
77
85
|
errorOptions.message = 'Looks like you don’t have access';
|
|
78
86
|
errorOptions.hideActions = true;
|
|
@@ -88,7 +96,17 @@ function handle(config, integration, optional = false) {
|
|
|
88
96
|
}
|
|
89
97
|
catch (e) {
|
|
90
98
|
console.error(e);
|
|
91
|
-
|
|
99
|
+
const message = 'Credentials to integration either expired or invalid';
|
|
100
|
+
if (isApiCall) {
|
|
101
|
+
return res.status(401).json({
|
|
102
|
+
error: {
|
|
103
|
+
message,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new util_1.CodeError(message, 401);
|
|
109
|
+
}
|
|
92
110
|
}
|
|
93
111
|
const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(clientId);
|
|
94
112
|
if (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config) {
|
package/out/modules/api/api.js
CHANGED
|
@@ -10,6 +10,7 @@ const swagger_jsdoc_1 = __importDefault(require("swagger-jsdoc"));
|
|
|
10
10
|
const crowdin_client_1 = __importDefault(require("../../middlewares/crowdin-client"));
|
|
11
11
|
const integration_credentials_1 = __importDefault(require("../../middlewares/integration-credentials"));
|
|
12
12
|
const json_response_1 = __importDefault(require("../../middlewares/json-response"));
|
|
13
|
+
const api_call_1 = __importDefault(require("../../middlewares/api-call"));
|
|
13
14
|
const crowdin_file_progress_1 = __importDefault(require("../integration/handlers/crowdin-file-progress"));
|
|
14
15
|
const crowdin_files_1 = __importDefault(require("../integration/handlers/crowdin-files"));
|
|
15
16
|
const crowdin_update_1 = __importDefault(require("../integration/handlers/crowdin-update"));
|
|
@@ -157,10 +158,7 @@ function getFormFields(fields) {
|
|
|
157
158
|
const formFields = [];
|
|
158
159
|
for (const field of fields) {
|
|
159
160
|
if (field === null || field === void 0 ? void 0 : field.key) {
|
|
160
|
-
formFields.push({
|
|
161
|
-
name: field.label,
|
|
162
|
-
key: field.key,
|
|
163
|
-
});
|
|
161
|
+
formFields.push(Object.assign({ name: field.label, key: field.key, type: field.type }, (field.type === 'select' && field.options ? { options: field.options } : {})));
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
164
|
return formFields;
|
|
@@ -187,7 +185,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
187
185
|
* data:
|
|
188
186
|
* $ref: '#/components/schemas/CrowdinFiles'
|
|
189
187
|
*/
|
|
190
|
-
app.get('/crowdin-files', json_response_1.default, (0, crowdin_client_1.default)({
|
|
188
|
+
app.get('/crowdin-files', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
191
189
|
config,
|
|
192
190
|
optional: false,
|
|
193
191
|
checkSubscriptionExpiration: true,
|
|
@@ -221,12 +219,12 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
221
219
|
* data:
|
|
222
220
|
* $ref: '#/components/schemas/FileProgress'
|
|
223
221
|
*/
|
|
224
|
-
app.get('/file-progress', (0, crowdin_client_1.default)({
|
|
222
|
+
app.get('/file-progress', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
225
223
|
config,
|
|
226
224
|
optional: false,
|
|
227
225
|
checkSubscriptionExpiration: true,
|
|
228
226
|
moduleKey: 'file-translation-progress-api',
|
|
229
|
-
}), (0, crowdin_file_progress_1.default)(config.projectIntegration));
|
|
227
|
+
}), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, crowdin_file_progress_1.default)(config.projectIntegration));
|
|
230
228
|
/**
|
|
231
229
|
* @openapi
|
|
232
230
|
* /integration-files:
|
|
@@ -248,7 +246,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
248
246
|
* $ref: '#/components/schemas/IntegrationFiles'
|
|
249
247
|
*
|
|
250
248
|
*/
|
|
251
|
-
app.get('/integration-files', json_response_1.default, (0, crowdin_client_1.default)({
|
|
249
|
+
app.get('/integration-files', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
252
250
|
config,
|
|
253
251
|
optional: false,
|
|
254
252
|
checkSubscriptionExpiration: true,
|
|
@@ -276,7 +274,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
276
274
|
* data:
|
|
277
275
|
* $ref: '#/components/schemas/UpdateResponse'
|
|
278
276
|
*/
|
|
279
|
-
app.post('/crowdin-update', json_response_1.default, (0, crowdin_client_1.default)({
|
|
277
|
+
app.post('/crowdin-update', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
280
278
|
config,
|
|
281
279
|
optional: false,
|
|
282
280
|
checkSubscriptionExpiration: true,
|
|
@@ -304,7 +302,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
304
302
|
* data:
|
|
305
303
|
* $ref: '#/components/schemas/UpdateResponse'
|
|
306
304
|
*/
|
|
307
|
-
app.post('/integration-update', json_response_1.default, (0, crowdin_client_1.default)({
|
|
305
|
+
app.post('/integration-update', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
308
306
|
config,
|
|
309
307
|
optional: false,
|
|
310
308
|
checkSubscriptionExpiration: true,
|
|
@@ -334,12 +332,12 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
334
332
|
* schema:
|
|
335
333
|
* $ref: '#/components/schemas/JobResponse'
|
|
336
334
|
*/
|
|
337
|
-
app.get('/jobs', json_response_1.default, (0, crowdin_client_1.default)({
|
|
335
|
+
app.get('/jobs', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
338
336
|
config,
|
|
339
337
|
optional: false,
|
|
340
338
|
checkSubscriptionExpiration: true,
|
|
341
339
|
moduleKey: 'job-get-api',
|
|
342
|
-
}), (0, job_info_1.default)(config));
|
|
340
|
+
}), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_info_1.default)(config));
|
|
343
341
|
/**
|
|
344
342
|
* @openapi
|
|
345
343
|
* /jobs:
|
|
@@ -352,7 +350,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
352
350
|
* - $ref: '#/components/parameters/ProjectId'
|
|
353
351
|
* - name: jobId
|
|
354
352
|
* in: query
|
|
355
|
-
* required:
|
|
353
|
+
* required: true
|
|
356
354
|
* schema:
|
|
357
355
|
* type: string
|
|
358
356
|
* example: 067da473-fc0b-43e3-b0a2-09d26af130c1
|
|
@@ -360,12 +358,12 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
360
358
|
* 204:
|
|
361
359
|
* description: 'Job canceled successfully'
|
|
362
360
|
*/
|
|
363
|
-
app.delete('/jobs', json_response_1.default, (0, crowdin_client_1.default)({
|
|
361
|
+
app.delete('/jobs', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
364
362
|
config,
|
|
365
363
|
optional: false,
|
|
366
364
|
checkSubscriptionExpiration: true,
|
|
367
365
|
moduleKey: 'job-cancel-api',
|
|
368
|
-
}), (0,
|
|
366
|
+
}), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_cancel_1.default)());
|
|
369
367
|
/**
|
|
370
368
|
* @openapi
|
|
371
369
|
* /settings:
|
|
@@ -386,7 +384,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
386
384
|
* data:
|
|
387
385
|
* $ref: '#/components/schemas/SettingsResponse'
|
|
388
386
|
*/
|
|
389
|
-
app.get('/settings', (0, crowdin_client_1.default)({
|
|
387
|
+
app.get('/settings', api_call_1.default, (0, crowdin_client_1.default)({
|
|
390
388
|
config,
|
|
391
389
|
optional: false,
|
|
392
390
|
checkSubscriptionExpiration: true,
|
|
@@ -409,7 +407,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
409
407
|
* 204:
|
|
410
408
|
* description: 'Application Settings was successfully update'
|
|
411
409
|
*/
|
|
412
|
-
app.post('/settings', (0, crowdin_client_1.default)({
|
|
410
|
+
app.post('/settings', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
413
411
|
config,
|
|
414
412
|
optional: false,
|
|
415
413
|
checkSubscriptionExpiration: true,
|
|
@@ -445,7 +443,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
445
443
|
* - { $ref: '#/components/schemas/CrowdinSyncSettingsResponse' }
|
|
446
444
|
* - { $ref: '#/components/schemas/IntegrationSyncSettingsResponse' }
|
|
447
445
|
*/
|
|
448
|
-
app.get('/sync-settings', json_response_1.default, (0, crowdin_client_1.default)({
|
|
446
|
+
app.get('/sync-settings', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
449
447
|
config,
|
|
450
448
|
optional: false,
|
|
451
449
|
checkSubscriptionExpiration: true,
|
|
@@ -468,7 +466,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
468
466
|
* 204:
|
|
469
467
|
* description: 'Application Sync Settings was successfully update'
|
|
470
468
|
*/
|
|
471
|
-
app.post('/sync-settings', json_response_1.default, (0, crowdin_client_1.default)({
|
|
469
|
+
app.post('/sync-settings', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
472
470
|
config,
|
|
473
471
|
optional: false,
|
|
474
472
|
checkSubscriptionExpiration: true,
|
|
@@ -493,12 +491,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
493
491
|
* data:
|
|
494
492
|
* $ref: '#/components/schemas/LoginFieldsResponse'
|
|
495
493
|
*/
|
|
496
|
-
app.get('/login-fields', json_response_1.default, (
|
|
497
|
-
config,
|
|
498
|
-
optional: false,
|
|
499
|
-
checkSubscriptionExpiration: true,
|
|
500
|
-
moduleKey: 'login-data',
|
|
501
|
-
}), (req, res) => {
|
|
494
|
+
app.get('/login-fields', api_call_1.default, json_response_1.default, (req, res) => {
|
|
502
495
|
var _a, _b;
|
|
503
496
|
let fields = [];
|
|
504
497
|
if ((_b = (_a = config.projectIntegration) === null || _a === void 0 ? void 0 : _a.loginForm) === null || _b === void 0 ? void 0 : _b.fields) {
|
|
@@ -523,7 +516,7 @@ function addDefaultApiEndpoints(app, config) {
|
|
|
523
516
|
* 204:
|
|
524
517
|
* description: 'Login successful'
|
|
525
518
|
*/
|
|
526
|
-
app.post('/login', (0, crowdin_client_1.default)({
|
|
519
|
+
app.post('/login', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
|
|
527
520
|
config,
|
|
528
521
|
optional: false,
|
|
529
522
|
checkSubscriptionExpiration: false,
|
|
@@ -13,6 +13,13 @@ const util_1 = require("../../../util");
|
|
|
13
13
|
const logger_1 = require("../../../util/logger");
|
|
14
14
|
function handle(integration) {
|
|
15
15
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
if (req.isApiCall && !req.params.fileId && !req.body.fileId) {
|
|
17
|
+
return res.status(400).json({
|
|
18
|
+
error: {
|
|
19
|
+
message: 'Missing required parameter: fileId',
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
16
23
|
const fileId = Number(req.params.fileId || req.body.fileId);
|
|
17
24
|
req.logInfo(`Loading translation progress for file ${fileId}`);
|
|
18
25
|
if (integration.getFileProgress) {
|
|
@@ -30,6 +30,13 @@ function handle(config, integration) {
|
|
|
30
30
|
if (rootFolder) {
|
|
31
31
|
req.logInfo(`Updating crowdin files under folder ${rootFolder.id}`);
|
|
32
32
|
}
|
|
33
|
+
if (req.isApiCall && !req.body.files) {
|
|
34
|
+
return res.status(400).json({
|
|
35
|
+
error: {
|
|
36
|
+
message: 'Missing required parameter: files',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
33
40
|
// A request via API has a different structure
|
|
34
41
|
if (((_b = config.api) === null || _b === void 0 ? void 0 : _b.default) && req.body.files) {
|
|
35
42
|
req.body = req.body.files;
|
|
@@ -14,10 +14,35 @@ const util_1 = require("../../../util");
|
|
|
14
14
|
const logger_1 = require("../../../util/logger");
|
|
15
15
|
function handle(config, integration) {
|
|
16
16
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
var _a;
|
|
17
|
+
var _a, _b;
|
|
18
18
|
req.logInfo('Received integration login request');
|
|
19
19
|
let credentials = req.body.credentials;
|
|
20
|
-
if (
|
|
20
|
+
if (req.isApiCall) {
|
|
21
|
+
if (!credentials) {
|
|
22
|
+
return res.status(400).json({
|
|
23
|
+
error: {
|
|
24
|
+
message: 'Missing required parameter: credentials',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if ((_a = integration.loginForm) === null || _a === void 0 ? void 0 : _a.fields) {
|
|
29
|
+
const missingFields = integration.loginForm.fields
|
|
30
|
+
.filter((field) => 'key' in field)
|
|
31
|
+
.filter((field) => {
|
|
32
|
+
const formField = field;
|
|
33
|
+
return formField.type !== 'checkbox' && !credentials[formField.key];
|
|
34
|
+
})
|
|
35
|
+
.map((field) => field.key);
|
|
36
|
+
if (missingFields.length > 0) {
|
|
37
|
+
return res.status(400).json({
|
|
38
|
+
error: {
|
|
39
|
+
message: `Missing required credential fields: ${missingFields.join(', ')}`,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if ((_b = integration.loginForm) === null || _b === void 0 ? void 0 : _b.performGetTokenRequest) {
|
|
21
46
|
req.logInfo('Performing custom get bearer token request');
|
|
22
47
|
const tokenCredentials = yield integration.loginForm.performGetTokenRequest(credentials);
|
|
23
48
|
credentials = Object.assign(Object.assign({}, credentials), { expireIn: Number(tokenCredentials.expires_in) + Date.now() / 1000, accessToken: tokenCredentials.access_token, refreshToken: tokenCredentials.refresh_token });
|
|
@@ -17,16 +17,24 @@ const logger_1 = require("../../../util/logger");
|
|
|
17
17
|
const files_1 = require("../util/files");
|
|
18
18
|
function handle(config, integration) {
|
|
19
19
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
20
|
-
var _a;
|
|
20
|
+
var _a, _b, _c;
|
|
21
21
|
req.logInfo('Updating integration data');
|
|
22
22
|
const client = req.crowdinApiClient;
|
|
23
23
|
const projectId = req.crowdinContext.jwtPayload.context.project_id;
|
|
24
|
+
const forcePushTranslations = ((_a = req.query) === null || _a === void 0 ? void 0 : _a.forcePushTranslations) === 'true' || !!((_b = req.body) === null || _b === void 0 ? void 0 : _b.forcePushTranslations);
|
|
24
25
|
const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, req.crowdinApiClient, projectId);
|
|
25
26
|
if (rootFolder) {
|
|
26
27
|
req.logInfo(`Updating integration data for crowding root folder ${rootFolder.id}`);
|
|
27
28
|
}
|
|
29
|
+
if (req.isApiCall && !req.body.files) {
|
|
30
|
+
return res.status(400).json({
|
|
31
|
+
error: {
|
|
32
|
+
message: 'Missing required parameter: files',
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
28
36
|
// A request via API has a different structure
|
|
29
|
-
if (((
|
|
37
|
+
if (((_c = config.api) === null || _c === void 0 ? void 0 : _c.default) && req.body.files) {
|
|
30
38
|
req.body = req.body.files;
|
|
31
39
|
}
|
|
32
40
|
let payload = req.body;
|
|
@@ -77,6 +85,7 @@ function handle(config, integration) {
|
|
|
77
85
|
});
|
|
78
86
|
throw e;
|
|
79
87
|
}),
|
|
88
|
+
forcePushTranslations,
|
|
80
89
|
});
|
|
81
90
|
}));
|
|
82
91
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
/// <reference types="qs" />
|
|
2
2
|
import { Response } from 'express';
|
|
3
|
-
|
|
4
|
-
export default function handle(config: Config): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
|
|
3
|
+
export default function handle(): (req: import("../../../types").CrowdinClientRequest | import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>, next: Function) => void;
|
|
@@ -12,22 +12,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
const types_1 = require("../util/types");
|
|
13
13
|
const util_1 = require("../../../util");
|
|
14
14
|
const storage_1 = require("../../../storage");
|
|
15
|
-
function handle(
|
|
15
|
+
function handle() {
|
|
16
16
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
17
|
-
var _a;
|
|
18
17
|
const id = req.query.jobId || req.body.jobId;
|
|
19
|
-
const isApi = (0, util_1.isApiRequest)(req, config);
|
|
20
|
-
if (isApi && !((_a = req.body) === null || _a === void 0 ? void 0 : _a.projectId)) {
|
|
21
|
-
res.send({
|
|
22
|
-
error: 'Project id is require',
|
|
23
|
-
});
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
18
|
if (!id) {
|
|
27
|
-
req.logInfo('Job id is
|
|
19
|
+
req.logInfo('Job id is missing');
|
|
28
20
|
res.status(400).send('Job id is required');
|
|
29
21
|
return;
|
|
30
22
|
}
|
|
23
|
+
if (req.isApiCall) {
|
|
24
|
+
const job = yield (0, storage_1.getStorage)().getJob({ id });
|
|
25
|
+
if (!job) {
|
|
26
|
+
return res.status(404).json({
|
|
27
|
+
error: {
|
|
28
|
+
message: `Job with ID ${id} not found`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
31
33
|
req.logInfo(`User has been canceled the job id: ${id}`);
|
|
32
34
|
yield (0, storage_1.getStorage)().updateJob({ id, status: types_1.JobStatus.CANCELED });
|
|
33
35
|
res.sendStatus(204);
|
|
@@ -15,6 +15,7 @@ const storage_1 = require("../../../storage");
|
|
|
15
15
|
const cron_1 = require("../util/cron");
|
|
16
16
|
const defaults_1 = require("../util/defaults");
|
|
17
17
|
const connection_1 = require("../../../util/connection");
|
|
18
|
+
const logger_1 = require("../../../util/logger");
|
|
18
19
|
const MINUTES = 60;
|
|
19
20
|
function getHumanETA(ms) {
|
|
20
21
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -35,15 +36,8 @@ function getHumanETA(ms) {
|
|
|
35
36
|
}
|
|
36
37
|
function handle(config) {
|
|
37
38
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
38
|
-
|
|
39
|
+
const isApi = req.isApiCall;
|
|
39
40
|
const id = req.query.jobId || req.body.jobId;
|
|
40
|
-
const isApi = (0, util_1.isApiRequest)(req, config);
|
|
41
|
-
if (isApi && !((_a = req.body) === null || _a === void 0 ? void 0 : _a.projectId)) {
|
|
42
|
-
res.send({
|
|
43
|
-
error: 'Project id is require',
|
|
44
|
-
});
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
41
|
if (!id) {
|
|
48
42
|
req.logInfo('Get active jobs');
|
|
49
43
|
const jobs = yield (0, storage_1.getStorage)().getActiveJobs({
|
|
@@ -67,6 +61,13 @@ function handle(config) {
|
|
|
67
61
|
}
|
|
68
62
|
req.logInfo(`Get job info for id ${id}`);
|
|
69
63
|
const job = yield (0, storage_1.getStorage)().getJob({ id });
|
|
64
|
+
if (isApi && !job) {
|
|
65
|
+
return res.status(404).json({
|
|
66
|
+
error: {
|
|
67
|
+
message: `Job with ID ${id} not found`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
70
71
|
if (job && job.status === types_1.JobStatus.IN_PROGRESS && job.progress > 5 && job.updatedAt) {
|
|
71
72
|
job.eta = ((Date.now() - job.createdAt) / job.progress) * (100 - job.progress);
|
|
72
73
|
job.info = getHumanETA(job.eta) + (job.info ? `\n${job.info}` : '');
|
|
@@ -88,30 +89,47 @@ function handle(config) {
|
|
|
88
89
|
const integration = config.projectIntegration;
|
|
89
90
|
const crowdinId = req.crowdinContext.crowdinId;
|
|
90
91
|
const integrationId = req.crowdinContext.clientId;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
92
|
+
try {
|
|
93
|
+
const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(integrationId);
|
|
94
|
+
const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
|
|
95
|
+
const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(integrationId);
|
|
96
|
+
const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
|
|
97
|
+
? JSON.parse(integrationConfig.config)
|
|
98
|
+
: { schedule: '0', condition: '0' };
|
|
99
|
+
const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, req.crowdinApiClient, projectId);
|
|
100
|
+
req.logInfo(`Restarting the job after no updates for more than ${MINUTES} minutes.`);
|
|
101
|
+
return (0, cron_1.runUpdateProviderJob)({
|
|
102
|
+
integrationId,
|
|
103
|
+
crowdinId,
|
|
104
|
+
type: job.type,
|
|
105
|
+
title: job.title,
|
|
106
|
+
payload: JSON.parse(job.payload),
|
|
107
|
+
jobType: types_1.JobClientType.RERUN,
|
|
108
|
+
projectId,
|
|
109
|
+
client: req.crowdinApiClient,
|
|
110
|
+
integration,
|
|
111
|
+
context,
|
|
112
|
+
credentials,
|
|
113
|
+
rootFolder,
|
|
114
|
+
appSettings: intConfig,
|
|
115
|
+
reRunJobId: id,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
(0, logger_1.logError)(e);
|
|
120
|
+
yield (0, storage_1.getStorage)().updateJob({
|
|
121
|
+
id: job.id,
|
|
122
|
+
status: types_1.JobStatus.FAILED,
|
|
123
|
+
info: (0, logger_1.getErrorMessage)(e),
|
|
124
|
+
});
|
|
125
|
+
return res.status(500).json({
|
|
126
|
+
error: {
|
|
127
|
+
message: `Job was failed after attempt to restart it due to inactivity (no updates for ${MINUTES} minutes). Please try to restart the job manually.`,
|
|
128
|
+
jobId: job.id,
|
|
129
|
+
status: types_1.JobStatus.FAILED,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
115
133
|
}
|
|
116
134
|
req.logInfo(`Returning job info ${JSON.stringify(job, null, 2)}`);
|
|
117
135
|
res.send(job);
|
|
@@ -94,6 +94,7 @@ function handle(config, integration) {
|
|
|
94
94
|
options.integrationSearchListener = integration.integrationSearchListener;
|
|
95
95
|
options.checkSubscription = !(0, subscription_1.isAppFree)(config);
|
|
96
96
|
options.uploadTranslations = integration.uploadTranslations;
|
|
97
|
+
options.forcePushTranslations = integration.forcePushTranslations;
|
|
97
98
|
options.excludedTargetLanguages = integration.excludedTargetLanguages;
|
|
98
99
|
options.sentryData = process.env.SENTRY_DSN
|
|
99
100
|
? {
|
|
@@ -18,6 +18,13 @@ const cron_1 = require("../util/cron");
|
|
|
18
18
|
function handle(config, integration) {
|
|
19
19
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
20
20
|
const appSettings = req.body.config;
|
|
21
|
+
if (req.isApiCall && !req.body.config) {
|
|
22
|
+
return res.status(400).json({
|
|
23
|
+
error: {
|
|
24
|
+
message: 'Missing required parameter: config',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
21
28
|
const clientId = req.crowdinContext.clientId;
|
|
22
29
|
req.logInfo(`Saving settings ${JSON.stringify(appSettings, null, 2)}`);
|
|
23
30
|
const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(clientId);
|
|
@@ -38,10 +45,10 @@ function handle(config, integration) {
|
|
|
38
45
|
});
|
|
39
46
|
}
|
|
40
47
|
else {
|
|
41
|
-
if (appSettings['new-crowdin-files']) {
|
|
48
|
+
if (appSettings === null || appSettings === void 0 ? void 0 : appSettings['new-crowdin-files']) {
|
|
42
49
|
(0, snapshot_1.createOrUpdateFileSnapshot)(config, integration, req, types_1.Provider.CROWDIN);
|
|
43
50
|
}
|
|
44
|
-
if (appSettings['new-integration-files']) {
|
|
51
|
+
if (appSettings === null || appSettings === void 0 ? void 0 : appSettings['new-integration-files']) {
|
|
45
52
|
(0, snapshot_1.createOrUpdateFileSnapshot)(config, integration, req, types_1.Provider.INTEGRATION);
|
|
46
53
|
}
|
|
47
54
|
}
|
|
@@ -19,6 +19,7 @@ const snapshot_1 = require("../util/snapshot");
|
|
|
19
19
|
const files_1 = require("../util/files");
|
|
20
20
|
const job_1 = require("../util/job");
|
|
21
21
|
const types_1 = require("../util/types");
|
|
22
|
+
const types_2 = require("../types");
|
|
22
23
|
function checkAutoSyncSettings(integration, appSettings, provider) {
|
|
23
24
|
var _a;
|
|
24
25
|
return !!(!integration.webhooks && ((_a = integration.syncNewElements) === null || _a === void 0 ? void 0 : _a[provider]) && appSettings[`new-${provider}-files`]);
|
|
@@ -26,6 +27,22 @@ function checkAutoSyncSettings(integration, appSettings, provider) {
|
|
|
26
27
|
function handle(config, integration) {
|
|
27
28
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
28
29
|
const { files, provider, expandIntegrationFolders } = req.body;
|
|
30
|
+
if (req.isApiCall) {
|
|
31
|
+
if (!files || !provider) {
|
|
32
|
+
return res.status(400).json({
|
|
33
|
+
error: {
|
|
34
|
+
message: `Missing required parameter: ${!files ? 'files' : 'provider'}`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (!Object.values(types_2.Provider).includes(provider)) {
|
|
39
|
+
return res.status(400).json({
|
|
40
|
+
error: {
|
|
41
|
+
message: `Invalid provider. Must be one of: ${Object.values(types_2.Provider).join(', ')}`,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
29
46
|
yield (0, job_1.runAsJob)({
|
|
30
47
|
integrationId: req.crowdinContext.clientId,
|
|
31
48
|
crowdinId: req.crowdinContext.crowdinId,
|
|
@@ -15,6 +15,13 @@ function handle() {
|
|
|
15
15
|
return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
16
16
|
let files = {};
|
|
17
17
|
const provider = req.params.provider || req.body.provider;
|
|
18
|
+
if (req.isApiCall && !provider) {
|
|
19
|
+
return res.status(400).json({
|
|
20
|
+
error: {
|
|
21
|
+
message: 'Missing required parameter: provider',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
}
|
|
18
25
|
req.logInfo(`Loading sync settings for provider ${provider}`);
|
|
19
26
|
const syncSettings = yield (0, storage_1.getStorage)().getSyncSettingsByProvider(req.crowdinContext.clientId, provider);
|
|
20
27
|
if (syncSettings) {
|
|
@@ -99,7 +99,7 @@ function register({ config, app }) {
|
|
|
99
99
|
optional: false,
|
|
100
100
|
checkSubscriptionExpiration: true,
|
|
101
101
|
moduleKey: integrationLogic.key,
|
|
102
|
-
}), (0, job_cancel_1.default)(
|
|
102
|
+
}), (0, job_cancel_1.default)());
|
|
103
103
|
app.post('/api/settings', (0, crowdin_client_1.default)({
|
|
104
104
|
config,
|
|
105
105
|
optional: false,
|
|
@@ -154,6 +154,10 @@ export interface IntegrationLogic extends ModuleKey {
|
|
|
154
154
|
* Enable the option to upload translations to crowdin that are already present in the integration.
|
|
155
155
|
*/
|
|
156
156
|
uploadTranslations?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Force sync translations from Crowdin to integration regardless of etag.
|
|
159
|
+
*/
|
|
160
|
+
forcePushTranslations?: boolean;
|
|
157
161
|
/**
|
|
158
162
|
* Enable the option to upload file for translation into selected languages.
|
|
159
163
|
*/
|
|
@@ -146,7 +146,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
146
146
|
}
|
|
147
147
|
const getUserSettings = integration.getConfiguration;
|
|
148
148
|
integration.getConfiguration = (projectId, crowdinClient, integrationCredentials) => __awaiter(this, void 0, void 0, function* () {
|
|
149
|
-
var _m, _o;
|
|
149
|
+
var _m, _o, _p;
|
|
150
150
|
let fields = [];
|
|
151
151
|
const project = (yield crowdinClient.projectsGroupsApi.getProject(projectId));
|
|
152
152
|
if (getUserSettings) {
|
|
@@ -163,42 +163,49 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
if (integration.withCronSync || integration.webhooks) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
166
|
+
const userSchedule = fields.find((field) => 'key' in field && field.key === 'schedule');
|
|
167
|
+
if (userSchedule) {
|
|
168
|
+
userSchedule.position = (_m = userSchedule.position) !== null && _m !== void 0 ? _m : 0;
|
|
169
|
+
userSchedule.category = types_1.DefaultCategory.SYNC;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
defaultSettings.push({
|
|
173
|
+
key: 'schedule',
|
|
174
|
+
label: 'Sync schedule',
|
|
175
|
+
helpText: `Defines how often content is synced between ${config.name} and Crowdin. Make sure Auto Sync is enabled for selected directories and files in the dual pane view.`,
|
|
176
|
+
type: 'select',
|
|
177
|
+
defaultValue: '0',
|
|
178
|
+
category: types_1.DefaultCategory.SYNC,
|
|
179
|
+
position: 0,
|
|
180
|
+
options: [
|
|
181
|
+
{
|
|
182
|
+
value: '0',
|
|
183
|
+
label: 'Disabled',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
value: '1',
|
|
187
|
+
label: '1 hour',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
value: '3',
|
|
191
|
+
label: '3 hours',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
value: '6',
|
|
195
|
+
label: '6 hours',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
value: '12',
|
|
199
|
+
label: '12 hours',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
value: '24',
|
|
203
|
+
label: '24 hours',
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if ((_o = integration.syncNewElements) === null || _o === void 0 ? void 0 : _o.crowdin) {
|
|
202
209
|
defaultSettings.push({
|
|
203
210
|
key: 'new-crowdin-files',
|
|
204
211
|
label: 'Automatically sync new translations from Crowdin',
|
|
@@ -208,7 +215,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
208
215
|
position: 1,
|
|
209
216
|
});
|
|
210
217
|
}
|
|
211
|
-
if ((
|
|
218
|
+
if ((_p = integration.syncNewElements) === null || _p === void 0 ? void 0 : _p.integration) {
|
|
212
219
|
defaultSettings.push({
|
|
213
220
|
key: 'new-integration-files',
|
|
214
221
|
label: `Automatically sync new content from ${config.name}`,
|
|
@@ -293,7 +300,7 @@ function applyIntegrationModuleDefaults(config, integration) {
|
|
|
293
300
|
if (!((_b = integration.filtering) === null || _b === void 0 ? void 0 : _b.hasOwnProperty('crowdinLanguages'))) {
|
|
294
301
|
integration.filtering = Object.assign(Object.assign({}, (integration.filtering || {})), { crowdinLanguages: true });
|
|
295
302
|
}
|
|
296
|
-
integration.filtering.integrationFileStatus = Object.assign({ notSynced: true }, integration.filtering.integrationFileStatus);
|
|
303
|
+
integration.filtering.integrationFileStatus = Object.assign(Object.assign({}, (integration.integrationOneLevelFetching ? {} : { notSynced: true })), integration.filtering.integrationFileStatus);
|
|
297
304
|
if (!((_c = integration.filtering) === null || _c === void 0 ? void 0 : _c.hasOwnProperty('integrationFilterConfig'))) {
|
|
298
305
|
const filterItems = [
|
|
299
306
|
{
|
|
@@ -2,7 +2,7 @@ import { JobClient, JobClientType, JobStoreType, JobType } from './types';
|
|
|
2
2
|
import { Response } from 'express';
|
|
3
3
|
import Crowdin from '@crowdin/crowdin-api-client';
|
|
4
4
|
import { Config } from '../../../types';
|
|
5
|
-
export declare function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, jobCallback, onError, reRunJobId, }: {
|
|
5
|
+
export declare function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, jobCallback, onError, reRunJobId, forcePushTranslations, }: {
|
|
6
6
|
integrationId: string;
|
|
7
7
|
crowdinId: string;
|
|
8
8
|
type: JobType;
|
|
@@ -16,5 +16,6 @@ export declare function runAsJob({ integrationId, crowdinId, type, title, payloa
|
|
|
16
16
|
jobCallback: (arg1: JobClient) => Promise<any>;
|
|
17
17
|
onError?: (e: any) => Promise<void>;
|
|
18
18
|
reRunJobId?: string;
|
|
19
|
+
forcePushTranslations?: boolean;
|
|
19
20
|
}): Promise<void>;
|
|
20
21
|
export declare function reRunInProgressJobs(config: Config): Promise<void>;
|
|
@@ -48,7 +48,7 @@ const blockingJobs = {
|
|
|
48
48
|
};
|
|
49
49
|
const maxAttempts = 3;
|
|
50
50
|
const store = {};
|
|
51
|
-
function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, jobCallback, onError, reRunJobId, }) {
|
|
51
|
+
function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, jobCallback, onError, reRunJobId, forcePushTranslations, }) {
|
|
52
52
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
53
|
let jobId;
|
|
54
54
|
const storage = (0, storage_1.getStorage)();
|
|
@@ -141,7 +141,7 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
|
|
|
141
141
|
(0, logger_1.log)(`Receiving translation for file ${fileId} in language ${languageId}`);
|
|
142
142
|
translation = yield client.translationsApi.buildProjectFileTranslation(projectId, fileId, {
|
|
143
143
|
targetLanguageId: languageId,
|
|
144
|
-
}, (translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag) ? translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag : undefined);
|
|
144
|
+
}, (translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag) && !forcePushTranslations ? translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag : undefined);
|
|
145
145
|
return translation;
|
|
146
146
|
}
|
|
147
147
|
catch (e) {
|
|
@@ -69,7 +69,16 @@ function webhookHandler(config, webhooks) {
|
|
|
69
69
|
const json = JSON.parse(req.body.toString());
|
|
70
70
|
for (const webhook of webhooks) {
|
|
71
71
|
if (webhook.key === moduleKey) {
|
|
72
|
-
yield webhook.callback({
|
|
72
|
+
yield webhook.callback({
|
|
73
|
+
events: json.events,
|
|
74
|
+
client,
|
|
75
|
+
webhookContext: {
|
|
76
|
+
domain: credentials.domain,
|
|
77
|
+
organizationId: credentials.organizationId,
|
|
78
|
+
userId: credentials.userId,
|
|
79
|
+
agentId: credentials.agentId,
|
|
80
|
+
},
|
|
81
|
+
});
|
|
73
82
|
}
|
|
74
83
|
}
|
|
75
84
|
}));
|
|
@@ -9,10 +9,17 @@ export interface Webhook extends ModuleKey {
|
|
|
9
9
|
* handle function
|
|
10
10
|
*/
|
|
11
11
|
callback: (data: {
|
|
12
|
+
webhookContext: WebhookContext;
|
|
12
13
|
events: Event[];
|
|
13
14
|
client: Crowdin;
|
|
14
15
|
}) => Promise<void>;
|
|
15
16
|
}
|
|
17
|
+
interface WebhookContext {
|
|
18
|
+
domain?: string;
|
|
19
|
+
organizationId?: number;
|
|
20
|
+
userId?: number;
|
|
21
|
+
agentId?: number;
|
|
22
|
+
}
|
|
16
23
|
interface EventPayload {
|
|
17
24
|
event: string;
|
|
18
25
|
}
|
package/out/storage/index.d.ts
CHANGED
|
@@ -30,8 +30,8 @@ export interface Storage {
|
|
|
30
30
|
getAllIntegrationCredentials(crowdinId: string): Promise<IntegrationCredentials[]>;
|
|
31
31
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
32
32
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
33
|
-
saveMetadata(id: string, metadata: any, crowdinId
|
|
34
|
-
updateMetadata(id: string, metadata: any, crowdinId
|
|
33
|
+
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
34
|
+
updateMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
35
35
|
getMetadata(id: string): Promise<any | undefined>;
|
|
36
36
|
getAllMetadata(): Promise<any[] | undefined>;
|
|
37
37
|
deleteMetadata(id: string): Promise<void>;
|
package/out/storage/mysql.d.ts
CHANGED
|
@@ -17,6 +17,11 @@ export declare class MySQLStorage implements Storage {
|
|
|
17
17
|
private _rej?;
|
|
18
18
|
private dbPromise;
|
|
19
19
|
private config;
|
|
20
|
+
tableIndexes: {
|
|
21
|
+
[key: string]: {
|
|
22
|
+
[key: string]: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
20
25
|
tables: {
|
|
21
26
|
crowdin_credentials: string;
|
|
22
27
|
integration_credentials: string;
|
|
@@ -48,7 +53,7 @@ export declare class MySQLStorage implements Storage {
|
|
|
48
53
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
49
54
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
50
55
|
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
51
|
-
updateMetadata(id: string, metadata: any, crowdinId
|
|
56
|
+
updateMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
52
57
|
getMetadata(id: string): Promise<any>;
|
|
53
58
|
getAllMetadata(): Promise<any[]>;
|
|
54
59
|
deleteMetadata(id: string): Promise<void>;
|
package/out/storage/mysql.js
CHANGED
|
@@ -24,6 +24,46 @@ class MySQLStorage {
|
|
|
24
24
|
this._res = res;
|
|
25
25
|
this._rej = rej;
|
|
26
26
|
});
|
|
27
|
+
this.tableIndexes = {
|
|
28
|
+
integration_credentials: {
|
|
29
|
+
idx_crowdin: '(crowdin_id)',
|
|
30
|
+
},
|
|
31
|
+
sync_settings: {
|
|
32
|
+
idx_integration_provider: '(integration_id, provider)',
|
|
33
|
+
idx_crowdin: '(crowdin_id)',
|
|
34
|
+
idx_type: '(type)',
|
|
35
|
+
},
|
|
36
|
+
files_snapshot: {
|
|
37
|
+
idx_integration: '(integration_id)',
|
|
38
|
+
idx_crowdin: '(crowdin_id)',
|
|
39
|
+
},
|
|
40
|
+
webhooks: {
|
|
41
|
+
idx_integration_crowdin: '(integration_id, crowdin_id)',
|
|
42
|
+
idx_file_provider: '(file_id, provider)',
|
|
43
|
+
},
|
|
44
|
+
user_errors: {
|
|
45
|
+
idx_integration_crowdin: '(integration_id, crowdin_id)',
|
|
46
|
+
idx_crowdin: '(crowdin_id)',
|
|
47
|
+
idx_created_at: '(created_at)',
|
|
48
|
+
},
|
|
49
|
+
integration_settings: {
|
|
50
|
+
idx_integration: '(integration_id)',
|
|
51
|
+
idx_crowdin: '(crowdin_id)',
|
|
52
|
+
},
|
|
53
|
+
job: {
|
|
54
|
+
idx_integration_crowdin: '(integration_id, crowdin_id)',
|
|
55
|
+
idx_finished_at: '(finished_at)',
|
|
56
|
+
idx_status: '(status)',
|
|
57
|
+
},
|
|
58
|
+
translation_file_cache: {
|
|
59
|
+
idx_integration_crowdin_file_language: '(integration_id, crowdin_id, file_id, language_id)',
|
|
60
|
+
idx_crowdin: '(crowdin_id)',
|
|
61
|
+
},
|
|
62
|
+
unsynced_files: {
|
|
63
|
+
idx_integration_crowdin: '(integration_id, crowdin_id)',
|
|
64
|
+
idx_crowdin: '(crowdin_id)',
|
|
65
|
+
},
|
|
66
|
+
};
|
|
27
67
|
this.tables = {
|
|
28
68
|
crowdin_credentials: `(
|
|
29
69
|
id varchar(255) primary key,
|
|
@@ -168,6 +208,14 @@ class MySQLStorage {
|
|
|
168
208
|
return __awaiter(this, void 0, void 0, function* () {
|
|
169
209
|
for (const [tableName, tableSchema] of Object.entries(this.tables)) {
|
|
170
210
|
yield connection.execute(`create table if not exists ${tableName} ${tableSchema}`);
|
|
211
|
+
if (this.tableIndexes[tableName]) {
|
|
212
|
+
for (const [indexName, indexSchema] of Object.entries(this.tableIndexes[tableName])) {
|
|
213
|
+
// For MySQL, we need to handle partial indexes differently since MySQL doesn't support WHERE clauses in indexes
|
|
214
|
+
// We'll create the basic index without the WHERE clause
|
|
215
|
+
const indexColumns = indexSchema.replace(/WHERE.*$/, '').trim();
|
|
216
|
+
yield connection.execute(`create index if not exists ${indexName} on ${tableName}${indexColumns}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
171
219
|
}
|
|
172
220
|
});
|
|
173
221
|
}
|
package/out/storage/postgre.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export declare class PostgreStorage implements Storage {
|
|
|
62
62
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
63
63
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
64
64
|
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
65
|
-
updateMetadata(id: string, metadata: any, crowdinId
|
|
65
|
+
updateMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
66
66
|
getMetadata(id: string): Promise<any>;
|
|
67
67
|
getAllMetadata(): Promise<any[]>;
|
|
68
68
|
deleteMetadata(id: string): Promise<void>;
|
package/out/storage/postgre.js
CHANGED
|
@@ -197,7 +197,7 @@ class PostgreStorage {
|
|
|
197
197
|
migrate() {
|
|
198
198
|
return __awaiter(this, void 0, void 0, function* () {
|
|
199
199
|
try {
|
|
200
|
-
if (this.directoryPath && fs_1.default.existsSync(this.directoryPath)) {
|
|
200
|
+
if (this.directoryPath && fs_1.default.existsSync(this.directoryPath + '/' + types_1.storageFiles.SQLITE)) {
|
|
201
201
|
yield this.migrateFromSqlite(this.directoryPath);
|
|
202
202
|
}
|
|
203
203
|
yield this.executeQuery((client) => this.addTables(client));
|
package/out/storage/sqlite.d.ts
CHANGED
|
@@ -50,8 +50,8 @@ export declare class SQLiteStorage implements Storage {
|
|
|
50
50
|
getAllIntegrationCredentials(crowdinId: string): Promise<IntegrationCredentials[]>;
|
|
51
51
|
deleteIntegrationCredentials(id: string): Promise<void>;
|
|
52
52
|
deleteAllIntegrationCredentials(crowdinId: string): Promise<void>;
|
|
53
|
-
saveMetadata(id: string, metadata: any, crowdinId
|
|
54
|
-
updateMetadata(id: string, metadata: any, crowdinId
|
|
53
|
+
saveMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
54
|
+
updateMetadata(id: string, metadata: any, crowdinId: string): Promise<void>;
|
|
55
55
|
getMetadata(id: string): Promise<any>;
|
|
56
56
|
getAllMetadata(): Promise<any[]>;
|
|
57
57
|
deleteMetadata(id: string): Promise<void>;
|
package/out/types.d.ts
CHANGED
|
@@ -328,6 +328,7 @@ export interface CrowdinClientRequest extends Request {
|
|
|
328
328
|
subscriptionInfo?: SubscriptionInfo;
|
|
329
329
|
logInfo: LogFunction;
|
|
330
330
|
logError: LogErrorFunction;
|
|
331
|
+
isApiCall?: boolean;
|
|
331
332
|
}
|
|
332
333
|
export interface CrowdinCredentials {
|
|
333
334
|
id: string;
|
|
@@ -393,7 +394,7 @@ export interface CrowdinAppUtilities extends CrowdinMetadataStore {
|
|
|
393
394
|
storage: Storage;
|
|
394
395
|
}
|
|
395
396
|
export interface CrowdinMetadataStore {
|
|
396
|
-
saveMetadata: (id: string, metadata: any, crowdinId
|
|
397
|
+
saveMetadata: (id: string, metadata: any, crowdinId: string) => Promise<void>;
|
|
397
398
|
getMetadata: (id: string) => Promise<any | undefined>;
|
|
398
399
|
deleteMetadata: (id: string) => Promise<void>;
|
|
399
400
|
/**
|
package/out/util/index.d.ts
CHANGED
|
@@ -14,7 +14,6 @@ export declare function isJson(string: string): boolean;
|
|
|
14
14
|
export declare function getPreviousDate(days: number): Date;
|
|
15
15
|
export declare function prepareFormDataMetadataId(req: CrowdinClientRequest, config: Config): Promise<string>;
|
|
16
16
|
export declare function validateEmail(email: string | number): boolean;
|
|
17
|
-
export declare function isApiRequest(req: Request, config: Config): boolean;
|
|
18
17
|
export declare function getFormattedDate({ date, userTimezone }: {
|
|
19
18
|
date: Date | number;
|
|
20
19
|
userTimezone: string | undefined;
|
package/out/util/index.js
CHANGED
|
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.getFormattedDate = exports.
|
|
35
|
+
exports.getFormattedDate = exports.validateEmail = exports.prepareFormDataMetadataId = exports.getPreviousDate = exports.isJson = exports.isAuthorizedConfig = exports.getLogoUrl = exports.executeWithRetry = exports.decryptData = exports.encryptData = exports.runAsyncWrapper = exports.CodeError = void 0;
|
|
36
36
|
const crypto = __importStar(require("crypto-js"));
|
|
37
37
|
const storage_1 = require("../storage");
|
|
38
38
|
const types_1 = require("../types");
|
|
@@ -162,11 +162,6 @@ function validateEmail(email) {
|
|
|
162
162
|
return emailRegExp.test(String(email).toLowerCase());
|
|
163
163
|
}
|
|
164
164
|
exports.validateEmail = validateEmail;
|
|
165
|
-
function isApiRequest(req, config) {
|
|
166
|
-
const origin = req.headers['origin'] || req.headers['referer'];
|
|
167
|
-
return !origin || !origin.includes(config.baseUrl);
|
|
168
|
-
}
|
|
169
|
-
exports.isApiRequest = isApiRequest;
|
|
170
165
|
// Format the date as 'MMM DD, YYYY HH:mm'
|
|
171
166
|
function getFormattedDate({ date, userTimezone }) {
|
|
172
167
|
if (!userTimezone) {
|
|
@@ -89,6 +89,9 @@
|
|
|
89
89
|
integration-button-menu-items='[{"label":"Select target languages", "title":"Upload file for translation into selected languages", "action":"excludedTargetLanguages"}]'
|
|
90
90
|
{{/if}}
|
|
91
91
|
{{/if}}
|
|
92
|
+
{{#if forcePushTranslations}}
|
|
93
|
+
crowdin-button-menu-items='[{"label":"Force Sync Translations", "action":"forcePushTranslations"}]'
|
|
94
|
+
{{/if}}
|
|
92
95
|
{{#if filtering.crowdinLanguages}}
|
|
93
96
|
crowdin-filter
|
|
94
97
|
{{/if}}
|
|
@@ -862,18 +865,28 @@
|
|
|
862
865
|
}
|
|
863
866
|
}
|
|
864
867
|
|
|
865
|
-
function uploadFilesToIntegration(
|
|
866
|
-
|
|
868
|
+
function uploadFilesToIntegration(event) {
|
|
869
|
+
let files = {};
|
|
870
|
+
let forcePushTranslations = false;
|
|
871
|
+
if (event.detail?.action === 'forcePushTranslations') {
|
|
872
|
+
files = event.detail.files;
|
|
873
|
+
forcePushTranslations = true;
|
|
874
|
+
} else {
|
|
875
|
+
files = event.detail;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (Object.keys(event.detail).length === 0 || !event.detail) {
|
|
867
879
|
showToast('Select files which will be uploaded to {{name}}');
|
|
868
880
|
return;
|
|
869
881
|
}
|
|
882
|
+
|
|
870
883
|
appComponent.setAttribute('is-to-integration-process', true);
|
|
871
884
|
const req = {};
|
|
872
|
-
Object.keys(
|
|
885
|
+
Object.keys(files)
|
|
873
886
|
.filter(id => crowdinData.find(c => c.id === id).node_type === fileType)
|
|
874
|
-
.forEach(id => (req[id] =
|
|
887
|
+
.forEach(id => (req[id] = files[id]));
|
|
875
888
|
checkOrigin()
|
|
876
|
-
.then(restParams => fetch(
|
|
889
|
+
.then(restParams => fetch(`api/integration/update${restParams}&forcePushTranslations=${forcePushTranslations}`, {
|
|
877
890
|
method: 'POST',
|
|
878
891
|
headers: { 'Content-Type': 'application/json' },
|
|
879
892
|
body: JSON.stringify(req)
|
package/package.json
CHANGED