From dad9f39c2e9b918256c9e77df670e2fc6d727cef Mon Sep 17 00:00:00 2001 From: Oliver-ctrlo <119069310+Oliver-ctrlo@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:39:47 +0100 Subject: [PATCH 1/2] Added script for bulk import/export of layout group permissions This script allows bulk importing and exporting group layout permissions. --- bin/bulk_layout_permissions.pl | 121 +++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 bin/bulk_layout_permissions.pl diff --git a/bin/bulk_layout_permissions.pl b/bin/bulk_layout_permissions.pl new file mode 100644 index 000000000..81b13a98a --- /dev/null +++ b/bin/bulk_layout_permissions.pl @@ -0,0 +1,121 @@ +#!/usr/bin/perl + +use FindBin; +use lib "$FindBin::Bin/../lib"; + +use Dancer2; +use Dancer2::Plugin::DBIC; +use Getopt::Long; +use Text::CSV; +use feature 'say'; + +my ($action); + +GetOptions( + 'action=s' => \$action +) or exit; + +$action or die "ERROR: Please state if you want to import/export group permissions with --action"; + +if ($action eq 'import') { + Import_permissions(); +} +elsif ($action eq 'export') { + Export_permissions(); +} +else { + die "ERROR: You must either import or export within --action"; +} + +sub Import_permissions { + say "Enter the file you want to import from:"; + my $file = ; + chomp($file); + + -f $file or die "ERROR: File '$file' does not exist"; + + $file or die "Usage: $0 filename"; + my $csv = Text::CSV->new({ binary => 1 }) + or die "Cannot use CSV: ".Text::CSV->error_diag (); + open my $fh, "<:encoding(utf8)", $file or die "$file: $!"; + my $guard = schema->txn_scope_guard; + + while (my $row = $csv->getline($fh)) { + my ($layout_id, $group_id, $permission) = @$row; + my $RowCount = $csv->record_number; + + unless (defined $layout_id && $layout_id =~ /^\d+$/) { + die "ERROR: Invalid Layout ID '$layout_id' on row $RowCount.\n"; + } + unless (defined $group_id && $group_id =~ /^\d+$/) { + die "ERROR: Invalid Group ID '$group_id' on row $RowCount.\n"; + } + unless (defined $permission && $permission =~ /^(?:read|write_new|write_existing|write_existing_no_approval|write_new_no_approval)$/) { + die "ERROR: Invalid Permission '$permission' on row $RowCount. Value must be read, write_new, write_existing, or write_existing_no_approval.\n"; + } + + rset('Layout')->find($layout_id) or die "ERROR: Layout ID '$layout_id' does not exist\n"; + rset('Group')->find($group_id) or die "ERROR: Group ID '$group_id' does not exist\n"; + + my $existing = rset('LayoutGroup')->find({ + layout_id => $layout_id, + group_id => $group_id, + permission => $permission + }); + + if ($existing) { + say "Skipping permission '$permission' for group '$group_id' on layout '$layout_id' because this permission already exists"; + next; + } + + rset('LayoutGroup')->create({ + layout_id => $layout_id, + group_id => $group_id, + permission => $permission, + }); + say "Will add permission '$permission' to group '$group_id' for layout '$layout_id'"; + } + $guard->commit; + say "Import complete"; +} + +sub Export_permissions { + my $export_file = './Group_permission_export.csv'; + ! -f $export_file or die "ERROR: $export_file already exists, unable to overwrite file!"; + say "Enter the group IDs you're looking to export (e.g. 1,2,3): "; + my $input = ; + chomp($input); + + if (!$input || $input !~ /^\s*\d+(?:\s*,\s*\d+)*\s*$/) { + die "Error: You need to enter a number or a comma-separated list of numbers.\n"; + } + + my @groups = split(/\s*,\s*/, $input); + + my $csv = Text::CSV->new({ binary => 1, auto_diag => 1 }); + open my $fh, ">:encoding(utf8)", $export_file or die "$export_file: $!"; + + my @layout_groups = rset('LayoutGroup')->search( + { + 'me.group_id' => { '-in' => \@groups }, + }, + { + prefetch => [ 'layout' ], + } + )->all; + + $csv->say($fh, [ 'Instance ID','Layout Name','Layout ID', 'Group ID','Permission' ]); + + foreach my $lg (@layout_groups) { + my $layout = $lg->layout; + my $instance_id = $layout->instance_id; + my $layout_name = $layout->name ; + my $group_id = $lg->group_id; + my $layout_id = $lg->layout_id; + my $permission = $lg->permission; + + $csv->say($fh, [ $instance_id, $layout_name, $layout_id, $group_id, $permission ]); + } + close($fh); + say "Successfully exported permissions to '$export_file'"; +} From 87f2b5ca857ccbd6dfe9fe0ad909b21f7559716c Mon Sep 17 00:00:00 2001 From: Oliver-ctrlo Date: Wed, 29 Jul 2026 12:36:12 +0100 Subject: [PATCH 2/2] Replaced dies with log report, and added file to options. --- bin/bulk_layout_permissions.pl | 67 +++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 30 deletions(-) mode change 100644 => 100755 bin/bulk_layout_permissions.pl diff --git a/bin/bulk_layout_permissions.pl b/bin/bulk_layout_permissions.pl old mode 100644 new mode 100755 index 81b13a98a..e9bc98a82 --- a/bin/bulk_layout_permissions.pl +++ b/bin/bulk_layout_permissions.pl @@ -8,15 +8,17 @@ use Getopt::Long; use Text::CSV; use feature 'say'; +use Log::Report syntax => 'LONG'; -my ($action); +my ($action, $file); GetOptions( - 'action=s' => \$action + 'action=s' => \$action, + 'file=s' => \$file ) or exit; -$action or die "ERROR: Please state if you want to import/export group permissions with --action"; - +$action or report ERROR => "Please state if you want to import/export group permissions with --action"; +$file or report ERROR => "Please provide the file you want to import/export to/from with --file"; if ($action eq 'import') { Import_permissions(); } @@ -24,38 +26,44 @@ Export_permissions(); } else { - die "ERROR: You must either import or export within --action"; + report ERROR => "You must state if you want to import or export with --action"; } + sub Import_permissions { - say "Enter the file you want to import from:"; - my $file = ; - chomp($file); - -f $file or die "ERROR: File '$file' does not exist"; + -f $file or report ERROR => "File '$file' does not exist"; - $file or die "Usage: $0 filename"; my $csv = Text::CSV->new({ binary => 1 }) - or die "Cannot use CSV: ".Text::CSV->error_diag (); - open my $fh, "<:encoding(utf8)", $file or die "$file: $!"; + or report ERROR => "Cannot use CSV: ".Text::CSV->error_diag (); + open my $fh, "<:encoding(utf8)", $file or report FAULT => "$file"; my $guard = schema->txn_scope_guard; - - while (my $row = $csv->getline($fh)) { - my ($layout_id, $group_id, $permission) = @$row; + + my $headers = $csv->getline($fh); + $csv->column_names(@$headers) if $headers; + + report ERROR => "Invalid CSV header/s. Import must consist of 3 columns: layout_id, group_id and permission" + unless $headers && join(',', sort @$headers) eq 'group_id,layout_id,permission'; + + while (my $row = $csv->getline_hr($fh)) { + my $RowCount = $csv->record_number; - + my $layout_id = $row->{layout_id}; + my $group_id = $row->{group_id}; + my $permission = $row->{permission}; + unless (defined $layout_id && $layout_id =~ /^\d+$/) { - die "ERROR: Invalid Layout ID '$layout_id' on row $RowCount.\n"; + report ERROR => "ERROR: Invalid Layout ID '$layout_id' on row $RowCount."; } unless (defined $group_id && $group_id =~ /^\d+$/) { - die "ERROR: Invalid Group ID '$group_id' on row $RowCount.\n"; + report ERROR => "ERROR: Invalid Group ID '$row->{group_id}' on row $RowCount."; } unless (defined $permission && $permission =~ /^(?:read|write_new|write_existing|write_existing_no_approval|write_new_no_approval)$/) { - die "ERROR: Invalid Permission '$permission' on row $RowCount. Value must be read, write_new, write_existing, or write_existing_no_approval.\n"; + report ERROR => "Invalid Permission '$permission' on row $RowCount. Value must be read, write_new, write_existing, or write_existing_no_approval."; } - rset('Layout')->find($layout_id) or die "ERROR: Layout ID '$layout_id' does not exist\n"; - rset('Group')->find($group_id) or die "ERROR: Group ID '$group_id' does not exist\n"; + rset('Layout')->find($layout_id) or report ERROR => "Layout ID '$layout_id' does not exist\n"; + rset('Group')->find($group_id) or report ERROR => "Group ID '$group_id' does not exist\n"; my $existing = rset('LayoutGroup')->find({ layout_id => $layout_id, @@ -64,7 +72,7 @@ sub Import_permissions { }); if ($existing) { - say "Skipping permission '$permission' for group '$group_id' on layout '$layout_id' because this permission already exists"; + info "Skipping permission '$permission' for group '$group_id' on layout '$layout_id' because this permission already exists"; next; } @@ -73,27 +81,26 @@ sub Import_permissions { group_id => $group_id, permission => $permission, }); - say "Will add permission '$permission' to group '$group_id' for layout '$layout_id'"; + info "Will add permission '$permission' to group '$group_id' for layout '$layout_id'"; } $guard->commit; - say "Import complete"; + info "Import complete"; } sub Export_permissions { - my $export_file = './Group_permission_export.csv'; - ! -f $export_file or die "ERROR: $export_file already exists, unable to overwrite file!"; + ! -f $file or report ERROR => "$file already exists, unable to overwrite file!"; say "Enter the group IDs you're looking to export (e.g. 1,2,3): "; my $input = ; chomp($input); if (!$input || $input !~ /^\s*\d+(?:\s*,\s*\d+)*\s*$/) { - die "Error: You need to enter a number or a comma-separated list of numbers.\n"; + report ERROR => "You need to enter a number or a comma-separated list of numbers.\n"; } my @groups = split(/\s*,\s*/, $input); my $csv = Text::CSV->new({ binary => 1, auto_diag => 1 }); - open my $fh, ">:encoding(utf8)", $export_file or die "$export_file: $!"; + open my $fh, ">:encoding(utf8)", $file or report FAULT => "$file"; my @layout_groups = rset('LayoutGroup')->search( { @@ -104,7 +111,7 @@ sub Export_permissions { } )->all; - $csv->say($fh, [ 'Instance ID','Layout Name','Layout ID', 'Group ID','Permission' ]); + $csv->say($fh, [ 'instance_id','layout_name','layout_id','group_id','permission' ]); foreach my $lg (@layout_groups) { my $layout = $lg->layout; @@ -117,5 +124,5 @@ sub Export_permissions { $csv->say($fh, [ $instance_id, $layout_name, $layout_id, $group_id, $permission ]); } close($fh); - say "Successfully exported permissions to '$export_file'"; + info "Successfully exported permissions to '$file'"; }