@crowdin/app-project-module 0.98.0 → 0.100.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 CHANGED
@@ -210,6 +210,7 @@ function addCrowdinEndpoints(app, clientConfig) {
210
210
  fileProcessingApps.registerFilePostImport({ config, app });
211
211
  fileProcessingApps.registerFilePreExport({ config, app });
212
212
  fileProcessingApps.registerFilePostExport({ config, app });
213
+ fileProcessingApps.registerFileTranslationsAlignmentExport({ config, app });
213
214
  apiApp.register({ config, app });
214
215
  aiProvider.register({ config, app });
215
216
  aiPromptProvider.register({ config, app });
@@ -15,7 +15,9 @@ const crowdin_file_progress_1 = __importDefault(require("../integration/handlers
15
15
  const crowdin_files_1 = __importDefault(require("../integration/handlers/crowdin-files"));
16
16
  const crowdin_update_1 = __importDefault(require("../integration/handlers/crowdin-update"));
17
17
  const integration_data_1 = __importDefault(require("../integration/handlers/integration-data"));
18
+ const job_info_deprecated_1 = __importDefault(require("../integration/handlers/job-info-deprecated"));
18
19
  const job_info_1 = __importDefault(require("../integration/handlers/job-info"));
20
+ const job_list_1 = __importDefault(require("../integration/handlers/job-list"));
19
21
  const job_cancel_1 = __importDefault(require("../integration/handlers/job-cancel"));
20
22
  const integration_login_1 = __importDefault(require("../integration/handlers/integration-login"));
21
23
  const integration_update_1 = __importDefault(require("../integration/handlers/integration-update"));
@@ -85,6 +87,20 @@ function getDefaultApiEndpointsManifest(config) {
85
87
  method: types_1.RequestMethods.POST,
86
88
  description: 'Update integration data',
87
89
  documentationUrl: '/api-docs#tag/Files/operation/integration.update',
90
+ }, {
91
+ key: 'job-list-api',
92
+ name: 'Job List',
93
+ url: '/all-jobs',
94
+ method: types_1.RequestMethods.GET,
95
+ description: 'Get All Jobs',
96
+ documentationUrl: '/api-docs#tag/Jobs/operation/job.list',
97
+ }, {
98
+ key: 'job-info-api',
99
+ name: 'Job Info',
100
+ url: '/job-info',
101
+ method: types_1.RequestMethods.GET,
102
+ description: 'Get Job Info',
103
+ documentationUrl: '/api-docs#tag/Jobs/operation/job.info',
88
104
  }, {
89
105
  key: 'job-get-api',
90
106
  name: 'Job Status',
@@ -94,7 +110,7 @@ function getDefaultApiEndpointsManifest(config) {
94
110
  documentationUrl: '/api-docs#tag/Jobs/operation/job.get',
95
111
  }, {
96
112
  key: 'job-cancel-api',
97
- name: 'Job Status',
113
+ name: 'Cancel Job',
98
114
  url: '/jobs',
99
115
  method: types_1.RequestMethods.DELETE,
100
116
  description: 'Cancel Job',
@@ -308,6 +324,74 @@ function addDefaultApiEndpoints(app, config) {
308
324
  checkSubscriptionExpiration: true,
309
325
  moduleKey: 'integration-update-api',
310
326
  }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, integration_update_1.default)(config, config.projectIntegration));
327
+ /**
328
+ * @openapi
329
+ * /all-jobs:
330
+ * get:
331
+ * tags:
332
+ * - 'Jobs'
333
+ * summary: 'List Jobs'
334
+ * operationId: job.list
335
+ * parameters:
336
+ * - $ref: '#/components/parameters/ProjectId'
337
+ * - name: limit
338
+ * in: query
339
+ * required: false
340
+ * description: 'Number of jobs to return'
341
+ * schema:
342
+ * type: integer
343
+ * example: 25
344
+ * - name: offset
345
+ * in: query
346
+ * required: false
347
+ * description: 'Number of jobs to skip'
348
+ * schema:
349
+ * type: integer
350
+ * example: 0
351
+ * responses:
352
+ * 200:
353
+ * description: 'Job information retrieved successfully'
354
+ * content:
355
+ * application/json:
356
+ * schema:
357
+ * $ref: '#/components/schemas/JobResponse'
358
+ */
359
+ app.get('/all-jobs', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
360
+ config,
361
+ optional: false,
362
+ checkSubscriptionExpiration: true,
363
+ moduleKey: 'job-list-api',
364
+ }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_list_1.default)());
365
+ /**
366
+ * @openapi
367
+ * /job-info:
368
+ * get:
369
+ * tags:
370
+ * - 'Jobs'
371
+ * summary: 'Get Job Info'
372
+ * operationId: job.info
373
+ * parameters:
374
+ * - $ref: '#/components/parameters/ProjectId'
375
+ * - name: jobId
376
+ * in: query
377
+ * required: true
378
+ * schema:
379
+ * type: string
380
+ * example: 067da473-fc0b-43e3-b0a2-09d26af130c1
381
+ * responses:
382
+ * 200:
383
+ * description: 'Job information retrieved successfully'
384
+ * content:
385
+ * application/json:
386
+ * schema:
387
+ * $ref: '#/components/schemas/JobResponse'
388
+ */
389
+ app.get('/job-info', api_call_1.default, json_response_1.default, (0, crowdin_client_1.default)({
390
+ config,
391
+ optional: false,
392
+ checkSubscriptionExpiration: true,
393
+ moduleKey: 'job-info-api',
394
+ }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_info_1.default)());
311
395
  /**
312
396
  * @openapi
313
397
  * /jobs:
@@ -316,6 +400,7 @@ function addDefaultApiEndpoints(app, config) {
316
400
  * - 'Jobs'
317
401
  * summary: 'Get Job Info'
318
402
  * operationId: job.get
403
+ * deprecated: true
319
404
  * parameters:
320
405
  * - $ref: '#/components/parameters/ProjectId'
321
406
  * - name: jobId
@@ -337,7 +422,7 @@ function addDefaultApiEndpoints(app, config) {
337
422
  optional: false,
338
423
  checkSubscriptionExpiration: true,
339
424
  moduleKey: 'job-get-api',
340
- }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_info_1.default)(config));
425
+ }), (0, integration_credentials_1.default)(config, config.projectIntegration), (0, job_info_deprecated_1.default)(config));
341
426
  /**
342
427
  * @openapi
343
428
  * /jobs:
@@ -0,0 +1,5 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ import { Config, CrowdinClientRequest } from '../../../types';
4
+ import { TranslationsAlignmentLogic } from '../types';
5
+ export default function handle(baseConfig: Config, config: TranslationsAlignmentLogic, folderName: string): (req: 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;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ const fs_1 = __importDefault(require("fs"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const util_1 = require("../../../util");
18
+ const files_1 = require("../util/files");
19
+ function handle(baseConfig, config, folderName) {
20
+ const folderPath = config.filesFolder || baseConfig.dbFolder;
21
+ if (!fs_1.default.existsSync(path_1.default.join(folderPath, folderName))) {
22
+ fs_1.default.mkdirSync(path_1.default.join(folderPath, folderName), { recursive: true });
23
+ }
24
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
25
+ var _a;
26
+ const response = {};
27
+ const baseFilesUrl = `${baseConfig.baseUrl}/file/download/${folderName}`;
28
+ const body = req.body;
29
+ // Skip assets (e.g., images, videos, or other media files) as they typically don't need to be processed
30
+ if (((_a = body === null || body === void 0 ? void 0 : body.file) === null || _a === void 0 ? void 0 : _a.type) === 'assets' && !('processAssets' in config && config.processAssets)) {
31
+ res.sendStatus(415);
32
+ return;
33
+ }
34
+ const fileSourceStringsContent = body.sourceStringsUrl
35
+ ? (yield (0, files_1.getFileStrings)(body.sourceStringsUrl))
36
+ : body.sourceStrings;
37
+ const fileTranslationsContent = body.translationStringsUrl
38
+ ? (yield (0, files_1.getFileStrings)(body.translationStringsUrl))
39
+ : body.translationStrings;
40
+ const fileProcessResult = yield config.fileProcess(body, {
41
+ sourceStrings: fileSourceStringsContent,
42
+ translations: fileTranslationsContent,
43
+ }, req.crowdinApiClient, req.crowdinContext, req.crowdinContext.jwtPayload.context.project_id);
44
+ const { strings, error: processingErrorMessage } = fileProcessResult;
45
+ if (!strings) {
46
+ response.translations = [];
47
+ res.send({
48
+ data: response,
49
+ error: processingErrorMessage ? { message: processingErrorMessage } : undefined,
50
+ });
51
+ return;
52
+ }
53
+ const stringsNDJson = strings.map((s) => JSON.stringify(s)).join('\n\r');
54
+ const bufferData = Buffer.from(stringsNDJson, 'utf-8');
55
+ if (Buffer.byteLength(bufferData) < files_1.MAX_BODY_SIZE) {
56
+ response.translations = strings;
57
+ }
58
+ else {
59
+ let url;
60
+ if (config.storeFile) {
61
+ url = yield config.storeFile(bufferData);
62
+ }
63
+ else {
64
+ const storedFile = yield (0, files_1.storeFile)(bufferData, path_1.default.join(folderPath, folderName));
65
+ url = `${baseFilesUrl}?file=${storedFile}`;
66
+ }
67
+ response.translationsUrl = url;
68
+ }
69
+ res.send({ data: response, error: processingErrorMessage ? { message: processingErrorMessage } : undefined });
70
+ }));
71
+ }
72
+ exports.default = handle;
@@ -20,3 +20,7 @@ export declare function registerFilePostExport({ config, app }: {
20
20
  config: Config;
21
21
  app: Express;
22
22
  }): void;
23
+ export declare function registerFileTranslationsAlignmentExport({ config, app }: {
24
+ config: Config;
25
+ app: Express;
26
+ }): void;
@@ -3,11 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.registerFilePostExport = exports.registerFilePreExport = exports.registerFilePostImport = exports.registerFilePreImport = exports.registerCustomFileFormat = void 0;
6
+ exports.registerFileTranslationsAlignmentExport = exports.registerFilePostExport = exports.registerFilePreExport = exports.registerFilePostImport = exports.registerFilePreImport = exports.registerCustomFileFormat = void 0;
7
7
  const crowdin_client_1 = __importDefault(require("../../middlewares/crowdin-client"));
8
8
  const custom_file_format_1 = __importDefault(require("./handlers/custom-file-format"));
9
9
  const file_download_1 = __importDefault(require("./handlers/file-download"));
10
10
  const pre_post_process_1 = __importDefault(require("./handlers/pre-post-process"));
11
+ const translations_alignment_1 = __importDefault(require("./handlers/translations-alignment"));
11
12
  const types_1 = require("./types");
12
13
  const defaults_1 = require("./util/defaults");
13
14
  function registerCustomFileFormat({ config, app }) {
@@ -87,3 +88,17 @@ function registerFilePostExport({ config, app }) {
87
88
  app.get(`/file/download/${types_1.ProcessFileJobType.POST_EXPORT}`, (0, file_download_1.default)(config, config.filePostExport, types_1.ProcessFileJobType.POST_EXPORT));
88
89
  }
89
90
  exports.registerFilePostExport = registerFilePostExport;
91
+ function registerFileTranslationsAlignmentExport({ config, app }) {
92
+ if (!config.fileTranslationsAlignmentExport) {
93
+ return;
94
+ }
95
+ (0, defaults_1.applyFileProcessorsModuleDefaults)(config, config.fileTranslationsAlignmentExport);
96
+ app.post('/translations-alignment', (0, crowdin_client_1.default)({
97
+ config,
98
+ optional: false,
99
+ checkSubscriptionExpiration: true,
100
+ moduleKey: config.fileTranslationsAlignmentExport.key,
101
+ }), (0, translations_alignment_1.default)(config, config.fileTranslationsAlignmentExport, types_1.ProcessFileJobType.TRANSLATIONS_ALIGNMENT));
102
+ app.get(`/file/download/${types_1.ProcessFileJobType.POST_EXPORT}`, (0, file_download_1.default)(config, config.fileTranslationsAlignmentExport, types_1.ProcessFileJobType.TRANSLATIONS_ALIGNMENT));
103
+ }
104
+ exports.registerFileTranslationsAlignmentExport = registerFileTranslationsAlignmentExport;
@@ -60,9 +60,17 @@ export interface CustomFileFormatLogic extends FileProcessLogic {
60
60
  }
61
61
  export type FileImportExportLogic = FilePreImportLogic | FilePostImportLogic | FilePreExportLogic | FilePostExportLogic;
62
62
  export type FileImportExportContent = ProcessFileString[] | Buffer | undefined;
63
+ export type TranslationAlignmentContent = TranslationAlignmentString[] | Buffer | undefined;
64
+ export interface FileTranslationAlignmentContent {
65
+ sourceStrings: TranslationAlignmentContent;
66
+ translations: TranslationAlignmentContent;
67
+ }
63
68
  export interface BaseFileProcessLogic<T> {
64
69
  fileProcess: (req: ProcessFileRequest, content: FileImportExportContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<T>;
65
70
  }
71
+ export interface TranslationAlignmentProcessLogin<T> {
72
+ fileProcess: (req: ProcessTranslationAlignmentRequest, content: FileTranslationAlignmentContent, client: Crowdin, context: CrowdinContextInfo, projectId: number) => Promise<T>;
73
+ }
66
74
  export interface FilePreImportLogic extends FileProcessLogic, BaseFileProcessLogic<ContentFileResponse> {
67
75
  /**
68
76
  * Set to `true` to enable asset processing in the application.
@@ -79,6 +87,19 @@ export interface FilePostExportLogic extends FileProcessLogic, BaseFileProcessLo
79
87
  */
80
88
  processAssets?: boolean;
81
89
  }
