ToolJet/server/data-migrations/1766500000001-SeedFolderPermissions.ts
vjaris42 0431098e2c
Feat: Folder permission system (#15028)
* feat: Folder permission system

* fix(group-permissions): resolve custom group validation, folder edit check, and UI inconsistencie

* edit folder container && no folder in custom resource

* fix the ui for custom in empty state

* fix: coercion logic for folder permissions

* feat: enhance folder permissions handling in app components

* feat: add folder granular permissions handling in user apps permissions

* feat: implement granular folder permissions in ability guard and service

* feat: improve error handling for folder permissions with specific messages

* feat: enhance EnvironmentSelect component to handle disabled state and improve display logic

* chore: bump ee submodules

* feat: Update permission prop to isEditable in BaseManageGranularAccess component

* chore: bump ee server submodule

* fix: refine folder visibility logic based on user permissions

* feat: enhance MultiValue rendering and styling for "All environments" option

* feat: implement folder ownership checks and enhance app permissions handling

* feat: enhance folder permissions handling for app ownership and actions

* chore: clarify folder creation and deletion permissions in workspace context

* fix: update folder permission labels

* fixed folder permission cases

* fixed css class issue

* minor fix

* feat: streamline folder permissions handling by removing redundant checks and simplifying access logic

* refactor: made error message consistent

* fix: add missing permission message for folder deletion action

* refactor: consolidate forbidden messages for folder actions and maintain consistency

* feat: streamline permission handling and improve app visibility logic

* fix: remove default access denial message in AbilityGuard

* fixed all user page functionality falky case

* Fixed profile flaky case

* fixed granular access flaky case

* chore: revert package-lock.json

* chore: revert frontend/package-lock.json to main

* refactor: revert folder operations

* fix: remove unused AppEnvironmentsModule and AppsUtilService exports

---------

Co-authored-by: gsmithun4 <gsmithun4@gmail.com>
Co-authored-by: Pratush <pratush@Pratushs-MBP.lan>
Co-authored-by: Shantanu Mane <maneshantanu.20@gmail.com>
Co-authored-by: Yukti Goyal <yuktigoyal02@gmail.com>
2026-03-19 22:00:30 +05:30

117 lines
4.3 KiB
TypeScript

import { MigrationInterface, QueryRunner } from 'typeorm';
export class SeedFolderPermissions1766500000001 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// Step 1: Create granular_permissions entries for folders for each existing permission_group
// This ensures existing organizations get folder permissions based on their role
await queryRunner.query(`
INSERT INTO granular_permissions (group_id, name, type, is_all)
SELECT
pg.id as group_id,
'Folders' as name,
'folder'::resource_type as type,
true as is_all
FROM permission_groups pg
WHERE NOT EXISTS (
SELECT 1 FROM granular_permissions gp
WHERE gp.group_id = pg.id AND gp.type = 'folder'
);
`);
// Step 2: Create folders_group_permissions for each new granular permission
// Set permissions based on group type (admin, builder, end_user)
// Admin groups: Edit folder permission (highest level)
// Radio button approach: only the selected permission level is true
// Implied permissions (edit apps, view apps) are derived at runtime
await queryRunner.query(`
INSERT INTO folders_group_permissions (granular_permission_id, can_edit_folder, can_edit_apps, can_view_apps)
SELECT
gp.id as granular_permission_id,
true as can_edit_folder,
false as can_edit_apps,
false as can_view_apps
FROM granular_permissions gp
JOIN permission_groups pg ON gp.group_id = pg.id
WHERE gp.type = 'folder'
AND pg.type = 'default'
AND pg.name = 'admin'
AND NOT EXISTS (
SELECT 1 FROM folders_group_permissions fgp
WHERE fgp.granular_permission_id = gp.id
);
`);
// Builder groups: Edit folder permission (per PRD: "Edit folders - All folders")
// Radio button approach: only the selected permission level is true
// Implied permissions (edit apps, view apps) are derived at runtime
await queryRunner.query(`
INSERT INTO folders_group_permissions (granular_permission_id, can_edit_folder, can_edit_apps, can_view_apps)
SELECT
gp.id as granular_permission_id,
true as can_edit_folder,
false as can_edit_apps,
false as can_view_apps
FROM granular_permissions gp
JOIN permission_groups pg ON gp.group_id = pg.id
WHERE gp.type = 'folder'
AND pg.type = 'default'
AND pg.name = 'builder'
AND NOT EXISTS (
SELECT 1 FROM folders_group_permissions fgp
WHERE fgp.granular_permission_id = gp.id
);
`);
// End-user groups: view only
await queryRunner.query(`
INSERT INTO folders_group_permissions (granular_permission_id, can_edit_folder, can_edit_apps, can_view_apps)
SELECT
gp.id as granular_permission_id,
false as can_edit_folder,
false as can_edit_apps,
true as can_view_apps
FROM granular_permissions gp
JOIN permission_groups pg ON gp.group_id = pg.id
WHERE gp.type = 'folder'
AND pg.type = 'default'
AND pg.name = 'end_user'
AND NOT EXISTS (
SELECT 1 FROM folders_group_permissions fgp
WHERE fgp.granular_permission_id = gp.id
);
`);
// Custom groups: default to view only (conservative approach)
await queryRunner.query(`
INSERT INTO folders_group_permissions (granular_permission_id, can_edit_folder, can_edit_apps, can_view_apps)
SELECT
gp.id as granular_permission_id,
false as can_edit_folder,
false as can_edit_apps,
true as can_view_apps
FROM granular_permissions gp
JOIN permission_groups pg ON gp.group_id = pg.id
WHERE gp.type = 'folder'
AND pg.type = 'custom'
AND NOT EXISTS (
SELECT 1 FROM folders_group_permissions fgp
WHERE fgp.granular_permission_id = gp.id
);
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Delete folder granular permissions data
await queryRunner.query(`
DELETE FROM folders_group_permissions
WHERE granular_permission_id IN (
SELECT id FROM granular_permissions WHERE type = 'folder'
);
`);
await queryRunner.query(`
DELETE FROM granular_permissions WHERE type = 'folder';
`);
}
}