]> BookStack Code Mirror - bookstack/blob - database/migrations/2022_10_07_091406_flatten_entity_permissions_table.php
468f33248c365cfd23f65f5a6ef126df5389c829
[bookstack] / database / migrations / 2022_10_07_091406_flatten_entity_permissions_table.php
1 <?php
2
3 use Illuminate\Database\Migrations\Migration;
4 use Illuminate\Database\Query\Builder;
5 use Illuminate\Database\Schema\Blueprint;
6 use Illuminate\Support\Facades\DB;
7 use Illuminate\Support\Facades\Schema;
8
9 class FlattenEntityPermissionsTable extends Migration
10 {
11     /**
12      * Run the migrations.
13      *
14      * @return void
15      */
16     public function up()
17     {
18         // Remove entries for non-existing roles (Caused by previous lack of deletion handling)
19         $roleIds = DB::table('roles')->pluck('id');
20         DB::table('entity_permissions')->whereNotIn('role_id', $roleIds)->delete();
21
22         // Create new table structure for entity_permissions
23         Schema::create('new_entity_permissions', function (Blueprint $table) {
24             $table->id();
25             $table->unsignedInteger('entity_id');
26             $table->string('entity_type', 25);
27             $table->unsignedInteger('role_id')->index();
28             $table->boolean('view')->default(0);
29             $table->boolean('create')->default(0);
30             $table->boolean('update')->default(0);
31             $table->boolean('delete')->default(0);
32
33             $table->index(['entity_id', 'entity_type']);
34         });
35
36         // Migrate existing entity_permission data into new table structure
37
38         $subSelect = function (Builder $query, string $action, string $subAlias) {
39             $sub = $query->newQuery()->select('action')->from('entity_permissions', $subAlias)
40                 ->whereColumn('a.restrictable_id', '=', $subAlias . '.restrictable_id')
41                 ->whereColumn('a.restrictable_type', '=', $subAlias . '.restrictable_type')
42                 ->whereColumn('a.role_id', '=', $subAlias . '.role_id')
43                 ->where($subAlias . '.action', '=', $action);
44             return $query->selectRaw("EXISTS({$sub->toSql()})", $sub->getBindings());
45         };
46
47         $query = DB::table('entity_permissions', 'a')->select([
48             'restrictable_id as entity_id',
49             'restrictable_type as entity_type',
50             'role_id',
51             'view'   => fn(Builder $query) => $subSelect($query, 'view', 'b'),
52             'create' => fn(Builder $query) => $subSelect($query, 'create', 'c'),
53             'update' => fn(Builder $query) => $subSelect($query, 'update', 'd'),
54             'delete' => fn(Builder $query) => $subSelect($query, 'delete', 'e'),
55         ])->groupBy('restrictable_id', 'restrictable_type', 'role_id');
56
57         DB::table('new_entity_permissions')->insertUsing(['entity_id', 'entity_type', 'role_id', 'view', 'create', 'update', 'delete'], $query);
58
59         // Drop old entity_permissions table and replace with new structure
60         Schema::dropIfExists('entity_permissions');
61         Schema::rename('new_entity_permissions', 'entity_permissions');
62     }
63
64     /**
65      * Reverse the migrations.
66      *
67      * @return void
68      */
69     public function down()
70     {
71         // Create old table structure for entity_permissions
72         Schema::create('old_entity_permissions', function (Blueprint $table) {
73             $table->increments('id');
74             $table->integer('restrictable_id');
75             $table->string('restrictable_type', 191);
76             $table->integer('role_id')->index();
77             $table->string('action', 191)->index();
78
79             $table->index(['restrictable_id', 'restrictable_type']);
80         });
81
82         // Convert newer data format to old data format, and insert into old database
83
84         $actionQuery = function (Builder $query, string $action) {
85             return $query->select([
86                 'entity_id as restrictable_id',
87                 'entity_type as restrictable_type',
88                 'role_id',
89             ])->selectRaw("? as action", [$action])
90             ->from('entity_permissions')
91             ->where($action, '=', true);
92         };
93
94         $query = $actionQuery(DB::query(), 'view')
95             ->union(fn(Builder $query) => $actionQuery($query, 'create'))
96             ->union(fn(Builder $query) => $actionQuery($query, 'update'))
97             ->union(fn(Builder $query) => $actionQuery($query, 'delete'));
98
99         DB::table('old_entity_permissions')->insertUsing(['restrictable_id', 'restrictable_type', 'role_id', 'action'], $query);
100
101         // Drop new entity_permissions table and replace with old structure
102         Schema::dropIfExists('entity_permissions');
103         Schema::rename('old_entity_permissions', 'entity_permissions');
104     }
105 }