90
+ export interface TranslationsAlignmentLogic extends FileProcessLogic, TranslationAlignmentProcessLogin<TranslationAlignmentResponse> {
91
+ }
92
+ export interface ProcessTranslationAlignmentRequest {
93
+ jobType: ProcessFileJobType;
94
+ file: ProcessFileRecord;
95
+ sourceLanguage: LanguagesModel.Language;
96
+ targetLanguages: LanguagesModel.Language[];
97
+ sourceStrings: TranslationAlignmentString[];
98
+ sourceStringsUrl: string;
99
+ translationStrings: TranslationAlignmentString[];
100
+ translationStringsUrl: string;
101
+ getRawContent?: (encoding: BufferEncoding) => Promise<string | Buffer>;
102
+ }
82
103
  export interface ProcessFileRequest {
83
104
  jobType: ProcessFileJobType;
84
105
  file: ProcessFileRecord;
@@ -102,7 +123,8 @@ export declare enum ProcessFileJobType {
102
123
  PRE_IMPORT = "pre-import-file",
103
124
  POST_IMPORT = "post-import-file",
104
125
  PRE_EXPORT = "pre-export-file",
105
- POST_EXPORT = "post-export-file"
126
+ POST_EXPORT = "post-export-file",
127
+ TRANSLATIONS_ALIGNMENT = "translations-alignment-file"
106
128
  }
107
129
  export interface ParseFileResponse {
108
130
  previewFile?: Buffer;
@@ -121,6 +143,14 @@ export interface StringsFileResponse extends ParseFileResponse {
121
143
  export interface ContentFileResponse extends BuildFileResponse {
122
144
  notModified?: boolean;
123
145
  }
146
+ export interface TranslationAlignmentResponse {
147
+ strings?: TranslationAlignmentResult[];
148
+ error?: string;
149
+ }
150
+ export interface TranslationAlignmentResult {
151
+ sourceStringId: number;
152
+ text: string | SourceStringsModel.PluralText;
153
+ }
124
154
  export interface ProcessFileString {
125
155
  previewId?: number;
126
156
  id: number;
@@ -135,6 +165,11 @@ export interface ProcessFileString {
135
165
  translations?: StringTranslations;
136
166
  uniqId?: string;
137
167
  }
168
+ export interface TranslationAlignmentString {
169
+ id: number | null;
170
+ text: string | SourceStringsModel.PluralText;
171
+ context: string;
172
+ }
138
173
  export interface StringTranslations {
139
174
  [language: string]: {
140
175
  text: string | SourceStringsModel.PluralText;
@@ -9,4 +9,5 @@ var ProcessFileJobType;
9
9
  ProcessFileJobType["POST_IMPORT"] = "post-import-file";
10
10
  ProcessFileJobType["PRE_EXPORT"] = "pre-export-file";
11
11
  ProcessFileJobType["POST_EXPORT"] = "post-export-file";
12
+ ProcessFileJobType["TRANSLATIONS_ALIGNMENT"] = "translations-alignment-file";
12
13
  })(ProcessFileJobType = exports.ProcessFileJobType || (exports.ProcessFileJobType = {}));
@@ -64,7 +64,7 @@ function handle(config, integration) {
64
64
  payload = yield (0, files_1.expandFilesTree)(payload, req, integration, job);
65
65
  payload = (0, lodash_uniqby_1.default)(payload, 'id');
66
66
  }
67
- if (!forcePushSources && (payload === null || payload === void 0 ? void 0 : payload.length)) {
67
+ if (integration.forcePushSources === true && !forcePushSources && (payload === null || payload === void 0 ? void 0 : payload.length)) {
68
68
  payload = yield (0, files_1.filterSyncedData)(payload);
69
69
  }
70
70
  const result = yield integration.updateCrowdin({
@@ -0,0 +1,4 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
3
+ import { Config } from '../../../types';
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;
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const types_1 = require("../util/types");
13
+ const util_1 = require("../../../util");
14
+ const storage_1 = require("../../../storage");
15
+ const cron_1 = require("../util/cron");
16
+ const defaults_1 = require("../util/defaults");
17
+ const connection_1 = require("../../../util/connection");
18
+ const logger_1 = require("../../../util/logger");
19
+ const MINUTES = 60;
20
+ function getHumanETA(ms) {
21
+ const seconds = Math.floor(ms / 1000);
22
+ let minutes = Math.floor(seconds / 60);
23
+ const hours = Math.floor(minutes / 60);
24
+ if (seconds < 60) {
25
+ return 'Less than a minute remaining';
26
+ }
27
+ minutes = minutes % 60;
28
+ const timeParts = [];
29
+ if (hours) {
30
+ timeParts.push(`${hours} ${hours > 1 ? 'hours' : 'hour'}`);
31
+ }
32
+ if (minutes) {
33
+ timeParts.push(`${minutes} ${minutes > 1 ? 'minutes' : 'minute'}`);
34
+ }
35
+ return `About ${timeParts.join(' and ')} remaining`;
36
+ }
37
+ function handle(config) {
38
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
39
+ const isApi = req.isApiCall;
40
+ const id = req.query.jobId || req.body.jobId;
41
+ if (!id) {
42
+ req.logInfo('Get active jobs');
43
+ const jobs = yield (0, storage_1.getStorage)().getActiveJobs({
44
+ integrationId: req.crowdinContext.clientId,
45
+ crowdinId: req.crowdinContext.crowdinId,
46
+ });
47
+ if (isApi && jobs) {
48
+ const filteredJobs = jobs.map((job) => ({
49
+ id: job.id,
50
+ progress: job.progress,
51
+ status: job.status,
52
+ title: job.title,
53
+ }));
54
+ req.logInfo(`Returning active filtered jobs info ${JSON.stringify(filteredJobs, null, 2)}`);
55
+ res.send(filteredJobs);
56
+ return;
57
+ }
58
+ req.logInfo(`Returning active jobs info ${JSON.stringify(jobs, null, 2)}`);
59
+ res.send(jobs);
60
+ return;
61
+ }
62
+ req.logInfo(`Get job info for id ${id}`);
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
+ }
71
+ if (job && job.status === types_1.JobStatus.IN_PROGRESS && job.progress > 5 && job.updatedAt) {
72
+ job.eta = ((Date.now() - job.createdAt) / job.progress) * (100 - job.progress);
73
+ job.info = getHumanETA(job.eta) + (job.info ? `\n${job.info}` : '');
74
+ }
75
+ if (isApi && job) {
76
+ const filteredJob = {
77
+ id: job.id,
78
+ progress: job.progress,
79
+ status: job.status,
80
+ title: job.title,
81
+ };
82
+ req.logInfo(`Returning filtered job info ${JSON.stringify(filteredJob, null, 2)}`);
83
+ res.send([filteredJob]);
84
+ return;
85
+ }
86
+ if (job && (job === null || job === void 0 ? void 0 : job.updatedAt) && Date.now() - job.updatedAt >= MINUTES * 60 * 1000) {
87
+ const context = req.crowdinContext;
88
+ const projectId = context.jwtPayload.context.project_id;
89
+ const integration = config.projectIntegration;
90
+ const crowdinId = req.crowdinContext.crowdinId;
91
+ const integrationId = req.crowdinContext.clientId;
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
+ }
133
+ }
134
+ req.logInfo(`Returning job info ${JSON.stringify(job, null, 2)}`);
135
+ res.send(job);
136
+ }));
137
+ }
138
+ exports.default = handle;
@@ -1,4 +1,3 @@
1
1
  /// <reference types="qs" />
2
2
  import { Response } from 'express';
3
- import { Config } from '../../../types';
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,11 +12,6 @@ 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
- const cron_1 = require("../util/cron");
16
- const defaults_1 = require("../util/defaults");
17
- const connection_1 = require("../../../util/connection");
18
- const logger_1 = require("../../../util/logger");
19
- const MINUTES = 60;
20
15
  function getHumanETA(ms) {
21
16
  const seconds = Math.floor(ms / 1000);
22
17
  let minutes = Math.floor(seconds / 60);
@@ -34,30 +29,19 @@ function getHumanETA(ms) {
34
29
  }
35
30
  return `About ${timeParts.join(' and ')} remaining`;
36
31
  }
37
- function handle(config) {
32
+ function handle() {
38
33
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
39
34
  const isApi = req.isApiCall;
40
35
  const id = req.query.jobId || req.body.jobId;
41
36
  if (!id) {
42
- req.logInfo('Get active jobs');
43
- const jobs = yield (0, storage_1.getStorage)().getActiveJobs({
44
- integrationId: req.crowdinContext.clientId,
45
- crowdinId: req.crowdinContext.crowdinId,
46
- });
47
- if (isApi && jobs) {
48
- const filteredJobs = jobs.map((job) => ({
49
- id: job.id,
50
- progress: job.progress,
51
- status: job.status,
52
- title: job.title,
53
- }));
54
- req.logInfo(`Returning active filtered jobs info ${JSON.stringify(filteredJobs, null, 2)}`);
55
- res.send(filteredJobs);
56
- return;
37
+ if (isApi) {
38
+ return res.status(400).json({
39
+ error: {
40
+ message: 'jobId is required',
41
+ },
42
+ });
57
43
  }
58
- req.logInfo(`Returning active jobs info ${JSON.stringify(jobs, null, 2)}`);
59
- res.send(jobs);
60
- return;
44
+ return res.status(400).send('jobId is required');
61
45
  }
62
46
  req.logInfo(`Get job info for id ${id}`);
63
47
  const job = yield (0, storage_1.getStorage)().getJob({ id });
@@ -73,64 +57,25 @@ function handle(config) {
73
57
  job.info = getHumanETA(job.eta) + (job.info ? `\n${job.info}` : '');
74
58
  }
75
59
  if (isApi && job) {
76
- const filteredJob = {
60
+ const baseJob = {
77
61
  id: job.id,
62
+ info: job.info,
63
+ data: job.data,
64
+ attempt: job.attempt,
65
+ payload: job.payload,
66
+ eta: job.eta,
78
67
  progress: job.progress,
79
68
  status: job.status,
80
69
  title: job.title,
70
+ type: job.type,
71
+ createdAt: new Date(+job.createdAt).toISOString(),
72
+ updatedAt: job.updatedAt ? new Date(+job.updatedAt).toISOString() : null,
73
+ finishedAt: job.finishedAt ? new Date(+job.finishedAt).toISOString() : null,
81
74
  };
82
- req.logInfo(`Returning filtered job info ${JSON.stringify(filteredJob, null, 2)}`);
83
- res.send([filteredJob]);
75
+ req.logInfo(`Returning filtered job info ${JSON.stringify(baseJob, null, 2)}`);
76
+ res.send(baseJob);
84
77
  return;
85
78
  }
86
- if (job && (job === null || job === void 0 ? void 0 : job.updatedAt) && Date.now() - job.updatedAt >= MINUTES * 60 * 1000) {
87
- const context = req.crowdinContext;
88
- const projectId = context.jwtPayload.context.project_id;
89
- const integration = config.projectIntegration;
90
- const crowdinId = req.crowdinContext.crowdinId;
91
- const integrationId = req.crowdinContext.clientId;
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
- }
133
- }
134
79
  req.logInfo(`Returning job info ${JSON.stringify(job, null, 2)}`);
135
80
  res.send(job);
136
81
  }));
@@ -0,0 +1,3 @@
1
+ /// <reference types="qs" />
2
+ import { Response } from 'express';
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;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const util_1 = require("../../../util");
13
+ const storage_1 = require("../../../storage");
14
+ function handle() {
15
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
16
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 25;
17
+ const offset = req.query.offset ? parseInt(req.query.offset, 10) : 0;
18
+ req.logInfo('Get all jobs');
19
+ const jobs = yield (0, storage_1.getStorage)().getAllJobs({
20
+ integrationId: req.crowdinContext.clientId,
21
+ crowdinId: req.crowdinContext.crowdinId,
22
+ limit,
23
+ offset,
24
+ });
25
+ if (!jobs) {
26
+ req.logInfo('No jobs found');
27
+ res.send([]);
28
+ return;
29
+ }
30
+ const filteredJobs = jobs.map((job) => {
31
+ const baseJob = {
32
+ id: job.id,
33
+ progress: job.progress,
34
+ status: job.status,
35
+ title: job.title,
36
+ type: job.type,
37
+ createdAt: new Date(+job.createdAt).toISOString(),
38
+ finishedAt: job.finishedAt ? new Date(+job.finishedAt).toISOString() : null,
39
+ };
40
+ return baseJob;
41
+ });
42
+ req.logInfo(`Returning ${filteredJobs.length} jobs`);
43
+ res.send(filteredJobs);
44
+ }));
45
+ }
46
+ exports.default = handle;
@@ -54,7 +54,9 @@ const integration_logout_1 = __importDefault(require("./handlers/integration-log
54
54
  const integration_update_1 = __importDefault(require("./handlers/integration-update"));
55
55
  const integration_webhook_1 = __importDefault(require("./handlers/integration-webhook"));
56
56
  const job_cancel_1 = __importDefault(require("./handlers/job-cancel"));
57
+ const job_info_deprecated_1 = __importDefault(require("./handlers/job-info-deprecated"));
57
58
  const job_info_1 = __importDefault(require("./handlers/job-info"));
59
+ const job_list_1 = __importDefault(require("./handlers/job-list"));
58
60
  const main_1 = __importDefault(require("./handlers/main"));
59
61
  const oauth_login_1 = __importDefault(require("./handlers/oauth-login"));
60
62
  const oauth_url_1 = __importDefault(require("./handlers/oauth-url"));
@@ -88,12 +90,24 @@ function register({ config, app }) {
88
90
  checkSubscriptionExpiration: true,
89
91
  moduleKey: integrationLogic.key,
90
92
  }), (0, subscription_info_1.default)(config));
