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: ( + 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 && ( -
- - - {INSPECTOR_HEADER_OPTIONS.map((option) => ( -
{ - e.stopPropagation(); - handleInspectorHeaderActions(option.value); - }} - > -
{option.icon}
-
- {option?.label} -
-
- ))} -
- - } - > - setShowHeaderActionsMenu(true)}> - - -
-
+ <> +
+ + + {INSPECTOR_HEADER_OPTIONS.map((option) => { + const optionBody = ( +
{ + e.stopPropagation(); + handleInspectorHeaderActions(option.value); + }} + > +
{option.icon}
+
+ {option?.label} +
+ {option.value === 'permission' && + !licenseValid && + option.trailingIcon && + option.trailingIcon} +
+ ); + + return option.value === 'permission' ? ( + + {optionBody} + + ) : ( + optionBody + ); + })} +
+ + } + > + setShowHeaderActionsMenu(true)}> + + +
+
+ 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;