Merge branch 'main' into release/marketplace-sprint-12

This commit is contained in:
Ganesh Kumar 2025-07-02 17:47:21 +05:30
commit a80fc752d8
13 changed files with 121 additions and 59 deletions

View file

@ -42,6 +42,20 @@ else
echo "Using external PostgREST at $PGRST_HOST." echo "Using external PostgREST at $PGRST_HOST."
fi fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
npm run worker:prod
else
# Determine setup command based on the presence of ./server/dist
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else
SETUP_CMD='npm run db:setup'
fi
fi
# Neo4j configuration # Neo4j configuration
# ---------------------------------- # ----------------------------------
# Default Neo4j environment values # Default Neo4j environment values
@ -63,14 +77,14 @@ if [ -n "$NEO4J_AUTH" ]; then
export NEO4J_USERNAME export NEO4J_USERNAME
export NEO4J_PASSWORD export NEO4J_PASSWORD
echo "Neo4j authentication configured with username: $NEO4J_USERNAME" echo "Neo4j authentication configured with username: $NEO4J_USERNAME" >/dev/null 2>&1
else else
echo "NEO4J_AUTH not set, using default authentication" echo "NEO4J_AUTH not set, using default authentication" >/dev/null 2>&1
fi fi
# Check if Neo4j is already initialized and set password if necessary # Check if Neo4j is already initialized and set password if necessary
if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then
echo "Setting Neo4j initial password..." echo "Setting Neo4j initial password..." >/dev/null 2>&1
# Ensure Neo4j is not running before setting the initial password # Ensure Neo4j is not running before setting the initial password
neo4j stop || true neo4j stop || true
@ -78,27 +92,27 @@ if [ "$NEO4J_AUTH" != "none" ] && [ -n "$NEO4J_PASSWORD" ]; then
# Set the initial password using the correct command format for Neo4j 5.x # Set the initial password using the correct command format for Neo4j 5.x
NEO4J_ADMIN_CMD=$(which neo4j-admin) NEO4J_ADMIN_CMD=$(which neo4j-admin)
NEO4J_VERSION=$(neo4j --version | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -n 1) NEO4J_VERSION=$(neo4j --version | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -n 1)
echo "Detected Neo4j version: $NEO4J_VERSION" echo "Detected Neo4j version: $NEO4J_VERSION" >/dev/null 2>&1
# Use version-specific command format # Use version-specific command format
MAJOR_VERSION=$(echo $NEO4J_VERSION | cut -d. -f1) MAJOR_VERSION=$(echo $NEO4J_VERSION | cut -d. -f1)
if [ "$MAJOR_VERSION" -ge "5" ]; then if [ "$MAJOR_VERSION" -ge "5" ]; then
# For Neo4j 5.x and higher # For Neo4j 5.x and higher
echo "Using Neo4j 5.x+ password command format" echo "Using Neo4j 5.x+ password command format" >/dev/null 2>&1
$NEO4J_ADMIN_CMD dbms set-initial-password "$NEO4J_PASSWORD" --require-password-change=false >/dev/null 2>&1 || { $NEO4J_ADMIN_CMD dbms set-initial-password "$NEO4J_PASSWORD" --require-password-change=false >/dev/null 2>&1 || {
echo "Warning: Could not set Neo4j password, it may already be set" echo "Warning: Could not set Neo4j password, it may already be set" >/dev/null 2>&1
} }
else else
# For Neo4j 4.x and lower # For Neo4j 4.x and lower
echo "Using Neo4j 4.x password command format" >/dev/null 2>&1 echo "Using Neo4j 4.x password command format" >/dev/null 2>&1
$NEO4J_ADMIN_CMD set-initial-password "$NEO4J_PASSWORD" >/dev/null 2>&1 || { $NEO4J_ADMIN_CMD set-initial-password "$NEO4J_PASSWORD" >/dev/null 2>&1 || {
echo "Warning: Could not set Neo4j password, it may already be set" echo "Warning: Could not set Neo4j password, it may already be set" >/dev/null 2>&1
} }
fi fi
fi fi
# Update Neo4j configuration # Update Neo4j configuration
echo "Configuring Neo4j..." echo "Configuring Neo4j..." >/dev/null 2>&1
cat > /etc/neo4j/neo4j.conf << EOF cat > /etc/neo4j/neo4j.conf << EOF
# Neo4j configuration # Neo4j configuration
dbms.security.auth_enabled=true dbms.security.auth_enabled=true
@ -124,12 +138,12 @@ echo "Starting Neo4j service..."
neo4j console >/dev/null 2>&1 & neo4j console >/dev/null 2>&1 &
# Add a wait for Neo4j to be ready with more robust checking # Add a wait for Neo4j to be ready with more robust checking
echo "Waiting for Neo4j to be ready..." echo "Waiting for Neo4j to be ready..." >/dev/null 2>&1
NEO4J_READY=false NEO4J_READY=false
for i in {1..60}; do for i in {1..60}; do
# First try standard status check # First try standard status check
if neo4j status >/dev/null 2>&1; then if neo4j status >/dev/null 2>&1; then
echo "Neo4j is ready (via status check)" echo "Neo4j is ready 🚀"
NEO4J_READY=true NEO4J_READY=true
break break
fi fi
@ -143,7 +157,7 @@ for i in {1..60}; do
fi fi
fi fi
echo "Waiting for Neo4j to start... ($i/60)" echo "Waiting for Neo4j to start... ($i/60)" >/dev/null 2>&1
sleep 2 sleep 2
done done
@ -151,19 +165,6 @@ if [ "$NEO4J_READY" = false ]; then
echo "WARNING: Neo4j may not be fully started yet, but continuing..." echo "WARNING: Neo4j may not be fully started yet, but continuing..."
fi fi
# Check WORKLOW_WORKER and skip SETUP_CMD if true
if [ "${WORKFLOW_WORKER}" == "true" ]; then
echo "WORKFLOW_WORKER is set to true. Running worker process."
npm run worker:prod
else
# Determine setup command based on the presence of ./server/dist
if [ -d "./server/dist" ]; then
SETUP_CMD='npm run db:setup:prod'
else
SETUP_CMD='npm run db:setup'
fi
fi
# Wait for PostgreSQL connection # Wait for PostgreSQL connection
if [ -z "$DATABASE_URL" ]; then if [ -z "$DATABASE_URL" ]; then
./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- echo "PostgreSQL is up" ./server/scripts/wait-for-it.sh $PG_HOST:${PG_PORT:-5432} --strict --timeout=300 -- echo "PostgreSQL is up"