93
+ app.get('/api/all-jobs', json_response_1.default, (0, crowdin_client_1.default)({
94
+ config,
95
+ optional: false,
96
+ checkSubscriptionExpiration: true,
97
+ moduleKey: integrationLogic.key,
98
+ }), (0, job_list_1.default)());
99
+ app.get('/api/job-info', json_response_1.default, (0, crowdin_client_1.default)({
100
+ config,
101
+ optional: false,
102
+ checkSubscriptionExpiration: true,
103
+ moduleKey: integrationLogic.key,
104
+ }), (0, job_info_1.default)());
91
105
  app.get('/api/jobs', json_response_1.default, (0, crowdin_client_1.default)({
92
106
  config,
93
107
  optional: false,
94
108
  checkSubscriptionExpiration: true,
95
109
  moduleKey: integrationLogic.key,
96
- }), (0, job_info_1.default)(config));
110
+ }), (0, job_info_deprecated_1.default)(config));
97
111
  app.delete('/api/jobs', json_response_1.default, (0, crowdin_client_1.default)({
98
112
  config,
99
113
  optional: false,
@@ -126,3 +126,9 @@ export interface IntegrationSyncedData {
126
126
  updatedAt: string;
127
127
  files?: any;
128
128
  }
129
+ export type GetAllJobsParams = {
130
+ integrationId: string;
131
+ crowdinId: string;
132
+ limit: number;
133
+ offset: number;
134
+ };
@@ -86,6 +86,17 @@ function handle(config) {
86
86
  },
87
87
  ];
88
88
  }
89
+ if (config.fileTranslationsAlignmentExport) {
90
+ // prevent possible overrides of the other modules
91
+ config.fileTranslationsAlignmentExport = Object.assign(Object.assign({}, config.fileTranslationsAlignmentExport), { key: config.identifier + '-ftae' });
92
+ modules['file-translations-alignment'] = [
93
+ {
94
+ key: config.fileTranslationsAlignmentExport.key,
95
+ signaturePatterns: config.fileTranslationsAlignmentExport.signaturePatterns,
96
+ url: '/translations-alignment',
97
+ },
98
+ ];
99
+ }
89
100
  if (config.customMT) {
90
101
  // prevent possible overrides of the other modules
91
102
  config.customMT = Object.assign(Object.assign({}, config.customMT), { key: config.identifier + '-mt' });
@@ -1,6 +1,6 @@
1
1
  import { IntegrationConfig, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks, Provider } from '../modules/integration/types';
2
2
  import { Config, CrowdinCredentials, UnauthorizedConfig } from '../types';
3
- import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData } from '../modules/integration/util/types';
3
+ import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData, GetAllJobsParams } from '../modules/integration/util/types';
4
4
  import { UserErrors } from './types';
