From 1fbb148f163efdcb17bcd0a50903bdd16d9f5a15 Mon Sep 17 00:00:00 2001 From: Midhun G S Date: Sun, 22 Jun 2025 12:59:13 +0530 Subject: [PATCH] Deprecated static local data sources (#12687) * deprecated static local data sources * changes * transactions wrapped under dbTransaction wrap * removed entity manager from app import export service * Update export app cypress test case. * update app export test case * type error fix for loader * fix for listing of tooljetdb for app import export * postgrest configs * added readme * cleanup * fix for app import export version order * Update app export spec * fix --------- Co-authored-by: ajith-k-v --- .../platform/ceTestcases/apps/appExport.cy.js | 363 +-- ...8714733-DeprecateLocalStaticDataSources.ts | 152 ++ ...45409452884-AddMissingStaticDataSources.ts | 83 + ...31920-AddDataSourceConstraintsForStatic.ts | 45 + server/src/modules/app/loader.ts | 12 +- .../services/app-import-export.service.ts | 463 ++-- server/src/modules/apps/util.service.ts | 55 +- .../modules/data-sources/constants/index.ts | 4 + server/src/modules/data-sources/repository.ts | 24 +- server/src/modules/data-sources/service.ts | 8 +- .../src/modules/data-sources/types/index.ts | 5 +- .../src/modules/data-sources/util.service.ts | 18 - .../import-export-resources/service.ts | 68 +- .../src/modules/setup-organization/module.ts | 3 +- .../setup-organization/util.service.ts | 18 +- .../external-modules/postgrest/README.md | 30 + .../external-modules/postgrest/postgrest.conf | 4 + .../versions/services/create.service.ts | 186 +- .../ignored/app_import_export.service.ts | 2027 ----------------- .../src/services/ignored/app_users.service.ts | 31 - .../src/services/ignored/comment.service.ts | 155 -- .../services/ignored/comment_users.service.ts | 83 - .../services/ignored/external_apis.service.ts | 343 --- .../import_export_resources.service.ts | 139 -- .../ignored/library_app_creation.service.ts | 140 -- .../org_environment_variables.service.ts | 126 - .../src/services/ignored/temporal.service.ts | 254 --- server/src/services/ignored/thread.service.ts | 76 - .../services/ignored/users-common.service.ts | 19 - server/src/services/ignored/worker.service.ts | 8 - .../ignored/workflow_executions.service.ts | 882 ------- .../ignored/workflow_schedules.service.ts | 74 - .../ignored/workflow_webhooks.service.ts | 121 - 33 files changed, 893 insertions(+), 5126 deletions(-) create mode 100644 server/data-migrations/1745318714733-DeprecateLocalStaticDataSources.ts create mode 100644 server/data-migrations/1745409452884-AddMissingStaticDataSources.ts create mode 100644 server/data-migrations/1745409631920-AddDataSourceConstraintsForStatic.ts create mode 100644 server/src/modules/tooljet-db/external-modules/postgrest/README.md create mode 100644 server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf delete mode 100644 server/src/services/ignored/app_import_export.service.ts delete mode 100644 server/src/services/ignored/app_users.service.ts delete mode 100644 server/src/services/ignored/comment.service.ts delete mode 100644 server/src/services/ignored/comment_users.service.ts delete mode 100644 server/src/services/ignored/external_apis.service.ts delete mode 100644 server/src/services/ignored/import_export_resources.service.ts delete mode 100644 server/src/services/ignored/library_app_creation.service.ts delete mode 100644 server/src/services/ignored/org_environment_variables.service.ts delete mode 100644 server/src/services/ignored/temporal.service.ts delete mode 100644 server/src/services/ignored/thread.service.ts delete mode 100644 server/src/services/ignored/users-common.service.ts delete mode 100644 server/src/services/ignored/worker.service.ts delete mode 100644 server/src/services/ignored/workflow_executions.service.ts delete mode 100644 server/src/services/ignored/workflow_schedules.service.ts delete mode 100644 server/src/services/ignored/workflow_webhooks.service.ts diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js index b1f3733c73..26a80bfb52 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/appExport.cy.js @@ -5,213 +5,214 @@ import { commonText } from "Texts/common"; import { exportAppModalText } from "Texts/exportImport"; import { - clickOnExportButtonAndVerify, - exportAllVersionsAndVerify, - verifyElementsOfExportModal, + clickOnExportButtonAndVerify, + exportAllVersionsAndVerify, + verifyElementsOfExportModal, } from "Support/utils/exportImport"; import { selectAppCardOption, closeModal } from "Support/utils/common"; describe("App Export", () => { - const TEST_DATA = { - appFiles: { - multiVersion: "cypress/fixtures/templates/three-versions.json", - singleVersion: "cypress/fixtures/templates/one_version.json", - }, - }; + const TEST_DATA = { + appFiles: { + multiVersion: "cypress/fixtures/templates/three-versions.json", + singleVersion: "cypress/fixtures/templates/one_version.json", + }, + }; - let data; + let data; + data = { + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), + appName: `${fake.companyName}-IE-App`, + appReName: `${fake.companyName}-${fake.companyName}-IE-App`, + dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), + }; + + beforeEach(() => { data = { - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), - appName: `${fake.companyName}-IE-App`, - appReName: `${fake.companyName}-${fake.companyName}-IE-App`, - dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), + workspaceName: fake.firstName, + workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), + appName: `${fake.companyName}-IE-App`, + appReName: `${fake.companyName}-${fake.companyName}-IE-App`, + dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), }; + cy.exec("mkdir -p ./cypress/downloads/"); + cy.exec("cd ./cypress/downloads/ && rm -rf *"); + cy.exec("mkdir -p ./cypress/downloads/"); + cy.wait(3000); - beforeEach(() => { - data = { - workspaceName: fake.firstName, - workspaceSlug: fake.firstName.toLowerCase().replace(/\s+/g, "-"), - appName: `${fake.companyName}-IE-App`, - appReName: `${fake.companyName}-${fake.companyName}-IE-App`, - dsName: fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""), - }; - cy.exec("mkdir -p ./cypress/downloads/"); - cy.wait(3000); + cy.apiLogin(); + cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); + cy.apiLogout(); + }); - cy.apiLogin(); - cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); - cy.apiLogout(); - }); + it("Verify the elements of export dialog box", () => { + cy.skipWalkthrough(); - it("Verify the elements of export dialog box", () => { - cy.skipWalkthrough() + cy.apiLogin(); + cy.visit(`${data.workspaceSlug}`); + cy.get(importSelectors.importOptionInput) + .eq(0) + .selectFile(TEST_DATA.appFiles.multiVersion, { force: true }); + cy.wait(2000); + cy.clearAndType(commonSelectors.appNameInput, data.appName); + cy.get(importSelectors.importAppButton).click(); + cy.wait(3000); + cy.backToApps(); - cy.apiLogin(); - cy.visit(`${data.workspaceSlug}`); - cy.get(importSelectors.importOptionInput) - .eq(0) - .selectFile(TEST_DATA.appFiles.multiVersion, { - force: true, - }); - cy.wait(1500); - cy.clearAndType(commonSelectors.appNameInput, data.appName); - cy.get(importSelectors.importAppButton).click(); - cy.wait(3000); - cy.backToApps(); + // Select the app card option to export the app + selectAppCardOption( + data.appName, + commonSelectors.appCardOptions(commonText.exportAppOption) + ); - // Select the app card option to export the app - selectAppCardOption( - data.appName, - commonSelectors.appCardOptions(commonText.exportAppOption) + // Verify the elements of the export modal + verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]); + + // Close the modal + closeModal(exportAppModalText.modalCloseButton); + + // Ensure the modal title is no longer visible + cy.get( + commonSelectors.modalTitle(exportAppModalText.selectVersionTitle) + ).should("not.exist"); + + // Re-open the export modal and click the export button + cy.wait(2000); + selectAppCardOption( + data.appName, + commonSelectors.appCardOptions(commonText.exportAppOption) + ); + clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName); + + cy.exec("ls ./cypress/downloads/").then((result) => { + const downloadedAppExportFileName = result.stdout.split("\n")[0]; + const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; + + // Ensure the file name contains the expected app export name + expect(downloadedAppExportFileName).to.contain( + data.appName.toLowerCase() + ); + + // Read and validate the exported JSON file + cy.readFile(filePath).then((appData) => { + // Validate the app name + const appNameFromFile = appData.app[0].definition.appV2.name; + expect(appNameFromFile).to.equal(data.appName); + + // Validate the schema for the student table in tooljetdb + const tooljetDatabase = appData.tooljet_database.find( + (db) => db.table_name === "student" + ); + expect(tooljetDatabase).to.exist; + expect(tooljetDatabase.schema).to.exist; + + // Validate components and queries + const components = appData.app[0].definition.appV2.components; + + const text2Component = components.find( + (component) => component.name === "text2" + ); + expect(text2Component).to.exist; + expect(text2Component.properties.text.value).to.equal( + "{{constants.pageHeader}}" ); - // Verify the elements of the export modal - verifyElementsOfExportModal("v3", ["v2", "v1"], [true, false, false]); - - // Close the modal - closeModal(exportAppModalText.modalCloseButton); - - // Ensure the modal title is no longer visible - cy.get( - commonSelectors.modalTitle(exportAppModalText.selectVersionTitle) - ).should("not.exist"); - - // Re-open the export modal and click the export button - selectAppCardOption( - data.appName, - commonSelectors.appCardOptions(commonText.exportAppOption) + const textinput1 = components.find( + (component) => component.name === "textinput1" ); - clickOnExportButtonAndVerify(exportAppModalText.exportAll, data.appName); + expect(textinput1).to.exist; + expect(textinput1.properties.value.value).to.include("queries"); - cy.exec("ls ./cypress/downloads/").then((result) => { - const downloadedAppExportFileName = result.stdout.split("\n")[0]; - const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; - - // Ensure the file name contains the expected app export name - expect(downloadedAppExportFileName).to.contain( - data.appName.toLowerCase() - ); - - // Read and validate the exported JSON file - cy.readFile(filePath).then((appData) => { - // Validate the app name - const appNameFromFile = appData.app[0].definition.appV2.name; - expect(appNameFromFile).to.equal(data.appName); - - // Validate the schema for the student table in tooljetdb - const tooljetDatabase = appData.tooljet_database.find( - (db) => db.table_name === "student" - ); - expect(tooljetDatabase).to.exist; - expect(tooljetDatabase.schema).to.exist; - - // Validate components and queries - const components = appData.app[0].definition.appV2.components; - - const text2Component = components.find( - (component) => component.name === "text2" - ); - expect(text2Component).to.exist; - expect(text2Component.properties.text.value).to.equal( - "{{constants.pageHeader}}" - ); - - const textinput1 = components.find( - (component) => component.name === "textinput1" - ); - expect(textinput1).to.exist; - expect(textinput1.properties.value.value).to.include("queries"); - - const textinput2 = components.find( - (component) => component.name === "textinput2" - ); - expect(textinput2).to.exist; - expect(textinput2.properties.value.value).to.include("queries"); - - const textinput3 = components.find( - (component) => component.name === "textinput3" - ); - expect(textinput3).to.exist; - expect(textinput3.properties.value.value).to.include("queries"); - - // Validate the data queries - const dataQueries = appData.app[0].definition.appV2.dataQueries; - - const postgresqlQuery = dataQueries.find( - (query) => query.name === "postgresql1" - ); - expect(postgresqlQuery).to.exist; - expect(postgresqlQuery.options.query).to.include( - "Select * from {{secrets.db_name}}" - ); - - const restapiQuery = dataQueries.find( - (query) => query.name === "restapi1" - ); - expect(restapiQuery).to.exist; - expect(restapiQuery.options.url).to.equal( - "https://jsonplaceholder.typicode.com/users/1" - ); - - const tooljetdbQuery = dataQueries.find( - (query) => query.name === "tooljetdb1" - ); - expect(tooljetdbQuery).to.exist; - expect(tooljetdbQuery.options.operation).to.equal("list_rows"); - - // Ensure appVersions exists - const appVersions = appData.app[0].definition.appV2.appVersions; - expect(appVersions).to.exist; - - // Map and verify app version names - const versionNames = appVersions.map((version) => version.name); - expect(versionNames).to.include.members(["v1", "v2", "v3"]); - }); - }); - - cy.exec("cd ./cypress/downloads/ && rm -rf *"); - - selectAppCardOption( - data.appName, - commonSelectors.appCardOptions(commonText.exportAppOption) + const textinput2 = components.find( + (component) => component.name === "textinput2" ); - cy.get(`[data-cy="v1-radio-button"]`).check(); - cy.get( - commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion) - ).click(); + expect(textinput2).to.exist; + expect(textinput2.properties.value.value).to.include("queries"); - cy.exec("ls ./cypress/downloads/").then((result) => { - const downloadedAppExportFileName = result.stdout.split("\n")[0]; - const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; + const textinput3 = components.find( + (component) => component.name === "textinput3" + ); + expect(textinput3).to.exist; + expect(textinput3.properties.value.value).to.include("queries"); - // Ensure the file name contains the expected app export name - expect(downloadedAppExportFileName).to.contain( - data.appName.toLowerCase() - ); + // Validate the data queries + const dataQueries = appData.app[0].definition.appV2.dataQueries; - // Read and validate the exported JSON file - cy.readFile(filePath).then((appData) => { - // Validate the app name - const appNameFromFile = appData.app[0].definition.appV2.name; - expect(appNameFromFile).to.equal(data.appName); - }); - }); + const postgresqlQuery = dataQueries.find( + (query) => query.name === "postgresql1" + ); + expect(postgresqlQuery).to.exist; + expect(postgresqlQuery.options.query).to.include( + "Select * from {{secrets.db_name}}" + ); + + const restapiQuery = dataQueries.find( + (query) => query.name === "restapi1" + ); + expect(restapiQuery).to.exist; + expect(restapiQuery.options.url).to.equal( + "https://jsonplaceholder.typicode.com/users/1" + ); + + const tooljetdbQuery = dataQueries.find( + (query) => query.name === "tooljetdb1" + ); + expect(tooljetdbQuery).to.exist; + expect(tooljetdbQuery.options.operation).to.equal("list_rows"); + + // Ensure appVersions exists + const appVersions = appData.app[0].definition.appV2.appVersions; + expect(appVersions).to.exist; + + // Map and verify app version names + const versionNames = appVersions.map((version) => version.name); + expect(versionNames).to.include.members(["v1", "v2", "v3"]); + }); }); - it.skip("Verify 'Export app' functionality of an application inside app editor", () => { - data.appName2 = `${fake.companyName}-App`; - cy.apiCreateApp(data.appName2); - cy.openApp(data.appName2); + cy.exec("cd ./cypress/downloads/ && rm -rf *"); - cy.dragAndDropWidget("Text Input", 50, 50); + selectAppCardOption( + data.appName, + commonSelectors.appCardOptions(commonText.exportAppOption) + ); + cy.get(`[data-cy="v1-radio-button"]`).check(); + cy.get( + commonSelectors.buttonSelector(exportAppModalText.exportSelectedVersion) + ).click(); - cy.get('[data-cy="left-sidebar-settings-button"]').click(); - cy.get('[data-cy="button-user-status-change"]').click(); + cy.exec("ls ./cypress/downloads/").then((result) => { + const downloadedAppExportFileName = result.stdout.split("\n")[0]; + const filePath = `./cypress/downloads/${downloadedAppExportFileName}`; - verifyElementsOfExportModal("v1"); + // Ensure the file name contains the expected app export name + expect(downloadedAppExportFileName).to.contain( + data.appName.toLowerCase() + ); - exportAllVersionsAndVerify(data.appName1, "v1"); + // Read and validate the exported JSON file + cy.readFile(filePath).then((appData) => { + // Validate the app name + const appNameFromFile = appData.app[0].definition.appV2.name; + expect(appNameFromFile).to.equal(data.appName); + }); }); + }); + + it.skip("Verify 'Export app' functionality of an application inside app editor", () => { + data.appName2 = `${fake.companyName}-App`; + cy.apiCreateApp(data.appName2); + cy.openApp(data.appName2); + + cy.dragAndDropWidget("Text Input", 50, 50); + + cy.get('[data-cy="left-sidebar-settings-button"]').click(); + cy.get('[data-cy="button-user-status-change"]').click(); + + verifyElementsOfExportModal("v1"); + + exportAllVersionsAndVerify(data.appName1, "v1"); + }); }); diff --git a/server/data-migrations/1745318714733-DeprecateLocalStaticDataSources.ts b/server/data-migrations/1745318714733-DeprecateLocalStaticDataSources.ts new file mode 100644 index 0000000000..0e99e852b9 --- /dev/null +++ b/server/data-migrations/1745318714733-DeprecateLocalStaticDataSources.ts @@ -0,0 +1,152 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DeprecateLocalStaticDataSourcesb1745318714733 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // Get all organization IDs + const organizationsResult = await queryRunner.query(`SELECT id FROM organizations`); + const organizationIds = organizationsResult.map((row) => row.id); + + console.log(`Found ${organizationIds.length} organizations to process`); + + // Process each organization + for (const orgId of organizationIds) { + console.log(`Processing organization: ${orgId}`); + + // Get all app_version_ids under this organization + const appVersionsResult = await queryRunner.query( + ` + SELECT av.id as app_version_id + FROM app_versions av + JOIN apps a ON av.app_id = a.id + WHERE a.organization_id = $1 + `, + [orgId] + ); + + const appVersionIds = appVersionsResult.map((row) => row.app_version_id); + + if (appVersionIds.length === 0) { + console.log(`No app versions found for organization: ${orgId}`); + continue; + } + + console.log(`Found ${appVersionIds.length} app versions for organization: ${orgId}`); + + // Get all distinct kinds for static global data sources associated with these app versions + const kindsResult = await queryRunner.query( + ` + SELECT DISTINCT kind + FROM data_sources + WHERE type = 'static' AND scope = 'local' AND app_version_id = ANY($1::uuid[]) + `, + [appVersionIds] + ); + + const kinds = kindsResult.map((row) => row.kind); + + if (kinds.length === 0) { + console.log(`No global static data sources found for app versions under organization: ${orgId}`); + continue; + } + + console.log(`Found ${kinds.length} different kinds of data sources`); + + // Process each kind of data source + for (const kind of kinds) { + console.log(`Processing kind: ${kind}`); + + // Get first data source ID of this kind to keep + const primaryResult = await queryRunner.query( + ` + SELECT id + FROM data_sources + WHERE type = 'static' AND scope = 'local' AND app_version_id = ANY($1::uuid[]) AND kind = $2 + LIMIT 1 + `, + [appVersionIds, kind] + ); + + if (primaryResult.length === 0) { + console.log(`No data sources found for kind: ${kind}`); + continue; + } + + const primaryDataSourceId = primaryResult[0].id; + + // Ensure the primary data source has scope set to 'global', app_version_id set to null, + // and organization_id set to the current organization + await queryRunner.query( + ` + UPDATE data_sources + SET scope = 'global', app_version_id = NULL, organization_id = $1 + WHERE id = $2 + `, + [orgId, primaryDataSourceId] + ); + + console.log( + `Updated primary data source ${primaryDataSourceId} scope to 'global', app_version_id to NULL, and organization_id to ${orgId}` + ); + + // Get all other data source IDs of the same kind to be replaced + const duplicatesResult = await queryRunner.query( + ` + SELECT id + FROM data_sources + WHERE type = 'static' AND scope = 'local' AND + app_version_id = ANY($1::uuid[]) AND + kind = $2 AND + id != $3 + `, + [appVersionIds, kind, primaryDataSourceId] + ); + + const duplicateDataSourceIds = duplicatesResult.map((row) => row.id); + + if (duplicateDataSourceIds.length === 0) { + console.log(`No duplicates found for kind: ${kind}`); + continue; + } + + console.log(`Found ${duplicateDataSourceIds.length} duplicate data sources for kind: ${kind}`); + console.log(`Primary ID: ${primaryDataSourceId}, Duplicates: ${duplicateDataSourceIds.join(', ')}`); + + // Update data_queries to use the primary data source + const updateResult = await queryRunner.query( + ` + UPDATE data_queries + SET data_source_id = $1 + WHERE data_source_id = ANY($2::uuid[]) + `, + [primaryDataSourceId, duplicateDataSourceIds] + ); + + console.log( + `Updated ${updateResult.length || 'multiple'} data_queries to use primary data source: ${primaryDataSourceId}` + ); + + // Delete the duplicate data sources + const deleteResult = await queryRunner.query( + ` + DELETE FROM data_sources + WHERE id = ANY($1::uuid[]) + `, + [duplicateDataSourceIds] + ); + + console.log(`Deleted ${deleteResult.length || duplicateDataSourceIds.length} duplicate data sources`); + } + } + + console.log('Data source consolidation complete.'); + } + + public async down(queryRunner: QueryRunner): Promise { + // This is a data consolidation migration and cannot be easily reverted + console.log(` + WARNING: No down migration available for data consolidation. + This migration consolidates duplicate data sources and cannot be automatically reverted. + Please restore from a backup if needed. + `); + } +} diff --git a/server/data-migrations/1745409452884-AddMissingStaticDataSources.ts b/server/data-migrations/1745409452884-AddMissingStaticDataSources.ts new file mode 100644 index 0000000000..1aec833717 --- /dev/null +++ b/server/data-migrations/1745409452884-AddMissingStaticDataSources.ts @@ -0,0 +1,83 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMissingStaticDataSources1745409452884 implements MigrationInterface { + private requiredDataSourceKinds = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']; + + public async up(queryRunner: QueryRunner): Promise { + // Get all organization IDs + const organizationsResult = await queryRunner.query(`SELECT id FROM organizations`); + const organizationIds = organizationsResult.map((row) => row.id); + + console.log(`Found ${organizationIds.length} organizations to validate`); + + // Process each organization + for (const orgId of organizationIds) { + console.log(`Processing organization: ${orgId}`); + + // Get existing static data source kinds for this organization + const existingDataSourcesResult = await queryRunner.query( + ` + SELECT DISTINCT kind + FROM data_sources + WHERE type = 'static' AND scope = 'global' AND organization_id = $1 + `, + [orgId] + ); + + const existingKinds = existingDataSourcesResult.map((row) => row.kind); + console.log(`Found existing data source kinds: ${existingKinds.join(', ') || 'none'}`); + + // Find missing kinds + const missingKinds = this.requiredDataSourceKinds.filter((kind) => !existingKinds.includes(kind)); + + if (missingKinds.length === 0) { + console.log(`Organization ${orgId} has all required data source kinds`); + continue; + } + + console.log(`Missing data source kinds for organization ${orgId}: ${missingKinds.join(', ')}`); + + // Add missing data sources + for (const kind of missingKinds) { + const name = this.getDefaultNameForKind(kind); + + await queryRunner.query( + ` + INSERT INTO data_sources ( + name, kind, type, scope, organization_id, created_at, updated_at + ) VALUES ( + $1, $2, 'static', 'global', $3, NOW(), NOW() + ) + `, + [name, kind, orgId] + ); + + console.log(`Added new data source: ${kind} for organization ${orgId}`); + } + } + + console.log('Data source validation and addition complete.'); + } + + public async down(queryRunner: QueryRunner): Promise { + // This is a data addition migration that adds missing required data sources + console.log(` + NOTE: Down migration is not implemented for data source validation. + This migration adds missing required data sources to organizations. + If needed, you could delete data sources by kind and organization manually. + `); + } + + // Helper function to get default names for data source kinds + private getDefaultNameForKind(kind: string): string { + const nameMap: Record = { + restapi: 'restapidefault', + runjs: 'runjsdefault', + runpy: 'runpydefault', + tooljetdb: 'tooljetdbdefault', + workflows: 'workflowsdefault', + }; + + return nameMap[kind] || `${kind}default`; + } +} diff --git a/server/data-migrations/1745409631920-AddDataSourceConstraintsForStatic.ts b/server/data-migrations/1745409631920-AddDataSourceConstraintsForStatic.ts new file mode 100644 index 0000000000..36668df6de --- /dev/null +++ b/server/data-migrations/1745409631920-AddDataSourceConstraintsForStatic.ts @@ -0,0 +1,45 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDataSourceConstraintsForStatic1745409631920 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + console.log('Starting migration to add constraints to data_sources table'); + + // Add check constraint to ensure static type data sources have global scope + await queryRunner.query(` + ALTER TABLE data_sources + ADD CONSTRAINT chk_static_type_global_scope + CHECK (type != 'static' OR scope = 'global'); + `); + console.log('Added constraint: static type data sources must have global scope'); + + // Add unique constraint for combination of kind, type, and organization_id + await queryRunner.query(` + CREATE UNIQUE INDEX idx_unique_static_kind_org + ON public.data_sources (kind, type, organization_id) + WHERE type = 'static'; + `); + console.log('Added unique constraint on kind, type, and organization_id'); + + console.log('Migration completed successfully'); + } + + public async down(queryRunner: QueryRunner): Promise { + console.log('Starting rollback of data_sources constraints'); + + // Drop the unique constraint + await queryRunner.query(` + ALTER TABLE public.data_sources + DROP CONSTRAINT IF EXISTS idx_unique_static_kind_org; + `); + console.log('Dropped unique constraint on kind, type, and organization_id'); + + // Drop the check constraint + await queryRunner.query(` + ALTER TABLE public.data_sources + DROP CONSTRAINT IF EXISTS chk_static_type_global_scope; + `); + console.log('Dropped constraint: static type data sources must have global scope'); + + console.log('Rollback completed successfully'); + } +} diff --git a/server/src/modules/app/loader.ts b/server/src/modules/app/loader.ts index ab6cb2ce72..1e1083fdd6 100644 --- a/server/src/modules/app/loader.ts +++ b/server/src/modules/app/loader.ts @@ -15,7 +15,7 @@ import { GuardValidatorModule } from './validators/feature-guard.validator'; import { SentryModule } from '@modules/observability/sentry/module'; export class AppModuleLoader { - static async loadModules(configs: { IS_GET_CONTEXT: boolean }): Promise<(DynamicModule | Type)[]> { + static async loadModules(configs: { IS_GET_CONTEXT: boolean }): Promise<(DynamicModule | typeof GuardValidatorModule)[]> { // Static imports that are always loaded const staticModules = [ EventEmitterModule.forRoot({ @@ -33,7 +33,7 @@ export class AppModuleLoader { port: parseInt(process.env.REDIS_PORT) || 6379, }, }), - ConfigModule.forRoot({ + await ConfigModule.forRoot({ isGlobal: true, envFilePath: [`../.env.${process.env.NODE_ENV}`, '../.env'], load: [() => getEnvVars()], @@ -111,17 +111,17 @@ export class AppModuleLoader { * █ █ * ███████████████████████████████████████████████████████████████████████████████ */ - const dynamicModules: Promise[] = []; + const dynamicModules: DynamicModule[] = []; try { const { LogToFileModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/log-to-file/module`); const { AuditLogsModule } = await import(`${await getImportPath(configs.IS_GET_CONTEXT)}/audit-logs/module`); - dynamicModules.push(LogToFileModule.register(configs)); - dynamicModules.push(AuditLogsModule.register(configs)); + dynamicModules.push(await LogToFileModule.register(configs)); + dynamicModules.push(await AuditLogsModule.register(configs)); } catch (error) { console.error('Error loading dynamic modules:', error); } - return [...staticModules, ...dynamicModules] as (Type | DynamicModule)[]; + return [...staticModules, ...dynamicModules]; } } diff --git a/server/src/modules/apps/services/app-import-export.service.ts b/server/src/modules/apps/services/app-import-export.service.ts index 5d6da2ea7d..ccee419f3a 100644 --- a/server/src/modules/apps/services/app-import-export.service.ts +++ b/server/src/modules/apps/services/app-import-export.service.ts @@ -49,7 +49,6 @@ interface AppResourceMappings { componentsMapping: Record; } -type DefaultDataSourceKind = 'restapi' | 'runjs' | 'runpy' | 'tooljetdb' | 'workflows'; type DefaultDataSourceName = | 'restapidefault' | 'runjsdefault' @@ -76,7 +75,6 @@ const DefaultDataSourceNames: DefaultDataSourceName[] = [ 'tooljetdbdefault', 'workflowsdefault', ]; -const DefaultDataSourceKinds: DefaultDataSourceKind[] = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']; const NewRevampedComponents: NewRevampedComponent[] = [ 'Text', 'TextInput', @@ -97,7 +95,6 @@ export class AppImportExportService { protected dataSourcesRepository: DataSourcesRepository, protected appEnvironmentUtilService: AppEnvironmentUtilService, protected usersUtilService: UsersUtilService, - protected readonly entityManager: EntityManager, protected componentsService: ComponentsService ) {} @@ -261,54 +258,65 @@ export class AppImportExportService { externalResourceMappings = {}, isGitApp = false, tooljetVersion = '', - cloning = false + cloning = false, + manager?: EntityManager ): Promise { - if (typeof appParamsObj !== 'object') { - throw new BadRequestException('Invalid params for app import'); - } + return await dbTransactionWrap(async (manager: EntityManager) => { + if (typeof appParamsObj !== 'object') { + throw new BadRequestException('Invalid params for app import'); + } - let appParams = appParamsObj; + let appParams = appParamsObj; - if (appParams?.appV2) { - appParams = { ...appParams.appV2 }; - } + if (appParams?.appV2) { + appParams = { ...appParams.appV2 }; + } - if (!appParams?.name) { - throw new BadRequestException('Invalid params for app import'); - } + if (!appParams?.name) { + throw new BadRequestException('Invalid params for app import'); + } - const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages - ? appParams - : convertSinglePageSchemaToMultiPageSchema(appParams); - schemaUnifiedAppParams.name = appName; + const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages + ? appParams + : convertSinglePageSchemaToMultiPageSchema(appParams); + schemaUnifiedAppParams.name = appName; - const importedAppTooljetVersion = !cloning && extractMajorVersion(tooljetVersion); - const isNormalizedAppDefinitionSchema = cloning - ? true - : isTooljetVersionWithNormalizedAppDefinitionSchem(importedAppTooljetVersion); + const importedAppTooljetVersion = !cloning && extractMajorVersion(tooljetVersion); + const isNormalizedAppDefinitionSchema = cloning + ? true + : isTooljetVersionWithNormalizedAppDefinitionSchem(importedAppTooljetVersion); - const currentTooljetVersion = !cloning ? tooljetVersion : null; + const currentTooljetVersion = !cloning ? tooljetVersion : null; - const importedApp = await this.createImportedAppForUser(this.entityManager, schemaUnifiedAppParams, user, isGitApp); + const importedApp = await this.createImportedAppForUser(manager, schemaUnifiedAppParams, user, isGitApp); - const resourceMapping = await this.setupImportedAppAssociations( - this.entityManager, - importedApp, - schemaUnifiedAppParams, - user, - externalResourceMappings, - isNormalizedAppDefinitionSchema, - currentTooljetVersion - ); - await this.updateEntityReferencesForImportedApp(this.entityManager, resourceMapping); + const resourceMapping = await this.setupImportedAppAssociations( + manager, + importedApp, + schemaUnifiedAppParams, + user, + externalResourceMappings, + isNormalizedAppDefinitionSchema, + currentTooljetVersion + ); + await this.updateEntityReferencesForImportedApp(manager, resourceMapping); - // NOTE: App slug updation callback doesn't work while wrapped in transaction - // hence updating slug explicitly - await importedApp.reload(); - importedApp.slug = importedApp.id; - await this.entityManager.save(importedApp); + // Update latest version as editing version + const { + importingAppVersions, + } = this.extractImportDataFromAppParams(appParams) - return importedApp; + await this.setEditingVersionAsLatestVersion(manager, resourceMapping.appVersionMapping, importingAppVersions) + + // NOTE: App slug updation callback doesn't work while wrapped in transaction + // hence updating slug explicitly + //await importedApp.reload(); -> this will not work as we are using transaction + const newApp = await manager.findOne(App, { where: { id: importedApp.id } }); + newApp.slug = importedApp.id; + await manager.save(newApp); + + return newApp; + }, manager); } async updateEntityReferencesForImportedApp(manager: EntityManager, resourceMapping: AppResourceMappings) { @@ -487,196 +495,194 @@ export class AppImportExportService { * If an error occurs during the function execution, the transaction will rolled back. */ - await manager.transaction(async (transactionalEntityManager) => { - appResourceMappings = await this.setupAppVersionAssociations( - transactionalEntityManager, - importingAppVersions, - user, - appResourceMappings, - externalResourceMappings, - importingAppEnvironments, - importingDataSources, - importingDataSourceOptions, - importingDataQueries, - importingDefaultAppEnvironmentId, - importingPages, - importingComponents, - importingEvents, - tooljetVersion - ); + appResourceMappings = await this.setupAppVersionAssociations( + manager, + importingAppVersions, + user, + appResourceMappings, + externalResourceMappings, + importingAppEnvironments, + importingDataSources, + importingDataSourceOptions, + importingDataQueries, + importingDefaultAppEnvironmentId, + importingPages, + importingComponents, + importingEvents, + tooljetVersion + ); - if (!isNormalizedAppDefinitionSchema) { - for (const importingAppVersion of importingAppVersions) { - const updatedDefinition: DeepPartial = this.replaceDataQueryIdWithinDefinitions( - importingAppVersion.definition, - appResourceMappings.dataQueryMapping - ); + if (!isNormalizedAppDefinitionSchema) { + for (const importingAppVersion of importingAppVersions) { + const updatedDefinition: DeepPartial = this.replaceDataQueryIdWithinDefinitions( + importingAppVersion.definition, + appResourceMappings.dataQueryMapping + ); - let updateHomepageId = null; + let updateHomepageId = null; - if (updatedDefinition?.pages) { - for (const pageId of Object.keys(updatedDefinition?.pages)) { - const page = updatedDefinition.pages[pageId]; + if (updatedDefinition?.pages) { + for (const pageId of Object.keys(updatedDefinition?.pages)) { + const page = updatedDefinition.pages[pageId]; - const pageEvents = page.events || []; - const componentEvents = []; + const pageEvents = page.events || []; + const componentEvents = []; - const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId); + const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId); - const isHompage = (updatedDefinition['homePageId'] as any) === pageId; + const isHompage = (updatedDefinition['homePageId'] as any) === pageId; - const pageComponents = page.components; + const pageComponents = page.components; - const mappedComponents = transformComponentData( - pageComponents, - componentEvents, - appResourceMappings.componentsMapping, - isNormalizedAppDefinitionSchema, - tooljetVersion - ); + const mappedComponents = transformComponentData( + pageComponents, + componentEvents, + appResourceMappings.componentsMapping, + isNormalizedAppDefinitionSchema, + tooljetVersion + ); - const componentLayouts = []; + const componentLayouts = []; - const newPage = transactionalEntityManager.create(Page, { - name: page.name, - handle: page.handle, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - index: pagePostionIntheList, - disabled: page.disabled || false, - hidden: page.hidden || false, - autoComputeLayout: page.autoComputeLayout || false, - isPageGroup: page.isPageGroup, - pageGroupIndex: page.pageGroupIndex || null, - icon: page.icon || null, - }); - const pageCreated = await transactionalEntityManager.save(newPage); + const newPage = manager.create(Page, { + name: page.name, + handle: page.handle, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + index: pagePostionIntheList, + disabled: page.disabled || false, + hidden: page.hidden || false, + autoComputeLayout: page.autoComputeLayout || false, + isPageGroup: page.isPageGroup, + pageGroupIndex: page.pageGroupIndex || null, + icon: page.icon || null, + }); + const pageCreated = await manager.save(newPage); - appResourceMappings.pagesMapping[pageId] = pageCreated.id; + appResourceMappings.pagesMapping[pageId] = pageCreated.id; - mappedComponents.forEach((component) => { - component.page = pageCreated; - }); + mappedComponents.forEach((component) => { + component.page = pageCreated; + }); - const savedComponents = await transactionalEntityManager.save(Component, mappedComponents); + const savedComponents = await manager.save(Component, mappedComponents); - for (const componentId in pageComponents) { - const componentLayout = pageComponents[componentId]['layouts']; + for (const componentId in pageComponents) { + const componentLayout = pageComponents[componentId]['layouts']; - if (componentLayout && appResourceMappings.componentsMapping[componentId]) { - for (const type in componentLayout) { - const layout = componentLayout[type]; - const newLayout = new Layout(); - newLayout.type = type; - newLayout.top = layout.top; - newLayout.left = - layout.dimensionUnit !== LayoutDimensionUnits.COUNT - ? this.componentsService.resolveGridPositionForComponent(layout.left, type) - : layout.left; - newLayout.dimensionUnit = LayoutDimensionUnits.COUNT; - newLayout.width = layout.width; - newLayout.height = layout.height; - newLayout.componentId = appResourceMappings.componentsMapping[componentId]; + if (componentLayout && appResourceMappings.componentsMapping[componentId]) { + for (const type in componentLayout) { + const layout = componentLayout[type]; + const newLayout = new Layout(); + newLayout.type = type; + newLayout.top = layout.top; + newLayout.left = + layout.dimensionUnit !== LayoutDimensionUnits.COUNT + ? this.componentsService.resolveGridPositionForComponent(layout.left, type) + : layout.left; + newLayout.dimensionUnit = LayoutDimensionUnits.COUNT; + newLayout.width = layout.width; + newLayout.height = layout.height; + newLayout.componentId = appResourceMappings.componentsMapping[componentId]; - componentLayouts.push(newLayout); - } + componentLayouts.push(newLayout); } } + } - await transactionalEntityManager.save(Layout, componentLayouts); + await manager.save(Layout, componentLayouts); - //Event handlers + //Event handlers - if (pageEvents.length > 0) { - pageEvents.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - sourceId: pageCreated.id, - target: Target.page, - event: event, - index: pageEvents.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; + if (pageEvents.length > 0) { + await Promise.all(pageEvents.map(async (event, index) => { + const newEvent = { + name: event.eventId, + sourceId: pageCreated.id, + target: Target.page, + event: event, + index: pageEvents.index || index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], + }; - await transactionalEntityManager.save(EventHandler, newEvent); + await manager.save(EventHandler, newEvent); + })); + } + + await Promise.all(componentEvents.map(async (eventObj) => { + if (eventObj.event?.length === 0) return; + + await Promise.all(eventObj.event.map(async (event, index) => { + const newEvent = manager.create(EventHandler, { + name: event.eventId, + sourceId: appResourceMappings.componentsMapping[eventObj.componentId], + target: Target.component, + event: event, + index: eventObj.index || index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], }); - } - componentEvents.forEach((eventObj) => { - if (eventObj.event?.length === 0) return; + await manager.save(EventHandler, newEvent); + })); + })); - eventObj.event.forEach(async (event, index) => { - const newEvent = transactionalEntityManager.create(EventHandler, { - name: event.eventId, - sourceId: appResourceMappings.componentsMapping[eventObj.componentId], - target: Target.component, - event: event, - index: eventObj.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); + await Promise.all(savedComponents.map(async (component) => { + if (component.type === 'Table') { + const tableActions = component.properties?.actions?.value || []; + const tableColumns = component.properties?.columns?.value || []; - await transactionalEntityManager.save(EventHandler, newEvent); - }); - }); + const tableActionAndColumnEvents = []; - savedComponents.forEach(async (component) => { - if (component.type === 'Table') { - const tableActions = component.properties?.actions?.value || []; - const tableColumns = component.properties?.columns?.value || []; + tableActions.forEach((action) => { + const actionEvents = action.events || []; - const tableActionAndColumnEvents = []; - - tableActions.forEach((action) => { - const actionEvents = action.events || []; - - actionEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableAction, - event: { ...event, ref: action.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); + actionEvents.forEach((event, index) => { + tableActionAndColumnEvents.push({ + name: event.eventId, + sourceId: component.id, + target: Target.tableAction, + event: { ...event, ref: action.name }, + index: event.index ?? index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], }); }); + }); - tableColumns.forEach((column) => { - if (column?.columnType !== 'toggle') return; - const columnEvents = column.events || []; + tableColumns.forEach((column) => { + if (column?.columnType !== 'toggle') return; + const columnEvents = column.events || []; - columnEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableColumn, - event: { ...event, ref: column.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); + columnEvents.forEach((event, index) => { + tableActionAndColumnEvents.push({ + name: event.eventId, + sourceId: component.id, + target: Target.tableColumn, + event: { ...event, ref: column.name }, + index: event.index ?? index, + appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], }); }); + }); - await transactionalEntityManager.save(EventHandler, tableActionAndColumnEvents); - } - }); - - if (isHompage) { - updateHomepageId = pageCreated.id; + await manager.save(EventHandler, tableActionAndColumnEvents); } + })); + + if (isHompage) { + updateHomepageId = pageCreated.id; } } - - await transactionalEntityManager.update( - AppVersion, - { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, - { - definition: updatedDefinition, - homePageId: updateHomepageId, - } - ); } + + await manager.update( + AppVersion, + { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, + { + definition: updatedDefinition, + homePageId: updateHomepageId, + } + ); } - }); + } const appVersionIds = Object.values(appResourceMappings.appVersionMapping); @@ -690,8 +696,6 @@ export class AppImportExportService { ); } - await this.setEditingVersionAsLatestVersion(manager, appResourceMappings.appVersionMapping, importingAppVersions); - return appResourceMappings; } @@ -748,7 +752,6 @@ export class AppImportExportService { const dataSourceForAppVersion = await this.findOrCreateDataSourceForAppVersion( manager, importingDataSource, - appResourceMappings.appVersionMapping[importingAppVersion.id], user ); @@ -914,7 +917,7 @@ export class AppImportExportService { appResourceMappings.componentsMapping[component.id] = savedComponent.id; const componentLayout = component.layouts; - componentLayout.forEach(async (layout) => { + await Promise.all(componentLayout.map(async (layout) => { const newLayout = new Layout(); newLayout.type = layout.type; newLayout.top = layout.top; @@ -928,12 +931,12 @@ export class AppImportExportService { newLayout.component = savedComponent; await manager.save(newLayout); - }); + })); const componentEvents = importingEvents.filter((event) => event.sourceId === component.id); if (componentEvents.length > 0) { - componentEvents.forEach(async (componentEvent) => { + await Promise.all(componentEvents.map(async (componentEvent) => { const newEvent = await manager.create(EventHandler, { name: componentEvent.name, sourceId: savedComponent.id, @@ -944,7 +947,7 @@ export class AppImportExportService { }); await manager.save(EventHandler, newEvent); - }); + })); } } } @@ -952,7 +955,7 @@ export class AppImportExportService { const pageEvents = importingEvents.filter((event) => event.sourceId === page.id); if (pageEvents.length > 0) { - pageEvents.forEach(async (pageEvent) => { + await Promise.all(pageEvents.map(async (pageEvent) => { const newEvent = await manager.create(EventHandler, { name: pageEvent.name, sourceId: pageCreated.id, @@ -963,7 +966,7 @@ export class AppImportExportService { }); await manager.save(EventHandler, newEvent); - }); + })); } } @@ -990,7 +993,7 @@ export class AppImportExportService { ); if (importingQueryEvents.length > 0) { - importingQueryEvents.forEach(async (dataQueryEvent) => { + await Promise.all(importingQueryEvents.map(async (dataQueryEvent) => { const newEvent = await manager.create(EventHandler, { name: dataQueryEvent.name, sourceId: mappedNewDataQuery.id, @@ -1001,7 +1004,7 @@ export class AppImportExportService { }); await manager.save(EventHandler, newEvent); - }); + })); } else { this.replaceDataQueryOptionsWithNewDataQueryIds( mappedNewDataQuery?.options, @@ -1012,7 +1015,7 @@ export class AppImportExportService { delete mappedNewDataQuery?.options?.events; if (queryEvents.length > 0) { - queryEvents.forEach(async (event, index) => { + await Promise.all(queryEvents.map(async (event, index) => { const newEvent = await manager.create(EventHandler, { name: event.eventId, sourceId: mappedNewDataQuery.id, @@ -1023,7 +1026,7 @@ export class AppImportExportService { }); await manager.save(EventHandler, newEvent); - }); + })); } } @@ -1195,12 +1198,7 @@ export class AppImportExportService { user: User, appResourceMappings: AppResourceMappings ) { - const defaultDataSourceIds = await this.createDefaultDataSourceForVersion( - user?.organizationId, - appResourceMappings.appVersionMapping[appVersion.id], - DefaultDataSourceKinds, - manager - ); + const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(user.organizationId, manager); appResourceMappings.defaultDataSourceIdMapping[appVersion.id] = defaultDataSourceIds; return appResourceMappings; @@ -1209,7 +1207,6 @@ export class AppImportExportService { async findOrCreateDataSourceForAppVersion( manager: EntityManager, dataSource: DataSource, - appVersionId: string, user: User ): Promise { const isDefaultDatasource = DefaultDataSourceNames.includes(dataSource.name as DefaultDataSourceName); @@ -1218,10 +1215,10 @@ export class AppImportExportService { if (isDefaultDatasource) { const createdDefaultDatasource = await manager.findOne(DataSource, { where: { - appVersionId, + organizationId: user.organizationId, kind: dataSource.kind, type: DataSourceTypes.STATIC, - scope: 'local', + scope: DataSourceScopes.GLOBAL, }, }); @@ -1234,8 +1231,8 @@ export class AppImportExportService { id: dataSource.id, kind: dataSource.kind, type: DataSourceTypes.DEFAULT, - scope: 'global', - organizationId: user?.organizationId, + scope: DataSourceScopes.GLOBAL, + organizationId: user.organizationId, }, }); }; @@ -1245,8 +1242,8 @@ export class AppImportExportService { name: dataSource.name, kind: dataSource.kind, type: In([DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE]), - scope: 'global', - organizationId: user?.organizationId, + scope: DataSourceScopes.GLOBAL, + organizationId: user.organizationId, }, }); }; @@ -1268,7 +1265,7 @@ export class AppImportExportService { name: dataSource.name, kind: dataSource.kind, type: DataSourceTypes.DEFAULT, - scope: 'global', // No appVersionId for global data sources + scope: DataSourceScopes.GLOBAL, // No appVersionId for global data sources pluginId: plugin.id, }); await manager.save(newDataSource); @@ -1283,7 +1280,7 @@ export class AppImportExportService { name: dataSource.name, kind: dataSource.kind, type: DataSourceTypes.DEFAULT, - scope: 'global', // No appVersionId for global data sources + scope: DataSourceScopes.GLOBAL, // No appVersionId for global data sources pluginId: null, }); await manager.save(newDataSource); @@ -1499,19 +1496,12 @@ export class AppImportExportService { return appResourceMappings; } - async createDefaultDataSourceForVersion( - organizationId: string, - versionId: string, - kinds: DefaultDataSourceKind[], - manager: EntityManager - ): Promise { - const response = {}; - for (const defaultSource of kinds) { - const dataSource = await this.dataSourcesRepository.createDefaultDataSource(defaultSource, versionId, manager); - response[defaultSource] = dataSource.id; - await this.dataSourcesUtilService.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager); - } - return response; + async createDefaultDataSourceForVersion(organizationId: string, manager: EntityManager): Promise { + const dataSources = await this.dataSourcesRepository.getStaticDataSources(organizationId, manager); + return dataSources?.reduce>((acc, source) => { + acc[source.kind] = source.id; + return acc; + }, {}); } async setEditingVersionAsLatestVersion(manager: EntityManager, appVersionMapping: any, appVersions: Array) { @@ -1657,12 +1647,7 @@ export class AppImportExportService { await manager.save(version); // Create default data sources - const defaultDataSourceIds = await this.createDefaultDataSourceForVersion( - user?.organizationId, - version.id, - DefaultDataSourceKinds, - manager - ); + const defaultDataSourceIds = await this.createDefaultDataSourceForVersion(user.organizationId, manager); let envIdArray: string[] = []; const organization: Organization = await manager.findOne(Organization, { @@ -1759,7 +1744,7 @@ export class AppImportExportService { newQuery.options = newOptions; await manager.save(newQuery); - queryEvents.forEach(async (event, index) => { + await Promise.all(queryEvents.map(async (event, index) => { const newEvent = { name: event.eventId, sourceId: newQuery.id, @@ -1770,7 +1755,7 @@ export class AppImportExportService { }; await manager.save(EventHandler, newEvent); - }); + })); } await manager.update( diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 5e94c25beb..57cac46979 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -34,9 +34,7 @@ import { mergeWith } from 'lodash'; import { isArray } from 'lodash'; import { UserAppsPermissions, UserWorkflowPermissions } from '@modules/ability/types'; import { AbilityService } from '@modules/ability/interfaces/IService'; -import { DataSourcesRepository } from '@modules/data-sources/repository'; import { IAppsUtilService } from './interfaces/IUtilService'; -import { DataSourcesUtilService } from '@modules/data-sources/util.service'; import { AppVersionUpdateDto } from '@dto/app-version-update.dto'; import { APP_TYPES } from './constants'; import { Component } from 'src/entities/component.entity'; @@ -52,10 +50,8 @@ export class AppsUtilService implements IAppsUtilService { protected readonly versionRepository: VersionRepository, protected readonly licenseTermsService: LicenseTermsService, protected readonly organizationRepository: OrganizationRepository, - protected readonly abilityService: AbilityService, - protected readonly dataSourceRepository: DataSourcesRepository, - protected readonly dataSourceUtilService: DataSourcesUtilService - ) { } + protected readonly abilityService: AbilityService + ) {} async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise { return await dbTransactionWrap(async (manager: EntityManager) => { const app = await catchDbException(() => { @@ -77,14 +73,6 @@ export class AppsUtilService implements IAppsUtilService { const firstPriorityEnv = await this.appEnvironmentUtilService.get(user.organizationId, null, true, manager); const appVersion = await this.versionRepository.createOne('v1', app.id, firstPriorityEnv.id, null, manager); - for (const defaultSource of ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']) { - const dataSource = await this.dataSourceRepository.createDefaultDataSource( - defaultSource, - appVersion.id, - manager - ); - await this.dataSourceUtilService.createDataSourceInAllEnvironments(user.organizationId, dataSource.id, manager); - } const defaultHomePage = await manager.save( manager.create(Page, { name: 'Home', @@ -157,43 +145,6 @@ export class AppsUtilService implements IAppsUtilService { }, manager); } - // async createVersion( - // user: User, - // app: App, - // versionName: string, - // versionFromId: string, - // manager?: EntityManager - // ): Promise { - // return await dbTransactionWrap(async (manager: EntityManager) => { - // let versionFrom: AppVersion; - // const { organizationId } = user; - - // if (versionFromId) { - // versionFrom = await manager.findOneOrFail(AppVersion, { - // where: { - // id: versionFromId, - // app: { - // id: app.id, - // organizationId, - // }, - // }, - // relations: ['app', 'dataSources', 'dataSources.dataQueries', 'dataSources.dataSourceOptions'], - // }); - // } - - // const noOfVersions = await manager.count(AppVersion, { where: { appId: app?.id } }); - - // if (noOfVersions && !versionFrom) { - // throw new BadRequestException('Version from should not be empty'); - // } - - // if (versionFrom) { - // } - - // return appVersion; - // }, manager); - // } - async findAppWithIdOrSlug(slug: string, organizationId: string): Promise { let app: App; try { @@ -675,7 +626,7 @@ export class AppsUtilService implements IAppsUtilService { const tooljetDbDataQueries = await manager .createQueryBuilder(DataQuery, 'data_queries') .innerJoin(DataSource, 'data_sources', 'data_queries.data_source_id = data_sources.id') - .innerJoin(AppVersion, 'app_versions', 'app_versions.id = data_sources.app_version_id') + .innerJoin(AppVersion, 'app_versions', 'app_versions.id = data_queries.app_version_id') .where('app_versions.app_id = :appId', { appId }) .andWhere('data_sources.kind = :kind', { kind: 'tooljetdb' }) .getMany(); diff --git a/server/src/modules/data-sources/constants/index.ts b/server/src/modules/data-sources/constants/index.ts index ac09f78c3c..9b9ec4bc1e 100644 --- a/server/src/modules/data-sources/constants/index.ts +++ b/server/src/modules/data-sources/constants/index.ts @@ -1,3 +1,5 @@ +import { DefaultDataSourceKind } from '../types'; + export enum FEATURE_KEY { GET = 'GET', GET_FOR_APP = 'GET_FOR_APP', @@ -23,3 +25,5 @@ export enum DataSourceScopes { LOCAL = 'local', GLOBAL = 'global', } + +export const DefaultDataSourceKinds: DefaultDataSourceKind[] = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']; diff --git a/server/src/modules/data-sources/repository.ts b/server/src/modules/data-sources/repository.ts index 0bc8195d47..a14f3dbe90 100644 --- a/server/src/modules/data-sources/repository.ts +++ b/server/src/modules/data-sources/repository.ts @@ -19,7 +19,7 @@ export class DataSourcesRepository extends Repository { organizationId: string, queryVars: GetQueryVariables ): Promise { - const { appVersionId, environmentId } = queryVars; + const { appVersionId, environmentId, types } = queryVars; // Data source options are attached only if selectedEnvironmentId is passed // Returns global data sources + sample data sources // If version Id is passed, then data queries under each are also returned @@ -67,6 +67,9 @@ export class DataSourcesRepository extends Repository { .andWhere('data_source.organization_id = :organizationId', { organizationId }) .andWhere('data_source.scope = :scope', { scope: DataSourceScopes.GLOBAL }); + if (types && types.length > 0) { + query.andWhere('data_source.type IN (:...types)', { types }); + } if (environmentId) { query.andWhere('data_source_options.environmentId = :environmentId', { environmentId }); } @@ -140,11 +143,12 @@ export class DataSourcesRepository extends Repository { }, manager || this.manager); } - async createDefaultDataSource(kind: string, appVersionId: string, manager?: EntityManager): Promise { + async createDefaultDataSource(kind: string, organizationId: string, manager?: EntityManager): Promise { const newDataSource = manager.create(DataSource, { name: `${kind}default`, kind, - appVersionId, + scope: DataSourceScopes.GLOBAL, + organizationId, type: DataSourceTypes.STATIC, createdAt: new Date(), updatedAt: new Date(), @@ -152,6 +156,12 @@ export class DataSourcesRepository extends Repository { return await manager.save(newDataSource); } + async getStaticDataSources(organizationId: string, manager?: EntityManager): Promise { + return await manager.find(DataSource, { + where: { organizationId, type: DataSourceTypes.STATIC }, + }); + } + findByQuery(dataQueryId: string, organizationId: string, dataSourceId?: string, manager?: EntityManager) { return dbTransactionWrap((manager: EntityManager) => { return manager.findOne(DataSource, { @@ -161,14 +171,6 @@ export class DataSourcesRepository extends Repository { }, manager || this.manager); } - getAllStaticDataSources(versionId: string, manager?: EntityManager): Promise { - return dbTransactionWrap((manager: EntityManager) => { - return manager.find(DataSource, { - where: { appVersionId: versionId, type: DataSourceTypes.STATIC }, - }); - }, manager || this.manager); - } - getDatasourceByPluginId(pluginId: string) { return dbTransactionWrap((manager: EntityManager) => { return manager.find(DataSource, { diff --git a/server/src/modules/data-sources/service.ts b/server/src/modules/data-sources/service.ts index f104f228f9..f2454744fd 100644 --- a/server/src/modules/data-sources/service.ts +++ b/server/src/modules/data-sources/service.ts @@ -43,14 +43,13 @@ export class DataSourcesService implements IDataSourcesService { }); const shouldIncludeWorkflows = query.shouldIncludeWorkflows ?? true; - const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {}); - let staticDataSources = await this.dataSourcesRepository.getAllStaticDataSources(query.appVersionId); + let dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, query ?? {}); if (!shouldIncludeWorkflows) { // remove workflowsdefault data source from static data sources - staticDataSources = staticDataSources.filter((dataSource) => dataSource.kind !== 'workflows'); + dataSources = dataSources.filter((dataSource) => dataSource.kind !== 'workflows'); } - const decamelizedDatasources = decamelizeKeys([...staticDataSources, ...dataSources]); + const decamelizedDatasources = decamelizeKeys(dataSources); return { data_sources: decamelizedDatasources }; } @@ -66,6 +65,7 @@ export class DataSourcesService implements IDataSourcesService { const dataSources = await this.dataSourcesRepository.allGlobalDS(userPermissions, user.organizationId, { appVersionId: query.appVersionId, environmentId: selectedEnvironmentId, + types: [DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE], }); for (const dataSource of dataSources) { const parseIfNeeded = (data: any) => { diff --git a/server/src/modules/data-sources/types/index.ts b/server/src/modules/data-sources/types/index.ts index 791a5b8af0..a8ea37368e 100644 --- a/server/src/modules/data-sources/types/index.ts +++ b/server/src/modules/data-sources/types/index.ts @@ -1,4 +1,4 @@ -import { FEATURE_KEY } from '../constants'; +import { DataSourceTypes, FEATURE_KEY } from '../constants'; import { FeatureConfig } from '@modules/app/types'; import { MODULES } from '@modules/app/constants/modules'; import { QueryError, OAuthUnauthorizedClientError } from '@tooljet/plugins/dist/server'; @@ -50,6 +50,7 @@ export { QueryError, OAuthUnauthorizedClientError }; export interface GetQueryVariables { appVersionId?: string; environmentId?: string; + types?: DataSourceTypes[]; shouldIncludeWorkflows?: boolean; } @@ -57,3 +58,5 @@ export interface UpdateOptions { dataSourceId: string; environmentId: string; } + +export type DefaultDataSourceKind = 'restapi' | 'runjs' | 'runpy' | 'tooljetdb' | 'workflows'; diff --git a/server/src/modules/data-sources/util.service.ts b/server/src/modules/data-sources/util.service.ts index 03ad5ddd52..85527e8910 100644 --- a/server/src/modules/data-sources/util.service.ts +++ b/server/src/modules/data-sources/util.service.ts @@ -712,24 +712,6 @@ export class DataSourcesUtilService implements IDataSourcesUtilService { } } - async findDefaultDataSource( - kind: string, - appVersionId: string, - organizationId: string, - manager: EntityManager - ): Promise { - const defaultDataSource = await manager.findOne(DataSource, { - where: { kind, appVersionId, type: DataSourceTypes.STATIC }, - }); - - if (defaultDataSource) { - return defaultDataSource; - } - const dataSource = await this.dataSourceRepository.createDefaultDataSource(kind, appVersionId, manager); - await this.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager); - return dataSource; - } - async getAuthUrl(getDataSourceOauthUrlDto: GetDataSourceOauthUrlDto): Promise<{ url: string }> { const { provider, source_options = {}, plugin_id = null } = getDataSourceOauthUrlDto; const service = await this.pluginsServiceSelector.getService(plugin_id, provider); diff --git a/server/src/modules/import-export-resources/service.ts b/server/src/modules/import-export-resources/service.ts index e47205258e..b03697397b 100644 --- a/server/src/modules/import-export-resources/service.ts +++ b/server/src/modules/import-export-resources/service.ts @@ -9,6 +9,7 @@ import { isEmpty } from 'lodash'; import { InternalTableRepository } from '@modules/tooljet-db/repository'; import { RequestContext } from '@modules/request-context/service'; import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants'; +import { dbTransactionWrap } from '@helpers/database.helper'; @Injectable() export class ImportExportResourcesService { @@ -82,40 +83,43 @@ export class ImportExportResourcesService { } } - if (!isEmpty(importResourcesDto.tooljet_database)) { - const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning); - tableNameMapping = res.tableNameMapping; - imports.tooljet_database = res.tooljet_database; - imports.tableNameMapping = tableNameMapping; - } - - if (!isEmpty(importResourcesDto.app)) { - for (const appImportDto of importResourcesDto.app) { - user.organizationId = importResourcesDto.organization_id; - const createdApp = await this.appImportExportService.import( - user, - appImportDto.definition, - appImportDto.appName, - { - tooljet_database: tableNameMapping, - }, - isGitApp, - importResourcesDto.tooljet_version, - cloning - ); - - imports.app.push({ id: createdApp.id, name: createdApp.name }); - - RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { - userId: user.id, - organizationId: user.organizationId, - resourceId: createdApp.id, - resourceName: createdApp.name, - }); + return await dbTransactionWrap(async (manager) => { + if (!isEmpty(importResourcesDto.tooljet_database)) { + const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning); + tableNameMapping = res.tableNameMapping; + imports.tooljet_database = res.tooljet_database; + imports.tableNameMapping = tableNameMapping; } - } - return imports; + if (!isEmpty(importResourcesDto.app)) { + for (const appImportDto of importResourcesDto.app) { + user.organizationId = importResourcesDto.organization_id; + const createdApp = await this.appImportExportService.import( + user, + appImportDto.definition, + appImportDto.appName, + { + tooljet_database: tableNameMapping, + }, + isGitApp, + importResourcesDto.tooljet_version, + cloning, + manager + ); + + imports.app.push({ id: createdApp.id, name: createdApp.name }); + + RequestContext.setLocals(AUDIT_LOGS_REQUEST_CONTEXT_KEY, { + userId: user.id, + organizationId: user.organizationId, + resourceId: createdApp.id, + resourceName: createdApp.name, + }); + } + } + + return imports; + }); } async legacyImport(user: User, templateDefinition: any, appName: string) { diff --git a/server/src/modules/setup-organization/module.ts b/server/src/modules/setup-organization/module.ts index 9d0405886c..81629c0392 100644 --- a/server/src/modules/setup-organization/module.ts +++ b/server/src/modules/setup-organization/module.ts @@ -10,6 +10,7 @@ import { ThemesModule } from '@modules/organization-themes/module'; import { SessionModule } from '@modules/session/module'; import { InstanceSettingsModule } from '@modules/instance-settings/module'; import { TooljetDbModule } from '@modules/tooljet-db/module'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; export class SetupOrganizationsModule { static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise { @@ -38,7 +39,7 @@ export class SetupOrganizationsModule { OrganizationRepository, OrganizationUsersRepository, FeatureAbilityFactory, - TooljetDbModule, + DataSourcesRepository, ], exports: [SetupOrganizationsUtilService], }; diff --git a/server/src/modules/setup-organization/util.service.ts b/server/src/modules/setup-organization/util.service.ts index 8eac88ec33..0eecaaa6e4 100644 --- a/server/src/modules/setup-organization/util.service.ts +++ b/server/src/modules/setup-organization/util.service.ts @@ -15,6 +15,9 @@ import { OrganizationUsersRepository } from '@modules/organization-users/reposit import { SampleDataSourceService } from '@modules/data-sources/services/sample-ds.service'; import { ISetupOrganizationsUtilService } from './interfaces/IUtilService'; import { TooljetDbTableOperationsService } from '@modules/tooljet-db/services/tooljet-db-table-operations.service'; +import { DataSourcesUtilService } from '@modules/data-sources/util.service'; +import { DataSourcesRepository } from '@modules/data-sources/repository'; +import { DefaultDataSourceKinds } from '@modules/data-sources/constants'; import { OrganizationInputs } from './types/organization-inputs'; @Injectable() @@ -29,7 +32,9 @@ export class SetupOrganizationsUtilService implements ISetupOrganizationsUtilSer protected readonly sampleDBService: SampleDataSourceService, protected readonly licenseOrganizationService: LicenseOrganizationService, protected readonly licenseUserService: LicenseUserService, - protected readonly organizationUserRepository: OrganizationUsersRepository + protected readonly organizationUserRepository: OrganizationUsersRepository, + protected readonly dataSourceUtilService: DataSourcesUtilService, + protected readonly dataSourcesRepository: DataSourcesRepository ) {} async create(organizationInputs: OrganizationInputs, user?: User, manager?: EntityManager): Promise { @@ -48,6 +53,17 @@ export class SetupOrganizationsUtilService implements ISetupOrganizationsUtilSer //create default theme for this organization await this.organizationThemesUtilService.createDefaultTheme(manager, organization.id); await this.tooljetDbTableOperationsService.createTooljetDbTenantSchemaAndRole(organization.id, manager); + + // Create static data sources for the organization + for (const defaultSource of DefaultDataSourceKinds) { + const dataSource = await this.dataSourcesRepository.createDefaultDataSource( + defaultSource, + organization.id, + manager + ); + await this.dataSourceUtilService.createDataSourceInAllEnvironments(organization.id, dataSource.id, manager); + } + return organization; }, manager); } diff --git a/server/src/modules/tooljet-db/external-modules/postgrest/README.md b/server/src/modules/tooljet-db/external-modules/postgrest/README.md new file mode 100644 index 0000000000..3e653f4e09 --- /dev/null +++ b/server/src/modules/tooljet-db/external-modules/postgrest/README.md @@ -0,0 +1,30 @@ + +# ToolJet DB Postgrest + +This module is required to setup ToolJet database + +## Install postgrest +https://docs.postgrest.org/en/v12/explanations/install.html + +## PostgREST configuration file - postgrest.conf +``` +db-uri = "postgres://postgres:postgres@localhost:5432/tooljet_new_db" +db-pre-config = "postgrest.pre_config" +server-port = "3001" +jwt-secret = +``` + +### ToolJet .env settings +``` +PGRST_JWT_SECRET=Same value configured as jwt-secret +TOOLJET_DB=tooljet_new_db (Same value configured as db-uri db name) +PGRST_HOST=localhost:3001 +TOOLJET_DB_USER=postgres +TOOLJET_DB_PASS=postgres +PGRST_DB_PRE_CONFIG=postgrest.pre_config +``` + +### Start Postgrest +``` +postgrest postgrest.conf +``` diff --git a/server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf b/server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf new file mode 100644 index 0000000000..461b2c17b1 --- /dev/null +++ b/server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf @@ -0,0 +1,4 @@ +db-uri = "postgres://postgres:postgres@localhost:5432/tooljet_new_db" +db-pre-config = "postgrest.pre_config" +server-port = "3001" +jwt-secret = "cba75674be5b6305a18d18e3761d82bb031fe36b45719744a308209db3d6d805" diff --git a/server/src/modules/versions/services/create.service.ts b/server/src/modules/versions/services/create.service.ts index 12a24ac7c6..00ae419ca3 100644 --- a/server/src/modules/versions/services/create.service.ts +++ b/server/src/modules/versions/services/create.service.ts @@ -91,87 +91,44 @@ export class VersionsCreateService implements IVersionsCreateService { manager ); - if (!versionFrom) { - //create default data sources - for (const defaultSource of ['restapi', 'runjs', 'tooljetdb', 'workflows']) { - const dataSource = await this.dataSourceRepository.createDefaultDataSource( - defaultSource, - appVersion.id, - manager - ); - await this.dataSourceUtilService.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager); - } - } else { - const globalQueries: DataQuery[] = await this.dataQueryRepository.getQueriesByVersionId( - versionFrom.id, - DataSourceScopes.GLOBAL, - manager - ); - const dataSources = versionFrom?.dataSources.filter((ds) => ds.scope == DataSourceScopes.LOCAL); //Local data sources - const globalDataSources = [...new Map(globalQueries.map((gq) => [gq.dataSource.id, gq.dataSource])).values()]; + const globalQueries: DataQuery[] = await this.dataQueryRepository.getQueriesByVersionId( + versionFrom.id, + DataSourceScopes.GLOBAL, + manager + ); + const dataSources = versionFrom?.dataSources.filter((ds) => ds.scope == DataSourceScopes.LOCAL); //Local data sources + const globalDataSources = [...new Map(globalQueries.map((gq) => [gq.dataSource.id, gq.dataSource])).values()]; - const dataSourceMapping = {}; - const newDataQueries = []; - const allEvents = await manager.find(EventHandler, { - where: { appVersionId: versionFrom?.id, target: Target.dataQuery }, - }); + const dataSourceMapping = {}; + const newDataQueries = []; + const allEvents = await manager.find(EventHandler, { + where: { appVersionId: versionFrom?.id, target: Target.dataQuery }, + }); - if (dataSources?.length > 0 || globalDataSources?.length > 0) { - if (dataSources?.length > 0) { - for (const dataSource of dataSources) { - const dataSourceParams: Partial = { - name: dataSource.name, - kind: dataSource.kind, - type: dataSource.type, - appVersionId: appVersion.id, - }; - const newDataSource = await manager.save(manager.create(DataSource, dataSourceParams)); - dataSourceMapping[dataSource.id] = newDataSource.id; + if (dataSources?.length > 0 || globalDataSources?.length > 0) { + if (dataSources?.length > 0) { + for (const dataSource of dataSources) { + const dataSourceParams: Partial = { + name: dataSource.name, + kind: dataSource.kind, + type: dataSource.type, + appVersionId: appVersion.id, + }; + const newDataSource = await manager.save(manager.create(DataSource, dataSourceParams)); + dataSourceMapping[dataSource.id] = newDataSource.id; - const dataQueries = versionFrom?.dataSources?.find((ds) => ds.id === dataSource.id).dataQueries; + const dataQueries = versionFrom?.dataSources?.find((ds) => ds.id === dataSource.id).dataQueries; - for (const dataQuery of dataQueries) { - const dataQueryParams = { - name: dataQuery.name, - options: dataQuery.options, - dataSourceId: newDataSource.id, - appVersionId: appVersion.id, - }; - const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams)); - - const dataQueryEvents = allEvents.filter((event) => event.sourceId === dataQuery.id); - - dataQueryEvents.forEach(async (event, index) => { - const newEvent = new EventHandler(); - - newEvent.id = uuid.v4(); - newEvent.name = event.name; - newEvent.sourceId = newQuery.id; - newEvent.target = event.target; - newEvent.event = event.event; - newEvent.index = event.index ?? index; - newEvent.appVersionId = appVersion.id; - - await manager.save(newEvent); - }); - - oldDataQueryToNewMapping[dataQuery.id] = newQuery.id; - newDataQueries.push(newQuery); - } - } - } - - if (globalQueries?.length > 0) { - for (const globalQuery of globalQueries) { + for (const dataQuery of dataQueries) { const dataQueryParams = { - name: globalQuery.name, - options: globalQuery.options, - dataSourceId: globalQuery.dataSourceId, + name: dataQuery.name, + options: dataQuery.options, + dataSourceId: newDataSource.id, appVersionId: appVersion.id, }; - const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams)); - const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id); + + const dataQueryEvents = allEvents.filter((event) => event.sourceId === dataQuery.id); dataQueryEvents.forEach(async (event, index) => { const newEvent = new EventHandler(); @@ -186,45 +143,70 @@ export class VersionsCreateService implements IVersionsCreateService { await manager.save(newEvent); }); - oldDataQueryToNewMapping[globalQuery.id] = newQuery.id; + + oldDataQueryToNewMapping[dataQuery.id] = newQuery.id; newDataQueries.push(newQuery); } } + } - for (const newQuery of newDataQueries) { - const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds( - newQuery.options, - oldDataQueryToNewMapping - ); - newQuery.options = newOptions; + if (globalQueries?.length > 0) { + for (const globalQuery of globalQueries) { + const dataQueryParams = { + name: globalQuery.name, + options: globalQuery.options, + dataSourceId: globalQuery.dataSourceId, + appVersionId: appVersion.id, + }; - await manager.save(newQuery); + const newQuery = await manager.save(manager.create(DataQuery, dataQueryParams)); + const dataQueryEvents = allEvents.filter((event) => event.sourceId === globalQuery.id); + + dataQueryEvents.forEach(async (event, index) => { + const newEvent = new EventHandler(); + + newEvent.id = uuid.v4(); + newEvent.name = event.name; + newEvent.sourceId = newQuery.id; + newEvent.target = event.target; + newEvent.event = event.event; + newEvent.index = event.index ?? index; + newEvent.appVersionId = appVersion.id; + + await manager.save(newEvent); + }); + oldDataQueryToNewMapping[globalQuery.id] = newQuery.id; + newDataQueries.push(newQuery); } + } - appVersion.definition = this.replaceDataQueryIdWithinDefinitions( - appVersion.definition, - oldDataQueryToNewMapping - ); - await manager.save(appVersion); + for (const newQuery of newDataQueries) { + const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(newQuery.options, oldDataQueryToNewMapping); + newQuery.options = newOptions; - for (const appEnvironment of appEnvironments) { - for (const dataSource of dataSources) { - const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, { - where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id }, - }); + await manager.save(newQuery); + } - const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options); - const newOptions = await this.dataSourceUtilService.parseOptionsForCreate(convertedOptions, false, manager); - await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager); + appVersion.definition = this.replaceDataQueryIdWithinDefinitions(appVersion.definition, oldDataQueryToNewMapping); + await manager.save(appVersion); - await manager.save( - manager.create(DataSourceOptions, { - options: newOptions, - dataSourceId: dataSourceMapping[dataSource.id], - environmentId: appEnvironment.id, - }) - ); - } + for (const appEnvironment of appEnvironments) { + for (const dataSource of dataSources) { + const dataSourceOption = await manager.findOneOrFail(DataSourceOptions, { + where: { dataSourceId: dataSource.id, environmentId: appEnvironment.id }, + }); + + const convertedOptions = this.convertToArrayOfKeyValuePairs(dataSourceOption.options); + const newOptions = await this.dataSourceUtilService.parseOptionsForCreate(convertedOptions, false, manager); + await this.setNewCredentialValueFromOldValue(newOptions, convertedOptions, manager); + + await manager.save( + manager.create(DataSourceOptions, { + options: newOptions, + dataSourceId: dataSourceMapping[dataSource.id], + environmentId: appEnvironment.id, + }) + ); } } } diff --git a/server/src/services/ignored/app_import_export.service.ts b/server/src/services/ignored/app_import_export.service.ts deleted file mode 100644 index 47aa0724da..0000000000 --- a/server/src/services/ignored/app_import_export.service.ts +++ /dev/null @@ -1,2027 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { isEmpty, set } from 'lodash'; -import { App } from 'src/entities/app.entity'; -import { AppEnvironment } from 'src/entities/app_environments.entity'; -import { AppVersion } from 'src/entities/app_version.entity'; -import { DataQuery } from 'src/entities/data_query.entity'; -import { DataSource } from 'src/entities/data_source.entity'; -import { DataSourceOptions } from 'src/entities/data_source_options.entity'; -import { User } from 'src/entities/user.entity'; -import { EntityManager, In, DeepPartial } from 'typeorm'; -import { DataSourcesService } from './data_sources.service'; -import { - defaultAppEnvironments, - catchDbException, - extractMajorVersion, - isTooljetVersionWithNormalizedAppDefinitionSchem, - isVersionGreaterThanOrEqual, -} from 'src/helpers/utils.helper'; -import { dbTransactionWrap } from 'src/helpers/database.helper'; -import { LayoutDimensionUnits, resolveGridPositionForComponent } from 'src/helpers/components.helper'; -import { AppEnvironmentService } from '../../ee/app-environments/service'; -import { convertAppDefinitionFromSinglePageToMultiPage } from '../../lib/single-page-to-and-from-multipage-definition-conversion'; -import { DataSourceScopes, DataSourceTypes } from 'src/helpers/data_source.constants'; -import { Organization } from 'src/entities/organization.entity'; -import { DataBaseConstraints } from 'src/helpers/db_constraints.constants'; -import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import { Plugin } from 'src/entities/plugin.entity'; -import { Page } from 'src/entities/page.entity'; -import { Component } from 'src/entities/component.entity'; -import { Layout } from 'src/entities/layout.entity'; -import { EventHandler, Target } from 'src/entities/event_handler.entity'; -import { v4 as uuid } from 'uuid'; -import { updateEntityReferences } from 'src/helpers/import_export.helpers'; -interface AppResourceMappings { - defaultDataSourceIdMapping: Record; - dataQueryMapping: Record; - appVersionMapping: Record; - appEnvironmentMapping: Record; - appDefaultEnvironmentMapping: Record; - pagesMapping: Record; - componentsMapping: Record; -} - -type DefaultDataSourceKind = 'restapi' | 'runjs' | 'runpy' | 'tooljetdb' | 'workflows'; -type DefaultDataSourceName = - | 'restapidefault' - | 'runjsdefault' - | 'runpydefault' - | 'tooljetdbdefault' - | 'workflowsdefault'; - -type NewRevampedComponent = 'Text' | 'TextInput' | 'PasswordInput' | 'NumberInput' | 'Table' | 'Button' | 'Checkbox'; - -const DefaultDataSourceNames: DefaultDataSourceName[] = [ - 'restapidefault', - 'runjsdefault', - 'runpydefault', - 'tooljetdbdefault', - 'workflowsdefault', -]; -const DefaultDataSourceKinds: DefaultDataSourceKind[] = ['restapi', 'runjs', 'runpy', 'tooljetdb', 'workflows']; -const NewRevampedComponents: NewRevampedComponent[] = [ - 'Text', - 'TextInput', - 'PasswordInput', - 'NumberInput', - 'Table', - 'Checkbox', - 'Button', -]; - -@Injectable() -export class AppImportExportService { - constructor( - private dataSourcesService: DataSourcesService, - private appEnvironmentService: AppEnvironmentService, - private readonly entityManager: EntityManager - ) {} - - async export(user: User, id: string, searchParams: any = {}): Promise<{ appV2: App }> { - // https://github.com/typeorm/typeorm/issues/3857 - // Making use of query builder - // filter by search params - const versionId = searchParams?.version_id; - return await dbTransactionWrap(async (manager: EntityManager) => { - const queryForAppToExport = manager - .createQueryBuilder(App, 'apps') - .where('apps.id = :id AND apps.organization_id = :organizationId', { - id, - organizationId: user.organizationId, - }); - const appToExport = await queryForAppToExport.getOne(); - - const queryAppVersions = manager - .createQueryBuilder(AppVersion, 'app_versions') - .where('app_versions.appId = :appId', { - appId: appToExport.id, - }); - - if (versionId) { - queryAppVersions.andWhere('app_versions.id = :versionId', { versionId }); - } - const appVersions = await queryAppVersions.orderBy('app_versions.created_at', 'ASC').getMany(); - - const legacyLocalDataSources = - appVersions?.length && - (await manager - .createQueryBuilder(DataSource, 'data_sources') - .where('data_sources.appVersionId IN(:...versionId)', { - versionId: appVersions.map((v) => v.id), - }) - .andWhere('data_sources.scope != :scope', { scope: DataSourceScopes.GLOBAL }) - .orderBy('data_sources.created_at', 'ASC') - .getMany()); - - const appEnvironments = await manager - .createQueryBuilder(AppEnvironment, 'app_environments') - .where('app_environments.organizationId = :organizationId', { - organizationId: user.organizationId, - }) - .orderBy('app_environments.createdAt', 'ASC') - .getMany(); - - let dataQueries: DataQuery[] = []; - let dataSourceOptions: DataSourceOptions[] = []; - - const globalQueries: DataQuery[] = await manager - .createQueryBuilder(DataQuery, 'data_query') - .innerJoinAndSelect('data_query.dataSource', 'dataSource') - .where('data_query.appVersionId IN(:...versionId)', { - versionId: appVersions.map((v) => v.id), - }) - .andWhere('dataSource.scope = :scope', { scope: DataSourceScopes.GLOBAL }) - .getMany(); - - const globalDataSources = [...new Map(globalQueries.map((gq) => [gq.dataSource.id, gq.dataSource])).values()]; - - const dataSources = [...legacyLocalDataSources, ...globalDataSources]; - - if (dataSources?.length) { - dataQueries = await manager - .createQueryBuilder(DataQuery, 'data_queries') - .where('data_queries.dataSourceId IN(:...dataSourceId)', { - dataSourceId: dataSources?.map((v) => v.id), - }) - .andWhere('data_queries.appVersionId IN(:...versionId)', { - versionId: appVersions.map((v) => v.id), - }) - .orderBy('data_queries.created_at', 'ASC') - .getMany(); - - dataSourceOptions = await manager - .createQueryBuilder(DataSourceOptions, 'data_source_options') - .where( - 'data_source_options.environmentId IN(:...environmentId) AND data_source_options.dataSourceId IN(:...dataSourceId)', - { - environmentId: appEnvironments.map((v) => v.id), - dataSourceId: dataSources.map((v) => v.id), - } - ) - .orderBy('data_source_options.createdAt', 'ASC') - .getMany(); - - dataSourceOptions?.forEach((dso) => { - delete dso?.options?.tokenData; - }); - } - - const pages = await manager - .createQueryBuilder(Page, 'pages') - .where('pages.appVersionId IN(:...versionId)', { - versionId: appVersions.map((v) => v.id), - }) - .orderBy('pages.created_at', 'ASC') - .getMany(); - - const components = - pages.length > 0 - ? await manager - .createQueryBuilder(Component, 'components') - .leftJoinAndSelect('components.layouts', 'layouts') - .where('components.pageId IN(:...pageId)', { - pageId: pages.map((v) => v.id), - }) - .orderBy('components.created_at', 'ASC') - .getMany() - : []; - - const events = await manager - .createQueryBuilder(EventHandler, 'event_handlers') - .where('event_handlers.appVersionId IN(:...versionId)', { - versionId: appVersions.map((v) => v.id), - }) - .orderBy('event_handlers.created_at', 'ASC') - .getMany(); - - appToExport['components'] = components; - appToExport['pages'] = pages; - appToExport['events'] = events; - appToExport['dataQueries'] = dataQueries; - appToExport['dataSources'] = dataSources; - appToExport['appVersions'] = appVersions; - appToExport['appEnvironments'] = appEnvironments; - appToExport['dataSourceOptions'] = dataSourceOptions; - appToExport['schemaDetails'] = { - multiPages: true, - multiEnv: true, - globalDataSources: true, - }; - - return { appV2: appToExport }; - }); - } - - async import( - user: User, - appParamsObj: any, - appName: string, - externalResourceMappings = {}, - isGitApp = false, - tooljetVersion = '', - cloning = false - ): Promise { - if (typeof appParamsObj !== 'object') { - throw new BadRequestException('Invalid params for app import'); - } - - let appParams = appParamsObj; - - if (appParams?.appV2) { - appParams = { ...appParams.appV2 }; - } - - if (!appParams?.name) { - throw new BadRequestException('Invalid params for app import'); - } - - const schemaUnifiedAppParams = appParams?.schemaDetails?.multiPages - ? appParams - : convertSinglePageSchemaToMultiPageSchema(appParams); - schemaUnifiedAppParams.name = appName; - - const importedAppTooljetVersion = !cloning && extractMajorVersion(tooljetVersion); - const isNormalizedAppDefinitionSchema = cloning - ? true - : isTooljetVersionWithNormalizedAppDefinitionSchem(importedAppTooljetVersion); - - const currentTooljetVersion = !cloning ? tooljetVersion : null; - - const importedApp = await this.createImportedAppForUser(this.entityManager, schemaUnifiedAppParams, user, isGitApp); - - const resourceMapping = await this.setupImportedAppAssociations( - this.entityManager, - importedApp, - schemaUnifiedAppParams, - user, - externalResourceMappings, - isNormalizedAppDefinitionSchema, - currentTooljetVersion - ); - await this.updateEntityReferencesForImportedApp(this.entityManager, resourceMapping); - - // NOTE: App slug updation callback doesn't work while wrapped in transaction - // hence updating slug explicitly - await importedApp.reload(); - importedApp.slug = importedApp.id; - await this.entityManager.save(importedApp); - - return importedApp; - } - - async updateEntityReferencesForImportedApp(manager: EntityManager, resourceMapping: AppResourceMappings) { - const mappings = { ...resourceMapping.componentsMapping, ...resourceMapping.dataQueryMapping }; - const newComponentIds = Object.values(resourceMapping.componentsMapping); - const newQueriesIds = Object.values(resourceMapping.dataQueryMapping); - - if (newComponentIds.length > 0) { - const components = await manager - .createQueryBuilder(Component, 'components') - .where('components.id IN(:...componentIds)', { componentIds: newComponentIds }) - .select([ - 'components.id', - 'components.properties', - 'components.styles', - 'components.general', - 'components.validation', - 'components.generalStyles', - 'components.displayPreferences', - ]) - .getMany(); - - const toUpdateComponents = components.filter((component) => { - return updateEntityReferences(component, mappings); - }); - - if (!isEmpty(toUpdateComponents)) { - await manager.save(toUpdateComponents); - } - } - - if (newQueriesIds.length > 0) { - const dataQueries = await manager - .createQueryBuilder(DataQuery, 'dataQueries') - .where('dataQueries.id IN(:...dataQueryIds)', { dataQueryIds: newQueriesIds }) - .select(['dataQueries.id', 'dataQueries.options']) - .getMany(); - - const toUpdateDataQueries = dataQueries.filter((dataQuery) => { - return updateEntityReferences(dataQuery, mappings); - }); - - if (!isEmpty(toUpdateDataQueries)) { - await manager.save(toUpdateDataQueries); - } - } - // update Global settings of created versions - const appVersionIds = Object.values(resourceMapping.appVersionMapping); - const newAppVersions = await manager.find(AppVersion, { - where: { - id: In(appVersionIds), - }, - select: ['id', 'globalSettings'], - }); - for (const appVersion of newAppVersions) { - if (appVersion.globalSettings) { - const updatedGlobalSettings = updateEntityReferences(appVersion.globalSettings, mappings); - await manager.update(AppVersion, { id: appVersion.id }, { globalSettings: updatedGlobalSettings }); - } - } - } - - async createImportedAppForUser(manager: EntityManager, appParams: any, user: User, isGitApp = false): Promise { - return await catchDbException(async () => { - const importedApp = manager.create(App, { - name: appParams.name, - organizationId: user.organizationId, - userId: user.id, - slug: null, - icon: appParams.icon, - creationMode: `${isGitApp ? 'GIT' : 'DEFAULT'}`, - isPublic: false, - createdAt: new Date(), - updatedAt: new Date(), - }); - - await manager.save(importedApp); - return importedApp; - }, [{ dbConstraint: DataBaseConstraints.APP_NAME_UNIQUE, message: 'This app name is already taken.' }]); - } - - extractImportDataFromAppParams(appParams: Record): { - importingDataSources: DataSource[]; - importingDataQueries: DataQuery[]; - importingAppVersions: AppVersion[]; - importingAppEnvironments: AppEnvironment[]; - importingDataSourceOptions: DataSourceOptions[]; - importingDefaultAppEnvironmentId: string; - importingPages: Page[]; - importingComponents: Component[]; - importingEvents: EventHandler[]; - } { - const importingDataSources = appParams?.dataSources || []; - const importingDataQueries = appParams?.dataQueries || []; - const importingAppVersions = appParams?.appVersions || []; - const importingAppEnvironments = appParams?.appEnvironments || []; - const importingDataSourceOptions = appParams?.dataSourceOptions || []; - const importingDefaultAppEnvironmentId = importingAppEnvironments.find( - (env: { isDefault: any }) => env.isDefault - )?.id; - - const importingPages = appParams?.pages || []; - const importingComponents = appParams?.components || []; - const importingEvents = appParams?.events || []; - - return { - importingDataSources, - importingDataQueries, - importingAppVersions, - importingAppEnvironments, - importingDataSourceOptions, - importingDefaultAppEnvironmentId, - importingPages, - importingComponents, - importingEvents, - }; - } - - /* - * With new multi-env changes. the imported apps will not have any released versions from now (if the importing schema has any currentVersionId). - * All version's default environment will be development or least priority environment only. - */ - async setupImportedAppAssociations( - manager: EntityManager, - importedApp: App, - appParams: any, - user: User, - externalResourceMappings: Record, - isNormalizedAppDefinitionSchema: boolean, - tooljetVersion: string | null - ) { - // Old version without app version - // Handle exports prior to 0.12.0 - // TODO: have version based conditional based on app versions - // isLessThanExportVersion(appParams.tooljet_version, 'v0.12.0') - if (!appParams?.appVersions) { - await this.performLegacyAppImport(manager, importedApp, appParams, externalResourceMappings, user); - return; - } - - let appResourceMappings: AppResourceMappings = { - defaultDataSourceIdMapping: {}, - dataQueryMapping: {}, - appVersionMapping: {}, - appEnvironmentMapping: {}, - appDefaultEnvironmentMapping: {}, - pagesMapping: {}, - componentsMapping: {}, - }; - const { - importingDataSources, - importingDataQueries, - importingAppVersions, - importingAppEnvironments, - importingDataSourceOptions, - importingDefaultAppEnvironmentId, - importingPages, - importingComponents, - importingEvents, - } = this.extractImportDataFromAppParams(appParams); - - const { appDefaultEnvironmentMapping, appVersionMapping } = await this.createAppVersionsForImportedApp( - manager, - user, - importedApp, - importingAppVersions, - appResourceMappings, - isNormalizedAppDefinitionSchema - ); - appResourceMappings.appDefaultEnvironmentMapping = appDefaultEnvironmentMapping; - appResourceMappings.appVersionMapping = appVersionMapping; - - /** - * as multiple operations are run within a single transaction using the transaction method provides a convenient way to handle transactions. - * The transaction will automatically committed when the function completes without throwing an error. - * If an error occurs during the function execution, the transaction will rolled back. - */ - - await manager.transaction(async (transactionalEntityManager) => { - appResourceMappings = await this.setupAppVersionAssociations( - transactionalEntityManager, - importingAppVersions, - user, - appResourceMappings, - externalResourceMappings, - importingAppEnvironments, - importingDataSources, - importingDataSourceOptions, - importingDataQueries, - importingDefaultAppEnvironmentId, - importingPages, - importingComponents, - importingEvents, - tooljetVersion - ); - - if (!isNormalizedAppDefinitionSchema) { - for (const importingAppVersion of importingAppVersions) { - const updatedDefinition: DeepPartial = this.replaceDataQueryIdWithinDefinitions( - importingAppVersion.definition, - appResourceMappings.dataQueryMapping - ); - - let updateHomepageId = null; - - if (updatedDefinition?.pages) { - for (const pageId of Object.keys(updatedDefinition?.pages)) { - const page = updatedDefinition.pages[pageId]; - - const pageEvents = page.events || []; - const componentEvents = []; - - const pagePostionIntheList = Object.keys(updatedDefinition?.pages).indexOf(pageId); - - const isHompage = (updatedDefinition['homePageId'] as any) === pageId; - - const pageComponents = page.components; - - const mappedComponents = transformComponentData( - pageComponents, - componentEvents, - appResourceMappings.componentsMapping, - isNormalizedAppDefinitionSchema, - tooljetVersion - ); - - const componentLayouts = []; - - const newPage = transactionalEntityManager.create(Page, { - name: page.name, - handle: page.handle, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - index: pagePostionIntheList, - disabled: page.disabled || false, - hidden: page.hidden || false, - autoComputeLayout: page.autoComputeLayout || false, - isPageGroup: page.isPageGroup, - pageGroupIndex: page.pageGroupIndex || null, - icon: page.icon || null, - }); - const pageCreated = await transactionalEntityManager.save(newPage); - - appResourceMappings.pagesMapping[pageId] = pageCreated.id; - - mappedComponents.forEach((component) => { - component.page = pageCreated; - }); - - const savedComponents = await transactionalEntityManager.save(Component, mappedComponents); - - for (const componentId in pageComponents) { - const componentLayout = pageComponents[componentId]['layouts']; - - if (componentLayout && appResourceMappings.componentsMapping[componentId]) { - for (const type in componentLayout) { - const layout = componentLayout[type]; - const newLayout = new Layout(); - newLayout.type = type; - newLayout.top = layout.top; - newLayout.left = - layout.dimensionUnit !== LayoutDimensionUnits.COUNT - ? resolveGridPositionForComponent(layout.left, type) - : layout.left; - newLayout.dimensionUnit = LayoutDimensionUnits.COUNT; - newLayout.width = layout.width; - newLayout.height = layout.height; - newLayout.componentId = appResourceMappings.componentsMapping[componentId]; - - componentLayouts.push(newLayout); - } - } - } - - await transactionalEntityManager.save(Layout, componentLayouts); - - //Event handlers - - if (pageEvents.length > 0) { - pageEvents.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - sourceId: pageCreated.id, - target: Target.page, - event: event, - index: pageEvents.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }; - - await transactionalEntityManager.save(EventHandler, newEvent); - }); - } - - componentEvents.forEach((eventObj) => { - if (eventObj.event?.length === 0) return; - - eventObj.event.forEach(async (event, index) => { - const newEvent = transactionalEntityManager.create(EventHandler, { - name: event.eventId, - sourceId: appResourceMappings.componentsMapping[eventObj.componentId], - target: Target.component, - event: event, - index: eventObj.index || index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await transactionalEntityManager.save(EventHandler, newEvent); - }); - }); - - savedComponents.forEach(async (component) => { - if (component.type === 'Table') { - const tableActions = component.properties?.actions?.value || []; - const tableColumns = component.properties?.columns?.value || []; - - const tableActionAndColumnEvents = []; - - tableActions.forEach((action) => { - const actionEvents = action.events || []; - - actionEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableAction, - event: { ...event, ref: action.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - }); - }); - - tableColumns.forEach((column) => { - if (column?.columnType !== 'toggle') return; - const columnEvents = column.events || []; - - columnEvents.forEach((event, index) => { - tableActionAndColumnEvents.push({ - name: event.eventId, - sourceId: component.id, - target: Target.tableColumn, - event: { ...event, ref: column.name }, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - }); - }); - - await transactionalEntityManager.save(EventHandler, tableActionAndColumnEvents); - } - }); - - if (isHompage) { - updateHomepageId = pageCreated.id; - } - } - } - - await transactionalEntityManager.update( - AppVersion, - { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, - { - definition: updatedDefinition, - homePageId: updateHomepageId, - } - ); - } - } - }); - - const appVersionIds = Object.values(appResourceMappings.appVersionMapping); - - for (const appVersionId of appVersionIds) { - await this.updateEventActionsForNewVersionWithNewMappingIds( - manager, - appVersionId, - appResourceMappings.dataQueryMapping, - appResourceMappings.componentsMapping, - appResourceMappings.pagesMapping - ); - } - - await this.setEditingVersionAsLatestVersion(manager, appResourceMappings.appVersionMapping, importingAppVersions); - - return appResourceMappings; - } - - async setupAppVersionAssociations( - manager: EntityManager, - importingAppVersions: AppVersion[], - user: User, - appResourceMappings: AppResourceMappings, - externalResourceMappings: Record, - importingAppEnvironments: AppEnvironment[], - importingDataSources: DataSource[], - importingDataSourceOptions: DataSourceOptions[], - importingDataQueries: DataQuery[], - importingDefaultAppEnvironmentId: string, - importingPages: Page[], - importingComponents: Component[], - importingEvents: EventHandler[], - tooljetVersion: string | null - ): Promise { - appResourceMappings = { ...appResourceMappings }; - - for (const importingAppVersion of importingAppVersions) { - let isHomePage = false; - let updateHomepageId = null; - - const { appEnvironmentMapping } = await this.associateAppEnvironmentsToAppVersion( - manager, - user, - importingAppEnvironments, - importingAppVersion, - appResourceMappings - ); - appResourceMappings.appEnvironmentMapping = appEnvironmentMapping; - - const { defaultDataSourceIdMapping } = await this.createDefaultDatasourcesForAppVersion( - manager, - importingAppVersion, - user, - appResourceMappings - ); - appResourceMappings.defaultDataSourceIdMapping = defaultDataSourceIdMapping; - - const importingDataSourcesForAppVersion = await this.rejectMarketplacePluginsNotInstalled( - manager, - importingDataSources - ); - - const importingDataQueriesForAppVersion = importingDataQueries.filter( - (dq: { dataSourceId: string; appVersionId: string }) => dq.appVersionId === importingAppVersion.id - ); - - // associate data sources and queries for each of the app versions - for (const importingDataSource of importingDataSourcesForAppVersion) { - const dataSourceForAppVersion = await this.findOrCreateDataSourceForAppVersion( - manager, - importingDataSource, - appResourceMappings.appVersionMapping[importingAppVersion.id], - user - ); - - // TODO: Have version based conditional based on app versions - // currently we are checking on existence of keys and handling - // imports accordingly. Would be pragmatic to do: - // isLessThanExportVersion(appParams.tooljet_version, 'v2.0.0') - // Will need to have JSON schema setup for each versions - if (importingDataSource.options) { - const convertedOptions = this.convertToArrayOfKeyValuePairs(importingDataSource.options); - - await Promise.all( - appResourceMappings.appDefaultEnvironmentMapping[importingAppVersion.id].map(async (envId: any) => { - if (this.isExistingDataSource(dataSourceForAppVersion)) return; - - const newOptions = await this.dataSourcesService.parseOptionsForCreate(convertedOptions, true, manager); - const dsOption = manager.create(DataSourceOptions, { - environmentId: envId, - dataSourceId: dataSourceForAppVersion.id, - options: newOptions, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(dsOption); - }) - ); - } - - const isDefaultDatasource = DefaultDataSourceNames.includes(importingDataSource.name as DefaultDataSourceName); - if (!isDefaultDatasource) { - await this.createDataSourceOptionsForExistingAppEnvs( - manager, - importingAppVersion, - dataSourceForAppVersion, - importingDataSourceOptions, - importingDataSource, - importingAppEnvironments, - appResourceMappings, - importingDefaultAppEnvironmentId - ); - } - - const { dataQueryMapping } = await this.createDataQueriesForAppVersion( - manager, - user.organizationId, - importingDataQueriesForAppVersion, - importingDataSource, - dataSourceForAppVersion, - importingAppVersion, - appResourceMappings, - externalResourceMappings - ); - appResourceMappings.dataQueryMapping = dataQueryMapping; - } - - const pagesOfAppVersion = importingPages.filter((page) => page.appVersionId === importingAppVersion.id); - const oldNewIdMap = {}; - const pageGroupIdArr = []; - - for (const page of pagesOfAppVersion) { - const newPage = manager.create(Page, { - name: page.name, - handle: page.handle, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - index: page.index, - pageGroupIndex: page.pageGroupIndex || null, - disabled: page.disabled || false, - hidden: page.hidden || false, - autoComputeLayout: page.autoComputeLayout || false, - icon: page.icon || null, - isPageGroup: !!page.isPageGroup, - }); - - const pageCreated = await manager.save(newPage); - oldNewIdMap[page.id] = pageCreated.id; - if (page.pageGroupId) { - pageGroupIdArr.push({ - pageId: page.id, - groupId: page.pageGroupId, - }); - } - - appResourceMappings.pagesMapping[page.id] = pageCreated.id; - - isHomePage = importingAppVersion.homePageId === page.id; - - if (isHomePage) { - updateHomepageId = pageCreated.id; - } - - const pageComponents = importingComponents.filter((component) => component.pageId === page.id); - - const newComponentIdsMap = {}; - - for (const component of pageComponents) { - newComponentIdsMap[component.id] = uuid(); - } - - for (const component of pageComponents) { - let skipComponent = false; - const newComponent = new Component(); - - let parentId = component.parent ? component.parent : null; - if (component?.properties?.buttonToSubmit) { - const newButtonToSubmitValue = newComponentIdsMap[component?.properties?.buttonToSubmit?.value]; - if (newButtonToSubmitValue) set(component, 'properties.buttonToSubmit.value', newButtonToSubmitValue); - } - - const isParentTabOrCalendar = hasSlottedChildren(component, pageComponents, parentId, true); - - if (isParentTabOrCalendar) { - const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null; - - const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - const mappedParentId = newComponentIdsMap[_parentId]; - - parentId = `${mappedParentId}-${childTabId}`; - } else if (isChildOfKanbanModal(component, pageComponents, parentId, true)) { - const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - const mappedParentId = newComponentIdsMap[_parentId]; - - parentId = `${mappedParentId}-modal`; - } else { - if (component.parent && !newComponentIdsMap[parentId]) { - skipComponent = true; - } - - parentId = newComponentIdsMap[parentId]; - } - if (!skipComponent) { - const { properties, styles, general, validation, generalStyles } = migrateProperties( - component.type as NewRevampedComponent, - component, - NewRevampedComponents, - tooljetVersion - ); - newComponent.id = newComponentIdsMap[component.id]; - newComponent.name = component.name; - newComponent.type = component.type; - newComponent.properties = properties; - newComponent.styles = styles; - newComponent.generalStyles = generalStyles; - newComponent.general = general; - newComponent.displayPreferences = component.displayPreferences; - newComponent.validation = validation; - newComponent.parent = component.parent ? parentId : null; - - newComponent.page = pageCreated; - - const savedComponent = await manager.save(newComponent); - - appResourceMappings.componentsMapping[component.id] = savedComponent.id; - const componentLayout = component.layouts; - - componentLayout.forEach(async (layout) => { - const newLayout = new Layout(); - newLayout.type = layout.type; - newLayout.top = layout.top; - newLayout.left = - layout.dimensionUnit !== LayoutDimensionUnits.COUNT - ? resolveGridPositionForComponent(layout.left, layout.type) - : layout.left; - newLayout.dimensionUnit = LayoutDimensionUnits.COUNT; - newLayout.width = layout.width; - newLayout.height = layout.height; - newLayout.component = savedComponent; - - await manager.save(newLayout); - }); - - const componentEvents = importingEvents.filter((event) => event.sourceId === component.id); - - if (componentEvents.length > 0) { - componentEvents.forEach(async (componentEvent) => { - const newEvent = await manager.create(EventHandler, { - name: componentEvent.name, - sourceId: savedComponent.id, - target: componentEvent.target, - event: componentEvent.event, - index: componentEvent.index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await manager.save(EventHandler, newEvent); - }); - } - } - } - - const pageEvents = importingEvents.filter((event) => event.sourceId === page.id); - - if (pageEvents.length > 0) { - pageEvents.forEach(async (pageEvent) => { - const newEvent = await manager.create(EventHandler, { - name: pageEvent.name, - sourceId: pageCreated.id, - target: pageEvent.target, - event: pageEvent.event, - index: pageEvent.index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await manager.save(EventHandler, newEvent); - }); - } - } - // relink page groups - const updateArr = []; - for (const { pageId, groupId } of pageGroupIdArr) { - updateArr.push(manager.update(Page, { id: oldNewIdMap[pageId] }, { pageGroupId: oldNewIdMap[groupId] })); - } - await Promise.all(updateArr); - - const newDataQueries = await manager.find(DataQuery, { - where: { appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id] }, - }); - - for (const importedDataQuery of importingDataQueriesForAppVersion) { - const mappedNewDataQuery = newDataQueries.find( - (dq) => dq.id === appResourceMappings.dataQueryMapping[importedDataQuery.id] - ); - - if (!mappedNewDataQuery) continue; - - const importingQueryEvents = importingEvents.filter( - (event) => event.target === Target.dataQuery && event.sourceId === importedDataQuery.id - ); - - if (importingQueryEvents.length > 0) { - importingQueryEvents.forEach(async (dataQueryEvent) => { - const newEvent = await manager.create(EventHandler, { - name: dataQueryEvent.name, - sourceId: mappedNewDataQuery.id, - target: dataQueryEvent.target, - event: dataQueryEvent.event, - index: dataQueryEvent.index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await manager.save(EventHandler, newEvent); - }); - } else { - this.replaceDataQueryOptionsWithNewDataQueryIds( - mappedNewDataQuery?.options, - appResourceMappings.dataQueryMapping - ); - const queryEvents = mappedNewDataQuery?.options?.events || []; - - delete mappedNewDataQuery?.options?.events; - - if (queryEvents.length > 0) { - queryEvents.forEach(async (event, index) => { - const newEvent = await manager.create(EventHandler, { - name: event.eventId, - sourceId: mappedNewDataQuery.id, - target: Target.dataQuery, - event: event, - index: event.index ?? index, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await manager.save(EventHandler, newEvent); - }); - } - } - - await manager.save(mappedNewDataQuery); - } - - await manager.update( - AppVersion, - { id: appResourceMappings.appVersionMapping[importingAppVersion.id] }, - { - homePageId: updateHomepageId, - } - ); - } - - // const appVersionIds = Object.values(appResourceMappings.appVersionMapping); - - // for (const appVersionId of appVersionIds) { - // await this.updateEventActionsForNewVersionWithNewMappingIds( - // manager, - // appVersionId, - // appResourceMappings.dataQueryMapping, - // appResourceMappings.componentsMapping, - // appResourceMappings.pagesMapping - // ); - // } - - return appResourceMappings; - } - - async rejectMarketplacePluginsNotInstalled( - manager: EntityManager, - importingDataSources: DataSource[] - ): Promise { - const pluginsFound = new Set(); - - const isPluginInstalled = async (kind: string): Promise => { - if (pluginsFound.has(kind)) return true; - - const pluginExists = !!(await manager.findOne(Plugin, { where: { pluginId: kind } })); - - if (pluginExists) pluginsFound.add(kind); - - return pluginExists; - }; - - const filteredDataSources: DataSource[] = []; - - for (const ds of importingDataSources) { - const isPlugin = !!ds.pluginId; - if (!isPlugin || (isPlugin && (await isPluginInstalled(ds.kind)))) { - filteredDataSources.push(ds); - } - } - - return filteredDataSources; - } - - async createDataQueriesForAppVersion( - manager: EntityManager, - organizationId: string, - importingDataQueriesForAppVersion: DataQuery[], - importingDataSource: DataSource, - dataSourceForAppVersion: DataSource, - importingAppVersion: AppVersion, - appResourceMappings: AppResourceMappings, - externalResourceMappings: { [x: string]: any } - ) { - appResourceMappings = { ...appResourceMappings }; - const importingQueriesForSource = importingDataQueriesForAppVersion.filter( - (dq: { dataSourceId: any }) => dq.dataSourceId === importingDataSource.id - ); - if (isEmpty(importingDataQueriesForAppVersion)) return appResourceMappings; - - for (const importingQuery of importingQueriesForSource) { - const options = - importingDataSource.kind === 'tooljetdb' - ? this.replaceTooljetDbTableIds( - importingQuery.options, - externalResourceMappings['tooljet_database'], - organizationId - ) - : importingQuery.options; - - const newQuery = manager.create(DataQuery, { - name: importingQuery.name, - options, - dataSourceId: dataSourceForAppVersion.id, - appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id], - }); - - await manager.save(newQuery); - appResourceMappings.dataQueryMapping[importingQuery.id] = newQuery.id; - } - - return appResourceMappings; - } - - isExistingDataSource(dataSourceForAppVersion: DataSource): boolean { - return !!dataSourceForAppVersion.createdAt; - } - - async createDataSourceOptionsForExistingAppEnvs( - manager: EntityManager, - appVersion: AppVersion, - dataSourceForAppVersion: DataSource, - dataSourceOptions: DataSourceOptions[], - importingDataSource: DataSource, - appEnvironments: AppEnvironment[], - appResourceMappings: AppResourceMappings, - defaultAppEnvironmentId: string - ) { - appResourceMappings = { ...appResourceMappings }; - const importingDatasourceOptionsForAppVersion = dataSourceOptions.filter( - (dso: { dataSourceId: string }) => dso.dataSourceId === importingDataSource.id - ); - // create the datasource options for datasource if other environments present which is not in the export - if (appEnvironments?.length !== appResourceMappings.appDefaultEnvironmentMapping[appVersion.id].length) { - const availableEnvironments = importingDatasourceOptionsForAppVersion.map( - (option) => appResourceMappings.appEnvironmentMapping[option.environmentId] - ); - const otherEnvironmentsIds = appResourceMappings.appDefaultEnvironmentMapping[appVersion.id].filter( - (defaultEnv) => !availableEnvironments.includes(defaultEnv) - ); - const defaultEnvDsOption = importingDatasourceOptionsForAppVersion.find( - (dso) => dso.environmentId === defaultAppEnvironmentId - ); - for (const otherEnvironmentId of otherEnvironmentsIds) { - const existingDataSourceOptions = await manager.findOne(DataSourceOptions, { - where: { - dataSourceId: dataSourceForAppVersion.id, - environmentId: otherEnvironmentId, - }, - }); - !existingDataSourceOptions && - (await this.createDatasourceOption( - manager, - defaultEnvDsOption.options, - otherEnvironmentId, - dataSourceForAppVersion.id - )); - } - } - - // create datasource options only for newly created datasources - for (const importingDataSourceOption of importingDatasourceOptionsForAppVersion) { - if (importingDataSourceOption?.environmentId in appResourceMappings.appEnvironmentMapping) { - const existingDataSourceOptions = await manager.findOne(DataSourceOptions, { - where: { - dataSourceId: dataSourceForAppVersion.id, - environmentId: appResourceMappings.appEnvironmentMapping[importingDataSourceOption.environmentId], - }, - }); - - !existingDataSourceOptions && - (await this.createDatasourceOption( - manager, - importingDataSourceOption.options, - appResourceMappings.appEnvironmentMapping[importingDataSourceOption.environmentId], - dataSourceForAppVersion.id - )); - } - } - } - - async createDefaultDatasourcesForAppVersion( - manager: EntityManager, - appVersion: AppVersion, - user: User, - appResourceMappings: AppResourceMappings - ) { - const defaultDataSourceIds = await this.createDefaultDataSourceForVersion( - user.organizationId, - appResourceMappings.appVersionMapping[appVersion.id], - DefaultDataSourceKinds, - manager - ); - appResourceMappings.defaultDataSourceIdMapping[appVersion.id] = defaultDataSourceIds; - - return appResourceMappings; - } - - async findOrCreateDataSourceForAppVersion( - manager: EntityManager, - dataSource: DataSource, - appVersionId: string, - user: User - ): Promise { - const isDefaultDatasource = DefaultDataSourceNames.includes(dataSource.name as DefaultDataSourceName); - const isPlugin = !!dataSource.pluginId; - - if (isDefaultDatasource) { - const createdDefaultDatasource = await manager.findOne(DataSource, { - where: { - appVersionId, - kind: dataSource.kind, - type: DataSourceTypes.STATIC, - scope: 'local', - }, - }); - - return createdDefaultDatasource; - } - - const globalDataSourceWithSameIdExists = async (dataSource: DataSource) => { - return await manager.findOne(DataSource, { - where: { - id: dataSource.id, - kind: dataSource.kind, - type: DataSourceTypes.DEFAULT, - scope: 'global', - organizationId: user.organizationId, - }, - }); - }; - const globalDataSourceWithSameNameExists = async (dataSource: DataSource) => { - return await manager.findOne(DataSource, { - where: { - name: dataSource.name, - kind: dataSource.kind, - type: In([DataSourceTypes.DEFAULT, DataSourceTypes.SAMPLE]), - scope: 'global', - organizationId: user.organizationId, - }, - }); - }; - const existingDatasource = - (await globalDataSourceWithSameIdExists(dataSource)) || (await globalDataSourceWithSameNameExists(dataSource)); - - if (existingDatasource) return existingDatasource; - - const createDsFromPluginInstalled = async (ds: DataSource): Promise => { - const plugin = await manager.findOneOrFail(Plugin, { - where: { - pluginId: dataSource.kind, - }, - }); - - if (plugin) { - const newDataSource = manager.create(DataSource, { - organizationId: user.organizationId, - name: dataSource.name, - kind: dataSource.kind, - type: DataSourceTypes.DEFAULT, - scope: 'global', - pluginId: plugin.id, - }); - await manager.save(newDataSource); - - return newDataSource; - } - }; - - const createNewGlobalDs = async (ds: DataSource): Promise => { - const newDataSource = manager.create(DataSource, { - organizationId: user.organizationId, - name: dataSource.name, - kind: dataSource.kind, - type: DataSourceTypes.DEFAULT, - scope: 'global', - pluginId: null, - }); - await manager.save(newDataSource); - - return newDataSource; - }; - - if (isPlugin) { - return await createDsFromPluginInstalled(dataSource); - } else { - return await createNewGlobalDs(dataSource); - } - } - - async associateAppEnvironmentsToAppVersion( - manager: EntityManager, - user: User, - appEnvironments: Record[], - appVersion: AppVersion, - appResourceMappings: AppResourceMappings - ) { - appResourceMappings = { ...appResourceMappings }; - const currentOrgEnvironments = await this.appEnvironmentService.getAll(user.organizationId, manager); - - if (!appEnvironments?.length) { - currentOrgEnvironments.map((env) => (appResourceMappings.appEnvironmentMapping[env.id] = env.id)); - } else if (appEnvironments?.length && appEnvironments[0]?.appVersionId) { - const appVersionedEnvironments = appEnvironments.filter( - (appEnv: { appVersionId: string }) => appEnv.appVersionId === appVersion.id - ); - for (const currentOrgEnv of currentOrgEnvironments) { - const appEnvironment = appVersionedEnvironments.filter( - (appEnv: { name: string }) => appEnv.name === currentOrgEnv.name - )[0]; - if (appEnvironment) { - appResourceMappings.appEnvironmentMapping[appEnvironment.id] = currentOrgEnv.id; - } - } - } else { - //For apps imported on v2 where organizationId not available - for (const currentOrgEnv of currentOrgEnvironments) { - const appEnvironment = appEnvironments.filter( - (appEnv: { name: string }) => appEnv.name === currentOrgEnv.name - )[0]; - if (appEnvironment) { - appResourceMappings.appEnvironmentMapping[appEnvironment.id] = currentOrgEnv.id; - } - } - } - - return appResourceMappings; - } - - createViewerNavigationVisibilityForImportedApp(importedVersion: AppVersion) { - let pageSettings = {}; - if (importedVersion.pageSettings) { - pageSettings = { ...importedVersion.pageSettings }; - } else { - pageSettings = { - properties: { - disableMenu: { - value: `{{${!importedVersion.showViewerNavigation}}}`, - fxActive: false, - }, - }, - }; - } - return pageSettings; - } - - async createAppVersionsForImportedApp( - manager: EntityManager, - user: User, - importedApp: App, - appVersions: AppVersion[], - appResourceMappings: AppResourceMappings, - isNormalizedAppDefinitionSchema: boolean - ) { - appResourceMappings = { ...appResourceMappings }; - const { appVersionMapping, appDefaultEnvironmentMapping } = appResourceMappings; - const organization: Organization = await manager.findOne(Organization, { - where: { id: user.organizationId }, - relations: ['appEnvironments'], - }); - let currentEnvironmentId: string; - - for (const appVersion of appVersions) { - const appEnvIds: string[] = [...organization.appEnvironments.map((env) => env.id)]; - - //app is exported to CE - if (defaultAppEnvironments.length === 1) { - currentEnvironmentId = organization.appEnvironments.find((env: any) => env.isDefault)?.id; - } else { - //to EE or cloud - currentEnvironmentId = organization.appEnvironments.find((env) => env.priority === 1)?.id; - } - - const version = await manager.create(AppVersion, { - appId: importedApp.id, - definition: appVersion.definition, - name: appVersion.name, - currentEnvironmentId, - createdAt: new Date(), - updatedAt: new Date(), - }); - - if (isNormalizedAppDefinitionSchema) { - version.showViewerNavigation = appVersion.showViewerNavigation; - version.homePageId = appVersion.homePageId; - version.globalSettings = appVersion.globalSettings; - version.pageSettings = this.createViewerNavigationVisibilityForImportedApp(appVersion); - } else { - version.showViewerNavigation = appVersion.definition?.showViewerNavigation || true; - version.homePageId = appVersion.definition?.homePageId; - - if (!appVersion.definition?.globalSettings) { - version.globalSettings = { - hideHeader: false, - appInMaintenance: false, - canvasMaxWidth: 100, - canvasMaxWidthType: '%', - canvasMaxHeight: 2400, - canvasBackgroundColor: '#edeff5', - backgroundFxQuery: '', - appMode: 'auto', - }; - } else { - version.globalSettings = appVersion.definition?.globalSettings; - version.pageSettings = this.createViewerNavigationVisibilityForImportedApp(appVersion); - } - } - - await manager.save(version); - - appDefaultEnvironmentMapping[appVersion.id] = appEnvIds; - appVersionMapping[appVersion.id] = version.id; - } - - return appResourceMappings; - } - - async createDefaultDataSourceForVersion( - organizationId: string, - versionId: string, - kinds: DefaultDataSourceKind[], - manager: EntityManager - ): Promise { - const response = {}; - for (const defaultSource of kinds) { - const dataSource = await this.dataSourcesService.createDefaultDataSource(defaultSource, versionId, null, manager); - response[defaultSource] = dataSource.id; - await this.appEnvironmentService.createDataSourceInAllEnvironments(organizationId, dataSource.id, manager); - } - return response; - } - - async setEditingVersionAsLatestVersion(manager: EntityManager, appVersionMapping: any, appVersions: Array) { - if (isEmpty(appVersions)) return; - - const lastVersionFromImport = appVersions[appVersions.length - 1]; - const lastVersionIdToUpdate = appVersionMapping[lastVersionFromImport.id]; - - await manager.update(AppVersion, { id: lastVersionIdToUpdate }, { updatedAt: new Date() }); - } - - async createDatasourceOption( - manager: EntityManager, - options: Record, - environmentId: string, - dataSourceId: string - ) { - const convertedOptions = this.convertToArrayOfKeyValuePairs(options); - const newOptions = await this.dataSourcesService.parseOptionsForCreate(convertedOptions, true, manager); - const dsOption = manager.create(DataSourceOptions, { - options: newOptions, - environmentId, - dataSourceId, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(dsOption); - } - - convertToArrayOfKeyValuePairs(options: Record): Array { - if (!options) return; - return Object.keys(options).map((key) => { - return { - key: key, - value: options[key]['value'], - encrypted: options[key]['encrypted'], - }; - }); - } - - replaceDataQueryOptionsWithNewDataQueryIds( - options: { events: Record[] }, - dataQueryMapping: Record - ) { - if (options && options.events) { - const replacedEvents = options.events.map((event: { queryId: string }) => { - if (event.queryId) { - event.queryId = dataQueryMapping[event.queryId]; - } - return event; - }); - options.events = replacedEvents; - } - return options; - } - - replaceDataQueryIdWithinDefinitions( - definition: DeepPartial, - dataQueryMapping: Record - ): QueryDeepPartialEntity { - if (definition?.pages) { - for (const pageId of Object.keys(definition?.pages)) { - if (definition.pages[pageId].events) { - const replacedPageEvents = definition.pages[pageId].events.map((event: { queryId: string }) => { - if (event.queryId) { - event.queryId = dataQueryMapping[event.queryId]; - } - return event; - }); - definition.pages[pageId].events = replacedPageEvents; - } - if (definition.pages[pageId].components) { - for (const id of Object.keys(definition.pages[pageId].components)) { - const component = definition.pages[pageId].components[id].component; - - if (component?.definition?.events) { - const replacedComponentEvents = component.definition.events.map((event: { queryId: string }) => { - if (event.queryId) { - event.queryId = dataQueryMapping[event.queryId]; - } - return event; - }); - component.definition.events = replacedComponentEvents; - } - - if (component?.definition?.properties?.actions?.value) { - for (const value of component.definition.properties.actions.value) { - if (value?.events) { - const replacedComponentActionEvents = value.events.map((event: { queryId: string }) => { - if (event.queryId) { - event.queryId = dataQueryMapping[event.queryId]; - } - return event; - }); - value.events = replacedComponentActionEvents; - } - } - } - - if (component?.component === 'Table') { - for (const column of component?.definition?.properties?.columns?.value ?? []) { - if (column?.events) { - const replacedComponentActionEvents = column.events.map((event: { queryId: string }) => { - if (event.queryId) { - event.queryId = dataQueryMapping[event.queryId]; - } - return event; - }); - column.events = replacedComponentActionEvents; - } - } - } - - definition.pages[pageId].components[id].component = component; - } - } - } - } - return definition; - } - - async performLegacyAppImport( - manager: EntityManager, - importedApp: App, - appParams: any, - externalResourceMappings: any, - user: any - ) { - const dataSourceMapping = {}; - const dataQueryMapping = {}; - const dataSources = appParams?.dataSources || []; - const dataQueries = appParams?.dataQueries || []; - let currentEnvironmentId = null; - - const version = manager.create(AppVersion, { - appId: importedApp.id, - definition: appParams.definition, - name: 'v1', - currentEnvironmentId, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(version); - - // Create default data sources - const defaultDataSourceIds = await this.createDefaultDataSourceForVersion( - user.organizationId, - version.id, - DefaultDataSourceKinds, - manager - ); - let envIdArray: string[] = []; - - const organization: Organization = await manager.findOne(Organization, { - where: { id: user.organizationId }, - relations: ['appEnvironments'], - }); - envIdArray = [...organization.appEnvironments.map((env) => env.id)]; - - if (!envIdArray.length) { - await Promise.all( - defaultAppEnvironments.map(async (en) => { - const env = manager.create(AppEnvironment, { - organizationId: user.organizationId, - name: en.name, - isDefault: en.isDefault, - priority: en.priority, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(env); - if (defaultAppEnvironments.length === 1 || en.priority === 1) { - currentEnvironmentId = env.id; - } - envIdArray.push(env.id); - }) - ); - } else { - //get starting env from the organization environments list - const { appEnvironments } = organization; - if (appEnvironments.length === 1) currentEnvironmentId = appEnvironments[0].id; - else { - appEnvironments.map((appEnvironment) => { - if (appEnvironment.priority === 1) currentEnvironmentId = appEnvironment.id; - }); - } - } - - for (const source of dataSources) { - const convertedOptions = this.convertToArrayOfKeyValuePairs(source.options); - - const newSource = manager.create(DataSource, { - name: source.name, - kind: source.kind, - appVersionId: version.id, - }); - await manager.save(newSource); - dataSourceMapping[source.id] = newSource.id; - - await Promise.all( - envIdArray.map(async (envId) => { - let newOptions: Record; - if (source.options) { - newOptions = await this.dataSourcesService.parseOptionsForCreate(convertedOptions, true, manager); - } - - const dsOption = manager.create(DataSourceOptions, { - environmentId: envId, - dataSourceId: newSource.id, - options: newOptions, - createdAt: new Date(), - updatedAt: new Date(), - }); - await manager.save(dsOption); - }) - ); - } - - const newDataQueries = []; - for (const query of dataQueries) { - const dataSourceId = dataSourceMapping[query.dataSourceId]; - const newQuery = manager.create(DataQuery, { - name: query.name, - dataSourceId: !dataSourceId ? defaultDataSourceIds[query.kind] : dataSourceId, - appVersionId: query.appVersionId, - options: - dataSourceId == defaultDataSourceIds['tooljetdb'] - ? this.replaceTooljetDbTableIds( - query.options, - externalResourceMappings['tooljet_database'], - user.organizationId - ) - : query.options, - }); - await manager.save(newQuery); - dataQueryMapping[query.id] = newQuery.id; - newDataQueries.push(newQuery); - } - - for (const newQuery of newDataQueries) { - const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(newQuery.options, dataQueryMapping); - const queryEvents = newQuery.options?.events || []; - delete newOptions?.events; - - newQuery.options = newOptions; - await manager.save(newQuery); - - queryEvents.forEach(async (event, index) => { - const newEvent = { - name: event.eventId, - sourceId: newQuery.id, - target: Target.dataQuery, - event: event, - index: queryEvents.index || index, - appVersionId: newQuery.appVersionId, - }; - - await manager.save(EventHandler, newEvent); - }); - } - - await manager.update( - AppVersion, - { id: version.id }, - { definition: this.replaceDataQueryIdWithinDefinitions(version.definition, dataQueryMapping) } - ); - } - - // Entire function should be santised for Undefined values - replaceTooljetDbTableIds(queryOptions, tooljetDatabaseMapping, organizationId: string) { - let transformedQueryOptions = { ...queryOptions }; - - // FIXME: Even if the operation is not 'join_tables', - // the queryOptions currently can have fields on join_table - // if the user switches b/w operations in the UI - if (Object.keys(queryOptions).includes('join_table')) { - transformedQueryOptions = this.replaceTooljetDbTableIdOnJoin( - queryOptions, - tooljetDatabaseMapping, - organizationId - ); - } - if (queryOptions?.operation === 'join_tables') { - return transformedQueryOptions; - } - - const mappedTableId = tooljetDatabaseMapping[transformedQueryOptions.table_id]?.id; - return { - ...transformedQueryOptions, - ...(mappedTableId && { table_id: mappedTableId }), - ...(organizationId && { organization_id: organizationId }), - }; - } - - replaceTooljetDbTableIdOnJoin( - queryOptions, - tooljetDatabaseMapping, - organizationId: string - ): Partial<{ - table_id: string; - join_table: unknown; - organization_id: string; - }> { - const joinOptions = { ...(queryOptions?.join_table ?? {}) }; - - // JOIN Section - if (joinOptions?.joins && joinOptions.joins.length > 0) { - const joinsTableIdUpdatedList = joinOptions.joins.map((joinCondition) => { - const updatedJoinCondition = { ...joinCondition }; - // Updating Join tableId - if (updatedJoinCondition.table) - updatedJoinCondition.table = - tooljetDatabaseMapping[updatedJoinCondition.table]?.id ?? updatedJoinCondition.table; - // Updating TableId on Conditions in Join Query - if (updatedJoinCondition.conditions) { - const updatedJoinConditionFilter = this.updateNewTableIdForFilter( - updatedJoinCondition.conditions, - tooljetDatabaseMapping - ); - updatedJoinCondition.conditions = updatedJoinConditionFilter.conditions; - } - - return updatedJoinCondition; - }); - joinOptions.joins = joinsTableIdUpdatedList; - } - - // Filter Section - if (joinOptions?.conditions) { - joinOptions.conditions = this.updateNewTableIdForFilter( - joinOptions.conditions, - tooljetDatabaseMapping - ).conditions; - } - - // Select Section - if (joinOptions?.fields) { - joinOptions.fields = joinOptions.fields.map((eachField) => { - if (eachField.table) { - eachField.table = tooljetDatabaseMapping[eachField.table]?.id ?? eachField.table; - return eachField; - } - return eachField; - }); - } - - // From Section - if (joinOptions?.from) { - const { name = '' } = joinOptions.from; - joinOptions.from = { ...joinOptions.from, name: tooljetDatabaseMapping[name]?.id ?? name }; - } - - // Sort Section - if (joinOptions?.order_by) { - joinOptions.order_by = joinOptions.order_by.map((eachOrderBy) => { - if (eachOrderBy.table) { - eachOrderBy.table = tooljetDatabaseMapping[eachOrderBy.table]?.id ?? eachOrderBy.table; - return eachOrderBy; - } - return eachOrderBy; - }); - } - - return { - ...queryOptions, - table_id: tooljetDatabaseMapping[queryOptions.table_id]?.id, - join_table: joinOptions, - organization_id: organizationId, - }; - } - - updateNewTableIdForFilter(joinConditions, tooljetDatabaseMapping) { - const { conditionsList = [] } = { ...joinConditions }; - const updatedConditionList = conditionsList.map((condition) => { - if (condition.conditions) { - return this.updateNewTableIdForFilter(condition.conditions, tooljetDatabaseMapping); - } else { - const { operator = '=', leftField = {}, rightField = {} } = { ...condition }; - if (leftField?.table) leftField['table'] = tooljetDatabaseMapping[leftField.table]?.id ?? leftField.table; - if (rightField?.table) rightField['table'] = tooljetDatabaseMapping[rightField.table]?.id ?? rightField.table; - return { operator, leftField, rightField }; - } - }); - return { conditions: { ...joinConditions, conditionsList: [...updatedConditionList] } }; - } - - async updateEventActionsForNewVersionWithNewMappingIds( - manager: EntityManager, - versionId: string, - oldDataQueryToNewMapping: Record, - oldComponentToNewComponentMapping: Record, - oldPageToNewPageMapping: Record - ) { - const allEvents = await manager - .createQueryBuilder(EventHandler, 'event') - .where('event.appVersionId = :versionId', { versionId }) - .getMany(); - const mappings = { ...oldDataQueryToNewMapping, ...oldComponentToNewComponentMapping } as Record; - - for (const event of allEvents) { - const eventDefinition = updateEntityReferences(event.event, mappings); - - if (eventDefinition?.actionId === 'run-query' && oldDataQueryToNewMapping[eventDefinition.queryId]) { - eventDefinition.queryId = oldDataQueryToNewMapping[eventDefinition.queryId]; - } - - if ( - eventDefinition?.actionId === 'control-component' && - oldComponentToNewComponentMapping[eventDefinition.componentId] - ) { - eventDefinition.componentId = oldComponentToNewComponentMapping[eventDefinition.componentId]; - } - - if (eventDefinition?.actionId === 'switch-page' && oldPageToNewPageMapping[eventDefinition.pageId]) { - eventDefinition.pageId = oldPageToNewPageMapping[eventDefinition.pageId]; - } - - if ( - (eventDefinition?.actionId == 'show-modal' || eventDefinition?.actionId === 'close-modal') && - oldComponentToNewComponentMapping[eventDefinition.modal] - ) { - eventDefinition.modal = oldComponentToNewComponentMapping[eventDefinition.modal]; - } - - if (eventDefinition?.actionId == 'set-table-page' && oldComponentToNewComponentMapping[eventDefinition.table]) { - eventDefinition.table = oldComponentToNewComponentMapping[eventDefinition.table]; - } - - event.event = eventDefinition; - - await manager.save(event); - } - } -} - -export function convertSinglePageSchemaToMultiPageSchema(appParams: any) { - const appParamsWithMultipageSchema = { - ...appParams, - appVersions: appParams.appVersions?.map((appVersion: { definition: any }) => ({ - ...appVersion, - definition: convertAppDefinitionFromSinglePageToMultiPage(appVersion.definition), - })), - }; - return appParamsWithMultipageSchema; -} - -/** - * Migrates styles to properties of the component based on the specified component types. - * @param {NewRevampedComponent} componentType - Component type for which to perform property migration. - * @param {Component} component - The component object containing properties, styles, and general information. - * @param {NewRevampedComponent[]} componentTypes - An array of component types for which to perform property migration. - * @returns {object} An object containing the modified properties, styles, and general information. - */ -function migrateProperties( - componentType: NewRevampedComponent, - component: Component, - componentTypes: NewRevampedComponent[], - tooljetVersion: string | null -) { - const properties = { ...component.properties }; - const styles = { ...component.styles }; - const general = { ...component.general }; - const validation = { ...component.validation }; - const generalStyles = { ...component.generalStyles }; - - if (!tooljetVersion) { - return { properties, styles, general, generalStyles, validation }; - } - - const shouldHandleBackwardCompatibility = isVersionGreaterThanOrEqual(tooljetVersion, '2.29.0') ? false : true; - - // Check if the component type is included in the specified component types - if (componentTypes.includes(componentType as NewRevampedComponent)) { - if (styles.visibility) { - properties.visibility = styles.visibility; - delete styles.visibility; - } - - if (styles.disabledState) { - properties.disabledState = styles.disabledState; - delete styles.disabledState; - } - - if (general?.tooltip) { - properties.tooltip = general?.tooltip; - delete general?.tooltip; - } - - if (generalStyles?.boxShadow) { - styles.boxShadow = generalStyles?.boxShadow; - delete generalStyles?.boxShadow; - } - - if ( - shouldHandleBackwardCompatibility && - (componentType === 'TextInput' || componentType === 'PasswordInput' || componentType === 'NumberInput') - ) { - properties.label = ''; - } - - if (componentType === 'NumberInput') { - if (properties.minValue) { - validation.minValue = properties?.minValue; - delete properties.minValue; - } - - if (properties.maxValue) { - validation.maxValue = properties?.maxValue; - delete properties.maxValue; - } - } - } - return { properties, styles, general, generalStyles, validation }; -} - -function transformComponentData( - data: object, - componentEvents: any[], - componentsMapping: Record, - isNormalizedAppDefinitionSchema = true, - tooljetVersion: string -): Component[] { - const transformedComponents: Component[] = []; - - const allComponents = Object.keys(data).map((key) => { - return { - id: key, - ...data[key], - }; - }); - - for (const componentId in data) { - const component = data[componentId]; - const componentData = component['component']; - - let skipComponent = false; - const transformedComponent: Component = new Component(); - - let parentId = component.parent ? component.parent : null; - - const isParentTabOrCalendar = hasSlottedChildren( - component, - allComponents, - parentId, - isNormalizedAppDefinitionSchema - ); - - if (isParentTabOrCalendar) { - const childTabId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[2] : null; - const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - const mappedParentId = componentsMapping[_parentId]; - - parentId = `${mappedParentId}-${childTabId}`; - } else if (isChildOfKanbanModal(component, allComponents, parentId, isNormalizedAppDefinitionSchema)) { - const _parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - const mappedParentId = componentsMapping[_parentId]; - - parentId = `${mappedParentId}-modal`; - } else { - if (component.parent && !componentsMapping[parentId]) { - skipComponent = true; - } - parentId = componentsMapping[parentId]; - } - - if (!skipComponent) { - const { properties, styles, general, validation, generalStyles } = migrateProperties( - componentData.component, - componentData.definition, - NewRevampedComponents, - tooljetVersion - ); - transformedComponent.id = uuid(); - transformedComponent.name = componentData.name; - transformedComponent.type = componentData.component; - transformedComponent.properties = properties || {}; - transformedComponent.styles = styles || {}; - transformedComponent.validation = validation || {}; - transformedComponent.general = general || {}; - transformedComponent.generalStyles = generalStyles || {}; - transformedComponent.displayPreferences = componentData.definition.others || {}; - transformedComponent.parent = component.parent ? parentId : null; - - transformedComponents.push(transformedComponent); - - componentEvents.push({ - componentId: componentId, - event: componentData.definition.events, - }); - componentsMapping[componentId] = transformedComponent.id; - } - } - - return transformedComponents; -} - -const hasSlottedChildren = ( - component, - allComponents = [], - componentParentId = undefined, - isNormalizedAppDefinitionSchema: boolean -) => { - if (componentParentId) { - const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - - const parentComponent = allComponents.find((comp) => comp.id === parentId); - - const hasSlotedHeaderOrFooter = componentParentId.includes('-header') || componentParentId.includes('-footer'); - const isParentModal = parentComponent.type === 'Modal' || parentComponent.type === 'ModalV2'; - - if (isParentModal && hasSlotedHeaderOrFooter) return true; - if (parentComponent) { - if (!isNormalizedAppDefinitionSchema) { - return parentComponent.component.component === 'Tabs' || parentComponent.component.component === 'Calendar'; - } - - return parentComponent.type === 'Tabs' || parentComponent.type === 'Calendar'; - } - } - - return false; -}; - -const isChildOfKanbanModal = ( - component, - allComponents = [], - componentParentId = undefined, - isNormalizedAppDefinitionSchema: boolean -) => { - if (!componentParentId || !componentParentId.includes('modal')) return false; - - const parentId = component?.parent ? component.parent?.match(/([a-fA-F0-9-]{36})-(.+)/)?.[1] : null; - - const parentComponent = allComponents.find((comp) => comp.id === parentId); - - if (!isNormalizedAppDefinitionSchema) { - return parentComponent.component.component === 'Kanban'; - } - - return parentComponent?.type === 'Kanban'; -}; diff --git a/server/src/services/ignored/app_users.service.ts b/server/src/services/ignored/app_users.service.ts deleted file mode 100644 index b44dd31f7c..0000000000 --- a/server/src/services/ignored/app_users.service.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from 'src/entities/user.entity'; -import { AppUser } from 'src/entities/app_user.entity'; -import { OrganizationUser } from 'src/entities/organization_user.entity'; - -@Injectable() -export class AppUsersService { - constructor( - @InjectRepository(AppUser) - private appUsersRepository: Repository, - @InjectRepository(OrganizationUser) - private organizationUsersRepository: Repository - ) {} - - // TODO: remove deprecated - async create(user: User, appId: string, organizationUserId: string, role: string): Promise { - const organizationUser = await this.organizationUsersRepository.findOne({ where: { id: organizationUserId } }); - - return await this.appUsersRepository.save( - this.appUsersRepository.create({ - appId, - userId: organizationUser.userId, - role, - createdAt: new Date(), - updatedAt: new Date(), - }) - ); - } -} diff --git a/server/src/services/ignored/comment.service.ts b/server/src/services/ignored/comment.service.ts deleted file mode 100644 index 62e67ddf0d..0000000000 --- a/server/src/services/ignored/comment.service.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; - -import { Comment } from '../entities/comment.entity'; -import { CommentRepository } from '../repositories/comment.repository'; -import { CreateCommentDto, UpdateCommentDto } from '../dto/comment.dto'; -import { groupBy, head } from 'lodash'; -import { DataSource, Repository } from 'typeorm'; -import { AppVersion } from 'src/entities/app_version.entity'; -import { User } from 'src/entities/user.entity'; -import { CommentUsers } from 'src/entities/comment_user.entity'; -import { EventEmitter2 } from '@nestjs/event-emitter'; -import { EMAIL_EVENTS } from '@modules/email/constants'; - -@Injectable() -export class CommentService { - constructor( - private readonly commentRepository: CommentRepository, - @InjectRepository(AppVersion) - private appVersionsRepository: Repository, - @InjectRepository(User) - private usersRepository: Repository, - @InjectRepository(CommentUsers) - private commentUsersRepository: Repository, - private readonly _dataSource: DataSource, - private eventEmitter: EventEmitter2 - ) {} - - public async createComment(createCommentDto: CreateCommentDto, user: User): Promise { - try { - const comment = await this.commentRepository.createComment(createCommentDto, user.id, user.organizationId); - - // todo: move mentioned user emails to a queue service - const [appLink, commentLink, appName] = await this.getAppLinks(createCommentDto.appVersionsId, comment); - - for (const userId of createCommentDto.mentionedUsers) { - const mentionedUser = await this.usersRepository.findOne({ where: { id: userId }, relations: ['avatar'] }); - if (!mentionedUser) return null; // todo: invite user - this.eventEmitter.emit('emailEvent', { - type: EMAIL_EVENTS.SEND_COMMENT_MENTION_EMAIL, - payload: { - to: mentionedUser.email, - from: user.firstName, - appName: appName, - appLink: appLink, - commentLink: commentLink, - timestamp: comment.createdAt.toUTCString(), - comment: comment.comment, - fromAvatar: mentionedUser.avatar?.data.toString('base64'), - }, - }); - void this.commentUsersRepository.save( - this.commentUsersRepository.create({ commentId: comment.id, userId: mentionedUser.id }) - ); - } - return comment; - } catch (error) { - throw new InternalServerErrorException(error); - } - } - - private async getAppLinks(appVersionsId: string, comment: Comment) { - const appVersion = await this.appVersionsRepository.findOne({ where: { id: appVersionsId }, relations: ['app'] }); - const appLink = `${process.env.TOOLJET_HOST}/apps/${appVersion.app.id}`; - const commentLink = `${appLink}?threadId=${comment.threadId}&commentId=${comment.id}`; - - return [appLink, commentLink, appVersion.app.name]; - } - - public async getComments(threadId: string, appVersionsId: string): Promise { - return await this._dataSource - .createQueryBuilder(Comment, 'comment') - .innerJoin('comment.user', 'user') - .addSelect(['user.id', 'user.firstName', 'user.lastName']) - .andWhere('comment.threadId = :threadId', { - threadId, - }) - .andWhere('comment.appVersionsId = :appVersionsId', { - appVersionsId, - }) - .orderBy('comment.createdAt', 'ASC') - .getMany(); - } - - public async getOrganizationComments(organizationId: string, appVersionsId: string): Promise { - return await this.commentRepository.find({ - where: { - organizationId, - appVersionsId, - }, - order: { - createdAt: 'ASC', - }, - }); - } - - public async getNotifications( - appId: string, - userId: string, - isResolved = false, - appVersionsId: string, - pageId: string - ): Promise { - const comments = await this._dataSource - .createQueryBuilder(Comment, 'comment') - .innerJoin('comment.user', 'user') - .addSelect(['user.id', 'user.firstName', 'user.lastName']) - .innerJoin('comment.thread', 'thread') - .addSelect(['thread.id']) - .andWhere('thread.appId = :appId', { - appId, - }) - .andWhere('thread.pageId = :pageId', { - pageId, - }) - .andWhere('thread.isResolved = :isResolved', { - isResolved, - }) - .andWhere('comment.appVersionsId = :appVersionsId', { - appVersionsId, - }) - .orderBy('comment.createdAt', 'DESC') - .getMany(); - - const groupedComments = groupBy(comments, 'threadId'); - - const _comments = []; - - Object.keys(groupedComments).map((k) => { - _comments.push({ comment: head(groupedComments[k]), count: groupedComments[k].length }); - }); - - return _comments; - } - - public async getComment(commentId: string): Promise { - const foundComment = await this.commentRepository.findOne({ where: { id: commentId } }); - if (!foundComment) { - throw new NotFoundException('Comment not found'); - } - return foundComment; - } - - public async editComment(commentId: string, updateCommentDto: UpdateCommentDto): Promise { - const editedComment = await this.commentRepository.findOne({ where: { id: commentId } }); - if (!editedComment) { - throw new NotFoundException('Comment not found'); - } - return this.commentRepository.editComment(updateCommentDto, editedComment); - } - - public async deleteComment(commentId: string): Promise { - await this.commentRepository.delete(commentId); - } -} diff --git a/server/src/services/ignored/comment_users.service.ts b/server/src/services/ignored/comment_users.service.ts deleted file mode 100644 index 56fe338f62..0000000000 --- a/server/src/services/ignored/comment_users.service.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Comment } from '../entities/comment.entity'; -import { Repository } from 'typeorm'; -import { AppVersion } from 'src/entities/app_version.entity'; -import { User } from 'src/entities/user.entity'; -import { CommentUsers } from 'src/entities/comment_user.entity'; -import { UpdateCommentUserDto } from '@dto/comment-user.dto'; - -@Injectable() -export class CommentUsersService { - constructor( - @InjectRepository(AppVersion) - private appVersionsRepository: Repository, - @InjectRepository(User) - private usersRepository: Repository, - @InjectRepository(CommentUsers) - private commentUsersRepository: Repository - ) {} - - private async getAppLinks(appVersionsId: string, comment: Comment) { - const appVersion = await this.appVersionsRepository.findOne({ where: { id: appVersionsId }, relations: ['app'] }); - const appLink = `${process.env.TOOLJET_HOST}/apps/${appVersion.app.id}`; - const commentLink = `${appLink}?threadId=${comment.threadId}&commentId=${comment.id}`; - - return [appLink, commentLink, appVersion.app.name]; - } - - public async findAll(userId: string, isRead = false) { - const notifications = await this.commentUsersRepository.find({ - where: { - userId, - isRead, - }, - order: { - createdAt: 'DESC', - }, - relations: ['comment'], - }); - - if (!notifications) { - throw new NotFoundException('User notifications not found'); - } - - try { - const _notifications = notifications.map(async (notification) => { - const [, commentLink] = await this.getAppLinks(notification.comment.appVersionsId, notification.comment); - const user = await this.usersRepository.findOne({ - where: { id: notification.comment.userId }, - relations: ['avatar'], - }); - const creator = { - firstName: user.firstName, - lastName: user.lastName, - avatar: user.avatar?.data.toString('base64'), - }; - return { - id: notification.id, - creator, - comment: notification.comment.comment, - createdAt: notification.comment.createdAt, - updatedAt: notification.comment.updatedAt, - commentLink, - isRead: notification.isRead, - }; - }); - return Promise.all(_notifications); - } catch (error) { - throw new InternalServerErrorException(error); - } - } - - public async update(commentUserId: string, body: UpdateCommentUserDto) { - const item = await this.commentUsersRepository.update(commentUserId, { isRead: body.isRead }); - return item; - } - - public async updateAll(userId: string, body: UpdateCommentUserDto) { - const { isRead } = body; - const item = await this.commentUsersRepository.update({ userId }, { isRead }); - return item; - } -} diff --git a/server/src/services/ignored/external_apis.service.ts b/server/src/services/ignored/external_apis.service.ts deleted file mode 100644 index ebacd9671a..0000000000 --- a/server/src/services/ignored/external_apis.service.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { User } from 'src/entities/user.entity'; -import { OrganizationUser } from 'src/entities/organization_user.entity'; -import { Organization } from 'src/entities/organization.entity'; -import { GroupPermission } from 'src/entities/group_permission.entity'; -import { UserGroupPermission } from 'src/entities/user_group_permission.entity'; -import { dbTransactionWrap } from 'src/helpers/database.helper'; -import { EntityManager } from 'typeorm'; -import { BadRequestException } from '@nestjs/common'; -import { CreateUserDto, Status, UpdateGivenWorkspaceDto, UpdateUserDto, WorkspaceDto } from '@dto/external_apis.dto'; - -@Injectable() -export class ExternalApisService { - constructor() {} - - private generateRandomPassword(length: number = 8): string { - const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - let password = ''; - for (let i = 0; i < length; i++) { - const randomIndex = Math.floor(Math.random() * charset.length); - password += charset[randomIndex]; - } - return password; - } - - async getAllUsers(id?: string, manager?: EntityManager) { - return await dbTransactionWrap(async (manager: EntityManager) => { - const query = manager - .createQueryBuilder(User, 'user') - .leftJoinAndSelect('user.organizationUsers', 'organizationUser') - .leftJoinAndSelect('organizationUser.organization', 'organization', 'organization.status=:activeStatus', { - activeStatus: 'active', - }) - .leftJoinAndSelect('user.userGroupPermissions', 'userGroupPermissions') - .leftJoinAndSelect('userGroupPermissions.groupPermission', 'groupPermissions'); - - if (id) { - query.andWhere('user.id=:id', { id }); - } - const users: User[] = await query.getMany(); - - const userResponses = users?.map((user) => { - const userResponse = { - id: user.id, - name: `${user.firstName} ${user.lastName}`, - email: user.email, - status: user.status, - workspaces: [], - }; - const workspaces = user?.organizationUsers?.map((ou) => { - const workspaceResponse = { - id: ou.organization?.id, - name: ou.organization?.name, - status: ou.organization?.status, - groups: [], - }; - const groups = user?.userGroupPermissions - ?.filter((ugp) => ugp.groupPermission.organizationId === workspaceResponse.id) - ?.map((ugp) => { - return { - id: ugp?.groupPermission?.id, - name: ugp?.groupPermission.group, - }; - }); - workspaceResponse.groups = groups || []; - return workspaceResponse; - }); - userResponse.workspaces = workspaces || []; - return userResponse; - }); - return !id ? userResponses || [] : userResponses && userResponses.length > 0 ? userResponses[0] : []; - }, manager); - } - - async createUser(userDto: CreateUserDto) { - return await dbTransactionWrap(async (manager: EntityManager) => { - const { name, email, password, status, workspaces } = userDto; - - const [firstName, lastName] = name.split(' '); - - // Generate a password if not provided - const userPassword = password || this.generateRandomPassword(); - - const newUser = manager.create(User, { - firstName, - lastName, - email, - password: userPassword, - status: status || Status.ARCHIVED, - }); - - await manager.save(User, newUser); - - for (const workspace of workspaces) { - let organization = null; - - if (workspace.id) { - organization = await manager.findOne(Organization, { where: { id: workspace.id } }); - } else if (workspace.name) { - organization = await manager.findOne(Organization, { where: { name: workspace.name } }); - } - - if (!organization) { - throw new BadRequestException( - `The workspaces id or name do not exist: id ${workspace.id}, name ${workspace.name}` - ); - } - - const organizationUser = manager.create(OrganizationUser, { - userId: newUser.id, - organizationId: organization.id, - status: workspace?.status || Status.ARCHIVED, - role: 'all-users', - }); - - await manager.save(OrganizationUser, organizationUser); - - let groups = workspace.groups; - - if (!groups || groups.length === 0) { - groups = [{ name: 'all_users' }]; - } - - for (const group of groups) { - let groupPermission = null; - - if (group.id) { - groupPermission = await manager.findOne(GroupPermission, { where: { id: group.id } }); - } else if (group.name) { - groupPermission = await manager.findOne(GroupPermission, { - where: { group: group.name, organizationId: organization.id }, - }); - } - - if (!groupPermission) { - throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`); - } - - // Associate user with group permission - await manager.save(UserGroupPermission, { - userId: newUser.id, - groupPermissionId: groupPermission.id, - }); - } - } - - return await this.getAllUsers(newUser.id, manager); - }); - } - - async updateUser(id: string, updateDto: UpdateUserDto) { - return await dbTransactionWrap(async (manager: EntityManager) => { - const user = await manager.findOne(User, { where: { id } }); - - if (!user) { - throw new NotFoundException('User not found'); - } - - const { name, email, password, status } = updateDto; - - const userUpdateParams: Partial = {}; - - if (name) { - const [firstName, lastName] = name.split(' '); - userUpdateParams.firstName = firstName; - userUpdateParams.lastName = lastName; - } - - if (email) { - userUpdateParams.email = email; - } - - if (password) { - userUpdateParams.password = password; - } - - if (status) { - userUpdateParams.status = status; - } - - await manager.update(User, { id: user.id }, userUpdateParams); - - return; - }); - } - - async replaceUserAllWorkspacesRelations(userId: string, workspacesDto: WorkspaceDto[]) { - return await dbTransactionWrap(async (manager: EntityManager) => { - const user = await manager.findOne(User, { where: { id: userId } }); - - if (!user) { - throw new NotFoundException('User not found'); - } - - // Remove existing group permissions for the user from all workspaces - await manager.delete(UserGroupPermission, { userId }); - - // Remove existing organization user records for the user - await manager.delete(OrganizationUser, { userId }); - - for (const workspace of workspacesDto) { - let organization = null; - - if (workspace.id) { - organization = await manager.findOne(Organization, { where: { id: workspace.id } }); - } else if (workspace.name) { - organization = await manager.findOne(Organization, { where: { name: workspace.name } }); - } - - if (!organization) { - throw new BadRequestException( - `The workspaces id or name do not exist: id ${workspace.id}, name ${workspace.name}` - ); - } - - const organizationUser = manager.create(OrganizationUser, { - userId: userId, - organizationId: organization.id, - status: workspace.status || Status.ARCHIVED, - role: 'all-users', - }); - - await manager.save(OrganizationUser, organizationUser); - - const groups = !workspace.groups || workspace.groups.length === 0 ? [{ name: 'all_users' }] : workspace.groups; - - for (const group of groups) { - let groupPermission = null; - - if (group.id) { - groupPermission = await manager.findOne(GroupPermission, { - where: { id: group.id, organizationId: organization.id }, - }); - } else if (group.name) { - groupPermission = await manager.findOne(GroupPermission, { - where: { group: group.name, organizationId: organization.id }, - }); - } - - if (!groupPermission) { - throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`); - } - - // Associate user with group permission - await manager.save(UserGroupPermission, { - userId: userId, - groupPermissionId: groupPermission.id, - }); - } - } - - return; - }); - } - - async replaceUserWorkspaceRelations(userId: string, workspaceId: string, workspaceDto: UpdateGivenWorkspaceDto) { - return await dbTransactionWrap(async (manager: EntityManager) => { - const query = manager - .createQueryBuilder(OrganizationUser, 'organizationUser') - .innerJoin('organizationUser.organization', 'organization', 'organization.status = :active', { - active: 'active', - }) - .where('organizationUser.userId = :userId', { userId }) - .andWhere('organizationUser.organizationId = :workspaceId', { workspaceId }); - - const organizationUser = await query.getOne(); - - if (!organizationUser) { - throw new NotFoundException('User not found'); - } - - if (workspaceDto.status && organizationUser.status !== workspaceDto.status) { - await manager.update(OrganizationUser, { status: workspaceDto.status }, { id: organizationUser.id }); - } - - if (workspaceDto.groups && workspaceDto.groups.length > 0) { - // Remove existing group permissions for the user in this workspace - await manager - .createQueryBuilder() - .delete() - .from(UserGroupPermission) - .where('userId = :userId', { userId }) - .andWhere('groupPermissionId IN (SELECT id FROM group_permissions WHERE organization_id = :organizationId)', { - organizationId: workspaceId, - }) - .execute(); - - // Add to groups - for (const group of workspaceDto.groups) { - let groupPermission = null; - - if (group.id) { - groupPermission = await manager.findOne(GroupPermission, { - where: { id: group.id, organizationId: workspaceId }, - }); - } else if (group.name) { - groupPermission = await manager.findOne(GroupPermission, { - where: { group: group.name, organizationId: workspaceId }, - }); - } - - if (!groupPermission) { - throw new BadRequestException(`Group permission id or name not found: id ${group.id}, name ${group.name}`); - } - - // Associate user with group permission - await manager.save(UserGroupPermission, { - userId: userId, - groupPermissionId: groupPermission.id, - }); - } - } - - return; - }); - } - - async getAllWorkspaces() { - return await dbTransactionWrap(async (manager: EntityManager) => { - const workspaces: Organization[] = await manager.find(Organization, { - where: { status: 'active' }, - relations: ['groupPermissions'], - }); - - const workspaceResponses = workspaces.map((workspace: Organization) => { - return { - id: workspace.id, - name: workspace.name, - status: workspace.status, - groups: - workspace?.groupPermissions?.map((groupPermission: GroupPermission) => { - return { - id: groupPermission.id, - name: groupPermission.group, - }; - }) || [], - }; - }); - - return workspaceResponses; - }); - } -} diff --git a/server/src/services/ignored/import_export_resources.service.ts b/server/src/services/ignored/import_export_resources.service.ts deleted file mode 100644 index 36d64d64d6..0000000000 --- a/server/src/services/ignored/import_export_resources.service.ts +++ /dev/null @@ -1,139 +0,0 @@ -// import { Injectable } from '@nestjs/common'; -// import { User } from 'src/entities/user.entity'; -// import { ExportResourcesDto } from '@dto/export-resources.dto'; -// import { AppImportExportService } from './app_import_export.service'; -// import { TooljetDbImportExportService } from './tooljet_db_import_export_service'; -// import { ImportResourcesDto, ImportTooljetDatabaseDto } from '@dto/import-resources.dto'; -// import { AppsService } from './apps.service'; -// import { CloneResourcesDto } from '@dto/clone-resources.dto'; -// import { isEmpty } from 'lodash'; -// import { ActionTypes, ResourceTypes } from 'src/entities/audit_log.entity'; -// import { EventEmitter2 } from '@nestjs/event-emitter'; - -// @Injectable() -// export class ImportExportResourcesService { -// constructor( -// private readonly appImportExportService: AppImportExportService, -// private readonly appsService: AppsService, -// private readonly tooljetDbImportExportService: TooljetDbImportExportService, -// private eventEmitter: EventEmitter2 -// ) {} - -// async export( -// user: User, -// exportResourcesDto: ExportResourcesDto -// ): Promise<{ -// tooljet_database?: Array; -// app?: Array>; // TODO: Define the type for app -// }> { -// const resourcesExport: { -// tooljet_database?: Array; -// app?: Array>; -// } = {}; - -// if (exportResourcesDto.tooljet_database?.length) { -// const exportedDbs: ImportTooljetDatabaseDto[] = []; -// for (const tjdb of exportResourcesDto.tooljet_database) { -// const exportedDb = await this.tooljetDbImportExportService.export( -// exportResourcesDto.organization_id, -// tjdb, -// exportResourcesDto.tooljet_database -// ); -// exportedDbs.push(exportedDb); -// } - -// if (exportedDbs.length > 0) resourcesExport.tooljet_database = exportedDbs; -// } - -// if (exportResourcesDto.app?.length) { -// const exportedApps: Record[] = []; -// for (const app of exportResourcesDto.app) { -// const exportedApp = { -// definition: await this.appImportExportService.export(user, app.id, app.search_params), -// }; -// exportedApps.push(exportedApp); -// } - -// if (exportedApps.length > 0) resourcesExport.app = exportedApps; -// } - -// return resourcesExport; -// } - -// async import( -// user: User, -// importResourcesDto: ImportResourcesDto, -// cloning = false, -// isGitApp = false, -// isTemplateApp = false -// ) { -// let tableNameMapping = {}; -// const imports = { app: [], tooljet_database: [], tableNameMapping: {} }; -// const importingVersion = importResourcesDto.tooljet_version; - -// if (!isEmpty(importResourcesDto.tooljet_database)) { -// const res = await this.tooljetDbImportExportService.bulkImport(importResourcesDto, importingVersion, cloning); -// tableNameMapping = res.tableNameMapping; -// imports.tooljet_database = res.tooljet_database; -// imports.tableNameMapping = tableNameMapping; -// } - -// if (!isEmpty(importResourcesDto.app)) { -// for (const appImportDto of importResourcesDto.app) { -// user.organizationId = importResourcesDto.organization_id; -// const createdApp = await this.appImportExportService.import( -// user, -// appImportDto.definition, -// appImportDto.appName, -// { -// tooljet_database: tableNameMapping, -// }, -// isGitApp, -// importResourcesDto.tooljet_version, -// cloning -// ); - -// imports.app.push({ id: createdApp.id, name: createdApp.name }); - -// this.eventEmitter.emit('auditLogEntry', { -// userId: user.id, -// organizationId: user.organizationId, -// resourceId: createdApp.id, -// resourceType: ResourceTypes.APP, -// resourceName: createdApp.name, -// actionType: ActionTypes.APP_CREATE, -// }); -// } -// } - -// return imports; -// } - -// async clone(user: User, { organization_id, app: [{ id: appId, name: newAppName }] }: CloneResourcesDto) { -// const tablesForApp = await this.appsService.findTooljetDbTables(appId); -// const exportResourcesDto: ExportResourcesDto = { -// organization_id, -// app: [{ id: appId, search_params: null }], -// tooljet_database: tablesForApp, -// }; - -// const resourceExport = await this.export(user, exportResourcesDto); -// // TODO: Verify if this is required as we always pass name on imports -// // Without this appImportExportService.import will throw an error -// resourceExport.app[0].definition.appV2.name = newAppName; - -// const importResourcesDto: ImportResourcesDto = { -// organization_id, -// tooljet_version: globalThis.TOOLJET_VERSION, -// app: [ -// { -// appName: newAppName, -// definition: resourceExport.app[0].definition, -// }, -// ], -// tooljet_database: resourceExport.tooljet_database, -// }; - -// return this.import(user, importResourcesDto, true); -// } -// } diff --git a/server/src/services/ignored/library_app_creation.service.ts b/server/src/services/ignored/library_app_creation.service.ts deleted file mode 100644 index 628cff158a..0000000000 --- a/server/src/services/ignored/library_app_creation.service.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; -import { User } from '../entities/user.entity'; -import { readFileSync } from 'fs'; -import { Logger } from 'nestjs-pino'; -import { ImportExportResourcesService } from './import_export_resources.service'; -import { ImportResourcesDto } from '@dto/import-resources.dto'; -import { AppImportExportService } from './app_import_export.service'; -import { isVersionGreaterThanOrEqual } from 'src/helpers/utils.helper'; -import { AppsService } from './apps.service'; -import { getMaxCopyNumber } from 'src/helpers/utils.helper'; -import * as fs from 'fs'; -import * as path from 'path'; -import { TooljetDbBulkUploadService } from '@services/tooljet_db_bulk_upload.service'; -import { PluginsService } from './plugins.service'; - -@Injectable() -export class LibraryAppCreationService { - constructor( - private readonly importExportResourcesService: ImportExportResourcesService, - private readonly appImportExportService: AppImportExportService, - private readonly appsService: AppsService, - private readonly logger: Logger, - private readonly tooljetDbBulkUploadService: TooljetDbBulkUploadService, - private readonly pluginsService: PluginsService - ) {} - - async perform( - currentUser: User, - identifier: string, - appName: string, - dependentPluginsForTemplate: Array, - shouldAutoImportPlugin: boolean - ) { - const templateDefinition = this.findTemplateDefinition(identifier); - if (dependentPluginsForTemplate.length) - await this.pluginsService.autoInstallPluginsForTemplates(dependentPluginsForTemplate, shouldAutoImportPlugin); - return this.importTemplate(currentUser, templateDefinition, appName, identifier); - } - - async createSampleApp(currentUser: User) { - let name = 'Sample app '; - const allSampleApps = await this.appsService.findAll(currentUser?.organizationId, { name }); - const existNameList = allSampleApps.map((app) => app.name); - const maxNumber = getMaxCopyNumber(existNameList, ' '); - name = `${name} ${maxNumber}`; - const sampleAppDef = JSON.parse(readFileSync(`templates/sample_app_def.json`, 'utf-8')); - return this.importTemplate(currentUser, sampleAppDef, name); - } - - async createSampleOnboardApp(currentUser: User) { - const name = 'Product inventory'; - const sampleAppDef = JSON.parse(readFileSync(`templates/onboard_sample_app.json`, 'utf-8')); - return this.importTemplate(currentUser, sampleAppDef, name); - } - - async importTemplate(currentUser: User, templateDefinition: any, appName: string, identifier?: string) { - const importDto = new ImportResourcesDto(); - importDto.organization_id = currentUser.organizationId; - importDto.app = templateDefinition.app || templateDefinition.appV2; - importDto.tooljet_database = templateDefinition.tooljet_database; - importDto.tooljet_version = templateDefinition.tooljet_version; - - if (isVersionGreaterThanOrEqual(templateDefinition.tooljet_version, '2.16.0')) { - importDto.app[0].appName = appName; - const importedResources = await this.importExportResourcesService.import( - currentUser, - importDto, - false, - false, - true - ); - - const tableNameMapping: { [key: string]: { id: string; table_name: string } } = - importedResources.tableNameMapping; - const entries = Object.entries(tableNameMapping); - - for (let i = 0; i < entries.length; i++) { - const [key, { id: tableId }] = entries[i]; - const tableIdFromDefinition = key; - const newTableid = tableId; - - const tableDetails = templateDefinition.tooljet_database.find( - (table: Record) => table.id === tableIdFromDefinition - ); - - if (tableDetails) { - const tableNameAsPerDefinition = tableDetails.table_name; - this.processCsvFile(identifier, tableNameAsPerDefinition, newTableid, currentUser.organizationId); - } - } - - return importedResources; - } else { - const importedApp = await this.appImportExportService.import(currentUser, templateDefinition, appName); - return { - app: [importedApp], - tooljet_database: [], - }; - } - } - - findTemplateDefinition(identifier: string) { - try { - return JSON.parse(readFileSync(`templates/${identifier}/definition.json`, 'utf-8')); - } catch (err) { - this.logger.error(err); - throw new BadRequestException('App definition not found'); - } - } - async processCsvFile(identifier: string, tableName: string, tableId: string, organizationId: string) { - try { - const csvFilePath = path.join('templates', `${identifier}/data/${tableName}/data.csv`); - - // Read the CSV file and convert it into a buffer - const fileBuffer = fs.readFileSync(csvFilePath); - - return await this.tooljetDbBulkUploadService.bulkUploadCsv(tableId, fileBuffer, organizationId); - } catch (error) { - console.error('Error processing CSV file:', error); - throw new BadRequestException('Failed to process CSV file'); - } - } - - async findDepedentPluginsFromTemplateDefinition(identifier: string) { - const templateDefinition = this.findTemplateDefinition(identifier); - const importDto = new ImportResourcesDto(); - importDto.app = templateDefinition.app || templateDefinition.appV2; - - const dataSourcesUsedInApps = []; - importDto.app.forEach((appDefinition) => { - appDefinition.definition?.appV2.dataSources.forEach((dataSource) => { - dataSourcesUsedInApps.push(dataSource); - }); - }); - const { pluginsToBeInstalled, pluginsListIdToDetailsMap } = await this.pluginsService.checkIfPluginsToBeInstalled( - dataSourcesUsedInApps - ); - return { pluginsToBeInstalled, pluginsListIdToDetailsMap }; - } -} diff --git a/server/src/services/ignored/org_environment_variables.service.ts b/server/src/services/ignored/org_environment_variables.service.ts deleted file mode 100644 index 36ec7447a3..0000000000 --- a/server/src/services/ignored/org_environment_variables.service.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { CreateEnvironmentVariableDto, UpdateEnvironmentVariableDto } from '@dto/environment-variable.dto'; -import { ConflictException, Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { OrgEnvironmentVariable } from 'src/entities/org_envirnoment_variable.entity'; -import { Repository } from 'typeorm'; -import { User } from '../entities/user.entity'; -import { cleanObject } from 'src/helpers/utils.helper'; -import { EncryptionService } from '@modules/encryption/service'; - -@Injectable() -export class OrgEnvironmentVariablesService { - constructor( - @InjectRepository(OrgEnvironmentVariable) - private orgEnvironmentVariablesRepository: Repository, - private encryptionService: EncryptionService - ) {} - - async fetchVariables(organizationId: string): Promise { - const variables: OrgEnvironmentVariable[] = await this.orgEnvironmentVariablesRepository.find({ - where: { organizationId }, - }); - - await Promise.all( - variables.map(async (variable: OrgEnvironmentVariable) => { - if (variable.variableType === 'server') { - delete variable.value; - } else { - if (variable.encrypted) variable['value'] = await this.decryptSecret(organizationId, variable.value); - } - }) - ); - - return variables; - } - - async create( - currentUser: User, - environmentVariableDto: CreateEnvironmentVariableDto - ): Promise { - const variableToFind = await this.orgEnvironmentVariablesRepository.findOne({ - where: { - variableName: environmentVariableDto.variable_name, - variableType: environmentVariableDto.variable_type, - }, - }); - - if (variableToFind) { - throw new ConflictException( - `Variable name already exists in ${environmentVariableDto.variable_type ?? 'environment'} variables` - ); - } - - const encrypted = environmentVariableDto.variable_type === 'server' ? true : environmentVariableDto.encrypted; - let value: string; - if (encrypted && environmentVariableDto.value) { - value = await this.encryptSecret(currentUser.organizationId, environmentVariableDto.value); - } else { - value = environmentVariableDto.value; - } - return await this.orgEnvironmentVariablesRepository.save( - this.orgEnvironmentVariablesRepository.create({ - variableName: environmentVariableDto.variable_name, - value, - variableType: environmentVariableDto.variable_type, - encrypted, - organizationId: currentUser.organizationId, - createdAt: new Date(), - updatedAt: new Date(), - }) - ); - } - - async fetch(organizationId: string, variableId: string) { - return await this.orgEnvironmentVariablesRepository.findOne({ - where: { - organizationId, - id: variableId, - }, - }); - } - - async update(organizationId: string, variableId: string, params: UpdateEnvironmentVariableDto) { - const { variable_name } = params; - let value = params.value; - const variable = await this.fetch(organizationId, variableId); - - if (variable_name) { - const variableToFind = await this.orgEnvironmentVariablesRepository.findOne({ - where: { - variableName: variable_name, - variableType: variable.variableType, - }, - }); - - if (variableToFind && variableToFind.id !== variableId) { - throw new ConflictException(`Variable name already exists in ${variable.variableType} variables`); - } - } - - if (variable.encrypted && value) { - value = await this.encryptSecret(organizationId, value); - } - - const updateableParams = { - variableName: variable_name, - value, - }; - - // removing keys with undefined values - cleanObject(updateableParams); - - return await this.orgEnvironmentVariablesRepository.update({ organizationId, id: variableId }, updateableParams); - } - - async delete(organizationId: string, variableId: string) { - return await this.orgEnvironmentVariablesRepository.delete({ organizationId, id: variableId }); - } - - private async encryptSecret(workspaceId: string, value: string) { - return await this.encryptionService.encryptColumnValue('org_environment_variables', workspaceId, value); - } - - private async decryptSecret(workspaceId: string, value: string) { - return await this.encryptionService.decryptColumnValue('org_environment_variables', workspaceId, value); - } -} diff --git a/server/src/services/ignored/temporal.service.ts b/server/src/services/ignored/temporal.service.ts deleted file mode 100644 index 3e8b18135b..0000000000 --- a/server/src/services/ignored/temporal.service.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { Injectable, OnModuleInit, OnApplicationShutdown } from '@nestjs/common'; -import { NativeConnection, Worker } from '@temporalio/worker'; -import { Connection, Client, ScheduleOptions, ScheduleOverlapPolicy } from '@temporalio/client'; -import * as moment from 'moment'; -import { CreateWorkflowExecutionDto } from '@dto/create-workflow-execution.dto'; -import { WorkflowSchedule } from '@entities/workflow_schedule.entity'; -import { isValidCron } from 'cron-validator'; - -@Injectable() -export class TemporalService implements OnModuleInit, OnApplicationShutdown { - client: Client; - temporalConnection: Connection = undefined; - workerNativeConnection: NativeConnection = undefined; - worker: Worker = undefined; - - async onModuleInit() { - this.#connectToTemporal(); - } - - async onApplicationShutdown() { - await this.#closeConnection(); - await this.#closeWokerNativeConnection(); - } - - async isTemporalConnected(): Promise { - try { - await this.temporalConnection.healthService.check({}); - return true; - } catch (exception) { - return false; - } - } - - async runWorker() { - try { - console.log(`\x1b[1;33m[INFO] Starting worker connection to Temporal\x1b[0m`); - this.workerNativeConnection = await NativeConnection.connect({ - address: process.env.TEMPORAL_SERVER_ADDRESS, - }); - this.worker = await Worker.create({ - connection: this.workerNativeConnection, - namespace: process.env?.TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE ?? 'default', - taskQueue: process.env?.TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS, - workflowsPath: require.resolve('../temporal/workflows'), - activities: require('../temporal/activities'), - }); - - console.log(`\x1b[1;33m[INFO] Worker connection to Temporal established.\x1b[0m`); - await this.worker.run(); - } catch (error) { - console.log(`\x1b[1;31m[ERROR] Temporal server could not be reached, exiting.\x1b[0m Reason: ${error.message}`); - process.exit(1); - } - } - - async createScheduleInTemporal( - workflowScheduleId: string, - settings: any, - schedule: WorkflowSchedule, - environmentId, - timezone, - userId, - paused = true - ) { - const workflowExecution = new CreateWorkflowExecutionDto(); - workflowExecution.executeUsing = 'version'; - workflowExecution.appVersionId = schedule.workflowId; - workflowExecution.environmentId = environmentId; - - workflowExecution.params = {}; - workflowExecution.userId = userId; - workflowExecution.app = schedule.workflow.appId; - - const interval: string = - schedule.type === 'cron' - ? `${settings.minute} ${settings.hours} ${settings.dayOfMonth} ${settings.month} ${settings.dayOfWeek}` - : this.#convertWorkflowScheduleSettingsToCronString(settings); - - if (isValidCron(interval, { seconds: false }) == false) { - throw Error('Invalid interval configuration ' + interval); - } - - const scheduleOptions: ScheduleOptions = { - scheduleId: workflowScheduleId, - action: { - taskQueue: process.env?.TEMPORAL_TASK_QUEUE_NAME_FOR_WORKFLOWS ?? 'tooljet-workflows', - type: 'startWorkflow', - workflowType: 'execute', - workflowId: `schedule-${workflowScheduleId}`, - args: [JSON.parse(JSON.stringify(workflowExecution))], - retry: { - maximumAttempts: 1, - }, - }, - spec: { - cronExpressions: [interval], - timezone, - }, - policies: { - overlap: ScheduleOverlapPolicy.SKIP, - }, - state: { - paused, - }, - }; - - await this.client.schedule.create(scheduleOptions); - } - - async setScheduleState(scheduleId: string, paused: boolean) { - const handle = await this.client.schedule.getHandle(scheduleId); - - if (paused) await handle.pause('Paused from ToolJet'); - else await handle.unpause('Unpaused from ToolJet'); - } - - async removeSchedule(scheduleId: string) { - const handle = await this.client.schedule.getHandle(scheduleId); - - await handle.delete(); - } - - async updateSchedule(updatedSchedule: WorkflowSchedule, settings: any, timezone, existingSchedule, userId) { - const handle = this.client.schedule.getHandle(updatedSchedule.id); - - await handle.delete(); - - try { - await this.createScheduleInTemporal( - updatedSchedule.id, - updatedSchedule.details, - updatedSchedule, - updatedSchedule.environmentId, - timezone, - userId, - !existingSchedule.active - ); - } catch (error) { - console.log({ error }); - await this.createScheduleInTemporal( - existingSchedule.id, - existingSchedule.details, - existingSchedule, - existingSchedule.environmentId, - existingSchedule.timezone, - userId, - !existingSchedule.active - ); - - throw error; - } - } - - #convertWorkflowScheduleSettingsToCronString(settings: any): string { - switch (settings.frequency) { - case 'minute': { - return '* * * * *'; - } - - case 'hour': { - const { minutes } = settings; - return `${minutes} * * * *`; - } - - case 'day': { - const { hour } = settings; - const hourOfTheDay = this.#convertToHourOffset(hour); - return `0 ${hourOfTheDay} * * *`; - } - - case 'week': { - const { day, hour } = settings; - - const dayOfTheWeek = moment().day(day).day(); - const hourOfTheDay = this.#convertToHourOffset(hour); - - return `0 ${hourOfTheDay} * * ${dayOfTheWeek}`; - } - - case 'month': { - const { date: dayOfMonth, hour } = settings; - - const hourOfTheDay = this.#convertToHourOffset(hour); - - return `0 ${hourOfTheDay} ${dayOfMonth} * *`; - } - } - } - - #convertToHourOffset(timeString) { - const time = moment(timeString, 'h:mm A'); - return time.hours() + time.minutes() / 60; - } - - #connectToTemporal() { - if (!process.env.WORKER) { - if (process.env.ENABLE_WORKFLOW_SCHEDULING === 'true') { - console.log(`\x1b[1;33m[INFO] Connecting to Temporal server\x1b[0m`); - Connection.connect({ - address: process.env.TEMPORAL_SERVER_ADDRESS, - }) - .then((connection) => { - this.temporalConnection = connection; - this.client = new Client({ - connection, - namespace: process.env?.TOOLJET_WORKFLOWS_TEMPORAL_NAMESPACE ?? 'default', - }); - console.log(`\x1b[1;32m[INFO] Connected to Temporal server\x1b[0m`); - }) - .catch((reason) => { - console.log( - `\x1b[1;33m [WARNING] Temporal server could not be reached, workflow schedules cannot be created, deleted or updated.\x1b[0m Reason: ${reason.message}` - ); - }); - } else { - console.log( - `\x1b[1;33m[INFO] Not connecting to temporal as ENABLE_WORKFLOW_SCHEDULING is not set to 'true'. \x1b[0m` - ); - } - } - } - - async #closeConnection() { - if (this.temporalConnection) { - console.log(`\x1b[1;33m[INFO] Closing Temporal connection\x1b[0m`); - this.temporalConnection - .close() - .then(() => { - console.log(`\x1b[1;32m[INFO] Temporal connection closed\x1b[0m`); - }) - .catch(() => { - console.log(`\x1b[1;31m[ERROR] Could not close Temporal connection\x1b[0m`); - }); - } - } - - async #closeWokerNativeConnection() { - if (process.env.WORKER) { - console.log(`\x1b[1;33m[INFO] Closing worker connection to Temporal\x1b[0m`); - try { - await this.workerNativeConnection.close(); - console.log(`\x1b[1;32m[INFO] Worker connection to temporal closed\x1b[0m`); - } catch (error) { - console.log( - `\x1b[1;31m[ERROR] Worker connection to temporal failed to close. Reason: ${error.message} \x1b[0m` - ); - } - } - } - - shutDownWorker() { - this.worker.shutdown(); - } -} diff --git a/server/src/services/ignored/thread.service.ts b/server/src/services/ignored/thread.service.ts deleted file mode 100644 index 258816586b..0000000000 --- a/server/src/services/ignored/thread.service.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Thread } from '../entities/thread.entity'; -import { CreateThreadDto, UpdateThreadDto } from '../dto/thread.dto'; -import { ThreadRepository } from '../repositories/thread.repository'; -import { DataSource } from 'typeorm'; - -@Injectable() -export class ThreadService { - constructor( - @InjectRepository(ThreadRepository) - private threadRepository: ThreadRepository, - private readonly _dataSource: DataSource - ) {} - - public async createThread(createThreadDto: CreateThreadDto, userId: string, orgId: string): Promise { - const thread: Thread = await this.threadRepository.createThread(createThreadDto, userId, orgId); - return (await this.getThreads(thread.appId, thread.organizationId, thread.appVersionsId, thread.id))?.[0]; - } - - public async getThreads( - appId: string, - organizationId: string, - appVersionsId: string, - threadId?: string - ): Promise { - const query = this._dataSource - .createQueryBuilder(Thread, 'thread') - .innerJoin('thread.user', 'user') - .addSelect(['user.id', 'user.firstName', 'user.lastName']) - .andWhere('thread.appId = :appId', { - appId, - }) - .andWhere('thread.organizationId = :organizationId', { - organizationId, - }) - .andWhere('thread.appVersionsId = :appVersionsId', { - appVersionsId, - }); - - if (threadId) { - query.andWhere('thread.id = :threadId', { - threadId, - }); - } - return await query.getMany(); - } - - public async getOrganizationThreads(organizationId: string): Promise { - return await this.threadRepository.find({ - where: { - organizationId, - }, - }); - } - - public async getThread(threadId: string): Promise { - const foundThread = await this.threadRepository.findOne({ where: { id: threadId } }); - if (!foundThread) { - throw new NotFoundException('Thread not found'); - } - return foundThread; - } - - public async editThread(threadId: string, updateThreadDto: UpdateThreadDto): Promise { - const editedThread = await this.threadRepository.findOne({ where: { id: threadId } }); - if (!editedThread) { - throw new NotFoundException('Thread not found'); - } - return this.threadRepository.editThread(updateThreadDto, editedThread); - } - - public async deleteThread(threadId: string): Promise { - await this.threadRepository.delete(threadId); - } -} diff --git a/server/src/services/ignored/users-common.service.ts b/server/src/services/ignored/users-common.service.ts deleted file mode 100644 index 191e59c72e..0000000000 --- a/server/src/services/ignored/users-common.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { OnEvent } from '@nestjs/event-emitter'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { User } from '../entities/user.entity'; - -@Injectable() -export class UserCommonService { - constructor( - @InjectRepository(User) - private userRepository: Repository - ) {} - - @OnEvent('user.update') - async handleUserUpdate(event: { userId: string; details: Partial }) { - const { userId, details } = event; - return await this.userRepository.update(userId, details); - } -} diff --git a/server/src/services/ignored/worker.service.ts b/server/src/services/ignored/worker.service.ts deleted file mode 100644 index e4bf0b9f2c..0000000000 --- a/server/src/services/ignored/worker.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; - -@Injectable() -export class WorkerService implements OnModuleInit { - onModuleInit() { - console.log(`The module has been initialized.`); - } -} diff --git a/server/src/services/ignored/workflow_executions.service.ts b/server/src/services/ignored/workflow_executions.service.ts deleted file mode 100644 index 66a7ef6734..0000000000 --- a/server/src/services/ignored/workflow_executions.service.ts +++ /dev/null @@ -1,882 +0,0 @@ -// import { CreateWorkflowExecutionDto } from '@dto/create-workflow-execution.dto'; -// import { Injectable } from '@nestjs/common'; -// import { InjectRepository } from '@nestjs/typeorm'; -// import { AppVersion } from 'src/entities/app_version.entity'; -// import { App } from 'src/entities/app.entity'; -// import { WorkflowExecution } from 'src/entities/workflow_execution.entity'; -// import { WorkflowExecutionNode } from 'src/entities/workflow_execution_node.entity'; -// import { WorkflowExecutionEdge } from 'src/entities/workflow_execution_edge.entity'; -// import { dbTransactionWrap } from 'src/helpers/database.helper'; -// import { EntityManager, Repository } from 'typeorm'; -// import { find } from 'lodash'; -// import { DataQueriesService } from '../modules/data-queries/service'; -// import { User } from 'src/entities/user.entity'; -// import { getQueryVariables, resolveCode } from '../../lib/utils'; -// import { Graph, alg } from '@dagrejs/graphlib'; -// import * as moment from 'moment'; -// import { stringify } from 'flatted'; -// import { OrganizationConstantsService } from '../modules/organization-constants/service'; -// // import { Organization } from 'src/entities/organization.entity'; -// import { Response } from 'express'; -// import { cloneDeep } from 'lodash'; - -// const STATIC_NODE_TYPE_TO_HANDLE_MAPPING = { -// input: 'startTrigger', -// output: 'responseNode', -// 'if-condition': 'ifCondition', -// }; -// import { OrganizationConstantType } from 'src/entities/organization_constants.entity'; -// import { LicenseTermsService } from '@modules/licensing/interfaces/IService'; -// import { LICENSE_FIELD, LICENSE_LIMIT } from '@modules/licensing/constants'; - -// @Injectable() -// export class WorkflowExecutionsService { -// constructor( -// @InjectRepository(AppVersion) -// private appVersionsRepository: Repository, - -// @InjectRepository(WorkflowExecution) -// private workflowExecutionRepository: Repository, - -// @InjectRepository(WorkflowExecutionEdge) -// private workflowExecutionEdgeRepository: Repository, - -// @InjectRepository(WorkflowExecutionNode) -// private workflowExecutionNodeRepository: Repository, - -// @InjectRepository(User) -// private userRepository: Repository, - -// private dataQueriesService: DataQueriesService, -// private licenseTermsService: LicenseTermsService, -// private organizationConstantsService: OrganizationConstantsService -// ) {} - -// workflowExecutionTimeout = parseInt(process.env.WORKFLOW_TIMEOUT_SECONDS); - -// async create(createWorkflowExecutionDto: CreateWorkflowExecutionDto): Promise { -// const workflowExecution = await dbTransactionWrap(async (manager: EntityManager) => { -// const appVersionId = -// createWorkflowExecutionDto?.appVersionId ?? -// ( -// await manager.findOne(App, { -// where: { id: createWorkflowExecutionDto.appId }, -// }) -// ).editingVersion.id; - -// const workflowExecution = await manager.save( -// WorkflowExecution, -// manager.create(WorkflowExecution, { -// appVersionId: appVersionId, -// createdAt: new Date(), -// updatedAt: new Date(), -// }) -// ); - -// const appVersion = await this.appVersionsRepository.findOne({ where: { id: workflowExecution.appVersionId } }); -// const definition = appVersion.definition; - -// const queryIdsOnDefinitionToActualQueryIdMapping = Object.fromEntries( -// definition.nodes -// .filter((node) => node.type === 'query') -// .map((node) => node.data.idOnDefinition) -// .map((idOnDefinition) => [ -// idOnDefinition, -// find(appVersion.definition.queries, { -// idOnDefinition, -// }).id, -// ]) -// ); - -// const queries = await this.dataQueriesService.findByIds( -// Object.values(queryIdsOnDefinitionToActualQueryIdMapping) -// ); - -// const nodes = []; -// for (const nodeData of definition.nodes) { -// nodeData.data.handle = this.computeNodeHandle(nodeData, queries, queryIdsOnDefinitionToActualQueryIdMapping); - -// const node = await manager.save( -// WorkflowExecutionNode, -// manager.create(WorkflowExecutionNode, { -// type: nodeData.type, -// workflowExecutionId: workflowExecution.id, -// idOnWorkflowDefinition: nodeData.id, -// definition: nodeData.data, -// createdAt: new Date(), -// updatedAt: new Date(), -// }) -// ); - -// nodes.push(node); -// } - -// const startNode = find(nodes, (node) => node.definition.nodeType === 'start'); -// workflowExecution.startNodeId = startNode.id; - -// await manager.update(WorkflowExecution, workflowExecution.id, { startNode }); - -// const edges = []; -// for (const edgeData of definition.edges) { -// // const sourceNode = find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.source); -// // const targetNode = find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.target); - -// const edge = await manager.save( -// WorkflowExecutionEdge, -// manager.create(WorkflowExecutionEdge, { -// workflowExecutionId: workflowExecution.id, -// idOnWorkflowDefinition: edgeData.id, -// sourceWorkflowExecutionNodeId: find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.source).id, -// targetWorkflowExecutionNodeId: find(nodes, (node) => node.idOnWorkflowDefinition === edgeData.target).id, -// sourceHandle: edgeData.sourceHandle, -// createdAt: new Date(), -// updatedAt: new Date(), -// }) -// ); - -// edges.push(edge); -// } - -// return workflowExecution; -// }); - -// return workflowExecution; -// } - -// async getStatus(workflowExecutionId: string) { -// const workflowExecution = await this.workflowExecutionRepository.findOne({ -// where: { id: workflowExecutionId }, -// }); -// const workflowExecutionNodes = await this.workflowExecutionNodeRepository.find({ -// where: { -// workflowExecutionId: workflowExecution.id, -// }, -// }); - -// const nodes = workflowExecutionNodes.map((node) => ({ -// id: node.id, -// idOnDefinition: node.idOnWorkflowDefinition, -// executed: node.executed, -// result: node.result, -// })); - -// return { -// logs: workflowExecution.logs, -// status: workflowExecution.executed, -// nodes, -// }; -// } - -// async getWorkflowExecution(workflowExecutionId: string) { -// const workflowExecution = await this.workflowExecutionRepository.findOne({ -// where: { id: workflowExecutionId }, -// }); -// const nodes = await this.workflowExecutionNodeRepository.find({ -// where: { id: workflowExecutionId }, -// }); -// const appVersion = await this.appVersionsRepository.findOne({ -// where: { id: workflowExecution.appVersionId }, -// }); -// const queries = await this.dataQueriesService.all({ app_version_id: appVersion.id }); - -// const nodesWithHandles = nodes.map((node) => { -// const queryId = find(appVersion.definition.queries, { -// idOnDefinition: node.definition.idOnDefinition, -// })?.id; - -// const query = find(queries, { id: queryId }); -// if (query) { -// node.definition.handle = query.name; -// } else { -// node.definition.handle = STATIC_NODE_TYPE_TO_HANDLE_MAPPING[node.type]; -// } - -// return node; -// }); - -// workflowExecution.nodes = nodesWithHandles; - -// return workflowExecution; -// } - -// async listWorkflowExecutions(appVersionId: string) { -// const workflowExecutions = await this.workflowExecutionRepository.find({ -// where: { -// appVersionId, -// }, -// order: { -// createdAt: 'DESC', -// }, -// relations: ['nodes'], -// take: 10, -// }); - -// return workflowExecutions; -// } - -// async execute( -// workflowExecution: WorkflowExecution, -// params: object = {}, -// envId = '', -// response: Response -// ): Promise { -// const organization: any = await dbTransactionWrap(async (manager: EntityManager) => { -// return manager -// .createQueryBuilder('organizations', 'organization') -// .innerJoin('apps', 'app', 'app.organization_id = organization.id') -// .innerJoin('app_versions', 'av', 'av.app_id = app.id') -// .innerJoin('workflow_executions', 'we', 'we.app_version_id = av.id') -// .where('we.id = :workflowExecutionId', { workflowExecutionId: workflowExecution.id }) -// .getOne(); -// }); - -// const constants = await this.organizationConstantsService.getConstantsForEnvironment( -// organization.id, -// envId, -// OrganizationConstantType.GLOBAL -// ); -// const constantsObject = Object.fromEntries(constants.map((constant) => [constant.name, constant.value])); -// const appVersion = await this.appVersionsRepository.findOne({ -// where: { -// id: workflowExecution.appVersionId, -// }, -// relations: ['app'], -// }); - -// this.workflowExecutionTimeout = -// (await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.WORKFLOWS))?.execution_timeout ?? -// parseInt(process.env.WORKFLOW_TIMEOUT_SECONDS); - -// if (envId) appVersion.currentEnvironmentId = envId; - -// workflowExecution = await this.workflowExecutionRepository.findOne({ -// where: { -// id: workflowExecution.id, -// }, -// relations: ['startNode', 'user', 'nodes', 'edges'], -// }); - -// let finalResult = {}; -// const logs = []; -// const queue = []; - -// const addLog = (message: string, status = 'normal') => -// logs.push({ createdAt: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'), message, nodeId: undefined, status }); - -// // FIXME: isMaintenanceOn - Column is used to check whether workflow is enabled or not. -// if (appVersion.app.isMaintenanceOn) { -// queue.push( -// ...this.computeNodesToBeExecuted(workflowExecution.startNode, workflowExecution.nodes, workflowExecution.edges) -// ); -// } else { -// addLog('Workflow is disabled', 'failure'); -// } - -// let executionFailed = false; -// while (queue.length != 0 && executionFailed === false) { -// const nodeToBeExecuted = queue.shift(); -// const currentNode = await this.workflowExecutionNodeRepository.findOne({ where: { id: nodeToBeExecuted.id } }); - -// const addLog = (message: string, queryName: string = undefined, status = 'normal') => -// logs.push({ -// createdAt: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'), -// message, -// nodeId: currentNode.idOnWorkflowDefinition, -// kind: currentNode?.definition?.kind ?? STATIC_NODE_TYPE_TO_HANDLE_MAPPING[currentNode.type], -// handle: queryName ? queryName : STATIC_NODE_TYPE_TO_HANDLE_MAPPING[currentNode.type], -// status, -// }); - -// const currentTime = moment(); -// const timeTaken = currentTime.diff(moment(workflowExecution.createdAt)); -// if (timeTaken / 1000 > this.workflowExecutionTimeout) { -// addLog('Execution stopped due to timeout', undefined, 'failure'); -// break; -// } - -// let { state } = await this.getStateAndPreviousNodesExecutionCompletionStatus(currentNode); - -// state = { constants: constantsObject, ...state }; - -// // eslint-disable-next-line no-empty -// if (currentNode.executed) { -// } else { -// switch (currentNode.type) { -// case 'input': { -// const { result } = await this.processStartNode(currentNode, params, addLog); - -// if (result?.status === 'failed') executionFailed = true; -// break; -// } - -// case 'query': { -// const { result } = await this.processQueryNode( -// currentNode, -// workflowExecution, -// appVersion, -// state, -// addLog, -// response, -// queue -// ); - -// if (result?.status === 'failed' && !currentNode.definition.errorHandler) executionFailed = true; -// break; -// } - -// case 'if-condition': { -// const { result } = await this.processIfConditionNode( -// currentNode, -// workflowExecution, -// appVersion, -// state, -// addLog, -// response, -// queue -// ); - -// if (result?.status === 'failed') executionFailed = true; -// break; -// } - -// case 'output': { -// const { result } = await this.processResponseNode( -// currentNode, -// workflowExecution, -// appVersion, -// state, -// addLog, -// response, -// queue -// ); - -// finalResult = result?.data ?? {}; - -// if (result?.status === 'failed') executionFailed = true; -// break; -// } -// } -// } -// } - -// await this.saveExecutionStatus({ workflowExecution, logs, executionFailed }); -// await this.markWorkflowAsExecuted(workflowExecution); -// await this.saveWorkflowLogs(workflowExecution, logs); - -// return finalResult; -// } - -// async processStartNode(node: WorkflowExecutionNode, params: object, addLog) { -// addLog('Execution started', undefined, 'success'); -// await this.completeNodeExecution(node, '', { startTrigger: { params } }); - -// const result: any = { status: 'ok' }; - -// return { result }; -// } - -// async processQueryNode( -// node: WorkflowExecutionNode, -// execution: WorkflowExecution, -// appVersion: AppVersion, -// state: object, -// basicAddLog: any, -// response: Response, -// queue: WorkflowExecutionNode[] -// ) { -// const queryId = find(appVersion.definition.queries, { -// idOnDefinition: node.definition.idOnDefinition, -// }).id; - -// const query = await this.dataQueriesService.findOne(queryId); - -// const addLog = (message, status = 'normal') => basicAddLog(message, query.name, status); - -// //* beta: workflow execution's environment is "development" by default -// const currentEnvironmentId = appVersion.currentEnvironmentId; - -// const user = await this.userRepository.findOne({ -// where: { -// id: execution.executingUserId, -// }, -// relations: ['organization'], -// }); -// user.organizationId = user.organization.id; - -// let result: any = {}; -// let handleToSkip = 'failure'; - -// try { -// addLog(`Started execution`); -// if (node.definition.looped) { -// const iterationValues = resolveCode({ code: node.definition?.iterationValuesCode, state, addLog }); - -// if (!Array.isArray(iterationValues)) throw new Error('Loop array did not resolve into an array'); - -// let index = 0; -// result = []; -// for (const value of iterationValues) { -// const currentTime = moment(); -// const timeTaken = currentTime.diff(moment(execution.createdAt)); -// if (timeTaken / 1000 > this.workflowExecutionTimeout) { -// throw new Error('Execution stopped due to timeout'); -// } -// const modifiedState = { ...state, value, index }; -// const options = getQueryVariables(query.options, modifiedState, addLog); -// result.push( -// query.kind === 'runjs' -// ? resolveCode({ code: query.options?.code, state: modifiedState, addLog }) -// : ( -// await this.dataQueriesService.runQuery( -// user, -// cloneDeep(query), -// options, -// response, -// currentEnvironmentId -// ) -// )['data'] -// ); - -// index++; -// } -// } else { -// const options = getQueryVariables(query.options, state, addLog); -// result = -// query.kind === 'runjs' -// ? resolveCode({ code: query.options?.code, state, addLog }) -// : (await this.dataQueriesService.runQuery(user, query, options, response, currentEnvironmentId))['data']; -// } - -// const decoratedResult = { status: 'ok', data: result }; - -// const newState = { -// ...state, -// [query.name]: decoratedResult, -// }; - -// addLog(`Execution succeeded`, 'success'); -// await this.completeNodeExecution(node, stringify(decoratedResult), newState); -// } catch (exception) { -// // if (exception instanceof TypeError) throw exception; -// addLog(`Execution failed: ${exception.message}`, 'failure'); -// result = { status: 'failed', exception }; - -// const newState = { -// ...state, -// [query.name]: result, -// }; - -// handleToSkip = 'success'; - -// await this.completeNodeExecution(node, stringify(result), newState); -// } - -// execution.edges -// .filter((edge) => edge.sourceWorkflowExecutionNodeId === node.id && edge.sourceHandle == handleToSkip) -// .forEach((edge) => (edge.skipped = true)); - -// queue.length = 0; -// queue.push(...this.computeNodesToBeExecuted(execution.startNode, execution.nodes, execution.edges)); - -// return { result }; -// } - -// async processIfConditionNode( -// currentNode: WorkflowExecutionNode, -// workflowExecution: WorkflowExecution, -// appVersion: AppVersion, -// state: object, -// addLog: any, -// response: Response, -// queue: WorkflowExecutionNode[] -// ) { -// const code = currentNode.definition?.code ?? ''; -// let sourceHandleToBeSkipped = 'false'; -// let result: any = {}; - -// try { -// result = { status: 'ok', data: resolveCode({ code, state, isIfCondition: true, addLog }) }; -// addLog('If condition evaluated to ' + result); -// sourceHandleToBeSkipped = result.data ? 'false' : 'true'; - -// await this.completeNodeExecution(currentNode, stringify(result), { ...state }); -// } catch (exception) { -// addLog(`Code within if condition failed: ${exception.message}`); -// result = { status: 'failed' }; -// await this.completeNodeExecution(currentNode, stringify(result), { ...state }); -// } - -// workflowExecution.edges -// .filter( -// (edge) => edge.sourceWorkflowExecutionNodeId === currentNode.id && edge.sourceHandle === sourceHandleToBeSkipped -// ) -// .forEach((edge) => (edge.skipped = true)); - -// queue.length = 0; -// queue.push( -// ...this.computeNodesToBeExecuted(workflowExecution.startNode, workflowExecution.nodes, workflowExecution.edges) -// ); - -// return { result }; -// } - -// async processResponseNode( -// currentNode: WorkflowExecutionNode, -// workflowExecution: WorkflowExecution, -// appVersion: AppVersion, -// state: object, -// addLog: any, -// response: Response, -// queue: WorkflowExecutionNode[] -// ) { -// const code = currentNode.definition?.code ?? ''; -// let result: any = {}; - -// try { -// result = { -// data: resolveCode({ -// code, -// state, -// isIfCondition: false, -// addLog, -// }), -// status: 'ok', -// }; -// await this.completeNodeExecution(currentNode, stringify(result), state); -// } catch (exception) { -// result = { status: 'failed' }; -// await this.completeNodeExecution(currentNode, stringify(result), state); -// } - -// return { result }; -// } - -// computeNodesToBeExecuted( -// currentNode: WorkflowExecutionNode, -// nodes: WorkflowExecutionNode[], -// edges: WorkflowExecutionEdge[] -// ) { -// const nodeIds = nodes.map((node) => node.id); -// const dag = new Graph({ directed: true }); - -// nodeIds.forEach((nodeId) => dag.setNode(nodeId)); - -// edges.forEach((edge) => { -// if (!edge.skipped) { -// dag.setEdge(edge.sourceWorkflowExecutionNodeId, edge.targetWorkflowExecutionNodeId); -// } -// }); - -// const sortedNodeIds = alg.topsort(dag); -// const traversedNodeIds = alg.postorder(dag, [currentNode.id]); - -// const orderedNodes = sortedNodeIds -// .filter((nodeId) => traversedNodeIds.includes(nodeId)) -// .map((id) => { -// return find(nodes, { id }); -// }); -// return orderedNodes; -// } - -// async completeNodeExecution(node: WorkflowExecutionNode, result: any, state: object) { -// await dbTransactionWrap(async (manager: EntityManager) => { -// await manager.update(WorkflowExecutionNode, node.id, { executed: true, result, state }); -// }); -// } - -// async markWorkflowAsExecuted(workflow: WorkflowExecution) { -// await dbTransactionWrap(async (manager: EntityManager) => { -// await manager.update(WorkflowExecution, workflow.id, { executed: true }); -// }); -// } - -// async saveWorkflowLogs(workflow: WorkflowExecution, logs: any[]) { -// await dbTransactionWrap(async (manager: EntityManager) => { -// await manager.update(WorkflowExecution, workflow.id, { logs }); -// }); -// } - -// async saveExecutionStatus({ workflowExecution, logs, executionFailed }) { -// await dbTransactionWrap(async (manager: EntityManager) => { -// const status = executionFailed ? 'failure' : 'success'; -// await manager.update(WorkflowExecution, workflowExecution.id, { logs, status }); -// }); -// } - -// async getStateAndPreviousNodesExecutionCompletionStatus(node: WorkflowExecutionNode) { -// const incomingEdges = await this.workflowExecutionEdgeRepository.find({ -// where: { -// targetWorkflowExecutionNodeId: node.id, -// }, -// relations: ['sourceWorkflowExecutionNode'], -// }); - -// const incomingNodes = await Promise.all(incomingEdges.map((edge) => edge.sourceWorkflowExecutionNode)); - -// const previousNodesExecutionCompletionStatus = !incomingNodes.map((node) => node.executed).includes(false); - -// const state = incomingNodes.reduce((existingState, node) => { -// const nodeState = node.state ?? {}; -// return { ...existingState, ...nodeState }; -// }, {}); - -// return { state, previousNodesExecutionCompletionStatus }; -// } - -// async forwardNodes( -// startNode: WorkflowExecutionNode, -// sourceHandle: string = undefined -// ): Promise { -// const forwardEdges = await this.workflowExecutionEdgeRepository.find({ -// where: { -// sourceWorkflowExecutionNode: startNode, -// ...(sourceHandle ? { sourceHandle } : {}), -// }, -// }); - -// const forwardNodeIds = forwardEdges.map((edge) => edge.targetWorkflowExecutionNodeId); - -// const forwardNodes = Promise.all( -// forwardNodeIds.map((id) => -// this.workflowExecutionNodeRepository.findOne({ -// where: { -// id, -// }, -// }) -// ) -// ); - -// return forwardNodes; -// } - -// async incomingNodes(startNode: WorkflowExecutionNode): Promise { -// const incomingEdges = await this.workflowExecutionEdgeRepository.find({ -// where: { -// targetWorkflowExecutionNode: startNode, -// }, -// }); - -// const incomingNodeIds = incomingEdges.map((edge) => edge.sourceWorkflowExecutionNodeId); - -// const receivedNodes = Promise.all( -// incomingNodeIds.map((id) => -// this.workflowExecutionNodeRepository.findOne({ -// where: { -// id, -// }, -// }) -// ) -// ); - -// return receivedNodes; -// } - -// async previewQueryNode( -// queryId: string, -// nodeId: string, -// state: object, -// appVersion: AppVersion, -// user: User, -// response: Response -// ): Promise { -// const query = await this.dataQueriesService.findOne(queryId); -// const node = find(appVersion.definition.nodes, { id: nodeId }); -// //* beta: workflow execution's environment is "development" by default -// const currentEnvironmentId = appVersion.currentEnvironmentId; - -// const organization: any = await dbTransactionWrap(async (manager: EntityManager) => { -// return manager -// .createQueryBuilder('organizations', 'organization') -// .innerJoin('apps', 'app', 'app.organization_id = organization.id') -// .innerJoin('app_versions', 'av', 'av.app_id = app.id') -// .where('av.id = :appVersionId', { appVersionId: appVersion.id }) -// .getOne(); -// }); - -// const constants = await this.organizationConstantsService.getConstantsForEnvironment( -// organization.id, -// currentEnvironmentId, -// OrganizationConstantType.GLOBAL -// ); - -// const constantsObject = Object.fromEntries(constants.map((constant) => [constant.name, constant.value])); - -// state = { ...state, constants: constantsObject }; - -// // const user = await this.userRepository.findOne(execution.executingUserId, { -// // relations: ['organization'], -// // }); -// // user.organizationId = user.organization.id; -// try { -// void getQueryVariables(query.options, state); -// } catch (e) { -// console.log({ e }); -// } - -// const startingTime = moment(); -// let result: any; -// const addLog = () => {}; -// try { -// if (node.data.looped) { -// const iterationValues = resolveCode({ code: node.data?.iterationValuesCode, state, addLog }); - -// if (!Array.isArray(iterationValues)) throw new Error('Loop array did not resolve into an array'); - -// let index = 0; -// result = []; - -// for (const value of iterationValues) { -// const currentTime = moment(); -// const timeTaken = currentTime.diff(startingTime); -// if (timeTaken / 1000 > this.workflowExecutionTimeout) { -// throw new Error('Execution stopped due to timeout'); -// } -// const modifiedState = { ...state, value, index }; -// const options = getQueryVariables(query.options, modifiedState, addLog); -// result.push( -// query.kind === 'runjs' -// ? resolveCode({ code: query.options?.code, state: modifiedState, addLog }) -// : ( -// await this.dataQueriesService.runQuery( -// user, -// cloneDeep(query), -// options, -// response, -// currentEnvironmentId -// ) -// )['data'] -// ); - -// index++; -// } - -// result = { status: 'ok', data: result }; -// } else { -// const options = getQueryVariables(query.options, state, addLog); -// result = -// query.kind === 'runjs' -// ? { status: 'ok', data: resolveCode({ code: query.options?.code, state, addLog }) } -// : await this.dataQueriesService.runQuery(user, query, options, response, currentEnvironmentId); -// } -// } catch (exception) { -// const result = { status: 'failed', exception }; - -// return result; -// } - -// return result; -// } - -// computeNodeHandle(nodeData, queries, queryIdsOnDefinitionToActualQueryIdMapping): string { -// switch (nodeData.type) { -// case 'query': { -// return find(queries, { id: queryIdsOnDefinitionToActualQueryIdMapping[nodeData.data.idOnDefinition] }).name; -// } -// default: -// return STATIC_NODE_TYPE_TO_HANDLE_MAPPING[nodeData.type]; -// } -// } - -// async canExecuteWorkflow(organizationId: string): Promise<{ allowed: boolean; message: string }> { -// if (!organizationId) { -// return { allowed: false, message: 'WorkspaceId is missing' }; -// } - -// const workflowsLimit = await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.WORKFLOWS); -// if (!workflowsLimit?.workspace || !workflowsLimit?.instance) { -// return { allowed: false, message: 'Workflow is not enabled in the license, contact admin' }; -// } - -// return await dbTransactionWrap(async (manager) => { -// const dailyWorkspaceCount = ( -// await manager.query( -// `SELECT COUNT(*) -// FROM apps a -// INNER JOIN app_versions av on av.app_id = a.id -// INNER JOIN workflow_executions we on we.app_version_id = av.id -// WHERE a.organization_id = $1 -// AND extract (year from we.created_at) = extract (year from current_date) -// AND extract (month from we.created_at) = extract (month from current_date) -// AND DATE(we.created_at) = current_date`, -// [organizationId] -// ) -// )[0].count; - -// if ( -// workflowsLimit.workspace.daily_executions !== LICENSE_LIMIT.UNLIMITED && -// dailyWorkspaceCount >= workflowsLimit.workspace.daily_executions -// ) { -// return { -// allowed: false, -// message: 'Maximum daily limit for workflow execution has been reached for this workspace', -// }; -// } - -// // Workspace Level - Monthly Limit -// const monthlyWorkspaceCount = ( -// await manager.query( -// `SELECT COUNT(*) -// FROM apps a -// INNER JOIN app_versions av on av.app_id = a.id -// INNER JOIN workflow_executions we on we.app_version_id = av.id -// WHERE a.organization_id = $1 -// AND extract (year from we.created_at) = extract (year from current_date) -// AND extract (month from we.created_at) = extract (month from current_date)`, -// [organizationId] -// ) -// )[0].count; - -// if ( -// workflowsLimit.workspace.monthly_executions !== LICENSE_LIMIT.UNLIMITED && -// monthlyWorkspaceCount >= workflowsLimit.workspace.monthly_executions -// ) { -// return { -// allowed: false, -// message: 'Maximum monthly limit for workflow execution has been reached for this workspace', -// }; -// } - -// // Instance Level - Daily Limit -// const dailyInstanceCount = ( -// await manager.query( -// `SELECT COUNT(*) -// FROM apps a -// INNER JOIN app_versions av on av.app_id = a.id -// INNER JOIN workflow_executions we on we.app_version_id = av.id -// WHERE extract (year from we.created_at) = extract (year from current_date) -// AND extract (month from we.created_at) = extract (month from current_date) -// AND DATE(we.created_at) = current_date` -// ) -// )[0].count; - -// if ( -// workflowsLimit.instance.daily_executions !== LICENSE_LIMIT.UNLIMITED && -// dailyInstanceCount >= workflowsLimit.instance.daily_executions -// ) { -// return { allowed: false, message: 'Maximum daily limit for workflow execution has been reached' }; -// } - -// // Instance Level - Monthly Limit -// const monthlyInstanceCount = ( -// await manager.query( -// `SELECT COUNT(*) -// FROM apps a -// INNER JOIN app_versions av on av.app_id = a.id -// INNER JOIN workflow_executions we on we.app_version_id = av.id -// WHERE extract (year from we.created_at) = extract (year from current_date) -// AND extract (month from we.created_at) = extract (month from current_date)` -// ) -// )[0].count; - -// if ( -// workflowsLimit.instance.monthly_executions !== LICENSE_LIMIT.UNLIMITED && -// monthlyInstanceCount >= workflowsLimit.instance.monthly_executions -// ) { -// return { allowed: false, message: 'Maximum monthly limit for workflow execution has been reached' }; -// } - -// return { allowed: true, message: 'Workflow execution allowed' }; -// }); -// } -// // Workspace Level - Daily Limit -// } diff --git a/server/src/services/ignored/workflow_schedules.service.ts b/server/src/services/ignored/workflow_schedules.service.ts deleted file mode 100644 index cd1f458e43..0000000000 --- a/server/src/services/ignored/workflow_schedules.service.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { WorkflowSchedule } from '../entities/workflow_schedule.entity'; -import { AppVersion } from '../entities/app_version.entity'; - -@Injectable() -export class WorkflowSchedulesService { - constructor( - @InjectRepository(WorkflowSchedule) - private workflowSchedulesRepository: Repository - ) {} - - async create(createWorkflowScheduleDto: { - workflowId: string; - active: boolean; - environmentId: string; - type: string; - timezone: string; - details: any; - }): Promise { - const { workflowId, active, environmentId, type, timezone, details } = createWorkflowScheduleDto; - const scheduleOptions = this.workflowSchedulesRepository.create({ - workflow: { id: workflowId } as AppVersion, - active, - environmentId, - type, - timezone, - details, - }); - const workflowSchedule = await this.workflowSchedulesRepository.save(scheduleOptions); - - return workflowSchedule; - } - - async findOne(id: string): Promise { - const workflowSchedule = await this.workflowSchedulesRepository.findOne({ - where: { id }, - relations: ['workflow'], - }); - if (!workflowSchedule) { - throw new NotFoundException(`WorkflowSchedule with ID ${id} not found`); - } - return workflowSchedule; - } - - async findAll(appVersionId: string): Promise { - return await this.workflowSchedulesRepository.find({ - where: { workflowId: appVersionId }, - }); - } - - async update( - id: string, - updateWorkflowScheduleDto: Partial<{ - active: boolean; - environmentId: string; - type: string; - timezone: string; - details: any; - }> - ): Promise { - const workflowSchedule = await this.findOne(id); - Object.assign(workflowSchedule, updateWorkflowScheduleDto); - return await this.workflowSchedulesRepository.save(workflowSchedule); - } - - async remove(id: string): Promise { - const result = await this.workflowSchedulesRepository.delete(id); - if (result.affected === 0) { - throw new NotFoundException(`WorkflowSchedule with ID ${id} not found`); - } - } -} diff --git a/server/src/services/ignored/workflow_webhooks.service.ts b/server/src/services/ignored/workflow_webhooks.service.ts deleted file mode 100644 index 654cd879c3..0000000000 --- a/server/src/services/ignored/workflow_webhooks.service.ts +++ /dev/null @@ -1,121 +0,0 @@ -// import { BadRequestException, HttpException, HttpStatus, Injectable, NotFoundException } from '@nestjs/common'; -// import { EventEmitter2 } from '@nestjs/event-emitter'; -// import { DataSource, EntityManager } from 'typeorm'; -// import { App } from 'src/entities/app.entity'; -// import { AppEnvironment } from 'src/entities/app_environments.entity'; -// import { AppVersion } from 'src/entities/app_version.entity'; -// import { v4 as uuidv4 } from 'uuid'; -// import { WorkflowExecutionsService } from '@services/workflow_executions.service'; - -// @Injectable() -// export class WorkflowWebhooksService { -// constructor( -// private readonly manager: EntityManager, -// private eventEmitter: EventEmitter2, -// private workflowExecutionsService: WorkflowExecutionsService, -// private readonly _dataSource: DataSource -// ) {} - -// async triggerWorkflow(workflowApps, workflowParams, environment, response) { -// // When workflow version is introduced - Query needs to be tweaked -// const appVersion = await this.manager -// .createQueryBuilder(AppVersion, 'av') -// .select(['av.definition']) -// .innerJoinAndSelect(App, 'a', 'av.appId = a.id') -// .where('av.appId = :id', { id: workflowApps.appId }) -// .getOne(); - -// const app = await this.manager -// .createQueryBuilder(App, 'app') -// .where('app.id = :id', { id: workflowApps.appId }) -// .getOne(); -// const enabled = app.isMaintenanceOn; - -// if (!enabled) throw new HttpException('Forbidden', HttpStatus.FORBIDDEN); - -// // Type validation for input values passed. -// const inputValidators = appVersion?.definition?.webhookParams ?? []; -// if (inputValidators.length) { -// const inputParamsSet = new Set(); -// Object.entries(workflowParams).forEach(([key, _value]) => { -// inputParamsSet.add(key); -// }); - -// inputValidators.forEach((validator: { key: string; dataType: string }) => { -// if (!inputParamsSet.has(validator.key)) throw new BadRequestException(`Params - ${validator.key} is missing`); -// }); -// } - -// const sanitisedWorkflowParams = {}; -// inputValidators.length && -// Object.entries(workflowParams).forEach(([key, value]) => { -// const condition = inputValidators.find((input) => input.key == key); -// if (condition) { -// const isValidType = this.isValidateInputTypes(value, condition.dataType); -// if (!isValidType) throw new BadRequestException(`${key} has incorrect datatype`); -// if (isValidType) sanitisedWorkflowParams[key] = value; -// } -// }); - -// const environmentDetails = await this.manager -// .createQueryBuilder(App, 'apps') -// .leftJoinAndSelect(AppEnvironment, 'ae', 'ae.organizationId = apps.organizationId') -// .where('apps.id = :id and ae.name = :envName', { id: workflowApps.appId, envName: environment }) -// .select(['apps.id', 'ae.id']) -// .execute(); - -// if (!environmentDetails.length) throw new HttpException('Invalid environment', 404); -// const webhookEnvironmentId = environmentDetails[0]?.ae_id ?? ''; - -// const workflowExecution = await this.workflowExecutionsService.create(workflowApps); -// const result = await this.workflowExecutionsService.execute( -// workflowExecution, -// sanitisedWorkflowParams, -// webhookEnvironmentId, -// response -// ); -// return result; -// } - -// async updateWorkflow(workflowId, workflowValuesToUpdate) { -// if (Object.keys(workflowValuesToUpdate).length === 0) throw new BadRequestException('Values to update is empty'); -// if (!workflowId) throw new BadRequestException('Invalid workflowId'); -// const { isEnable } = workflowValuesToUpdate; -// const workflowApps = await this._dataSource -// .getRepository(App) -// .createQueryBuilder('apps') -// .where('apps.id = :id', { id: workflowId }) -// .getOne(); - -// if (!workflowApps) throw new NotFoundException("Workflow doesn't exist"); - -// return this._dataSource -// .createQueryBuilder() -// .update(App) -// .set({ -// workflowEnabled: isEnable === 'endPointTrigger', -// ...(!workflowApps?.workflowApiToken && { workflowApiToken: uuidv4() }), -// }) -// .where('id = :id', { id: workflowId }) -// .execute(); -// } - -// private isValidateInputTypes(value, type) { -// switch (type) { -// case 'string': -// return typeof value == 'string'; -// case 'number': -// return typeof value == 'number'; -// case 'array': -// return Array.isArray(value); -// case 'object': -// return typeof value == 'object'; -// case 'boolean': -// return typeof value == 'boolean'; -// case 'null': -// return value == null; -// default: -// return false; -// } -// } -// }