diff --git a/.github/workflows/cloud-frontend.yml b/.github/workflows/cloud-frontend.yml
index 9d0fda1e30..71e100c52d 100644
--- a/.github/workflows/cloud-frontend.yml
+++ b/.github/workflows/cloud-frontend.yml
@@ -110,6 +110,7 @@ jobs:
TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_TJDB_SQL_MODE_DISABLE }}
TOOLJET_SERVER_URL: ${{ secrets.CLOUD_TOOLJET_SERVER_URL }}
TOOLJET_EDITION: cloud
+ WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/ai-create-account
- name: 🚀 Deploy to Netlify
run: |
@@ -128,4 +129,5 @@ jobs:
SERVER_IP: ${{ secrets.CLOUD_SERVER_IP }}
TJDB_SQL_MODE_DISABLE: ${{ secrets.CLOUD_TJDB_SQL_MODE_DISABLE }}
TOOLJET_SERVER_URL: ${{ secrets.CLOUD_TOOLJET_SERVER_URL }}
+ WEBSITE_SIGNUP_URL: https://website-stage.tooljet.ai/ai-create-account
TOOLJET_EDITION: cloud
diff --git a/.github/workflows/merging-pr.yml b/.github/workflows/merging-pr.yml
index f091baf0ec..63c76b46a3 100644
--- a/.github/workflows/merging-pr.yml
+++ b/.github/workflows/merging-pr.yml
@@ -47,7 +47,6 @@ jobs:
- name: Checkout base repo
uses: actions/checkout@v4
with:
- repository: ToolJet/ToolJet
token: ${{ secrets.TOKEN_PR }}
ref: main
submodules: recursive
@@ -63,13 +62,30 @@ jobs:
git add frontend/ee server/ee
if git diff --cached --quiet; then
- echo "No submodule updates found."
- else
- git commit -m "🔄 chore: update submodules to latest main after auto-merge"
- git push origin main
+ echo "No submodule updates found." && exit 0
fi
env:
- GH_TOKEN: ${{ secrets.TOKEN_PR }}
+ GH_TOKEN: ${{ secrets.TOKEN_PR }}
+
+ - name: Create PR for submodule update
+ id: cpr
+ uses: peter-evans/create-pull-request@v6
+ with:
+ token: ${{ secrets.TOKEN_PR }}
+ commit-message: "🚀 chore: update submodules to latest main after auto-merge"
+ title: "🚀 chore: update submodules"
+ body: "Auto-generated PR to update submodules after base PR merge"
+ branch: auto/update-submodules-${{ github.run_id }}
+ base: main
+
+ - name: Auto-merge PR
+ if: steps.cpr.outputs.pull-request-number != ''
+ run: |
+ echo "Merging submodule update PR #${PR_NUMBER}"
+ gh pr merge --squash --admin "$PR_NUMBER" --repo ToolJet/ToolJet
+ env:
+ GH_TOKEN: ${{ secrets.TOKEN_PR }}
+ PR_NUMBER: ${{ steps.cpr.outputs.pull-request-number }}
check-submodule-prs:
if: github.event.action == 'labeled' && github.event.label.name == 'ready-to-merge'
diff --git a/.nvmrc b/.nvmrc
index 68c98aa7a7..16a4acdae1 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-v18.18.2
\ No newline at end of file
+v22.15.1
\ No newline at end of file
diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js
index cb3575906a..6b1954140a 100644
--- a/cypress-tests/cypress-platform.config.js
+++ b/cypress-tests/cypress-platform.config.js
@@ -98,12 +98,8 @@ module.exports = defineConfig({
configFile: environment.configFile,
specPattern: [
"cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js",
- // Exclude specific files from ceTestcases/apps and ceTestcases/workspace
"cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js",
- "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug|appImport|privateAndpublicApps|version).cy.js",
- // Exclude workspaceConstants.cy.js explicitly
- "cypress/e2e/happyPath/platform/ceTestcases/workspace/!(*groupDuplication|workspaceConstants).cy.js",
- "!cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js",
+ "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug).cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js",
],
numTestsKeptInMemory: 1,
diff --git a/cypress-tests/cypress.Dockerfile b/cypress-tests/cypress.Dockerfile
index 705ae6bea6..3537f4fa7d 100644
--- a/cypress-tests/cypress.Dockerfile
+++ b/cypress-tests/cypress.Dockerfile
@@ -22,7 +22,7 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch
-RUN git submodule foreach 'git checkout main'
+RUN git submodule foreach 'git checkout ${BRANCH_NAME}'
# Scripts for building
COPY ./package.json ./package.json
diff --git a/cypress-tests/cypress/constants/selectors/common.js b/cypress-tests/cypress/constants/selectors/common.js
index ca0367ba44..464d0c3cf1 100644
--- a/cypress-tests/cypress/constants/selectors/common.js
+++ b/cypress-tests/cypress/constants/selectors/common.js
@@ -288,6 +288,7 @@ export const commonSelectors = {
labelFieldAlert: (fieldName) => {
return `[data-cy="${cyParamName(fieldName)}-is-required-field-alert-text"]`;
},
+ pageLogo: '[data-cy="page-logo"]',
};
export const commonWidgetSelector = {
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js
index f763211bea..171f5fc4b0 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/apps/privateAndpublicApps.cy.js
@@ -100,7 +100,7 @@ describe(
);
// Test public access
- cy.get(commonSelectors.viewerPageLogo).click();
+ // cy.get(commonSelectors.viewerPageLogo).click();
cy.openApp(
"appSlug",
Cypress.env("workspaceId"),
@@ -152,7 +152,7 @@ describe(
"be.visible"
);
- cy.get(commonSelectors.viewerPageLogo).click();
+ // cy.get(commonSelectors.viewerPageLogo).click();
// Test public access
cy.defaultWorkspaceLogin();
@@ -258,7 +258,9 @@ describe(
"be.visible"
);
- cy.get('[data-cy="viewer-page-logo"]').click();
+ // cy.get('[data-cy="viewer-page-logo"]').click();
+ cy.visit("/my-workspace");
+ cy.wait(2000);
logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should(
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js
index cc0cee6f6d..7ddd4e937d 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/dataSources/dataSourcePermissions.cy.js
@@ -47,8 +47,8 @@ describe("Datasource Manager", () => {
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName2 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
- const allDataSources = host.includes("8082") ? "All data sources (43)" : "All data sources (45)";
- const allDatabase = host.includes("8082") ? "Databases (18)" : "Databases (20)";
+ const allDataSources = host.includes("8082") ? "All data sources (45)" : "All data sources (45)";
+ const allDatabase = host.includes("8082") ? "Databases (20)" : "Databases (20)";
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(commonSelectors.pageSectionHeader).verifyVisibleElement(
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js
index ddd3f3d91e..33d377459b 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js
@@ -39,6 +39,8 @@ describe("Workspace constants", () => {
beforeEach(() => {
cy.defaultWorkspaceLogin();
cy.skipWalkthrough();
+ cy.viewport(1800, 1800);
+
});
it("Verify workspace constants UI and CRUD operations", () => {
@@ -66,12 +68,11 @@ describe("Workspace constants", () => {
});
});
- it("Verify global and secret constants in the editor, inspector, data sources, static queries, query preview, and preview", () => {
+ it.only("Verify global and secret constants in the editor, inspector, data sources, static queries, query preview, and preview", () => {
data.workspaceName = fake.firstName;
data.workspaceSlug = fake.firstName.toLowerCase().replace(/[^A-Za-z]/g, "");
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.visit(data.workspaceSlug);
- cy.viewport(1440, 960);
data.appName = `${fake.companyName}-App`;
// create global constants
@@ -102,8 +103,8 @@ describe("Workspace constants", () => {
.eq(0)
.selectFile('cypress/fixtures/templates/workspace_constants.json', { force: true });
cy.get(importSelectors.importAppButton).click();
- cy.wait(5000);
-
+ cy.wait(6000);
+ cy.get(commonWidgetSelector.draggableWidget('textinput1')).should('be.visible');
//Verify global constant value is resolved in component
cy.get(commonWidgetSelector.draggableWidget('textinput1'))
.verifyVisibleElement("have.value", "customHeader");
@@ -115,9 +116,10 @@ describe("Workspace constants", () => {
cy.get(commonWidgetSelector.alertInfoText).contains(
"secrets cannot be used in apps"
);
-
//Verify all static and datasource queries output in components
+ cy.wait(8000);
for (let i = 3; i <= 16; i++) {
+ cy.wait(1000);
cy.log("Verifying textinput" + i);
cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`))
.verifyVisibleElement("have.value", "Production environment testing");
@@ -151,16 +153,20 @@ describe("Workspace constants", () => {
//Preview app and verify components
cy.openInCurrentTab(commonWidgetSelector.previewButton);
- cy.wait(6000);
- for (let i = 3; i <= 16; i++) {
+ cy.wait(8000);
+ cy.get(commonWidgetSelector.draggableWidget('textinput1')).should('be.visible');
+ for (let i = 16; i >= 3; i--) {
+ cy.wait(1000);
+ cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`)).should('be.visible');
cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`))
- .verifyVisibleElement("have.value", "Production environment testing");
+ .verifyVisibleElement("have.value", "Production environment testing", { timeout: 10000 });
}
- //back to dashboard and open app again
- cy.get(commonSelectors.viewerPageLogo).click();
- cy.wait(2000);
+
+ cy.visit('/');
+ cy.wait(4000);
cy.get(commonSelectors.appEditButton).click({ force: true });
+ cy.wait(4000);
cy.releaseApp();
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js
index fb8e932973..dc0ba39e61 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js
@@ -24,6 +24,7 @@ import { logout } from "Support/utils/common";
describe("dashboard", () => {
let data = {};
+
beforeEach(() => {
data = {
appName: `${fake.companyName}-App`,
@@ -44,164 +45,6 @@ describe("dashboard", () => {
cy.visit(`${data.workspaceSlug}`);
});
- // it("Should verify app card elements and app card operations", () => {
- // const customLayout = {
- // desktop: { top: 100, left: 20 },
- // mobile: { width: 8, height: 50 },
- // };
-
- // cy.apiCreateApp(data.appName);
- // cy.visit(`${data.workspaceSlug}`);
-
- // cy.wait(2000);
- // cy.get(commonSelectors.appCreationDetails).should("be.visible");
- // cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
- // cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
- // "have.text",
- // data.appName
- // );
-
- // viewAppCardOptions(data.appName);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.changeIconOption)
- // ).verifyVisibleElement("have.text", commonText.changeIconOption);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.addToFolderOption)
- // ).verifyVisibleElement("have.text", commonText.addToFolderOption);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.cloneAppOption)
- // ).verifyVisibleElement("have.text", commonText.cloneAppOption);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.exportAppOption)
- // ).verifyVisibleElement("have.text", commonText.exportAppOption);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.deleteAppOption)
- // ).verifyVisibleElement("have.text", commonText.deleteAppOption);
-
- // modifyAndVerifyAppCardIcon(data.appName);
- // createFolder(data.folderName);
-
- // viewAppCardOptions(data.appName);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.addToFolderOption)
- // ).click();
- // verifyModal(
- // dashboardText.addToFolderTitle,
- // dashboardText.addToFolderButton,
- // dashboardSelector.selectFolder
- // );
- // cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
- // "have.text",
- // dashboardText.moveAppText(data.appName)
- // );
-
- // cy.get(dashboardSelector.selectFolder).click();
- // cy.get(commonSelectors.folderList).contains(data.folderName).click();
- // cy.get(dashboardSelector.addToFolderButton).click();
- // cy.verifyToastMessage(
- // commonSelectors.toastMessage,
- // commonText.AddedToFolderToast,
- // false
- // );
-
- // cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
- // "have.text",
- // dashboardText.folderName(`${data.folderName} (1)`)
- // );
-
- // cy.get(dashboardSelector.folderName(data.folderName)).click();
- // cy.get(commonSelectors.appCard(data.appName))
- // .contains(data.appName)
- // .should("be.visible");
-
- // viewAppCardOptions(data.appName);
-
- // cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
- // .verifyVisibleElement("have.text", commonText.removeFromFolderOption)
- // .click();
- // verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
-
- // cancelModal(commonText.cancelButton);
-
- // viewAppCardOptions(data.appName);
- // cy.get(
- // commonSelectors.appCardOptions(commonText.removeFromFolderOption)
- // ).click();
- // cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
- // cy.verifyToastMessage(
- // commonSelectors.toastMessage,
- // commonText.appRemovedFromFolderTaost,
- // false
- // );
- // cy.get(commonSelectors.modalComponent).should("not.exist");
- // cy.get(commonSelectors.empytyFolderImage).should("be.visible");
- // cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
- // "have.text",
- // commonText.emptyFolderText
- // );
- // cy.get(commonSelectors.allApplicationsLink).click();
- // deleteFolder(data.folderName);
-
- // cy.get(commonSelectors.allApplicationsLink).click();
-
- // cy.wait(1000);
- // viewAppCardOptions(data.appName);
- // cy.wait(2000);
- // cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
- // cy.get(commonSelectors.exportAllButton).click();
-
- // cy.exec("ls ./cypress/downloads/").then((result) => {
- // const downloadedAppExportFileName = result.stdout.split("\n")[0];
- // expect(downloadedAppExportFileName).to.contain.string("app");
- // });
-
- // viewAppCardOptions(data.appName);
- // cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
- // cy.get('[data-cy="clone-app"]').click();
- // cy.get(".go3958317564")
- // .should("be.visible")
- // .and("have.text", dashboardText.appClonedToast);
- // cy.wait(3000);
-
- // cy.renameApp(data.cloneAppName);
- // cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
- // cy.backToApps();
- // cy.wait("@appLibrary");
- // cy.wait(1000);
-
- // cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
-
- // cy.wait(1000);
-
- // viewAppCardOptions(data.cloneAppName);
- // cy.get(commonSelectors.deleteAppOption).click();
- // cy.get(commonSelectors.modalMessage).verifyVisibleElement(
- // "have.text",
- // commonText.deleteAppModalMessage(data.cloneAppName)
- // );
- // cy.get(
- // commonSelectors.buttonSelector(commonText.cancelButton)
- // ).verifyVisibleElement("have.text", commonText.cancelButton);
- // cy.get(
- // commonSelectors.buttonSelector(commonText.modalYesButton)
- // ).verifyVisibleElement("have.text", commonText.modalYesButton);
- // cancelModal(commonText.cancelButton);
-
- // viewAppCardOptions(data.cloneAppName);
- // cy.get(commonSelectors.deleteAppOption).click();
- // cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
- // cy.verifyToastMessage(
- // commonSelectors.toastMessage,
- // commonText.appDeletedToast,
- // false
- // );
- // verifyAppDelete(data.cloneAppName);
- // cy.wait("@appLibrary");
-
- // cy.deleteApp(data.appName);
- // verifyAppDelete(data.appName);
- // });
-
it("should verify the elements on empty dashboard", () => {
cy.intercept("GET", "/api/metadata", {
body: {
@@ -259,9 +102,6 @@ describe("dashboard", () => {
.should("have.attr", "class")
.and("contain", "theme-dark");
cy.get(dashboardSelector.modeToggle).click();
- cy.get(dashboardSelector.homePageContent)
- .should("have.attr", "class")
- .and("contain", "bg-light-gray");
cy.wait(500);
cy.get(commonSelectors.settingsIcon).click();
@@ -329,6 +169,169 @@ describe("dashboard", () => {
verifyTooltip(dashboardSelector.modeToggle, "Mode");
});
+ it("Should verify app card elements and app card operations", () => {
+ cy.exec("mkdir -p ./cypress/downloads/");
+ cy.exec("cd ./cypress/downloads/ && rm -rf *");
+
+ const customLayout = {
+ desktop: { top: 100, left: 20 },
+ mobile: { width: 8, height: 50 },
+ };
+
+ cy.apiCreateApp(data.appName);
+ cy.visit(`${data.workspaceSlug}`);
+
+ cy.wait(2000);
+ cy.get(commonSelectors.appCreationDetails).should("be.visible");
+ cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
+ cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
+ "have.text",
+ data.appName
+ );
+
+ viewAppCardOptions(data.appName);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.changeIconOption)
+ ).verifyVisibleElement("have.text", commonText.changeIconOption);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.addToFolderOption)
+ ).verifyVisibleElement("have.text", commonText.addToFolderOption);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.cloneAppOption)
+ ).verifyVisibleElement("have.text", commonText.cloneAppOption);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.exportAppOption)
+ ).verifyVisibleElement("have.text", commonText.exportAppOption);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.deleteAppOption)
+ ).verifyVisibleElement("have.text", commonText.deleteAppOption);
+
+ modifyAndVerifyAppCardIcon(data.appName);
+ createFolder(data.folderName);
+
+ viewAppCardOptions(data.appName);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.addToFolderOption)
+ ).click();
+ verifyModal(
+ dashboardText.addToFolderTitle,
+ dashboardText.addToFolderButton,
+ dashboardSelector.selectFolder
+ );
+ cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
+ "have.text",
+ dashboardText.moveAppText(data.appName)
+ );
+
+ cy.get(dashboardSelector.selectFolder).click();
+ cy.get(commonSelectors.folderList).contains(data.folderName).click();
+ cy.get(dashboardSelector.addToFolderButton).click();
+ cy.verifyToastMessage(
+ commonSelectors.toastMessage,
+ commonText.AddedToFolderToast,
+ false
+ );
+
+ cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
+ "have.text",
+ dashboardText.folderName(`${data.folderName} (1)`)
+ );
+
+ cy.get(dashboardSelector.folderName(data.folderName)).click();
+ cy.get(commonSelectors.appCard(data.appName))
+ .contains(data.appName)
+ .should("be.visible");
+
+ viewAppCardOptions(data.appName);
+
+ cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
+ .verifyVisibleElement("have.text", commonText.removeFromFolderOption)
+ .click();
+ verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
+
+ cancelModal(commonText.cancelButton);
+
+ viewAppCardOptions(data.appName);
+ cy.get(
+ commonSelectors.appCardOptions(commonText.removeFromFolderOption)
+ ).click();
+ cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
+ cy.verifyToastMessage(
+ commonSelectors.toastMessage,
+ commonText.appRemovedFromFolderTaost,
+ false
+ );
+ cy.get(commonSelectors.modalComponent).should("not.exist");
+ cy.get(commonSelectors.empytyFolderImage).should("be.visible");
+ cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
+ "have.text",
+ commonText.emptyFolderText
+ );
+ cy.get(commonSelectors.allApplicationsLink).click();
+ deleteFolder(data.folderName);
+
+ cy.get(commonSelectors.allApplicationsLink).click();
+
+ cy.wait(1000);
+ viewAppCardOptions(data.appName);
+ cy.wait(2000);
+ cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
+ cy.get(commonSelectors.exportAllButton).click();
+
+
+ cy.exec("ls ./cypress/downloads/").then((result) => {
+ const downloadedAppExportFileName = result.stdout.split("\n")[0];
+ expect(downloadedAppExportFileName).to.contain.string("app");
+ });
+
+ viewAppCardOptions(data.appName);
+ cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
+ cy.get('[data-cy="clone-app"]').click();
+ cy.get(".go3958317564")
+ .should("be.visible")
+ .and("have.text", dashboardText.appClonedToast);
+ cy.wait(3000);
+
+ cy.renameApp(data.cloneAppName);
+ cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
+ cy.backToApps();
+ cy.wait("@appLibrary");
+ cy.wait(1000);
+
+ cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
+
+ cy.wait(1000);
+
+ viewAppCardOptions(data.cloneAppName);
+ cy.get(commonSelectors.deleteAppOption).click();
+ cy.get(commonSelectors.modalMessage).verifyVisibleElement(
+ "have.text",
+ commonText.deleteAppModalMessage(data.cloneAppName)
+ );
+ cy.get(
+ commonSelectors.buttonSelector(commonText.cancelButton)
+ ).verifyVisibleElement("have.text", commonText.cancelButton);
+ cy.get(
+ commonSelectors.buttonSelector(commonText.modalYesButton)
+ ).verifyVisibleElement("have.text", commonText.modalYesButton);
+ cancelModal(commonText.cancelButton);
+
+ viewAppCardOptions(data.cloneAppName);
+ cy.get(commonSelectors.deleteAppOption).click();
+ cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
+ cy.verifyToastMessage(
+ commonSelectors.toastMessage,
+ commonText.appDeletedToast,
+ false
+ );
+
+ cy.get(commonSelectors.appCard(data.cloneAppName)).should("not.exist");
+ cy.wait("@appLibrary");
+
+ cy.deleteApp(data.appName);
+ cy.get(commonSelectors.appCard(data.appName)).should("not.exist");
+ });
+
it("Should verify the app CRUD operation", () => {
const customLayout = {
desktop: { top: 100, left: 20 },
@@ -353,7 +356,7 @@ describe("dashboard", () => {
cy.deleteApp(data.appName);
- verifyAppDelete(data.appName);
+ cy.get(commonSelectors.appCard(data.appName)).should("not.exist");
});
it("Should verify the folder CRUD operation", () => {
@@ -474,7 +477,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.allApplicationsLink).click();
cy.deleteApp(data.appName);
- verifyAppDelete(data.appName);
+ cy.get(commonSelectors.appCard(data.appName)).should("not.exist");
logout();
});
});
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js
index 96b87dd9b7..681a2d93e6 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/basicPermissions.cy.js
@@ -76,11 +76,11 @@ describe("Manage Groups", () => {
// App operations
cy.createApp(data.appName);
- cy.verifyToastMessage(
- commonSelectors.toastMessage,
- commonText.appCreatedToast,
- false
- );
+ // cy.verifyToastMessage(
+ // commonSelectors.toastMessage,
+ // commonText.appCreatedToast,
+ // false
+ // );
cy.backToApps();
cy.deleteApp(data.appName);
@@ -178,11 +178,11 @@ describe("Manage Groups", () => {
// App operations
cy.createApp(data.appName);
- cy.verifyToastMessage(
- commonSelectors.toastMessage,
- commonText.appCreatedToast,
- false
- );
+ // cy.verifyToastMessage(
+ // commonSelectors.toastMessage,
+ // commonText.appCreatedToast,
+ // false
+ // );
cy.backToApps();
cy.deleteApp(data.appName);
diff --git a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js
index bfa5806939..e9a4f5c222 100644
--- a/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js
+++ b/cypress-tests/cypress/e2e/happyPath/platform/commonTestcases/workspace/groups/permissions.cy.js
@@ -196,10 +196,10 @@ describe("Manage Groups", () => {
// App operations
cy.createApp(data.appName);
- cy.verifyToastMessage(
- commonSelectors.toastMessage,
- commonText.appCreatedToast
- );
+ // cy.verifyToastMessage(
+ // commonSelectors.toastMessage,
+ // commonText.appCreatedToast
+ // );
cy.backToApps();
cy.wait(2500);
diff --git a/cypress-tests/cypress/support/utils/version.js b/cypress-tests/cypress/support/utils/version.js
index c6d79853fb..ffb1ad32a0 100644
--- a/cypress-tests/cypress/support/utils/version.js
+++ b/cypress-tests/cypress/support/utils/version.js
@@ -150,7 +150,8 @@ export const verifyVersionAfterPreview = (currentVersion) => {
cy.wait(2000);
cy.get('[data-cy^="draggable-widget-table"]').should("be.visible");
cy.url().should("include", `version=${currentVersion}`);
- cy.get('[data-cy="viewer-page-logo"]').click();
+ // cy.get('[data-cy="viewer-page-logo"]').click();
+ cy.go("back");
cy.wait(8000);
};
diff --git a/docker/cloud/cloud-production.Dockerfile b/docker/cloud/cloud-production.Dockerfile
index d55671a5be..f3e1f09d0b 100644
--- a/docker/cloud/cloud-production.Dockerfile
+++ b/docker/cloud/cloud-production.Dockerfile
@@ -26,7 +26,14 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch
-RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true'
+RUN git submodule foreach " \
+ if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
+ git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
+ git checkout ${BRANCH_NAME}; \
+ else \
+ echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to main'; \
+ git checkout main; \
+ fi"
# Scripts for building
COPY ./package.json ./package.json
@@ -118,7 +125,7 @@ COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
-COPY --from=builder /app/server/src/assets ./app/server/src/assets
+COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
diff --git a/docker/cloud/cloud-server.Dockerfile b/docker/cloud/cloud-server.Dockerfile
index 2259c8cecb..ce591f0898 100644
--- a/docker/cloud/cloud-server.Dockerfile
+++ b/docker/cloud/cloud-server.Dockerfile
@@ -27,8 +27,17 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
# Checkout the same branch in submodules if it exists, otherwise stay on default branch
-RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true'
+RUN git submodule foreach " \
+ if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
+ git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
+ git checkout ${BRANCH_NAME}; \
+ else \
+ echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to main'; \
+ git checkout main; \
+ fi"
+
+# Scripts for building
COPY ./package.json ./package.json
# Building ToolJet plugins
@@ -104,7 +113,7 @@ COPY --from=builder /app/server/node_modules ./app/server/node_modules
COPY --from=builder /app/server/templates ./app/server/templates
COPY --from=builder /app/server/scripts ./app/server/scripts
COPY --from=builder /app/server/dist ./app/server/dist
-COPY --from=builder /app/server/src/assets ./app/server/src/assets
+COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/cloud/cloud-entrypoint.sh ./app/server/cloud-entrypoint.sh
diff --git a/docker/ee/ee-production.Dockerfile b/docker/ee/ee-production.Dockerfile
index c69843063b..bf9386d262 100644
--- a/docker/ee/ee-production.Dockerfile
+++ b/docker/ee/ee-production.Dockerfile
@@ -24,8 +24,15 @@ RUN git checkout ${BRANCH_NAME}
RUN git submodule update --init --recursive
-# Checkout the same branch in submodules if it exists, otherwise stay on default branch
-RUN git submodule foreach 'git checkout ${BRANCH_NAME} || true'
+# Checkout the same branch in submodules if it exists, otherwise fallback to main
+RUN git submodule foreach " \
+ if git show-ref --verify --quiet refs/heads/${BRANCH_NAME} || \
+ git ls-remote --exit-code --heads origin ${BRANCH_NAME}; then \
+ git checkout ${BRANCH_NAME}; \
+ else \
+ echo 'Branch ${BRANCH_NAME} not found in submodule \$name, falling back to main'; \
+ git checkout main; \
+ fi"
# Scripts for building
COPY ./package.json ./package.json
@@ -156,7 +163,7 @@ COPY --from=builder --chown=appuser:0 /app/server/node_modules ./app/server/node
COPY --from=builder --chown=appuser:0 /app/server/templates ./app/server/templates
COPY --from=builder --chown=appuser:0 /app/server/scripts ./app/server/scripts
COPY --from=builder --chown=appuser:0 /app/server/dist ./app/server/dist
-COPY --from=builder --chown=appuser:0 /app/server/src/assets ./app/server/src/assets
+COPY --from=builder --chown=appuser:0 /app/server/ee/ai/assets ./app/server/ee/ai/assets
COPY ./docker/ee/ee-entrypoint.sh ./app/server/ee-entrypoint.sh
RUN mkdir -p /var/lib/neo4j/data/databases /var/lib/neo4j/data/transactions /var/log/neo4j /opt/neo4j/run && \
diff --git a/frontend/ee b/frontend/ee
index c955741e5a..01af83abcd 160000
--- a/frontend/ee
+++ b/frontend/ee
@@ -1 +1 @@
-Subproject commit c955741e5a120cd61651eaf14aef0d5c67059cb0
+Subproject commit 01af83abcdb663c04088e215ff84f90d2bd33a62
diff --git a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx
index 66e62fcd8b..dd2fb8c916 100644
--- a/frontend/src/AppBuilder/Header/CreateVersionModal.jsx
+++ b/frontend/src/AppBuilder/Header/CreateVersionModal.jsx
@@ -16,11 +16,15 @@ const CreateVersionModal = ({
canCommit,
orgGit,
fetchingOrgGit,
- handleCommitOnVersionCreation = () => { },
+ handleCommitOnVersionCreation = () => {},
}) => {
const { moduleId } = useModuleContext();
const [isCreatingVersion, setIsCreatingVersion] = useState(false);
const [versionName, setVersionName] = useState('');
+ const isGitSyncEnabled =
+ orgGit?.org_git?.git_ssh?.is_enabled ||
+ orgGit?.org_git?.git_https?.is_enabled ||
+ orgGit?.org_git?.git_lab?.is_enabled;
const {
createNewVersionAction,
@@ -102,8 +106,8 @@ const CreateVersionModal = ({
});
},
(error) => {
- if (error?.data?.code === "23505") {
- toast.error("Version name already exists.");
+ if (error?.data?.code === '23505') {
+ toast.error('Version name already exists.');
} else {
toast.error(error?.error);
}
@@ -172,7 +176,7 @@ const CreateVersionModal = ({
- {orgGit?.org_git?.is_enabled && (
+ {isGitSyncEnabled && (
({
return query;
});
});
- setSelectedQuery(data.id, data);
+ setSelectedQuery(data.id);
if (shouldRunQuery) setQueryToBeRun(data);
/** Checks if there is an API call cached. If yes execute it */
@@ -321,10 +321,10 @@ export const createDataQuerySlice = (set, get) => ({
type: queryToClone.permissions[0]?.type,
...(queryToClone.permissions[0]?.type === 'GROUP'
? {
- groups: (queryToClone.permissions[0]?.groups || queryToClone.permissions[0]?.users || []).map(
- (group) => group.permissionGroupsId || group.permission_groups_id
- ),
- }
+ groups: (queryToClone.permissions[0]?.groups || queryToClone.permissions[0]?.users || []).map(
+ (group) => group.permissionGroupsId || group.permission_groups_id
+ ),
+ }
: { users: queryToClone.permissions[0]?.users.map((user) => user.userId || user.user_id) }),
};
appPermissionService
diff --git a/frontend/src/AppBuilder/_stores/slices/whiteLabellingSlice.js b/frontend/src/AppBuilder/_stores/slices/whiteLabellingSlice.js
index d1031fea65..dd5a4ca363 100644
--- a/frontend/src/AppBuilder/_stores/slices/whiteLabellingSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/whiteLabellingSlice.js
@@ -2,7 +2,7 @@
const initialState = {
activeOrganizationId: null,
whiteLabelText: 'ToolJet',
- whiteLabelLogo: null,
+ whiteLabelLogo: 'assets/images/tj-logo.svg', //Default whitelbeling logo
whiteLabelFavicon: null,
loadingWhiteLabelDetails: true,
isWhiteLabelDetailsFetched: false,
diff --git a/frontend/src/HomePage/AppMenu.jsx b/frontend/src/HomePage/AppMenu.jsx
index 5db2f81af5..6c2a521c57 100644
--- a/frontend/src/HomePage/AppMenu.jsx
+++ b/frontend/src/HomePage/AppMenu.jsx
@@ -85,12 +85,6 @@ export const AppMenu = function AppMenu({
)}
{canUpdateApp && canCreateApp && appType !== 'workflow' && (
<>
- {appType !== 'workflow' && (
- openAppActionModal('clone-app')}
- />
- )}
{
setShowInput(true);
}}
- data-cy="create-new-folder-button"
+ data-cy="folder-search-icon"
>
)}
diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx
index d6b2edcebb..8acb36bcac 100644
--- a/frontend/src/HomePage/HomePage.jsx
+++ b/frontend/src/HomePage/HomePage.jsx
@@ -1659,11 +1659,6 @@ class HomePageComponent extends React.Component {
{featuresLoaded && !isLoading ? (
<>
-
{
const [isOpen, setShowModal] = useState(propShowModal);
- const currentUser = authenticationService.currentSessionValue;
const handleClose = () => {
setShowModal(false);
toggleModal && toggleModal();
@@ -63,7 +63,7 @@ const LegalReasonsErrorModal = ({
- {currentUser?.super_admin && (
+ {actionButtonAdmin && (
)}
@@ -140,20 +140,21 @@ function Header({
iconWidth="14"
size="md"
onClick={toggleCollapsibleSidebar}
- >
+ />
)}
-
+
+
Version {currentVersion}
diff --git a/frontend/src/_ui/Icon/solidIcons/PremiumPlan.jsx b/frontend/src/_ui/Icon/solidIcons/PremiumPlan.jsx
new file mode 100644
index 0000000000..4ff4b4f8a7
--- /dev/null
+++ b/frontend/src/_ui/Icon/solidIcons/PremiumPlan.jsx
@@ -0,0 +1,14 @@
+import React from 'react';
+
+const PremiumPlan = ({ width = '41', height = '41' }) => (
+
+);
+
+export default PremiumPlan;
diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js
index 6e0f40c9c9..6531303435 100644
--- a/frontend/src/_ui/Icon/solidIcons/index.js
+++ b/frontend/src/_ui/Icon/solidIcons/index.js
@@ -236,6 +236,7 @@ import AICrown from './AICrown.jsx';
import BookDemo from './BookDemo.jsx';
import Contactv3 from './Contactv3.jsx';
import PremiumLogo from './PremiumLogo.jsx';
+import PremiumPlan from './PremiumPlan.jsx';
import StudentIcon from './StudentIcon.jsx';
import CalendarIcon from './CalendarIcon.jsx';
import CalendarSmall from './CalendarSmall.jsx';
@@ -765,6 +766,8 @@ const Icon = (props) => {
return ;
case 'premium-logo':
return ;
+ case 'premium-plan':
+ return ;
case 'calendar-icon':
return ;
case 'calendar-small':
diff --git a/frontend/src/_ui/Layout/index.jsx b/frontend/src/_ui/Layout/index.jsx
index 1f7095dd04..5783c5510a 100644
--- a/frontend/src/_ui/Layout/index.jsx
+++ b/frontend/src/_ui/Layout/index.jsx
@@ -26,6 +26,7 @@ function Layout({
const [licenseValid, setLicenseValid] = useState(false);
const logo = retrieveWhiteLabelLogo();
const router = useRouter();
+ const [licenseStatus, setLicenseStatus] = useState(null);
const { featureAccess } = useLicenseStore(
(state) => ({
featureAccess: state.featureAccess,
@@ -84,6 +85,7 @@ function Layout({
useEffect(() => {
let licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
setLicenseValid(licenseValid);
+ setLicenseStatus(featureAccess?.licenseStatus);
}, [featureAccess]);
const currentUserValue = authenticationService.currentSessionValue;
@@ -147,6 +149,7 @@ function Layout({
enableCollapsibleSidebar={enableCollapsibleSidebar}
collapseSidebar={collapseSidebar}
toggleCollapsibleSidebar={toggleCollapsibleSidebar}
+ licenseStatus={licenseStatus}
/>
{children}
diff --git a/frontend/src/modules/WorkspaceSettings/pages/Groups/components/BaseManageGroupPermissions/BaseManageGroupPermissions.jsx b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/BaseManageGroupPermissions/BaseManageGroupPermissions.jsx
index a8f70603f7..10cb9e2583 100644
--- a/frontend/src/modules/WorkspaceSettings/pages/Groups/components/BaseManageGroupPermissions/BaseManageGroupPermissions.jsx
+++ b/frontend/src/modules/WorkspaceSettings/pages/Groups/components/BaseManageGroupPermissions/BaseManageGroupPermissions.jsx
@@ -394,6 +394,8 @@ class BaseManageGroupPermissions extends React.Component {
} = this.state;
const { featureAccess, isFeatureEnabled, isTrial } = this.props;
+ const isValidLicense = featureAccess?.licenseStatus.isLicenseValid;
+ const planType = featureAccess?.licenseStatus?.licenseType;
const grounNameErrorStyle =
this.state.newGroupName?.length > 50 ? { color: '#ff0000', borderColor: '#ff0000' } : {};
@@ -797,7 +799,7 @@ class BaseManageGroupPermissions extends React.Component {
)}
- {!_.isEmpty(featureAccess) && !isFeatureEnabled && (
+ {(!isValidLicense || planType === 'trial') && featureAccess && (
({
@@ -77,7 +79,6 @@ function BaseSettingsMenu({
featureAccess,
checkForUnsavedChanges,
});
-
return (
{/* Marketplace section */}
@@ -101,6 +102,9 @@ function BaseSettingsMenu({
{/* Super Admin Settings */}
{superAdmin && midMenuContent}
+ {/* Specifically for Cloud Edition */}
+ {edition === 'cloud' && admin && !superAdmin && midMenuContent}
+
{/* Admin section - Workspace settings */}
{admin && (
@@ -192,7 +194,10 @@ const UsersTable = ({
>
{user.status}
- {user.status === 'invited' && !hideAccountSetupLink && user?.invitation_token ? (
+ {user.status === 'invited' &&
+ !hideAccountSetupLink &&
+ user?.invitation_token &&
+ edition != 'cloud' ? (
diff --git a/frontend/src/modules/dataSources/components/List/index.jsx b/frontend/src/modules/dataSources/components/List/index.jsx
index 25f46fd226..6f44ab3cd4 100644
--- a/frontend/src/modules/dataSources/components/List/index.jsx
+++ b/frontend/src/modules/dataSources/components/List/index.jsx
@@ -102,7 +102,7 @@ export const List = ({ updateSelectedDatasource }) => {
setFilteredData(filtered);
};
- function handleClose() {
+ function handleClose () {
setShowInput(false);
setFilteredData(dataSources);
}
@@ -150,7 +150,7 @@ export const List = ({ updateSelectedDatasource }) => {
onClick={() => {
setShowInput(true);
}}
- data-cy="create-new-folder-button"
+ data-cy="added-ds-search-icon"
>
diff --git a/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx b/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx
index 81397312df..327683f3cb 100644
--- a/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx
+++ b/frontend/src/modules/onboarding/pages/SignupPage/SignupPage.jsx
@@ -7,8 +7,11 @@ import OnboardingBackgroundWrapper from '@/modules/onboarding/components/Onboard
import { onInvitedUserSignUpSuccess } from '@/_helpers/platform/utils/auth.utils';
import { SignupForm, SignupSuccessInfo } from './components';
import { GeneralFeatureImage } from '@/modules/common/components';
+import { fetchEdition } from '@/modules/common/helpers/utils';
+import * as envConfigs from 'config';
const SignupPage = ({ configs, organizationId }) => {
+ const edition = fetchEdition();
const { t } = useTranslation();
const location = useLocation();
const navigate = useNavigate();
@@ -18,13 +21,15 @@ const SignupPage = ({ configs, organizationId }) => {
email: '',
name: '',
});
-
const routeState = location.state;
const organizationToken = routeState?.organizationToken;
const inviteeEmail = routeState?.inviteeEmail;
const inviteOrganizationId = organizationId;
const paramInviteOrganizationSlug = params.organizationId;
const redirectTo = location?.search?.split('redirectTo=')[1];
+ if (!paramInviteOrganizationSlug && edition === 'cloud') {
+ window.location.href = envConfigs.WEBSITE_SIGNUP_URL || 'https://www.tooljet.ai/signup';
+ }
useEffect(() => {
const errorMessage = location?.state?.errorMessage;
if (errorMessage) {
diff --git a/frontend/src/modules/onboarding/services/onboarding.service.ce.js b/frontend/src/modules/onboarding/services/onboarding.service.ce.js
index 7e193867c3..ce100b30e5 100644
--- a/frontend/src/modules/onboarding/services/onboarding.service.ce.js
+++ b/frontend/src/modules/onboarding/services/onboarding.service.ce.js
@@ -17,7 +17,6 @@ function setupFirstUser({ companyName, buildPurpose, name, workspaceName, passwo
password,
}),
};
- console.log(requestOptions.body, 'BRUH');
return fetch(`${config.apiUrl}/onboarding/setup-super-admin`, requestOptions)
.then(handleResponse)
diff --git a/frontend/src/modules/onboarding/stores/onboardingStore/onboarding.store.js b/frontend/src/modules/onboarding/stores/onboardingStore/onboarding.store.js
index 133437ba42..62bd090e72 100644
--- a/frontend/src/modules/onboarding/stores/onboardingStore/onboarding.store.js
+++ b/frontend/src/modules/onboarding/stores/onboardingStore/onboarding.store.js
@@ -61,7 +61,6 @@ const useCEOnboardingStore = create(
createSuperAdminAccount: async () => {
if (!get().accountCreated) {
const data = get().prepareSetupAdminData();
- console.log('BRUH CE', data);
await setupFirstUser(data);
set({ accountCreated: true });
}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 902f7d1aa2..c12754054b 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -103,6 +103,14 @@ module.exports = {
boxShadow: {
'interactive-focus-outline': ' 0px 0px 0px 2px var(--interactive-focus-outline)',
'interactive-focus-outline-inset': 'inset 0px 0px 0px 2px #fff',
+ 'elevation-000': '0px 1px 0px 0px rgba(0, 0, 0, 0.10)',
+ 'elevation-100': '0px 1px 1px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-200': '0px 2px 4px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-300': '0px 4px 8px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-400': '0px 8px 16px 0px rgba(48, 50, 51, 0.10), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-500': '0px 16px 24px 0px rgba(48, 50, 51, 0.09), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-600': '0px 24px 40px 0px rgba(48, 50, 51, 0.08), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
+ 'elevation-700': '0px 32px 50px 0px rgba(48, 50, 51, 0.08), 0px 0px 1px 0px rgba(48, 50, 51, 0.05)',
},
fontSize: {
sm: ['11px', '16px'],
diff --git a/server/.eslintignore b/server/.eslintignore
deleted file mode 100644
index 6782f3eb5e..0000000000
--- a/server/.eslintignore
+++ /dev/null
@@ -1,2 +0,0 @@
-dist
-migrations
\ No newline at end of file
diff --git a/server/.gitignore b/server/.gitignore
index c16ef026a3..0db113322a 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -31,4 +31,7 @@ lerna-debug.log*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
-!.vscode/extensions.json
\ No newline at end of file
+!.vscode/extensions.json
+
+# Postgrest configuration
+**/postgrest.conf
\ No newline at end of file
diff --git a/server/.node-version b/server/.node-version
index c6244cda04..8320a6d299 100644
--- a/server/.node-version
+++ b/server/.node-version
@@ -1 +1 @@
-14.17.3
+22.15.1
diff --git a/server/data-migrations/1650485473528-PopulateSSOConfigs.ts b/server/data-migrations/1650485473528-PopulateSSOConfigs.ts
index adfa7f0d1b..71929e7891 100644
--- a/server/data-migrations/1650485473528-PopulateSSOConfigs.ts
+++ b/server/data-migrations/1650485473528-PopulateSSOConfigs.ts
@@ -1,46 +1,59 @@
import { Organization } from '@entities/organization.entity';
import { SSOConfigs, SSOType } from '@entities/sso_config.entity';
import { MigrationInterface, QueryRunner } from 'typeorm';
-import { EncryptionService } from '@modules/encryption/service';
+import { getImportPath, TOOLJET_EDITIONS } from '@modules/app/constants';
+import { getTooljetEdition } from '@helpers/utils.helper';
+import { NestFactory } from '@nestjs/core';
+import { AppModule } from '@modules/app/module';
+import { getEnvVars } from 'scripts/database-config-utils';
export class PopulateSSOConfigs1650485473528 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
const entityManager = queryRunner.manager;
- const encryptionService = new EncryptionService();
- const OrganizationRepository = entityManager.getRepository(Organization);
+ const nestApp = await NestFactory.createApplicationContext(await AppModule.register({ IS_GET_CONTEXT: true }));
- const isSingleOrganization = process.env.DISABLE_MULTI_WORKSPACE === 'true';
- const enableSignUp = process.env.SSO_DISABLE_SIGNUP !== 'true';
- const domain = process.env.SSO_RESTRICTED_DOMAIN;
+ const edition = getTooljetEdition() as TOOLJET_EDITIONS;
+ const { EncryptionService } = await import(`${await getImportPath(true, edition)}/encryption/service`);
+ const encryptionService = nestApp.get(EncryptionService);
- const googleEnabled = !!process.env.SSO_GOOGLE_OAUTH2_CLIENT_ID;
+ const envVars = getEnvVars();
+
+ const isSingleOrganization = envVars.DISABLE_MULTI_WORKSPACE === 'true';
+ const enableSignUp = envVars.SSO_DISABLE_SIGNUP !== 'true';
+ const domain = envVars.SSO_RESTRICTED_DOMAIN;
+
+ const googleEnabled = !!envVars.SSO_GOOGLE_OAUTH2_CLIENT_ID;
const googleConfigs = {
- clientId: process.env.SSO_GOOGLE_OAUTH2_CLIENT_ID,
+ clientId: envVars.SSO_GOOGLE_OAUTH2_CLIENT_ID,
};
- const gitEnabled = !!process.env.SSO_GIT_OAUTH2_CLIENT_ID;
+ const gitEnabled = !!envVars.SSO_GIT_OAUTH2_CLIENT_ID;
const gitConfigs = {
- clientId: process.env.SSO_GIT_OAUTH2_CLIENT_ID,
+ clientId: envVars.SSO_GIT_OAUTH2_CLIENT_ID,
clientSecret:
- process.env.SSO_GIT_OAUTH2_CLIENT_SECRET &&
+ envVars.SSO_GIT_OAUTH2_CLIENT_SECRET &&
(await encryptionService.encryptColumnValue(
'ssoConfigs',
'clientSecret',
- process.env.SSO_GIT_OAUTH2_CLIENT_SECRET
+ envVars.SSO_GIT_OAUTH2_CLIENT_SECRET
)),
};
- const passwordEnabled = process.env.DISABLE_PASSWORD_LOGIN !== 'true';
+ const passwordEnabled = envVars.DISABLE_PASSWORD_LOGIN !== 'true';
- const organizations: Organization[] = await OrganizationRepository.find({
+ const organizations: Organization[] = await entityManager.find(Organization, {
relations: ['ssoConfigs'],
select: ['ssoConfigs', 'id'],
});
if (organizations && organizations.length > 0) {
for (const organization of organizations) {
- await OrganizationRepository.update({ id: organization.id }, { enableSignUp, ...(domain ? { domain } : {}) });
+ await entityManager.update(
+ Organization,
+ { id: organization.id },
+ { enableSignUp, ...(domain ? { domain } : {}) }
+ );
// adding form configs for organizations which does not have any
if (
!organization.ssoConfigs?.some((og) => {
diff --git a/server/data-migrations/1706024347284-AddInstanceLevelSSOInSSOConfigs.ts b/server/data-migrations/1706024347284-AddInstanceLevelSSOInSSOConfigs.ts
index 38bf6b78c6..e9abe948c5 100644
--- a/server/data-migrations/1706024347284-AddInstanceLevelSSOInSSOConfigs.ts
+++ b/server/data-migrations/1706024347284-AddInstanceLevelSSOInSSOConfigs.ts
@@ -1,33 +1,44 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { ConfigScope, SSOConfigs, SSOType } from '@entities/sso_config.entity';
-import { EncryptionService } from '@modules/encryption/service';
+import { NestFactory } from '@nestjs/core';
+import { AppModule } from '@modules/app/module';
+import { getTooljetEdition } from '@helpers/utils.helper';
+import { getImportPath, TOOLJET_EDITIONS } from '@modules/app/constants';
+import { getEnvVars } from 'scripts/database-config-utils';
export class AddInstanceLevelSSOInSSOConfigs1706024347284 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
const entityManager = queryRunner.manager;
- const encryptionService = new EncryptionService();
+ const nestApp = await NestFactory.createApplicationContext(await AppModule.register({ IS_GET_CONTEXT: true }));
+
+ const edition = getTooljetEdition() as TOOLJET_EDITIONS;
+ const { EncryptionService } = await import(`${await getImportPath(true, edition)}/encryption/service`);
+ const encryptionService = nestApp.get(EncryptionService);
+
+ const envVars = getEnvVars();
+
const ssoConfigs: Partial[] = [
{
configScope: ConfigScope.INSTANCE,
sso: SSOType.GOOGLE,
- enabled: !!process.env?.SSO_GOOGLE_OAUTH2_CLIENT_ID,
+ enabled: !!envVars?.SSO_GOOGLE_OAUTH2_CLIENT_ID,
configs: {
- clientId: process.env?.SSO_GOOGLE_OAUTH2_CLIENT_ID || '',
+ clientId: envVars?.SSO_GOOGLE_OAUTH2_CLIENT_ID || '',
},
},
{
configScope: ConfigScope.INSTANCE,
sso: SSOType.GIT,
- enabled: !!process.env?.SSO_GIT_OAUTH2_CLIENT_ID,
+ enabled: !!envVars?.SSO_GIT_OAUTH2_CLIENT_ID,
configs: {
- clientId: process.env?.SSO_GIT_OAUTH2_CLIENT_ID || '',
- hostName: process.env?.SSO_GIT_OAUTH2_HOST || '',
+ clientId: envVars?.SSO_GIT_OAUTH2_CLIENT_ID || '',
+ hostName: envVars?.SSO_GIT_OAUTH2_HOST || '',
clientSecret:
- (process.env?.SSO_GIT_OAUTH2_CLIENT_SECRET &&
+ (envVars?.SSO_GIT_OAUTH2_CLIENT_SECRET &&
(await encryptionService.encryptColumnValue(
'ssoConfigs',
'clientSecret',
- process.env.SSO_GIT_OAUTH2_CLIENT_SECRET
+ envVars.SSO_GIT_OAUTH2_CLIENT_SECRET
))) ||
'',
},
@@ -35,19 +46,19 @@ export class AddInstanceLevelSSOInSSOConfigs1706024347284 implements MigrationIn
{
configScope: ConfigScope.INSTANCE,
sso: SSOType.OPENID,
- enabled: !!process.env?.SSO_OPENID_CLIENT_ID,
+ enabled: !!envVars?.SSO_OPENID_CLIENT_ID,
configs: {
- clientId: process.env?.SSO_OPENID_CLIENT_ID || '',
- name: process.env?.SSO_OPENID_NAME || '',
+ clientId: envVars?.SSO_OPENID_CLIENT_ID || '',
+ name: envVars?.SSO_OPENID_NAME || '',
clientSecret:
- (process.env?.SSO_OPENID_CLIENT_SECRET &&
+ (envVars?.SSO_OPENID_CLIENT_SECRET &&
(await encryptionService.encryptColumnValue(
'ssoConfigs',
'clientSecret',
- process.env.SSO_OPENID_CLIENT_SECRET
+ envVars.SSO_OPENID_CLIENT_SECRET
))) ||
'',
- wellKnownUrl: process.env?.SSO_OPENID_WELL_KNOWN_URL || '',
+ wellKnownUrl: envVars?.SSO_OPENID_WELL_KNOWN_URL || '',
},
},
{
diff --git a/server/data-migrations/1710780718114-AddGracePeriodExpiryDateColumnInOrganizationLicenseTable.ts b/server/data-migrations/1710780718114-AddGracePeriodExpiryDateColumnInOrganizationLicenseTable.ts
index d68ba4e155..75233be2f5 100644
--- a/server/data-migrations/1710780718114-AddGracePeriodExpiryDateColumnInOrganizationLicenseTable.ts
+++ b/server/data-migrations/1710780718114-AddGracePeriodExpiryDateColumnInOrganizationLicenseTable.ts
@@ -1,14 +1,6 @@
-import { getTooljetEdition } from '@helpers/utils.helper';
-import { TOOLJET_EDITIONS } from '@modules/app/constants';
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddGracePeriodExpiryDateColumnInOrganizationLicenseTable1710780718114 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
- const edition: TOOLJET_EDITIONS = getTooljetEdition() as TOOLJET_EDITIONS;
- // If edition is not cloud, skip this migration
- if (edition !== TOOLJET_EDITIONS.Cloud) {
- console.log('Migration is only restricted for cloud edition.');
- return; // Exit the migration early
- }
await queryRunner.query('ALTER TABLE organization_license ADD COLUMN expiry_with_grace_period TIMESTAMP');
// Update the new column with expiry_date + 14 days
diff --git a/server/data-migrations/1721236971725-MoveToolJetDatabaseTablesFromPublicToTenantSchema.ts b/server/data-migrations/1721236971725-MoveToolJetDatabaseTablesFromPublicToTenantSchema.ts
index 8f0b5a98ae..63a19bcfd3 100644
--- a/server/data-migrations/1721236971725-MoveToolJetDatabaseTablesFromPublicToTenantSchema.ts
+++ b/server/data-migrations/1721236971725-MoveToolJetDatabaseTablesFromPublicToTenantSchema.ts
@@ -16,7 +16,7 @@ import {
revokeAccessToPublicSchema,
grantTenantRoleToTjdbAdminRole,
} from '@helpers/tooljet_db.helper';
-const crypto = require('crypto');
+import * as crypto from 'crypto';
export class MoveToolJetDatabaseTablesFromPublicToTenantSchema1721236971725 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise {
diff --git a/server/data-migrations/1740401100000-SetDefaultWorkspace.ts b/server/data-migrations/1740401100000-SetDefaultWorkspace.ts
index c337155fd8..314625421e 100644
--- a/server/data-migrations/1740401100000-SetDefaultWorkspace.ts
+++ b/server/data-migrations/1740401100000-SetDefaultWorkspace.ts
@@ -23,17 +23,20 @@ export class SetDefaultWorkspace1740401100000 implements MigrationInterface {
where: { slug: workspaceSlug, status: WORKSPACE_STATUS.ACTIVE },
select: ['id'],
});
- if (organization){
- await queryRunner.query(`
+ if (organization) {
+ await queryRunner.query(
+ `
UPDATE organizations
SET is_default = true
WHERE slug = $1
- `, [workspaceSlug]);
- return;
+ `,
+ [workspaceSlug]
+ );
+ return;
}
console.log(`No active organization found with slug: ${workspaceSlug}`);
}
- } catch (err) {
+ } catch {
console.log('Invalid TOOLJET_DEFAULT_WORKSPACE_URL format');
}
}
@@ -63,4 +66,4 @@ export class SetDefaultWorkspace1740401100000 implements MigrationInterface {
SET is_default = false;
`);
}
-}
+}
diff --git a/server/ee b/server/ee
index 1754d14f2f..39b3bebc8f 160000
--- a/server/ee
+++ b/server/ee
@@ -1 +1 @@
-Subproject commit 1754d14f2fc5dee8616f09f13e63b3569e42f3ee
+Subproject commit 39b3bebc8f352d0e078a0a91629652610ce7aa6b
diff --git a/server/migrations/1738235725332-AddAutoActivatedToUsers.ts b/server/migrations/1738235725332-AddAutoActivatedToUsers.ts
new file mode 100644
index 0000000000..597f2719f5
--- /dev/null
+++ b/server/migrations/1738235725332-AddAutoActivatedToUsers.ts
@@ -0,0 +1,19 @@
+import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
+
+export class AddAutoActivatedToUsers1738235725332 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.addColumn(
+ 'users',
+ new TableColumn({
+ name: 'auto_activated',
+ type: 'boolean',
+ default: false,
+ isNullable: false,
+ })
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.dropColumn('users', 'auto_activated');
+ }
+}
diff --git a/server/scripts/create-database.ts b/server/scripts/create-database.ts
index 80be547fe9..ae29ed2059 100644
--- a/server/scripts/create-database.ts
+++ b/server/scripts/create-database.ts
@@ -48,7 +48,7 @@ function checkCommandAvailable(command: string) {
try {
const options = { env: process.env } as ExecFileSyncOptions;
execFileSync('which', [command], options);
- } catch (error) {
+ } catch {
throw `Error: ${command} not found. Make sure it's installed and available in the system's PATH.`;
}
}
diff --git a/server/scripts/drop-database.ts b/server/scripts/drop-database.ts
index 6c79cdbb16..70993e8dbf 100644
--- a/server/scripts/drop-database.ts
+++ b/server/scripts/drop-database.ts
@@ -40,7 +40,7 @@ function checkCommandAvailable(command: string) {
try {
const options = { env: Object.assign({}, process.env) } as ExecFileSyncOptions;
execFileSync('which', [command], options);
- } catch (error) {
+ } catch {
throw `Error: ${command} not found. Make sure it's installed and available in the system's PATH.`;
}
}
diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts
index f43396f0ed..fb0b25746c 100644
--- a/server/src/entities/user.entity.ts
+++ b/server/src/entities/user.entity.ts
@@ -16,7 +16,7 @@ import {
} from 'typeorm';
import { App } from './app.entity';
import { GroupPermission } from './group_permission.entity';
-const bcrypt = require('bcrypt');
+import * as bcrypt from 'bcrypt';
import { OrganizationUser } from './organization_user.entity';
import { File } from './file.entity';
import { Organization } from './organization.entity';
@@ -118,6 +118,9 @@ export class User extends BaseEntity {
@Column({ name: 'company_size' })
companySize: string;
+ @Column({ name: 'auto_activated', default: false })
+ autoActivated: boolean;
+
@Column({ name: 'password_retry_count' })
passwordRetryCount: number;
diff --git a/server/src/helpers/bootstrap.helper.ts b/server/src/helpers/bootstrap.helper.ts
new file mode 100644
index 0000000000..f9111388dc
--- /dev/null
+++ b/server/src/helpers/bootstrap.helper.ts
@@ -0,0 +1,314 @@
+import { NestExpressApplication } from '@nestjs/platform-express';
+import { ConfigService } from '@nestjs/config';
+import { bootstrap as globalAgentBootstrap } from 'global-agent';
+import { join } from 'path';
+import helmet from 'helmet';
+import * as fs from 'fs';
+import { LicenseInitService } from '@modules/licensing/interfaces/IService';
+import { TOOLJET_EDITIONS, getImportPath } from '@modules/app/constants';
+import { ILicenseUtilService } from '@modules/licensing/interfaces/IUtilService';
+import { getTooljetEdition } from '@helpers/utils.helper';
+
+/**
+ * Creates a logger instance with a specific context
+ */
+export function createLogger(context: string) {
+ return {
+ log: (message: string, ...optionalParams: any[]) => {
+ const timestamp = new Date().toISOString();
+ console.log(`[${timestamp}] [${context}] ${message}`, ...optionalParams);
+ },
+ error: (message: string, error?: any) => {
+ const timestamp = new Date().toISOString();
+ console.error(`[${timestamp}] [${context}] ERROR: ${message}`, error);
+ },
+ warn: (message: string, ...optionalParams: any[]) => {
+ const timestamp = new Date().toISOString();
+ console.warn(`[${timestamp}] [${context}] WARN: ${message}`, ...optionalParams);
+ },
+ };
+}
+
+/**
+ * Raw body buffer handler for request processing
+ */
+export function rawBodyBuffer(req: any, res: any, buf: Buffer, encoding: BufferEncoding) {
+ if (buf && buf.length) {
+ req.rawBody = buf.toString(encoding || 'utf8');
+ }
+}
+
+/**
+ * Handles licensing initialization for Enterprise Edition
+ */
+export async function handleLicensingInit(app: NestExpressApplication) {
+ const logger = createLogger('Licensing');
+ const tooljetEdition = getTooljetEdition() as TOOLJET_EDITIONS;
+
+ logger.log(`Current edition: ${tooljetEdition}`);
+
+ if (tooljetEdition !== TOOLJET_EDITIONS.EE) {
+ logger.log('Skipping licensing initialization for non-EE edition');
+ return;
+ }
+
+ try {
+ logger.log('Initializing Enterprise Edition licensing...');
+ const importPath = await getImportPath(false, tooljetEdition);
+ const { LicenseUtilService } = await import(`${importPath}/licensing/util.service`);
+
+ const licenseInitService = app.get(LicenseInitService);
+ const licenseUtilService = app.get(LicenseUtilService);
+
+ logger.log('Calling license initialization service...');
+ await licenseInitService.init();
+ logger.log('✅ License initialization completed');
+
+ logger.log('Loading license configuration...');
+ const License = await import(`${importPath}/licensing/configs/License`);
+ const license = License.default;
+
+ logger.log('Validating hostname and subpath...');
+ licenseUtilService.validateHostnameSubpath(license.Instance()?.domains);
+
+ const licenseInfo = license.Instance();
+ logger.log(`✅ License validation completed`);
+ logger.log(`License valid: ${licenseInfo.isValid}`);
+ logger.log(`License terms: ${JSON.stringify(licenseInfo.terms)}`);
+
+ console.log(`License valid : ${licenseInfo.isValid} License Terms : ${JSON.stringify(licenseInfo.terms)} 🚀`);
+ } catch (error) {
+ logger.error('❌ Failed to initialize licensing:', error);
+ throw error;
+ }
+}
+
+/**
+ * Replaces subpath placeholders in static assets
+ */
+export function replaceSubpathPlaceHoldersInStaticAssets() {
+ const logger = createLogger('StaticAssets');
+ const filesToReplaceAssetPath = ['index.html', 'runtime.js', 'main.js'];
+
+ logger.log('Starting subpath placeholder replacement...');
+
+ for (const fileName of filesToReplaceAssetPath) {
+ try {
+ const file = join(__dirname, '../../../../', 'frontend/build', fileName);
+ logger.log(`Processing file: ${fileName}`);
+
+ let newValue = process.env.SUB_PATH;
+
+ if (process.env.SUB_PATH === undefined) {
+ newValue = fileName === 'index.html' ? '/' : '';
+ logger.log(`Using default value for ${fileName}: "${newValue}"`);
+ } else {
+ logger.log(`Using SUB_PATH value for ${fileName}: "${newValue}"`);
+ }
+
+ if (!fs.existsSync(file)) {
+ logger.warn(`File not found: ${file}`);
+ continue;
+ }
+
+ const data = fs.readFileSync(file, { encoding: 'utf8' });
+ const result = data
+ .replace(/__REPLACE_SUB_PATH__\/api/g, join(newValue, '/api'))
+ .replace(/__REPLACE_SUB_PATH__/g, newValue);
+
+ fs.writeFileSync(file, result, { encoding: 'utf8' });
+ logger.log(`✅ Successfully processed: ${fileName}`);
+ } catch (error) {
+ logger.error(`❌ Failed to process ${fileName}:`, error);
+ }
+ }
+
+ logger.log('✅ Subpath placeholder replacement completed');
+}
+
+/**
+ * Sets up security headers including CORS and CSP
+ */
+export function setSecurityHeaders(app: NestExpressApplication, configService: ConfigService) {
+ const logger = createLogger('Security');
+ logger.log('Setting up security headers...');
+
+ try {
+ const tooljetHost = configService.get('TOOLJET_HOST');
+ const host = new URL(tooljetHost);
+ const domain = host.hostname;
+
+ logger.log(`Configuring CORS for domain: ${domain}`);
+ logger.log(`CORS enabled: ${configService.get('ENABLE_CORS') === 'true'}`);
+
+ // Enable CORS
+ app.enableCors({
+ origin: configService.get('ENABLE_CORS') === 'true' || tooljetHost,
+ credentials: true,
+ });
+
+ // Get CSP whitelisted domains
+ const cspWhitelistedDomains = configService.get('CSP_WHITELISTED_DOMAINS')?.split(',') || [];
+ logger.log(`CSP whitelisted domains: ${cspWhitelistedDomains.join(', ')}`);
+
+ // Configure Helmet
+ app.use(
+ helmet({
+ contentSecurityPolicy: {
+ useDefaults: true,
+ directives: {
+ upgradeInsecureRequests: null,
+ 'img-src': ['*', 'data:', 'blob:'],
+ 'script-src': [
+ 'maps.googleapis.com',
+ 'storage.googleapis.com',
+ 'apis.google.com',
+ 'accounts.google.com',
+ "'self'",
+ "'unsafe-inline'",
+ "'unsafe-eval'",
+ 'blob:',
+ 'https://unpkg.com/@babel/standalone@7.17.9/babel.min.js',
+ 'https://unpkg.com/react@16.7.0/umd/react.production.min.js',
+ 'https://unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js',
+ 'cdn.skypack.dev',
+ 'cdn.jsdelivr.net',
+ 'https://esm.sh',
+ 'www.googletagmanager.com',
+ ].concat(cspWhitelistedDomains),
+ 'object-src': ["'self'", 'data:'],
+ 'media-src': ["'self'", 'data:'],
+ 'default-src': [
+ 'maps.googleapis.com',
+ 'storage.googleapis.com',
+ 'apis.google.com',
+ 'accounts.google.com',
+ '*.sentry.io',
+ "'self'",
+ 'blob:',
+ 'www.googletagmanager.com',
+ ].concat(cspWhitelistedDomains),
+ 'connect-src': ['ws://' + domain, "'self'", '*', 'data:'],
+ 'frame-ancestors': ['*'],
+ 'frame-src': ['*'],
+ },
+ },
+ frameguard: configService.get('DISABLE_APP_EMBED') !== 'true' ? false : { action: 'deny' },
+ hidePoweredBy: true,
+ referrerPolicy: {
+ policy: 'no-referrer',
+ },
+ })
+ );
+
+ logger.log(`Frame embedding ${configService.get('DISABLE_APP_EMBED') !== 'true' ? 'enabled' : 'disabled'}`);
+
+ const subPath = configService.get('SUB_PATH');
+
+ // Custom headers middleware
+ app.use((req, res, next) => {
+ res.setHeader('Permissions-Policy', 'geolocation=(self), camera=(), microphone=()');
+ res.setHeader('X-Powered-By', 'ToolJet');
+
+ if (req.path.startsWith(`${subPath || '/'}api/`)) {
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
+ } else {
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
+ }
+
+ return next();
+ });
+
+ logger.log('✅ Security headers configured successfully');
+ } catch (error) {
+ logger.error('❌ Failed to configure security headers:', error);
+ throw error;
+ }
+}
+
+/**
+ * Builds the application version string
+ */
+export function buildVersion(): string {
+ const logger = createLogger('Version');
+
+ try {
+ logger.log('Reading version from .version file...');
+ const rawVersion = fs.readFileSync('./.version', 'utf8').trim();
+ logger.log(`Raw version: ${rawVersion}`);
+
+ const ltsRegex = /-lts$/i;
+ const edition = getTooljetEdition();
+
+ let version: string;
+ if (ltsRegex.test(rawVersion)) {
+ // Extract base version (everything before -lts)
+ const baseVersion = rawVersion.replace(ltsRegex, '');
+ // Construct: baseVersion-edition-lts
+ version = `${baseVersion}-${edition}-lts`;
+ logger.log(`LTS version detected. Built version: ${version}`);
+ } else {
+ // Current implementation: version-edition
+ version = `${rawVersion}-${edition}`;
+ logger.log(`Standard version. Built version: ${version}`);
+ }
+
+ return version;
+ } catch (error) {
+ logger.error('❌ Failed to build version:', error);
+ throw error;
+ }
+}
+
+/**
+ * Sets up global agent for HTTP proxy if configured
+ */
+export function setupGlobalAgent() {
+ const logger = createLogger('GlobalAgent');
+
+ if (process.env.TOOLJET_HTTP_PROXY) {
+ logger.log(`Setting up global HTTP proxy: ${process.env.TOOLJET_HTTP_PROXY}`);
+ process.env['GLOBAL_AGENT_HTTP_PROXY'] = process.env.TOOLJET_HTTP_PROXY;
+ globalAgentBootstrap();
+ logger.log('✅ Global HTTP proxy configured');
+ } else {
+ logger.log('No HTTP proxy configured');
+ }
+}
+
+/**
+ * Logs startup information
+ */
+export function logStartupInfo(configService: ConfigService, logger: any) {
+ const tooljetHost = configService.get('TOOLJET_HOST');
+ const subPath = configService.get('SUB_PATH');
+ const corsEnabled = configService.get('ENABLE_CORS') === 'true';
+ const edition = getTooljetEdition();
+ const version = globalThis.TOOLJET_VERSION;
+
+ logger.log('='.repeat(60));
+ logger.log('🚀 TOOLJET APPLICATION STARTED SUCCESSFULLY');
+ logger.log('='.repeat(60));
+ logger.log(`Edition: ${edition}`);
+ logger.log(`Version: ${version}`);
+ logger.log(`Host: ${tooljetHost}${subPath || ''}`);
+ logger.log(`Subpath: ${subPath || 'None'}`);
+ logger.log(`CSP Whitelisted Domains: ${configService.get('CSP_WHITELISTED_DOMAINS') || 'None'}`);
+ logger.log(`CORS Enabled: ${corsEnabled}`);
+ logger.log(`global HTTP proxy: ${configService.get('TOOLJET_HTTP_PROXY') || 'Not configured'}`);
+ logger.log(`Frame embedding: ${configService.get('DISABLE_APP_EMBED') !== 'true' ? 'enabled' : 'disabled'}`);
+ logger.log(`Environment: ${configService.get('NODE_ENV') || 'development'}`);
+ logger.log(`Port: ${configService.get('PORT') || 3000}`);
+ logger.log(`Listen Address: ${configService.get('LISTEN_ADDR') || '::'}`);
+ logger.log('='.repeat(60));
+}
+
+/**
+ * Logs shutdown information
+ */
+export function logShutdownInfo(signal: string, logger: any) {
+ logger.log('='.repeat(60));
+ logger.log(`🛑 ${signal} SIGNAL RECEIVED - SHUTTING DOWN`);
+ logger.log('='.repeat(60));
+ logger.log('Gracefully closing application...');
+}
diff --git a/server/src/helpers/import_export.helpers.ts b/server/src/helpers/import_export.helpers.ts
index 1f1bc57b7e..e9163c054d 100644
--- a/server/src/helpers/import_export.helpers.ts
+++ b/server/src/helpers/import_export.helpers.ts
@@ -130,7 +130,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp
valueWithId: `{{${replacedExpression}}}`,
valueWithBrackets: `{{${bracketNotationExpression}}}`,
});
- } catch (error) {
+ } catch {
replacedString += fullMatch;
bracketNotationString += fullMatch;
results.push({
@@ -207,7 +207,7 @@ export function extractAndReplaceReferencesFromString(input, componentIdNameMapp
valueWithId: `{{${replacedExpression}}}`,
valueWithBrackets: `{{${bracketNotationExpression}}}`,
});
- } catch (error) {
+ } catch {
replacedString += fullMatch;
bracketNotationString += fullMatch;
results.push({
@@ -317,7 +317,7 @@ function replaceIdsInExpression(
}
return result;
- } catch (error) {
+ } catch {
return expression;
}
}
diff --git a/server/src/helpers/utils.helper.ts b/server/src/helpers/utils.helper.ts
index 78e7259770..7effd864f5 100644
--- a/server/src/helpers/utils.helper.ts
+++ b/server/src/helpers/utils.helper.ts
@@ -7,8 +7,7 @@ import { ConflictException } from '@nestjs/common';
import { DataBaseConstraints } from './db_constraints.constants';
import { getEnvVars } from 'scripts/database-config-utils';
import { decamelizeKeys } from 'humps';
-
-const semver = require('semver');
+import * as semver from 'semver';
export function parseJson(jsonString: string, errorMessage?: string): object {
try {
@@ -84,7 +83,7 @@ export function isJSONString(value: string): boolean {
try {
JSON.parse(value);
return true;
- } catch (e) {
+ } catch {
return false;
}
}
diff --git a/server/src/main.ts b/server/src/main.ts
index d87e1f41ac..7c9979a191 100644
--- a/server/src/main.ts
+++ b/server/src/main.ts
@@ -14,17 +14,12 @@ import {
INestApplicationContext,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
-import { bootstrap as globalAgentBootstrap } from 'global-agent';
import { custom } from 'openid-client';
import { join } from 'path';
-import helmet from 'helmet';
import * as express from 'express';
-import * as fs from 'fs';
-import { LicenseInitService } from '@modules/licensing/interfaces/IService';
import { AppModule } from '@modules/app/module';
import { TOOLJET_EDITIONS, getImportPath } from '@modules/app/constants';
import { GuardValidator } from '@modules/app/validators/feature-guard.validator';
-import { ILicenseUtilService } from '@modules/licensing/interfaces/IUtilService';
import { ITemporalService } from '@modules/workflows/interfaces/ITemporalService';
import { getTooljetEdition } from '@helpers/utils.helper';
import { validateEdition } from '@helpers/edition.helper';
@@ -32,168 +27,152 @@ import { ResponseInterceptor } from '@modules/app/interceptors/response.intercep
import { Reflector } from '@nestjs/core';
import { EventEmitter2 } from '@nestjs/event-emitter';
+// Import helper functions
+import {
+ handleLicensingInit,
+ replaceSubpathPlaceHoldersInStaticAssets,
+ setSecurityHeaders,
+ buildVersion,
+ rawBodyBuffer,
+ setupGlobalAgent,
+ createLogger,
+ logStartupInfo,
+ logShutdownInfo,
+} from '@helpers/bootstrap.helper';
+
let appContext: INestApplicationContext = undefined;
-async function handleLicensingInit(app: NestExpressApplication) {
- const tooljetEdition = getTooljetEdition() as TOOLJET_EDITIONS;
-
- if (tooljetEdition !== TOOLJET_EDITIONS.EE) {
- // If the edition is not EE, we don't need to initialize licensing
- return;
- }
-
- const importPath = await getImportPath(false, tooljetEdition);
- const { LicenseUtilService } = await import(`${importPath}/licensing/util.service`);
-
- const licenseInitService = app.get(LicenseInitService);
- const licenseUtilService = app.get(LicenseUtilService);
-
- await licenseInitService.init();
-
- const License = await import(`${importPath}/licensing/configs/License`);
- const license = License.default;
- licenseUtilService.validateHostnameSubpath(license.Instance()?.domains);
-
- console.log(
- `License valid : ${license.Instance().isValid} License Terms : ${JSON.stringify(license.Instance().terms)} 🚀`
- );
-}
-function replaceSubpathPlaceHoldersInStaticAssets() {
- const filesToReplaceAssetPath = ['index.html', 'runtime.js', 'main.js'];
-
- for (const fileName of filesToReplaceAssetPath) {
- const file = join(__dirname, '../../../', 'frontend/build', fileName);
-
- let newValue = process.env.SUB_PATH;
-
- if (process.env.SUB_PATH === undefined) {
- newValue = fileName === 'index.html' ? '/' : '';
- }
-
- const data = fs.readFileSync(file, { encoding: 'utf8' });
-
- const result = data
- .replace(/__REPLACE_SUB_PATH__\/api/g, join(newValue, '/api'))
- .replace(/__REPLACE_SUB_PATH__/g, newValue);
-
- fs.writeFileSync(file, result, { encoding: 'utf8' });
- }
-}
-
-function setSecurityHeaders(app, configService) {
- const tooljetHost = configService.get('TOOLJET_HOST');
- const host = new URL(tooljetHost);
- const domain = host.hostname;
-
- app.enableCors({
- origin: configService.get('ENABLE_CORS') === 'true' || tooljetHost,
- credentials: true,
- });
-
- app.use(
- helmet({
- contentSecurityPolicy: {
- useDefaults: true,
- directives: {
- upgradeInsecureRequests: null,
- 'img-src': ['*', 'data:', 'blob:'],
- 'script-src': [
- 'maps.googleapis.com',
- 'storage.googleapis.com',
- 'apis.google.com',
- 'accounts.google.com',
- "'self'",
- "'unsafe-inline'",
- "'unsafe-eval'",
- 'blob:',
- 'https://unpkg.com/@babel/standalone@7.17.9/babel.min.js',
- 'https://unpkg.com/react@16.7.0/umd/react.production.min.js',
- 'https://unpkg.com/react-dom@16.7.0/umd/react-dom.production.min.js',
- 'cdn.skypack.dev',
- 'cdn.jsdelivr.net',
- 'https://esm.sh',
- 'www.googletagmanager.com',
- ].concat(configService.get('CSP_WHITELISTED_DOMAINS')?.split(',') || []),
- 'object-src': ["'self'", 'data:'],
- 'media-src': ["'self'", 'data:'],
- 'default-src': [
- 'maps.googleapis.com',
- 'storage.googleapis.com',
- 'apis.google.com',
- 'accounts.google.com',
- '*.sentry.io',
- "'self'",
- 'blob:',
- 'www.googletagmanager.com',
- ].concat(configService.get('CSP_WHITELISTED_DOMAINS')?.split(',') || []),
- 'connect-src': ['ws://' + domain, "'self'", '*', 'data:'],
- 'frame-ancestors': ['*'],
- 'frame-src': ['*'],
- },
- },
- frameguard: configService.get('DISABLE_APP_EMBED') !== 'true' ? false : { action: 'deny' },
- hidePoweredBy: true,
- referrerPolicy: {
- policy: 'no-referrer',
- },
- })
- );
-
- app.use((req, res, next) => {
- res.setHeader('Permissions-Policy', 'geolocation=(self), camera=(), microphone=()');
- res.setHeader('X-Powered-By', 'ToolJet');
-
- if (req.path.startsWith('/api/')) {
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
- } else {
- res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
- }
-
- return next();
- });
-}
-
async function bootstrap() {
- const app = await NestFactory.create(await AppModule.register({ IS_GET_CONTEXT: false }), {
- bufferLogs: true,
- abortOnError: false,
- });
+ const logger = createLogger('Bootstrap');
+ logger.log('🚀 Starting ToolJet application bootstrap...');
- // Get DataSource from the app
- await validateEdition(app);
+ try {
+ logger.log('Creating NestJS application...');
+ const app = await NestFactory.create(await AppModule.register({ IS_GET_CONTEXT: false }), {
+ bufferLogs: true,
+ abortOnError: false,
+ });
- globalThis.TOOLJET_VERSION = `${fs.readFileSync('./.version', 'utf8').trim()}-${getTooljetEdition()}`;
- process.env['RELEASE_VERSION'] = globalThis.TOOLJET_VERSION;
+ const configService = app.get(ConfigService);
+ logger.log('✅ NestJS application created successfully');
- process.on('SIGINT', async () => {
- console.log('SIGINT signal received: closing application...');
- await app.close();
- process.exit(0);
- });
+ // Validate edition
+ logger.log('Validating ToolJet edition...');
+ await validateEdition(app);
+ logger.log('✅ Edition validation completed');
- if (process.env.SERVE_CLIENT !== 'false' && process.env.NODE_ENV === 'production') {
- replaceSubpathPlaceHoldersInStaticAssets();
- }
- console.log(getTooljetEdition(), 'ToolJet Edition 🚀');
+ // Build version
+ logger.log('Building version information...');
+ const version = buildVersion();
+ globalThis.TOOLJET_VERSION = version;
+ process.env['RELEASE_VERSION'] = version;
+ logger.log(`✅ Version set: ${version}`);
- if (getTooljetEdition() !== TOOLJET_EDITIONS.Cloud) {
+ // Setup graceful shutdown
+ logger.log('Setting up graceful shutdown handlers...');
+ setupGracefulShutdown(app, logger);
+ logger.log('✅ Graceful shutdown handlers configured');
+
+ // Handle static assets in production
+ if (process.env.SERVE_CLIENT !== 'false' && process.env.NODE_ENV === 'production') {
+ logger.log('Replacing subpath placeholders in static assets...');
+ replaceSubpathPlaceHoldersInStaticAssets();
+ logger.log('✅ Static assets processed');
+ }
+
+ // Initialize licensing
+ logger.log('Initializing licensing...');
await handleLicensingInit(app);
+ logger.log('✅ Licensing initialization completed');
+
+ // Configure OIDC timeout
+ logger.log('Configuring OIDC connection timeout...');
+ const oidcTimeout = parseInt(process.env.OIDC_CONNECTION_TIMEOUT || '3500');
+ custom.setHttpOptionsDefaults({ timeout: oidcTimeout });
+ logger.log(`✅ OIDC timeout set to ${oidcTimeout}ms`);
+
+ // Setup application middleware and pipes
+ logger.log('Setting up application middleware and pipes...');
+ setupApplicationMiddleware(app);
+ logger.log('✅ Application middleware configured');
+
+ // Configure URL prefix and excluded paths
+ logger.log('Configuring URL prefix and excluded paths...');
+ const { urlPrefix, pathsToExclude } = configureUrlPrefix();
+ app.setGlobalPrefix(urlPrefix + 'api', { exclude: pathsToExclude });
+ logger.log(`✅ URL prefix configured: ${urlPrefix}`);
+
+ // Setup body parsers
+ logger.log('Setting up body parsers...');
+ setupBodyParsers(app, configService);
+ logger.log('✅ Body parsers configured');
+
+ // Enable versioning
+ logger.log('Enabling API versioning...');
+ app.enableVersioning({
+ type: VersioningType.URI,
+ defaultVersion: VERSION_NEUTRAL,
+ });
+ logger.log('✅ API versioning enabled');
+
+ // Setup security headers
+ logger.log('Setting up security headers...');
+ setSecurityHeaders(app, configService);
+ logger.log('✅ Security headers configured');
+
+ // Setup static assets
+ logger.log('Setting up static assets...');
+ app.use(`${urlPrefix}/assets`, express.static(join(__dirname, '/assets')));
+ logger.log('✅ Static assets configured');
+
+ // Validate JWT guard
+ logger.log('Validating Ability guard on controllers...');
+ const guardValidator = app.get(GuardValidator);
+ await guardValidator.validateJwtGuard();
+ logger.log('✅ Ability guard validation completed');
+
+ // Start server
+ const listen_addr = process.env.LISTEN_ADDR || '::';
+ const port = parseInt(process.env.PORT) || 3000;
+
+ logger.log(`Starting server on ${listen_addr}:${port}...`);
+ await app.listen(port, listen_addr, async function () {
+ logStartupInfo(configService, logger);
+ });
+ } catch (error) {
+ logger.error('❌ Failed to bootstrap application:', error);
+ process.exit(1);
}
+}
- const configService = app.get(ConfigService);
+function setupGracefulShutdown(app: NestExpressApplication, logger: any) {
+ const gracefulShutdown = async (signal: string) => {
+ logShutdownInfo(signal, logger);
+ try {
+ await app.close();
+ logger.log('✅ Application closed successfully');
+ process.exit(0);
+ } catch (error) {
+ logger.error('❌ Error during application shutdown:', error);
+ process.exit(1);
+ }
+ };
- custom.setHttpOptionsDefaults({
- timeout: parseInt(process.env.OIDC_CONNECTION_TIMEOUT || '3500'), // Default 3.5 seconds
- });
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
+}
+function setupApplicationMiddleware(app: NestExpressApplication) {
app.useLogger(app.get(Logger));
app.useGlobalInterceptors(new ResponseInterceptor(app.get(Reflector), app.get(Logger), app.get(EventEmitter2)));
app.useGlobalFilters(new AllExceptionsFilter(app.get(Logger)));
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
app.useWebSocketAdapter(new WsAdapter(app));
+}
+function configureUrlPrefix() {
const hasSubPath = process.env.SUB_PATH !== undefined;
- const UrlPrefix = hasSubPath ? process.env.SUB_PATH : '';
+ const urlPrefix = hasSubPath ? process.env.SUB_PATH : '';
// Exclude these endpoints from prefix. These endpoints are required for health checks.
const pathsToExclude = [];
@@ -203,69 +182,84 @@ async function bootstrap() {
pathsToExclude.push({ path: '/health', method: RequestMethod.GET });
pathsToExclude.push({ path: '/api/health', method: RequestMethod.GET });
- app.setGlobalPrefix(UrlPrefix + 'api', {
- exclude: pathsToExclude,
- });
+ return { urlPrefix, pathsToExclude };
+}
+
+function setupBodyParsers(app: NestExpressApplication, configService: ConfigService) {
+ const maxSize = configService.get('MAX_JSON_SIZE') || '50mb';
app.use(compression());
app.use(cookieParser());
- app.use(json({ limit: '50mb' }));
- app.use(urlencoded({ extended: true, limit: '50mb', parameterLimit: 1000000 }));
-
- app.enableVersioning({
- type: VersioningType.URI,
- defaultVersion: VERSION_NEUTRAL,
- });
-
- setSecurityHeaders(app, configService);
-
- app.use(`${UrlPrefix}/assets`, express.static(join(__dirname, '/assets')));
-
- const listen_addr = process.env.LISTEN_ADDR || '::';
- const port = parseInt(process.env.PORT) || 3000;
-
- const guardValidator = app.get(GuardValidator);
- // Run the validation
- await guardValidator.validateJwtGuard();
-
- await app.listen(port, listen_addr, async function () {
- const tooljetHost = configService.get('TOOLJET_HOST');
- const subPath = configService.get('SUB_PATH');
-
- console.log(`Ready to use at ${tooljetHost}${subPath || ''} 🚀`);
- });
-}
-
-// Bootstrap global agent only if TOOLJET_HTTP_PROXY is set
-if (process.env.TOOLJET_HTTP_PROXY) {
- process.env['GLOBAL_AGENT_HTTP_PROXY'] = process.env.TOOLJET_HTTP_PROXY;
- globalAgentBootstrap();
+ app.use(json({ verify: rawBodyBuffer, limit: maxSize }));
+ app.use(
+ urlencoded({
+ verify: rawBodyBuffer,
+ extended: true,
+ limit: maxSize,
+ parameterLimit: 1000000,
+ })
+ );
}
async function bootstrapWorker() {
- appContext = await NestFactory.createApplicationContext(await AppModule.register({ IS_GET_CONTEXT: false }));
+ const logger = createLogger('Worker');
+ logger.log('🚀 Starting ToolJet worker bootstrap...');
- process.on('SIGINT', async () => {
- console.log('SIGINT signal received: closing application...');
- temporalService.shutDownWorker();
- });
+ try {
+ logger.log('Creating application context...');
+ appContext = await NestFactory.createApplicationContext(await AppModule.register({ IS_GET_CONTEXT: false }));
+ logger.log('✅ Application context created');
- process.on('SIGTERM', async () => {
- console.log('SIGTERM signal received: closing application...');
- temporalService.shutDownWorker();
- });
+ // Setup graceful shutdown for worker
+ setupWorkerGracefulShutdown(logger);
- const importPath = await getImportPath(false);
- const { TemporalService } = await import(`${importPath}/workflows/services/temporal.service`);
+ logger.log('Initializing Temporal service...');
+ const importPath = await getImportPath(false);
+ const { TemporalService } = await import(`${importPath}/workflows/services/temporal.service`);
- const temporalService = appContext.get(TemporalService);
- await temporalService.runWorker();
- await appContext.close();
+ const temporalService = appContext.get(TemporalService);
+ logger.log('✅ Temporal service initialized');
+
+ logger.log('Starting Temporal worker...');
+ await temporalService.runWorker();
+ logger.log('✅ Temporal worker started');
+
+ await appContext.close();
+ logger.log('✅ Worker bootstrap completed');
+ } catch (error) {
+ logger.error('❌ Failed to bootstrap worker:', error);
+ process.exit(1);
+ }
+}
+
+function setupWorkerGracefulShutdown(logger: any) {
+ const gracefulShutdown = async (signal: string) => {
+ logShutdownInfo(signal, logger);
+ try {
+ const importPath = await getImportPath(false);
+ const { TemporalService } = await import(`${importPath}/workflows/services/temporal.service`);
+ const temporalService = appContext.get(TemporalService);
+
+ logger.log('Shutting down Temporal worker...');
+ temporalService.shutDownWorker();
+ logger.log('✅ Temporal worker shutdown completed');
+ } catch (error) {
+ logger.error('❌ Error during worker shutdown:', error);
+ }
+ };
+
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
}
export function getAppContext(): INestApplicationContext {
return appContext;
}
+
+// Bootstrap global agent only if TOOLJET_HTTP_PROXY is set
+setupGlobalAgent();
+
+// Main execution
if (getTooljetEdition() === TOOLJET_EDITIONS.EE) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
process.env.WORKER ? bootstrapWorker() : bootstrap();
diff --git a/server/src/modules/app-git/ability/guard.ts b/server/src/modules/app-git/ability/guard.ts
index 41c7bf9ed9..286577b528 100644
--- a/server/src/modules/app-git/ability/guard.ts
+++ b/server/src/modules/app-git/ability/guard.ts
@@ -1,23 +1,21 @@
import { Injectable } from '@nestjs/common';
-import { AppGitAbilityFactory } from '.';
+import { FeatureAbilityFactory } from '.';
import { AbilityGuard } from '@modules/app/guards/ability.guard';
-import { App } from '@entities/app.entity';
import { ResourceDetails } from '@modules/app/types';
import { MODULES } from '@modules/app/constants/modules';
+import { AppGitSync } from '@entities/app_git_sync.entity';
@Injectable()
-export class AppGitAbilityGuard extends AbilityGuard {
- protected getResource(): ResourceDetails {
- return {
- resourceType: MODULES.APP_GIT,
- };
+export class FeatureAbilityGuard extends AbilityGuard {
+ protected getResource(): ResourceDetails | ResourceDetails[] {
+ return [{ resourceType: MODULES.APP_GIT }, { resourceType: MODULES.APP }];
}
protected getAbilityFactory() {
- return AppGitAbilityFactory;
+ return FeatureAbilityFactory;
}
protected getSubjectType() {
- return App;
+ return AppGitSync;
}
}
diff --git a/server/src/modules/app-git/ability/index.ts b/server/src/modules/app-git/ability/index.ts
index 8996f754b7..4d3dc8d5f0 100644
--- a/server/src/modules/app-git/ability/index.ts
+++ b/server/src/modules/app-git/ability/index.ts
@@ -3,15 +3,16 @@ import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
import { AbilityFactory } from '@modules/app/ability-factory';
import { UserAllPermissions } from '@modules/app/types';
import { FEATURE_KEY } from '../constants';
-import { App } from '@entities/app.entity';
+import { AppGitSync } from '@entities/app_git_sync.entity';
+import { MODULES } from '@modules/app/constants/modules';
-type Subjects = InferSubjects | 'all';
+type Subjects = InferSubjects | 'all';
export type AppGitAbility = Ability<[FEATURE_KEY, Subjects]>;
@Injectable()
-export class AppGitAbilityFactory extends AbilityFactory {
+export class FeatureAbilityFactory extends AbilityFactory {
protected getSubjectType() {
- return App;
+ return AppGitSync;
}
protected defineAbilityFor(
@@ -21,48 +22,44 @@ export class AppGitAbilityFactory extends AbilityFactory
request?: any
): void {
const appId = request?.tj_resource_id;
- const { superAdmin, isAdmin, isBuilder, userPermission } = UserAllPermissions;
+ const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
- const userAppGitPermissions = userPermission?.APP;
+ const userAppGitPermissions = userPermission?.[MODULES.APP];
const isAllAppsEditable = !!userAppGitPermissions?.isAllEditable;
const isAllAppsCreatable = !!userPermission?.appCreate;
- const isAllAppsViewable = !!userAppGitPermissions?.isAllViewable;
- // Grant feature-level access based on resource actions
- if (isAdmin || superAdmin || isBuilder) {
- // Admin or Super Admin gets full access to all features
- can(FEATURE_KEY.GIT_CREATE_APP, App);
- can(FEATURE_KEY.GIT_UPDATE_APP, App);
- can(FEATURE_KEY.GIT_GET_APPS, App);
- can(FEATURE_KEY.GIT_GET_APP, App);
- can(FEATURE_KEY.GIT_GET_APP_CONFIG, App);
- can(FEATURE_KEY.GIT_SYNC_APP, App);
- can(FEATURE_KEY.GIT_APP_VERSION_RENAME, App);
- can(FEATURE_KEY.GIT_APP_CONFIGS_UPDATE, App);
- can(FEATURE_KEY.GIT_FETCH_APP_CONFIGS, App);
- return;
- }
- // READ-based features
- if (
- isAllAppsViewable ||
- (userAppGitPermissions?.viewableAppsId?.length && appId && userAppGitPermissions?.viewableAppsId?.includes(appId))
- ) {
- can(FEATURE_KEY.GIT_GET_APPS, App);
- can(FEATURE_KEY.GIT_GET_APP, App);
+ // Used for public endpoint to get the app configs
+ can(FEATURE_KEY.GIT_FETCH_APP_CONFIGS, AppGitSync);
+
+ // Grant feature-level access based on resource actions
+ if (isAdmin || superAdmin) {
+ // Admin or Super Admin gets full access to all features
+ can(FEATURE_KEY.GIT_CREATE_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_UPDATE_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_GET_APPS, AppGitSync);
+ can(FEATURE_KEY.GIT_GET_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_GET_APP_CONFIG, AppGitSync);
+ can(FEATURE_KEY.GIT_SYNC_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_APP_VERSION_RENAME, AppGitSync);
+ can(FEATURE_KEY.GIT_APP_CONFIGS_UPDATE, AppGitSync);
+ return;
}
// CREATE-based features
if (isAllAppsCreatable) {
- can(FEATURE_KEY.GIT_CREATE_APP, App);
+ can(FEATURE_KEY.GIT_CREATE_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_GET_APPS, AppGitSync);
}
-
- // UPDATE-based features
if (
isAllAppsEditable ||
(userAppGitPermissions?.editableAppsId?.length && appId && userAppGitPermissions.editableAppsId.includes(appId))
) {
- can(FEATURE_KEY.GIT_UPDATE_APP, App);
- can(FEATURE_KEY.GIT_SYNC_APP, App);
+ can(FEATURE_KEY.GIT_UPDATE_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_SYNC_APP, AppGitSync);
+ can(FEATURE_KEY.GIT_APP_VERSION_RENAME, AppGitSync);
+ can(FEATURE_KEY.GIT_APP_CONFIGS_UPDATE, AppGitSync);
+ can(FEATURE_KEY.GIT_GET_APP, AppGitSync); // Used for syncing data from inside the application so only users with edit permission can perform the operation
+ can(FEATURE_KEY.GIT_GET_APP_CONFIG, AppGitSync);
}
// Additional checks based on specific actions
@@ -71,7 +68,7 @@ export class AppGitAbilityFactory extends AbilityFactory
appId &&
userAppGitPermissions.editableAppsId.includes(appId)
) {
- can(FEATURE_KEY.GIT_GET_APP_CONFIG, App);
+ can(FEATURE_KEY.GIT_GET_APP_CONFIG, AppGitSync);
}
}
}
diff --git a/server/src/modules/app-git/constants/feature.ts b/server/src/modules/app-git/constants/feature.ts
index 5c5cada809..75cb30255e 100644
--- a/server/src/modules/app-git/constants/feature.ts
+++ b/server/src/modules/app-git/constants/feature.ts
@@ -5,14 +5,14 @@ import { LICENSE_FIELD } from '@modules/licensing/constants';
export const FEATURES: FeaturesConfig = {
[MODULES.APP_GIT]: {
- [FEATURE_KEY.GIT_CREATE_APP]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_GET_APP]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_GET_APPS]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_GET_APP_CONFIG]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_SYNC_APP]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_UPDATE_APP]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_APP_VERSION_RENAME]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_APP_CONFIGS_UPDATE]: { license: LICENSE_FIELD.VALID },
- [FEATURE_KEY.GIT_FETCH_APP_CONFIGS]: { isPublic: true },
+ [FEATURE_KEY.GIT_CREATE_APP]: { license: LICENSE_FIELD.GIT_SYNC }, // Used for importing an application from git to any workspace
+ [FEATURE_KEY.GIT_GET_APP]: { license: LICENSE_FIELD.GIT_SYNC }, // Used to fetch the latest git commit data for syncing the application
+ [FEATURE_KEY.GIT_GET_APPS]: { license: LICENSE_FIELD.GIT_SYNC }, // Used for listing all the application from GIT
+ [FEATURE_KEY.GIT_GET_APP_CONFIG]: { license: LICENSE_FIELD.GIT_SYNC }, // Used for getting latest app configs and creates an application if app is not already created
+ [FEATURE_KEY.GIT_SYNC_APP]: { license: LICENSE_FIELD.GIT_SYNC }, // Push an application to git
+ [FEATURE_KEY.GIT_UPDATE_APP]: { license: LICENSE_FIELD.GIT_SYNC }, // Update the application with latest git commit
+ [FEATURE_KEY.GIT_APP_VERSION_RENAME]: { license: LICENSE_FIELD.GIT_SYNC }, // Rename app/version name
+ [FEATURE_KEY.GIT_APP_CONFIGS_UPDATE]: { license: LICENSE_FIELD.GIT_SYNC }, // Used to update the permission to allow app edit for imported applications
+ [FEATURE_KEY.GIT_FETCH_APP_CONFIGS]: {}, // Used for fetching app configs
},
};
diff --git a/server/src/modules/app-git/controller.ts b/server/src/modules/app-git/controller.ts
index df68e38551..60d058b531 100644
--- a/server/src/modules/app-git/controller.ts
+++ b/server/src/modules/app-git/controller.ts
@@ -10,9 +10,7 @@ import {
} from '@modules/app-git/dto';
import { MODULES } from '@modules/app/constants/modules';
import { InitModule } from '@modules/app/decorators/init-module';
-import { LICENSE_FIELD } from '@modules/licensing/constants';
import { InitFeature } from '@modules/app/decorators/init-feature.decorator';
-import { RequireFeature } from '@modules/app/decorators/require-feature.decorator';
import { FEATURE_KEY } from './constants';
@InitModule(MODULES.APP_GIT)
@@ -20,7 +18,6 @@ import { FEATURE_KEY } from './constants';
export class AppGitController {
constructor() {}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_GET_APPS)
@UseGuards(JwtAuthGuard)
@Get('gitpull')
@@ -28,7 +25,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_SYNC_APP)
@UseGuards(JwtAuthGuard)
@Post('gitpush/:appGitId/:versionId')
@@ -40,7 +36,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_GET_APP)
@UseGuards(JwtAuthGuard)
@Get('gitpull/app/:appId')
@@ -48,7 +43,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_GET_APP_CONFIG)
@UseGuards(JwtAuthGuard)
@Get(':workspaceId/app/:versionId')
@@ -60,7 +54,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_CREATE_APP)
@UseGuards(JwtAuthGuard)
@Post('gitpull/app')
@@ -68,7 +61,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_UPDATE_APP)
@UseGuards(JwtAuthGuard)
@Post('gitpull/app/:appId')
@@ -76,7 +68,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_APP_VERSION_RENAME)
@Put('app/:appId/rename')
async renameAppOrVersion(
@@ -87,7 +78,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@InitFeature(FEATURE_KEY.GIT_APP_CONFIGS_UPDATE)
@Put(':appId/configs')
async updateAppGitConfigs(
@@ -98,7 +88,6 @@ export class AppGitController {
throw new NotFoundException();
}
- @RequireFeature(LICENSE_FIELD.GIT_SYNC)
@Get(':workspaceId/app/:versionId/configs')
async getAppGitConfigs(
@User() user,
diff --git a/server/src/modules/app-git/guards/app-resource.guard.ts b/server/src/modules/app-git/guards/app-resource.guard.ts
new file mode 100644
index 0000000000..8c5d2d2b42
--- /dev/null
+++ b/server/src/modules/app-git/guards/app-resource.guard.ts
@@ -0,0 +1,38 @@
+import { Injectable, CanActivate, ExecutionContext, BadRequestException, NotFoundException } from '@nestjs/common';
+import { AppsRepository } from '@modules/apps/repository';
+import { User } from '@entities/user.entity';
+import { VersionRepository } from '@modules/versions/repository';
+import { App } from '@entities/app.entity';
+// This Guard should be used after jwt auth guard
+@Injectable()
+export class AppResourceGuard implements CanActivate {
+ constructor(
+ protected readonly appRepository: AppsRepository,
+ protected readonly versionRepository: VersionRepository
+ ) {}
+
+ async canActivate(context: ExecutionContext): Promise {
+ const request = context.switchToHttp().getRequest();
+ const { appId, versionId } = request.params;
+ const user: User = request.user;
+ if (!appId && !versionId) {
+ throw new BadRequestException('App ID or version ID must be provided');
+ }
+
+ let app: App;
+ if (appId) {
+ app = request.tj_app || (appId && (await this.appRepository.findById(appId, user.organizationId)));
+ } else if (versionId) {
+ const version = await this.versionRepository.getAppVersionById(versionId);
+ app = version?.app;
+ }
+ if (!app) {
+ throw new NotFoundException('App not found. Invalid App id');
+ }
+
+ // Attach the found app to the request
+ request.tj_app = app;
+ request.tj_resource_id = app.id;
+ return true;
+ }
+}
diff --git a/server/src/modules/app-git/module.ts b/server/src/modules/app-git/module.ts
index 3178afc212..e9929ed888 100644
--- a/server/src/modules/app-git/module.ts
+++ b/server/src/modules/app-git/module.ts
@@ -6,7 +6,7 @@ import { AppsModule } from '@modules/apps/module';
import { TooljetDbModule } from '@modules/tooljet-db/module';
import { ImportExportResourcesModule } from '@modules/import-export-resources/module';
import { VersionModule } from '@modules/versions/module';
-import { AppGitAbilityFactory } from '@modules/app-git/ability/index';
+import { FeatureAbilityFactory } from '@modules/app-git/ability/index';
import { OrganizationGitSyncRepository } from '@modules/git-sync/repository';
import { AppGitRepository } from './repository';
import { SubModule } from '@modules/app/sub-module';
@@ -58,7 +58,7 @@ export class AppGitModule extends SubModule {
HTTPSAppGitUtilityService,
GitLabAppGitUtilityService,
VersionRepository,
- AppGitAbilityFactory,
+ FeatureAbilityFactory,
AppVersionRenameListener,
],
exports: [SSHAppGitUtilityService, HTTPSAppGitUtilityService, GitLabAppGitUtilityService],
diff --git a/server/src/modules/app/constants/module-info.ts b/server/src/modules/app/constants/module-info.ts
index 8d54a1226b..b1fb9fce3e 100644
--- a/server/src/modules/app/constants/module-info.ts
+++ b/server/src/modules/app/constants/module-info.ts
@@ -40,6 +40,7 @@ import { FEATURES as APP_PERMISSIONS_FEATURES } from '@modules/app-permissions/c
import { FEATURES as EXTERNAL_API_FEATURES } from '@modules/external-apis/constants/feature';
import { FEATURES as MODULE_FEATURES } from '@modules/modules/constants/feature';
import { FEATURES as APP_GIT_FEATURES } from '@modules/app-git/constants/feature';
+import { FEATURES as GIT_SYNC_FEATURES } from '@modules/git-sync/constants/feature';
const GROUP_PERMISSIONS_FEATURES =
getTooljetEdition() === TOOLJET_EDITIONS.EE ? GROUP_PERMISSIONS_FEATURES_EE : GROUP_PERMISSIONS_FEATURES_CE;
@@ -85,4 +86,5 @@ export const MODULE_INFO: { [key: string]: any } = {
...EXTERNAL_API_FEATURES,
...MODULE_FEATURES,
...APP_GIT_FEATURES,
+ ...GIT_SYNC_FEATURES,
};
diff --git a/server/src/modules/app/constants/modules.ts b/server/src/modules/app/constants/modules.ts
index 4e059420db..8a1f3c4930 100644
--- a/server/src/modules/app/constants/modules.ts
+++ b/server/src/modules/app/constants/modules.ts
@@ -22,7 +22,6 @@ export enum MODULES {
CUSTOM_STYLES = 'CustomStyles',
SMTP = 'SMTP',
ONBOARDING = 'Onboarding',
- APP_GIT = 'AppGit', //register
INSTANCE_SETTINGS = 'instanceSettings',
LICENSING = 'Licensing',
FILE = 'file',
@@ -41,5 +40,6 @@ export enum MODULES {
EXTERNAL_APIS = 'externalApis',
ORGANIZATION_PAYMENTS = 'organizationPayments',
MODULES = 'Modules',
- GIT_SYNC = 'GitSync', //register
+ APP_GIT = 'AppGit',
+ GIT_SYNC = 'GitSync',
}
diff --git a/server/src/modules/app/guards/cloud-feature.guard.ts b/server/src/modules/app/guards/cloud-feature.guard.ts
new file mode 100644
index 0000000000..fac40fd723
--- /dev/null
+++ b/server/src/modules/app/guards/cloud-feature.guard.ts
@@ -0,0 +1,10 @@
+import { getTooljetEdition } from '@helpers/utils.helper';
+import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
+import { TOOLJET_EDITIONS } from '../constants';
+
+@Injectable()
+export class CloudFeatureGuard implements CanActivate {
+ async canActivate(context: ExecutionContext): Promise {
+ return getTooljetEdition() === TOOLJET_EDITIONS.Cloud;
+ }
+}
diff --git a/server/src/modules/app/validators/feature-guard.validator.ts b/server/src/modules/app/validators/feature-guard.validator.ts
index f3b76d1e5b..2c8f9aa587 100644
--- a/server/src/modules/app/validators/feature-guard.validator.ts
+++ b/server/src/modules/app/validators/feature-guard.validator.ts
@@ -7,9 +7,12 @@ import { GUARDS_METADATA } from '@nestjs/common/constants';
@Injectable()
// Validates if all routes are guarded with AbilityGuard
export class GuardValidator {
- private unprotectedRoutes: string[] = [];
+ private unprotectedRoutes = new Set();
- constructor(private readonly metadataScanner: MetadataScanner, private readonly modulesContainer: ModulesContainer) {}
+ constructor(
+ private readonly metadataScanner: MetadataScanner,
+ private readonly modulesContainer: ModulesContainer
+ ) {}
async validateJwtGuard() {
console.log('Validating if all routes are guarded with AbilityGuard');
@@ -53,13 +56,13 @@ export class GuardValidator {
const hasJwtGuard = this.hasJwtAuthGuard(allGuards);
if (!hasJwtGuard) {
- this.unprotectedRoutes.push(`${requestMethod} ${routePath}`);
+ this.unprotectedRoutes.add(`${requestMethod} ${routePath}`);
}
}
}
}
- if (this.unprotectedRoutes.length > 0) {
+ if (this.unprotectedRoutes.size > 0) {
console.error(
'\x1b[31m%s\x1b[0m',
'ERROR: The following routes are not protected by AbilityGuard or its descendants:'
@@ -105,7 +108,7 @@ export class GuardValidator {
return false;
} catch (error) {
- console.error('Error checking guard:', guard);
+ console.error('Error checking guard:', guard, error);
return false;
}
});
diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts
index 2f57c6d737..cfbc5280f6 100644
--- a/server/src/modules/apps/service.ts
+++ b/server/src/modules/apps/service.ts
@@ -60,7 +60,7 @@ export class AppsService implements IAppsService {
protected readonly componentsService: ComponentsService,
protected readonly eventEmitter: EventEmitter2,
protected readonly appGitRepository: AppGitRepository
- ) { }
+ ) {}
async create(user: User, appCreateDto: AppCreateDto) {
const { name, icon, type, prompt } = appCreateDto;
return await dbTransactionWrap(async (manager: EntityManager) => {
@@ -107,12 +107,12 @@ export class AppsService implements IAppsService {
: versionName
? await this.versionRepository.findByName(versionName, app.id)
: // Handle version retrieval based on env
- await this.versionRepository.findLatestVersionForEnvironment(
- app.id,
- envId,
- environmentName,
- app.organizationId
- );
+ await this.versionRepository.findLatestVersionForEnvironment(
+ app.id,
+ envId,
+ environmentName,
+ app.organizationId
+ );
if (!version) {
throw new NotFoundException("Couldn't found app version. Please check the version name");
@@ -332,7 +332,7 @@ export class AppsService implements IAppsService {
: await this.versionRepository.findVersion(app.editingVersion?.id);
const pagesForVersion = app.editingVersion
- ? await this.pageService.findPagesForVersion(versionToLoad.id, user.organizationId)
+ ? await this.pageService.findPagesForVersion(versionToLoad.id, app.organizationId)
: [];
const eventsForVersion = app.editingVersion ? await this.eventService.findEventsForVersion(versionToLoad.id) : [];
const appTheme = await this.organizationThemeUtilService.getTheme(
diff --git a/server/src/modules/auth/constants/feature.ts b/server/src/modules/auth/constants/feature.ts
index c21c4195ea..6a32030180 100644
--- a/server/src/modules/auth/constants/feature.ts
+++ b/server/src/modules/auth/constants/feature.ts
@@ -51,5 +51,17 @@ export const FEATURES: FeaturesConfig = {
[FEATURE_KEY.OAUTH_SAML_RESPONSE]: {
isPublic: true,
},
+ [FEATURE_KEY.AI_ONBOARDING]: {
+ isPublic: true,
+ },
+ [FEATURE_KEY.AI_ONBOARDING_SSO]: {
+ isPublic: true,
+ },
+ [FEATURE_KEY.AI_COOKIE_SET]: {
+ isPublic: true,
+ },
+ [FEATURE_KEY.AI_COOKIE_DELETE]: {
+ isPublic: true,
+ },
},
};
diff --git a/server/src/modules/auth/constants/index.ts b/server/src/modules/auth/constants/index.ts
index ef769b9230..0ddfe50e0b 100644
--- a/server/src/modules/auth/constants/index.ts
+++ b/server/src/modules/auth/constants/index.ts
@@ -21,4 +21,10 @@ export enum FEATURE_KEY {
OAUTH_SAML_CONFIGS = '/oauth/saml/configs/:configId',
OAUTH_COMMON_SIGN_IN = '/oauth/sign-in/common/:ssoType',
OAUTH_SAML_RESPONSE = '/oauth/saml/:configId',
+
+ // AI Onboarding
+ AI_ONBOARDING = 'aiOnboarding', // POST 'ai-onboarding'
+ AI_ONBOARDING_SSO = 'aiOnboardingSSO', // POST 'sign-in/common/:ssoType
+ AI_COOKIE_SET = 'aiCookieSet', // POST 'set-ai-cookie'
+ AI_COOKIE_DELETE = 'aiCookieDelete', // GET 'delete-ai-cookies'
}
diff --git a/server/src/modules/auth/decorators/ai-cookie.decorator.ts b/server/src/modules/auth/decorators/ai-cookie.decorator.ts
new file mode 100644
index 0000000000..fc13ae57af
--- /dev/null
+++ b/server/src/modules/auth/decorators/ai-cookie.decorator.ts
@@ -0,0 +1,13 @@
+import { createParamDecorator, ExecutionContext } from '@nestjs/common';
+
+export const AiCookies = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
+ const request = ctx.switchToHttp().getRequest();
+ let aiCookies = {};
+ if (request.cookies['tj_ai_prompt'] || request.cookies['tj_template_id']) {
+ aiCookies = {
+ tj_ai_prompt: request.cookies['tj_ai_prompt'],
+ tj_template_id: request.cookies['tj_template_id'],
+ };
+ }
+ return aiCookies || {};
+});
diff --git a/server/src/modules/auth/dto/index.ts b/server/src/modules/auth/dto/index.ts
index b166d74a75..6bb69a2cfe 100644
--- a/server/src/modules/auth/dto/index.ts
+++ b/server/src/modules/auth/dto/index.ts
@@ -1,5 +1,5 @@
import { IsEmail, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength, MinLength } from 'class-validator';
-import { lowercaseString } from 'src/helpers/utils.helper';
+import { lowercaseString, sanitizeInput } from 'src/helpers/utils.helper';
import { Transform } from 'class-transformer';
export class AppAuthenticationDto {
@@ -74,3 +74,20 @@ export class ChangePasswordDto {
@MaxLength(100, { message: 'Password should be Max 100 characters' })
newPassword: string;
}
+
+export class CreateAiUserDto {
+ @IsString()
+ @Transform(({ value }) => sanitizeInput(value))
+ name: string;
+
+ @IsEmail()
+ @Transform(({ value }) => {
+ const newValue = sanitizeInput(value);
+ return lowercaseString(newValue);
+ })
+ email: string;
+
+ @IsString()
+ @MinLength(5, { message: 'Password should contain more than 5 letters' })
+ password: string;
+}
diff --git a/server/src/modules/auth/interfaces/IController.ts b/server/src/modules/auth/interfaces/IController.ts
index aa7545d49f..c7831b21b6 100644
--- a/server/src/modules/auth/interfaces/IController.ts
+++ b/server/src/modules/auth/interfaces/IController.ts
@@ -1,6 +1,7 @@
import { Response } from 'express';
import { User } from '@entities/user.entity';
-import { AppAuthenticationDto, AppForgotPasswordDto, AppPasswordResetDto } from '../dto';
+import { AppAuthenticationDto, AppForgotPasswordDto, AppPasswordResetDto, CreateAiUserDto } from '../dto';
+import { SSOType } from '@entities/sso_config.entity';
export interface IAuthController {
login(appAuthDto: AppAuthenticationDto, response: Response): Promise;
@@ -16,3 +17,39 @@ export interface IAuthController {
forgotPassword(appAuthDto: AppForgotPasswordDto): Promise>;
resetPassword(appAuthDto: AppPasswordResetDto): Promise>;
}
+
+export interface IWebsiteAuthController {
+ /**
+ * Handles user onboarding process
+ * @param onboardingData - The user data for onboarding
+ * @param response - Express response object
+ * @returns Promise with onboarding result
+ */
+ onboard(onboardingData: CreateAiUserDto, response: Response): Promise;
+
+ /**
+ * Handles common sign-in process for SSO providers
+ * @param ssoType - The SSO type (Google or Git)
+ * @param body - Request body data
+ * @param user - Authenticated user object
+ * @param response - Express response object
+ * @returns Promise with sign-in result
+ */
+ commonSignIn(ssoType: SSOType.GOOGLE | SSOType.GIT, body: any, user: any, response: Response): Promise;
+
+ /**
+ * Sets AI-related cookies in response (Safari browser support)
+ * @param response - Express response object
+ * @param body - Cookie data to set
+ * @returns Promise with cookie setting result
+ */
+ setAiCookie(response: Response, body: Record): any;
+
+ /**
+ * Deletes AI-related cookies from response
+ * @param response - Express response object
+ * @param cookies - Current cookies to clear
+ * @returns Promise with cookie clearing result
+ */
+ deleteAiCookies(response: Response, cookies: Record): any;
+}
diff --git a/server/src/modules/auth/interfaces/IService.ts b/server/src/modules/auth/interfaces/IService.ts
index cdeafa391d..256ab68724 100644
--- a/server/src/modules/auth/interfaces/IService.ts
+++ b/server/src/modules/auth/interfaces/IService.ts
@@ -1,6 +1,8 @@
import { Response } from 'express';
import { User } from '@entities/user.entity';
-import { AppAuthenticationDto } from '../dto';
+import { AppAuthenticationDto, CreateAiUserDto } from '../dto';
+import { EntityManager } from 'typeorm';
+import { SSOType } from '@entities/sso_config.entity';
export interface IAuthService {
login(response: Response, appAuthDto: AppAuthenticationDto, organizationId?: string, user?: User): Promise;
@@ -9,3 +11,38 @@ export interface IAuthService {
forgotPassword(email: string): Promise;
resetPassword(token: string, password: string): Promise;
}
+
+export interface IWebsiteAuthService {
+ /**
+ * Handles the complete onboarding process for new users
+ * @param userParams - User data for onboarding
+ * @param existingUser - Optional existing user object
+ * @param response - Express response object for setting cookies
+ * @param ssoType - Optional SSO type for social login
+ * @param manager - Optional database transaction manager
+ * @returns Promise with login result payload
+ */
+ handleOnboarding(
+ userParams: CreateAiUserDto,
+ existingUser?: User,
+ response?: Response,
+ ssoType?: SSOType.GOOGLE | SSOType.GIT,
+ manager?: EntityManager
+ ): Promise;
+
+ /**
+ * Sets AI-related session cookies in response
+ * @param response - Express response object
+ * @param keyValues - Cookie key-value pairs to set
+ * @returns Promise with success message
+ */
+ setSessionAICookies(response: Response, keyValues: Record): { message: string };
+
+ /**
+ * Clears AI-related session cookies from response
+ * @param response - Express response object
+ * @param cookies - Current cookies to clear
+ * @returns Promise with success message
+ */
+ clearSessionAICookies(response: Response, cookies: Record): { message: string };
+}
diff --git a/server/src/modules/auth/module.ts b/server/src/modules/auth/module.ts
index d5bbed351c..4c0174e0fe 100644
--- a/server/src/modules/auth/module.ts
+++ b/server/src/modules/auth/module.ts
@@ -4,7 +4,6 @@ import { InstanceSettingsModule } from '@modules/instance-settings/module';
import { OrganizationUsersModule } from '@modules/organization-users/module';
import { RolesModule } from '@modules/roles/module';
import { GroupPermissionsModule } from '@modules/group-permissions/module';
-import { OnboardingModule } from '@modules/onboarding/module';
import { ProfileModule } from '@modules/profile/module';
import { UserRepository } from '@modules/users/repositories/repository';
import { OrganizationRepository } from '@modules/organizations/repository';
@@ -18,6 +17,7 @@ import { SetupOrganizationsModule } from '@modules/setup-organization/module';
import { SSOConfigsRepository } from '@modules/login-configs/repository';
import { AppEnvironmentsModule } from '@modules/app-environments/module';
import { SubModule } from '@modules/app/sub-module';
+import { OnboardingModule } from '@modules/onboarding/module';
@Module({})
export class AuthModule extends SubModule {
@@ -33,6 +33,8 @@ export class AuthModule extends SubModule {
GoogleOAuthService,
OidcOAuthService,
LdapService,
+ WebsiteAuthController,
+ WebsiteAuthService,
} = await this.getProviders(configs, 'auth', [
'controller',
'service',
@@ -44,6 +46,8 @@ export class AuthModule extends SubModule {
'oauth/util-services/google-oauth.service',
'oauth/util-services/oidc-auth.service',
'oauth/util-services/ldap.service',
+ 'website/controller',
+ 'website/service',
]);
return {
@@ -53,16 +57,15 @@ export class AuthModule extends SubModule {
await InstanceSettingsModule.register(configs),
await OrganizationUsersModule.register(configs),
await RolesModule.register(configs),
- await OnboardingModule.register(configs),
await GroupPermissionsModule.register(configs),
await ProfileModule.register(configs),
await SessionModule.register(configs),
- await OrganizationUsersModule.register(configs),
await LoginConfigsModule.register(configs),
await SetupOrganizationsModule.register(configs),
await AppEnvironmentsModule.register(configs),
+ await OnboardingModule.register(configs),
],
- controllers: [AuthController, OauthController],
+ controllers: [AuthController, OauthController, WebsiteAuthController],
providers: [
AuthService,
UserRepository,
@@ -80,6 +83,7 @@ export class AuthModule extends SubModule {
FeatureAbilityFactory,
GroupPermissionsRepository,
SSOConfigsRepository,
+ WebsiteAuthService,
],
exports: [AuthUtilService],
};
diff --git a/server/src/modules/auth/oauth/service.ts b/server/src/modules/auth/oauth/service.ts
index 2cc1be2dea..b6b6ea2644 100644
--- a/server/src/modules/auth/oauth/service.ts
+++ b/server/src/modules/auth/oauth/service.ts
@@ -37,7 +37,7 @@ import { LicenseUserService } from '@modules/licensing/services/user.service';
import { OnboardingUtilService } from '@modules/onboarding/util.service';
import { SessionUtilService } from '@modules/session/util.service';
import { SetupOrganizationsUtilService } from '@modules/setup-organization/util.service';
-const uuid = require('uuid');
+import * as uuid from 'uuid';
@Injectable()
export class OauthService implements IOAuthService {
@@ -221,7 +221,11 @@ export class OauthService implements IOAuthService {
if (!isInviteRedirect) {
// no SSO login enabled organization available for user - creating new one
const { name, slug } = generateNextNameAndSlug('My workspace');
- organizationDetails = await this.setupOrganizationsUtilService.create({ name, slug }, userDetails, manager);
+ organizationDetails = await this.setupOrganizationsUtilService.create(
+ { name, slug },
+ userDetails,
+ manager
+ );
await this.userRepository.updateOne(
userDetails.id,
{ defaultOrganizationId: organizationDetails.id },
@@ -283,9 +287,8 @@ export class OauthService implements IOAuthService {
signupOrganizationId !== defaultOrganizationId;
let personalWorkspace: Organization;
if (shouldActivatePersonalWorkspace) {
- const defaultOrganizationUser = await this.organizationUsersRepository.getOrganizationUser(
- defaultOrganizationId
- );
+ const defaultOrganizationUser =
+ await this.organizationUsersRepository.getOrganizationUser(defaultOrganizationId);
await this.organizationUsersUtilService.activateOrganization(defaultOrganizationUser, manager);
}
diff --git a/server/src/modules/auth/types/index.ts b/server/src/modules/auth/types/index.ts
index 2283b240d6..351d1655e5 100644
--- a/server/src/modules/auth/types/index.ts
+++ b/server/src/modules/auth/types/index.ts
@@ -16,6 +16,10 @@ export interface Features {
[FEATURE_KEY.OAUTH_SAML_CONFIGS]: FeatureConfig;
[FEATURE_KEY.OAUTH_SAML_RESPONSE]: FeatureConfig;
[FEATURE_KEY.OAUTH_SIGN_IN]: FeatureConfig;
+ [FEATURE_KEY.AI_ONBOARDING]: FeatureConfig;
+ [FEATURE_KEY.AI_ONBOARDING_SSO]: FeatureConfig;
+ [FEATURE_KEY.AI_COOKIE_SET]: FeatureConfig;
+ [FEATURE_KEY.AI_COOKIE_DELETE]: FeatureConfig;
}
export interface FeaturesConfig {
diff --git a/server/src/modules/auth/website/controller.ts b/server/src/modules/auth/website/controller.ts
new file mode 100644
index 0000000000..a2ead1cd78
--- /dev/null
+++ b/server/src/modules/auth/website/controller.ts
@@ -0,0 +1,55 @@
+import { Controller, Post, Body, Res, UseGuards, Param, Get, NotImplementedException } from '@nestjs/common';
+import { Response } from 'express';
+import { SSOType } from '@entities/sso_config.entity';
+import { IWebsiteAuthController } from '../interfaces/IController';
+import { CreateAiUserDto } from '../dto';
+import { OrganizationAuthGuard } from '@modules/session/guards/organization-auth.guard';
+import { User } from '@modules/app/decorators/user.decorator';
+import { MODULES } from '@modules/app/constants/modules';
+import { InitModule } from '@modules/app/decorators/init-module';
+import { InitFeature } from '@modules/app/decorators/init-feature.decorator';
+import { FEATURE_KEY } from '../constants';
+import { AiCookies } from '../decorators/ai-cookie.decorator';
+import { FeatureAbilityGuard } from '../ability/guard';
+
+/*
+ This module is for ai onboarding from the website
+ Email and password signup and common ssos - google and git ssos will be supported
+*/
+@InitModule(MODULES.AUTH)
+@Controller('ai/onboarding')
+export class WebsiteAuthController implements IWebsiteAuthController {
+ @InitFeature(FEATURE_KEY.AI_ONBOARDING)
+ @UseGuards(FeatureAbilityGuard)
+ @Post()
+ async onboard(@Body() onboardingData: CreateAiUserDto, @Res({ passthrough: true }) response: Response) {
+ throw new NotImplementedException();
+ }
+
+ @InitFeature(FEATURE_KEY.AI_ONBOARDING_SSO)
+ @UseGuards(OrganizationAuthGuard, FeatureAbilityGuard)
+ @Post('sign-in/common/:ssoType')
+ async commonSignIn(
+ @Param('ssoType') ssoType: SSOType.GOOGLE | SSOType.GIT,
+ @Body() body,
+ @User() user,
+ @Res({ passthrough: true }) response: Response
+ ) {
+ throw new NotImplementedException();
+ }
+
+ /* Incase if we need to support the safari browsers */
+ @InitFeature(FEATURE_KEY.AI_COOKIE_SET)
+ @Post('set-ai-cookie')
+ @UseGuards(FeatureAbilityGuard)
+ setAiCookie(@Res({ passthrough: true }) response: Response, @Body() body: Record) {
+ throw new NotImplementedException();
+ }
+
+ @InitFeature(FEATURE_KEY.AI_COOKIE_DELETE)
+ @Get('delete-ai-cookies')
+ @UseGuards(FeatureAbilityGuard)
+ deleteAiCookies(@Res({ passthrough: true }) response: Response, @AiCookies() cookies: Record) {
+ throw new NotImplementedException();
+ }
+}
diff --git a/server/src/modules/auth/website/service.ts b/server/src/modules/auth/website/service.ts
new file mode 100644
index 0000000000..82acc3dd9b
--- /dev/null
+++ b/server/src/modules/auth/website/service.ts
@@ -0,0 +1,28 @@
+import { Injectable, NotImplementedException } from '@nestjs/common';
+import { EntityManager } from 'typeorm';
+import { User } from '@entities/user.entity';
+import { Response } from 'express';
+import { IWebsiteAuthService } from '../interfaces/IService';
+import { SSOType } from '@entities/sso_config.entity';
+import { CreateAiUserDto } from '../dto';
+
+@Injectable()
+export class WebsiteAuthService implements IWebsiteAuthService {
+ async handleOnboarding(
+ userParams: CreateAiUserDto,
+ existingUser?: User,
+ response?: Response,
+ ssoType?: SSOType.GOOGLE | SSOType.GIT,
+ manager?: EntityManager
+ ) {
+ throw new NotImplementedException('Method not implemented');
+ }
+
+ setSessionAICookies(response: Response, keyValues: Record) {
+ return { message: 'AI Cookies set successfully' };
+ }
+
+ clearSessionAICookies(response: Response, cookies: Record) {
+ return { message: 'AI Cookies cleared successfully' };
+ }
+}
diff --git a/server/src/modules/data-sources/module.ts b/server/src/modules/data-sources/module.ts
index 38e7ad0ef7..ee312174ac 100644
--- a/server/src/modules/data-sources/module.ts
+++ b/server/src/modules/data-sources/module.ts
@@ -30,8 +30,6 @@ export class DataSourcesModule extends SubModule {
'services/sample-ds.service',
]);
- const { OrganizationsService } = await this.getProviders(configs, 'organizations', ['service']);
-
return {
module: DataSourcesModule,
imports: [
@@ -53,7 +51,6 @@ export class DataSourcesModule extends SubModule {
PluginsRepository,
SampleDataSourceService,
FeatureAbilityFactory,
- OrganizationsService,
OrganizationRepository,
],
controllers: [DataSourcesController],
diff --git a/server/src/modules/data-sources/service.ts b/server/src/modules/data-sources/service.ts
index f2454744fd..4381017d85 100644
--- a/server/src/modules/data-sources/service.ts
+++ b/server/src/modules/data-sources/service.ts
@@ -20,10 +20,9 @@ import { GetQueryVariables, UpdateOptions } from './types';
import { DataSource } from '@entities/data_source.entity';
import { PluginsServiceSelector } from './services/plugin-selector.service';
import { IDataSourcesService } from './interfaces/IService';
-// import { FEATURE_KEY } from './constants';
-import { OrganizationsService } from '@modules/organizations/service';
import { RequestContext } from '@modules/request-context/service';
import { AUDIT_LOGS_REQUEST_CONTEXT_KEY } from '@modules/app/constants';
+import * as fs from 'fs';
@Injectable()
export class DataSourcesService implements IDataSourcesService {
@@ -32,8 +31,7 @@ export class DataSourcesService implements IDataSourcesService {
protected readonly dataSourcesUtilService: DataSourcesUtilService,
protected readonly abilityService: AbilityService,
protected readonly appEnvironmentsUtilService: AppEnvironmentUtilService,
- protected readonly pluginsServiceSelector: PluginsServiceSelector,
- protected readonly organizationsService: OrganizationsService
+ protected readonly pluginsServiceSelector: PluginsServiceSelector
) {}
async getForApp(query: GetQueryVariables, user: User): Promise<{ data_sources: object[] }> {
@@ -120,7 +118,6 @@ export class DataSourcesService implements IDataSourcesService {
if (kind === 'grpc') {
const rootDir = process.cwd().split('/').slice(0, -1).join('/');
const protoFilePath = `${rootDir}/protos/service.proto`;
- const fs = require('fs');
const filecontent = fs.readFileSync(protoFilePath, 'utf8');
const rcps = await this.dataSourcesUtilService.getServiceAndRpcNames(filecontent);
diff --git a/server/src/modules/email/service.ts b/server/src/modules/email/service.ts
index f2c9ceeb5d..ce1f60a5e4 100644
--- a/server/src/modules/email/service.ts
+++ b/server/src/modules/email/service.ts
@@ -56,6 +56,7 @@ export class EmailService implements IEmailService {
this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text;
this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo;
this.defaultWhiteLabelState = whiteLabelSettings?.default;
+ await this.emailUtilService.init();
}
protected compileTemplate(templatePath: string, templateData: object) {
@@ -77,7 +78,6 @@ export class EmailService implements IEmailService {
redirectTo,
} = payload;
await this.init(organizationId);
- await this.emailUtilService.init(organizationId);
const isOrgInvite = organizationInvitationToken && sender && organizationName;
const inviteUrl = generateInviteURL(invitationtoken, organizationInvitationToken, organizationId, null, redirectTo);
const subject = isOrgInvite ? `Welcome to ${organizationName || 'ToolJet'}` : 'Set up your account!';
diff --git a/server/src/modules/git-sync/ability/guard.ts b/server/src/modules/git-sync/ability/guard.ts
new file mode 100644
index 0000000000..86c09b8014
--- /dev/null
+++ b/server/src/modules/git-sync/ability/guard.ts
@@ -0,0 +1,23 @@
+import { Injectable } from '@nestjs/common';
+import { FeatureAbilityFactory } from '.';
+import { AbilityGuard } from '@modules/app/guards/ability.guard';
+import { ResourceDetails } from '@modules/app/types';
+import { MODULES } from '@modules/app/constants/modules';
+import { OrganizationGitSync } from '@entities/organization_git_sync.entity';
+
+@Injectable()
+export class FeatureAbilityGuard extends AbilityGuard {
+ protected getResource(): ResourceDetails {
+ return {
+ resourceType: MODULES.GIT_SYNC,
+ };
+ }
+
+ protected getAbilityFactory() {
+ return FeatureAbilityFactory;
+ }
+
+ protected getSubjectType() {
+ return OrganizationGitSync;
+ }
+}
diff --git a/server/src/modules/git-sync/ability/index.ts b/server/src/modules/git-sync/ability/index.ts
new file mode 100644
index 0000000000..c669c1cedb
--- /dev/null
+++ b/server/src/modules/git-sync/ability/index.ts
@@ -0,0 +1,38 @@
+import { Injectable } from '@nestjs/common';
+import { Ability, AbilityBuilder, InferSubjects } from '@casl/ability';
+import { AbilityFactory } from '@modules/app/ability-factory';
+import { UserAllPermissions } from '@modules/app/types';
+import { FEATURE_KEY } from '../constants';
+import { OrganizationGitSync } from '@entities/organization_git_sync.entity';
+
+type Subjects = InferSubjects | 'all';
+export type FeatureAbility = Ability<[FEATURE_KEY, Subjects]>;
+
+@Injectable()
+export class FeatureAbilityFactory extends AbilityFactory {
+ protected getSubjectType() {
+ return OrganizationGitSync;
+ }
+
+ protected defineAbilityFor(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ extractedMetadata: { moduleName: string; features: string[] },
+ request?: any
+ ): void {
+ const { superAdmin, isAdmin } = UserAllPermissions;
+ if (isAdmin || superAdmin) {
+ // Admin or Super Admin gets full access to all features
+ can(FEATURE_KEY.GET_ORGANIZATION_GIT, OrganizationGitSync);
+ can(FEATURE_KEY.GET_ORGANIZATION_GIT_STATUS, OrganizationGitSync);
+ can(FEATURE_KEY.CREATE_ORGANIZATION_GIT, OrganizationGitSync);
+ can(FEATURE_KEY.SAVE_PROVIDER_CONFIGS, OrganizationGitSync);
+ can(FEATURE_KEY.FINALIZE_CONFIGS, OrganizationGitSync);
+ can(FEATURE_KEY.UPDATE_PROVIDER_CONFIGS, OrganizationGitSync);
+ can(FEATURE_KEY.UPDATE_ORGANIZATION_GIT_STATUS, OrganizationGitSync);
+ can(FEATURE_KEY.DELETE_ORGANIZATION_GIT_CONFIGS, OrganizationGitSync);
+ return;
+ }
+ can(FEATURE_KEY.GET_ORGANIZATION_GIT_STATUS, OrganizationGitSync);
+ }
+}
diff --git a/server/src/modules/git-sync/constants/feature.ts b/server/src/modules/git-sync/constants/feature.ts
new file mode 100644
index 0000000000..ee87e73c0d
--- /dev/null
+++ b/server/src/modules/git-sync/constants/feature.ts
@@ -0,0 +1,17 @@
+import { FEATURE_KEY } from '.';
+import { MODULES } from '@modules/app/constants/modules';
+import { FeaturesConfig } from '../types';
+import { LICENSE_FIELD } from '@modules/licensing/constants';
+
+export const FEATURES: FeaturesConfig = {
+ [MODULES.GIT_SYNC]: {
+ [FEATURE_KEY.GET_ORGANIZATION_GIT]: {},
+ [FEATURE_KEY.GET_ORGANIZATION_GIT_STATUS]: {},
+ [FEATURE_KEY.CREATE_ORGANIZATION_GIT]: { license: LICENSE_FIELD.GIT_SYNC },
+ [FEATURE_KEY.SAVE_PROVIDER_CONFIGS]: { license: LICENSE_FIELD.GIT_SYNC },
+ [FEATURE_KEY.FINALIZE_CONFIGS]: { license: LICENSE_FIELD.GIT_SYNC },
+ [FEATURE_KEY.UPDATE_PROVIDER_CONFIGS]: { license: LICENSE_FIELD.GIT_SYNC },
+ [FEATURE_KEY.UPDATE_ORGANIZATION_GIT_STATUS]: { license: LICENSE_FIELD.GIT_SYNC },
+ [FEATURE_KEY.DELETE_ORGANIZATION_GIT_CONFIGS]: { license: LICENSE_FIELD.GIT_SYNC },
+ },
+};
diff --git a/server/src/modules/git-sync/constants/index.ts b/server/src/modules/git-sync/constants/index.ts
new file mode 100644
index 0000000000..42e87e647a
--- /dev/null
+++ b/server/src/modules/git-sync/constants/index.ts
@@ -0,0 +1,10 @@
+export enum FEATURE_KEY {
+ GET_ORGANIZATION_GIT = 'GET_ORGANIZATION_GIT',
+ GET_ORGANIZATION_GIT_STATUS = 'GET_ORGANIZATION_GIT_STATUS',
+ CREATE_ORGANIZATION_GIT = 'CREATE_ORGANIZATION_GIT',
+ SAVE_PROVIDER_CONFIGS = 'SAVE_PROVIDER_CONFIGS',
+ FINALIZE_CONFIGS = 'FINALIZE_CONFIGS',
+ UPDATE_PROVIDER_CONFIGS = 'UPDATE_PROVIDER_CONFIGS',
+ UPDATE_ORGANIZATION_GIT_STATUS = 'UPDATE_ORGANIZATION_GIT_STATUS',
+ DELETE_ORGANIZATION_GIT_CONFIGS = 'DELETE_ORGANIZATION_GIT_CONFIGS',
+}
diff --git a/server/src/modules/git-sync/controller.ts b/server/src/modules/git-sync/controller.ts
index f4087ffbe9..d3bf45ed47 100644
--- a/server/src/modules/git-sync/controller.ts
+++ b/server/src/modules/git-sync/controller.ts
@@ -8,8 +8,11 @@ import {
import { User as UserEntity } from 'src/entities/user.entity';
import { IGitSyncController } from './Interfaces/IController';
import { ProviderConfigDTO } from './dto/provider-config.dto';
+import { InitModule } from '@modules/app/decorators/init-module';
+import { MODULES } from '@modules/app/constants/modules';
@Controller('git-sync')
+@InitModule(MODULES.GIT_SYNC)
export class GitSyncController implements IGitSyncController {
constructor() {}
diff --git a/server/src/modules/git-sync/constants/git_default.constant.ts b/server/src/modules/git-sync/error-constants/git_default.constant.ts
similarity index 100%
rename from server/src/modules/git-sync/constants/git_default.constant.ts
rename to server/src/modules/git-sync/error-constants/git_default.constant.ts
diff --git a/server/src/modules/git-sync/constants/gitsync_error.constant.ts b/server/src/modules/git-sync/error-constants/gitsync_error.constant.ts
similarity index 100%
rename from server/src/modules/git-sync/constants/gitsync_error.constant.ts
rename to server/src/modules/git-sync/error-constants/gitsync_error.constant.ts
diff --git a/server/src/modules/git-sync/module.ts b/server/src/modules/git-sync/module.ts
index 61a55bc1a6..a1039cca52 100644
--- a/server/src/modules/git-sync/module.ts
+++ b/server/src/modules/git-sync/module.ts
@@ -7,6 +7,7 @@ import { OrganizationGitSyncRepository } from './repository';
import { VersionRepository } from '@modules/versions/repository';
import { AppGitRepository } from '@modules/app-git/repository';
import { SubModule } from '@modules/app/sub-module';
+import { FeatureAbilityFactory } from './ability';
export class GitSyncModule extends SubModule {
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise {
@@ -59,8 +60,15 @@ export class GitSyncModule extends SubModule {
SSHGitSyncUtilityService,
GitLabGitSyncUtilityService,
SourceControlProviderService,
+ FeatureAbilityFactory,
+ ],
+ exports: [
+ HTTPSGitSyncUtilityService,
+ SSHGitSyncUtilityService,
+ GitLabGitSyncUtilityService,
+ BaseGitSyncService,
+ BaseGitUtilService,
],
- exports: [HTTPSGitSyncUtilityService, SSHGitSyncUtilityService, GitLabGitSyncUtilityService, BaseGitSyncService, BaseGitUtilService],
};
}
}
diff --git a/server/src/modules/git-sync/types/index.ts b/server/src/modules/git-sync/types/index.ts
new file mode 100644
index 0000000000..5d350d8d63
--- /dev/null
+++ b/server/src/modules/git-sync/types/index.ts
@@ -0,0 +1,18 @@
+import { FEATURE_KEY } from '../constants';
+import { FeatureConfig } from '@modules/app/types';
+import { MODULES } from '@modules/app/constants/modules';
+
+interface Features {
+ [FEATURE_KEY.GET_ORGANIZATION_GIT]: FeatureConfig;
+ [FEATURE_KEY.GET_ORGANIZATION_GIT_STATUS]: FeatureConfig;
+ [FEATURE_KEY.CREATE_ORGANIZATION_GIT]: FeatureConfig;
+ [FEATURE_KEY.SAVE_PROVIDER_CONFIGS]: FeatureConfig;
+ [FEATURE_KEY.FINALIZE_CONFIGS]: FeatureConfig;
+ [FEATURE_KEY.UPDATE_PROVIDER_CONFIGS]: FeatureConfig;
+ [FEATURE_KEY.UPDATE_ORGANIZATION_GIT_STATUS]: FeatureConfig;
+ [FEATURE_KEY.DELETE_ORGANIZATION_GIT_CONFIGS]: FeatureConfig;
+}
+
+export interface FeaturesConfig {
+ [MODULES.GIT_SYNC]: Features;
+}
diff --git a/server/src/modules/ignored/comment_users/comment_users.module.ts b/server/src/modules/ignored/comment_users/comment_users.module.ts
deleted file mode 100644
index 7f5ca8fda9..0000000000
--- a/server/src/modules/ignored/comment_users/comment_users.module.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { CommentUsersController } from '@controllers/comment_users.controller';
-import { CommentUsersService } from '@services/comment_users.service';
-import { CaslModule } from '../casl/casl.module';
-import { CommentUsers } from 'src/entities/comment_user.entity';
-import { User } from 'src/entities/user.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-
-@Module({
- controllers: [CommentUsersController],
- imports: [TypeOrmModule.forFeature([CommentUsers, User, AppVersion]), CaslModule],
- providers: [CommentUsersService],
-})
-export class CommentUsersModule {}
diff --git a/server/src/modules/ignored/comments/comment.module.ts b/server/src/modules/ignored/comments/comment.module.ts
deleted file mode 100644
index 69b8e2b710..0000000000
--- a/server/src/modules/ignored/comments/comment.module.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { CommentController } from '@controllers/comment.controller';
-import { CommentService } from '@services/comment.service';
-import { CommentRepository } from '../../repositories/comment.repository';
-import { CaslModule } from '../casl/casl.module';
-import { User } from 'src/entities/user.entity';
-import { Organization } from 'src/entities/organization.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-import { CommentUsers } from 'src/entities/comment_user.entity';
-import { Comment } from 'src/entities/comment.entity';
-import { EmailModule } from '@modules/email/module';
-
-@Module({
- controllers: [CommentController],
- imports: [TypeOrmModule.forFeature([CommentUsers, AppVersion, User, Organization, Comment]), CaslModule, EmailModule],
- providers: [CommentService, CommentRepository],
-})
-export class CommentModule {}
diff --git a/server/src/modules/ignored/import_export_resources/import_export_resources.module.ts b/server/src/modules/ignored/import_export_resources/import_export_resources.module.ts
deleted file mode 100644
index 91d898eeb7..0000000000
--- a/server/src/modules/ignored/import_export_resources/import_export_resources.module.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { ImportExportResourcesController } from '@controllers/import_export_resources.controller';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { ImportExportResourcesService } from '@services/import_export_resources.service';
-import { AppImportExportService } from '@services/app_import_export.service';
-import { TooljetDbImportExportService } from '@services/tooljet_db_import_export_service';
-import { DataSourcesService } from '@services/data_sources.service';
-import { AppEnvironmentService } from '@ee/app-environments/service';
-import { Plugin } from 'src/entities/plugin.entity';
-import { PluginsHelper } from 'src/helpers/plugins.helper';
-import { CredentialsService } from '@services/credentials.service';
-import { DataSource } from 'src/entities/data_source.entity';
-import { PluginsModule } from '../plugins/plugins.module';
-import { Credential } from '../../../src/entities/credential.entity';
-import { CaslModule } from '../casl/casl.module';
-import { AppsService } from '@services/apps.service';
-import { App } from 'src/entities/app.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-import { AppUser } from 'src/entities/app_user.entity';
-import { User } from 'src/entities/user.entity';
-import { Organization } from 'src/entities/organization.entity';
-import { TooljetDbOperationsService } from '@services/tooljet_db_operations.service';
-import { PostgrestProxyService } from '@services/postgrest_proxy.service';
-import { TooljetDbModule } from '../tooljet_db/tooljet_db.module';
-import { UserResourcePermissionsModule } from '@modules/user_resource_permissions/user_resource_permissions.module';
-import { UsersModule } from '@modules/users/users.module';
-import { EncryptionModule } from '@modules/encryption/module';
-
-const imports = [
- PluginsModule,
- CaslModule,
- TypeOrmModule.forFeature([User, Organization, AppUser, AppVersion, App, Credential, Plugin, DataSource]),
- TooljetDbModule,
- UserResourcePermissionsModule,
- UsersModule,
- EncryptionModule,
-];
-
-@Module({
- imports,
- controllers: [ImportExportResourcesController],
- providers: [
- ImportExportResourcesService,
- AppImportExportService,
- TooljetDbImportExportService,
- DataSourcesService,
- AppEnvironmentService,
- TooljetDbService,
- PluginsHelper,
- AppsService,
- CredentialsService,
- TooljetDbOperationsService,
- PostgrestProxyService,
- ],
- exports: [ImportExportResourcesService],
-})
-export class ImportExportResourcesModule {}
diff --git a/server/src/modules/ignored/instance_login_configs/instance_login_configs.module.ts b/server/src/modules/ignored/instance_login_configs/instance_login_configs.module.ts
deleted file mode 100644
index d7fc1583ec..0000000000
--- a/server/src/modules/ignored/instance_login_configs/instance_login_configs.module.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Module } from '@nestjs/common';
-import { InstanceLoginConfigsController } from '@controllers/instance_login-configs.controller';
-import { InstanceSettingsModule } from '@instance-settings/module';
-import { OrganizationsModule } from '../organizations/organizations.module';
-import { SSOGuard } from '@modules/licensing/guards/sso/sso.guard';
-import { LDAPGuard } from '@modules/licensing/guards/sso/ldap.guard';
-import { OIDCGuard } from '@modules/licensing/guards/sso/oidc.guard';
-import { SAMLGuard } from '@modules/licensing/guards/sso/saml.guard';
-import { InstanceLoginConfigsService } from '@services/instance_login-configs.service';
-@Module({
- controllers: [InstanceLoginConfigsController],
- providers: [InstanceLoginConfigsService, SSOGuard, OIDCGuard, LDAPGuard, SAMLGuard],
- imports: [InstanceSettingsModule, OrganizationsModule],
-})
-export class InstanceLoginConfigsModule {}
diff --git a/server/src/modules/ignored/library_app/library_app.module.ts b/server/src/modules/ignored/library_app/library_app.module.ts
deleted file mode 100644
index 6376d42137..0000000000
--- a/server/src/modules/ignored/library_app/library_app.module.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { Module } from '@nestjs/common';
-import { LibraryAppsController } from '@controllers/library_apps.controller';
-import { LibraryAppCreationService } from '@services/library_app_creation.service';
-import { AppImportExportService } from '@services/app_import_export.service';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { App } from 'src/entities/app.entity';
-import { DataSourcesService } from '@services/data_sources.service';
-import { CredentialsService } from '@services/credentials.service';
-import { Credential } from 'src/entities/credential.entity';
-import { DataSource } from 'src/entities/data_source.entity';
-import { CaslModule } from '../casl/casl.module';
-import { FilesService } from '@services/files.service';
-import { File } from 'src/entities/file.entity';
-import { PluginsService } from '@services/plugins.service';
-import { Plugin } from 'src/entities/plugin.entity';
-import { PluginsHelper } from 'src/helpers/plugins.helper';
-import { AppEnvironmentService } from '@ee/app-environments/service';
-import { AppEnvironment } from 'src/entities/app_environments.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-import { User } from 'src/entities/user.entity';
-import { Organization } from 'src/entities/organization.entity';
-import { ImportExportResourcesModule } from '../import_export_resources/import_export_resources.module';
-import { TooljetDbOperationsService } from '@services/tooljet_db_operations.service';
-import { PostgrestProxyService } from '@services/postgrest_proxy.service';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { AppsService } from '@services/apps.service';
-import { AppUser } from 'src/entities/app_user.entity';
-import { TooljetDbModule } from '../tooljet_db/tooljet_db.module';
-import { UserResourcePermissionsModule } from '@modules/user_resource_permissions/user_resource_permissions.module';
-import { UsersModule } from '@modules/users/users.module';
-import { EncryptionModule } from '@modules/encryption/module';
-
-@Module({
- imports: [
- TypeOrmModule.forFeature([
- App,
- Credential,
- File,
- Plugin,
- DataSource,
- AppEnvironment,
- AppVersion,
- User,
- AppUser,
- Organization,
- ]),
- CaslModule,
- ImportExportResourcesModule,
- TooljetDbModule,
- UserResourcePermissionsModule,
- UsersModule,
- EncryptionModule,
- ],
- providers: [
- CredentialsService,
- DataSourcesService,
- LibraryAppCreationService,
- AppImportExportService,
- FilesService,
- PluginsService,
- PluginsHelper,
- AppEnvironmentService,
- TooljetDbOperationsService,
- TooljetDbService,
- PostgrestProxyService,
- AppsService,
- ],
- controllers: [LibraryAppsController],
-})
-export class LibraryAppModule {}
diff --git a/server/src/modules/ignored/org_environment_variables/org_environment_variables.module.ts b/server/src/modules/ignored/org_environment_variables/org_environment_variables.module.ts
deleted file mode 100644
index 034ac597af..0000000000
--- a/server/src/modules/ignored/org_environment_variables/org_environment_variables.module.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { OrgEnvironmentVariable } from '../../entities/org_envirnoment_variable.entity';
-import { OrgEnvironmentVariablesController } from '../../controllers/org_environment_variables.controller';
-import { OrgEnvironmentVariablesService } from '../../services/org_environment_variables.service';
-import { App } from 'src/entities/app.entity';
-import { User } from 'src/entities/user.entity';
-import { OrganizationUser } from 'src/entities/organization_user.entity';
-import { Organization } from 'src/entities/organization.entity';
-import { CaslModule } from '../casl/casl.module';
-import { FilesService } from '@services/files.service';
-import { Plugin } from 'src/entities/plugin.entity';
-import { PluginsService } from '@services/plugins.service';
-import { File } from 'src/entities/file.entity';
-import { AppsService } from '@services/apps.service';
-import { AppUser } from 'src/entities/app_user.entity';
-import { DataSource } from 'src/entities/data_source.entity';
-import { DataQuery } from 'src/entities/data_query.entity';
-import { FolderApp } from 'src/entities/folder_app.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-import { AppImportExportService } from '@services/app_import_export.service';
-import { DataSourcesService } from '@services/data_sources.service';
-import { CredentialsService } from '@services/credentials.service';
-import { Credential } from 'src/entities/credential.entity';
-import { AppEnvironment } from 'src/entities/app_environments.entity';
-import { PluginsHelper } from 'src/helpers/plugins.helper';
-import { AppEnvironmentService } from '@ee/app-environments/service';
-import { TooljetDbOperationsService } from '@services/tooljet_db_operations.service';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { PostgrestProxyService } from '@services/postgrest_proxy.service';
-import { TooljetDbModule } from '../tooljet_db/tooljet_db.module';
-import { UsersModule } from '@modules/users/users.module';
-import { EncryptionModule } from '@modules/encryption/module';
-
-@Module({
- controllers: [OrgEnvironmentVariablesController],
- imports: [
- TypeOrmModule.forFeature([
- App,
- OrgEnvironmentVariable,
- User,
- OrganizationUser,
- Organization,
- File,
- Plugin,
- AppVersion,
- AppUser,
- DataSource,
- DataQuery,
- FolderApp,
- Credential,
- AppEnvironment,
- ]),
- CaslModule,
- TooljetDbModule,
- UsersModule,
- EncryptionModule,
- ],
- providers: [
- OrgEnvironmentVariablesService,
- AppsService,
- FilesService,
- PluginsService,
- AppImportExportService,
- DataSourcesService,
- CredentialsService,
- PluginsHelper,
- AppEnvironmentService,
- PostgrestProxyService,
- TooljetDbOperationsService,
- TooljetDbService,
- ],
-})
-export class OrgEnvironmentVariablesModule {}
diff --git a/server/src/modules/ignored/repositories/comment.repository.ts b/server/src/modules/ignored/repositories/comment.repository.ts
deleted file mode 100644
index eed717e203..0000000000
--- a/server/src/modules/ignored/repositories/comment.repository.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
-import { Comment } from '../entities/comment.entity';
-import { CreateCommentDto, UpdateCommentDto } from '../dto/comment.dto';
-
-@Injectable()
-export class CommentRepository extends Repository {
- constructor(
- @InjectRepository(Comment)
- private commentRepository: Repository
- ) {
- super(commentRepository.target, commentRepository.manager, commentRepository.queryRunner);
- }
-
- public async createComment(
- createCommentDto: CreateCommentDto,
- userId: string,
- organizationId: string
- ): Promise {
- const { comment, threadId, appVersionsId } = createCommentDto;
- const _comment = this.commentRepository.create({
- comment,
- threadId,
- userId,
- organizationId,
- appVersionsId,
- });
- return this.commentRepository.save(_comment);
- }
-
- public async editComment(updateCommentDto: UpdateCommentDto, editedComment: Comment): Promise {
- const { comment, threadId } = updateCommentDto;
- editedComment.comment = comment;
- editedComment.threadId = threadId;
- return this.commentRepository.save(editedComment);
- }
-}
diff --git a/server/src/modules/ignored/repositories/group_permission.repository.ts b/server/src/modules/ignored/repositories/group_permission.repository.ts
deleted file mode 100644
index d025d2df7c..0000000000
--- a/server/src/modules/ignored/repositories/group_permission.repository.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { DataSource, EntityManager, Repository } from 'typeorm';
-import { GroupPermissions } from '@entities/group_permissions.entity';
-
-interface IGroupPermissionRepository extends Repository {
- getAllUserGroups(userId: string, organizationId: string, manager: EntityManager): Promise;
-}
-
-export class GroupPermissionRepository extends Repository implements IGroupPermissionRepository {
- constructor(private dataSource: DataSource) {
- super(GroupPermissions, dataSource.createEntityManager());
- }
-
- getAllUserGroups(userId: string, organizationId: string, manager: EntityManager): Promise {
- return manager.find(GroupPermissions, {
- where: {
- organizationId: organizationId,
- groupUsers: {
- userId: userId,
- },
- },
- relations: ['groupUsers'],
- });
- }
-}
diff --git a/server/src/modules/ignored/repositories/ssoConfigs.repository.ts b/server/src/modules/ignored/repositories/ssoConfigs.repository.ts
deleted file mode 100644
index 284a174eaa..0000000000
--- a/server/src/modules/ignored/repositories/ssoConfigs.repository.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { Repository } from 'typeorm';
-import { SSOConfigs, SSOType } from '@entities/sso_config.entity';
-
-@Injectable()
-export class SSOConfigsRepository extends Repository {
- async findByOrganizationId(organizationId: string): Promise {
- return this.find({ where: { organizationId } });
- }
-
- async findInstanceConfigs(): Promise {
- return this.find({ where: { organizationId: null } });
- }
-
- async createOrUpdateSSOConfig(configData: Partial): Promise {
- const existingConfig = await this.findOne({
- where: { sso: configData.sso, organizationId: configData.organizationId, configScope: configData.configScope },
- });
-
- if (existingConfig) {
- return this.save({ ...existingConfig, ...configData });
- }
-
- return this.save(this.create(configData));
- }
-
- async updateConfig(id: string, updateData: Partial): Promise {
- await this.update(id, updateData);
- return this.findOne({ where: { id } });
- }
-
- async deleteConfig(id: string): Promise {
- await this.delete(id);
- }
-
- async getSSOConfigsForOrganization(organizationId: string, sso: SSOType | string): Promise {
- return this.findOne({
- where: {
- organizationId,
- sso: sso as SSOType,
- },
- relations: ['organization'],
- });
- }
-}
diff --git a/server/src/modules/ignored/repositories/thread.repository.ts b/server/src/modules/ignored/repositories/thread.repository.ts
deleted file mode 100644
index 7216990153..0000000000
--- a/server/src/modules/ignored/repositories/thread.repository.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import { InjectRepository } from '@nestjs/typeorm';
-import { Repository } from 'typeorm';
-import { CreateThreadDto, UpdateThreadDto } from '@dto/thread.dto';
-import { Thread } from '@entities/thread.entity';
-
-@Injectable()
-export class ThreadRepository extends Repository {
- constructor(
- @InjectRepository(Thread)
- private threadRepository: Repository
- ) {
- super(threadRepository.target, threadRepository.manager, threadRepository.queryRunner);
- }
-
- public async createThread(createThreadDto: CreateThreadDto, userId: string, organizationId: string): Promise {
- const { x, y, appId, appVersionsId, pageId } = createThreadDto;
-
- const thread = this.threadRepository.create({
- x,
- y,
- appId,
- userId,
- organizationId,
- appVersionsId,
- pageId,
- });
-
- return await this.threadRepository.save(thread);
- }
-
- public async editThread(updateThreadDto: UpdateThreadDto, editedThread: Thread): Promise {
- const { x, y, isResolved } = updateThreadDto;
-
- editedThread.x = x;
- editedThread.y = y;
- editedThread.isResolved = isResolved;
-
- return await this.threadRepository.save(editedThread);
- }
-}
diff --git a/server/src/modules/ignored/seeds/seeds.module.ts b/server/src/modules/ignored/seeds/seeds.module.ts
deleted file mode 100644
index eb7ca9dfd7..0000000000
--- a/server/src/modules/ignored/seeds/seeds.module.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Module } from '@nestjs/common';
-import { SeedsService } from '../../services/seeds.service';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { UserResourcePermissionsModule } from '@modules/user_resource_permissions/user_resource_permissions.module';
-
-@Module({
- imports: [UserResourcePermissionsModule],
- providers: [SeedsService, TooljetDbService],
- exports: [SeedsService, TooljetDbService],
-})
-export class SeedsModule {}
diff --git a/server/src/modules/ignored/thread/thread.module.ts b/server/src/modules/ignored/thread/thread.module.ts
deleted file mode 100644
index afa355424c..0000000000
--- a/server/src/modules/ignored/thread/thread.module.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { CaslModule } from '../casl/casl.module';
-
-import { ThreadController } from '../../controllers/thread.controller';
-import { ThreadService } from '../../services/thread.service';
-import { ThreadRepository } from '../../repositories/thread.repository';
-import { Thread } from 'src/entities/thread.entity';
-
-@Module({
- imports: [TypeOrmModule.forFeature([Thread]), CaslModule],
- controllers: [ThreadController],
- providers: [ThreadService, ThreadRepository],
-})
-export class ThreadModule {}
diff --git a/server/src/modules/ignored/tooljet_db/tooljet-db.types.ts b/server/src/modules/ignored/tooljet_db/tooljet-db.types.ts
deleted file mode 100644
index 73b72013aa..0000000000
--- a/server/src/modules/ignored/tooljet_db/tooljet-db.types.ts
+++ /dev/null
@@ -1,251 +0,0 @@
-import { QueryFailedError } from 'typeorm';
-import { InternalTable } from 'src/entities/internal_table.entity';
-import { capitalize } from 'lodash';
-
-export const TJDB = {
- character_varying: 'character varying' as const,
- integer: 'integer' as const,
- bigint: 'bigint' as const,
- serial: 'serial' as const,
- double_precision: 'double precision' as const,
- boolean: 'boolean' as const,
- timestampz: 'timestamp with time zone' as const,
- jsonb: 'jsonb' as const,
-};
-
-export type TooljetDatabaseDataTypes = (typeof TJDB)[keyof typeof TJDB];
-
-export type TooljetDatabaseColumn = {
- column_name: string;
- data_type: TooljetDatabaseDataTypes;
- column_default: string | null;
- character_maximum_length: number | null;
- numeric_precision: number | null;
- constraints_type: {
- is_not_null: boolean;
- is_primary_key: boolean;
- is_unique: boolean;
- };
- keytype: string | null;
-};
-
-export type TooljetDatabaseForeignKey = {
- column_names: string[];
- referenced_table_name: string;
- referenced_column_names: string[];
- on_update: string;
- on_delete: string;
- constraint_name: string;
- referenced_table_id: string;
-};
-
-export type TooljetDatabaseTable = {
- id: string;
- table_name: string;
- schema: {
- columns: TooljetDatabaseColumn[];
- foreign_keys: TooljetDatabaseForeignKey[];
- };
-};
-
-enum PostgresErrorCode {
- UniqueViolation = '23505',
- CheckViolation = '23514',
- NotNullViolation = '23502',
- ForeignKeyViolation = '23503',
- DuplicateColumn = '42701',
- UndefinedTable = '42P01',
- PermissionDenied = '42501',
- UndefinedFunction = '42883',
-}
-
-export type TooljetDbActions =
- | 'add_column'
- | 'create_foreign_key'
- | 'create_table'
- | 'delete_foreign_key'
- | 'drop_column'
- | 'drop_table'
- | 'edit_column'
- | 'edit_table'
- | 'join_tables'
- | 'update_foreign_key'
- | 'view_table'
- | 'view_tables'
- | 'sql_execution'
- | 'bulk_upload'
- | 'proxy_postgrest';
-
-type ErrorCodeMappingItem = Partial>;
-type ErrorCodeMapping = {
- [key in PostgresErrorCode]: ErrorCodeMappingItem;
-};
-
-const errorCodeMapping: Partial = {
- [PostgresErrorCode.NotNullViolation]: {
- edit_column: 'Cannot add NOT NULL constraint as this column contains null values',
- proxy_postgrest: 'Not null constraint violated for {{table}}.{{column}}',
- },
- [PostgresErrorCode.UniqueViolation]: {
- edit_column: 'Cannot add UNIQUE constraint as this column contains duplicate values',
- proxy_postgrest: 'Unique constraint violated as {{value}} already exists in {{table}}.{{column}}',
- bulk_upload: 'Duplicate value violates unique constraint',
- },
- [PostgresErrorCode.UndefinedTable]: {
- default: 'Could not find the table {{table}}.',
- sql_execution: `Could not find the table or schema`,
- },
- [PostgresErrorCode.ForeignKeyViolation]: {
- proxy_postgrest: 'Update or delete on {{table}}.{{column}} with {{value}} violates foreign key constraint',
- sql_execution: 'Update or delete on {{table}}.{{column}} with {{value}} violates foreign key constraint',
- bulk_upload: 'Insert or update violates foreign key constraint',
- },
- [PostgresErrorCode.PermissionDenied]: {
- default: 'Insufficient privilege',
- },
- [PostgresErrorCode.UndefinedFunction]: {
- // proxy_postgrest: '{{fxName}} - aggregate function requires serial, integer, float or big int column type',
- // join_tables: '{{fxName}} - aggregate function requires serial, integer, float or big int column type',
- },
-};
-
-export class PostgrestError extends Error {
- code: string;
- details: string;
- hint: string;
- message: string;
-
- constructor(postgrestErrorResponse: { code: string; details: string; hint: string; message: string }) {
- super();
-
- const { code, details, hint, message } = postgrestErrorResponse;
- this.code = code;
- this.details = details;
- this.hint = hint;
- this.message = message;
- }
-
- toString(): string {
- return `PostgrestError [${this.code}]: ${this.message}`;
- }
-}
-
-export class TooljetDatabaseError extends QueryFailedError {
- public readonly code: string;
- public readonly context: {
- origin: TooljetDbActions;
- internalTables: (InternalTable | { id: string; tableName: string })[];
- };
- public readonly queryError: QueryFailedError;
-
- constructor(
- message: string,
- context: { origin: TooljetDbActions; internalTables: InternalTable[] | { id: string; tableName: string }[] },
- errorObj: QueryFailedError
- ) {
- super(message, errorObj.parameters, errorObj.driverError);
- this.context = context;
- this.code = errorObj.driverError['code'];
- this.queryError = errorObj;
- }
-
- toString(): string {
- const errorMessage =
- errorCodeMapping[this.code]?.[this.context.origin] ||
- errorCodeMapping[this.code]?.['default'] ||
- capitalize(this.message);
- return this.replaceErrorPlaceholders(errorMessage);
- }
-
- replaceErrorPlaceholders(errorMessage: string): string {
- let modifiedErrorMessage = errorMessage;
- const internalTableEntries = this.context.internalTables.map(({ id, tableName }) => [id, tableName]);
-
- // Templates strings replacement current works in expectation that
- // there will only be one table involved
- const replaceTemplateStrings = (errorMessage: string, replacements: Record): string => {
- return Object.entries(replacements).reduce((message, [key, value]) => {
- return message.replace(new RegExp(`{{${key}}}`, 'g'), value);
- }, errorMessage);
- };
-
- const replaceTableUUIDs = (errorMessage: string, internalTableEntries: string[][]): string => {
- return internalTableEntries.reduce((acc, [key, value]) => {
- return acc.replace(new RegExp(key, 'g'), value);
- }, errorMessage);
- };
-
- const maskWorkspaceSchemaNameInErrorMessage = (errorMessage): string => {
- let output = errorMessage
- .replace(/workspace_[\w-]+\./g, '')
- .replace(/'workspace_[\w-]+'\./g, "'")
- .replace(/workspace_[\w-]+'?/g, '')
- .replace(/\s*workspace_[\w-]+\s*/g, '')
- .replace(/\s{2,}/g, ' ')
- .replace(/"\s*"/g, '')
- .trim();
-
- output = output.trim();
- return output;
- };
-
- // Handle custom errors that are thrown from PostgREST with
- // specific parsers for the error code
- if (this.queryError.driverError instanceof PostgrestError) {
- const parsedTableInfo = this.postgrestDetailsParser();
- if (parsedTableInfo) {
- modifiedErrorMessage = replaceTemplateStrings(modifiedErrorMessage, parsedTableInfo);
- }
- }
-
- // TODO: Need to handle errors wherein multiple tables are involved when need arises
- //
- // Based on the internalTables in context replace the template placeholders
- // that are used in the error message
- if (this.context.internalTables.length === 1) {
- const replacements = { table: this.context.internalTables[0].tableName };
- modifiedErrorMessage = replaceTemplateStrings(modifiedErrorMessage, replacements);
- }
-
- // Based on the internalTables in context replace table UUIDs in
- // the error message
- modifiedErrorMessage = replaceTableUUIDs(modifiedErrorMessage, internalTableEntries);
- modifiedErrorMessage = maskWorkspaceSchemaNameInErrorMessage(modifiedErrorMessage);
- return modifiedErrorMessage;
- }
-
- postgrestDetailsParser(): Record | null {
- const parsers = {
- [PostgresErrorCode.NotNullViolation]: () => {
- const errorMessage = this.queryError.driverError.message;
- const regex = /null value in column "(.*?)" of relation "(.*?)" violates not-null constraint/;
- const matches = regex.exec(errorMessage);
- const table = this.context.internalTables[0].tableName;
- return { table, column: matches[1], value: matches[2] };
- },
- [PostgresErrorCode.UniqueViolation]: () => {
- const errorMessage = this.queryError.driverError['details'];
- const regex = /Key \((.*?)\)=\((.*?)\) already exists\./;
- const matches = regex.exec(errorMessage);
- const table = this.context.internalTables[0].tableName;
- return { table, column: matches[1], value: matches[2] };
- },
- [PostgresErrorCode.ForeignKeyViolation]: () => {
- const errorMessage = this.queryError.driverError['details'];
- const regex = /Key \((.*?)\)=\((.*?)\) (is still referenced from table|is not present in table) "(.*?)"\./;
- const matches = regex.exec(errorMessage);
- const table = this.context.internalTables[0].tableName;
- return { table, column: matches[1], value: matches[2], referencedTables: [matches[2]] };
- },
- [PostgresErrorCode.UndefinedFunction]: () => {
- const errorMessage = this.queryError.driverError.message;
- const regex = /function (\w+)\(([\w\s]+)\) does not exist/;
- const matches = regex.exec(errorMessage);
- const table = this.context.internalTables[0].tableName;
- if (Array.isArray(matches) && matches.length) return { table, fxName: matches[1] };
- return null;
- },
- };
- return parsers[this.code]?.() || null;
- }
-}
diff --git a/server/src/modules/ignored/tooljet_db/tooljet_db.module.ts b/server/src/modules/ignored/tooljet_db/tooljet_db.module.ts
deleted file mode 100644
index 5018b7fbb9..0000000000
--- a/server/src/modules/ignored/tooljet_db/tooljet_db.module.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { Module, OnModuleInit } from '@nestjs/common';
-import { InjectEntityManager, TypeOrmModule } from '@nestjs/typeorm';
-import { Credential } from '../../../src/entities/credential.entity';
-import { TooljetDbController } from '@controllers/tooljet_db.controller';
-import { CaslModule } from '../casl/casl.module';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { PostgrestProxyService } from '@services/postgrest_proxy.service';
-import { InternalTable } from 'src/entities/internal_table.entity';
-import { AppUser } from 'src/entities/app_user.entity';
-import { TableCountGuard } from '@modules/licensing/guards/table.guard';
-import { TooljetDbBulkUploadService } from '@services/tooljet_db_bulk_upload.service';
-import { TooljetDbOperationsService } from '@services/tooljet_db_operations.service';
-import { ConfigService } from '@nestjs/config';
-import { EntityManager } from 'typeorm';
-import { reconfigurePostgrest } from './utils/helper';
-import { Logger } from 'nestjs-pino';
-
-@Module({
- imports: [TypeOrmModule.forFeature([Credential, InternalTable, AppUser]), CaslModule],
- controllers: [TooljetDbController],
- providers: [
- TooljetDbService,
- TooljetDbBulkUploadService,
- TooljetDbOperationsService,
- PostgrestProxyService,
- TableCountGuard,
- ],
- exports: [TooljetDbService, TooljetDbBulkUploadService, TooljetDbOperationsService, PostgrestProxyService],
-})
-export class TooljetDbModule implements OnModuleInit {
- constructor(
- private logger: Logger,
- private configService: ConfigService,
- @InjectEntityManager('tooljetDb')
- private readonly tooljetDbManager: EntityManager
- ) {}
-
- async onModuleInit() {
- if (!process.env.WORKER) {
- const tooljtDbUser = this.configService.get('TOOLJET_DB_USER');
- const statementTimeout = this.configService.get('TOOLJET_DB_STATEMENT_TIMEOUT') || 60000;
- const statementTimeoutInSecs = Number.isNaN(Number(statementTimeout)) ? 60 : Number(statementTimeout) / 1000;
-
- await reconfigurePostgrest(this.tooljetDbManager, {
- user: tooljtDbUser,
- enableAggregates: true,
- statementTimeoutInSecs: statementTimeoutInSecs,
- });
- await this.tooljetDbManager.query("NOTIFY pgrst, 'reload schema'");
- }
- }
-}
diff --git a/server/src/modules/ignored/tooljet_db/utils/helper.ts b/server/src/modules/ignored/tooljet_db/utils/helper.ts
deleted file mode 100644
index 5dab99a0d2..0000000000
--- a/server/src/modules/ignored/tooljet_db/utils/helper.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { EntityManager } from 'typeorm';
-
-export async function reconfigurePostgrest(
- tooljetDbManager: EntityManager,
- options: { user: string; enableAggregates: boolean; statementTimeoutInSecs: number }
-) {
- try {
- await tooljetDbManager.transaction(async (transactionalEntityManager) => {
- await transactionalEntityManager.queryRunner.query('CREATE SCHEMA IF NOT EXISTS postgrest');
- await transactionalEntityManager.queryRunner.query(`GRANT USAGE ON SCHEMA postgrest to ${options.user}`);
- await transactionalEntityManager.queryRunner.query(`create or replace function postgrest.pre_config()
- returns void as $$
- select
- set_config('pgrst.db_aggregates_enabled', '${options.enableAggregates}', false);
- select
- set_config('pgrst.db_schemas', string_agg(nspname, ','), true)
- from pg_namespace
- where nspname like 'workspace_%';
- $$ language sql;
- `);
- await transactionalEntityManager.queryRunner.query(
- `ALTER ROLE ${options.user} SET statement_timeout TO '${options.statementTimeoutInSecs}s'`
- );
- });
- } catch (error) {
- console.error('The tooljet database reconfiguration process encountered an error.', error);
- throw error;
- }
-}
diff --git a/server/src/modules/ignored/webhooks/webhooks.module.ts b/server/src/modules/ignored/webhooks/webhooks.module.ts
deleted file mode 100644
index d512792cca..0000000000
--- a/server/src/modules/ignored/webhooks/webhooks.module.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Module } from '@nestjs/common';
-import { TypeOrmModule } from '@nestjs/typeorm';
-import { CaslModule } from '../casl/casl.module';
-
-import { App } from 'src/entities/app.entity';
-import { AppVersion } from 'src/entities/app_version.entity';
-import { Credential } from 'src/entities/credential.entity';
-import { DataQuery } from 'src/entities/data_query.entity';
-import { DataSource } from 'src/entities/data_source.entity';
-import { File } from 'src/entities/file.entity';
-import { OrgEnvironmentVariable } from 'src/entities/org_envirnoment_variable.entity';
-import { Organization } from 'src/entities/organization.entity';
-import { Plugin } from 'src/entities/plugin.entity';
-import { User } from 'src/entities/user.entity';
-import { WorkflowExecution } from 'src/entities/workflow_execution.entity';
-import { WorkflowExecutionEdge } from 'src/entities/workflow_execution_edge.entity';
-import { WorkflowExecutionNode } from 'src/entities/workflow_execution_node.entity';
-
-import { AppEnvironmentService } from '@ee/app-environments/service';
-import { ConfigModule, ConfigService } from '@nestjs/config';
-import { CredentialsService } from '@services/credentials.service';
-import { DataQueriesService } from '@modules/data-queries/service';
-import { DataSourcesService } from '@services/data_sources.service';
-import { FilesService } from '@services/files.service';
-import { PluginsHelper } from 'src/helpers/plugins.helper';
-import { ThrottlerModule } from '@nestjs/throttler';
-import { WorkflowExecutionsController } from '@controllers/workflow_executions_controller';
-import { WorkflowExecutionsService } from '@services/workflow_executions.service';
-import { WorkflowWebhooksController } from '@controllers/workflow_webhooks.controller';
-import { WorkflowWebhooksListener } from '../../listeners/workflow_webhooks.listener';
-import { WorkflowWebhooksService } from '@services/workflow_webhooks.service';
-import { TooljetDbOperationsService } from '@services/tooljet_db_operations.service';
-import { TooljetDbService } from '@services/tooljet_db.service';
-import { PostgrestProxyService } from '@services/postgrest_proxy.service';
-import { OrganizationConstantsService } from '@modules/organization-constants/service';
-import { OrganizationConstant } from 'src/entities/organization_constants.entity';
-import { UserResourcePermissionsModule } from '@modules/user_resource_permissions/user_resource_permissions.module';
-import { UsersModule } from '@modules/users/users.module';
-import { EncryptionModule } from '@modules/encryption/module';
-
-@Module({
- imports: [
- UserResourcePermissionsModule,
- ThrottlerModule.forRootAsync({
- imports: [ConfigModule],
- inject: [ConfigService],
- useFactory: (config: ConfigService) => [
- {
- ttl: config.get('WEBHOOK_THROTTLE_TTL') || 60000,
- limit: config.get('WEBHOOK_THROTTLE_LIMIT') || 100,
- },
- ],
- }),
- TypeOrmModule.forFeature([
- App,
- AppVersion,
- Credential,
- DataQuery,
- DataSource,
- File,
- OrgEnvironmentVariable,
- Organization,
- Plugin,
- User,
- WorkflowExecution,
- WorkflowExecutionEdge,
- WorkflowExecutionNode,
- OrganizationConstant,
- ]),
- CaslModule,
- UsersModule,
- EncryptionModule,
- ],
- providers: [
- AppEnvironmentService,
- CredentialsService,
- DataQueriesService,
- DataSourcesService,
- FilesService,
- PluginsHelper,
- WorkflowExecutionsService,
- WorkflowWebhooksListener,
- WorkflowWebhooksService,
- TooljetDbOperationsService,
- TooljetDbService,
- PostgrestProxyService,
- OrganizationConstantsService,
- ],
- controllers: [WorkflowExecutionsController, WorkflowWebhooksController],
-})
-export class WebhooksModule {}
diff --git a/server/src/modules/ignored/worker.module.ts b/server/src/modules/ignored/worker.module.ts
deleted file mode 100644
index 39f914ee71..0000000000
--- a/server/src/modules/ignored/worker.module.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Module } from '@nestjs/common';
-import { WorkerService } from '@services/worker.service';
-
-@Module({
- imports: [],
- providers: [WorkerService],
- controllers: [],
- exports: [],
-})
-export class WorkerModule {}
diff --git a/server/src/modules/licensing/ability/index.ts b/server/src/modules/licensing/ability/index.ts
index 0092823b5b..9502e4fd6a 100644
--- a/server/src/modules/licensing/ability/index.ts
+++ b/server/src/modules/licensing/ability/index.ts
@@ -33,6 +33,7 @@ export class FeatureAbilityFactory extends AbilityFactory
FEATURE_KEY.GET_ORGANIZATION_LIMITS,
FEATURE_KEY.GET_USER_LIMITS,
FEATURE_KEY.GET_WORKFLOW_LIMITS,
+ FEATURE_KEY.GENERATE_CLOUD_TRIAL_LICENSE,
],
InstanceSettings
);
diff --git a/server/src/modules/licensing/configs/LicenseBase.ts b/server/src/modules/licensing/configs/LicenseBase.ts
index 5c8d82025a..084cc05d83 100644
--- a/server/src/modules/licensing/configs/LicenseBase.ts
+++ b/server/src/modules/licensing/configs/LicenseBase.ts
@@ -1,4 +1,4 @@
-import { LICENSE_LIMIT, LICENSE_TYPE, PLAN_DETAILS } from '@modules/licensing/constants';
+import { LICENSE_LIMIT, LICENSE_TYPE } from '@modules/licensing/constants';
import { Terms } from '@modules/licensing/interfaces/terms';
import {
BUSINESS_PLAN_TERMS,
@@ -80,7 +80,11 @@ export default class LicenseBase {
this._type = LICENSE_TYPE.BASIC;
return;
}
- this._expiryDate = expiryDate ? new Date(expiryDate) : (licenseData?.expiry ? new Date(`${licenseData?.expiry} 23:59:59`) : null);
+ this._expiryDate = expiryDate
+ ? new Date(expiryDate)
+ : licenseData?.expiry
+ ? new Date(`${licenseData?.expiry} 23:59:59`)
+ : null;
this._startDate = startDate;
this._isFlexiblePlan = licenseData?.plan?.isFlexible === true;
this._appsCount = licenseData?.apps;
diff --git a/server/src/modules/licensing/constants/PlanTerms.ts b/server/src/modules/licensing/constants/PlanTerms.ts
index cfbfb851bd..0c1791f7bc 100644
--- a/server/src/modules/licensing/constants/PlanTerms.ts
+++ b/server/src/modules/licensing/constants/PlanTerms.ts
@@ -86,6 +86,9 @@ export const CLOUD_EDITION_SETTINGS = {
SMTP_ENV_CONFIGURED: {
value: 'true',
},
+ ENABLE_SIGNUP: {
+ value: 'true',
+ },
};
export const BUSINESS_PLAN_TERMS = {
diff --git a/server/src/modules/licensing/constants/features.ts b/server/src/modules/licensing/constants/features.ts
index 3eb3894474..c98d276031 100644
--- a/server/src/modules/licensing/constants/features.ts
+++ b/server/src/modules/licensing/constants/features.ts
@@ -24,5 +24,9 @@ export const FEATURES: FeaturesConfig = {
},
[FEATURE_KEY.GET_WORKFLOW_LIMITS]: {},
[FEATURE_KEY.GET_USER_LIMITS]: {},
+ [FEATURE_KEY.GENERATE_CLOUD_TRIAL_LICENSE]: {},
+ [FEATURE_KEY.GENERATE_EE_TRIAL_LICENSE]: {
+ isPublic: true,
+ },
},
};
diff --git a/server/src/modules/licensing/constants/index.ts b/server/src/modules/licensing/constants/index.ts
index 2978eb9285..6c65f4cc73 100644
--- a/server/src/modules/licensing/constants/index.ts
+++ b/server/src/modules/licensing/constants/index.ts
@@ -168,6 +168,8 @@ export enum FEATURE_KEY {
GET_ORGANIZATION_LIMITS = 'get_organization_limits',
GET_USER_LIMITS = 'get_user_limits',
GET_WORKFLOW_LIMITS = 'get_workflow_limits',
+ GENERATE_CLOUD_TRIAL_LICENSE = 'generate_cloud_trial_license',
+ GENERATE_EE_TRIAL_LICENSE = 'generate_ee_trial_license',
}
export const ORGANIZATION_INSTANCE_KEY = 'INSTANCE';
diff --git a/server/src/modules/licensing/guards/auditLog.guard.ts b/server/src/modules/licensing/guards/auditLog.guard.ts
index 8ce49e453a..d312c64ed2 100644
--- a/server/src/modules/licensing/guards/auditLog.guard.ts
+++ b/server/src/modules/licensing/guards/auditLog.guard.ts
@@ -13,7 +13,7 @@ export class AuditLogsDurationGuard implements CanActivate {
auditLogsEnabled,
} = await this.licenseTermsService.getLicenseTerms(
[LICENSE_FIELD.STATUS, LICENSE_FIELD.MAX_DURATION_FOR_AUDIT_LOGS, LICENSE_FIELD.AUDIT_LOGS],
- request?.user?.organization?.id
+ request?.user?.organizationId
);
if (!auditLogsEnabled) {
throw new HttpException(
diff --git a/server/src/modules/licensing/guards/editorUser.guard.ts b/server/src/modules/licensing/guards/editorUser.guard.ts
index 4a7c232952..13a4fb1ae4 100644
--- a/server/src/modules/licensing/guards/editorUser.guard.ts
+++ b/server/src/modules/licensing/guards/editorUser.guard.ts
@@ -21,7 +21,7 @@ export class EditorUserCountGuard implements CanActivate {
const request = context.switchToHttp().getRequest();
const organizationId = request.body.organizationId;
const isWorkspaceSignup = !!organizationId;
- if (!isWorkspaceSignup && getTooljetEdition() === TOOLJET_EDITIONS.Cloud) {
+ if (isWorkspaceSignup && getTooljetEdition() === TOOLJET_EDITIONS.Cloud) {
// Not needed for cloud edition, as it is not used in the cloud
return true;
}
diff --git a/server/src/modules/licensing/guards/user.guard.ts b/server/src/modules/licensing/guards/user.guard.ts
index 0ae7b82039..3bee4ef0ad 100644
--- a/server/src/modules/licensing/guards/user.guard.ts
+++ b/server/src/modules/licensing/guards/user.guard.ts
@@ -20,7 +20,7 @@ export class UserCountGuard implements CanActivate {
: await this.licenseTermsService.getLicenseTermsInstance(LICENSE_FIELD.TOTAL_USERS);
if (
totalUsers !== LICENSE_LIMIT.UNLIMITED &&
- (await this.licenseCountsService.getUsersCount(organizationId)) >= totalUsers
+ (await this.licenseCountsService.getUsersCount(organizationId, true)) >= totalUsers
) {
throw new HttpException('License violation - Maximum user limit reached', 451);
}
diff --git a/server/src/modules/licensing/service.ts b/server/src/modules/licensing/service.ts
index eccde55de1..41abdd9a94 100644
--- a/server/src/modules/licensing/service.ts
+++ b/server/src/modules/licensing/service.ts
@@ -24,7 +24,7 @@ export class LicenseService implements ILicenseService {
async plans(): Promise<{ plans: any }> {
try {
/* TODO API request to the cloud server to a specific version license plans */
- } catch (error) {
+ } catch {
throw new HttpException('Failed to fetch plans', 500);
}
return { plans: PLAN_DETAILS };
diff --git a/server/src/modules/licensing/services/terms.service.ts b/server/src/modules/licensing/services/terms.service.ts
index e691da567c..025b8d6fa5 100644
--- a/server/src/modules/licensing/services/terms.service.ts
+++ b/server/src/modules/licensing/services/terms.service.ts
@@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common';
-import { LICENSE_FIELD } from '@modules/licensing/constants';
+import { LICENSE_FIELD, ORGANIZATION_INSTANCE_KEY } from '@modules/licensing/constants';
import { LicenseInitService } from '../interfaces/IService';
import { LicenseTermsService as ILicenseTermsService } from '../interfaces/IService';
import License from '../configs/License';
-
@Injectable()
export class LicenseTermsService extends ILicenseTermsService {
constructor(protected readonly licenseInitService: LicenseInitService) {
@@ -11,7 +10,7 @@ export class LicenseTermsService extends ILicenseTermsService {
}
async getLicenseTermsInstance(type: LICENSE_FIELD | LICENSE_FIELD[]): Promise {
- throw new Error('Method not implemented. This method is not supported on cloud.');
+ return this.getLicenseTerms(type, ORGANIZATION_INSTANCE_KEY);
}
// This function should be called to get a specific license term
diff --git a/server/src/modules/licensing/types/index.ts b/server/src/modules/licensing/types/index.ts
index 4c49b18dbe..a345e6e6c5 100644
--- a/server/src/modules/licensing/types/index.ts
+++ b/server/src/modules/licensing/types/index.ts
@@ -17,6 +17,8 @@ interface Features {
[FEATURE_KEY.UPDATE_LICENSE]: FeatureConfig;
[FEATURE_KEY.GET_ORGANIZATION_LIMITS]: FeatureConfig;
[FEATURE_KEY.GET_APP_LIMITS]: FeatureConfig;
+ [FEATURE_KEY.GENERATE_CLOUD_TRIAL_LICENSE]: FeatureConfig;
+ [FEATURE_KEY.GENERATE_EE_TRIAL_LICENSE]: FeatureConfig;
}
export interface FeaturesConfig {
diff --git a/server/src/modules/onboarding/service.ts b/server/src/modules/onboarding/service.ts
index 9bad731b4a..69d1146fdc 100644
--- a/server/src/modules/onboarding/service.ts
+++ b/server/src/modules/onboarding/service.ts
@@ -40,7 +40,7 @@ import { USER_ROLE } from '@modules/group-permissions/constants';
import { ActivateAccountWithTokenDto } from '@modules/onboarding/dto/activate-account-with-token.dto';
import { AppSignupDto } from '@modules/auth/dto';
import { SIGNUP_ERRORS } from 'src/helpers/errors.constants';
-const uuid = require('uuid');
+import * as uuid from 'uuid';
import { INSTANCE_SYSTEM_SETTINGS, INSTANCE_USER_SETTINGS } from '@modules/instance-settings/constants';
import { ResendInviteDto } from '@modules/onboarding/dto/resend-invite.dto';
import { EventEmitter2 } from '@nestjs/event-emitter';
@@ -399,6 +399,7 @@ export class OnboardingService implements IOnboardingService {
type: EMAIL_EVENTS.SEND_WELCOME_EMAIL,
payload: {
to: user.email,
+ // eslint-disable-next-line no-constant-binary-expression
name: `${user.firstName} ${user.lastName}` || '',
invitationtoken: user.invitationToken,
organizationInvitationToken: `${organizationUser.invitationToken}`,
diff --git a/server/src/modules/onboarding/util.service.ts b/server/src/modules/onboarding/util.service.ts
index 8c58979565..1d2dfb90a6 100644
--- a/server/src/modules/onboarding/util.service.ts
+++ b/server/src/modules/onboarding/util.service.ts
@@ -6,7 +6,7 @@ import { OrganizationUser } from '../../entities/organization_user.entity';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { TrialUserDto } from '@modules/onboarding/dto/user.dto';
import { LicenseCountsService } from '../licensing/services/count.service';
-import { LICENSE_TRIAL_API } from '../licensing/constants';
+import { LICENSE_TRIAL_API, ORGANIZATION_INSTANCE_KEY } from '../licensing/constants';
import got from 'got/dist/source';
import { HttpException } from '@nestjs/common';
import { fullName, generateNextNameAndSlug, generateOrgInviteURL } from 'src/helpers/utils.helper';
@@ -40,7 +40,7 @@ import { UserOnboardingDetails } from './types';
import { OnboardingStatus } from './constants';
import { IOnboardingUtilService } from './interfaces/IUtilService';
import { SetupOrganizationsUtilService } from '@modules/setup-organization/util.service';
-const uuid = require('uuid');
+import * as uuid from 'uuid';
@Injectable()
export class OnboardingUtilService implements IOnboardingUtilService {
@@ -72,7 +72,10 @@ export class OnboardingUtilService implements IOnboardingUtilService {
const otherData = { companySize, role, phoneNumber };
await dbTransactionWrap(async (manager: EntityManager) => {
- const { editor, viewer } = await this.licenseCountsService.fetchTotalViewerEditorCount('INSTANCE',manager);
+ const { editor, viewer } = await this.licenseCountsService.fetchTotalViewerEditorCount(
+ ORGANIZATION_INSTANCE_KEY,
+ manager
+ );
const body = {
hostname,
@@ -252,12 +255,17 @@ export class OnboardingUtilService implements IOnboardingUtilService {
case hasWorkspaceInviteButUserWantsInstanceSignup: {
const firstTimeSignup = ![SOURCE.SIGNUP, SOURCE.WORKSPACE_SIGNUP].includes(existingUser.source as SOURCE);
if (firstTimeSignup) {
- if(defaultWorkspace) {
- return this.updateExistingUserDefaultWorkspace({
- password,
- firstName,
- lastName
- },existingUser, defaultWorkspace, manager);
+ if (defaultWorkspace) {
+ return this.updateExistingUserDefaultWorkspace(
+ {
+ password,
+ firstName,
+ lastName,
+ },
+ existingUser,
+ defaultWorkspace,
+ manager
+ );
}
/* Invite user doing instance signup. So reset name fields and set password */
@@ -266,7 +274,7 @@ export class OnboardingUtilService implements IOnboardingUtilService {
(await this.instanceSettingsUtilService.getSettings(INSTANCE_USER_SETTINGS.ALLOW_PERSONAL_WORKSPACE)) ===
'true';
- if (!existingUser.defaultOrganizationId && isPersonalWorkspaceAllowed) {
+ if (!existingUser.defaultOrganizationId && isPersonalWorkspaceAllowed) {
const personalWorkspaces = await this.organizationUsersUtilService.personalWorkspaces(existingUser.id);
if (personalWorkspaces.length) {
defaultOrganizationId = personalWorkspaces[0].organizationId;
@@ -629,7 +637,7 @@ export class OnboardingUtilService implements IOnboardingUtilService {
// Create user with end-user role in default workspace
const lifeCycleParms = getUserStatusAndSource(lifecycleEvents.USER_SIGN_UP);
-
+
const user = await this.create(
{
email,
@@ -661,8 +669,8 @@ export class OnboardingUtilService implements IOnboardingUtilService {
// Send welcome email
this.eventEmitter.emit('emailEvent', {
- type: EMAIL_EVENTS.SEND_WELCOME_EMAIL,
- payload: {
+ type: EMAIL_EVENTS.SEND_WELCOME_EMAIL,
+ payload: {
to: user.email,
name: user.firstName,
invitationtoken: user.invitationToken,
@@ -686,10 +694,10 @@ export class OnboardingUtilService implements IOnboardingUtilService {
where: {
userId: existingUser.id,
organizationId: defaultWorkspace.id,
- }
+ },
});
- if(existingOrgUser){
+ if (existingOrgUser) {
throw new NotAcceptableException(
'The user is already registered. Please check your inbox for the activation link'
);
@@ -708,20 +716,20 @@ export class OnboardingUtilService implements IOnboardingUtilService {
manager
);
- await this.organizationUserRepository.createOne(
- existingUser,
- defaultWorkspace,
- true,
- manager,
- WORKSPACE_USER_SOURCE.SIGNUP
- );
+ await this.organizationUserRepository.createOne(
+ existingUser,
+ defaultWorkspace,
+ true,
+ manager,
+ WORKSPACE_USER_SOURCE.SIGNUP
+ );
- // Add end-user role in default workspace if not already present
- await this.rolesUtilService.addUserRole(
- defaultWorkspace.id,
- { role: USER_ROLE.END_USER, userId: existingUser.id },
- manager
- );
+ // Add end-user role in default workspace if not already present
+ await this.rolesUtilService.addUserRole(
+ defaultWorkspace.id,
+ { role: USER_ROLE.END_USER, userId: existingUser.id },
+ manager
+ );
// Validate license
await this.licenseUserService.validateUser(manager, existingUser?.defaultOrganizationId);
diff --git a/server/src/modules/organization-payments/ability/index.ts b/server/src/modules/organization-payments/ability/index.ts
index 01c16af075..18ed7f0ad6 100644
--- a/server/src/modules/organization-payments/ability/index.ts
+++ b/server/src/modules/organization-payments/ability/index.ts
@@ -6,7 +6,7 @@ import { FEATURE_KEY } from '@modules/organization-payments/constants';
import { OrganizationSubscription } from '@entities/organization_subscription.entity';
type Subjects = InferSubjects | 'all';
-export type OrganizationConstantAbility = Ability<[FEATURE_KEY, Subjects]>;
+export type OrganizationPaymentsAbility = Ability<[FEATURE_KEY, Subjects]>;
@Injectable()
export class FeatureAbilityFactory extends AbilityFactory {
@@ -15,25 +15,26 @@ export class FeatureAbilityFactory extends AbilityFactory
}
protected defineAbilityFor(
- can: AbilityBuilder['can'],
+ can: AbilityBuilder['can'],
userPermissions: UserAllPermissions
): void {
- const { superAdmin, isAdmin } = userPermissions;
+ const { isAdmin } = userPermissions;
- if (superAdmin || isAdmin) {
+ if (isAdmin) {
can(
[
FEATURE_KEY.CREATE_PORTAL_LINK,
- FEATURE_KEY.GENERATE_PAYMENT_LINK,
+ FEATURE_KEY.GET_CURRENT_PLAN_DETAILS,
FEATURE_KEY.GET_PRORATION,
FEATURE_KEY.GET_REDIRECT_URL,
FEATURE_KEY.GET_UPCOMING_INVOICE,
- FEATURE_KEY.STRIPE_WEBHOOK,
FEATURE_KEY.UPDATE_INVOICE,
FEATURE_KEY.UPDATE_SUBSCRIPTION,
],
OrganizationSubscription
);
}
+
+ can([FEATURE_KEY.STRIPE_WEBHOOK], OrganizationSubscription);
}
}
diff --git a/server/src/modules/organization-payments/constants/feature.ts b/server/src/modules/organization-payments/constants/feature.ts
index ce230c8c62..1cce121230 100644
--- a/server/src/modules/organization-payments/constants/feature.ts
+++ b/server/src/modules/organization-payments/constants/feature.ts
@@ -5,11 +5,13 @@ import { FeaturesConfig } from '@modules/organization-payments/types';
export const FEATURES: FeaturesConfig = {
[MODULES.ORGANIZATION_PAYMENTS]: {
[FEATURE_KEY.CREATE_PORTAL_LINK]: {},
- [FEATURE_KEY.GENERATE_PAYMENT_LINK]: {},
+ [FEATURE_KEY.GET_CURRENT_PLAN_DETAILS]: {},
[FEATURE_KEY.GET_PRORATION]: {},
[FEATURE_KEY.GET_REDIRECT_URL]: {},
[FEATURE_KEY.GET_UPCOMING_INVOICE]: {},
- [FEATURE_KEY.STRIPE_WEBHOOK]: {},
+ [FEATURE_KEY.STRIPE_WEBHOOK]: {
+ isPublic: true,
+ },
[FEATURE_KEY.UPDATE_INVOICE]: {},
[FEATURE_KEY.UPDATE_SUBSCRIPTION]: {},
},
diff --git a/server/src/modules/organization-payments/constants/index.ts b/server/src/modules/organization-payments/constants/index.ts
index a66d218c8a..073ead8dbe 100644
--- a/server/src/modules/organization-payments/constants/index.ts
+++ b/server/src/modules/organization-payments/constants/index.ts
@@ -1,6 +1,6 @@
export enum FEATURE_KEY {
GET_UPCOMING_INVOICE = 'get_upcoming_invoice',
- GENERATE_PAYMENT_LINK = 'generate_payment_link',
+ GET_CURRENT_PLAN_DETAILS = 'get_current_plan_details',
UPDATE_INVOICE = 'update_invoice',
GET_PRORATION = 'get_proration',
CREATE_PORTAL_LINK = 'create_portal_link',
diff --git a/server/src/modules/organization-payments/controller.ts b/server/src/modules/organization-payments/controller.ts
index 058661db01..8648893bb5 100644
--- a/server/src/modules/organization-payments/controller.ts
+++ b/server/src/modules/organization-payments/controller.ts
@@ -1,9 +1,50 @@
import { InitModule } from '@modules/app/decorators/init-module';
import { MODULES } from '@modules/app/constants/modules';
-import { Controller } from '@nestjs/common';
+import { Controller, UseGuards } from '@nestjs/common';
+import { IOrganizationPaymentController } from './interfaces/IController';
+import { ProrationDto, PortalDto, PaymentRedirectDto } from './dto';
+import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard';
+import { FeatureAbilityGuard } from './ability/guard';
@Controller('organization/payment')
@InitModule(MODULES.ORGANIZATION_PAYMENTS)
-export class OrganizationPaymentController {
+@UseGuards(JwtAuthGuard, FeatureAbilityGuard)
+export class OrganizationPaymentController implements IOrganizationPaymentController {
constructor() {}
+ getUpcomingInvoice(organizationId: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getCurrentPlan(organizationId: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ updateInvoice(organizationId: string, id: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getProration(organizationId: string, prorationDto: ProrationDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getPortalLink(portalDto: PortalDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ updateSubscription(organizationId: string, prorationDto: ProrationDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ webhookCheckoutSessionCompleteHandler(paymentObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ upcomingInvoiceHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ paymentFailedHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ subscriptionUpdateHandler(subscription: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ webhookInvoicePaidHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getRedirectUrl(user: any, paymentRedirectDto: PaymentRedirectDto): Promise {
+ throw new Error('Method not implemented.');
+ }
}
diff --git a/server/src/modules/organization-payments/interfaces/IController.ts b/server/src/modules/organization-payments/interfaces/IController.ts
new file mode 100644
index 0000000000..4fdd86d3f8
--- /dev/null
+++ b/server/src/modules/organization-payments/interfaces/IController.ts
@@ -0,0 +1,27 @@
+import { PortalDto, ProrationDto, PaymentRedirectDto } from '@modules/organization-payments/dto';
+
+export interface IOrganizationPaymentController {
+ getUpcomingInvoice(organizationId: string): Promise;
+
+ getCurrentPlan(organizationId: string): Promise;
+
+ updateInvoice(organizationId, string, id: string): Promise;
+
+ getProration(organizationId: string, prorationDto: ProrationDto): Promise;
+
+ getPortalLink(portalDto: PortalDto): Promise;
+
+ updateSubscription(organizationId: string, prorationDto: ProrationDto): Promise;
+
+ webhookCheckoutSessionCompleteHandler(paymentObject: any): Promise;
+
+ upcomingInvoiceHandler(invoiceObject: any): Promise;
+
+ paymentFailedHandler(invoiceObject: any): Promise;
+
+ subscriptionUpdateHandler(subscription: any): Promise;
+
+ webhookInvoicePaidHandler(invoiceObject: any): Promise;
+
+ getRedirectUrl(user: any, paymentRedirectDto: PaymentRedirectDto): Promise;
+}
diff --git a/server/src/modules/organization-payments/interfaces/IService.ts b/server/src/modules/organization-payments/interfaces/IService.ts
new file mode 100644
index 0000000000..5bc14514db
--- /dev/null
+++ b/server/src/modules/organization-payments/interfaces/IService.ts
@@ -0,0 +1,47 @@
+import { OrganizationSubscriptionInvoice } from '@entities/organization_subscription_invoice.entity';
+import { PaymentRedirectDto, PortalDto, ProrationDto } from '../dto';
+import { OrganizationSubscription } from '@entities/organization_subscription.entity';
+
+export interface IOrganizationPaymentService {
+ getUpcomingInvoice(organizationId: string): Promise;
+
+ getCurrentPlan(organizationId: string): Promise;
+
+ updateInvoice(updateCondition?: any, updateObject?: any): Promise;
+
+ getProration(organizationId: string, prorationDto: ProrationDto): Promise;
+
+ getPortalLink(portalDto: PortalDto): Promise;
+
+ updateSubscription(organizationId: string, updatedSubscription: ProrationDto): Promise;
+
+ licenseUpgradeValidation(organizationId: string, checkParam: any, manager?: any): Promise;
+
+ UpdateOrInsertCloudLicense(organizationSubscription: OrganizationSubscription, manager?: any): Promise;
+
+ paymentFailedHandler(invoiceObject: any): Promise;
+
+ subscriptionUpdateHandler(subscriptionObject: any): Promise;
+
+ upcomingInvoiceHandler(invoiceObject: any): Promise;
+
+ webhookCheckoutSessionCompleteHandler(paymentObject: any): Promise;
+
+ webhookInvoicePaidHandler(invoiceObject: any): Promise;
+
+ getOrganizationLicensePayments(organizationId?: string, subscriptionId?: string): Promise;
+
+ createOrganizationLicensePayment(
+ organizationPaymentCreateObj: Partial,
+ manager?: any
+ ): Promise;
+
+ createUpcomingInvoice(
+ orgInvoice: Partial,
+ manager?: any
+ ): Promise;
+
+ updateOrganizationSubscription(updateCondition?: any, updateObject?: any): Promise;
+
+ getRedirectUrl(user: any, paymentRedirectDto: PaymentRedirectDto): Promise;
+}
diff --git a/server/src/modules/organization-payments/module.ts b/server/src/modules/organization-payments/module.ts
index 58bfe27e0a..9dab27c768 100644
--- a/server/src/modules/organization-payments/module.ts
+++ b/server/src/modules/organization-payments/module.ts
@@ -3,6 +3,7 @@ import { WhiteLabellingModule } from '../white-labelling/module';
import { EmailModule } from '@modules/email/module';
import { CrmModule } from '@modules/CRM/module';
import { SubModule } from '@modules/app/sub-module';
+import { FeatureAbilityFactory } from './ability';
@Module({})
export class OrganizationPaymentModule extends SubModule {
@@ -21,7 +22,7 @@ export class OrganizationPaymentModule extends SubModule {
await CrmModule.register(configs),
],
controllers: [OrganizationPaymentController],
- providers: [OrganizationPaymentService],
+ providers: [OrganizationPaymentService, FeatureAbilityFactory],
exports: [],
};
}
diff --git a/server/src/modules/organization-payments/service.ts b/server/src/modules/organization-payments/service.ts
index 22f8333d60..de031052c4 100644
--- a/server/src/modules/organization-payments/service.ts
+++ b/server/src/modules/organization-payments/service.ts
@@ -1,6 +1,70 @@
import { Injectable } from '@nestjs/common';
+import { IOrganizationPaymentService } from './interfaces/IService';
+import { OrganizationSubscription } from '@entities/organization_subscription.entity';
+import { OrganizationSubscriptionInvoice } from '@entities/organization_subscription_invoice.entity';
+import { ProrationDto, PortalDto, PaymentRedirectDto } from './dto';
@Injectable()
-export class OrganizationPaymentService {
+export class OrganizationPaymentService implements IOrganizationPaymentService {
constructor() {}
+ getUpcomingInvoice(organizationId: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getCurrentPlan(organizationId: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ updateInvoice(updateCondition?: any, updateObject?: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getProration(organizationId: string, prorationDto: ProrationDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getPortalLink(portalDto: PortalDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ updateSubscription(organizationId: string, updatedSubscription: ProrationDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+ licenseUpgradeValidation(organizationId: string, checkParam: any, manager?: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ UpdateOrInsertCloudLicense(organizationSubscription: OrganizationSubscription, manager?: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ paymentFailedHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ subscriptionUpdateHandler(subscriptionObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ upcomingInvoiceHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ webhookCheckoutSessionCompleteHandler(paymentObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ webhookInvoicePaidHandler(invoiceObject: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getOrganizationLicensePayments(organizationId?: string, subscriptionId?: string): Promise {
+ throw new Error('Method not implemented.');
+ }
+ createOrganizationLicensePayment(
+ organizationPaymentCreateObj: Partial,
+ manager?: any
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ createUpcomingInvoice(
+ orgInvoice: Partial,
+ manager?: any
+ ): Promise {
+ throw new Error('Method not implemented.');
+ }
+ updateOrganizationSubscription(updateCondition?: any, updateObject?: any): Promise {
+ throw new Error('Method not implemented.');
+ }
+ getRedirectUrl(user: any, paymentRedirectDto: PaymentRedirectDto): Promise {
+ throw new Error('Method not implemented.');
+ }
}
diff --git a/server/src/modules/organization-payments/types/index.ts b/server/src/modules/organization-payments/types/index.ts
index e11213ca8a..1047d068ab 100644
--- a/server/src/modules/organization-payments/types/index.ts
+++ b/server/src/modules/organization-payments/types/index.ts
@@ -4,7 +4,7 @@ import { MODULES } from '@modules/app/constants/modules';
interface Features {
[FEATURE_KEY.CREATE_PORTAL_LINK]: FeatureConfig;
- [FEATURE_KEY.GENERATE_PAYMENT_LINK]: FeatureConfig;
+ [FEATURE_KEY.GET_CURRENT_PLAN_DETAILS]: FeatureConfig;
[FEATURE_KEY.GET_PRORATION]: FeatureConfig;
[FEATURE_KEY.GET_REDIRECT_URL]: FeatureConfig;
[FEATURE_KEY.GET_UPCOMING_INVOICE]: FeatureConfig;
diff --git a/server/src/modules/organizations/controller.ts b/server/src/modules/organizations/controller.ts
index 509bcd98d9..f19523b789 100644
--- a/server/src/modules/organizations/controller.ts
+++ b/server/src/modules/organizations/controller.ts
@@ -50,30 +50,6 @@ export class OrganizationsController implements IOrganizationsController {
return;
}
- @InitFeature(FEATURE_KEY.WORKSPACE_ARCHIVE)
- @UseGuards(JwtAuthGuard)
- @Patch('/archive/:id')
- async archiveOrganization(
- @Body() organizationUpdateDto: OrganizationStatusUpdateDto,
- @Param('id') organizationId: string,
- @User() user: UserEntity
- ) {
- await this.organizationsService.updateOrganizationStatus(organizationId, organizationUpdateDto, user);
- return;
- }
-
- @InitFeature(FEATURE_KEY.WORKSPACE_UNARCHIVE)
- @UseGuards(JwtAuthGuard)
- @Patch('/unarchive/:id')
- async unarchiveOrganization(
- @Body() organizationUpdateDto: OrganizationStatusUpdateDto,
- @Param('id') organizationId: string,
- @User() user: UserEntity
- ) {
- await this.organizationsService.updateOrganizationStatus(organizationId, organizationUpdateDto, user);
- return;
- }
-
@InitFeature(FEATURE_KEY.CHECK_UNIQUE)
@UseGuards(JwtAuthGuard, FeatureAbilityGuard)
@Get('/is-unique')
@@ -87,4 +63,26 @@ export class OrganizationsController implements IOrganizationsController {
async checkUniqueWorkspaceName(@Query('name') name: string) {
return this.organizationsService.checkWorkspaceNameUniqueness(name);
}
+
+ @InitFeature(FEATURE_KEY.WORKSPACE_ARCHIVE)
+ @UseGuards(JwtAuthGuard, FeatureAbilityGuard)
+ @Patch('/archive/:id')
+ async archiveOrganization(
+ @Body() organizationUpdateDto: OrganizationStatusUpdateDto,
+ @Param('id') organizationId: string,
+ @User() user: UserEntity
+ ) {
+ throw new Error('Not implemented');
+ }
+
+ @InitFeature(FEATURE_KEY.WORKSPACE_UNARCHIVE)
+ @UseGuards(JwtAuthGuard, FeatureAbilityGuard)
+ @Patch('/unarchive/:id')
+ async unarchiveOrganization(
+ @Body() organizationUpdateDto: OrganizationStatusUpdateDto,
+ @Param('id') organizationId: string,
+ @User() user: UserEntity
+ ) {
+ throw new Error('Not implemented');
+ }
}
diff --git a/server/src/modules/organizations/repository.ts b/server/src/modules/organizations/repository.ts
index 15862f9c21..812288b042 100644
--- a/server/src/modules/organizations/repository.ts
+++ b/server/src/modules/organizations/repository.ts
@@ -34,7 +34,7 @@ export class OrganizationRepository extends Repository {
...conditions,
where: { ...conditions.where, slug },
});
- } catch (error) {
+ } catch {
organization = await this.manager.findOneOrFail(Organization, {
...conditions,
where: { ...conditions.where, id: slug },
@@ -87,7 +87,7 @@ export class OrganizationRepository extends Repository {
where: { slug },
select,
});
- } catch (error) {
+ } catch {
organization = await manager.findOneOrFail(Organization, {
where: { id: slug },
select,
@@ -211,7 +211,7 @@ export class OrganizationRepository extends Repository {
return await manager.findOneOrFail(Organization, {
where: { isDefault: true },
});
- } catch (error) {
+ } catch {
console.error('No default workspace in this instance');
return null;
}
diff --git a/server/src/modules/session/guards/organization-auth.guard.ts b/server/src/modules/session/guards/organization-auth.guard.ts
index 0b74911207..41a1285c58 100644
--- a/server/src/modules/session/guards/organization-auth.guard.ts
+++ b/server/src/modules/session/guards/organization-auth.guard.ts
@@ -12,7 +12,7 @@ export class OrganizationAuthGuard extends AuthGuard('jwt') {
if (request?.cookies['tj_auth_token']) {
try {
user = await super.canActivate(context);
- } catch (err) {
+ } catch {
return true;
}
return user;
diff --git a/server/src/modules/setup-organization/types/organization-inputs.ts b/server/src/modules/setup-organization/types/organization-inputs.ts
index af427cf785..3b2264ca1f 100644
--- a/server/src/modules/setup-organization/types/organization-inputs.ts
+++ b/server/src/modules/setup-organization/types/organization-inputs.ts
@@ -2,4 +2,5 @@ export interface OrganizationInputs {
name: string;
slug: string;
isDefault?: boolean;
+ ownerId?: string;
}
diff --git a/server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf b/server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf.example
similarity index 100%
rename from server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf
rename to server/src/modules/tooljet-db/external-modules/postgrest/postgrest.conf.example
diff --git a/server/src/modules/tooljet-db/services/tooljet-db-table-operations.service.ts b/server/src/modules/tooljet-db/services/tooljet-db-table-operations.service.ts
index 47ae973e4d..e67b9c8f87 100644
--- a/server/src/modules/tooljet-db/services/tooljet-db-table-operations.service.ts
+++ b/server/src/modules/tooljet-db/services/tooljet-db-table-operations.service.ts
@@ -12,7 +12,7 @@ import {
} from 'typeorm';
import { InjectEntityManager } from '@nestjs/typeorm';
import { InternalTable } from 'src/entities/internal_table.entity';
-import { formatJoinsJSONBPath, formatJSONB } from 'src/helpers/utils.helper';
+import { formatJoinsJSONBPath, formatJSONB, getTooljetEdition } from 'src/helpers/utils.helper';
import { isString, isEmpty, camelCase } from 'lodash';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
@@ -30,7 +30,7 @@ import {
isSQLModeDisabled,
} from 'src/helpers/tooljet_db.helper';
import { OrganizationTjdbConfigurations } from 'src/entities/organization_tjdb_configurations.entity';
-const crypto = require('crypto');
+import * as crypto from 'crypto';
import {
PostgrestError,
TooljetDatabaseColumn,
@@ -46,6 +46,7 @@ import { ConfigService } from '@nestjs/config';
import { LICENSE_FIELD, LICENSE_LIMIT, LICENSE_LIMITS_LABEL } from '@modules/licensing/constants';
import { generatePayloadForLimits } from '@modules/licensing/helper';
import { LicenseTermsService } from '@modules/licensing/interfaces/IService';
+import { TOOLJET_EDITIONS } from '@modules/app/constants';
enum AggregateFunctions {
sum = 'SUM',
@@ -836,11 +837,27 @@ export class TooljetDbTableOperationsService {
[LICENSE_FIELD.TABLE_COUNT, LICENSE_FIELD.STATUS],
organizationId
);
+ if (licenseTerms[LICENSE_FIELD.TABLE_COUNT] === LICENSE_LIMIT.UNLIMITED) {
+ return {
+ tablesCount: generatePayloadForLimits(
+ 0,
+ licenseTerms[LICENSE_FIELD.TABLE_COUNT],
+ licenseTerms[LICENSE_FIELD.STATUS],
+ LICENSE_LIMITS_LABEL.TABLES
+ ),
+ };
+ }
+ const edition: TOOLJET_EDITIONS = getTooljetEdition() as TOOLJET_EDITIONS;
+ const tableCount =
+ edition === TOOLJET_EDITIONS.Cloud
+ ? await this.manager
+ .createQueryBuilder(InternalTable, 'internal_table')
+ .where('internal_table.organizationId = :organizationId', { organizationId })
+ .getCount()
+ : await this.manager.createQueryBuilder(InternalTable, 'internal_table').getCount();
return {
tablesCount: generatePayloadForLimits(
- licenseTerms[LICENSE_FIELD.TABLE_COUNT] !== LICENSE_LIMIT.UNLIMITED
- ? await this.manager.createQueryBuilder(InternalTable, 'internal_table').getCount()
- : 0,
+ tableCount,
licenseTerms[LICENSE_FIELD.TABLE_COUNT],
licenseTerms[LICENSE_FIELD.STATUS],
LICENSE_LIMITS_LABEL.TABLES
diff --git a/server/src/modules/users/repositories/repository.ts b/server/src/modules/users/repositories/repository.ts
index e3417e5e50..bbc4acaefd 100644
--- a/server/src/modules/users/repositories/repository.ts
+++ b/server/src/modules/users/repositories/repository.ts
@@ -99,7 +99,7 @@ export class UserRepository extends Repository {
if (existingUser) {
Object.assign(existingUser, user);
- return manager.save(User, existingUser);
+ return manager.update(User, { id: existingUser.id }, user);
} else {
const newUser = manager.create(User, user);
return manager.save(User, newUser);
diff --git a/server/src/modules/white-labelling/Interfaces/IController.ts b/server/src/modules/white-labelling/Interfaces/IController.ts
index 955f94e624..91038a06d6 100644
--- a/server/src/modules/white-labelling/Interfaces/IController.ts
+++ b/server/src/modules/white-labelling/Interfaces/IController.ts
@@ -2,15 +2,15 @@ import { User } from '@entities/user.entity';
import { UpdateWhiteLabellingDto } from '@modules/white-labelling/dto';
export interface IWhiteLabellingController {
- get(req: any): Promise; // Method to fetch white labeling settings
+ getInstanceWhiteLabelling(): Promise; // Method to fetch instance level white labeling settings
- update(updateWhiteLabellingDto: UpdateWhiteLabellingDto, user: User): Promise; // Method to update white labeling settings
+ updateInstanceWhiteLabelling(updateWhiteLabellingDto: UpdateWhiteLabellingDto, user: User): Promise; // Method to update instance level white labeling settings
- getWorkspaceSettings(workspaceId: string): Promise; // Method to get specific white labeling settings for an organization
+ getWorkspaceWhiteLabelling(organizationId: string): Promise; // Method to get workspace level white labeling settings
- updateWorkspaceSettings(
+ updateWorkspaceWhiteLabelling(
organizationId: string,
updateWhiteLabellingDto: UpdateWhiteLabellingDto,
user: User
- ): Promise; // Method to update white labeling settings for a specific organization
+ ): Promise; // Method to update workspace level white labeling settings
}
diff --git a/server/src/modules/white-labelling/ability/index.ts b/server/src/modules/white-labelling/ability/index.ts
index 17973b3286..385160a63b 100644
--- a/server/src/modules/white-labelling/ability/index.ts
+++ b/server/src/modules/white-labelling/ability/index.ts
@@ -21,9 +21,9 @@ export class FeatureAbilityFactory extends AbilityFactory
const { superAdmin, isAdmin } = userPermissions;
if (isAdmin || superAdmin) {
- can([FEATURE_KEY.UPDATE, FEATURE_KEY.UPDATE_WORKSPACE_SETTINGS], WhiteLabelling);
+ can([FEATURE_KEY.UPDATE, FEATURE_KEY.UPDATE_ORGANIZATION_WHITE_LABELS], WhiteLabelling);
}
// All users can perform these actions
- can([FEATURE_KEY.GET, FEATURE_KEY.GET_WORKSPACE_SETTINGS], WhiteLabelling);
+ can([FEATURE_KEY.GET, FEATURE_KEY.GET_ORGANIZATION_WHITE_LABELS], WhiteLabelling);
}
}
diff --git a/server/src/modules/white-labelling/constant/feature.ts b/server/src/modules/white-labelling/constant/feature.ts
index e020c7b1ef..c519f0bd9a 100644
--- a/server/src/modules/white-labelling/constant/feature.ts
+++ b/server/src/modules/white-labelling/constant/feature.ts
@@ -5,9 +5,9 @@ import { FEATURE_KEY } from '.';
export const FEATURES: FeaturesConfig = {
[MODULES.WHITE_LABELLING]: {
- [FEATURE_KEY.GET]: {},
+ [FEATURE_KEY.GET]: { isPublic: true },
[FEATURE_KEY.UPDATE]: { license: LICENSE_FIELD.WHITE_LABEL },
- [FEATURE_KEY.GET_WORKSPACE_SETTINGS]: { isPublic: true },
- [FEATURE_KEY.UPDATE_WORKSPACE_SETTINGS]: { license: LICENSE_FIELD.WHITE_LABEL },
+ [FEATURE_KEY.GET_ORGANIZATION_WHITE_LABELS]: { license: LICENSE_FIELD.WHITE_LABEL },
+ [FEATURE_KEY.UPDATE_ORGANIZATION_WHITE_LABELS]: { license: LICENSE_FIELD.WHITE_LABEL },
},
};
diff --git a/server/src/modules/white-labelling/constant/index.ts b/server/src/modules/white-labelling/constant/index.ts
index 1ba03ac5da..03e1228cf1 100644
--- a/server/src/modules/white-labelling/constant/index.ts
+++ b/server/src/modules/white-labelling/constant/index.ts
@@ -7,6 +7,7 @@ export const DEFAULT_WHITE_LABELLING_SETTINGS = {
export enum FEATURE_KEY {
GET = 'GET', // For the get method (fetching general white-labelling info)
UPDATE = 'UPDATE', // For the update method (updating white-labelling settings)
- GET_WORKSPACE_SETTINGS = 'GET_WORKSPACE_SETTINGS', // For the getCloudSettings method
- UPDATE_WORKSPACE_SETTINGS = 'UPDATE_WORKSPACE_SETTINGS', // For the updateCloudSettings method
+ GET_ORGANIZATION_WHITE_LABELS = 'GET_ORGANIZATION_WHITE_LABELS', // For the getCloudSettings method
+ UPDATE_ORGANIZATION_WHITE_LABELS = 'UPDATE_WORKSPACE_SETTINGS_CLOUD', // For the updateCloudSettings method
}
+// Review the name change : name looks ambigous for cloud white labelling.
diff --git a/server/src/modules/white-labelling/controller.ts b/server/src/modules/white-labelling/controller.ts
index cbe4b70198..67b1e1ecbe 100644
--- a/server/src/modules/white-labelling/controller.ts
+++ b/server/src/modules/white-labelling/controller.ts
@@ -1,4 +1,4 @@
-import { Controller, Body, Put, Get, Param, Query } from '@nestjs/common';
+import { Controller, Body, Put, Get, Param } from '@nestjs/common';
import { UpdateWhiteLabellingDto } from './dto';
import { IWhiteLabellingController } from './Interfaces/IController';
import { NotFoundException } from '@nestjs/common';
@@ -7,6 +7,8 @@ import { InitFeature } from '@modules/app/decorators/init-feature.decorator';
import { MODULES } from '@modules/app/constants/modules';
import { FEATURE_KEY } from './constant';
import { WhiteLabellingService } from './service';
+import { User as UserEntity } from '@entities/user.entity';
+import { User } from '@modules/app/decorators/user.decorator';
@Controller('white-labelling')
@InitModule(MODULES.WHITE_LABELLING)
@@ -15,25 +17,29 @@ export class WhiteLabellingController implements IWhiteLabellingController {
@Get()
@InitFeature(FEATURE_KEY.GET)
- async get(@Query('organizationId') organizationId: string) {
- return this.whiteLabellingService.getProcessedSettings(null);
+ async getInstanceWhiteLabelling() {
+ const formattedSettings = await this.whiteLabellingService.getProcessedSettings(null);
+ return formattedSettings;
}
@Put()
@InitFeature(FEATURE_KEY.UPDATE)
- async update(@Body() updateWhiteLabellingDto: UpdateWhiteLabellingDto) {
+ async updateInstanceWhiteLabelling(
+ @Body() updateWhiteLabellingDto: UpdateWhiteLabellingDto,
+ @User() user: UserEntity
+ ) {
throw new NotFoundException();
}
- @Get('/:workspaceId')
- @InitFeature(FEATURE_KEY.GET_WORKSPACE_SETTINGS)
- async getWorkspaceSettings(req: any) {
+ @Get('/:organizationId')
+ @InitFeature(FEATURE_KEY.GET_ORGANIZATION_WHITE_LABELS)
+ async getWorkspaceWhiteLabelling(req: any) {
throw new NotFoundException();
}
@Put('/:organizationId')
- @InitFeature(FEATURE_KEY.UPDATE_WORKSPACE_SETTINGS)
- async updateWorkspaceSettings(
+ @InitFeature(FEATURE_KEY.UPDATE_ORGANIZATION_WHITE_LABELS)
+ async updateWorkspaceWhiteLabelling(
@Param('organizationId') organizationId: string,
@Body() updateWhiteLabellingDto: UpdateWhiteLabellingDto,
user: any
diff --git a/server/src/modules/white-labelling/guards/verifyOrgId.guard.ts b/server/src/modules/white-labelling/guards/verifyOrgId.guard.ts
index d40f86a8e3..c613b3e529 100644
--- a/server/src/modules/white-labelling/guards/verifyOrgId.guard.ts
+++ b/server/src/modules/white-labelling/guards/verifyOrgId.guard.ts
@@ -8,7 +8,7 @@ export class OrganizationIdSlugValidationGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise {
const request = context.switchToHttp().getRequest();
- const workspaceId = request?.query['organizationId'];
+ const workspaceId = request?.params.organizationId;
let organization: Organization;
try {
diff --git a/server/src/modules/white-labelling/types/index.ts b/server/src/modules/white-labelling/types/index.ts
index 235063b0c0..ad42bad385 100644
--- a/server/src/modules/white-labelling/types/index.ts
+++ b/server/src/modules/white-labelling/types/index.ts
@@ -5,8 +5,8 @@ import { FEATURE_KEY } from '../constant';
interface Features {
[FEATURE_KEY.GET]: FeatureConfig;
[FEATURE_KEY.UPDATE]: FeatureConfig;
- [FEATURE_KEY.GET_WORKSPACE_SETTINGS]: FeatureConfig;
- [FEATURE_KEY.UPDATE_WORKSPACE_SETTINGS]: FeatureConfig;
+ [FEATURE_KEY.GET_ORGANIZATION_WHITE_LABELS]: FeatureConfig;
+ [FEATURE_KEY.UPDATE_ORGANIZATION_WHITE_LABELS]: FeatureConfig;
}
export interface FeaturesConfig {
diff --git a/server/tsconfig.json b/server/tsconfig.json
index 68f4ca18bc..cfcc76168c 100644
--- a/server/tsconfig.json
+++ b/server/tsconfig.json
@@ -1,7 +1,10 @@
{
"compilerOptions": {
"moduleResolution": "node",
- "types": ["node", "rxjs"],
+ "types": [
+ "node",
+ "rxjs"
+ ],
"resolveJsonModule": true,
"module": "commonjs",
"declaration": true,
@@ -17,22 +20,25 @@
"incremental": true,
"skipLibCheck": true,
"paths": {
- "@ee/*": ["ee/*"],
- "@apps/*": ["ee/apps/*"],
- "@entities/*": ["src/entities/*"],
- "@services/*": ["src/services/*"],
- "@controllers/*": ["src/controllers/*"],
- "@repositories/*": ["src/repositories/*"],
- "@dto/*": ["src/dto/*"],
- "@modules/*": ["src/modules/*"],
- "@helpers/*": ["src/helpers/*"],
- "@instance-settings/*": ["ee/instance-settings/*"],
+ "@ee/*": [
+ "ee/*"
+ ],
+ "@entities/*": [
+ "src/entities/*"
+ ],
+ "@dto/*": [
+ "src/dto/*"
+ ],
+ "@modules/*": [
+ "src/modules/*"
+ ],
+ "@helpers/*": [
+ "src/helpers/*"
+ ],
},
},
"exclude": [
"node_modules",
"dist",
- "src/services/ignored",
- "ee/ignored"
]
-}
+}
\ No newline at end of file