5
5
  export declare const TABLES: {
6
6
  crowdin_credentials: string;
@@ -62,6 +62,7 @@ export interface Storage {
62
62
  getActiveJobs(params: GetActiveJobsParams): Promise<Job[] | undefined>;
63
63
  deleteFinishedJobs(): Promise<void>;
64
64
  getAllInProgressJobs(): Promise<Job[] | undefined>;
65
+ getAllJobs(params: GetAllJobsParams): Promise<Job[] | undefined>;
65
66
  saveTranslationCache(params: TranslationCache): Promise<void>;
66
67
  getFileTranslationCache(params: GetFileTranslationCache): Promise<TranslationCache[] | undefined>;
67
68
  getFileTranslationCacheByLanguage(params: GetFileTranslationCacheByLanguageParams): Promise<TranslationCache | undefined>;
@@ -1,6 +1,6 @@
1
1
  import { Storage } from '.';
2
2
  import { CrowdinCredentials } from '../types';
3
- import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData } from '../modules/integration/util/types';
3
+ import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData, GetAllJobsParams } from '../modules/integration/util/types';
4
4
  import { IntegrationConfig, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks } from '../modules/integration/types';
5
5
  import { UserErrors } from './types';
6
6
  export interface MySQLStorageConfig {
@@ -83,6 +83,7 @@ export declare class MySQLStorage implements Storage {
83
83
  getActiveJobs({ integrationId, crowdinId }: GetActiveJobsParams): Promise<Job[] | undefined>;
84
84
  deleteFinishedJobs(): Promise<void>;
85
85
  getAllInProgressJobs(): Promise<Job[] | undefined>;
86
+ getAllJobs({ integrationId, crowdinId, limit, offset }: GetAllJobsParams): Promise<Job[] | undefined>;
86
87
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }: TranslationCache): Promise<void>;
87
88
  getFileTranslationCache({ integrationId, crowdinId, fileId, }: GetFileTranslationCache): Promise<TranslationCache[] | undefined>;
