@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
JavaScript
;
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;