]> BookStack Code Mirror - bookstack/blob - database/migrations/2025_09_15_134751_update_entity_relation_columns.php
DB: Updated handling of deleted user ID handling in DB
[bookstack] / database / migrations / 2025_09_15_134751_update_entity_relation_columns.php
1 <?php
2
3 use BookStack\Permissions\JointPermissionBuilder;
4 use Illuminate\Database\Migrations\Migration;
5 use Illuminate\Database\Schema\Blueprint;
6 use Illuminate\Support\Facades\Schema;
7
8 return new class extends Migration
9 {
10     /**
11      * @var array<string, string|array<string>> $columnByTable
12      */
13     protected static array $columnByTable = [
14         'activities' => 'loggable_id',
15         'attachments' => 'uploaded_to',
16         'bookshelves_books' => ['bookshelf_id', 'book_id'],
17         'comments' => 'entity_id',
18         'deletions' => 'deletable_id',
19         'entity_permissions' => 'entity_id',
20         'favourites' => 'favouritable_id',
21         'images' => 'uploaded_to',
22         'joint_permissions' => 'entity_id',
23         'page_revisions' => 'page_id',
24         'references' => ['from_id', 'to_id'],
25         'search_terms' => 'entity_id',
26         'tags' => 'entity_id',
27         'views' => 'viewable_id',
28         'watches' => 'watchable_id',
29     ];
30
31     protected static array $nullable = [
32         'activities.loggable_id',
33         'images.uploaded_to',
34     ];
35
36     /**
37      * Run the migrations.
38      */
39     public function up(): void
40     {
41         // Drop foreign key constraints
42         Schema::table('bookshelves_books', function (Blueprint $table) {
43             $table->dropForeign(['book_id']);
44             $table->dropForeign(['bookshelf_id']);
45         });
46
47         // Update column types to unsigned big integers
48         foreach (static::$columnByTable as $table => $column) {
49             $tableName = $table;
50             Schema::table($table, function (Blueprint $table) use ($tableName, $column) {
51                 if (is_string($column)) {
52                     $column = [$column];
53                 }
54
55                 foreach ($column as $col) {
56                     if (in_array($tableName . '.' . $col, static::$nullable)) {
57                         $table->unsignedBigInteger($col)->nullable()->change();
58                     } else {
59                         $table->unsignedBigInteger($col)->change();
60                     }
61                 }
62             });
63         }
64
65         // Convert image and activity zero values to null
66         DB::table('images')->where('uploaded_to', '=', 0)->update(['uploaded_to' => null]);
67         DB::table('activities')->where('loggable_id', '=', 0)->update(['loggable_id' => null]);
68
69         // Rebuild joint permissions if needed
70         // This was moved here from 2023_01_24_104625_refactor_joint_permissions_storage since the changes
71         // made for this release would mean our current logic would not be compatible with
72         // the database changes being made. This is based on a count since any joint permissions
73         // would have been truncated in the previous migration.
74         if (\Illuminate\Support\Facades\DB::table('joint_permissions')->count() === 0) {
75             app(JointPermissionBuilder::class)->rebuildForAll();
76         }
77     }
78
79     /**
80      * Reverse the migrations.
81      */
82     public function down(): void
83     {
84         // Convert image null values back to zeros
85         DB::table('images')->whereNull('uploaded_to')->update(['uploaded_to' => '0']);
86
87         // Revert columns to standard integers
88         foreach (static::$columnByTable as $table => $column) {
89             $tableName = $table;
90             Schema::table($table, function (Blueprint $table) use ($tableName, $column) {
91                 if (is_string($column)) {
92                     $column = [$column];
93                 }
94
95                 foreach ($column as $col) {
96                     if ($tableName . '.' . $col === 'activities.loggable_id') {
97                         $table->unsignedInteger($col)->nullable()->change();
98                     } else if ($tableName . '.' . $col === 'images.uploaded_to') {
99                         $table->unsignedInteger($col)->default(0)->change();
100                     } else {
101                         $table->unsignedInteger($col)->change();
102                     }
103                 }
104             });
105         }
106
107         // Re-add foreign key constraints
108         Schema::table('bookshelves_books', function (Blueprint $table) {
109             $table->foreign('bookshelf_id')->references('id')->on('bookshelves')
110                 ->onUpdate('cascade')->onDelete('cascade');
111             $table->foreign('book_id')->references('id')->on('books')
112                 ->onUpdate('cascade')->onDelete('cascade');
113         });
114     }
115 };