View file

@ -34,8 +34,7 @@ COPY ./package.json ./package.json
COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/ COPY ./plugins/package.json ./plugins/package-lock.json ./plugins/
RUN npm --prefix plugins ci --omit=dev RUN npm --prefix plugins ci --omit=dev
COPY ./plugins/ ./plugins/ COPY ./plugins/ ./plugins/
RUN NODE_ENV=production npm --prefix plugins run build RUN NODE_ENV=production npm --prefix plugins run build && npm --prefix plugins prune --omit=dev
RUN npm --prefix plugins prune --omit=dev
ENV TOOLJET_EDITION=ee ENV TOOLJET_EDITION=ee
@ -52,14 +51,12 @@ ENV TOOLJET_EDITION=ee
COPY ./server/package.json ./server/package-lock.json ./server/ COPY ./server/package.json ./server/package-lock.json ./server/
RUN npm --prefix server ci --omit=dev RUN npm --prefix server ci --omit=dev
COPY ./server/ ./server/ COPY ./server/ ./server/
RUN npm install -g @nestjs/cli RUN npm install -g @nestjs/cli && npm install -g copyfiles
RUN npm install -g copyfiles RUN npm --prefix server run build && npm prune --production --prefix server
RUN npm --prefix server run build
RUN npm prune --production --prefix server
# Install dependencies for PostgREST, curl, unzip, etc. # Install dependencies for PostgREST, curl, tar, etc.
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
curl ca-certificates unzip tar \ curl ca-certificates tar \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
ENV POSTGREST_VERSION=v12.2.0 ENV POSTGREST_VERSION=v12.2.0
@ -81,7 +78,6 @@ RUN apt-get update && \
ca-certificates \ ca-certificates \
xz-utils \ xz-utils \
tar \ tar \
zip \
postgresql-client \ postgresql-client \
redis \ redis \
libaio1 \ libaio1 \

