UNPKG

@crowdin/app-project-module

Version:

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

965 lines (964 loc) 49 kB
"use strict"; /* eslint-disable no-unused-expressions */ /* eslint-disable @typescript-eslint/camelcase */ 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgreStorage = void 0; const pg_1 = require("pg"); const uuid_1 = require("uuid"); const types_1 = require("../types"); const types_2 = require("../modules/integration/util/types"); const util_1 = require("../util"); const fs_1 = __importDefault(require("fs")); const path_1 = require("path"); class PostgreStorage { constructor(config, directoryPath) { this.dbPromise = new Promise((res, rej) => { this._res = res; this._rej = rej; }); this.tables = { crowdin_credentials: `( id varchar primary key, app_secret varchar, domain varchar, user_id varchar, agent_id varchar, organization_id varchar, base_url varchar, access_token varchar not null, refresh_token varchar not null, expire varchar not null, type varchar not null )`, integration_credentials: `( id varchar primary key, credentials varchar, crowdin_id varchar not null, managers varchar )`, sync_settings: `( id serial primary key, files varchar, integration_id varchar not null, crowdin_id varchar not null, type varchar not null, provider varchar not null )`, app_metadata: `( id varchar primary key, data varchar, crowdin_id varchar )`, files_snapshot: `( id serial primary key, integration_id varchar not null, crowdin_id varchar not null, files varchar, provider varchar not null )`, webhooks: `( id serial primary key, file_id varchar not null, integration_id varchar not null, crowdin_id varchar not null, provider varchar not null )`, user_errors: `( id serial primary key, action varchar not null, message varchar not null, data varchar, created_at varchar not null, crowdin_id varchar not null, integration_id varchar )`, integration_settings: `( id serial primary key, integration_id varchar not null, crowdin_id varchar not null, config varchar )`, job: `( id varchar not null primary key, integration_id varchar not null, crowdin_id varchar not null, type varchar not null, title varchar null, progress int default 0, status varchar default '${types_2.JobStatus.CREATED}', payload varchar null, info varchar null, data varchar null, attempt int default 0, created_at varchar not null, updated_at varchar null, finished_at varchar null )`, translation_file_cache: `( id serial primary key, integration_id varchar not null, crowdin_id varchar not null, file_id int not null, language_id varchar not null, etag varchar )`, unsynced_files: `( id serial primary key, integration_id varchar not null, crowdin_id varchar not null, files varchar )`, synced_data: `( id serial primary key, files varchar, integration_id varchar not null, crowdin_id varchar not null, type varchar not null, updated_at varchar null )`, }; this.tableIndexes = { integration_credentials: { idx_crowdin: '(crowdin_id)', }, sync_settings: { idx_integration_provider: '(integration_id, provider)', idx_crowdin: '(crowdin_id)', idx_type: '(type)', }, files_snapshot: { idx_integration: '(integration_id)', idx_crowdin: '(crowdin_id)', }, webhooks: { idx_integration_crowdin: '(integration_id, crowdin_id)', idx_file_provider: '(file_id, provider)', }, user_errors: { idx_integration_crowdin: '(integration_id, crowdin_id)', idx_crowdin: '(crowdin_id)', idx_created_at: '(created_at)', }, integration_settings: { idx_integration: '(integration_id)', idx_crowdin: '(crowdin_id)', }, job: { idx_integration_crowdin: '(integration_id, crowdin_id) WHERE finished_at IS NULL', idx_finished_at: '(finished_at) WHERE finished_at IS NOT NULL', idx_status: '(status) WHERE finished_at IS NULL', }, translation_file_cache: { idx_integration_crowdin_file_language: '(integration_id, crowdin_id, file_id, language_id)', idx_crowdin: '(crowdin_id)', }, unsynced_files: { idx_integration_crowdin: '(integration_id, crowdin_id)', idx_crowdin: '(crowdin_id)', }, }; this.config = config; this.directoryPath = directoryPath; } executeQuery(command) { return __awaiter(this, void 0, void 0, function* () { return (0, util_1.executeWithRetry)(() => __awaiter(this, void 0, void 0, function* () { const client = new pg_1.Client(this.config); try { yield client.connect(); const res = yield command(client); yield client.end(); return res; } catch (error) { try { yield client.end(); throw error; } catch (nestedError) { throw nestedError; } } })); }); } hasDumpFiles(directoryPath) { if (!fs_1.default.existsSync(directoryPath)) { return false; } const [name, extension] = types_1.storageFiles.DUMP.split('%s'); const files = fs_1.default.readdirSync(directoryPath).filter((file) => file.startsWith(name) && file.endsWith(extension)); return files.length > 0; } migrate() { return __awaiter(this, void 0, void 0, function* () { try { if (this.directoryPath && this.hasDumpFiles(this.directoryPath)) { yield this.migrateFromSqlite(this.directoryPath); } yield this.executeQuery((client) => this.addTables(client)); this._res && this._res(); // TODO: temporary code yield this.executeQuery((client) => this.alterTables(client)); } catch (e) { console.error(e); this._rej && this._rej(); } }); } alterTables(client) { return __awaiter(this, void 0, void 0, function* () { yield this.addColumns(client, ['crowdin_id'], 'app_metadata'); yield this.addColumns(client, ['agent_id'], 'crowdin_credentials'); yield this.addColumn(client, 'attempt', 'job', 'int default 0'); yield this.addColumn(client, 'managers', 'integration_credentials', 'varchar NULL'); }); } addColumns(client, newColumns, tableName) { return __awaiter(this, void 0, void 0, function* () { const tableInfo = yield client.query('SELECT column_name FROM information_schema.columns WHERE table_name = $1', [tableName]); tableInfo.rows.map((columnInfo) => __awaiter(this, void 0, void 0, function* () { const index = newColumns.indexOf(columnInfo.column_name); if (~index) { newColumns.splice(index, 1); } })); for (const column of newColumns) { yield client.query(`ALTER TABLE ${tableName} ADD COLUMN ${column} varchar NULL;`); } }); } addColumn(client, columnName, tableName, columnType) { return __awaiter(this, void 0, void 0, function* () { const tableInfo = yield client.query('SELECT column_name FROM information_schema.columns WHERE table_name = $1', [tableName]); const exists = tableInfo.rows.some((columnInfo) => columnInfo.column_name === columnName); if (!exists) { yield client.query(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnType};`); } }); } addTables(client) { return __awaiter(this, void 0, void 0, function* () { for (const [tableName, tableSchema] of Object.entries(this.tables)) { yield client.query(`create table if not exists ${tableName} ${tableSchema}`); if (this.tableIndexes[tableName]) { for (const [indexName, indexSchema] of Object.entries(this.tableIndexes[tableName])) { yield client.query(`create index if not exists ${indexName} on ${tableName}${indexSchema}`); } } } }); } saveCrowdinCredentials(credentials) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO crowdin_credentials(id, app_secret, domain, user_id, agent_id, organization_id, base_url, access_token, refresh_token, expire, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)', [ credentials.id, credentials.appSecret, credentials.domain, credentials.userId, credentials.agentId, credentials.organizationId, credentials.baseUrl, credentials.accessToken, credentials.refreshToken, credentials.expire, credentials.type, ])); }); } updateCrowdinCredentials(credentials) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE crowdin_credentials SET app_secret = $1, domain = $2, user_id = $3, agent_id = $4, organization_id = $5, base_url = $6, access_token = $7, refresh_token = $8, expire = $9 WHERE id = $10', [ credentials.appSecret, credentials.domain, credentials.userId, credentials.agentId, credentials.organizationId, credentials.baseUrl, credentials.accessToken, credentials.refreshToken, credentials.expire, credentials.id, ])); }); } getCrowdinCredentials(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, app_secret as "appSecret", domain, user_id as "userId", agent_id as "agentId", organization_id as "organizationId", base_url as "baseUrl", access_token as "accessToken", refresh_token as "refreshToken", expire, type FROM crowdin_credentials WHERE id = $1', [id]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } getAllCrowdinCredentials() { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, app_secret as "appSecret", domain, user_id as "userId", agent_id as "agentId", organization_id as "organizationId", base_url as "baseUrl", access_token as "accessToken", refresh_token as "refreshToken", expire, type FROM crowdin_credentials', []); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } deleteCrowdinCredentials(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query('DELETE FROM crowdin_credentials where id = $1', [id]); yield client.query('DELETE FROM integration_credentials where crowdin_id = $1', [id]); yield client.query('DELETE FROM sync_settings WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM files_snapshot WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM app_metadata WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM webhooks WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM user_errors WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM integration_settings WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM job WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM translation_file_cache WHERE crowdin_id = $1', [id]); yield client.query('DELETE FROM unsynced_files WHERE crowdin_id = $1', [id]); })); }); } saveIntegrationCredentials(id, credentials, crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO integration_credentials(id, credentials, crowdin_id) VALUES ($1, $2, $3)', [ id, credentials, crowdinId, ])); }); } updateIntegrationCredentials(id, credentials) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE integration_credentials SET credentials = $1 WHERE id = $2', [credentials, id])); }); } updateIntegrationManagers(id, managers) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE integration_credentials SET managers = $1 WHERE id = $2', [managers, id])); }); } getIntegrationCredentials(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, credentials, crowdin_id as "crowdinId", managers FROM integration_credentials WHERE id = $1', [id]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } getAllIntegrationCredentials(crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, credentials, crowdin_id as "crowdinId", managers FROM integration_credentials WHERE crowdin_id = $1', [crowdinId]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } deleteIntegrationCredentials(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query('DELETE FROM integration_credentials where id = $1', [id]); yield client.query('DELETE FROM sync_settings where integration_id = $1', [id]); yield client.query('DELETE FROM files_snapshot where integration_id = $1', [id]); yield client.query('DELETE FROM webhooks where integration_id = $1', [id]); yield client.query('DELETE FROM job where integration_id = $1', [id]); yield client.query('DELETE FROM unsynced_files where integration_id = $1', [id]); })); }); } deleteAllIntegrationCredentials(crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query('DELETE FROM integration_credentials where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM sync_settings where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM files_snapshot where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM webhooks where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM user_errors where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM job where crowdin_id = $1', [crowdinId]); yield client.query('DELETE FROM unsynced_files where crowdin_id = $1', [crowdinId]); })); }); } saveMetadata(id, metadata, crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO app_metadata(id, data, crowdin_id) VALUES ($1, $2, $3)', [ id, JSON.stringify(metadata), crowdinId, ])); }); } updateMetadata(id, metadata, crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE app_metadata SET data = $1, crowdin_id = $2 WHERE id = $3', [ JSON.stringify(metadata), crowdinId, id, ])); }); } getMetadata(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT data FROM app_metadata WHERE id = $1', [id]); if (res === null || res === void 0 ? void 0 : res.rows[0]) { return JSON.parse(res === null || res === void 0 ? void 0 : res.rows[0].data); } })); }); } getAllMetadata() { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT * FROM app_metadata'); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } deleteMetadata(id) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('DELETE FROM app_metadata where id = $1', [id])); }); } getSyncSettingsByProvider(integrationId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, files, integration_id as "integrationId", crowdin_id as "crowdinId", type, provider FROM sync_settings WHERE integration_id = $1 AND provider = $2', [integrationId, provider]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } getSyncSettingsBySchedule(type, schedule) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT s.id, s.files, s.integration_id as "integrationId", s.crowdin_id as "crowdinId", s.type, s.provider FROM sync_settings s INNER JOIN integration_settings i ON s.integration_id = i.integration_id WHERE s.type = $1 AND CASE WHEN i.config IS NULL THEN false ELSE (i.config::json->>'schedule') = $2 END `, [type, schedule]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } saveSyncSettings(files, integrationId, crowdinId, type, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO sync_settings(files, integration_id, crowdin_id, type, provider) VALUES ($1, $2, $3, $4, $5)', [files, integrationId, crowdinId, type, provider])); }); } updateSyncSettings(files, integrationId, crowdinId, type, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE sync_settings SET files = $1 WHERE integration_id = $2 AND crowdin_id = $3 AND type = $4 AND provider = $5', [files, integrationId, crowdinId, type, provider])); }); } getSyncSettings(integrationId, crowdinId, type, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, files, integration_id as "integrationId", crowdin_id as "crowdinId", type, provider FROM sync_settings WHERE integration_id = $1 AND crowdin_id = $2 AND type = $3 AND provider = $4', [integrationId, crowdinId, type, provider]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } saveFilesSnapshot(files, integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO files_snapshot(integration_id, crowdin_id, files, provider) VALUES ($1, $2, $3, $4)', [files, integrationId, crowdinId, provider])); }); } updateFilesSnapshot(files, integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE files_snapshot SET files = $1 WHERE integration_id = $2 AND crowdin_id = $3 AND provider = $4', [files, integrationId, crowdinId, provider])); }); } getFilesSnapshot(integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, files, integration_id as "integrationId", crowdin_id as "crowdinId" FROM files_snapshot WHERE integration_id = $1 AND crowdin_id = $2 AND provider = $3', [integrationId, crowdinId, provider]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } getAllWebhooks(integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, file_id as "fileId", integration_id as "integrationId", crowdin_id as "crowdinId" FROM webhooks WHERE integration_id = $1 AND crowdin_id = $2 AND provider = $3', [integrationId, crowdinId, provider]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } getWebhooks(fileId, integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, file_id as "fileId", integration_id as "integrationId", crowdin_id as "crowdinId" FROM webhooks WHERE file_id = $1 AND integration_id = $2 AND crowdin_id = $3 AND provider = $4', [fileId, integrationId, crowdinId, provider]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } saveWebhooks(fileId, integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO webhooks(file_id, integration_id, crowdin_id, provider) VALUES ($1, $2, $3, $4)', [fileId, integrationId, crowdinId, provider])); }); } deleteWebhooks(fileIds, integrationId, crowdinId, provider) { return __awaiter(this, void 0, void 0, function* () { if (!fileIds.length) { return; } let index = 0; const placeholders = fileIds.map(() => `$${++index}`).join(','); yield this.dbPromise; yield this.executeQuery((client) => client.query(`DELETE FROM webhooks WHERE file_id IN (${placeholders}) AND integration_id = $${++index} AND crowdin_id = $${++index} AND provider = $${++index}`, [...fileIds, integrationId, crowdinId, provider])); }); } getAllUserErrors(crowdinId, integrationId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { let whereIntegrationCondition = 'integration_id is NULL'; const params = [crowdinId]; if (integrationId) { whereIntegrationCondition = 'integration_id = $2'; params.push(integrationId); } const res = yield client.query(`SELECT id, action, message, data, created_at as "createdAt", integration_id as "integrationId", crowdin_id as "crowdinId" FROM user_errors WHERE crowdin_id = $1 AND ${whereIntegrationCondition}`, params); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } saveUserError(action, message, data, createdAt, crowdinId, integrationId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO user_errors(action, message, data, created_at, integration_id, crowdin_id) VALUES ($1, $2, $3, $4, $5, $6)', [action, message, data, createdAt, integrationId, crowdinId])); }); } deleteUserErrors(createdAt, crowdinId, integrationId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => { let whereIntegrationCondition = 'integration_id is NULL'; const params = [createdAt, crowdinId]; if (integrationId) { whereIntegrationCondition = 'integration_id = $3'; params.push(integrationId); } return client.query(`DELETE FROM user_errors WHERE created_at < $1 AND crowdin_id = $2 AND ${whereIntegrationCondition}`, params); }); }); } deleteAllUsersErrorsOlderThan(createdAt) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => { return client.query('DELETE FROM user_errors WHERE created_at < $1', [createdAt]); }); }); } saveIntegrationConfig(integrationId, crowdinId, config) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO integration_settings(integration_id, crowdin_id, config) VALUES ($1, $2, $3)', [ integrationId, crowdinId, config, ])); }); } getAllIntegrationConfigs(crowdinId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT config FROM integration_settings WHERE crowdin_id = $1', [crowdinId]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } getIntegrationConfig(integrationId) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT config FROM integration_settings WHERE integration_id = $1', [integrationId]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } updateIntegrationConfig(integrationId, config) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE integration_settings SET config = $1 WHERE integration_id = $2', [ config, integrationId, ])); }); } createJob({ integrationId, crowdinId, type, payload, title }) { return __awaiter(this, void 0, void 0, function* () { const id = (0, uuid_1.v4)(); yield this.dbPromise; yield this.executeQuery((client) => client.query(` INSERT INTO job(id, integration_id, crowdin_id, type, payload, title, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7) `, [id, integrationId, crowdinId, type, payload, title, Date.now().toString()])); return id; }); } updateJob({ id, progress, status, info, data, attempt }) { return __awaiter(this, void 0, void 0, function* () { let parametersPointer = 1; const updateFields = [`updated_at = $${parametersPointer}`]; const updateParams = [Date.now().toString()]; if (progress) { parametersPointer++; updateFields.push('progress = $' + parametersPointer); updateParams.push(Math.round(progress)); if (progress >= 100) { parametersPointer++; updateFields.push('finished_at = $' + parametersPointer); updateParams.push(Date.now().toString()); } } if (status) { parametersPointer++; updateFields.push('status = $' + parametersPointer); updateParams.push(status); if ((!progress || progress <= 100) && [types_2.JobStatus.FAILED, types_2.JobStatus.CANCELED].includes(status)) { parametersPointer++; updateFields.push('finished_at = $' + parametersPointer); updateParams.push(Date.now().toString()); } } if (data) { parametersPointer++; updateFields.push('data = $' + parametersPointer); updateParams.push(data); } if (info) { parametersPointer++; updateFields.push('info = $' + parametersPointer); updateParams.push(info); } if (attempt) { parametersPointer++; updateFields.push('attempt = $' + parametersPointer); updateParams.push(attempt); } parametersPointer++; updateParams.push(id); yield this.dbPromise; yield this.executeQuery((client) => client.query(` UPDATE job SET ${updateFields.join(', ')} WHERE id = $${parametersPointer} `, updateParams)); }); } getJob({ id }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status, title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt" FROM job WHERE id = $1 `, [id]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } getActiveJobs({ integrationId, crowdinId }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status, title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt" FROM job WHERE integration_id = $1 AND crowdin_id = $2 AND finished_at is NULL `, [integrationId, crowdinId]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } deleteFinishedJobs() { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query(' DELETE FROM job WHERE finished_at is not NULL', [])); }); } getAllInProgressJobs() { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT id, integration_id as "integrationId", crowdin_id as "crowdinId", type, payload, progress, status, title, info, data, attempt, created_at as "createdAt", updated_at as "updatedAt", finished_at as "finishedAt" FROM job WHERE status IN ($1, $2) AND finished_at is NULL `, [types_2.JobStatus.IN_PROGRESS, types_2.JobStatus.CREATED]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } saveTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query(` INSERT INTO translation_file_cache(integration_id, crowdin_id, file_id, language_id, etag) VALUES ($1, $2, $3, $4, $5) `, [integrationId, crowdinId, fileId, languageId, etag])); }); } getFileTranslationCache({ integrationId, crowdinId, fileId, }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT integration_id as "integrationId", crowdin_id as "crowdinId", file_id as "fileId", language_id as "languageId", etag FROM translation_file_cache WHERE integration_id = $1 AND crowdin_id = $2 AND file_id = $3 `, [integrationId, crowdinId, fileId]); return (res === null || res === void 0 ? void 0 : res.rows) || []; })); }); } getFileTranslationCacheByLanguage({ integrationId, crowdinId, fileId, languageId, }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT integration_id as "integrationId", crowdin_id as "crowdinId", file_id as "fileId", language_id as "languageId", etag FROM translation_file_cache WHERE integration_id = $1 AND crowdin_id = $2 AND file_id = $3 AND language_id = $4 `, [integrationId, crowdinId, fileId, languageId]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } updateTranslationCache({ integrationId, crowdinId, fileId, languageId, etag, }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query(` UPDATE translation_file_cache SET etag = $1 WHERE integration_id = $2 AND crowdin_id = $3 AND file_id = $4 AND language_id = $5 `, [etag, integrationId, crowdinId, fileId, languageId])); }); } migrateFromSqlite(directoryPath) { return __awaiter(this, void 0, void 0, function* () { const [name, extension] = types_1.storageFiles.DUMP.split('%s'); const files = fs_1.default .readdirSync(directoryPath) .filter((file) => file.startsWith(name) && file.endsWith(extension)) .sort((a, b) => a.localeCompare(b, undefined, { numeric: true })); for (const file of files) { const filePath = (0, path_1.join)(directoryPath, file); const sql = fs_1.default.readFileSync(filePath, 'utf8'); try { yield this.executeQuery((client) => client.query(sql)); fs_1.default.unlinkSync(filePath); } catch (e) { console.error('Error while executing', file); console.error(e); fs_1.default.renameSync(filePath, filePath.replace('dump_table_', 'error_dump_table_')); } } // Reset sequences for tables with serial primary keys yield this.resetSequences(); }); } saveUnsyncedFiles({ integrationId, crowdinId, files }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query(` INSERT INTO unsynced_files(integration_id, crowdin_id, files) VALUES ($1, $2, $3,) `, [integrationId, crowdinId, files])); }); } updateUnsyncedFiles({ integrationId, crowdinId, files }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query(` UPDATE unsynced_files SET files = $1 WHERE integration_id = $2 AND crowdin_id = $3 `, [files, integrationId, crowdinId])); }); } getUnsyncedFiles({ integrationId, crowdinId }) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(` SELECT files FROM unsynced_files WHERE integration_id = $1 AND crowdin_id = $2 `, [integrationId, crowdinId]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } registerCustomTable(tableName, schema) { return __awaiter(this, void 0, void 0, function* () { const columns = Object.entries(schema) .map(([col, def]) => `"${col}" ${def}`) .join(', '); const query = `CREATE TABLE IF NOT EXISTS "${tableName}" (${columns});`; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query(query); return; })); }); } insertRecord(tableName, data) { return __awaiter(this, void 0, void 0, function* () { const columns = Object.keys(data) .map((col) => `"${col}"`) .join(', '); const placeholders = Object.keys(data) .map((_, i) => `$${i + 1}`) .join(', '); const values = Object.values(data); const query = `INSERT INTO "${tableName}" (${columns}) VALUES (${placeholders});`; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query(query, values); return; })); }); } selectRecords(tableName, options = {}, params = []) { var _a; return __awaiter(this, void 0, void 0, function* () { const columns = ((_a = options.columns) === null || _a === void 0 ? void 0 : _a.length) ? options.columns.join(', ') : '*'; const distinctKeyword = options.distinct ? 'DISTINCT ' : ''; const whereClause = options.whereClause ? ` ${options.whereClause}` : ''; const orderByClause = options.orderBy ? ` ORDER BY ${options.orderBy}` : ''; const limitClause = options.limit ? ` LIMIT ${options.limit}` : ''; const query = `SELECT ${distinctKeyword}${columns} FROM ${tableName}${whereClause}${orderByClause}${limitClause};`; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query(query, params); return res.rows; })); }); } updateRecord(tableName, data, whereClause, params = []) { return __awaiter(this, void 0, void 0, function* () { const keys = Object.keys(data); const setClause = keys.map((key, index) => `"${key}" = $${index + 1}`).join(', '); const values = Object.values(data); const query = `UPDATE "${tableName}" SET ${setClause} ${whereClause};`; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query(query, [...values, ...params]); return; })); }); } deleteRecord(tableName, whereClause, params = []) { return __awaiter(this, void 0, void 0, function* () { const query = `DELETE FROM ${tableName} ${whereClause};`; yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { yield client.query(query, params); return; })); }); } saveSyncedData(files, integrationId, crowdinId, type) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('INSERT INTO synced_data(files, integration_id, crowdin_id, type, updated_at) VALUES ($1, $2, $3, $4, $5)', [files, integrationId, crowdinId, type, Date.now().toString()])); }); } updateSyncedData(files, integrationId, crowdinId, type) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; yield this.executeQuery((client) => client.query('UPDATE synced_data SET files = $1, updated_at = $2 WHERE integration_id = $3 AND crowdin_id = $4 AND type = $5', [files, Date.now().toString(), integrationId, crowdinId, type])); }); } getSyncedData(integrationId, crowdinId, type) { return __awaiter(this, void 0, void 0, function* () { yield this.dbPromise; return this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const res = yield client.query('SELECT id, files, integration_id as "integrationId", crowdin_id as "crowdinId", type, updated_at as "updatedAt" FROM synced_data WHERE integration_id = $1 AND crowdin_id = $2 AND type = $3', [integrationId, crowdinId, type]); return res === null || res === void 0 ? void 0 : res.rows[0]; })); }); } resetSequences() { return __awaiter(this, void 0, void 0, function* () { yield this.executeQuery((client) => __awaiter(this, void 0, void 0, function* () { const tables = Object.keys(this.tables); for (const table of tables) { try { const primaryKeyResult = yield client.query(` SELECT a.attname FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '${table}'::regclass AND i.indisprimary; `); if (primaryKeyResult.rows.length === 0) { continue; } const primaryKey = primaryKeyResult.rows[0].attname; const maxIdResult = yield client.query(`SELECT MAX(${primaryKey}) FROM ${table}`); const maxId = maxIdResult.rows[0].max; // Skip if maxId is not a valid integer if (maxId === null || isNaN(Number(maxId))) { continue; } yield client.query(`SELECT setval('${table}_${primaryKey}_seq', ${Number(maxId)}, true)`); } catch (error) { console.error(`Error resetting sequence for table ${table}:`, error); } } })); }); } } exports.PostgreStorage = PostgreStorage;