88
89
  getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }: GetFileTranslationCacheByLanguageParams): Promise<TranslationCache | undefined>;
@@ -289,6 +289,7 @@ class MySQLStorage {
289
289
  yield connection.execute('DELETE FROM job WHERE crowdin_id = ?', [id]);
290
290
  yield connection.execute('DELETE FROM translation_file_cache WHERE crowdin_id = ?', [id]);
291
291
  yield connection.execute('DELETE FROM unsynced_files WHERE crowdin_id = ?', [id]);
292
+ yield connection.execute('DELETE FROM synced_data WHERE crowdin_id = ?', [id]);
292
293
  }));
293
294
  });
294
295
  }
@@ -342,6 +343,7 @@ class MySQLStorage {
342
343
  yield connection.execute('DELETE FROM webhooks where integration_id = ?', [id]);
343
344
  yield connection.execute('DELETE FROM job where integration_id = ?', [id]);
344
345
  yield connection.execute('DELETE FROM unsynced_files where integration_id = ?', [id]);
346
+ yield connection.execute('DELETE FROM synced_data where integration_id = ?', [id]);
345
347
  }));
346
348
  });
347
349
  }
@@ -356,6 +358,7 @@ class MySQLStorage {
356
358
  yield connection.execute('DELETE FROM user_errors where crowdin_id = ?', [crowdinId]);
357
359
  yield connection.execute('DELETE FROM job where crowdin_id = ?', [crowdinId]);
358
360
  yield connection.execute('DELETE FROM unsynced_files where crowdin_id = ?', [crowdinId]);
361
+ yield connection.execute('DELETE FROM synced_data where crowdin_id = ?', [crowdinId]);
359
362
  }));