View file

@ -138,6 +138,15 @@ export const chartConfig = {
displayName: 'Background color', displayName: 'Background color',
validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' }, validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' },
}, },
borderColor: {
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
defaultValue: 'var(--cc-default-border)',
},
accordian: 'container',
},
padding: { padding: {
type: 'code', type: 'code',
displayName: 'Padding', displayName: 'Padding',
@ -233,6 +242,7 @@ export const chartConfig = {
borderRadius: { value: '{{4}}' }, borderRadius: { value: '{{4}}' },
visibility: { value: '{{true}}' }, visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' }, disabledState: { value: '{{false}}' },
borderColor: { value: 'var(--cc-default-border)' },
}, },
}, },
}; };

View file

@ -8,7 +8,7 @@ import { isEqual } from 'lodash';
import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store'; import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { getCssVarValue } from './utils'; import { getCssVarValue, getModifiedColor } from './utils';
var tinycolor = require('tinycolor2'); var tinycolor = require('tinycolor2');
@ -33,11 +33,11 @@ export const Chart = function Chart({
return '#fff'; return '#fff';
}; };
const { padding, visibility, disabledState, boxShadow, backgroundColor, borderRadius } = styles; const { padding, visibility, disabledState, boxShadow, backgroundColor, borderRadius, borderColor } = styles;
const { title, markerColor, showGridLines, type, data, jsonDescription, plotFromJson, showAxes, barmode } = const { title, markerColor, showGridLines, type, data, jsonDescription, plotFromJson, showAxes, barmode } =
properties; properties;
const modifiedBackgroundColor = getCssVarValue(document.documentElement, backgroundColor); const modifiedBackgroundColor = getModifiedColor(backgroundColor, 0);
const modifiedMarkerColor = getCssVarValue(document.documentElement, markerColor); const modifiedMarkerColor = getCssVarValue(document.documentElement, markerColor);
const modifiedGridLines = getCssVarValue(document.documentElement, 'var(--cc-weak-border)'); const modifiedGridLines = getCssVarValue(document.documentElement, 'var(--cc-weak-border)');
const modifiedTextColor = getCssVarValue(document.documentElement, 'var(--cc-primary-text)'); const modifiedTextColor = getCssVarValue(document.documentElement, 'var(--cc-primary-text)');
@ -55,7 +55,8 @@ export const Chart = function Chart({
width: width - 4, width: width - 4,
height, height,
display: visibility ? '' : 'none', display: visibility ? '' : 'none',
background: darkMode ? '#1f2936' : 'white', // background: darkMode ? '#1f2936' : 'white',
border: `1px solid ${borderColor}`,
boxShadow, boxShadow,
borderRadius, borderRadius,
}; };
@ -82,6 +83,7 @@ export const Chart = function Chart({
? '#1f2936' ? '#1f2936'
: '#fff' : '#fff'
: modifiedBackgroundColor; : modifiedBackgroundColor;
const fontColor = getColor(updatedBgColor); const fontColor = getColor(updatedBgColor);
const chartTitle = plotFromJson ? chartLayout?.title ?? title : title; const chartTitle = plotFromJson ? chartLayout?.title ?? title : title;
@ -105,8 +107,8 @@ export const Chart = function Chart({
}, [JSON.stringify(chartLayout, chartTitle)]); }, [JSON.stringify(chartLayout, chartTitle)]);
const layout = { const layout = {
width: width - 4, width: width - 6,
height, height: height - 4,
plot_bgcolor: updatedBgColor, plot_bgcolor: updatedBgColor,
paper_bgcolor: updatedBgColor, paper_bgcolor: updatedBgColor,
title: { title: {

@ -1 +1 @@
Subproject commit 80f2e4cab88aa586e9b8a731f8643bcc0cef52e7 Subproject commit cc864000dd03cc345e53ae9fc43821d3174f4c64

View file

@ -53,6 +53,7 @@ import { SampleDBScheduler } from '@modules/data-sources/schedulers/sample-db.sc
import { SessionScheduler } from '@modules/session/scheduler'; import { SessionScheduler } from '@modules/session/scheduler';
import { AuditLogsClearScheduler } from '@modules/audit-logs/scheduler'; import { AuditLogsClearScheduler } from '@modules/audit-logs/scheduler';
import { ModulesModule } from '@modules/modules/module'; import { ModulesModule } from '@modules/modules/module';
import { EmailListenerModule } from '@modules/email-listener/module';
export class AppModule implements OnModuleInit { export class AppModule implements OnModuleInit {
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
// Load static and dynamic modules // Load static and dynamic modules
@ -113,6 +114,7 @@ export class AppModule implements OnModuleInit {
await AppGitModule.register(configs), await AppGitModule.register(configs),
await CrmModule.register(configs), await CrmModule.register(configs),
await OrganizationPaymentModule.register(configs), await OrganizationPaymentModule.register(configs),
await EmailListenerModule.register(configs),
]; ];
return { return {

View file

@ -138,6 +138,15 @@ export const chartConfig = {
displayName: 'Background color', displayName: 'Background color',
validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' }, validation: { schema: { type: 'string' }, defaultValue: 'var(--cc-surface1-surface)' },
}, },
borderColor: {
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
defaultValue: 'var(--cc-default-border)',
},
accordian: 'container',
},
padding: { padding: {
type: 'code', type: 'code',
displayName: 'Padding', displayName: 'Padding',
@ -233,6 +242,7 @@ export const chartConfig = {
borderRadius: { value: '{{4}}' }, borderRadius: { value: '{{4}}' },
visibility: { value: '{{true}}' }, visibility: { value: '{{true}}' },
disabledState: { value: '{{false}}' }, disabledState: { value: '{{false}}' },
borderColor: { value: 'var(--cc-default-border)' },
}, },
}, },
}; };

View file

@ -1,9 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { Logger } from 'nestjs-pino'; import { Logger } from 'nestjs-pino';
import { EmailEventPayload } from './constants'; import { EmailEventPayload, EMAIL_EVENTS } from '@modules/email/constants';
import { EmailService } from '@modules/email/service'; import { EmailService } from '@modules/email/service';
import { EMAIL_EVENTS } from './constants';
@Injectable() @Injectable()
export class EmailListener { export class EmailListener {
@ -15,7 +14,6 @@ export class EmailListener {
@OnEvent('emailEvent') @OnEvent('emailEvent')
async handleEmailEvent(eventData: EmailEventPayload) { async handleEmailEvent(eventData: EmailEventPayload) {
const { type, payload } = eventData; const { type, payload } = eventData;
try { try {
switch (type) { switch (type) {
case EMAIL_EVENTS.SEND_WELCOME_EMAIL: case EMAIL_EVENTS.SEND_WELCOME_EMAIL:

View file

@ -0,0 +1,17 @@
import { DynamicModule } from '@nestjs/common';
import { SubModule } from '@modules/app/sub-module';
import { EmailModule } from '@modules/email/module';
import { getImportPath } from '@modules/app/constants';
export class EmailListenerModule extends SubModule {
static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise<DynamicModule> {
const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
const { EmailListener } = await import(`${importPath}/email-listener/listener`);
return {
module: EmailListenerModule,
imports: [await EmailModule.register(configs)],
providers: [EmailListener],
exports: [],
};
}
}

View file

@ -10,7 +10,6 @@ export class EmailModule extends SubModule {
const importPath = await getImportPath(configs?.IS_GET_CONTEXT); const importPath = await getImportPath(configs?.IS_GET_CONTEXT);
const { EmailService } = await import(`${importPath}/email/service`); const { EmailService } = await import(`${importPath}/email/service`);
const { EmailUtilService } = await import(`${importPath}/email/util.service`); const { EmailUtilService } = await import(`${importPath}/email/util.service`);
const { EmailListener } = await import(`${importPath}/email/listener`);
return { return {
module: EmailModule, module: EmailModule,
imports: [ imports: [
@ -18,8 +17,8 @@ export class EmailModule extends SubModule {
await DataSourcesModule.register(configs), await DataSourcesModule.register(configs),
await SMTPModule.register(configs), await SMTPModule.register(configs),
], ],
providers: [EmailService, EmailListener, EmailUtilService], providers: [EmailService, EmailUtilService],
exports: [EmailListener, EmailUtilService], exports: [EmailUtilService, EmailService],
}; };
} }
} }

View file

@ -9,7 +9,6 @@ import {
} from '@modules/email/dto'; } from '@modules/email/dto';
import { EmailUtilService } from './util.service'; import { EmailUtilService } from './util.service';
import { IEmailService } from './interfaces/IService'; import { IEmailService } from './interfaces/IService';
import { INSTANCE_SYSTEM_SETTINGS } from '@modules/instance-settings/constants';
import { WhiteLabellingUtilService } from '@modules/white-labelling/util.service'; import { WhiteLabellingUtilService } from '@modules/white-labelling/util.service';
handlebars.registerHelper('capitalize', function (value) { handlebars.registerHelper('capitalize', function (value) {
@ -29,15 +28,6 @@ export class EmailService implements IEmailService {
protected WHITE_LABEL_TEXT; protected WHITE_LABEL_TEXT;
protected WHITE_LABEL_LOGO; protected WHITE_LABEL_LOGO;
protected SUB_PATH; protected SUB_PATH;
protected SMTP: {
[INSTANCE_SYSTEM_SETTINGS.SMTP_ENABLED]: boolean;
[INSTANCE_SYSTEM_SETTINGS.SMTP_DOMAIN]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_PORT]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_USERNAME]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_PASSWORD]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_FROM_EMAIL]: string;
[INSTANCE_SYSTEM_SETTINGS.SMTP_ENV_CONFIGURED]: boolean;
};
protected defaultWhiteLabelState: boolean; protected defaultWhiteLabelState: boolean;
constructor( constructor(
@ -63,14 +53,13 @@ export class EmailService implements IEmailService {
async init(organizationId?: string | null) { async init(organizationId?: string | null) {
const whiteLabelSettings = await this.emailUtilService.retrieveWhiteLabelSettings(null); const whiteLabelSettings = await this.emailUtilService.retrieveWhiteLabelSettings(null);
this.SMTP = await this.emailUtilService.retrieveSmtpSettings();
this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text; this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text;
this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo; this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo;
this.defaultWhiteLabelState = whiteLabelSettings?.default; this.defaultWhiteLabelState = whiteLabelSettings?.default;
} }
protected compileTemplate(templatePath: string, templateData: object) { protected compileTemplate(templatePath: string, templateData: object) {
this.emailUtilService.compileTemplate(templatePath, templateData); return this.emailUtilService.compileTemplate(templatePath, templateData);
} }
protected stripTrailingSlash(hostname: string) { protected stripTrailingSlash(hostname: string) {
@ -88,6 +77,7 @@ export class EmailService implements IEmailService {
redirectTo, redirectTo,
} = payload; } = payload;
await this.init(organizationId); await this.init(organizationId);
await this.emailUtilService.init(organizationId);
const isOrgInvite = organizationInvitationToken && sender && organizationName; const isOrgInvite = organizationInvitationToken && sender && organizationName;
const inviteUrl = generateInviteURL(invitationtoken, organizationInvitationToken, organizationId, null, redirectTo); const inviteUrl = generateInviteURL(invitationtoken, organizationInvitationToken, organizationId, null, redirectTo);
const subject = isOrgInvite ? `Welcome to ${organizationName || 'ToolJet'}` : 'Set up your account!'; const subject = isOrgInvite ? `Welcome to ${organizationName || 'ToolJet'}` : 'Set up your account!';

View file

@ -36,7 +36,11 @@ export class EmailUtilService implements IEmailUtilService {
constructor( constructor(
protected readonly whiteLabellingUtilService: WhiteLabellingUtilService, protected readonly whiteLabellingUtilService: WhiteLabellingUtilService,
protected readonly smtpUtilService: SMTPUtilService protected readonly smtpUtilService: SMTPUtilService
) {} ) {
this.TOOLJET_HOST = this.stripTrailingSlash(process.env.TOOLJET_HOST);
this.SUB_PATH = process.env.SUB_PATH;
this.NODE_ENV = process.env.NODE_ENV || 'development';
}
async retrieveWhiteLabelSettings(organizationId?: string | null): Promise<any> { async retrieveWhiteLabelSettings(organizationId?: string | null): Promise<any> {
const whiteLabelSetting = await this.whiteLabellingUtilService.getProcessedSettings(organizationId); const whiteLabelSetting = await this.whiteLabellingUtilService.getProcessedSettings(organizationId);
@ -253,4 +257,14 @@ export class EmailUtilService implements IEmailUtilService {
whiteLabelLogo: DEFAULT_WHITE_LABELLING_SETTINGS.white_label_logo, whiteLabelLogo: DEFAULT_WHITE_LABELLING_SETTINGS.white_label_logo,
}); });
} }
protected stripTrailingSlash(hostname: string) {
return hostname?.endsWith('/') ? hostname.slice(0, -1) : hostname;
}
async init(organizationId?: string | null) {
const whiteLabelSettings = await this.retrieveWhiteLabelSettings(null);
this.SMTP = await this.retrieveSmtpSettings();
this.WHITE_LABEL_TEXT = whiteLabelSettings?.white_label_text;
this.WHITE_LABEL_LOGO = whiteLabelSettings?.white_label_logo;
this.defaultWhiteLabelState = whiteLabelSettings?.default;
}
} }

View file

@ -274,6 +274,29 @@ export class LicenseCountsService implements ILicenseCountsService {
manager manager
); );
} }
async getUserIdWithEndUserRole(manager: EntityManager): Promise<string[]> {
const statusList = [WORKSPACE_USER_STATUS.INVITED, WORKSPACE_USER_STATUS.ACTIVE];
const users = await manager.find(User, {
select: ['id'],
where: {
status: Not(USER_STATUS.ARCHIVED),
organizationUsers: {
status: In(statusList),
},
userPermissions: {
name: USER_ROLE.END_USER,
organization: {
status: WORKSPACE_STATUS.ACTIVE,
},
},
},
relations: ['organizationUsers', 'userPermissions', 'userPermissions.organization'],
});
// Extract unique user IDs
return [...new Set(users.map((user) => user.id))];
}
async fetchTotalAppCount(organizationId: string, manager: EntityManager): Promise<number> { async fetchTotalAppCount(organizationId: string, manager: EntityManager): Promise<number> {
if (getTooljetEdition() !== TOOLJET_EDITIONS.Cloud) { if (getTooljetEdition() !== TOOLJET_EDITIONS.Cloud) {