UNPKG

@crowdin/app-project-module

Version:

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

314 lines (313 loc) 16.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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.reRunInProgressJobs = exports.runAsJob = void 0; const types_1 = require("./types"); const storage_1 = require("../../../storage"); const logger_1 = require("../../../util/logger"); const crowdinAppFunctions = __importStar(require("@crowdin/crowdin-apps-functions")); const connection_1 = require("../../../util/connection"); const defaults_1 = require("./defaults"); const cron_1 = require("./cron"); 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.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], }; const maxAttempts = 3; const store = {}; function runAsJob({ integrationId, crowdinId, type, title, payload, res, projectId, client, jobType, jobStoreType, jobCallback, onError, reRunJobId, forcePushTranslations, }) { return __awaiter(this, void 0, void 0, function* () { 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), }); } else { jobId = reRunJobId; } if (res) { res.status(202).send({ jobId }); } 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({ progress, status, info, data, attempt, errors, processedEntities, }) { return __awaiter(this, void 0, void 0, function* () { 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: ({ fileId, languageId, params }) => __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const translationCache = isDbStore ? yield storage.getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }) : (_c = (_b = (_a = store[integrationId]) === null || _a === void 0 ? void 0 : _a[crowdinId]) === null || _b === void 0 ? void 0 : _b[fileId]) === null || _c === void 0 ? void 0 : _c[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: ({ fileId, translationParams }) => __awaiter(this, void 0, void 0, function* () { 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: ({ files }) => __awaiter(this, void 0, void 0, function* () { 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: ({ files }) => __awaiter(this, void 0, void 0, function* () { 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; } } }); } exports.runAsJob = runAsJob; 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 = crowdinAppFunctions.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 apiCredentials = 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: apiCredentials, rootFolder, appSettings: 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 }); } }))); }); } exports.reRunInProgressJobs = reRunInProgressJobs;