UNPKG

@crowdin/app-project-module

Version:

Module that generates for you all common endpoints for serving standalone Crowdin App

293 lines (292 loc) 15.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.runAsJob = runAsJob; exports.reRunInProgressJobs = reRunInProgressJobs; const storage_1 = require("../../../storage"); const token_1 = require("../../../util/app-functions/token"); const connection_1 = require("../../../util/connection"); const logger_1 = require("../../../util/logger"); const cron_1 = require("./cron"); const defaults_1 = require("./defaults"); const types_1 = require("./types"); const blockingJobs = { [types_1.JobType.UPDATE_TO_CROWDIN]: [types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION], [types_1.JobType.UPDATE_TO_INTEGRATION]: [types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION], [types_1.JobType.UPDATE_TARGET_LANGUAGES]: [types_1.JobType.UPDATE_TARGET_LANGUAGES], [types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE]: [types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE], [types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE]: [types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE], [types_1.JobType.INTEGRATION_SETTINGS_SAVE]: [types_1.JobType.INTEGRATION_SETTINGS_SAVE], }; const maxAttempts = 3; const store = {}; function runAsJob(_a) { return __awaiter(this, arguments, void 0, function* ({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, responseData, jobCallback, onError, reRunJobId, forcePushTranslations, initiatedBy, }) { let jobId; const storage = (0, storage_1.getStorage)(); if (!reRunJobId) { const activeJobs = yield storage.getActiveJobs({ integrationId, crowdinId }); if (activeJobs === null || activeJobs === void 0 ? void 0 : activeJobs.length) { const existingJob = activeJobs.find((job) => blockingJobs[type].includes(job.type)); if (existingJob === null || existingJob === void 0 ? void 0 : existingJob.id) { if (res) { res.status(202).send({ jobId: existingJob.id, message: 'Another sync is running' }); } (0, logger_1.log)(`Unable to run new job '${type}', with title '${title}', already running jobId ${existingJob.id}.`); return; } } jobId = yield storage.createJob({ integrationId, crowdinId, type, title: title || '', payload: JSON.stringify(payload), initiatedBy, }); } else { jobId = reRunJobId; } if (res) { res.status(202).send(Object.assign({ jobId }, responseData)); } const isDbStore = jobStoreType === 'db'; const job = { get: function getJob() { return __awaiter(this, void 0, void 0, function* () { return yield storage.getJob({ id: jobId }); }); }, update: function updateProgress(_a) { return __awaiter(this, arguments, void 0, function* ({ progress, status, info, data, attempt, errors, processedEntities, }) { const prevData = yield this.get(); if ((prevData === null || prevData === void 0 ? void 0 : prevData.status) === types_1.JobStatus.CANCELED) { return { isCanceled: true }; } yield storage.updateJob({ id: jobId, progress, status: status || types_1.JobStatus.IN_PROGRESS, info, data: JSON.stringify(data), attempt, errors, processedEntities: JSON.stringify(processedEntities), }); return { isCanceled: false }; }); }, type: jobType, fetchTranslation: (_a) => __awaiter(this, [_a], void 0, function* ({ fileId, languageId, params }) { var _b, _c, _d; const translationCache = isDbStore ? yield storage.getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }) : (_d = (_c = (_b = store[integrationId]) === null || _b === void 0 ? void 0 : _b[crowdinId]) === null || _c === void 0 ? void 0 : _c[fileId]) === null || _d === void 0 ? void 0 : _d[languageId]; if (!translationCache) { if (isDbStore) { yield storage.saveTranslationCache({ integrationId, crowdinId, fileId, languageId }); } else { if (!store[integrationId]) { store[integrationId] = {}; } if (!store[integrationId][crowdinId]) { store[integrationId] = { [crowdinId]: {}, }; } if (!store[integrationId][crowdinId][fileId]) { store[integrationId] = { [crowdinId]: { [fileId]: {}, }, }; } store[integrationId][crowdinId][fileId][languageId] = {}; } } let translation = null; try { (0, logger_1.log)(`Receiving translation for file ${fileId} in language ${languageId}`); translation = yield client.translationsApi.buildProjectFileTranslation(projectId, fileId, Object.assign({ targetLanguageId: languageId }, (params !== null && params !== void 0 ? params : {})), (translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag) && !forcePushTranslations ? translationCache === null || translationCache === void 0 ? void 0 : translationCache.etag : undefined); return translation; } catch (e) { (0, logger_1.log)(`Unable to get translation for file ${fileId} in language ${languageId}`); } return translation; }), translationUploaded: (_a) => __awaiter(this, [_a], void 0, function* ({ fileId, translationParams }) { if (!isDbStore) { translationParams.forEach(({ languageId, etag }) => (store[integrationId][crowdinId][fileId][languageId] = { etag })); return; } const translationCache = (yield storage.getFileTranslationCache({ integrationId, crowdinId, fileId, })) || []; const cacheMap = new Map(translationCache.map((cache) => [cache.languageId, cache])); const updates = []; const inserts = []; for (const { languageId, etag } of translationParams) { const cache = cacheMap.get(languageId); if (cache) { (0, logger_1.log)(`Updating etag translation for file ${fileId} in language ${languageId}`); updates.push(storage.updateTranslationCache({ integrationId, crowdinId, fileId, languageId, etag })); } else { (0, logger_1.log)(`Saving new etag translation for file ${fileId} in language ${languageId}`); inserts.push(storage.saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag })); } } yield Promise.all([...updates, ...inserts]); }), markFilesAsUnsynced: (_a) => __awaiter(this, [_a], void 0, function* ({ files }) { const unsyncedFilesData = yield storage.getUnsyncedFiles({ integrationId, crowdinId }); const updatedFileRecords = files.map((file) => ({ id: file.fileId, message: file.error, updatedAt: `${Date.now()}`, })); if (unsyncedFilesData) { const existingUnsyncedFiles = (unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files) ? JSON.parse(unsyncedFilesData.files) : []; const uniqueFilesMap = new Map([...existingUnsyncedFiles, ...updatedFileRecords].map((file) => [file.id, file])); const uniqueFiles = Array.from(uniqueFilesMap.values()); yield storage.updateUnsyncedFiles({ integrationId, crowdinId, files: JSON.stringify(uniqueFiles) }); } else { yield storage.saveUnsyncedFiles({ integrationId, crowdinId, files: JSON.stringify(updatedFileRecords), }); } }), unmarkFilesAsUnsynced: (_a) => __awaiter(this, [_a], void 0, function* ({ files }) { const unsyncedFilesData = yield storage.getUnsyncedFiles({ integrationId, crowdinId }); if (unsyncedFilesData) { const existingUnsyncedFiles = (unsyncedFilesData === null || unsyncedFilesData === void 0 ? void 0 : unsyncedFilesData.files) ? JSON.parse(unsyncedFilesData.files) : []; const updatedFiles = existingUnsyncedFiles.filter((file) => !files.some((unmarkFile) => unmarkFile.fileId === file.id)); yield storage.updateUnsyncedFiles({ integrationId, crowdinId, files: JSON.stringify(updatedFiles), }); } }), }; try { const data = yield jobCallback(job); yield job.update({ progress: 100, status: types_1.JobStatus.FINISHED, data, }); } catch (e) { yield job.update({ status: types_1.JobStatus.FAILED, info: (0, logger_1.getErrorMessage)(e), }); if (onError) { yield onError(e, job); } else { throw e; } } }); } function reRunInProgressJobs(config) { return __awaiter(this, void 0, void 0, function* () { const storage = (0, storage_1.getStorage)(); const inProgressJobs = yield storage.getAllInProgressJobs(); if (!(inProgressJobs === null || inProgressJobs === void 0 ? void 0 : inProgressJobs.length)) { return; } yield Promise.all(inProgressJobs.map((activeJob) => __awaiter(this, void 0, void 0, function* () { if (activeJob && ![types_1.JobStatus.IN_PROGRESS, types_1.JobStatus.CREATED].includes(activeJob.status)) { return; } if (activeJob.attempt && activeJob.attempt >= maxAttempts) { yield storage.updateJob({ id: activeJob.id, status: types_1.JobStatus.FAILED }); return; } const { integrationId, crowdinId, type, title, payload } = activeJob; const integration = config.projectIntegration; let crowdinClient; const integrationCredentials = yield (0, storage_1.getStorage)().getIntegrationCredentials(integrationId); const crowdinCredentials = yield (0, storage_1.getStorage)().getCrowdinCredentials(crowdinId); const integrationConfig = yield (0, storage_1.getStorage)().getIntegrationConfig(integrationId); if (!integrationCredentials || !crowdinCredentials) { return; } const projectId = (0, token_1.getProjectId)(integrationCredentials.id); const context = { jwtPayload: { context: { project_id: projectId, organization_id: crowdinCredentials.organizationId, organization_domain: crowdinCredentials.domain, user_id: crowdinCredentials.userId, }, }, }; try { const preparedCrowdinClient = yield (0, connection_1.prepareCrowdinClient)({ config, credentials: crowdinCredentials, autoRenew: true, context, }); crowdinClient = preparedCrowdinClient.client; } catch (e) { (0, logger_1.logError)(e); return; } const rootFolder = yield (0, defaults_1.getRootFolder)(config, integration, crowdinClient, projectId); const credentials = yield (0, connection_1.prepareIntegrationCredentials)(config, integration, integrationCredentials); const intConfig = (integrationConfig === null || integrationConfig === void 0 ? void 0 : integrationConfig.config) ? JSON.parse(integrationConfig.config) : { schedule: '0', condition: '0' }; yield storage.updateJob({ attempt: +(activeJob.attempt || 0) + 1, id: activeJob.id }); if ([types_1.JobType.UPDATE_TO_CROWDIN, types_1.JobType.UPDATE_TO_INTEGRATION].includes(type)) { yield (0, cron_1.runUpdateProviderJob)({ integrationId, crowdinId, type, title, payload: JSON.parse(payload), jobType: types_1.JobClientType.RERUN, projectId, client: crowdinClient, integration, context, credentials, rootFolder, settings: intConfig, reRunJobId: activeJob.id, }); } else if ([types_1.JobType.CROWDIN_SYNC_SETTINGS_SAVE, types_1.JobType.INTEGRATION_SYNC_SETTINGS_SAVE].includes(type)) { yield storage.updateJob({ status: types_1.JobStatus.CANCELED, id: activeJob.id }); } }))); }); }