360
363
  });
361
364
  }
@@ -749,6 +752,36 @@ class MySQLStorage {
749
752
  }));
750
753
  });
751
754
  }
755
+ getAllJobs({ integrationId, crowdinId, limit, offset }) {
756
+ return __awaiter(this, void 0, void 0, function* () {
757
+ yield this.dbPromise;
758
+ return this.executeQuery((connection) => __awaiter(this, void 0, void 0, function* () {
759
+ const [rows] = yield connection.execute(`
760
+ SELECT
761
+ id,
762
+ integration_id as "integrationId",
763
+ crowdin_id as "crowdinId",
764
+ type,
765
+ payload,
766
+ progress,
767
+ status,
768
+ title,
769
+ info,
770
+ data,
771
+ attempt,
772
+ created_at as "createdAt",
773
+ updated_at as "updatedAt",
774
+ finished_at as "finishedAt"
775
+ FROM job
776
+ WHERE integration_id = ?
777
+ AND crowdin_id = ?
778
+ ORDER BY created_at DESC
779
+ LIMIT ? OFFSET ?
780
+ `, [integrationId, crowdinId, limit, offset]);
781
+ return rows || [];
782
+ }));
783
+ });
784
+ }
752
785
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }) {
753
786
  return __awaiter(this, void 0, void 0, function* () {
754
787
  yield this.dbPromise;
@@ -2,7 +2,7 @@ import { Client } from 'pg';
2
2
  import { Storage } from '.';
3
3
  import { CrowdinCredentials } from '../types';
4
4
  import { IntegrationConfig, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks } from '../modules/integration/types';
5
- import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData } from '../modules/integration/util/types';
5
+ import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData, GetAllJobsParams } from '../modules/integration/util/types';
6
6
  import { UserErrors } from './types';
7
7
  export interface PostgreStorageConfig {
8
8
  host?: string;
@@ -93,6 +93,7 @@ export declare class PostgreStorage implements Storage {
93
93
  getActiveJobs({ integrationId, crowdinId }: GetActiveJobsParams): Promise<Job[] | undefined>;
94
94
  deleteFinishedJobs(): Promise<void>;
95
95
  getAllInProgressJobs(): Promise<Job[] | undefined>;
96
+ getAllJobs({ integrationId, crowdinId, limit, offset }: GetAllJobsParams): Promise<Job[] | undefined>;
96
97
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }: TranslationCache): Promise<void>;
97
98
  getFileTranslationCache({ integrationId, crowdinId, fileId, }: GetFileTranslationCache): Promise<TranslationCache[] | undefined>;
98
99
  getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }: GetFileTranslationCacheByLanguageParams): Promise<TranslationCache | undefined>;
