diff --git a/frontend/ee b/frontend/ee
index 9458c8d66f..51d0a7fbe9 160000
--- a/frontend/ee
+++ b/frontend/ee
@@ -1 +1 @@
-Subproject commit 9458c8d66f29f8334765b5757dd096139a8d53d2
+Subproject commit 51d0a7fbe974919786c938304e2214d46396c033
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
index 22a410c674..f25d815159 100644
--- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
@@ -4,6 +4,7 @@ import './configHandle.scss';
import useStore from '@/AppBuilder/_stores/store';
import { findHighestLevelofSelection } from '../Grid/gridUtils';
import SolidIcon from '@/_ui/Icon/solidIcons/index';
+import { ToolTip } from '@/_components/ToolTip';
import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext';
import { DROPPABLE_PARENTS } from '../appCanvasConstants';
@@ -52,7 +53,40 @@ export const ConfigHandle = ({
);
}, shallow);
+ const currentPageIndex = useStore((state) => state.modules.canvas.currentPageIndex);
+ const component = useStore((state) => state.modules.canvas.pages[currentPageIndex].components[id]);
+ const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
+ const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
+ const isRestricted = component.permissions && component.permissions.length !== 0;
+ const draggingComponentId = useStore((state) => state.draggingComponentId);
+
let height = visibility === false ? 10 : widgetHeight;
+
+ const getTooltip = () => {
+ const permission = component.permissions?.[0];
+ if (!permission) return null;
+
+ const users = permission.groups || permission.users || [];
+ if (users.length === 0) return null;
+
+ const isSingle = permission.type === 'SINGLE';
+ const isGroup = permission.type === 'GROUP';
+
+ if (isSingle) {
+ return users.length === 1
+ ? `Access restricted to ${users[0].user.email}`
+ : `Access restricted to ${users.length} users`;
+ }
+
+ if (isGroup) {
+ return users.length === 1
+ ? `Access restricted to ${users[0].permission_group?.name || users[0].permissionGroup?.name} group`
+ : `Access restricted to ${users.length} user groups`;
+ }
+
+ return null;
+ };
+
return (
+ {licenseValid && isRestricted && (
+
+
+
+
+
+ )}
({
createNewVersionAction: state.createNewVersionAction,
@@ -45,6 +46,7 @@ const CreateVersionModal = ({
currentVersionId: state.currentVersionId,
setCurrentVersionId: state.setCurrentVersionId,
selectedVersion: state.selectedVersion,
+ currentMode: state.currentMode,
}),
shallow
);
@@ -94,7 +96,7 @@ const CreateVersionModal = ({
setIsCreatingVersion(false);
setShowCreateAppVersion(false);
appVersionService
- .getAppVersionData(appId, newVersion.id)
+ .getAppVersionData(appId, newVersion.id, currentMode)
.then((data) => {
setCurrentVersionId(newVersion.id);
handleCommitOnVersionCreation(data);
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
index 40fd6c3f2f..189b06a040 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Inspector.jsx
@@ -43,6 +43,9 @@ import useStore from '@/AppBuilder/_stores/store';
import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes';
import { copyComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils.js';
import DatetimePickerV2 from './Components/DatetimePickerV2.jsx';
+import { ToolTip } from '@/_components/ToolTip';
+import AppPermissionsModal from '@/modules/Appbuilder/components/AppPermissionsModal';
+import { appPermissionService } from '@/_services';
import { ModuleContainerInspector, ModuleViewerInspector, ModuleEditorBanner } from '@/modules/Modules/components';
const INSPECTOR_HEADER_OPTIONS = [
@@ -61,6 +64,19 @@ const INSPECTOR_HEADER_OPTIONS = [
value: 'duplicate',
icon: ,
},
+ {
+ label: 'Component permission',
+ value: 'permission',
+ icon: (
+
+ ),
+ trailingIcon: ,
+ },
{
label: 'Delete',
value: 'delete',
@@ -104,6 +120,11 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
const isVersionReleased = useStore((state) => state.isVersionReleased);
const setWidgetDeleteConfirmation = useStore((state) => state.setWidgetDeleteConfirmation);
const setComponentToInspect = useStore((state) => state.setComponentToInspect);
+ const featureAccess = useStore((state) => state?.license?.featureAccess, shallow);
+ const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid;
+ const showComponentPermissionModal = useStore((state) => state.showComponentPermissionModal);
+ const toggleComponentPermissionModal = useStore((state) => state.toggleComponentPermissionModal);
+ const setComponentPermission = useStore((state) => state.setComponentPermission);
const dataQueries = useDataQueries();
const currentState = useCurrentState();
@@ -378,9 +399,14 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
if (value === 'delete') {
setWidgetDeleteConfirmation(true);
}
+ if (value === 'permission') {
+ if (!licenseValid) return;
+ toggleComponentPermissionModal(true);
+ }
if (value === 'duplicate') {
copyComponents({ isCloning: true });
}
+ setShowHeaderActionsMenu(false);
};
const buildGeneralStyle = () => {
if (!componentMeta?.definition?.generalStyles) {
@@ -446,7 +472,7 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
React.useEffect(() => {
const handleClickOutside = (event) => {
- if (showHeaderActionsMenu && event.target.closest('.list-menu') === null) {
+ if (showHeaderActionsMenu && event.target.closest('#list-menu') === null) {
setShowHeaderActionsMenu(false);
}
};
@@ -504,44 +530,79 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
{renderAppNameInput()}
{!isModuleContainer && (
-
-
-
+ <>
+
+
+
+ appPermissionService.getComponentPermission(appId, id)}
+ createPermission={(id, appId, body) => appPermissionService.createComponentPermission(appId, id, body)}
+ updatePermission={(id, appId, body) => appPermissionService.updateComponentPermission(appId, id, body)}
+ deletePermission={(id, appId) => appPermissionService.deleteComponentPermission(appId, id)}
+ onSuccess={(data) => setComponentPermission(selectedComponentId, data)}
+ />
+ >
)}
@@ -557,8 +618,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
componentMeta.displayName === 'Toggle Switch (Legacy)'
? 'Toggle (Legacy)'
: componentMeta.displayName === 'Toggle Switch'
- ? 'Toggle Switch'
- : componentMeta.component,
+ ? 'Toggle Switch'
+ : componentMeta.component,
})}
diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js
index cd7d18449c..a6aa248681 100644
--- a/frontend/src/AppBuilder/_hooks/useAppData.js
+++ b/frontend/src/AppBuilder/_hooks/useAppData.js
@@ -252,7 +252,7 @@ const useAppData = (
appDataPromise = appService.fetchAppBySlug(slug);
} else {
appDataPromise = isPreviewForVersion
- ? appVersionService.getAppVersionData(appId, versionId)
+ ? appVersionService.getAppVersionData(appId, versionId, mode)
: appService.fetchApp(appId);
}
}
@@ -573,7 +573,7 @@ const useAppData = (
if (isEnvChanged) {
setEnvironmentLoadingState('loading');
}
- appVersionService.getAppVersionData(appId, selectedVersion?.id).then(async (appData) => {
+ appVersionService.getAppVersionData(appId, selectedVersion?.id, mode).then(async (appData) => {
cleanUpStore(false);
const { should_freeze_editor } = appData;
setIsEditorFreezed(should_freeze_editor);
diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
index 9075dbede2..9d7c1b77f5 100644
--- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js
@@ -46,6 +46,7 @@ const initialState = {
showWidgetDeleteConfirmation: false,
focusedParentId: null,
modalsOpenOnCanvas: [],
+ showComponentPermissionModal: false,
};
export const createComponentsSlice = (set, get) => ({
@@ -1989,4 +1990,25 @@ export const createComponentsSlice = (set, get) => ({
setComponentProperty(componentId, `canvasHeight`, maxHeight, 'properties', 'value', false);
},
+ toggleComponentPermissionModal: (show) => {
+ set((state) => {
+ state.showComponentPermissionModal = show;
+ });
+ },
+ setComponentPermission: (componentId, data) => {
+ const { modules } = get();
+ const currentPageIndex = modules.canvas.currentPageIndex;
+ const component = modules.canvas.pages[currentPageIndex]?.components?.[componentId];
+
+ if (component) {
+ const updatedComponent = {
+ ...component,
+ permissions: data.length === 0 || data.length === undefined ? [] : [data[0]],
+ };
+
+ set((state) => {
+ state.modules.canvas.pages[currentPageIndex].components[componentId] = updatedComponent;
+ });
+ }
+ },
});
diff --git a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
index 1077a22608..457ca56370 100644
--- a/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
+++ b/frontend/src/AppBuilder/_stores/slices/environmentsAndVersionsSlice.js
@@ -196,7 +196,7 @@ export const createEnvironmentsAndVersionsSlice = (set, get) => ({
},
changeEditorVersionAction: async (appId, versionId, onSuccess, onFailure) => {
try {
- const data = await appVersionService.getAppVersionData(appId, versionId);
+ const data = await appVersionService.getAppVersionData(appId, versionId, get().currentMode);
const selectedVersion = {
id: data.editing_version.id,
name: data.editing_version.name,
diff --git a/frontend/src/_services/appPermission.service.js b/frontend/src/_services/appPermission.service.js
index fd2438f058..1aab6e97e1 100644
--- a/frontend/src/_services/appPermission.service.js
+++ b/frontend/src/_services/appPermission.service.js
@@ -11,6 +11,10 @@ export const appPermissionService = {
createQueryPermission,
updateQueryPermission,
deleteQueryPermission,
+ getComponentPermission,
+ createComponentPermission,
+ updateComponentPermission,
+ deleteComponentPermission,
};
function getPagePermission(appId, pageId) {
@@ -89,3 +93,49 @@ function deleteQueryPermission(appId, queryId) {
};
return fetch(`${config.apiUrl}/app-permissions/${appId}/queries/${queryId}`, requestOptions).then(handleResponse);
}
+
+function getComponentPermission(appId, componentId) {
+ const requestOptions = {
+ method: 'GET',
+ headers: authHeader(),
+ credentials: 'include',
+ };
+ return fetch(`${config.apiUrl}/app-permissions/${appId}/components/${componentId}`, requestOptions).then(
+ handleResponse
+ );
+}
+
+function createComponentPermission(appId, componentId, body) {
+ const requestOptions = {
+ method: 'POST',
+ headers: authHeader(),
+ credentials: 'include',
+ body: JSON.stringify(body),
+ };
+ return fetch(`${config.apiUrl}/app-permissions/${appId}/components/${componentId}`, requestOptions).then(
+ handleResponse
+ );
+}
+
+function updateComponentPermission(appId, componentId, body) {
+ const requestOptions = {
+ method: 'PUT',
+ headers: authHeader(),
+ credentials: 'include',
+ body: JSON.stringify(body),
+ };
+ return fetch(`${config.apiUrl}/app-permissions/${appId}/components/${componentId}`, requestOptions).then(
+ handleResponse
+ );
+}
+
+function deleteComponentPermission(appId, componentId) {
+ const requestOptions = {
+ method: 'DELETE',
+ headers: authHeader(),
+ credentials: 'include',
+ };
+ return fetch(`${config.apiUrl}/app-permissions/${appId}/components/${componentId}`, requestOptions).then(
+ handleResponse
+ );
+}
diff --git a/frontend/src/_services/appVersion.service.js b/frontend/src/_services/appVersion.service.js
index bda0b7a192..e510fb67d0 100644
--- a/frontend/src/_services/appVersion.service.js
+++ b/frontend/src/_services/appVersion.service.js
@@ -36,9 +36,9 @@ function promoteEnvironment(appId, versionId, currentEnvironmentId) {
};
return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}/promote`, requestOptions).then(handleResponse);
}
-function getAppVersionData(appId, versionId) {
+function getAppVersionData(appId, versionId, mode) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
- return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}`, requestOptions).then(handleResponse);
+ return fetch(`${config.apiUrl}/v2/apps/${appId}/versions/${versionId}?mode=${mode}`, requestOptions).then(handleResponse);
}
function create(appId, versionName, versionFromId, currentEnvironmentId) {
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index 2d3271898f..9f75bdc405 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -2800,7 +2800,7 @@ hr {
}
.config-handle {
- display: block;
+ display: flex;
}
.apps-table {
diff --git a/server/ee b/server/ee
index e76477d30e..213bba9801 160000
--- a/server/ee
+++ b/server/ee
@@ -1 +1 @@
-Subproject commit e76477d30eb21df5d188ca204c520df75fddd529
+Subproject commit 213bba98018d82fe2fee0689e5b7bf1a19a85ade
diff --git a/server/migrations/1748509644056-CreateComponentPermissions.ts b/server/migrations/1748509644056-CreateComponentPermissions.ts
new file mode 100644
index 0000000000..baa66f01de
--- /dev/null
+++ b/server/migrations/1748509644056-CreateComponentPermissions.ts
@@ -0,0 +1,51 @@
+import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
+
+export class CreateComponentPermissions1748509644056 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.createTable(
+ new Table({
+ name: 'component_permissions',
+ columns: [
+ {
+ name: 'id',
+ type: 'uuid',
+ isGenerated: true,
+ default: 'gen_random_uuid()',
+ isPrimary: true,
+ },
+ {
+ name: 'component_id',
+ type: 'uuid',
+ isNullable: false,
+ },
+ {
+ name: 'type',
+ type: 'enum',
+ enum: ['SINGLE', 'GROUP'],
+ },
+ {
+ name: 'created_at',
+ type: 'timestamp',
+ isNullable: false,
+ default: 'now()',
+ },
+ ],
+ }),
+ true
+ );
+
+ await queryRunner.createForeignKey(
+ 'component_permissions',
+ new TableForeignKey({
+ columnNames: ['component_id'],
+ referencedColumnNames: ['id'],
+ referencedTableName: 'components',
+ onDelete: 'CASCADE',
+ })
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.dropTable('component_permissions');
+ }
+}
diff --git a/server/migrations/1748509665915-CreateComponentUsers.ts b/server/migrations/1748509665915-CreateComponentUsers.ts
new file mode 100644
index 0000000000..dbc4e3ef45
--- /dev/null
+++ b/server/migrations/1748509665915-CreateComponentUsers.ts
@@ -0,0 +1,76 @@
+import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
+
+export class CreateComponentUsers1748509665915 implements MigrationInterface {
+ public async up(queryRunner: QueryRunner): Promise {
+ await queryRunner.createTable(
+ new Table({
+ name: 'component_users',
+ columns: [
+ {
+ name: 'id',
+ type: 'uuid',
+ isGenerated: true,
+ default: 'gen_random_uuid()',
+ isPrimary: true,
+ },
+ {
+ name: 'component_permissions_id',
+ type: 'uuid',
+ isNullable: false,
+ },
+ {
+ name: 'user_id',
+ type: 'uuid',
+ isNullable: true,
+ },
+ {
+ name: 'permission_groups_id',
+ type: 'uuid',
+ isNullable: true,
+ },
+ {
+ name: 'created_at',
+ type: 'timestamp',
+ isNullable: false,
+ default: 'now()',
+ },
+ ],
+ }),
+ true
+ );
+
+ await queryRunner.createForeignKey(
+ 'component_users',
+ new TableForeignKey({
+ columnNames: ['component_permissions_id'],
+ referencedColumnNames: ['id'],
+ referencedTableName: 'component_permissions',
+ onDelete: 'CASCADE',
+ })
+ );
+
+ await queryRunner.createForeignKey(
+ 'component_users',
+ new TableForeignKey({
+ columnNames: ['user_id'],
+ referencedColumnNames: ['id'],
+ referencedTableName: 'users',
+ onDelete: 'CASCADE',
+ })
+ );
+
+ await queryRunner.createForeignKey(
+ 'component_users',
+ new TableForeignKey({
+ columnNames: ['permission_groups_id'],
+ referencedColumnNames: ['id'],
+ referencedTableName: 'permission_groups',
+ onDelete: 'CASCADE',
+ })
+ );
+ }
+
+ public async down(queryRunner: QueryRunner): Promise {
+ await queryRunner.dropTable('component_users');
+ }
+}
diff --git a/server/src/entities/component.entity.ts b/server/src/entities/component.entity.ts
index bb22c7dcd4..6ef8d20b0c 100644
--- a/server/src/entities/component.entity.ts
+++ b/server/src/entities/component.entity.ts
@@ -11,6 +11,7 @@ import {
} from 'typeorm';
import { Page } from './page.entity';
import { Layout } from './layout.entity';
+import { ComponentPermission } from './component_permissions.entity';
@Entity({ name: 'components' })
@Index('idx_component_page_id', ['pageId'])
@@ -60,4 +61,7 @@ export class Component {
@OneToMany(() => Layout, (layout) => layout.component)
layouts: Layout[];
+
+ @OneToMany(() => ComponentPermission, (permission) => permission.component)
+ permissions: ComponentPermission[];
}
diff --git a/server/src/entities/component_permissions.entity.ts b/server/src/entities/component_permissions.entity.ts
new file mode 100644
index 0000000000..9ed804eb4e
--- /dev/null
+++ b/server/src/entities/component_permissions.entity.ts
@@ -0,0 +1,29 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, OneToMany } from 'typeorm';
+import { Component } from './component.entity';
+import { PAGE_PERMISSION_TYPE } from '@modules/app-permissions/constants';
+import { ComponentUser } from './component_users.entity';
+
+@Entity('component_permissions')
+export class ComponentPermission {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'component_id', type: 'uuid', nullable: false })
+ componentId: string;
+
+ @Column({
+ type: 'enum',
+ enum: PAGE_PERMISSION_TYPE,
+ })
+ type: PAGE_PERMISSION_TYPE;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @ManyToOne(() => Component, (component) => component.permissions, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'component_id' })
+ component: Component;
+
+ @OneToMany(() => ComponentUser, (componentUser) => componentUser.componentPermission)
+ users: ComponentUser[];
+}
diff --git a/server/src/entities/component_users.entity.ts b/server/src/entities/component_users.entity.ts
new file mode 100644
index 0000000000..2728297e3b
--- /dev/null
+++ b/server/src/entities/component_users.entity.ts
@@ -0,0 +1,34 @@
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm';
+import { User } from './user.entity';
+import { ComponentPermission } from './component_permissions.entity';
+import { GroupPermissions } from './group_permissions.entity';
+
+@Entity('component_users')
+export class ComponentUser {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({ name: 'component_permissions_id', type: 'uuid' })
+ componentPermissionsId: string;
+
+ @Column({ name: 'user_id', type: 'uuid', nullable: true })
+ userId: string | null;
+
+ @Column({ name: 'permission_groups_id', type: 'uuid', nullable: true })
+ permissionGroupsId: string | null;
+
+ @CreateDateColumn({ name: 'created_at' })
+ createdAt: Date;
+
+ @ManyToOne(() => ComponentPermission, { onDelete: 'CASCADE' })
+ @JoinColumn({ name: 'component_permissions_id' })
+ componentPermission: ComponentPermission;
+
+ @ManyToOne(() => User, { onDelete: 'CASCADE', nullable: true })
+ @JoinColumn({ name: 'user_id' })
+ user: User;
+
+ @ManyToOne(() => GroupPermissions, { onDelete: 'CASCADE', nullable: true })
+ @JoinColumn({ name: 'permission_groups_id' })
+ permissionGroup: GroupPermissions;
+}
diff --git a/server/src/entities/group_permissions.entity.ts b/server/src/entities/group_permissions.entity.ts
index 26667796ae..6fa49ddaa6 100644
--- a/server/src/entities/group_permissions.entity.ts
+++ b/server/src/entities/group_permissions.entity.ts
@@ -15,6 +15,7 @@ import { GranularPermissions } from './granular_permissions.entity';
import { GROUP_PERMISSIONS_TYPE } from '@modules/group-permissions/constants';
import { PageUser } from './page_users.entity';
import { QueryUser } from './query_users.entity';
+import { ComponentUser } from './component_users.entity';
@Entity({ name: 'permission_groups' })
export class GroupPermissions extends BaseEntity {
@@ -76,5 +77,8 @@ export class GroupPermissions extends BaseEntity {
@OneToMany(() => QueryUser, (queryUser) => queryUser.permissionGroup)
queryUsers: QueryUser[];
+ @OneToMany(() => ComponentUser, (componentUser) => componentUser.permissionGroup)
+ componentUsers: ComponentUser[];
+
disabled?: boolean;
}
diff --git a/server/src/entities/user.entity.ts b/server/src/entities/user.entity.ts
index 515b1f469e..f43396f0ed 100644
--- a/server/src/entities/user.entity.ts
+++ b/server/src/entities/user.entity.ts
@@ -31,6 +31,7 @@ import { AiResponseVote } from './ai_response_vote.entity';
import { USER_ROLE } from '@modules/group-permissions/constants';
import { PageUser } from './page_users.entity';
import { QueryUser } from './query_users.entity';
+import { ComponentUser } from './component_users.entity';
@Entity({ name: 'users' })
export class User extends BaseEntity {
@@ -192,6 +193,9 @@ export class User extends BaseEntity {
@OneToMany(() => QueryUser, (queryUser) => queryUser.user)
queryUsers: QueryUser[];
+ @OneToMany(() => ComponentUser, (componentUser) => componentUser.user)
+ componentUsers: ComponentUser[];
+
organizationId: string;
invitedOrganizationId: string;
organizationIds?: Array;
diff --git a/server/src/modules/app-permissions/ability/index.ts b/server/src/modules/app-permissions/ability/index.ts
index 5a03f417d5..81b25601b1 100644
--- a/server/src/modules/app-permissions/ability/index.ts
+++ b/server/src/modules/app-permissions/ability/index.ts
@@ -42,6 +42,10 @@ export class FeatureAbilityFactory extends AbilityFactory
FEATURE_KEY.CREATE_QUERY_PERMISSIONS,
FEATURE_KEY.UPDATE_QUERY_PERMISSIONS,
FEATURE_KEY.DELETE_QUERY_PERMISSIONS,
+ FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.CREATE_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.UPDATE_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.DELETE_COMPONENT_PERMISSIONS,
],
App
);
@@ -64,6 +68,10 @@ export class FeatureAbilityFactory extends AbilityFactory
FEATURE_KEY.CREATE_QUERY_PERMISSIONS,
FEATURE_KEY.UPDATE_QUERY_PERMISSIONS,
FEATURE_KEY.DELETE_QUERY_PERMISSIONS,
+ FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.CREATE_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.UPDATE_COMPONENT_PERMISSIONS,
+ FEATURE_KEY.DELETE_COMPONENT_PERMISSIONS,
],
App
);
@@ -80,6 +88,7 @@ export class FeatureAbilityFactory extends AbilityFactory
FEATURE_KEY.FETCH_USER_GROUPS,
FEATURE_KEY.FETCH_PAGE_PERMISSIONS,
FEATURE_KEY.FETCH_QUERY_PERMISSIONS,
+ FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS,
],
App
);
diff --git a/server/src/modules/app-permissions/constants/features.ts b/server/src/modules/app-permissions/constants/features.ts
index 360b1cf4c9..cd992b88aa 100644
--- a/server/src/modules/app-permissions/constants/features.ts
+++ b/server/src/modules/app-permissions/constants/features.ts
@@ -14,5 +14,9 @@ export const FEATURES: FeaturesConfig = {
[FEATURE_KEY.CREATE_QUERY_PERMISSIONS]: {},
[FEATURE_KEY.UPDATE_QUERY_PERMISSIONS]: {},
[FEATURE_KEY.DELETE_QUERY_PERMISSIONS]: {},
+ [FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS]: {},
+ [FEATURE_KEY.CREATE_COMPONENT_PERMISSIONS]: {},
+ [FEATURE_KEY.UPDATE_COMPONENT_PERMISSIONS]: {},
+ [FEATURE_KEY.DELETE_COMPONENT_PERMISSIONS]: {},
},
};
diff --git a/server/src/modules/app-permissions/constants/index.ts b/server/src/modules/app-permissions/constants/index.ts
index ff5063e948..14e7122e2f 100644
--- a/server/src/modules/app-permissions/constants/index.ts
+++ b/server/src/modules/app-permissions/constants/index.ts
@@ -21,4 +21,8 @@ export enum FEATURE_KEY {
CREATE_QUERY_PERMISSIONS = 'create_query_permissions',
UPDATE_QUERY_PERMISSIONS = 'update_query_permissions',
DELETE_QUERY_PERMISSIONS = 'delete_query_permissions',
+ FETCH_COMPONENT_PERMISSIONS = 'fetch_component_permissions',
+ CREATE_COMPONENT_PERMISSIONS = 'create_component_permissions',
+ UPDATE_COMPONENT_PERMISSIONS = 'update_component_permissions',
+ DELETE_COMPONENT_PERMISSIONS = 'delete_component_permissions',
}
diff --git a/server/src/modules/app-permissions/controller.ts b/server/src/modules/app-permissions/controller.ts
index d317e3115b..65bdaf017d 100644
--- a/server/src/modules/app-permissions/controller.ts
+++ b/server/src/modules/app-permissions/controller.ts
@@ -127,4 +127,50 @@ export class AppPermissionsController implements IAppPermissionsController {
): Promise {
throw new NotFoundException();
}
+
+ @InitFeature(FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS)
+ @Get(':appId/components/:componentId')
+ async fetchComponentPermissions(
+ @User() user,
+ @Param('appId') appId: string,
+ @Param('componentId') componentId: string,
+ @Res({ passthrough: true }) response: Response
+ ): Promise {
+ throw new NotFoundException();
+ }
+
+ @InitFeature(FEATURE_KEY.CREATE_COMPONENT_PERMISSIONS)
+ @Post(':appId/components/:componentId')
+ async createComponentPermissions(
+ @User() user,
+ @Param('appId') appId: string,
+ @Param('componentId') componentId: string,
+ @Body() body: CreatePermissionDto,
+ @Res({ passthrough: true }) response: Response
+ ): Promise {
+ throw new NotFoundException();
+ }
+
+ @InitFeature(FEATURE_KEY.UPDATE_COMPONENT_PERMISSIONS)
+ @Put(':appId/components/:componentId')
+ async updateComponentPermissions(
+ @User() user,
+ @Param('appId') appId: string,
+ @Param('componentId') componentId: string,
+ @Body() body: CreatePermissionDto,
+ @Res({ passthrough: true }) response: Response
+ ): Promise {
+ throw new NotFoundException();
+ }
+
+ @InitFeature(FEATURE_KEY.DELETE_COMPONENT_PERMISSIONS)
+ @Delete(':appId/components/:componentId')
+ async deleteComponentPermissions(
+ @User() user,
+ @Param('appId') appId: string,
+ @Param('componentId') componentId: string,
+ @Res({ passthrough: true }) response: Response
+ ): Promise {
+ throw new NotFoundException();
+ }
}
diff --git a/server/src/modules/app-permissions/interfaces/IController.ts b/server/src/modules/app-permissions/interfaces/IController.ts
index a7808d280d..e9b9cdc24e 100644
--- a/server/src/modules/app-permissions/interfaces/IController.ts
+++ b/server/src/modules/app-permissions/interfaces/IController.ts
@@ -46,4 +46,24 @@ export interface IAppPermissionsController {
): Promise;
deleteQueryPermissions(user: User, appId: string, queryId: string, response: Response): Promise;
+
+ fetchComponentPermissions(user: User, appId: string, componentId: string, response: Response): Promise;
+
+ createComponentPermissions(
+ user: User,
+ appId: string,
+ componentId: string,
+ body: CreatePermissionDto,
+ response: Response
+ ): Promise;
+
+ updateComponentPermissions(
+ user: User,
+ appId: string,
+ componentId: string,
+ body: CreatePermissionDto,
+ response: Response
+ ): Promise;
+
+ deleteComponentPermissions(user: User, appId: string, componentId: string, response: Response): Promise;
}
diff --git a/server/src/modules/app-permissions/interfaces/IUtilService.ts b/server/src/modules/app-permissions/interfaces/IUtilService.ts
index dbd390982a..a38d845076 100644
--- a/server/src/modules/app-permissions/interfaces/IUtilService.ts
+++ b/server/src/modules/app-permissions/interfaces/IUtilService.ts
@@ -14,4 +14,8 @@ export interface IUtilService {
createQueryPermission(queryId: string, body: CreatePermissionDto): Promise;
updateQueryPermission(queryId: string, body: CreatePermissionDto): Promise;
+
+ createComponentPermission(componentId: string, body: CreatePermissionDto): Promise;
+
+ updateComponentPermission(componentId: string, body: CreatePermissionDto): Promise;
}
diff --git a/server/src/modules/app-permissions/module.ts b/server/src/modules/app-permissions/module.ts
index 5e3e3db107..b290bd5dbc 100644
--- a/server/src/modules/app-permissions/module.ts
+++ b/server/src/modules/app-permissions/module.ts
@@ -9,10 +9,14 @@ import { PageUsersRepository } from './repositories/page-users.repository';
import { PagePermissionsRepository } from './repositories/page-permissions.repository';
import { QueryUsersRepository } from './repositories/query-users.repository';
import { QueryPermissionsRepository } from './repositories/query-permissions.repository';
+import { ComponentUsersRepository } from './repositories/component-users.repository';
+import { ComponentPermissionsRepository } from './repositories/component-permissions.repository';
import { PageUser } from '@entities/page_users.entity';
import { PagePermission } from '@entities/page_permissions.entity';
import { QueryUser } from '@entities/query_users.entity';
import { QueryPermission } from '@entities/query_permissions.entity';
+import { ComponentUser } from '@entities/component_users.entity';
+import { ComponentPermission } from '@entities/component_permissions.entity';
export class AppPermissionsModule {
static async register(configs: { IS_GET_CONTEXT: boolean }): Promise {
@@ -24,7 +28,16 @@ export class AppPermissionsModule {
return {
module: AppPermissionsModule,
imports: [
- TypeOrmModule.forFeature([GroupPermissions, User, PageUser, PagePermission, QueryUser, QueryPermission]),
+ TypeOrmModule.forFeature([
+ GroupPermissions,
+ User,
+ PageUser,
+ PagePermission,
+ QueryUser,
+ QueryPermission,
+ ComponentUser,
+ ComponentPermission,
+ ]),
],
controllers: [AppPermissionsController],
providers: [
@@ -35,6 +48,8 @@ export class AppPermissionsModule {
PagePermissionsRepository,
QueryUsersRepository,
QueryPermissionsRepository,
+ ComponentUsersRepository,
+ ComponentPermissionsRepository,
FeatureAbilityFactory,
],
exports: [AppPermissionsUtilService, AppPermissionsService],
diff --git a/server/src/modules/app-permissions/repositories/component-permissions.repository.ts b/server/src/modules/app-permissions/repositories/component-permissions.repository.ts
new file mode 100644
index 0000000000..802dd12868
--- /dev/null
+++ b/server/src/modules/app-permissions/repositories/component-permissions.repository.ts
@@ -0,0 +1,58 @@
+import { ComponentPermission } from '@entities/component_permissions.entity';
+import { Injectable } from '@nestjs/common';
+import { DataSource, EntityManager, Repository } from 'typeorm';
+import { ComponentUsersRepository } from './component-users.repository';
+import { dbTransactionWrap } from '@helpers/database.helper';
+import { PAGE_PERMISSION_TYPE } from '../constants';
+
+@Injectable()
+export class ComponentPermissionsRepository extends Repository {
+ constructor(private dataSource: DataSource, private readonly componentUsersRepository: ComponentUsersRepository) {
+ super(ComponentPermission, dataSource.createEntityManager());
+ }
+
+ async getComponentPermissions(componentId: string, manager?: EntityManager): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const componentPermissions = await manager.find(ComponentPermission, {
+ where: { componentId },
+ relations: ['users', 'users.user', 'users.permissionGroup'],
+ });
+
+ return componentPermissions.map((permission) => {
+ if (permission.type === PAGE_PERMISSION_TYPE.GROUP) {
+ return {
+ ...permission,
+ groups: permission.users,
+ users: undefined,
+ };
+ }
+ return permission;
+ });
+ }, manager || this.manager);
+ }
+
+ async createComponentPermissions(
+ componentId: string,
+ type: PAGE_PERMISSION_TYPE,
+ manager?: EntityManager
+ ): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const existingPermission = await manager.findOne(ComponentPermission, { where: { componentId } });
+ if (existingPermission) {
+ throw new Error(`Component permission already exists for Component id: ${componentId}`);
+ }
+
+ const componentPermission = manager.create(ComponentPermission, {
+ componentId,
+ type,
+ });
+ return manager.save(componentPermission);
+ }, manager || this.manager);
+ }
+
+ async deleteComponentPermissions(componentId: string, manager?: EntityManager): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ await manager.delete(ComponentPermission, { componentId });
+ }, manager || this.manager);
+ }
+}
diff --git a/server/src/modules/app-permissions/repositories/component-users.repository.ts b/server/src/modules/app-permissions/repositories/component-users.repository.ts
new file mode 100644
index 0000000000..98478b4334
--- /dev/null
+++ b/server/src/modules/app-permissions/repositories/component-users.repository.ts
@@ -0,0 +1,83 @@
+import { ComponentUser } from '@entities/component_users.entity';
+import { Injectable } from '@nestjs/common';
+import { DataSource, EntityManager, Repository } from 'typeorm';
+import { dbTransactionWrap } from '@helpers/database.helper';
+import { ComponentPermission } from '@entities/component_permissions.entity';
+
+@Injectable()
+export class ComponentUsersRepository extends Repository {
+ constructor(private dataSource: DataSource) {
+ super(ComponentUser, dataSource.createEntityManager());
+ }
+
+ async createComponentUsersWithSingle(
+ componentPermissionsId: string,
+ users: string[],
+ manager?: EntityManager
+ ): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const componentUsers = users.map((userId) => {
+ return manager.create(ComponentUser, {
+ componentPermissionsId,
+ userId,
+ permissionGroupsId: null,
+ });
+ });
+ return manager.save(componentUsers);
+ }, manager || this.manager);
+ }
+
+ async createComponentUsersWithGroup(
+ componentPermissionsId: string,
+ groups: string[],
+ manager?: EntityManager
+ ): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const componentUsers = groups.map((permissionGroupsId) => {
+ return manager.create(ComponentUser, {
+ componentPermissionsId,
+ permissionGroupsId,
+ userId: null,
+ });
+ });
+ return manager.save(componentUsers);
+ }, manager || this.manager);
+ }
+
+ async checkComponentUserWithGroup(
+ componentPermission: ComponentPermission,
+ userId: string,
+ manager?: EntityManager
+ ): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const result = await manager
+ .createQueryBuilder(ComponentUser, 'component_users')
+ .innerJoin('component_users.permissionGroup', 'group')
+ .innerJoin('group.groupUsers', 'groupUser')
+ .where('component_users.componentPermission = :permissionId', {
+ permissionId: componentPermission.id,
+ })
+ .andWhere('groupUser.userId = :userId', { userId })
+ .getOne();
+
+ return !!result;
+ }, manager || this.manager);
+ }
+
+ async checkComponentUserWithSingle(
+ componentPermission: ComponentPermission,
+ userId: string,
+ manager?: EntityManager
+ ): Promise {
+ return dbTransactionWrap(async (manager: EntityManager) => {
+ const componentUser = await manager.findOne(ComponentUser, {
+ where: {
+ componentPermission: { id: componentPermission.id },
+ userId,
+ },
+ });
+
+ return !!componentUser;
+ }, manager || this.manager);
+ }
+}
diff --git a/server/src/modules/app-permissions/types/index.ts b/server/src/modules/app-permissions/types/index.ts
index d377f5a08f..5e3acf64b4 100644
--- a/server/src/modules/app-permissions/types/index.ts
+++ b/server/src/modules/app-permissions/types/index.ts
@@ -13,6 +13,10 @@ interface Features {
[FEATURE_KEY.CREATE_QUERY_PERMISSIONS]: FeatureConfig;
[FEATURE_KEY.UPDATE_QUERY_PERMISSIONS]: FeatureConfig;
[FEATURE_KEY.DELETE_QUERY_PERMISSIONS]: FeatureConfig;
+ [FEATURE_KEY.FETCH_COMPONENT_PERMISSIONS]: FeatureConfig;
+ [FEATURE_KEY.CREATE_COMPONENT_PERMISSIONS]: FeatureConfig;
+ [FEATURE_KEY.UPDATE_COMPONENT_PERMISSIONS]: FeatureConfig;
+ [FEATURE_KEY.DELETE_COMPONENT_PERMISSIONS]: FeatureConfig;
}
export interface FeaturesConfig {
diff --git a/server/src/modules/app-permissions/util.service.ts b/server/src/modules/app-permissions/util.service.ts
index 7ff894a058..55d927eebb 100644
--- a/server/src/modules/app-permissions/util.service.ts
+++ b/server/src/modules/app-permissions/util.service.ts
@@ -31,4 +31,12 @@ export class AppPermissionsUtilService implements IUtilService {
async updateQueryPermission(queryId: string, body: CreatePermissionDto): Promise {
throw new Error('Method not implemented.');
}
+
+ async createComponentPermission(componentId: string, body: CreatePermissionDto): Promise {
+ throw new Error('Method not implemented.');
+ }
+
+ async updateComponentPermission(componentId: string, body: CreatePermissionDto): Promise {
+ throw new Error('Method not implemented.');
+ }
}
diff --git a/server/src/modules/apps/interfaces/services/IPageService.ts b/server/src/modules/apps/interfaces/services/IPageService.ts
index f8f8fed9eb..9f06d02719 100644
--- a/server/src/modules/apps/interfaces/services/IPageService.ts
+++ b/server/src/modules/apps/interfaces/services/IPageService.ts
@@ -4,7 +4,7 @@ import { EventHandler } from 'src/entities/event_handler.entity';
import { CreatePageDto, UpdatePageDto } from '@modules/apps/dto/page';
export interface IPageService {
- findPagesForVersion(appVersionId: string): Promise;
+ findPagesForVersion(appVersionId: string, mode?: string): Promise;
findOne(id: string): Promise;
createPage(page: CreatePageDto, appVersionId: string): Promise;
clonePage(pageId: string, appVersionId: string): Promise<{ pages: Page[]; events: EventHandler[] }>;
diff --git a/server/src/modules/apps/services/app-import-export.service.ts b/server/src/modules/apps/services/app-import-export.service.ts
index 7a6a089548..cead465762 100644
--- a/server/src/modules/apps/services/app-import-export.service.ts
+++ b/server/src/modules/apps/services/app-import-export.service.ts
@@ -41,6 +41,8 @@ import { PageUser } from '@entities/page_users.entity';
import { UsersUtilService } from '@modules/users/util.service';
import { QueryPermission } from '@entities/query_permissions.entity';
import { QueryUser } from '@entities/query_users.entity';
+import { ComponentPermission } from '@entities/component_permissions.entity';
+import { ComponentUser } from '@entities/component_users.entity';
interface AppResourceMappings {
defaultDataSourceIdMapping: Record;
dataQueryMapping: Record;
@@ -238,6 +240,9 @@ export class AppImportExportService {
? await manager
.createQueryBuilder(Component, 'components')
.leftJoinAndSelect('components.layouts', 'layouts')
+ .leftJoinAndSelect('components.permissions', 'permission')
+ .leftJoinAndSelect('permission.users', 'componentUser')
+ .leftJoinAndSelect('componentUser.permissionGroup', 'permissionGroup')
.where('components.pageId IN(:...pageId)', {
pageId: pages.map((v) => v.id),
})
@@ -245,6 +250,21 @@ export class AppImportExportService {
.getMany()
: [];
+ const componentsWithPermissionGroups = components.map((component) => {
+ const groupPermission = component.permissions.find((perm) => perm.type === 'GROUP');
+
+ return {
+ ...component,
+ permissions: groupPermission
+ ? {
+ permissionGroup: groupPermission.users
+ .map((user) => user.permissionGroup?.name)
+ .filter((name): name is string => Boolean(name)),
+ }
+ : undefined,
+ };
+ });
+
const events = await manager
.createQueryBuilder(EventHandler, 'event_handlers')
.where('event_handlers.appVersionId IN(:...versionId)', {
@@ -253,7 +273,7 @@ export class AppImportExportService {
.orderBy('event_handlers.created_at', 'ASC')
.getMany();
- appToExport['components'] = components;
+ appToExport['components'] = componentsWithPermissionGroups;
appToExport['pages'] = pagesWithPermissionGroups;
appToExport['events'] = events;
appToExport['dataQueries'] = queriesWithPermissionGroups;
@@ -953,6 +973,13 @@ export class AppImportExportService {
await manager.save(newLayout);
}));
+ if (component.permissions) {
+ savedComponent.permissions = component.permissions;
+ }
+
+ //create component permissions of component if flag enabled in dto
+ await this.createComponentPermissionsForGroups(savedComponent, user.organizationId, manager);
+
const componentEvents = importingEvents.filter((event) => event.sourceId === component.id);
if (componentEvents.length > 0) {
@@ -1383,7 +1410,7 @@ export class AppImportExportService {
return pageSettings;
}
- async checkIfGroupPermissionsExist(pages, queries, organizationId) {
+ async checkIfGroupPermissionsExist(pages, queries, components, organizationId) {
const allGroupNames = new Set();
for (const page of pages) {
@@ -1402,6 +1429,15 @@ export class AppImportExportService {
}
}
+ for (const component of components) {
+ const groupNames = component.permissions?.permissionGroup || [];
+ for (const name of groupNames) {
+ if (!allGroupNames.has(name)) {
+ allGroupNames.add(name);
+ }
+ }
+ }
+
if (!allGroupNames.size) return;
return await dbTransactionWrap(async (manager: EntityManager) => {
@@ -1497,6 +1533,41 @@ export class AppImportExportService {
await manager.save(queryUsers);
}
+ async createComponentPermissionsForGroups(component, organizationId: string, manager: EntityManager) {
+ const groupNames = component.permissions?.permissionGroup || [];
+ if (!groupNames.length) return;
+
+ const existingGroups = await manager
+ .createQueryBuilder(GroupPermissions, 'gp')
+ .where('gp.name IN (:...names)', { names: groupNames })
+ .andWhere('gp.organizationId = :organizationId', { organizationId })
+ .getMany();
+
+ const groupMap = new Map(existingGroups.map((g) => [g.name, g]));
+
+ // Filter to only existing group names
+ const validGroupNames = groupNames.filter((name) => groupMap.has(name));
+
+ // If no valid group names exist, do not create permissions
+ if (!validGroupNames.length) return;
+
+ const permission = manager.create(ComponentPermission, {
+ componentId: component.id,
+ type: PAGE_PERMISSION_TYPE.GROUP,
+ });
+
+ const savedPermission = await manager.save(permission);
+
+ const componentUsers = validGroupNames.map((name) =>
+ manager.create(ComponentUser, {
+ componentPermissionsId: savedPermission.id,
+ permissionGroupsId: groupMap.get(name).id,
+ })
+ );
+
+ await manager.save(componentUsers);
+ }
+
async createAppVersionsForImportedApp(
manager: EntityManager,
user: User,
diff --git a/server/src/modules/apps/services/page.service.ts b/server/src/modules/apps/services/page.service.ts
index 02bea48dab..19be4f9b12 100644
--- a/server/src/modules/apps/services/page.service.ts
+++ b/server/src/modules/apps/services/page.service.ts
@@ -23,7 +23,7 @@ export class PageService implements IPageService {
protected eventHandlerService: EventsService
) {}
- async findPagesForVersion(appVersionId: string): Promise {
+ async findPagesForVersion(appVersionId: string, mode?: string): Promise {
// const allPages = await this.pageRepository.find({ where: { appVersionId }, order: { index: 'ASC' } });
const allPages = await this.pageHelperService.fetchPages(appVersionId);
const pagesWithComponents = await Promise.all(
diff --git a/server/src/modules/import-export-resources/service.ts b/server/src/modules/import-export-resources/service.ts
index 465524076a..f644daa92c 100644
--- a/server/src/modules/import-export-resources/service.ts
+++ b/server/src/modules/import-export-resources/service.ts
@@ -79,8 +79,14 @@ export class ImportExportResourcesService {
appParams = { ...appParams.appV2 };
const pages = appParams?.pages;
const queries = appParams?.dataQueries;
- (pages?.length || queries?.length) &&
- (await this.appImportExportService.checkIfGroupPermissionsExist(pages, queries, user.organizationId));
+ const components = appParams?.components;
+ (pages?.length || queries?.length || components?.length) &&
+ (await this.appImportExportService.checkIfGroupPermissionsExist(
+ pages,
+ queries,
+ components,
+ user.organizationId
+ ));
}
}
}
diff --git a/server/src/modules/versions/controller.v2.ts b/server/src/modules/versions/controller.v2.ts
index 7a3f006883..02e9f91268 100644
--- a/server/src/modules/versions/controller.v2.ts
+++ b/server/src/modules/versions/controller.v2.ts
@@ -1,4 +1,4 @@
-import { Body, Controller, Get, Put, UseGuards } from '@nestjs/common';
+import { Body, Controller, Get, Put, Query, UseGuards } from '@nestjs/common';
import { VersionService } from './service';
import { InitModule } from '@modules/app/decorators/init-module';
import { MODULES } from '@modules/app/constants/modules';
@@ -26,8 +26,8 @@ export class VersionControllerV2 implements IVersionControllerV2 {
@InitFeature(FEATURE_KEY.GET_ONE)
@UseGuards(JwtAuthGuard, ValidAppGuard, FeatureAbilityGuard)
@Get(':id/versions/:versionId')
- getVersion(@User() user: UserEntity, @App() app: AppEntity) {
- return this.versionService.getVersion(app, user);
+ getVersion(@User() user: UserEntity, @App() app: AppEntity, @Query('mode') mode?: string) {
+ return this.versionService.getVersion(app, user, mode);
}
@InitFeature(FEATURE_KEY.UPDATE)
diff --git a/server/src/modules/versions/interfaces/IControllerV2.ts b/server/src/modules/versions/interfaces/IControllerV2.ts
index 2d86353981..f432e9d3dd 100644
--- a/server/src/modules/versions/interfaces/IControllerV2.ts
+++ b/server/src/modules/versions/interfaces/IControllerV2.ts
@@ -4,7 +4,7 @@ import { App as AppEntity } from '@entities/app.entity';
import { PromoteVersionDto } from '../dto';
export interface IVersionControllerV2 {
- getVersion(user: UserEntity, app: AppEntity): Promise;
+ getVersion(user: UserEntity, app: AppEntity, mode?: string): Promise;
updateVersion(user: UserEntity, app: AppEntity, appVersionUpdateDto: AppVersionUpdateDto): Promise;
updateGlobalSettings(user: UserEntity, app: AppEntity, appVersionUpdateDto: AppVersionUpdateDto): Promise;
promoteVersion(user: UserEntity, app: AppEntity, promoteVersionDto: PromoteVersionDto): Promise;
diff --git a/server/src/modules/versions/interfaces/IService.ts b/server/src/modules/versions/interfaces/IService.ts
index dc39061efa..5c6ed2d1b4 100644
--- a/server/src/modules/versions/interfaces/IService.ts
+++ b/server/src/modules/versions/interfaces/IService.ts
@@ -11,7 +11,7 @@ export interface IVersionService {
deleteVersion(app: App, user: User): Promise;
- getVersion(app: App, user: User): Promise;
+ getVersion(app: App, user: User, mode?: string): Promise;
update(app: App, user: User, appVersionUpdateDto: AppVersionUpdateDto): Promise;
diff --git a/server/src/modules/versions/repository.ts b/server/src/modules/versions/repository.ts
index 9f3dd5f017..e4879a51d5 100644
--- a/server/src/modules/versions/repository.ts
+++ b/server/src/modules/versions/repository.ts
@@ -147,6 +147,34 @@ export class VersionRepository extends Repository {
}, manager || this.manager);
}
+ async findVersionWithQueryPermissions(id: string, manager?: EntityManager): Promise {
+ return await dbTransactionWrap(async (manager: EntityManager) => {
+ const appVersion = await manager
+ .createQueryBuilder(AppVersion, 'appVersion')
+ .where('appVersion.id = :id', { id })
+ .leftJoinAndSelect('appVersion.app', 'app')
+ .leftJoinAndSelect('appVersion.dataQueries', 'dataQueries')
+ .leftJoinAndSelect('dataQueries.dataSource', 'dataSource')
+ .leftJoinAndSelect('dataQueries.plugins', 'plugins')
+ .leftJoinAndSelect('plugins.manifestFile', 'manifestFile')
+ .leftJoinAndSelect('dataQueries.permissions', 'permission')
+ .leftJoinAndSelect('permission.users', 'queryUser')
+ .leftJoinAndSelect('queryUser.user', 'user')
+ .leftJoinAndSelect('queryUser.permissionGroup', 'group')
+ .getOneOrFail();
+
+ if (appVersion?.dataQueries) {
+ for (const query of appVersion?.dataQueries) {
+ if (query?.plugin) {
+ query.plugin.manifestFile.data = JSON.parse(decode(query.plugin.manifestFile.data.toString('utf8')));
+ }
+ }
+ }
+
+ return appVersion;
+ }, manager || this.manager);
+ }
+
getVersionsInApp(appId: string, manager?: EntityManager): Promise {
return dbTransactionWrap((manager: EntityManager) => {
return manager.find(AppVersion, {
diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts
index 2f936419cd..558d1c5bfe 100644
--- a/server/src/modules/versions/service.ts
+++ b/server/src/modules/versions/service.ts
@@ -94,7 +94,7 @@ export class VersionService implements IVersionService {
return await this.versionsUtilService.deleteVersion(app, user, manager);
}
- async getVersion(app: App, user: User): Promise {
+ async getVersion(app: App, user: User, mode?: string): Promise {
const prepareResponse = async (app: App, versionId: string) => {
let appVersion,
updatedVersionId = versionId;