@crowdin/app-project-module 0.63.2 → 0.65.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.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { Express } from 'express';
2
2
  import { ClientConfig, Config, CrowdinAppUtilities, CrowdinMetadataStore } from './types';
3
3
  import express from './util/terminus-express';
4
+ import { getRequestCredentialsMasker, postRequestCredentialsMasker } from './util/credentials-masker';
5
+ export { getRequestCredentialsMasker, postRequestCredentialsMasker };
4
6
  export { ProjectPermissions, Scope, UserPermissions } from './types';
5
7
  export { express };
6
8
  export declare const metadataStore: CrowdinMetadataStore;
package/out/index.js CHANGED
@@ -35,7 +35,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
35
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
36
36
  };
37
37
  Object.defineProperty(exports, "__esModule", { value: true });
38
- exports.addCrowdinEndpoints = exports.createApp = exports.metadataStore = exports.express = exports.UserPermissions = exports.Scope = exports.ProjectPermissions = void 0;
38
+ exports.addCrowdinEndpoints = exports.createApp = exports.metadataStore = exports.express = exports.UserPermissions = exports.Scope = exports.ProjectPermissions = exports.postRequestCredentialsMasker = exports.getRequestCredentialsMasker = void 0;
39
39
  const logsFormatter = __importStar(require("@crowdin/logs-formatter"));
40
40
  const path_1 = require("path");
41
41
  const crowdin_client_1 = __importStar(require("./middlewares/crowdin-client"));
@@ -57,6 +57,8 @@ const logger_1 = require("./util/logger");
57
57
  const terminus_express_1 = __importDefault(require("./util/terminus-express"));
58
58
  exports.express = terminus_express_1.default;
59
59
  const credentials_masker_1 = require("./util/credentials-masker");
60
+ Object.defineProperty(exports, "getRequestCredentialsMasker", { enumerable: true, get: function () { return credentials_masker_1.getRequestCredentialsMasker; } });
61
+ Object.defineProperty(exports, "postRequestCredentialsMasker", { enumerable: true, get: function () { return credentials_masker_1.postRequestCredentialsMasker; } });
60
62
  //apps
61
63
  const apiApp = __importStar(require("./modules/api"));
62
64
  const contextMenuApp = __importStar(require("./modules/context-menu"));
@@ -109,7 +111,7 @@ function createApp(clientConfig) {
109
111
  const config = convertClientConfig(clientConfig);
110
112
  addCrowdinEndpoints(app, config);
111
113
  /* eslint no-console: "off" */
112
- app.listen(config.port, () => console.log(`App started on port ${config.port}`));
114
+ app.listen(config, () => console.log(`App started on port ${config.port}`));
113
115
  }
114
116
  exports.createApp = createApp;
115
117
  function addCrowdinEndpoints(app, clientConfig) {
@@ -15,8 +15,15 @@ function handle() {
15
15
  const projectId = req.crowdinContext.jwtPayload.context.project_id;
16
16
  req.logInfo(`Loading crowdin project ${projectId}`);
17
17
  const project = yield req.crowdinApiClient.projectsGroupsApi.getProject(projectId);
18
+ let projectEditorLink;
19
+ if (req.crowdinContext.jwtPayload.domain) {
20
+ projectEditorLink = `https://${req.crowdinContext.jwtPayload.domain}.crowdin.com/editor/${project.data.id}`;
21
+ }
22
+ else {
23
+ projectEditorLink = `https://crowdin.com/editor/${project.data.identifier}`;
24
+ }
18
25
  req.logInfo(`Loaded crowdin project ${projectId}`);
19
- res.send(project.data);
26
+ res.send(Object.assign(Object.assign({}, project.data), { projectEditorLink }));
20
27
  }));
21
28
  }
22
29
  exports.default = handle;
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const storage_1 = require("../../../storage");
13
13
  const util_1 = require("../../../util");
