From 4477c487349448cb47f2631d91993f119ac232d1 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 20 May 2025 20:34:10 +0530 Subject: [PATCH 01/35] Update submodule reference --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 30dbfa7545..0f2e1e5905 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 30dbfa754562d00f8d64181d5006e113798bd668 +Subproject commit 0f2e1e5905e4a731b45f962983aa5eff745c0b35 From 2d1bc33fe09c1005d6662d8aa1a6eefb1cdc53d5 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 20 May 2025 23:31:29 +0530 Subject: [PATCH 02/35] Added migrations for query_permissions and query_users table --- .../1747759439358-CreateQueryPermissions.ts | 51 +++++++++++++ .../1747763331564-CreateQueryUsers.ts | 76 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 server/migrations/1747759439358-CreateQueryPermissions.ts create mode 100644 server/migrations/1747763331564-CreateQueryUsers.ts diff --git a/server/migrations/1747759439358-CreateQueryPermissions.ts b/server/migrations/1747759439358-CreateQueryPermissions.ts new file mode 100644 index 0000000000..fe6bce52ce --- /dev/null +++ b/server/migrations/1747759439358-CreateQueryPermissions.ts @@ -0,0 +1,51 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; + +export class CreateQueryPermissions1747759439358 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'query_permissions', + columns: [ + { + name: 'id', + type: 'uuid', + isGenerated: true, + default: 'gen_random_uuid()', + isPrimary: true, + }, + { + name: 'query_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( + 'query_permissions', + new TableForeignKey({ + columnNames: ['query_id'], + referencedColumnNames: ['id'], + referencedTableName: 'data_queries', + onDelete: 'CASCADE', + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('query_permissions'); + } +} diff --git a/server/migrations/1747763331564-CreateQueryUsers.ts b/server/migrations/1747763331564-CreateQueryUsers.ts new file mode 100644 index 0000000000..819b736fba --- /dev/null +++ b/server/migrations/1747763331564-CreateQueryUsers.ts @@ -0,0 +1,76 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; + +export class CreateQueryUsers1747763331564 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'query_users', + columns: [ + { + name: 'id', + type: 'uuid', + isGenerated: true, + default: 'gen_random_uuid()', + isPrimary: true, + }, + { + name: 'query_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( + 'query_users', + new TableForeignKey({ + columnNames: ['query_permissions_id'], + referencedColumnNames: ['id'], + referencedTableName: 'query_permissions', + onDelete: 'CASCADE', + }) + ); + + await queryRunner.createForeignKey( + 'query_users', + new TableForeignKey({ + columnNames: ['user_id'], + referencedColumnNames: ['id'], + referencedTableName: 'users', + onDelete: 'CASCADE', + }) + ); + + await queryRunner.createForeignKey( + 'query_users', + new TableForeignKey({ + columnNames: ['permission_groups_id'], + referencedColumnNames: ['id'], + referencedTableName: 'permission_groups', + onDelete: 'CASCADE', + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable('query_users'); + } +} From fbb30fcff9333b706821dbfb0bd82bfc9a8119a3 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 21 May 2025 11:44:06 +0530 Subject: [PATCH 03/35] Added the required entities for query_permissions and query_users tables --- server/src/entities/data_query.entity.ts | 5 +++ .../src/entities/group_permissions.entity.ts | 4 +++ .../src/entities/query_permissions.entity.ts | 29 ++++++++++++++++ server/src/entities/query_users.entity.ts | 34 +++++++++++++++++++ server/src/entities/user.entity.ts | 4 +++ 5 files changed, 76 insertions(+) create mode 100644 server/src/entities/query_permissions.entity.ts create mode 100644 server/src/entities/query_users.entity.ts diff --git a/server/src/entities/data_query.entity.ts b/server/src/entities/data_query.entity.ts index 49e5b1f1a5..7e3a8d25a5 100644 --- a/server/src/entities/data_query.entity.ts +++ b/server/src/entities/data_query.entity.ts @@ -10,11 +10,13 @@ import { JoinTable, ManyToMany, AfterLoad, + OneToMany, } from 'typeorm'; import { App } from './app.entity'; import { AppVersion } from './app_version.entity'; import { DataSource } from './data_source.entity'; import { Plugin } from './plugin.entity'; +import { QueryPermission } from './query_permissions.entity'; @Entity({ name: 'data_queries' }) export class DataQuery extends BaseEntity { @@ -81,6 +83,9 @@ export class DataQuery extends BaseEntity { app: App; + @OneToMany(() => QueryPermission, (permission) => permission.query) + permissions: QueryPermission[]; + @AfterLoad() updatePlugin() { if (this.plugins?.length) this.plugin = this.plugins[0]; diff --git a/server/src/entities/group_permissions.entity.ts b/server/src/entities/group_permissions.entity.ts index 693f4f930c..0ff6e47b0b 100644 --- a/server/src/entities/group_permissions.entity.ts +++ b/server/src/entities/group_permissions.entity.ts @@ -14,6 +14,7 @@ import { GroupUsers } from './group_users.entity'; 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'; @Entity({ name: 'permission_groups' }) export class GroupPermissions extends BaseEntity { @@ -66,5 +67,8 @@ export class GroupPermissions extends BaseEntity { @OneToMany(() => PageUser, (pageUser) => pageUser.permissionGroup) pageUsers: PageUser[]; + @OneToMany(() => QueryUser, (queryUser) => queryUser.permissionGroup) + queryUsers: QueryUser[]; + disabled?: boolean; } diff --git a/server/src/entities/query_permissions.entity.ts b/server/src/entities/query_permissions.entity.ts new file mode 100644 index 0000000000..c693c6b994 --- /dev/null +++ b/server/src/entities/query_permissions.entity.ts @@ -0,0 +1,29 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn, OneToMany } from 'typeorm'; +import { DataQuery } from './data_query.entity'; +import { QueryUser } from './query_users.entity'; +import { PAGE_PERMISSION_TYPE } from '@modules/app-permissions/constants'; + +@Entity('query_permissions') +export class QueryPermission { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ name: 'query_id', type: 'uuid', nullable: false }) + queryId: string; + + @Column({ + type: 'enum', + enum: PAGE_PERMISSION_TYPE, + }) + type: PAGE_PERMISSION_TYPE; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @ManyToOne(() => DataQuery, (query) => query.permissions, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'query_id' }) + query: DataQuery; + + @OneToMany(() => QueryUser, (queryUser) => queryUser.queryPermission) + users: QueryUser[]; +} diff --git a/server/src/entities/query_users.entity.ts b/server/src/entities/query_users.entity.ts new file mode 100644 index 0000000000..46b16be0af --- /dev/null +++ b/server/src/entities/query_users.entity.ts @@ -0,0 +1,34 @@ +import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, CreateDateColumn } from 'typeorm'; +import { User } from './user.entity'; +import { QueryPermission } from './query_permissions.entity'; +import { GroupPermissions } from './group_permissions.entity'; + +@Entity('query_users') +export class QueryUser { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ name: 'query_permissions_id', type: 'uuid' }) + queryPermissionsId: 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(() => QueryPermission, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'query_permissions_id' }) + queryPermission: QueryPermission; + + @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/user.entity.ts b/server/src/entities/user.entity.ts index e052e11245..515b1f469e 100644 --- a/server/src/entities/user.entity.ts +++ b/server/src/entities/user.entity.ts @@ -30,6 +30,7 @@ import { AiConversation } from './ai_conversation.entity'; 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'; @Entity({ name: 'users' }) export class User extends BaseEntity { @@ -188,6 +189,9 @@ export class User extends BaseEntity { @OneToMany(() => PageUser, (pageUser) => pageUser.user) pageUsers: PageUser[]; + @OneToMany(() => QueryUser, (queryUser) => queryUser.user) + queryUsers: QueryUser[]; + organizationId: string; invitedOrganizationId: string; organizationIds?: Array; From b13a8ca376dc471cd38e61b9ba6199ac1f763485 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 21 May 2025 13:17:36 +0530 Subject: [PATCH 04/35] Created base for API endpoint, feature keys and DTO --- .../modules/app-permissions/ability/index.ts | 18 ++++++- .../app-permissions/constants/features.ts | 4 ++ .../app-permissions/constants/index.ts | 4 ++ .../src/modules/app-permissions/controller.ts | 48 ++++++++++++++++++- .../src/modules/app-permissions/dto/index.ts | 23 +++++++++ .../app-permissions/interfaces/IController.ts | 22 ++++++++- .../modules/app-permissions/types/index.ts | 4 ++ 7 files changed, 120 insertions(+), 3 deletions(-) diff --git a/server/src/modules/app-permissions/ability/index.ts b/server/src/modules/app-permissions/ability/index.ts index d2e8c263b2..5a03f417d5 100644 --- a/server/src/modules/app-permissions/ability/index.ts +++ b/server/src/modules/app-permissions/ability/index.ts @@ -38,6 +38,10 @@ export class FeatureAbilityFactory extends AbilityFactory FEATURE_KEY.CREATE_PAGE_PERMISSIONS, FEATURE_KEY.UPDATE_PAGE_PERMISSIONS, FEATURE_KEY.DELETE_PAGE_PERMISSIONS, + FEATURE_KEY.FETCH_QUERY_PERMISSIONS, + FEATURE_KEY.CREATE_QUERY_PERMISSIONS, + FEATURE_KEY.UPDATE_QUERY_PERMISSIONS, + FEATURE_KEY.DELETE_QUERY_PERMISSIONS, ], App ); @@ -56,6 +60,10 @@ export class FeatureAbilityFactory extends AbilityFactory FEATURE_KEY.CREATE_PAGE_PERMISSIONS, FEATURE_KEY.UPDATE_PAGE_PERMISSIONS, FEATURE_KEY.DELETE_PAGE_PERMISSIONS, + FEATURE_KEY.FETCH_QUERY_PERMISSIONS, + FEATURE_KEY.CREATE_QUERY_PERMISSIONS, + FEATURE_KEY.UPDATE_QUERY_PERMISSIONS, + FEATURE_KEY.DELETE_QUERY_PERMISSIONS, ], App ); @@ -66,7 +74,15 @@ export class FeatureAbilityFactory extends AbilityFactory isAllAppsViewable || (userAppPermissions?.viewableAppsId?.length && appId && userAppPermissions.viewableAppsId.includes(appId)) ) { - can([FEATURE_KEY.FETCH_USERS, FEATURE_KEY.FETCH_USER_GROUPS, FEATURE_KEY.FETCH_PAGE_PERMISSIONS], App); + can( + [ + FEATURE_KEY.FETCH_USERS, + FEATURE_KEY.FETCH_USER_GROUPS, + FEATURE_KEY.FETCH_PAGE_PERMISSIONS, + FEATURE_KEY.FETCH_QUERY_PERMISSIONS, + ], + App + ); } } } diff --git a/server/src/modules/app-permissions/constants/features.ts b/server/src/modules/app-permissions/constants/features.ts index 6d77625ec5..360b1cf4c9 100644 --- a/server/src/modules/app-permissions/constants/features.ts +++ b/server/src/modules/app-permissions/constants/features.ts @@ -10,5 +10,9 @@ export const FEATURES: FeaturesConfig = { [FEATURE_KEY.CREATE_PAGE_PERMISSIONS]: {}, [FEATURE_KEY.UPDATE_PAGE_PERMISSIONS]: {}, [FEATURE_KEY.DELETE_PAGE_PERMISSIONS]: {}, + [FEATURE_KEY.FETCH_QUERY_PERMISSIONS]: {}, + [FEATURE_KEY.CREATE_QUERY_PERMISSIONS]: {}, + [FEATURE_KEY.UPDATE_QUERY_PERMISSIONS]: {}, + [FEATURE_KEY.DELETE_QUERY_PERMISSIONS]: {}, }, }; diff --git a/server/src/modules/app-permissions/constants/index.ts b/server/src/modules/app-permissions/constants/index.ts index c1d2afe78b..7d9d067af7 100644 --- a/server/src/modules/app-permissions/constants/index.ts +++ b/server/src/modules/app-permissions/constants/index.ts @@ -11,4 +11,8 @@ export enum FEATURE_KEY { CREATE_PAGE_PERMISSIONS = 'create_page_permissions', UPDATE_PAGE_PERMISSIONS = 'update_page_permissions', DELETE_PAGE_PERMISSIONS = 'delete_page_permissions', + FETCH_QUERY_PERMISSIONS = 'fetch_query_permissions', + CREATE_QUERY_PERMISSIONS = 'create_query_permissions', + UPDATE_QUERY_PERMISSIONS = 'update_query_permissions', + DELETE_QUERY_PERMISSIONS = 'delete_query_permissions', } diff --git a/server/src/modules/app-permissions/controller.ts b/server/src/modules/app-permissions/controller.ts index 2d0ccea9ce..aac1e532a1 100644 --- a/server/src/modules/app-permissions/controller.ts +++ b/server/src/modules/app-permissions/controller.ts @@ -8,7 +8,7 @@ import { MODULES } from '@modules/app/constants/modules'; import { InitFeature } from '@modules/app/decorators/init-feature.decorator'; import { FEATURE_KEY } from './constants'; import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; -import { CreatePagePermissionDto } from './dto'; +import { CreatePagePermissionDto, CreateQueryPermissionDto } from './dto'; @InitModule(MODULES.APP_PERMISSIONS) @UseGuards(JwtAuthGuard, FeatureAbilityGuard) @@ -81,4 +81,50 @@ export class AppPermissionsController implements IAppPermissionsController { ): Promise { throw new NotFoundException(); } + + @InitFeature(FEATURE_KEY.FETCH_QUERY_PERMISSIONS) + @Get(':appId/queries/:queryId') + async fetchQueryPermissions( + @User() user, + @Param('appId') appId: string, + @Param('queryId') queryId: string, + @Res({ passthrough: true }) response: Response + ): Promise { + throw new NotFoundException(); + } + + @InitFeature(FEATURE_KEY.CREATE_QUERY_PERMISSIONS) + @Post(':appId/queries/:queryId') + async createQueryPermissions( + @User() user, + @Param('appId') appId: string, + @Param('queryId') queryId: string, + @Body() body: CreateQueryPermissionDto, + @Res({ passthrough: true }) response: Response + ): Promise { + throw new NotFoundException(); + } + + @InitFeature(FEATURE_KEY.UPDATE_QUERY_PERMISSIONS) + @Put(':appId/queries/:queryId') + async updateQueryPermissions( + @User() user, + @Param('appId') appId: string, + @Param('queryId') queryId: string, + @Body() body: CreateQueryPermissionDto, + @Res({ passthrough: true }) response: Response + ): Promise { + throw new NotFoundException(); + } + + @InitFeature(FEATURE_KEY.DELETE_QUERY_PERMISSIONS) + @Delete(':appId/queries/:queryId') + async deleteQueryPermissions( + @User() user, + @Param('appId') appId: string, + @Param('queryId') queryId: string, + @Res({ passthrough: true }) response: Response + ): Promise { + throw new NotFoundException(); + } } diff --git a/server/src/modules/app-permissions/dto/index.ts b/server/src/modules/app-permissions/dto/index.ts index 20a1bd98b8..ac4a2c8df6 100644 --- a/server/src/modules/app-permissions/dto/index.ts +++ b/server/src/modules/app-permissions/dto/index.ts @@ -24,3 +24,26 @@ export class CreatePagePermissionDto { @Type(() => String) groups?: string[]; } + +export class CreateQueryPermissionDto { + @IsUUID(4) + @IsOptional() + queryId: string; + + @IsEnum(PAGE_PERMISSION_TYPE) + type: PAGE_PERMISSION_TYPE; + + @ValidateIf((o) => o.type === PAGE_PERMISSION_TYPE.SINGLE) + @IsArray() + @IsString({ each: true }) + @IsOptional() + @Type(() => String) + users?: string[]; + + @ValidateIf((o) => o.type === PAGE_PERMISSION_TYPE.GROUP) + @IsArray() + @IsString({ each: true }) + @IsOptional() + @Type(() => String) + groups?: string[]; +} diff --git a/server/src/modules/app-permissions/interfaces/IController.ts b/server/src/modules/app-permissions/interfaces/IController.ts index bfa35aa730..4feb039ca0 100644 --- a/server/src/modules/app-permissions/interfaces/IController.ts +++ b/server/src/modules/app-permissions/interfaces/IController.ts @@ -1,6 +1,6 @@ import { User } from '@entities/user.entity'; import { Response } from 'express'; -import { CreatePagePermissionDto } from '../dto'; +import { CreatePagePermissionDto, CreateQueryPermissionDto } from '../dto'; export interface IAppPermissionsController { fetchUsers(user: User, appId: string, response: Response): Promise; @@ -26,4 +26,24 @@ export interface IAppPermissionsController { ): Promise; deletePagePermissions(user: User, appId: string, pageId: string, response: Response): Promise; + + fetchQueryPermissions(user: User, appId: string, queryId: string, response: Response): Promise; + + createQueryPermissions( + user: User, + appId: string, + queryId: string, + body: CreateQueryPermissionDto, + response: Response + ): Promise; + + updateQueryPermissions( + user: User, + appId: string, + queryId: string, + body: CreateQueryPermissionDto, + response: Response + ): Promise; + + deleteQueryPermissions(user: User, appId: string, queryId: string, response: Response): Promise; } diff --git a/server/src/modules/app-permissions/types/index.ts b/server/src/modules/app-permissions/types/index.ts index 86a41afba1..d377f5a08f 100644 --- a/server/src/modules/app-permissions/types/index.ts +++ b/server/src/modules/app-permissions/types/index.ts @@ -9,6 +9,10 @@ interface Features { [FEATURE_KEY.CREATE_PAGE_PERMISSIONS]: FeatureConfig; [FEATURE_KEY.UPDATE_PAGE_PERMISSIONS]: FeatureConfig; [FEATURE_KEY.DELETE_PAGE_PERMISSIONS]: FeatureConfig; + [FEATURE_KEY.FETCH_QUERY_PERMISSIONS]: FeatureConfig; + [FEATURE_KEY.CREATE_QUERY_PERMISSIONS]: FeatureConfig; + [FEATURE_KEY.UPDATE_QUERY_PERMISSIONS]: FeatureConfig; + [FEATURE_KEY.DELETE_QUERY_PERMISSIONS]: FeatureConfig; } export interface FeaturesConfig { From 82eb0204c2bbe9435e0bee52a70cac7fe77566bd Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 21 May 2025 15:55:02 +0530 Subject: [PATCH 05/35] Made the service functions reusable for both query as well as page permissions --- server/ee | 2 +- .../modules/app-permissions/constants/index.ts | 6 ++++++ .../app-permissions/interfaces/IService.ts | 15 +++++++++++---- server/src/modules/app-permissions/service.ts | 8 ++++---- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/server/ee b/server/ee index 0f2e1e5905..c1d051b043 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 0f2e1e5905e4a731b45f962983aa5eff745c0b35 +Subproject commit c1d051b04364f9a2db333b3cffb77d611d50c8eb diff --git a/server/src/modules/app-permissions/constants/index.ts b/server/src/modules/app-permissions/constants/index.ts index 7d9d067af7..ff5063e948 100644 --- a/server/src/modules/app-permissions/constants/index.ts +++ b/server/src/modules/app-permissions/constants/index.ts @@ -4,6 +4,12 @@ export enum PAGE_PERMISSION_TYPE { ALL = 'ALL', } +export enum PERMISSION_ENTITY_TYPE { + PAGE = 'PAGE', + QUERY = 'QUERY', + COMPONENT = 'COMPONENT', +} + export enum FEATURE_KEY { FETCH_USERS = 'fetch_users', FETCH_USER_GROUPS = 'fetch_user_groups', diff --git a/server/src/modules/app-permissions/interfaces/IService.ts b/server/src/modules/app-permissions/interfaces/IService.ts index cad5fef726..58a64dfc68 100644 --- a/server/src/modules/app-permissions/interfaces/IService.ts +++ b/server/src/modules/app-permissions/interfaces/IService.ts @@ -1,16 +1,23 @@ import { User } from '@entities/user.entity'; import { CreatePagePermissionDto } from '../dto'; +import { PERMISSION_ENTITY_TYPE } from '../constants'; export interface IAppPermissionsService { fetchUsers(appId: string, user: User): Promise; fetchUserGroups(appId: string, user: User): Promise; - fetchPagePermissions(pageId: string): Promise; + fetchAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string): Promise; - createPagePermissions(pageId: string, body: CreatePagePermissionDto): Promise; + createAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string, body: CreatePagePermissionDto): Promise; - updatePagePermissions(appId: string, pageId: string, body: CreatePagePermissionDto, user: User): Promise; + updateAppPermissions( + type: PERMISSION_ENTITY_TYPE, + appId: string, + pageId: string, + body: CreatePagePermissionDto, + user: User + ): Promise; - deletePagePermissions(pageId: string): Promise; + deleteAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string): Promise; } diff --git a/server/src/modules/app-permissions/service.ts b/server/src/modules/app-permissions/service.ts index c6b5bc640d..637e144c8e 100644 --- a/server/src/modules/app-permissions/service.ts +++ b/server/src/modules/app-permissions/service.ts @@ -13,19 +13,19 @@ export class AppPermissionsService implements IAppPermissionsService { throw new Error('Method not implemented.'); } - async fetchPagePermissions(pageId) { + async fetchAppPermissions(type, pageId) { throw new Error('Method not implemented.'); } - async createPagePermissions(pageId, body) { + async createAppPermissions(type, pageId, body) { throw new Error('Method not implemented.'); } - async updatePagePermissions(appId, pageId, body, user) { + async updateAppPermissions(type, appId, pageId, body, user) { throw new Error('Method not implemented.'); } - async deletePagePermissions(pageId) { + async deleteAppPermissions(type, pageId) { throw new Error('Method not implemented.'); } } From e68b5c48ad5582134d0575698f98b660ef4d083b Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 21 May 2025 16:48:33 +0530 Subject: [PATCH 06/35] Combined both DTOs to reduce duplicate code --- .../LeftSidebar/PageMenu/PagePermission.jsx | 4 +-- server/ee | 2 +- .../src/modules/app-permissions/controller.ts | 10 +++---- .../src/modules/app-permissions/dto/index.ts | 27 ++----------------- .../app-permissions/interfaces/IController.ts | 10 +++---- .../app-permissions/interfaces/IService.ts | 12 ++++----- .../interfaces/IUtilService.ts | 6 ++--- server/src/modules/app-permissions/service.ts | 8 +++--- .../modules/app-permissions/util.service.ts | 6 ++--- 9 files changed, 31 insertions(+), 54 deletions(-) diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx index e82f251665..bd7a865a54 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx @@ -177,7 +177,7 @@ export default function PagePermission({ darkMode }) { const createPagePermission = () => { const body = { - pageId: editingPage?.id, + id: editingPage?.id, type: PERMISSION_TYPES[pagePermissionType], ...(pagePermissionType === 'group' ? { groups: selectedUserGroups.map((group) => group?.value) } @@ -205,7 +205,7 @@ export default function PagePermission({ darkMode }) { const updatePagePermission = () => { const body = { - pageId: editingPage?.id, + id: editingPage?.id, type: PERMISSION_TYPES[pagePermissionType], ...(pagePermissionType === 'group' ? { groups: selectedUserGroups.map((group) => group?.value) } diff --git a/server/ee b/server/ee index c1d051b043..f61295c5f8 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit c1d051b04364f9a2db333b3cffb77d611d50c8eb +Subproject commit f61295c5f85c354048f15275d4b3af52019d95b3 diff --git a/server/src/modules/app-permissions/controller.ts b/server/src/modules/app-permissions/controller.ts index aac1e532a1..d317e3115b 100644 --- a/server/src/modules/app-permissions/controller.ts +++ b/server/src/modules/app-permissions/controller.ts @@ -8,7 +8,7 @@ import { MODULES } from '@modules/app/constants/modules'; import { InitFeature } from '@modules/app/decorators/init-feature.decorator'; import { FEATURE_KEY } from './constants'; import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; -import { CreatePagePermissionDto, CreateQueryPermissionDto } from './dto'; +import { CreatePermissionDto } from './dto'; @InitModule(MODULES.APP_PERMISSIONS) @UseGuards(JwtAuthGuard, FeatureAbilityGuard) @@ -53,7 +53,7 @@ export class AppPermissionsController implements IAppPermissionsController { @User() user, @Param('appId') appId: string, @Param('pageId') pageId: string, - @Body() body: CreatePagePermissionDto, + @Body() body: CreatePermissionDto, @Res({ passthrough: true }) response: Response ): Promise { throw new NotFoundException(); @@ -65,7 +65,7 @@ export class AppPermissionsController implements IAppPermissionsController { @User() user, @Param('appId') appId: string, @Param('pageId') pageId: string, - @Body() body: CreatePagePermissionDto, + @Body() body: CreatePermissionDto, @Res({ passthrough: true }) response: Response ): Promise { throw new NotFoundException(); @@ -99,7 +99,7 @@ export class AppPermissionsController implements IAppPermissionsController { @User() user, @Param('appId') appId: string, @Param('queryId') queryId: string, - @Body() body: CreateQueryPermissionDto, + @Body() body: CreatePermissionDto, @Res({ passthrough: true }) response: Response ): Promise { throw new NotFoundException(); @@ -111,7 +111,7 @@ export class AppPermissionsController implements IAppPermissionsController { @User() user, @Param('appId') appId: string, @Param('queryId') queryId: string, - @Body() body: CreateQueryPermissionDto, + @Body() body: CreatePermissionDto, @Res({ passthrough: true }) response: Response ): Promise { throw new NotFoundException(); diff --git a/server/src/modules/app-permissions/dto/index.ts b/server/src/modules/app-permissions/dto/index.ts index ac4a2c8df6..b32fafe0a1 100644 --- a/server/src/modules/app-permissions/dto/index.ts +++ b/server/src/modules/app-permissions/dto/index.ts @@ -2,33 +2,10 @@ import { IsUUID, IsEnum, IsArray, IsString, IsOptional, ValidateIf } from 'class import { Type } from 'class-transformer'; import { PAGE_PERMISSION_TYPE } from '../constants'; -export class CreatePagePermissionDto { +export class CreatePermissionDto { @IsUUID(4) @IsOptional() - pageId: string; - - @IsEnum(PAGE_PERMISSION_TYPE) - type: PAGE_PERMISSION_TYPE; - - @ValidateIf((o) => o.type === PAGE_PERMISSION_TYPE.SINGLE) - @IsArray() - @IsString({ each: true }) - @IsOptional() - @Type(() => String) - users?: string[]; - - @ValidateIf((o) => o.type === PAGE_PERMISSION_TYPE.GROUP) - @IsArray() - @IsString({ each: true }) - @IsOptional() - @Type(() => String) - groups?: string[]; -} - -export class CreateQueryPermissionDto { - @IsUUID(4) - @IsOptional() - queryId: string; + id: string; @IsEnum(PAGE_PERMISSION_TYPE) type: PAGE_PERMISSION_TYPE; diff --git a/server/src/modules/app-permissions/interfaces/IController.ts b/server/src/modules/app-permissions/interfaces/IController.ts index 4feb039ca0..a7808d280d 100644 --- a/server/src/modules/app-permissions/interfaces/IController.ts +++ b/server/src/modules/app-permissions/interfaces/IController.ts @@ -1,6 +1,6 @@ import { User } from '@entities/user.entity'; import { Response } from 'express'; -import { CreatePagePermissionDto, CreateQueryPermissionDto } from '../dto'; +import { CreatePermissionDto } from '../dto'; export interface IAppPermissionsController { fetchUsers(user: User, appId: string, response: Response): Promise; @@ -13,7 +13,7 @@ export interface IAppPermissionsController { user: User, appId: string, pageId: string, - body: CreatePagePermissionDto, + body: CreatePermissionDto, response: Response ): Promise; @@ -21,7 +21,7 @@ export interface IAppPermissionsController { user: User, appId: string, pageId: string, - body: CreatePagePermissionDto, + body: CreatePermissionDto, response: Response ): Promise; @@ -33,7 +33,7 @@ export interface IAppPermissionsController { user: User, appId: string, queryId: string, - body: CreateQueryPermissionDto, + body: CreatePermissionDto, response: Response ): Promise; @@ -41,7 +41,7 @@ export interface IAppPermissionsController { user: User, appId: string, queryId: string, - body: CreateQueryPermissionDto, + body: CreatePermissionDto, response: Response ): Promise; diff --git a/server/src/modules/app-permissions/interfaces/IService.ts b/server/src/modules/app-permissions/interfaces/IService.ts index 58a64dfc68..aa1892da43 100644 --- a/server/src/modules/app-permissions/interfaces/IService.ts +++ b/server/src/modules/app-permissions/interfaces/IService.ts @@ -1,5 +1,5 @@ import { User } from '@entities/user.entity'; -import { CreatePagePermissionDto } from '../dto'; +import { CreatePermissionDto } from '../dto'; import { PERMISSION_ENTITY_TYPE } from '../constants'; export interface IAppPermissionsService { @@ -7,17 +7,17 @@ export interface IAppPermissionsService { fetchUserGroups(appId: string, user: User): Promise; - fetchAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string): Promise; + fetchAppPermissions(type: PERMISSION_ENTITY_TYPE, id: string): Promise; - createAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string, body: CreatePagePermissionDto): Promise; + createAppPermissions(type: PERMISSION_ENTITY_TYPE, id: string, body: CreatePermissionDto): Promise; updateAppPermissions( type: PERMISSION_ENTITY_TYPE, appId: string, - pageId: string, - body: CreatePagePermissionDto, + id: string, + body: CreatePermissionDto, user: User ): Promise; - deleteAppPermissions(type: PERMISSION_ENTITY_TYPE, pageId: string): Promise; + deleteAppPermissions(type: PERMISSION_ENTITY_TYPE, id: string): Promise; } diff --git a/server/src/modules/app-permissions/interfaces/IUtilService.ts b/server/src/modules/app-permissions/interfaces/IUtilService.ts index 06654ed9e9..ea86fc05fe 100644 --- a/server/src/modules/app-permissions/interfaces/IUtilService.ts +++ b/server/src/modules/app-permissions/interfaces/IUtilService.ts @@ -1,13 +1,13 @@ import { User } from '@entities/user.entity'; import { GroupPermissions } from '@entities/group_permissions.entity'; -import { CreatePagePermissionDto } from '../dto'; +import { CreatePermissionDto } from '../dto'; export interface IUtilService { getUsersWithViewAccess(appId: string, organizationId: string): Promise; getUserGroupsWithViewAccess(appId: string, organizationId: string): Promise; - createPagePermission(pageId: string, body: CreatePagePermissionDto): Promise; + createPagePermission(pageId: string, body: CreatePermissionDto): Promise; - updatePagePermission(pageId: string, body: CreatePagePermissionDto): Promise; + updatePagePermission(pageId: string, body: CreatePermissionDto): Promise; } diff --git a/server/src/modules/app-permissions/service.ts b/server/src/modules/app-permissions/service.ts index 637e144c8e..0aed033079 100644 --- a/server/src/modules/app-permissions/service.ts +++ b/server/src/modules/app-permissions/service.ts @@ -13,19 +13,19 @@ export class AppPermissionsService implements IAppPermissionsService { throw new Error('Method not implemented.'); } - async fetchAppPermissions(type, pageId) { + async fetchAppPermissions(type, id) { throw new Error('Method not implemented.'); } - async createAppPermissions(type, pageId, body) { + async createAppPermissions(type, id, body) { throw new Error('Method not implemented.'); } - async updateAppPermissions(type, appId, pageId, body, user) { + async updateAppPermissions(type, appId, id, body, user) { throw new Error('Method not implemented.'); } - async deleteAppPermissions(type, pageId) { + async deleteAppPermissions(type, id) { throw new Error('Method not implemented.'); } } diff --git a/server/src/modules/app-permissions/util.service.ts b/server/src/modules/app-permissions/util.service.ts index 71432a0e4b..327410e304 100644 --- a/server/src/modules/app-permissions/util.service.ts +++ b/server/src/modules/app-permissions/util.service.ts @@ -2,7 +2,7 @@ import { User } from '@entities/user.entity'; import { IUtilService } from './interfaces/IUtilService'; import { Injectable } from '@nestjs/common'; import { GroupPermissions } from '@entities/group_permissions.entity'; -import { CreatePagePermissionDto } from './dto'; +import { CreatePermissionDto } from './dto'; @Injectable() export class AppPermissionsUtilService implements IUtilService { @@ -16,11 +16,11 @@ export class AppPermissionsUtilService implements IUtilService { throw new Error('Method not implemented.'); } - async createPagePermission(pageId: string, body: CreatePagePermissionDto): Promise { + async createPagePermission(pageId: string, body: CreatePermissionDto): Promise { throw new Error('Method not implemented.'); } - async updatePagePermission(pageId: string, body: CreatePagePermissionDto): Promise { + async updatePagePermission(pageId: string, body: CreatePermissionDto): Promise { throw new Error('Method not implemented.'); } } From f3fa111ad4abd74f6d5bf55f048f44823af58552 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 21 May 2025 21:21:19 +0530 Subject: [PATCH 07/35] Implemented business logic for Query Permissions API --- server/ee | 2 +- .../interfaces/IUtilService.ts | 4 ++ server/src/modules/app-permissions/module.ts | 10 +++- .../query-permissions.repository.ts | 58 +++++++++++++++++++ .../repositories/query-users.repository.ts | 45 ++++++++++++++ .../modules/app-permissions/util.service.ts | 8 +++ 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 server/src/modules/app-permissions/repositories/query-permissions.repository.ts create mode 100644 server/src/modules/app-permissions/repositories/query-users.repository.ts diff --git a/server/ee b/server/ee index f61295c5f8..c4ffed54bc 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit f61295c5f85c354048f15275d4b3af52019d95b3 +Subproject commit c4ffed54bc928820c832c831aab5d2a42cfba154 diff --git a/server/src/modules/app-permissions/interfaces/IUtilService.ts b/server/src/modules/app-permissions/interfaces/IUtilService.ts index ea86fc05fe..dbd390982a 100644 --- a/server/src/modules/app-permissions/interfaces/IUtilService.ts +++ b/server/src/modules/app-permissions/interfaces/IUtilService.ts @@ -10,4 +10,8 @@ export interface IUtilService { createPagePermission(pageId: string, body: CreatePermissionDto): Promise; updatePagePermission(pageId: string, body: CreatePermissionDto): Promise; + + createQueryPermission(queryId: string, body: CreatePermissionDto): Promise; + + updateQueryPermission(queryId: string, body: CreatePermissionDto): Promise; } diff --git a/server/src/modules/app-permissions/module.ts b/server/src/modules/app-permissions/module.ts index 704c6c1374..5e3e3db107 100644 --- a/server/src/modules/app-permissions/module.ts +++ b/server/src/modules/app-permissions/module.ts @@ -7,8 +7,12 @@ import { User } from '@entities/user.entity'; import { RolesRepository } from '@modules/roles/repository'; 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 { 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'; export class AppPermissionsModule { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { @@ -19,7 +23,9 @@ export class AppPermissionsModule { return { module: AppPermissionsModule, - imports: [TypeOrmModule.forFeature([GroupPermissions, User, PageUser, PagePermission])], + imports: [ + TypeOrmModule.forFeature([GroupPermissions, User, PageUser, PagePermission, QueryUser, QueryPermission]), + ], controllers: [AppPermissionsController], providers: [ AppPermissionsService, @@ -27,6 +33,8 @@ export class AppPermissionsModule { RolesRepository, PageUsersRepository, PagePermissionsRepository, + QueryUsersRepository, + QueryPermissionsRepository, FeatureAbilityFactory, ], exports: [AppPermissionsUtilService, AppPermissionsService], diff --git a/server/src/modules/app-permissions/repositories/query-permissions.repository.ts b/server/src/modules/app-permissions/repositories/query-permissions.repository.ts new file mode 100644 index 0000000000..d83976ca68 --- /dev/null +++ b/server/src/modules/app-permissions/repositories/query-permissions.repository.ts @@ -0,0 +1,58 @@ +import { QueryPermission } from '@entities/query_permissions.entity'; +import { Injectable } from '@nestjs/common'; +import { DataSource, EntityManager, Repository } from 'typeorm'; +import { QueryUsersRepository } from './query-users.repository'; +import { dbTransactionWrap } from '@helpers/database.helper'; +import { PAGE_PERMISSION_TYPE } from '../constants'; + +@Injectable() +export class QueryPermissionsRepository extends Repository { + constructor(private dataSource: DataSource, private readonly queryUsersRepository: QueryUsersRepository) { + super(QueryPermission, dataSource.createEntityManager()); + } + + async getQueryPermissions(queryId: string, manager?: EntityManager): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const queryPermissions = await manager.find(QueryPermission, { + where: { queryId }, + relations: ['users', 'users.user', 'users.permissionGroup'], + }); + + return queryPermissions.map((permission) => { + if (permission.type === PAGE_PERMISSION_TYPE.GROUP) { + return { + ...permission, + groups: permission.users, + users: undefined, + }; + } + return permission; + }); + }, manager || this.manager); + } + + async createQueryPermissions( + queryId: string, + type: PAGE_PERMISSION_TYPE, + manager?: EntityManager + ): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const existingPermission = await manager.findOne(QueryPermission, { where: { queryId } }); + if (existingPermission) { + throw new Error(`Query permission already exists for Query id: ${queryId}`); + } + + const queryPermission = manager.create(QueryPermission, { + queryId, + type, + }); + return manager.save(queryPermission); + }, manager || this.manager); + } + + async deleteQueryPermissions(queryId: string, manager?: EntityManager): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + await manager.delete(QueryPermission, { queryId }); + }, manager || this.manager); + } +} diff --git a/server/src/modules/app-permissions/repositories/query-users.repository.ts b/server/src/modules/app-permissions/repositories/query-users.repository.ts new file mode 100644 index 0000000000..bd1521afae --- /dev/null +++ b/server/src/modules/app-permissions/repositories/query-users.repository.ts @@ -0,0 +1,45 @@ +import { QueryUser } from '@entities/query_users.entity'; +import { Injectable } from '@nestjs/common'; +import { DataSource, EntityManager, Repository } from 'typeorm'; +import { dbTransactionWrap } from '@helpers/database.helper'; + +@Injectable() +export class QueryUsersRepository extends Repository { + constructor(private dataSource: DataSource) { + super(QueryUser, dataSource.createEntityManager()); + } + + async createQueryUsersWithSingle( + queryPermissionsId: string, + users: string[], + manager?: EntityManager + ): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const queryUsers = users.map((userId) => { + return manager.create(QueryUser, { + queryPermissionsId, + userId, + permissionGroupsId: null, + }); + }); + return manager.save(queryUsers); + }, manager || this.manager); + } + + async createQueryUsersWithGroup( + queryPermissionsId: string, + groups: string[], + manager?: EntityManager + ): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const queryUsers = groups.map((permissionGroupsId) => { + return manager.create(QueryUser, { + queryPermissionsId, + permissionGroupsId, + userId: null, + }); + }); + return manager.save(queryUsers); + }, manager || this.manager); + } +} diff --git a/server/src/modules/app-permissions/util.service.ts b/server/src/modules/app-permissions/util.service.ts index 327410e304..7ff894a058 100644 --- a/server/src/modules/app-permissions/util.service.ts +++ b/server/src/modules/app-permissions/util.service.ts @@ -23,4 +23,12 @@ export class AppPermissionsUtilService implements IUtilService { async updatePagePermission(pageId: string, body: CreatePermissionDto): Promise { throw new Error('Method not implemented.'); } + + async createQueryPermission(queryId: string, body: CreatePermissionDto): Promise { + throw new Error('Method not implemented.'); + } + + async updateQueryPermission(queryId: string, body: CreatePermissionDto): Promise { + throw new Error('Method not implemented.'); + } } From 59cac14f4dea487a9ea8285a9247cf6addbc387d Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 22 May 2025 17:54:04 +0530 Subject: [PATCH 08/35] Added filter logic for released apps --- server/ee | 2 +- .../repositories/query-users.repository.ts | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/server/ee b/server/ee index c4ffed54bc..e9288fca3a 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit c4ffed54bc928820c832c831aab5d2a42cfba154 +Subproject commit e9288fca3afc0d9064300e30dc5279b3c851be82 diff --git a/server/src/modules/app-permissions/repositories/query-users.repository.ts b/server/src/modules/app-permissions/repositories/query-users.repository.ts index bd1521afae..b5a2cc08fa 100644 --- a/server/src/modules/app-permissions/repositories/query-users.repository.ts +++ b/server/src/modules/app-permissions/repositories/query-users.repository.ts @@ -2,6 +2,7 @@ import { QueryUser } from '@entities/query_users.entity'; import { Injectable } from '@nestjs/common'; import { DataSource, EntityManager, Repository } from 'typeorm'; import { dbTransactionWrap } from '@helpers/database.helper'; +import { QueryPermission } from '@entities/query_permissions.entity'; @Injectable() export class QueryUsersRepository extends Repository { @@ -42,4 +43,41 @@ export class QueryUsersRepository extends Repository { return manager.save(queryUsers); }, manager || this.manager); } + + async checkQueryUserWithGroup( + queryPermission: QueryPermission, + userId: string, + manager?: EntityManager + ): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const result = await manager + .createQueryBuilder(QueryUser, 'query_users') + .innerJoin('query_users.permissionGroup', 'group') + .innerJoin('group.groupUsers', 'groupUser') + .where('query_users.queryPermission = :permissionId', { + permissionId: queryPermission.id, + }) + .andWhere('groupUser.userId = :userId', { userId }) + .getOne(); + + return !!result; + }, manager || this.manager); + } + + async checkQueryUserWithSingle( + queryPermission: QueryPermission, + userId: string, + manager?: EntityManager + ): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const queryUser = await manager.findOne(QueryUser, { + where: { + queryPermission: { id: queryPermission.id }, + userId, + }, + }); + + return !!queryUser; + }, manager || this.manager); + } } From 1d6d807720300dd65056638adc78069c3f840487 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 23 May 2025 03:08:23 +0530 Subject: [PATCH 09/35] Added filter logic for preview apps --- frontend/src/AppBuilder/_hooks/useAppData.js | 4 ++-- frontend/src/_services/dataquery.service.js | 4 ++-- server/ee | 2 +- server/src/modules/data-queries/controller.ts | 6 +++--- .../src/modules/data-queries/interfaces/IController.ts | 2 +- server/src/modules/data-queries/interfaces/IService.ts | 2 +- server/src/modules/data-queries/module.ts | 9 ++++++++- server/src/modules/data-queries/service.ts | 2 +- 8 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 433a677dc7..7b74639870 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -374,7 +374,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v const queryData = isPublicAccess || (mode !== 'edit' && appData.is_public) ? appData - : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id); + : await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id, mode); const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries; dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); setQueries(dataQueries); @@ -547,7 +547,7 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v setSecrets(orgSecrets); } - const queryData = await dataqueryService.getAll(currentVersionId); + const queryData = await dataqueryService.getAll(currentVersionId, mode); const dataQueries = queryData.data_queries; dataQueries.forEach((query) => normalizeQueryTransformationOptions(query)); setQueries(dataQueries); diff --git a/frontend/src/_services/dataquery.service.js b/frontend/src/_services/dataquery.service.js index 3919a6b0f3..36de55b907 100644 --- a/frontend/src/_services/dataquery.service.js +++ b/frontend/src/_services/dataquery.service.js @@ -13,9 +13,9 @@ export const dataqueryService = { bulkUpdateQueryOptions, }; -function getAll(appVersionId) { +function getAll(appVersionId, mode) { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; - return fetch(`${config.apiUrl}/data-queries/${appVersionId}`, requestOptions).then(handleResponse); + return fetch(`${config.apiUrl}/data-queries/${appVersionId}?mode=${mode}`, requestOptions).then(handleResponse); } function create(app_id, app_version_id, name, kind, options, data_source_id, plugin_id) { diff --git a/server/ee b/server/ee index e9288fca3a..4f421094fe 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e9288fca3afc0d9064300e30dc5279b3c851be82 +Subproject commit 4f421094fe70a51b964d5d664029e08c9eccb5be diff --git a/server/src/modules/data-queries/controller.ts b/server/src/modules/data-queries/controller.ts index 831834ef8a..21f8df688f 100644 --- a/server/src/modules/data-queries/controller.ts +++ b/server/src/modules/data-queries/controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param, Body, Post, Patch, Delete, UseGuards, Put, Res } from '@nestjs/common'; +import { Controller, Get, Param, Body, Post, Patch, Delete, UseGuards, Put, Res, Query } from '@nestjs/common'; import { JwtAuthGuard } from '@modules/session/guards/jwt-auth.guard'; import { DataQueriesService } from './service'; import { User, UserEntity } from '@modules/app/decorators/user.decorator'; @@ -30,8 +30,8 @@ export class DataQueriesController implements IDataQueriesController { @InitFeature(FEATURE_KEY.GET) @UseGuards(JwtAuthGuard, ValidateAppVersionGuard, ValidateQueryAppGuard, AppFeatureAbilityGuard) @Get(':versionId') - index(@Param('versionId') versionId: string) { - return this.dataQueriesService.getAll(versionId); + index(@User() user: UserEntity, @Param('versionId') versionId: string, @Query('mode') mode?: string) { + return this.dataQueriesService.getAll(user, versionId, mode); } @InitFeature(FEATURE_KEY.CREATE) diff --git a/server/src/modules/data-queries/interfaces/IController.ts b/server/src/modules/data-queries/interfaces/IController.ts index d68f28b382..93024bca3e 100644 --- a/server/src/modules/data-queries/interfaces/IController.ts +++ b/server/src/modules/data-queries/interfaces/IController.ts @@ -7,7 +7,7 @@ import { App } from '@entities/app.entity'; import { UpdateSourceDto } from '../dto'; import { Response } from 'express'; export interface IDataQueriesController { - index(versionId: string): Promise; + index(user: UserEntity, versionId: string, mode?: string): Promise; create( user: UserEntity, diff --git a/server/src/modules/data-queries/interfaces/IService.ts b/server/src/modules/data-queries/interfaces/IService.ts index 14848e6a45..a51ce642d2 100644 --- a/server/src/modules/data-queries/interfaces/IService.ts +++ b/server/src/modules/data-queries/interfaces/IService.ts @@ -5,7 +5,7 @@ import { CreateDataQueryDto, IUpdatingReferencesOptions, UpdateDataQueryDto } fr import { DataQuery } from '@entities/data_query.entity'; export interface IDataQueriesService { - getAll(versionId: string): Promise<{ data_queries: object[] }>; + getAll(user: User, versionId: string, mode?: string): Promise<{ data_queries: object[] }>; create(user: User, dataSource: DataSource, dataQueryDto: CreateDataQueryDto): Promise; diff --git a/server/src/modules/data-queries/module.ts b/server/src/modules/data-queries/module.ts index 5ec6b2c1a8..11f15d3994 100644 --- a/server/src/modules/data-queries/module.ts +++ b/server/src/modules/data-queries/module.ts @@ -9,6 +9,8 @@ import { FeatureAbilityFactory as AppFeatureAbilityFactory } from './ability/app import { FeatureAbilityFactory as DataSourceFeatureAbilityFactory } from './ability/data-source'; import { AppsRepository } from '@modules/apps/repository'; import { OrganizationRepository } from '@modules/organizations/repository'; +import { LicenseModule } from '@modules/licensing/module'; +import { AppPermissionsModule } from '@modules/app-permissions/module'; export class DataQueriesModule { static async register(configs?: { IS_GET_CONTEXT: boolean }): Promise { @@ -19,7 +21,12 @@ export class DataQueriesModule { return { module: DataQueriesModule, - imports: [await AppEnvironmentsModule.register(configs), await DataSourcesModule.register(configs)], + imports: [ + await AppEnvironmentsModule.register(configs), + await DataSourcesModule.register(configs), + await LicenseModule.forRoot(configs), + await AppPermissionsModule.register(configs), + ], providers: [ DataQueryRepository, VersionRepository, diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index 5337c6739a..64bdd66c68 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -24,7 +24,7 @@ export class DataQueriesService implements IDataQueriesService { protected readonly dataSourceRepository: DataSourcesRepository ) { } - async getAll(versionId: string) { + async getAll(user: User, versionId: string, mode?: string) { const queries = await this.dataQueryRepository.getAll(versionId); const serializedQueries = []; From 8ea84bfacd0ab48de78da7221301be91fabb66f9 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Fri, 23 May 2025 03:14:24 +0530 Subject: [PATCH 10/35] Fix: app permissions in public released apps --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 4f421094fe..1b52eeeff5 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 4f421094fe70a51b964d5d664029e08c9eccb5be +Subproject commit 1b52eeeff52a99ee910431b5644859f2622439b7 From 4beaaaef91fb906075d6a2d1205bfdcbc3da0bba Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Sat, 24 May 2025 20:55:50 +0530 Subject: [PATCH 11/35] Added check for running query in the backend --- frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js | 4 +++- frontend/src/_services/dataquery.service.js | 4 ++-- server/ee | 2 +- server/src/modules/data-queries/controller.ts | 6 ++++-- server/src/modules/data-queries/interfaces/IController.ts | 3 ++- server/src/modules/data-queries/interfaces/IService.ts | 3 ++- server/src/modules/data-queries/service.ts | 5 +++-- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index ddb67a5445..cd240a0c6d 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -215,6 +215,7 @@ export const createQueryPanelSlice = (set, get) => ({ selectedEnvironment, isPublicAccess, currentVersionId, + currentMode, } = get(); const { queryPreviewData, @@ -352,7 +353,8 @@ export const createQueryPanelSlice = (set, get) => ({ options, query?.options, currentVersionId, - !isPublicAccess ? (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id : undefined //TODO: currentAppEnvironmentId may no longer required. Need to check + !isPublicAccess ? (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id : undefined, //TODO: currentAppEnvironmentId may no longer required. Need to check + currentMode ); } diff --git a/frontend/src/_services/dataquery.service.js b/frontend/src/_services/dataquery.service.js index 36de55b907..3dc5e26593 100644 --- a/frontend/src/_services/dataquery.service.js +++ b/frontend/src/_services/dataquery.service.js @@ -72,7 +72,7 @@ function del(id, versionId) { return fetch(`${config.apiUrl}/data-queries/${id}/versions/${versionId}`, requestOptions).then(handleResponse); } -function run(queryId, resolvedOptions, options, versionId, environmentId) { +function run(queryId, resolvedOptions, options, versionId, environmentId, mode) { const body = { resolvedOptions: resolvedOptions, options: options, @@ -80,7 +80,7 @@ function run(queryId, resolvedOptions, options, versionId, environmentId) { let url = `${config.apiUrl}/data-queries/${queryId}/versions/${versionId}/run${ environmentId && environmentId !== 'undefined' ? `/${environmentId}` : '' - }`; + }?mode=${mode}`; //For public/released apps if (!environmentId || !versionId) { diff --git a/server/ee b/server/ee index 1b52eeeff5..648a1c9e79 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1b52eeeff52a99ee910431b5644859f2622439b7 +Subproject commit 648a1c9e7959d7e47576bfc74645ac96200940af diff --git a/server/src/modules/data-queries/controller.ts b/server/src/modules/data-queries/controller.ts index 21f8df688f..e734810703 100644 --- a/server/src/modules/data-queries/controller.ts +++ b/server/src/modules/data-queries/controller.ts @@ -113,7 +113,8 @@ export class DataQueriesController implements IDataQueriesController { @Body() updateDataQueryDto: UpdateDataQueryDto, @Ability() ability: AppAbility, @DataSource() dataSource: DataSourceEntity, - @Res({ passthrough: true }) response: Response + @Res({ passthrough: true }) response: Response, + @Query('mode') mode?: string ) { return this.dataQueriesService.runQueryOnBuilder( user, @@ -122,7 +123,8 @@ export class DataQueriesController implements IDataQueriesController { updateDataQueryDto, ability, dataSource, - response + response, + mode ); } diff --git a/server/src/modules/data-queries/interfaces/IController.ts b/server/src/modules/data-queries/interfaces/IController.ts index 93024bca3e..59292772e2 100644 --- a/server/src/modules/data-queries/interfaces/IController.ts +++ b/server/src/modules/data-queries/interfaces/IController.ts @@ -36,7 +36,8 @@ export interface IDataQueriesController { updateDataQueryDto: UpdateDataQueryDto, ability: AppAbility, dataSource: DataSourceEntity, - response: Response + response: Response, + mode?: string ): Promise; runQuery( diff --git a/server/src/modules/data-queries/interfaces/IService.ts b/server/src/modules/data-queries/interfaces/IService.ts index a51ce642d2..08127d22ce 100644 --- a/server/src/modules/data-queries/interfaces/IService.ts +++ b/server/src/modules/data-queries/interfaces/IService.ts @@ -22,7 +22,8 @@ export interface IDataQueriesService { updateDataQueryDto: UpdateDataQueryDto, ability: object, dataSource: DataSource, - response: Response + response: Response, + mode?: string ): Promise; runQueryForApp( diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index 64bdd66c68..e8d5003a6d 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -127,7 +127,8 @@ export class DataQueriesService implements IDataQueriesService { updateDataQueryDto: UpdateDataQueryDto, ability: AppAbility, dataSource: DataSource, - response: Response + response: Response, + mode?: string ) { const { options, resolvedOptions } = updateDataQueryDto; @@ -153,7 +154,7 @@ export class DataQueriesService implements IDataQueriesService { return this.runAndGetResult(user, dataQuery, options, response, environmentId); } - private async runAndGetResult( + protected async runAndGetResult( user: User, dataQuery: DataQuery, resolvedOptions: object, From 4b0d0184de63e8dfe7cbf844cf08356e1b537767 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 26 May 2025 09:01:42 +0530 Subject: [PATCH 12/35] Implemented versioning logic for query permissions --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 648a1c9e79..f26bc4fc5e 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 648a1c9e7959d7e47576bfc74645ac96200940af +Subproject commit f26bc4fc5e9fb9e0986a59b7cca7b84f7950cc08 From a0eb1956e2c05be0ba6ff041569f99dc5fcc96bf Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 26 May 2025 12:00:28 +0530 Subject: [PATCH 13/35] Implemented import-export logic --- server/src/dto/import-resources.dto.ts | 2 +- server/src/helpers/error_type.constant.ts | 2 +- .../services/app-import-export.service.ts | 76 ++++++++++++++++++- .../import-export-resources/service.ts | 8 +- 4 files changed, 81 insertions(+), 7 deletions(-) diff --git a/server/src/dto/import-resources.dto.ts b/server/src/dto/import-resources.dto.ts index 89b3ee182c..3ad6d2cee0 100644 --- a/server/src/dto/import-resources.dto.ts +++ b/server/src/dto/import-resources.dto.ts @@ -31,7 +31,7 @@ export class ImportResourcesDto { @IsOptional() @IsBoolean() - skip_page_permissions_group_check?: boolean; + skip_permissions_group_check?: boolean; } export class ImportAppDto { diff --git a/server/src/helpers/error_type.constant.ts b/server/src/helpers/error_type.constant.ts index cb483e8896..e34ce5b69d 100644 --- a/server/src/helpers/error_type.constant.ts +++ b/server/src/helpers/error_type.constant.ts @@ -1,7 +1,7 @@ export const APP_ERROR_TYPE = { IMPORT_EXPORT_SERVICE: { UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported', - PAGE_PERMISSION_GROUP_ERROR: 'Following groups are missing from the workspace', + PERMISSION_GROUP_ERROR: 'Following groups are missing from the workspace', PERMISSION_CHECK: 'permission-check', }, }; diff --git a/server/src/modules/apps/services/app-import-export.service.ts b/server/src/modules/apps/services/app-import-export.service.ts index 5d6da2ea7d..6522690675 100644 --- a/server/src/modules/apps/services/app-import-export.service.ts +++ b/server/src/modules/apps/services/app-import-export.service.ts @@ -39,6 +39,8 @@ import { PAGE_PERMISSION_TYPE } from '@modules/app-permissions/constants'; import { PagePermission } from '@entities/page_permissions.entity'; 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'; interface AppResourceMappings { defaultDataSourceIdMapping: Record; dataQueryMapping: Record; @@ -164,6 +166,9 @@ export class AppImportExportService { if (dataSources?.length) { dataQueries = await manager .createQueryBuilder(DataQuery, 'data_queries') + .leftJoinAndSelect('data_queries.permissions', 'permission') + .leftJoinAndSelect('permission.users', 'queryUser') + .leftJoinAndSelect('queryUser.permissionGroup', 'permissionGroup') .where('data_queries.dataSourceId IN(:...dataSourceId)', { dataSourceId: dataSources?.map((v) => v.id), }) @@ -216,6 +221,21 @@ export class AppImportExportService { }; }); + const queriesWithPermissionGroups = dataQueries.map((query) => { + const groupPermission = query.permissions.find((perm) => perm.type === 'GROUP'); + + return { + ...query, + permissions: groupPermission + ? { + permissionGroup: groupPermission.users + .map((user) => user.permissionGroup?.name) + .filter((name): name is string => Boolean(name)), + } + : undefined, + }; + }); + const components = pages.length > 0 ? await manager @@ -239,7 +259,7 @@ export class AppImportExportService { appToExport['components'] = components; appToExport['pages'] = pagesWithPermissionGroups; appToExport['events'] = events; - appToExport['dataQueries'] = dataQueries; + appToExport['dataQueries'] = queriesWithPermissionGroups; appToExport['dataSources'] = dataSources; appToExport['appVersions'] = appVersions; appToExport['appEnvironments'] = appEnvironments; @@ -1116,7 +1136,15 @@ export class AppImportExportService { }); await manager.save(newQuery); + + if (importingQuery.permissions) { + newQuery.permissions = importingQuery.permissions; + } + appResourceMappings.dataQueryMapping[importingQuery.id] = newQuery.id; + + //create query permissions of query if flag enabled in dto + await this.createQueryPermissionsForGroups(newQuery, organizationId, manager); } return appResourceMappings; @@ -1358,7 +1386,7 @@ export class AppImportExportService { return pageSettings; } - async checkIfGroupPermissionsExist(pages, organizationId) { + async checkIfGroupPermissionsExist(pages, queries, organizationId) { const allGroupNames = new Set(); for (const page of pages) { @@ -1368,6 +1396,15 @@ export class AppImportExportService { } } + for (const query of queries) { + const groupNames = query.permissions?.permissionGroup || []; + for (const name of groupNames) { + if (!allGroupNames.has(name)) { + allGroupNames.add(name); + } + } + } + if (!allGroupNames.size) return; return await dbTransactionWrap(async (manager: EntityManager) => { @@ -1428,6 +1465,41 @@ export class AppImportExportService { await manager.save(pageUsers); } + async createQueryPermissionsForGroups(query, organizationId: string, manager: EntityManager) { + const groupNames = query.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(QueryPermission, { + queryId: query.id, + type: PAGE_PERMISSION_TYPE.GROUP, + }); + + const savedPermission = await manager.save(permission); + + const queryUsers = validGroupNames.map((name) => + manager.create(QueryUser, { + queryPermissionsId: savedPermission.id, + permissionGroupsId: groupMap.get(name).id, + }) + ); + + await manager.save(queryUsers); + } + async createAppVersionsForImportedApp( manager: EntityManager, user: User, diff --git a/server/src/modules/import-export-resources/service.ts b/server/src/modules/import-export-resources/service.ts index e47205258e..7b50ca6076 100644 --- a/server/src/modules/import-export-resources/service.ts +++ b/server/src/modules/import-export-resources/service.ts @@ -69,15 +69,17 @@ export class ImportExportResourcesService { let tableNameMapping = {}; const imports = { app: [], tooljet_database: [], tableNameMapping: {} }; const importingVersion = importResourcesDto.tooljet_version; - const skipPagePermissionsGroupCheck = importResourcesDto.skip_page_permissions_group_check; + const skipPermissionsGroupCheck = importResourcesDto.skip_permissions_group_check; - if (!isEmpty(importResourcesDto.app) && !skipPagePermissionsGroupCheck) { + if (!isEmpty(importResourcesDto.app) && !skipPermissionsGroupCheck) { for (const appImportDto of importResourcesDto.app) { let appParams = appImportDto.definition; if (appParams?.appV2) { appParams = { ...appParams.appV2 }; const pages = appParams?.pages; - pages?.length && (await this.appImportExportService.checkIfGroupPermissionsExist(pages, user.organizationId)); + const queries = appParams?.dataQueries; + (pages?.length || queries?.length) && + (await this.appImportExportService.checkIfGroupPermissionsExist(pages, queries, user.organizationId)); } } } From dc0d46cd7b52630e23c21cd39eeeb4ec6c0a1b80 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 26 May 2025 17:14:50 +0530 Subject: [PATCH 14/35] Create service functions for query permissions --- .../src/_services/appPermission.service.js | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/frontend/src/_services/appPermission.service.js b/frontend/src/_services/appPermission.service.js index 85cb6ee004..fd2438f058 100644 --- a/frontend/src/_services/appPermission.service.js +++ b/frontend/src/_services/appPermission.service.js @@ -7,6 +7,10 @@ export const appPermissionService = { createPagePermission, updatePagePermission, deletePagePermission, + getQueryPermission, + createQueryPermission, + updateQueryPermission, + deleteQueryPermission, }; function getPagePermission(appId, pageId) { @@ -47,3 +51,41 @@ function deletePagePermission(appId, pageId) { }; return fetch(`${config.apiUrl}/app-permissions/${appId}/pages/${pageId}`, requestOptions).then(handleResponse); } + +function getQueryPermission(appId, queryId) { + const requestOptions = { + method: 'GET', + headers: authHeader(), + credentials: 'include', + }; + return fetch(`${config.apiUrl}/app-permissions/${appId}/queries/${queryId}`, requestOptions).then(handleResponse); +} + +function createQueryPermission(appId, queryId, body) { + const requestOptions = { + method: 'POST', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(body), + }; + return fetch(`${config.apiUrl}/app-permissions/${appId}/queries/${queryId}`, requestOptions).then(handleResponse); +} + +function updateQueryPermission(appId, queryId, body) { + const requestOptions = { + method: 'PUT', + headers: authHeader(), + credentials: 'include', + body: JSON.stringify(body), + }; + return fetch(`${config.apiUrl}/app-permissions/${appId}/queries/${queryId}`, requestOptions).then(handleResponse); +} + +function deleteQueryPermission(appId, queryId) { + const requestOptions = { + method: 'DELETE', + headers: authHeader(), + credentials: 'include', + }; + return fetch(`${config.apiUrl}/app-permissions/${appId}/queries/${queryId}`, requestOptions).then(handleResponse); +} From 3bd6bc5bebaccd67cb8c309c855a756eada308ac Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 27 May 2025 16:03:52 +0530 Subject: [PATCH 15/35] Extracted page permission modal as a reusable component --- frontend/ee | 2 +- .../LeftSidebar/PageMenu/PageMenu.jsx | 23 +- .../LeftSidebar/PageMenu/PagePermission.jsx | 505 ------------------ .../LeftSidebar/PageMenu/style.scss | 112 ---- .../src/AppBuilder/_stores/slices/appSlice.js | 12 + .../_stores/slices/pageMenuSlice.js | 18 - frontend/src/HomePage/HomePage.jsx | 4 +- .../AppPermissionsModal.jsx | 8 + .../components/AppPermissionsModal/index.js | 1 + .../modules/Appbuilder/components/index.js | 11 +- 10 files changed, 55 insertions(+), 641 deletions(-) delete mode 100644 frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx create mode 100644 frontend/src/modules/Appbuilder/components/AppPermissionsModal/AppPermissionsModal.jsx create mode 100644 frontend/src/modules/Appbuilder/components/AppPermissionsModal/index.js diff --git a/frontend/ee b/frontend/ee index 777446d71e..5c2787b498 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 777446d71e78e5941d34353606a12d982820438f +Subproject commit 5c2787b498202f4356b4598fb961ddbab04acbbf diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx index d4b8573c7d..d55864e91c 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx @@ -12,11 +12,12 @@ import './style.scss'; import { SortableTree } from './Tree/SortableTree'; import { PageGroupMenu } from './AddPageButton'; import { PageHandlerMenu } from './PageHandlerMenu.jsx'; +import AppPermissionsModal from '@/modules/Appbuilder/components/AppPermissionsModal'; import { EditModal } from './EditModal'; import { SettingsModal } from './SettingsModal'; import { DeletePageConfirmationModal } from './DeletePageConfirmationModal'; import SolidIcon from '@/_ui/Icon/SolidIcons'; -import PagePermission from './PagePermission'; +import { appPermissionService } from '@/_services'; export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => { const showAddNewPageInput = useStore((state) => state.showAddNewPageInput); @@ -27,6 +28,11 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => { const shouldFreeze = useStore((state) => state.getShouldFreeze()); const enableReleasedVersionPopupState = useStore((state) => state.enableReleasedVersionPopupState); const closePageEditPopover = useStore((state) => state.closePageEditPopover); + const editingPageId = useStore((state) => state.editingPage?.id); + const showPagePermissionModal = useStore((state) => state.showPagePermissionModal); + const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal); + const updatePageWithPermissions = useStore((state) => state.updatePageWithPermissions); + useEffect(() => { return () => { closePageEditPopover(); @@ -95,7 +101,20 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => { >
- {isLicensed ? : <>} + {isLicensed && ( + appPermissionService.getPagePermission(appId, id)} + createPermission={(id, appId, body) => appPermissionService.createPagePermission(appId, id, body)} + updatePermission={(id, appId, body) => appPermissionService.updatePagePermission(appId, id, body)} + deletePermission={(id, appId) => appPermissionService.deletePagePermission(appId, id)} + onSuccess={(data) => updatePageWithPermissions(editingPageId, data)} + /> + )} diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx deleted file mode 100644 index bd7a865a54..0000000000 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PagePermission.jsx +++ /dev/null @@ -1,505 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { components } from 'react-select'; -import ModalBase from '@/_ui/Modal'; -import Select from '@/_ui/Select'; -import SolidIcon from '@/_ui/Icon/SolidIcons'; -import useStore from '@/AppBuilder/_stores/store'; -import { appPermissionService } from '@/_services'; -import { ConfirmDialog } from '@/_components'; -import toast from 'react-hot-toast'; -import Spinner from '@/_ui/Spinner'; - -const PERMISSION_TYPES = { - single: 'SINGLE', - group: 'GROUP', - all: 'ALL', -}; - -export default function PagePermission({ darkMode }) { - const showPagePermissionModal = useStore((state) => state.showPagePermissionModal); - const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal); - const editingPage = useStore((state) => state.editingPage); - const appId = useStore((state) => state.app.appId); - const selectedUserGroups = useStore((state) => state.selectedUserGroups); - const setSelectedUserGroups = useStore((state) => state.setSelectedUserGroups); - const selectedUsers = useStore((state) => state.selectedUsers); - const setSelectedUsers = useStore((state) => state.setSelectedUsers); - const pagePermission = useStore((state) => state.pagePermission); - const setPagePermission = useStore((state) => state.setPagePermission); - const updatePageWithPermissions = useStore((state) => state.updatePageWithPermissions); - - const [pagePermissionType, setPagePermissionType] = useState('all'); - const [showUserGroupSelect, toggleUserGroupSelect] = useState(false); - const [showUsersSelect, toggleUsersSelect] = useState(false); - const [showConfirmDelete, setShowConfirmDelete] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [isPermissionsLoading, setPermissionsLoading] = useState(true); - const [initialSelectedGroups, setInitialSelectedGroups] = useState([]); - const [initialSelectedUsers, setInitialSelectedUsers] = useState([]); - const [initalPagePermissionType, setInitialPagePermissionType] = useState('all'); - - useEffect(() => { - if (!showPagePermissionModal) return; - const fetchPagePermission = () => { - appPermissionService.getPagePermission(appId, editingPage?.id).then((data) => { - if (data) { - if (data[0] && data[0]?.type === PERMISSION_TYPES.group) { - const groups = - data[0]?.groups?.map((user) => ({ - label: user?.permissionGroup?.name, - value: user?.permissionGroup?.id, - count: user?.permissionGroup?.count, - })) ?? []; - setPagePermissionType(data[0]?.type?.toLowerCase()); - setInitialPagePermissionType(data[0]?.type?.toLowerCase()); - setPagePermission(data); - toggleUserGroupSelect(true); - setInitialSelectedGroups(groups); - data?.length && setSelectedUserGroups(groups); - } else if (data[0] && data[0]?.type === PERMISSION_TYPES.single) { - const users = - data[0]?.users?.map(({ user }) => { - const firstName = user.firstName || ''; - const lastName = user.lastName || ''; - return { - value: user.id, - label: `${firstName} ${lastName}`.trim(), - email: user.email, - initials: `${firstName[0] || ''}${lastName[0] || ''}`.toUpperCase(), - }; - }) ?? []; - setPagePermissionType(data[0]?.type?.toLowerCase()); - setInitialPagePermissionType(data[0]?.type?.toLowerCase()); - setPagePermission(data); - toggleUsersSelect(true); - setInitialSelectedUsers(users); - data?.length && setSelectedUsers(users); - } - } - setPermissionsLoading(false); - }); - }; - fetchPagePermission(); - }, [showPagePermissionModal]); - - const isSelectionUnchanged = useMemo(() => { - if (pagePermissionType === 'group') { - if (!selectedUserGroups.length) return true; - const current = selectedUserGroups - .map((g) => g.value) - .sort() - .join(','); - const initial = initialSelectedGroups - .map((g) => g.value) - .sort() - .join(','); - return current === initial; - } else if (pagePermissionType === 'single') { - if (!selectedUsers.length) return true; - const current = selectedUsers - .map((u) => u.value) - .sort() - .join(','); - const initial = initialSelectedUsers - .map((u) => u.value) - .sort() - .join(','); - return current === initial; - } else { - if (!pagePermission?.length) { - return true; - } else { - return initalPagePermissionType == pagePermissionType; - } - } - }, [ - pagePermissionType, - selectedUserGroups, - initialSelectedGroups, - selectedUsers, - initialSelectedUsers, - initalPagePermissionType, - ]); - - const permissionTypeOptions = useMemo( - () => [ - { - label: 'All users with access to the app', - value: 'all', - icon: 'globe', - }, - { - label: 'Users', - value: 'single', - icon: 'user', - }, - { - label: 'User groups', - value: 'group', - icon: 'usergroup', - }, - ], - [] - ); - const handlePermissionTypeChange = (value) => { - switch (value) { - case 'group': { - toggleUserGroupSelect(true); - toggleUsersSelect(false); - setPagePermissionType('group'); - break; - } - case 'single': { - toggleUsersSelect(true); - toggleUserGroupSelect(false); - setPagePermissionType('single'); - break; - } - case 'all': { - toggleUsersSelect(false); - toggleUserGroupSelect(false); - setPagePermissionType('all'); - } - } - }; - - const handlePagePermissionModalClose = () => { - togglePagePermissionModal(false); - toggleUserGroupSelect(false); - toggleUsersSelect(false); - setPagePermissionType('all'); - setPagePermission(null); - setSelectedUsers([]); - setSelectedUserGroups([]); - setInitialSelectedGroups([]); - setInitialSelectedUsers([]); - }; - - const createPagePermission = () => { - const body = { - id: editingPage?.id, - type: PERMISSION_TYPES[pagePermissionType], - ...(pagePermissionType === 'group' - ? { groups: selectedUserGroups.map((group) => group?.value) } - : { users: selectedUsers.map((user) => user?.value) }), - }; - setIsLoading(true); - appPermissionService - .createPagePermission(appId, editingPage?.id, body) - .then((data) => { - toast.success('Permission successfully created!', { - className: 'text-nowrap w-auto mw-100', - }); - updatePageWithPermissions(editingPage?.id, data); - }) - .catch(() => { - toast.error('Permission could not be created. Please try again!', { - className: 'text-nowrap w-auto mw-100', - }); - }) - .finally(() => { - setIsLoading(false); - handlePagePermissionModalClose(); - }); - }; - - const updatePagePermission = () => { - const body = { - id: editingPage?.id, - type: PERMISSION_TYPES[pagePermissionType], - ...(pagePermissionType === 'group' - ? { groups: selectedUserGroups.map((group) => group?.value) } - : { users: selectedUsers.map((user) => user?.value) }), - }; - setIsLoading(true); - appPermissionService - .updatePagePermission(appId, editingPage?.id, body) - .then((data) => { - toast.success('Permission successfully updated!', { - className: 'text-nowrap w-auto mw-100', - }); - updatePageWithPermissions(editingPage?.id, data); - }) - .catch(() => { - toast.error('Permission could not be updated. Please try again!', { - className: 'text-nowrap w-auto mw-100', - }); - }) - .finally(() => { - setIsLoading(false); - handlePagePermissionModalClose(); - }); - }; - - const deletePagePermission = () => { - setIsLoading(true); - appPermissionService - .deletePagePermission(appId, editingPage?.id) - .then((data) => { - toast.success('Permission successfully deleted!', { - className: 'text-nowrap w-auto mw-100', - }); - updatePageWithPermissions(editingPage?.id, []); - }) - .catch(() => { - toast.error('Permission could not be deleted. Please try again!', { - className: 'text-nowrap w-auto mw-100', - }); - setShowConfirmDelete(false); - togglePagePermissionModal(true); - }) - .finally(() => { - setIsLoading(false); - setShowConfirmDelete(false); - }); - }; - - const renderPermissionTypeOptions = ({ label, icon }) => { - return ( -
-
- -
-
- {label} -
-
- ); - }; - - return ( - <> - - Page permission -
- } - handleConfirm={!pagePermission ? createPagePermission : updatePagePermission} - show={showPagePermissionModal} - isLoading={isLoading} - handleClose={handlePagePermissionModalClose} - confirmBtnProps={{ - title: pagePermission - ? 'Save changes' - : pagePermissionType === 'all' - ? 'Default permission' - : 'Create permission', - disabled: isPermissionsLoading || isSelectionUnchanged, - tooltipMessage: '', - leftIcon: pagePermission && 'save', - className: 'action-btn-page-permission', - }} - darkMode={darkMode} - className="page-permissions-modal" - > -
- {isPermissionsLoading ? ( -
- -
- ) : ( - <> -
-
- -
-
-
-

- Only selected users will be allowed to access this page. Read docs to know more. -

-
-
-
- - -
-
{data.label}
-
{data.count} users
-
-
- - ); - }; - - return ( -
- - -
{data.initials}
-
-
{data.label}
-
{data.email}
-
-
- - ); - }; - - const selectStyles = { - option: (base) => ({ - ...base, - padding: '8px 0px', - }), - }; - return ( -
- - { )}
- setShowQueryMenu(false)} - show={showQueryMenu && isQuerySelected} - popperConfig={{ - modifiers: [ - { - name: 'offset', - options: { - offset: [0, 3], - }, - }, - ], - }} - overlay={ - - - {QUERY_MENU_OPTIONS.map((option) => ( - -
{ - e.stopPropagation(); - handleQueryMenuActions(option.value); - }} - > -
{option.icon}
-
- {option?.label} -
- {option.trailingIcon && option.trailingIcon} -
-
- ))} -
-
- } - > - setShowQueryMenu(!showQueryMenu)} - size="small" - variant="outline" - className="" - /> -
+ toggleQueryHandlerMenu(true, `query-handler-menu-${dataQuery?.id}`)} + size="small" + variant="outline" + className="" + id={`query-handler-menu-${dataQuery?.id}`} + />
setShowDeleteConfirmation(false)} + onCancel={() => deleteDataQuery(null)} darkMode={darkMode} /> diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx new file mode 100644 index 0000000000..760adb8075 --- /dev/null +++ b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx @@ -0,0 +1,172 @@ +import React, { useCallback } from 'react'; +import { Overlay, Popover } from 'react-bootstrap'; +import useStore from '@/AppBuilder/_stores/store'; +import classNames from 'classnames'; +import Edit from '@/_ui/Icon/bulkIcons/Edit'; +import Trash from '@/_ui/Icon/solidIcons/Trash'; +import Copy from '@/_ui/Icon/solidIcons/Copy'; +import SolidIcon from '@/_ui/Icon/SolidIcons'; +import { shallow } from 'zustand/shallow'; +import { ToolTip } from '@/_components/ToolTip'; +import { debounce } from 'lodash'; +import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; + +const QueryCardMenu = ({ darkMode }) => { + const appId = useStore((state) => state.app.appId); + const selectedQuery = useStore((state) => state.queryPanel.selectedQuery); + const toggleQueryPermissionModal = useStore((state) => state.queryPanel.toggleQueryPermissionModal); + const featureAccess = useStore((state) => state?.license?.featureAccess, shallow); + const licenseValid = !featureAccess?.licenseStatus?.isExpired && featureAccess?.licenseStatus?.isLicenseValid; + const targetBtnForMenu = useStore((state) => state.queryPanel.targetBtnForMenu); + const targetElement = document.getElementById(targetBtnForMenu); + const showQueryHandlerMenu = useStore((state) => state.queryPanel.showQueryHandlerMenu); + const toggleQueryHandlerMenu = useStore((state) => state.queryPanel.toggleQueryHandlerMenu); + const duplicateQuery = useStore((state) => state.dataQuery.duplicateQuery); + const setPreviewData = useStore((state) => state.queryPanel.setPreviewData); + const setRenamingQuery = useStore((state) => state.queryPanel.setRenamingQuery); + const deleteDataQuery = useStore((state) => state.queryPanel.deleteDataQuery); + + const QUERY_MENU_OPTIONS = [ + { + label: 'Rename', + value: 'rename', + icon: , + showTooltip: false, + }, + { + label: 'Duplicate', + value: 'duplicate', + icon: , + showTooltip: false, + }, + { + label: 'Query permission', + value: 'permission', + icon: ( + permission-icon + ), + trailingIcon: , + }, + { + label: 'Delete', + value: 'delete', + icon: , + showTooltip: false, + }, + ]; + + // To prevent user clicking from continuous clicks + const debouncedDuplicateQuery = useCallback( + debounce((queryId, appId) => { + duplicateQuery(queryId, appId); + setPreviewData(null); + }, 500), + [duplicateQuery] + ); + + const handleQueryMenuActions = (value) => { + if (value === 'rename') { + setRenamingQuery(selectedQuery?.id); + } + if (value === 'duplicate') { + debouncedDuplicateQuery(selectedQuery?.id, appId); + } + if (value === 'permission') { + if (!licenseValid) return; + toggleQueryPermissionModal(true); + } + if (value === 'delete') { + deleteDataQuery(selectedQuery?.id); + } + toggleQueryHandlerMenu(false); + }; + + usePopoverObserver( + document.getElementsByClassName('query-list')[0], + targetElement, + document.getElementById('query-list-menu'), + showQueryHandlerMenu, + () => (document.getElementById('query-list-menu').style.display = 'block'), + () => (document.getElementById('query-list-menu').style.display = 'none') + ); + + return ( + toggleQueryHandlerMenu(false)} + popperConfig={{ + modifiers: [ + { + name: 'flip', + options: { + fallbackPlacements: ['top-start'], + flipVariations: true, + allowedAutoPlacements: ['top', 'bottom'], + boundary: 'viewport', + }, + }, + { + name: 'offset', + options: { + offset: [0, 3], + }, + }, + ], + }} + > + {(props) => ( + + + {QUERY_MENU_OPTIONS.map((option) => { + const optionBody = ( +
{ + e.stopPropagation(); + handleQueryMenuActions(option.value); + }} + > +
{option.icon}
+
+ {option?.label} +
+ {option.value === 'permission' && !licenseValid && option.trailingIcon && option.trailingIcon} +
+ ); + + return option.value === 'permission' ? ( + + {optionBody} + + ) : ( + optionBody + ); + })} +
+
+ )} +
+ ); +}; + +export default QueryCardMenu; diff --git a/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx b/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx index 62f27197ba..12e8fb3171 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx @@ -19,6 +19,7 @@ import useStore from '@/AppBuilder/_stores/store'; import AppPermissionsModal from '@/modules/Appbuilder/components/AppPermissionsModal'; import { shallow } from 'zustand/shallow'; import { appPermissionService } from '@/_services'; +import QueryCardMenu from './QueryCardMenu'; export const QueryDataPane = ({ darkMode }) => { const { t } = useTranslation(); @@ -180,6 +181,7 @@ export const QueryDataPane = ({ darkMode }) => { {filteredQueries.map((query) => ( ))} + {licenseValid && ( ({ @@ -1141,5 +1146,19 @@ export const createQueryPanelSlice = (set, get) => ({ state.queryPanel.showQueryPermissionModal = show; }); }, + toggleQueryHandlerMenu: (show, id) => { + set((state) => { + if (show) state.queryPanel.targetBtnForMenu = id; + state.queryPanel.showQueryHandlerMenu = show; + }); + }, + setRenamingQuery: (queryId) => + set((state) => { + state.queryPanel.renamingQueryId = queryId; + }), + deleteDataQuery: (queryId) => + set((state) => { + state.queryPanel.deletingQueryId = queryId; + }), }, }); From c4b3144d6468981782adaf6af9ab83c3d0cc5fbd Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 9 Jun 2025 12:37:34 +0530 Subject: [PATCH 24/35] Update submodule references --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 5c2787b498..201d9617d5 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 5c2787b498202f4356b4598fb961ddbab04acbbf +Subproject commit 201d9617d5700ad7fff5aaf7f56962726a8c213f diff --git a/server/ee b/server/ee index dd0462d3c3..7955b0c301 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit dd0462d3c3acf4b8e6856209eee52bc3cc78a02e +Subproject commit 7955b0c301128cfa995232b7039f2408cdee4be2 From dc7744d8addbbd51f3427e66041463f9e78830cf Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 9 Jun 2025 12:44:58 +0530 Subject: [PATCH 25/35] Fix: query not running on application load if query not accessible --- frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 79acc2c461..7fd9c46af6 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -420,7 +420,10 @@ export const createDataQuerySlice = (set, get) => ({ const queries = get().dataQuery.queries.modules.canvas; try { for (const query of queries) { - if ((query.options.runOnPageLoad || query.options.run_on_page_load) && isQueryRunnable(query)) { + if ( + (query.options.runOnPageLoad || query.options.run_on_page_load) && + (query.restricted || isQueryRunnable(query)) + ) { await get().queryPanel.runQuery(query.id, query.name, undefined, undefined, {}, false, true, 'canvas'); } } From 330b88d4205550a0be6f3a3864ec9a3617c28d3c Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Mon, 9 Jun 2025 12:47:45 +0530 Subject: [PATCH 26/35] Add page, query or component name to the permissions modal --- frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx | 2 ++ frontend/src/AppBuilder/QueryPanel/QueryDataPane.jsx | 1 + 2 files changed, 3 insertions(+) diff --git a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx index d55864e91c..68a54c2ca4 100644 --- a/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/PageMenu/PageMenu.jsx @@ -29,6 +29,7 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => { const enableReleasedVersionPopupState = useStore((state) => state.enableReleasedVersionPopupState); const closePageEditPopover = useStore((state) => state.closePageEditPopover); const editingPageId = useStore((state) => state.editingPage?.id); + const editingPageName = useStore((state) => state.editingPage?.name); const showPagePermissionModal = useStore((state) => state.showPagePermissionModal); const togglePagePermissionModal = useStore((state) => state.togglePagePermissionModal); const updatePageWithPermissions = useStore((state) => state.updatePageWithPermissions); @@ -105,6 +106,7 @@ export const PageMenu = ({ darkMode, switchPage, pinned, setPinned }) => { { Date: Thu, 5 Jun 2025 14:50:03 +0530 Subject: [PATCH 27/35] Added duplicating logic for query --- .../_stores/slices/dataQuerySlice.js | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 7fd9c46af6..3a8dba58c6 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -1,4 +1,4 @@ -import { dataqueryService } from '@/_services'; +import { dataqueryService, appPermissionService } from '@/_services'; import { getDefaultOptions } from '@/_stores/storeHelper'; import { v4 as uuidv4 } from 'uuid'; import _, { isEmpty, throttle } from 'lodash'; @@ -256,7 +256,6 @@ export const createDataQuerySlice = (set, get) => ({ ) .then((data) => { set((state) => { - state.dataQuery.creatingQueryInProcessId = null; state.dataQuery.queries.modules[moduleId] = [ { ...data, @@ -290,6 +289,42 @@ export const createDataQuerySlice = (set, get) => ({ }; createAppVersionEventHandlers(newEvent, moduleId); }); + + if (queryToClone.permissions && queryToClone.permissions.length !== 0) { + const body = { + type: queryToClone.permissions[0]?.type, + ...(queryToClone.permissions[0]?.type === 'GROUP' + ? { + groups: (queryToClone.permissions[0]?.groups || queryToClone.permissions[0]?.users || []).map( + (group) => group.permissionGroupsId || group.permission_groups_id + ), + } + : { users: queryToClone.permissions[0]?.users.map((user) => user.userId || user.user_id) }), + }; + appPermissionService + .createQueryPermission(appId, data.id, body) + .then((newQuery) => { + const dataQueries = get().dataQuery.queries.modules[moduleId]; + const updatedDataQueries = dataQueries.map((query) => { + if (query.id === data.id) { + return { + ...query, + permissions: newQuery.length === 0 || newQuery.length === undefined ? [] : [newQuery[0]], + }; + } + return query; + }); + get().dataQuery.setQueries(updatedDataQueries); + }) + .catch(() => { + toast.error('Permission could not be created. Please try again!', { + className: 'text-nowrap w-auto mw-100', + }); + }); + } + set((state) => { + state.dataQuery.creatingQueryInProcessId = null; + }); }) .catch((error) => { console.error('error', error); From b099b82066c4fa0dcf1161d713af2a17fb2b9d8d Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 00:27:18 +0530 Subject: [PATCH 28/35] Implemented backend call for restricting queries and adding audit logs --- .../_stores/slices/queryPanelSlice.js | 24 +------------------ server/ee | 2 +- .../data-queries/interfaces/IUtilService.ts | 3 ++- server/src/modules/data-queries/service.ts | 14 ++++++++--- .../src/modules/data-queries/util.service.ts | 5 ++-- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index deab8d6e00..8810fdf954 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -271,28 +271,6 @@ export const createQueryPanelSlice = (set, get) => ({ let dataQuery = {}; - //for viewer, if query is restricted throw unauthorized error - if (query.restricted) { - setResolvedQuery(queryId, { - isLoading: false, - metadata: { - response: { - statusCode: 401, - responseBody: 'Unauthorized access', - }, - }, - response: { - statusCode: 401, - responseBody: 'Unauthorized access', - }, - }); - onEvent('onDataQueryFailure', queryEvents); - return { - statusCode: 401, - responseBody: 'Unauthorized access', - }; - } - //for viewer we will only get the environment id from the url const { currentAppEnvironmentId, environmentId } = app; @@ -453,7 +431,7 @@ export const createQueryPanelSlice = (set, get) => ({ setResolvedQuery(queryId, { isLoading: false, - ...(query.kind === 'restapi' + ...(query.kind === 'restapi' || data.data.type === 'tj-401' ? { metadata: data.metadata, request: data.data.requestObject, diff --git a/server/ee b/server/ee index 7955b0c301..1ce85ca747 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 7955b0c301128cfa995232b7039f2408cdee4be2 +Subproject commit 1ce85ca747c4b4bd01cccd7afc2d36d4e4eec364 diff --git a/server/src/modules/data-queries/interfaces/IUtilService.ts b/server/src/modules/data-queries/interfaces/IUtilService.ts index f070380b54..3b5bdd10f1 100644 --- a/server/src/modules/data-queries/interfaces/IUtilService.ts +++ b/server/src/modules/data-queries/interfaces/IUtilService.ts @@ -14,7 +14,8 @@ export interface IDataQueriesUtilService { dataQuery: any, queryOptions: object, response: Response, - environmentId?: string + environmentId?: string, + mode?: string ): Promise; fetchServiceAndParsedParams( diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index e8d5003a6d..fc71a93b6e 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -139,7 +139,7 @@ export class DataQueriesService implements IDataQueriesService { dataQuery['options'] = options; } - return this.runAndGetResult(user, dataQuery, resolvedOptions, response, environmentId); + return this.runAndGetResult(user, dataQuery, resolvedOptions, response, environmentId, mode); } async runQueryForApp(user: User, dataQueryId: string, updateDataQueryDto: UpdateDataQueryDto, response: Response) { @@ -159,12 +159,20 @@ export class DataQueriesService implements IDataQueriesService { dataQuery: DataQuery, resolvedOptions: object, response: Response, - environmentId?: string + environmentId?: string, + mode?: string ): Promise { let result = {}; try { - result = await this.dataQueryUtilService.runQuery(user, dataQuery, resolvedOptions, response, environmentId); + result = await this.dataQueryUtilService.runQuery( + user, + dataQuery, + resolvedOptions, + response, + environmentId, + mode + ); } catch (error) { if (error.constructor.name === 'QueryError') { result = { diff --git a/server/src/modules/data-queries/util.service.ts b/server/src/modules/data-queries/util.service.ts index 6068c62bcc..1478e2a699 100644 --- a/server/src/modules/data-queries/util.service.ts +++ b/server/src/modules/data-queries/util.service.ts @@ -63,7 +63,8 @@ export class DataQueriesUtilService implements IDataQueriesUtilService { dataQuery: any, queryOptions: object, response: Response, - environmentId?: string + environmentId?: string, + mode?: string ): Promise { let result; const queryStatus = new DataQueryStatus(); @@ -320,7 +321,7 @@ export class DataQueriesUtilService implements IDataQueriesUtilService { return { service, sourceOptions, parsedQueryOptions }; } - private getCurrentUserToken = (isMultiAuthEnabled: boolean, tokenData: any, userId: string, isAppPublic: boolean) => { + protected getCurrentUserToken = (isMultiAuthEnabled: boolean, tokenData: any, userId: string, isAppPublic: boolean) => { if (isMultiAuthEnabled) { if (!tokenData || !Array.isArray(tokenData)) return null; return !isAppPublic From a50c83d13ba13772dc3494ddadd4ee2d6951415d Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 00:34:51 +0530 Subject: [PATCH 29/35] Fixed letter case in function call --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 1ce85ca747..e4fa150349 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1ce85ca747c4b4bd01cccd7afc2d36d4e4eec364 +Subproject commit e4fa15034948a3b8180b388765495d1205220c66 From c333e4072f8e1699c08ccc8cb67cab2607db78f2 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 02:45:00 +0530 Subject: [PATCH 30/35] Implemented restrictions for RunJS, RunPy and Workflows --- .../_stores/slices/dataQuerySlice.js | 2 +- .../_stores/slices/queryPanelSlice.js | 75 +++++++++++++++++-- 2 files changed, 71 insertions(+), 6 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 3a8dba58c6..f9b2badccd 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -456,7 +456,7 @@ export const createDataQuerySlice = (set, get) => ({ try { for (const query of queries) { if ( - (query.options.runOnPageLoad || query.options.run_on_page_load) && + (query.options?.runOnPageLoad || query.options?.run_on_page_load) && (query.restricted || isQueryRunnable(query)) ) { await get().queryPanel.runQuery(query.id, query.name, undefined, undefined, {}, false, true, 'canvas'); diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 8810fdf954..7a24a911ec 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -342,14 +342,15 @@ export const createQueryPanelSlice = (set, get) => ({ let queryExecutionPromise = null; if (query.kind === 'runjs') { - queryExecutionPromise = executeMultilineJS(query.options.code, query?.id, false, mode, parameters); + queryExecutionPromise = executeMultilineJS(query.options?.code, query?.id, false, mode, parameters); } else if (query.kind === 'runpy') { - queryExecutionPromise = executeRunPycode(query.options.code, query, false, mode, queryState); + queryExecutionPromise = executeRunPycode(query.options?.code, query, false, mode, queryState); } else if (query.kind === 'workflows') { queryExecutionPromise = executeWorkflow( moduleId, - query.options.workflowId, - query.options.blocking, + query, + query.options?.workflowId, + query.options?.blocking, query.options?.params, (currentAppEnvironmentId ?? environmentId) || selectedEnvironment?.id //TODO: currentAppEnvironmentId may no longer required. Need to check ); @@ -695,6 +696,28 @@ export const createQueryPanelSlice = (set, get) => ({ const { queryPanel: { evaluatePythonCode }, } = get(); + + if (query.restricted) { + return { + status: 'failed', + message: 'Unauthorized Access', + description: '', + data: { + type: 'tj-401', + responseObject: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + metadata: { + response: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + }; + } + return { data: await evaluatePythonCode({ code, query, isPreview, mode, currentState }) }; }, @@ -911,7 +934,7 @@ export const createQueryPanelSlice = (set, get) => ({ // queries: updatedQueries, // }); }, - executeWorkflow: async (moduleId, workflowId, _blocking = false, params = {}, appEnvId) => { + executeWorkflow: async (moduleId, query, workflowId, _blocking = false, params = {}, appEnvId) => { const { app: { appId }, getAllExposedValues, @@ -919,6 +942,27 @@ export const createQueryPanelSlice = (set, get) => ({ const currentState = getAllExposedValues(); const resolvedParams = get().resolveReferences(moduleId, params, currentState, {}, {}); + if (query.restricted) { + return { + status: 'failed', + message: 'Unauthorized Access', + description: '', + data: { + type: 'tj-401', + responseObject: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + metadata: { + response: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + }; + } + try { const response = await workflowExecutionsService.execute(workflowId, resolvedParams, appId, appEnvId); return { data: response.result, status: 'ok' }; @@ -969,6 +1013,27 @@ export const createQueryPanelSlice = (set, get) => ({ const queryDetails = dataQuery.queries.modules?.[moduleId].find((q) => q.id === queryId); + if (queryDetails.restricted) { + return { + status: 'failed', + message: 'Unauthorized Access', + description: '', + data: { + type: 'tj-401', + responseObject: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + metadata: { + response: { + statusCode: 401, + responseBody: 'Unauthorized Access', + }, + }, + }; + } + const defaultParams = queryDetails?.options?.parameters?.reduce( (paramObj, param) => ({ From c0423bd7137a9a7187070cffeae43b22b2f1e74c Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 17:26:46 +0530 Subject: [PATCH 31/35] Fix permission not being applied in preview if released app is set as public --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index e4fa150349..914e2c03f1 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit e4fa15034948a3b8180b388765495d1205220c66 +Subproject commit 914e2c03f18c8002ca296cf02068a9b163841bde From 86f0c1808c9eb8c66861d0d7c75ae166b5e4887b Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 17:34:27 +0530 Subject: [PATCH 32/35] Changed error message and description --- .../src/AppBuilder/_stores/slices/queryPanelSlice.js | 12 ++++++------ server/ee | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 7a24a911ec..06774289ba 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -700,8 +700,8 @@ export const createQueryPanelSlice = (set, get) => ({ if (query.restricted) { return { status: 'failed', - message: 'Unauthorized Access', - description: '', + message: 'Query could not be completed', + description: 'Response code 401 (Unauthorized)', data: { type: 'tj-401', responseObject: { @@ -945,8 +945,8 @@ export const createQueryPanelSlice = (set, get) => ({ if (query.restricted) { return { status: 'failed', - message: 'Unauthorized Access', - description: '', + message: 'Query could not be completed', + description: 'Response code 401 (Unauthorized)', data: { type: 'tj-401', responseObject: { @@ -1016,8 +1016,8 @@ export const createQueryPanelSlice = (set, get) => ({ if (queryDetails.restricted) { return { status: 'failed', - message: 'Unauthorized Access', - description: '', + message: 'Query could not be completed', + description: 'Response code 401 (Unauthorized)', data: { type: 'tj-401', responseObject: { diff --git a/server/ee b/server/ee index 914e2c03f1..bc43a53d3a 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 914e2c03f18c8002ca296cf02068a9b163841bde +Subproject commit bc43a53d3a1b6126f107ebfa09d645858f7065a4 From e3a22fe36db3b44f2443c68845e2c59b79c96188 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 18:06:54 +0530 Subject: [PATCH 33/35] Merged sprint-14 branch in ee-server submodule --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index bc43a53d3a..2218eb29db 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit bc43a53d3a1b6126f107ebfa09d645858f7065a4 +Subproject commit 2218eb29db1787c0c028a21ecbd660ba77390772 From 91a78c5eb93d4f916498c1e93e2d94ad80d5e029 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Thu, 12 Jun 2025 19:34:33 +0530 Subject: [PATCH 34/35] Added missing modules change --- frontend/ee | 2 +- frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 201d9617d5..c91427f3c4 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 201d9617d5700ad7fff5aaf7f56962726a8c213f +Subproject commit c91427f3c4de1f8ce9417d405214508e587a3ab9 diff --git a/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx index 760adb8075..a9e3030b51 100644 --- a/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx +++ b/frontend/src/AppBuilder/QueryPanel/QueryCardMenu.jsx @@ -10,9 +10,11 @@ import { shallow } from 'zustand/shallow'; import { ToolTip } from '@/_components/ToolTip'; import { debounce } from 'lodash'; import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; const QueryCardMenu = ({ darkMode }) => { - const appId = useStore((state) => state.app.appId); + const { moduleId } = useModuleContext(); + const appId = useStore((state) => state.appStore.modules[moduleId].app.appId); const selectedQuery = useStore((state) => state.queryPanel.selectedQuery); const toggleQueryPermissionModal = useStore((state) => state.queryPanel.toggleQueryPermissionModal); const featureAccess = useStore((state) => state?.license?.featureAccess, shallow); From bf826e4a89c19cf5c2b94cf702abc431da218732 Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Wed, 25 Jun 2025 13:04:07 +0530 Subject: [PATCH 35/35] Update submodule ref with feature branch itself --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index c91427f3c4..c5513e3034 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit c91427f3c4de1f8ce9417d405214508e587a3ab9 +Subproject commit c5513e303482c45289a974bb1c918ae22be6ec9c diff --git a/server/ee b/server/ee index 2218eb29db..8df193d06e 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 2218eb29db1787c0c028a21ecbd660ba77390772 +Subproject commit 8df193d06e146f72447a3fa95de4544cd47080d5