@@ -334,6 +334,7 @@ class PostgreStorage {
334
334
  yield client.query('DELETE FROM job WHERE crowdin_id = $1', [id]);
335
335
  yield client.query('DELETE FROM translation_file_cache WHERE crowdin_id = $1', [id]);
336
336
  yield client.query('DELETE FROM unsynced_files WHERE crowdin_id = $1', [id]);
337
+ yield client.query('DELETE FROM synced_data WHERE crowdin_id = $1', [id]);
337
338
  }));
338
339
  });
339
340
  }
@@ -387,6 +388,7 @@ class PostgreStorage {
387
388
  yield client.query('DELETE FROM webhooks where integration_id = $1', [id]);
388
389
  yield client.query('DELETE FROM job where integration_id = $1', [id]);
389
390
  yield client.query('DELETE FROM unsynced_files where integration_id = $1', [id]);
391
+ yield client.query('DELETE FROM synced_data where integration_id = $1', [id]);
390
392
  }));
391
393
  });
392
394
  }
@@ -401,6 +403,7 @@ class PostgreStorage {
401
403
  yield client.query('DELETE FROM user_errors where crowdin_id = $1', [crowdinId]);
402
404
  yield client.query('DELETE FROM job where crowdin_id = $1', [crowdinId]);
403
405
  yield client.query('DELETE FROM unsynced_files where crowdin_id = $1', [crowdinId]);
406
+ yield client.query('DELETE FROM synced_data where crowdin_id = $1', [crowdinId]);
404
407
  }));
405
408
  });
406
409
  }
@@ -754,6 +757,35 @@ class PostgreStorage {
754
757
  }));
755
758
  });
756
759
  }
760
+ getAllJobs({ integrationId, crowdinId, limit, offset }) {
761
+ return __awaiter(this, void 0, void 0, function* () {
762
+ yield this.dbPromise;
763
+ return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () {
764
+ const res = yield client.query(`
765
+ SELECT
766
+ id,
767
+ integration_id as "integrationId",
768
+ crowdin_id as "crowdinId",
769
+ type,
770
+ payload,
771
+ progress,
772
+ status,
773
+ title,
774
+ info,
775
+ data,
776
+ attempt,
777
+ created_at as "createdAt",
778
+ updated_at as "updatedAt",
779
+ finished_at as "finishedAt"
780
+ FROM job
781
+ WHERE integration_id = $1 AND crowdin_id = $2
782
+ ORDER BY created_at DESC
783
+ LIMIT $3 OFFSET $4
784
+ `, [integrationId, crowdinId, limit, offset]);
785
+ return (res === null || res === void 0 ? void 0 : res.rows) || [];
786
+ }));
787
+ });
788
+ }
757
789
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }) {
758
790
  return __awaiter(this, void 0, void 0, function* () {
759
791
  yield this.dbPromise;
@@ -1,7 +1,7 @@
1
1
  import { Storage } from '.';
2
2
  import { CrowdinCredentials } from '../types';
3
3
  import { IntegrationConfig, IntegrationCredentials, IntegrationFilesSnapshot, IntegrationSyncSettings, IntegrationWebhooks } from '../modules/integration/types';
4
- import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData } from '../modules/integration/util/types';
4
+ import { CreateJobParams, GetActiveJobsParams, GetJobParams, GetFileTranslationCacheByLanguageParams, Job, TranslationCache, UpdateJobParams, UpdateTranslationCacheParams, GetFileTranslationCache, UnsyncedFiles, GetUnsyncedFiles, IntegrationSyncedData, GetAllJobsParams } from '../modules/integration/util/types';
5
5
  import { UserErrors } from './types';
6
6
  export interface SQLiteStorageConfig {
7
7
  dbFolder: string;
@@ -81,6 +81,7 @@ export declare class SQLiteStorage implements Storage {
81
81
  getActiveJobs({ integrationId, crowdinId }: GetActiveJobsParams): Promise<Job[] | undefined>;
82
82
  deleteFinishedJobs(): Promise<void>;
83
83
  getAllInProgressJobs(): Promise<Job[] | undefined>;
84
+ getAllJobs({ integrationId, crowdinId, limit, offset }: GetAllJobsParams): Promise<Job[] | undefined>;
84
85
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag }: TranslationCache): Promise<void>;
85
86
  getFileTranslationCache({ integrationId, crowdinId, fileId, }: GetFileTranslationCache): Promise<TranslationCache[] | undefined>;
86
87
  getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }: GetFileTranslationCacheByLanguageParams): Promise<TranslationCache | undefined>;