14
+ const logger_1 = require("../../../util/logger");
14
15
  function handle() {
15
16
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
17
  var _a;
@@ -20,6 +21,12 @@ function handle() {
20
21
  }
21
22
  req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId}`);
22
23
  const userErrors = (_a = (yield (0, storage_1.getStorage)().getAllUserErrors(req.crowdinContext.crowdinId, req.crowdinContext.clientId))) === null || _a === void 0 ? void 0 : _a.map((userError) => {
24
+ if (!(0, util_1.isJson)(userError.data)) {
25
+ (0, logger_1.logError)(userError);
26
+ userError.data = JSON.stringify({
27
+ appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
28
+ });
29
+ }
23
30
  const date = new Date(+userError.createdAt);
24
31
  // Format the date as 'MMM DD, YYYY HH:mm'
25
32
  const formattedDate = new Intl.DateTimeFormat('en-US', {
@@ -31,7 +38,7 @@ function handle() {
31
38
  hour12: false,
32
39
  timeZone: userTimezone,
33
40
  }).format(date);
34
- return Object.assign(Object.assign({}, userError), { createdAt: formattedDate });
41
+ return Object.assign(Object.assign({}, userError), { formattedDate });
35
42
  });
36
43
  req.logInfo(`Returning ${userErrors === null || userErrors === void 0 ? void 0 : userErrors.length} user errors`);
37
44
  res.send(userErrors);
@@ -22,6 +22,15 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
25
34
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
35
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
36
  };
@@ -56,6 +65,7 @@ const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
56
65
  const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
57
66
  const user_errors_1 = __importDefault(require("./handlers/user-errors"));
58
67
  const cron_1 = require("./util/cron");
68
+ const storage_1 = require("../../storage");
59
69
  function register({ config, app }) {
60
70
  var _a, _b, _c;
61
71
  const integrationLogic = config.projectIntegration;
@@ -96,8 +106,12 @@ function register({ config, app }) {
96
106
  cron.schedule('0 */3 * * *', () => (0, cron_1.filesCron)({ config, integration: integrationLogic, period: '3' }).catch(console.error));
97
107
  cron.schedule('0 */6 * * *', () => (0, cron_1.filesCron)({ config, integration: integrationLogic, period: '6' }).catch(console.error));
98
108
  cron.schedule('0 */12 * * *', () => (0, cron_1.filesCron)({ config, integration: integrationLogic, period: '12' }).catch(console.error));
99
- cron.schedule('0 0 * * *', () => (0, cron_1.filesCron)({ config, integration: integrationLogic, period: '24' }).catch(console.error));
100
109
  }
110
+ // remove user errors
111
+ cron.schedule('0 0 * * *', () => __awaiter(this, void 0, void 0, function* () {
112
+ const date = (0, util_1.getPreviousDate)(integrationLogic.userErrorLifetimeDays);
113
+ yield (0, storage_1.getStorage)().deleteAllUsersErrorsOlderThan(`${date.getTime()}`);
114
+ }));
101
115
  if (integrationLogic.webhooks) {
102
116
  app.post(`${integrationLogic.webhooks.crowdinWebhookUrl
103
117
  ? integrationLogic.webhooks.crowdinWebhookUrl
@@ -142,6 +142,10 @@ export interface IntegrationLogic {
142
142
  asyncProgress?: {
143
143
  checkInterval?: number;
144
144
  };
145
+ /**
146
+ * The duration for storing user errors, default is 30 days.
147
+ */
148
+ userErrorLifetimeDays: number;
145
149
  }
146
150
  export interface LoginForm {
147
151
  fields: FormEntity[];
@@ -1,11 +1,28 @@
1
- import Crowdin from '@crowdin/crowdin-api-client';
2
- import { Config } from '../../../types';
1
+ import Crowdin, { SourceFilesModel } from '@crowdin/crowdin-api-client';
2
+ import { Config, CrowdinContextInfo } from '../../../types';
3
3
  import { CronJob, IntegrationLogic, IntegrationRequest, Provider, UpdateIntegrationRequest } from '../types';
4
+ import { JobClientType, JobType } from './types';
4
5
  export declare function runJob({ config, integration, job, }: {
5
6
  config: Config;
6
7
  integration: IntegrationLogic;
7
8
  job: CronJob;
8
9
  }): Promise<void>;
10
+ export declare function runUpdateProviderJob({ integrationId, crowdinId, type, title, payload, jobType, projectId, client, integration, context, credentials, rootFolder, appSettings, reRunJobId, }: {
11
+ integrationId: string;
12
+ crowdinId: string;
13
+ type: JobType;
14
+ title?: string;
15
+ payload?: any;
16
+ jobType: JobClientType;
17
+ projectId: number;
18
+ client: Crowdin;
19
+ integration: IntegrationLogic;
20
+ context: CrowdinContextInfo;
21
+ credentials: any;
22
+ rootFolder?: SourceFilesModel.Directory;
23
+ appSettings?: any;
24
+ reRunJobId?: string;
25
+ }): Promise<void>;
9
26
  export declare function filesCron({ config, integration, period, }: {
10
27
  config: Config;
11
28
  integration: IntegrationLogic;
@@ -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.removeFinishedJobs = exports.createOrUpdateSyncSettings = exports.skipFoldersFromIntegrationRequest = exports.filesCron = exports.runJob = void 0;
35
+ exports.removeFinishedJobs = exports.createOrUpdateSyncSettings = exports.skipFoldersFromIntegrationRequest = exports.filesCron = exports.runUpdateProviderJob = exports.runJob = void 0;
36
36
  const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
37
37
  const storage_1 = require("../../../storage");
38
38
  const connection_1 = require("../../../util/connection");
@@ -80,6 +80,52 @@ function runJob({ config, integration, job, }) {
80
80
  });
81
81
  }
82
82
  exports.runJob = runJob;
83
+ function runUpdateProviderJob({ integrationId, crowdinId, type, title, payload, jobType, projectId, client, integration, context, credentials, rootFolder, appSettings, reRunJobId, }) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ try {
86
+ yield (0, job_1.runAsJob)({
87
+ integrationId,
88
+ crowdinId,
89
+ type,
90
+ title,
91
+ payload,
92
+ jobType,
93
+ projectId,
94
+ client,
95
+ reRunJobId,
96
+ jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
97
+ const updateParams = {
98
+ projectId,
99
+ client,
100
+ credentials,
101
+ request: payload,
102
+ rootFolder,
103
+ appSettings,
104
+ job,
105
+ };
106
+ if (type === types_2.JobType.UPDATE_TO_CROWDIN) {
107
+ yield integration.updateCrowdin(updateParams);
108
+ }
109
+ else if (type === types_2.JobType.UPDATE_TO_INTEGRATION) {
110
+ yield integration.updateIntegration(updateParams);
111
+ }
112
+ }),
113
+ });
114
+ }
115
+ catch (e) {
116
+ const action = type === types_2.JobType.UPDATE_TO_CROWDIN ? 'Auto sync files to Crowdin' : 'Auto sync files to External Service';
117
+ yield (0, logger_1.handleUserError)({
118
+ action,
119
+ error: e,
120
+ crowdinId: crowdinId,
121
+ clientId: integrationId,
122
+ });
123
+ (0, logger_1.logError)(e, context);
124
+ throw e;
125
+ }
126
+ });
127
+ }
128
+ exports.runUpdateProviderJob = runUpdateProviderJob;
83
129
  function filesCron({ config, integration, period, }) {
84
130
  return __awaiter(this, void 0, void 0, function* () {
85
131
  (0, logger_1.log)(`Starting files cron job with period [${period}]`);
@@ -237,7 +283,7 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
237
283
  removeInContextLanguage(filesToProcess, projectData);
238
284
  }
239
285
  try {
240
- yield (0, job_1.runAsJob)({
286
+ yield runUpdateProviderJob({
241
287
  integrationId: syncSettings.integrationId,
242
288
  crowdinId: syncSettings.crowdinId,
243
289
  type: types_2.JobType.UPDATE_TO_INTEGRATION,
@@ -246,27 +292,14 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
246
292
  jobType: types_2.JobClientType.CRON,
247
293
  projectId: projectId,
248
294
  client: crowdinClient,
249
- jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
250
- yield integration.updateIntegration({
251
- projectId,
252
- client: crowdinClient,
253
- credentials: apiCredentials,
254
- request: filesToProcess,
255
- rootFolder,
256
- appSettings: intConfig,
257
- job,
258
- });
259
- }),
295
+ integration,
296
+ context,
297
+ credentials: apiCredentials,
298
+ rootFolder,
299
+ appSettings: intConfig,
260
300
  });
261
301
  }
262
302
  catch (e) {
263
- yield (0, logger_1.handleUserError)({
264
- action: 'Auto sync files to External Service',
265
- error: e,
266
- crowdinId: syncSettings.crowdinId,
267
- clientId: syncSettings.integrationId,
268
- });
269
- (0, logger_1.logError)(e, context);
270
303
  return;
271
304
  }
272
305
  if (Object.keys(newFiles).length) {
@@ -289,7 +322,7 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
289
322
  (0, logger_1.log)(`Executing updateCrowdin task for files cron job with period [${period}] for project ${projectId}. Files ${intFiles.length}`);
290
323
  const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
291
324
  try {
292
- yield (0, job_1.runAsJob)({
325
+ yield runUpdateProviderJob({
293
326
  integrationId: syncSettings.integrationId,
294
327
  crowdinId: syncSettings.crowdinId,
295
328
  type: types_2.JobType.UPDATE_TO_CROWDIN,
@@ -298,27 +331,14 @@ function processSyncSettings({ config, integration, period, syncSettings, }) {
298
331
  jobType: types_2.JobClientType.CRON,
299
332
  projectId: projectId,
300
333
  client: crowdinClient,
301
- jobCallback: (job) => __awaiter(this, void 0, void 0, function* () {
302
- yield integration.updateCrowdin({
303
- projectId,
304
- client: crowdinClient,
305
- credentials: apiCredentials,
306
- request: intFiles,
307
- rootFolder,
308
- appSettings: intConfig,
309
- job,
310
- });
311
- }),
334
+ integration,
335
+ context,
336
+ credentials: apiCredentials,
337
+ rootFolder,
338
+ appSettings: intConfig,
312
339
  });
313
340
  }
314
341
  catch (e) {
315
- yield (0, logger_1.handleUserError)({
316
- action: 'Auto sync files to Crowdin',
317
- error: e,
318
- crowdinId: syncSettings.crowdinId,
319
- clientId: syncSettings.integrationId,
320
- });
321
- (0, logger_1.logError)(e, context);
322
342
  return;
323
343
  }
324
344
  if (Object.keys(newFiles).length) {
@@ -255,6 +255,9 @@ function applyIntegrationModuleDefaults(config, integration) {
255
255
  if (!((_b = integration.filtering) === null || _b === void 0 ? void 0 : _b.hasOwnProperty('crowdinLanguages'))) {
256
256
  integration.filtering = Object.assign(Object.assign({}, (integration.filtering || {})), { crowdinLanguages: true });
257
257
  }
258
+ if (!integration.userErrorLifetimeDays) {
259
+ integration.userErrorLifetimeDays = 30;
260
+ }
258
261
  }
259
262
  exports.applyIntegrationModuleDefaults = applyIntegrationModuleDefaults;
260
263
  function constructOauthUrl({ config, integration, clientId, loginForm, }) {
@@ -1,7 +1,8 @@
1
1
  import { JobClient, JobClientType, JobType } from './types';
2
2
  import { Response } from 'express';
3
3
  import Crowdin from '@crowdin/crowdin-api-client';
4
- export declare function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobCallback, onError, }: {
4
+ import { Config } from '../../../types';
5
+ export declare function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobCallback, onError, reRunJobId, }: {
5
6
  integrationId: string;
6
7
  crowdinId: string;
7
8
  type: JobType;
@@ -13,4 +14,6 @@ export declare function runAsJob({ integrationId, crowdinId, type, title, payloa
13
14
  jobType: JobClientType;
14
15
  jobCallback: (arg1: JobClient) => Promise<any>;
15
16
  onError?: (e: any) => Promise<void>;
17
+ reRunJobId?: string;
16
18
  }): Promise<void>;
19
+ export declare function reRunInProgressJobs(config: Config): Promise<void>;
@@ -1,4 +1,27 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
2
25
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
26
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
27
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -9,37 +32,48 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
32
  });
10
33
  };
11
34
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.runAsJob = void 0;
35
+ exports.reRunInProgressJobs = exports.runAsJob = void 0;
13
36
  const types_1 = require("./types");
14
37
  const storage_1 = require("../../../storage");
15
38
  const logger_1 = require("../../../util/logger");
39
+ const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions"));
40
+ const connection_1 = require("../../../util/connection");
41
+ const defaults_1 = require("./defaults");
42
+ const cron_1 = require("./cron");
16
43
  const blockingJobs = {
17
44
  [types_1.JobType.UPDATE_TO_CROWDIN]: [types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION],
18
45
  [types_1.JobType.UPDATE_TO_INTEGRATION]: [types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION],
19
46
  [types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE]: [types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE],
20
47
  [types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE]: [types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE],
21
48
  };
22
- function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobCallback, onError, }) {
49
+ const maxAttempts = 3;
50
+ function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobCallback, onError, reRunJobId, }) {
23
51
  return __awaiter(this, void 0, void 0, function* () {
52
+ let jobId;
24
53
  const storage = (0, storage_1.getStorage)();
25
- const activeJobs = yield storage.getActiveJobs({ integrationId, crowdinId });
26
- if (activeJobs === null || activeJobs === void 0 ? void 0 : activeJobs.length) {
27
- const existingJob = activeJobs.find((job) => blockingJobs[type].includes(job.type));
28
- if (existingJob === null || existingJob === void 0 ? void 0 : existingJob.id) {
29
- if (res) {
30
- res.status(202).send({ jobId: existingJob.id, message: 'Another sync is running' });
54
+ if (!reRunJobId) {
55
+ const activeJobs = yield storage.getActiveJobs({ integrationId, crowdinId });
56
+ if (activeJobs === null || activeJobs === void 0 ? void 0 : activeJobs.length) {
57
+ const existingJob = activeJobs.find((job) => blockingJobs[type].includes(job.type));
58
+ if (existingJob === null || existingJob === void 0 ? void 0 : existingJob.id) {
59
+ if (res) {
60
+ res.status(202).send({ jobId: existingJob.id, message: 'Another sync is running' });
61
+ }
62
+ (0, logger_1.log)(`Unable to run new job '${type}', with title '${title}', already running jobId ${existingJob.id}.`);
63
+ return;
31
64
  }
32
- (0, logger_1.log)(`Unable to run new job '${type}', with title '${title}', already running jobId ${existingJob.id}.`);
33
- return;
34
65
  }
66
+ jobId = yield storage.createJob({
67
+ integrationId,
68
+ crowdinId,
69
+ type,
70
+ title: title || '',
71
+ payload: JSON.stringify(payload),
72
+ });
73
+ }
74
+ else {
75
+ jobId = reRunJobId;
35
76
  }
36
- const jobId = yield storage.createJob({
37
- integrationId,
38
- crowdinId,
39
- type,
40
- title: title || '',
41
- payload: JSON.stringify(payload),
42
- });
43
77
  if (res) {
44
78
  res.status(202).send({ jobId });
45
79
  }
@@ -49,7 +83,7 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
49
83
  return yield storage.getJob({ id: jobId });
50
84
  });
51
85
  },
52
- update: function updateProgress({ progress, status, info, data }) {
86
+ update: function updateProgress({ progress, status, info, data, attempt }) {
53
87
  return __awaiter(this, void 0, void 0, function* () {
54
88
  const prevData = yield this.get();
55
89
  if ((prevData === null || prevData === void 0 ? void 0 : prevData.status) === types_1.JobStatus.CANCELED) {
@@ -61,13 +95,14 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
61
95
  status: status || types_1.JobStatus.IN_PROGRESS,
62
96
  info,
63
97
  data: JSON.stringify(data),
98
+ attempt,
64
99
  });
65
100
  return { isCanceled: false };
66
101
  });
67
102
  },
68
103
  type: jobType,
69
104
  fetchTranslation: ({ fileId, languageId }) => __awaiter(this, void 0, void 0, function* () {
70
- const translationCache = yield storage.getTranslationCache({
105
+ const translationCache = yield storage.getFileTranslationCacheByLanguage({
71
106
  integrationId,
72
107
  crowdinId,
73
108
  fileId,
@@ -89,20 +124,28 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
89
124
  }
90
125
  return translation;
91
126
  }),
92
- translationUploaded: ({ fileId, languageId, etag }) => __awaiter(this, void 0, void 0, function* () {
93
- const translationCache = yield storage.getTranslationCache({
127
+ // translationUploaded: async ({ fileId, languageId, etag }) => {
128
+ translationUploaded: ({ fileId, translationParams }) => __awaiter(this, void 0, void 0, function* () {
129
+ const translationCache = (yield storage.getFileTranslationCache({
94
130
  integrationId,
95
131
  crowdinId,
96
132
  fileId,
97
- languageId,
98
- });
99
- (0, logger_1.log)(`Saving etag translation for file ${fileId} in language ${languageId}`);
100
- if (!translationCache) {
101
- yield storage.saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag });
102
- }
103
- else {
104
- yield storage.updateTranslationCache({ integrationId, crowdinId, fileId, languageId, etag });
133
+ })) || [];
134
+ const cacheMap = new Map(translationCache.map((cache) => [cache.languageId, cache]));
135
+ const updates = [];
136
+ const inserts = [];
137
+ for (const { languageId, etag } of translationParams) {
138
+ const cache = cacheMap.get(languageId);
139
+ if (cache) {
140
+ (0, logger_1.log)(`Updating etag translation for file ${fileId} in language ${languageId}`);
141
+ updates.push(storage.updateTranslationCache({ integrationId, crowdinId, fileId, languageId, etag }));
142
+ }
143
+ else {
144
+ (0, logger_1.log)(`Saving new etag translation for file ${fileId} in language ${languageId}`);
145
+ inserts.push(storage.saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag }));
146
+ }
105
147
  }
148
+ yield Promise.all([...updates, ...inserts]);
106
149
  }),
107
150
  };
108
151
  try {
@@ -128,3 +171,86 @@ function runAsJob({ integrationId, crowdinId, type, title, payload, res, project
128
171
  });
129
172
  }
130
173
  exports.runAsJob = runAsJob;
174
+ function reRunInProgressJobs(config) {
175
+ return __awaiter(this, void 0, void 0, function* () {
176
+ const storage = (0, storage_1.getStorage)();
177
+ const inProgressJobs = yield storage.getAllInProgressJobs();
178
+ if (!(inProgressJobs === null || inProgressJobs === void 0 ? void 0 : inProgressJobs.length)) {
179
+ return;
180
+ }
181
+ yield Promise.all(inProgressJobs.map((activeJob) => __awaiter(this, void 0, void 0, function* () {
182
+ if (activeJob && activeJob.status !== types_1.JobStatus.IN_PROGRESS) {
183
+ return;
184
+ }
185
+ if (activeJob.attempt && activeJob.attempt >= maxAttempts) {
186
+ yield storage.updateJob({ id: activeJob.id, status: types_1.JobStatus.FAILED });
187
+ return;
188
+ }
189
+ const { integrationId, crowdinId, type, title, payload } = activeJob;
190
+ const integration = config.projectIntegration;
191
+ let crowdinClient;
192
+ const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(integrationId);
193
+ const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(crowdinId);
194
+ const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(integrationId);
195
+ if (!integrationCredentials || !crowdinCredentials) {
196
+ return;
197
+ }
198
+ const projectId = crowdinAppFunctions.getProjectId(integrationCredentials.id);
199
+ const context = {
200
+ jwtPayload: {
201
+ context: {
202
+ // eslint-disable-next-line @typescript-eslint/camelcase
203
+ project_id: projectId,
204
+ // eslint-disable-next-line @typescript-eslint/camelcase
205
+ organization_id: crowdinCredentials.organizationId,
206
+ // eslint-disable-next-line @typescript-eslint/camelcase
207
+ organization_domain: crowdinCredentials.domain,
208
+ // eslint-disable-next-line @typescript-eslint/camelcase
209
+ user_id: crowdinCredentials.userId,
210
+ },
211
+ },
212
+ };
213
+ try {
214
+ const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({
215
+ config,
216
+ credentials: crowdinCredentials,
217
+ autoRenew: true,
218
+ context,
219
+ });
220
+ crowdinClient = preparedCrowdinClient.client;
221
+ }
222
+ catch (e) {
223
+ (0, logger_1.logError)(e);
224
+ return;
225
+ }
226
+ const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId);
227
+ const apiCredentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials);
228
+ const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config)
229
+ ? JSON.parse(integrationConfig.config)
230
+ : { schedule: '0', condition: '0' };
231
+ yield storage.updateJob({ attempt: +(activeJob.attempt || 0) + 1, id: activeJob.id });
232
+ if ([types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION].includes(type)) {
233
+ yield (0, cron_1.runUpdateProviderJob)({
234
+ integrationId,
235
+ crowdinId,
236
+ type,
237
+ title,
238
+ payload: JSON.parse(payload),
239
+ jobType: types_1.JobClientType.RERUN,
240
+ projectId,
241
+ client: crowdinClient,
242
+ integration,
243
+ context,
244
+ credentials: apiCredentials,
245
+ rootFolder,
246
+ appSettings: intConfig,
247
+ reRunJobId: activeJob.id,
248
+ });
249
+ }
250
+ else if ([types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE, types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE].includes(type)) {
251
+ yield storage.updateJob({ status: types_1.JobStatus.CANCELED, id: activeJob.id });
252
+ }
253
+ })));
254
+ });
255
+ }
256
+ exports.reRunInProgressJobs = reRunInProgressJobs;
@@ -18,7 +18,8 @@ export declare enum JobStatus {
18
18
  }
19
19
  export declare enum JobClientType {
20
20
  CRON = "cron",
21
- MANUAL = "manual"
21
+ MANUAL = "manual",
22
+ RERUN = "rerun"
22
23
  }
23
24
  export interface Job {
24
25
  id: string;
@@ -35,6 +36,7 @@ export interface Job {
35
36
  finishedAt?: number;
36
37
  eta?: number;
37
38
  info?: string;
39
+ attempt?: number;
38
40
  }
39
41
  export type GetJobParams = Pick<Job, 'id'>;
40
42
  export type GetActiveJobsParams = Pick<Job, 'integrationId' | 'crowdinId'>;
@@ -45,6 +47,7 @@ export type UpdateJobParams = {
45
47
  status?: JobStatus;
46
48
  info?: string;
47
49
  data?: string;
50
+ attempt?: number;
48
51
  };
49
52
  export type JobClient = {
50
53
  get: () => Promise<Job | undefined>;
@@ -53,7 +56,7 @@ export type JobClient = {
53
56
  translationUploaded: SaveUploadedFileTranslation;
54
57
  fetchTranslation: FetchTranslation;
55
58
  };
56
- export type UpdateJobProgress = ({ progress, status, info, data, }: Omit<UpdateJobParams, 'id'>) => Promise<{
59
+ export type UpdateJobProgress = ({ progress, status, info, data, attempt, }: Omit<UpdateJobParams, 'id'>) => Promise<{
57
60
  isCanceled: boolean;
58
61
  }>;
59
62
  export interface GetAllNewFilesArgs {
@@ -67,10 +70,12 @@ export interface GetAllNewFilesArgs {
67
70
  integrationSettings: any;
68
71
  syncSettings: IntegrationSyncSettings;
69
72
  }
70
- export type SaveUploadedFileTranslation = ({ fileId, languageId, etag, }: {
73
+ export type SaveUploadedFileTranslation = ({ fileId, translationParams, }: {
71
74
  fileId: number;
72
- languageId: string;
73
- etag: string;
75
+ translationParams: {
76
+ languageId: string;
77
+ etag: string;
78
+ }[];
74
79
  }) => Promise<void>;
75
80
  export type FetchTranslation = ({ fileId, languageId, }: {
76
81
  fileId: number;
@@ -83,5 +88,6 @@ export interface TranslationCache {
83
88
  languageId: string;
84
89
  etag?: string;
85
90
  }
86
- export type GetTranslationCacheParams = Pick<TranslationCache, 'integrationId' | 'crowdinId' | 'fileId' | 'languageId'>;
91
+ export type GetFileTranslationCache = Pick<TranslationCache, 'integrationId' | 'crowdinId' | 'fileId'>;
92
+ export type GetFileTranslationCacheByLanguageParams = Pick<TranslationCache, 'integrationId' | 'crowdinId' | 'fileId' | 'languageId'>;
87
93
  export type UpdateTranslationCacheParams = Pick<TranslationCache, 'integrationId' | 'crowdinId' | 'fileId' | 'languageId' | 'etag'>;
@@ -20,4 +20,5 @@ var JobClientType;
20
20
  (function (JobClientType) {
21
21
  JobClientType["CRON"] = "cron";
22
22
  JobClientType["MANUAL"] = "manual";
23
+ JobClientType["RERUN"] = "rerun";
23
24
  })(JobClientType = exports.JobClientType || (exports.JobClientType = {}));