@@ -359,6 +359,7 @@ class SQLiteStorage {
359
359
  yield this.run('DELETE FROM job WHERE crowdin_id = ?', [id]);
360
360
  yield this.run('DELETE FROM translation_file_cache WHERE crowdin_id = ?', [id]);
361
361
  yield this.run('DELETE FROM unsynced_files WHERE crowdin_id = ?', [id]);
362
+ yield this.run('DELETE FROM synced_data WHERE crowdin_id = ?', [id]);
362
363
  });
363
364
  }
364
365
  saveIntegrationCredentials(id, credentials, crowdinId) {
@@ -393,6 +394,7 @@ class SQLiteStorage {
393
394
  yield this.run('DELETE FROM webhooks where integration_id = ?', [id]);
394
395
  yield this.run('DELETE FROM job where integration_id = ?', [id]);
395
396
  yield this.run('DELETE FROM unsynced_files where integration_id = ?', [id]);
397
+ yield this.run('DELETE FROM synced_data where integration_id = ?', [id]);
396
398
  });
397
399
  }
398
400
  deleteAllIntegrationCredentials(crowdinId) {
@@ -404,6 +406,7 @@ class SQLiteStorage {
404
406
  yield this.run('DELETE FROM user_errors where crowdin_id = ?', [crowdinId]);
405
407
  yield this.run('DELETE FROM job where crowdin_id = ?', [crowdinId]);
406
408
  yield this.run('DELETE FROM unsynced_files where crowdin_id = ?', [crowdinId]);
409
+ yield this.run('DELETE FROM synced_data where crowdin_id = ?', [crowdinId]);
407
410
  });
408
411
  }
409
412
  saveMetadata(id, metadata, crowdinId) {
@@ -662,6 +665,31 @@ class SQLiteStorage {
662
665
  `, [types_2.JobStatus.IN_PROGRESS, types_2.JobStatus.CREATED]);
663
666
  });
664
667
  }
668
+ getAllJobs({ integrationId, crowdinId, limit, offset }) {
669
+ return __awaiter(this, void 0, void 0, function* () {
670
+ return this.each(`
671
+ SELECT
672
+ id,
673
+ integration_id as integrationId,
674
+ crowdin_id as crowdinId,
675
+ type,
676
+ payload,
677
+ progress,
678
+ status,
679
+ title,
680
+ info,
681
+ data,
682
+ attempt,
683
+ created_at as createdAt,
684
+ updated_at as updatedAt,
685
+ finished_at as finishedAt
686
+ FROM job
687
+ WHERE integration_id = ? AND crowdin_id = ?
688
+ ORDER BY created_at DESC
689
+ LIMIT ? OFFSET ?
690
+ `, [integrationId, crowdinId, limit, offset]);
691
+ });
692
+ }
665
693
  saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag }) {
666
694
  return this.run(`
667
695
  INSERT
package/out/types.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ContextContent } from './modules/context-menu/types';
5
5
  import { CustomMTLogic } from './modules/custom-mt/types';
6
6
  import { CustomSpellcheckerModule } from './modules/custom-spell-check/types';
7
7
  import { EditorPanels } from './modules/editor-right-panel/types';
8
- import { CustomFileFormatLogic, FilePostExportLogic, FilePostImportLogic, FilePreExportLogic, FilePreImportLogic } from './modules/file-processing/types';
8
+ import { CustomFileFormatLogic, FilePostExportLogic, FilePostImportLogic, FilePreExportLogic, FilePreImportLogic, TranslationsAlignmentLogic } from './modules/file-processing/types';
9
9
  import { IntegrationLogic } from './modules/integration/types';
10
10
  import { Storage } from './storage';
11
11
  import { MySQLStorageConfig } from './storage/mysql';
@@ -203,6 +203,7 @@ export interface ClientConfig extends ImagePath {
203
203
  filePostImport?: FilePostImportLogic;
204
204
  filePreExport?: FilePreExportLogic;
205
205
  filePostExport?: FilePostExportLogic;
206
+ fileTranslationsAlignmentExport?: TranslationsAlignmentLogic;
206
207
  /**
207
208
  * Disable formatting logs
208
209
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdin/app-project-module",
3
- "version": "0.98.0",
3
+ "version": "0.100.0",
4
4
  "description": "Module that generates for you all common endpoints for serving standalone Crowdin App",
5
5
  "main": "out/index.js",
6
6
  "types": "out/index.d.ts",