mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-10-25 06:37:36 +03:00
DB: Updated handling of deleted user ID handling in DB
Updated uses of user ID to nullify on delete. Added testing to cover deletion of user relations. Added model factories to support changes and potential other tests. Cleans existing ID references in the DB via migration.
This commit is contained in:
28
database/factories/Access/Mfa/MfaValueFactory.php
Normal file
28
database/factories/Access/Mfa/MfaValueFactory.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Access\Mfa;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Access\Mfa\MfaValue>
|
||||
*/
|
||||
class MfaValueFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Access\Mfa\MfaValue::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'method' => 'totp',
|
||||
'value' => '123456',
|
||||
];
|
||||
}
|
||||
}
|
||||
29
database/factories/Access/SocialAccountFactory.php
Normal file
29
database/factories/Access/SocialAccountFactory.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Access;
|
||||
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Access\SocialAccount>
|
||||
*/
|
||||
class SocialAccountFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Access\SocialAccount::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'driver' => 'github',
|
||||
'driver_id' => '123456',
|
||||
'avatar' => '',
|
||||
];
|
||||
}
|
||||
}
|
||||
34
database/factories/Activity/Models/ActivityFactory.php
Normal file
34
database/factories/Activity/Models/ActivityFactory.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Activity\Models;
|
||||
|
||||
use BookStack\Activity\ActivityType;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Activity\Models\Activity>
|
||||
*/
|
||||
class ActivityFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Activity\Models\Activity::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$activities = ActivityType::all();
|
||||
$activity = $activities[array_rand($activities)];
|
||||
return [
|
||||
'type' => $activity,
|
||||
'detail' => 'Activity detail for ' . $activity,
|
||||
'user_id' => User::factory(),
|
||||
'ip' => $this->faker->ipv4(),
|
||||
'loggable_id' => null,
|
||||
'loggable_type' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
31
database/factories/Activity/Models/FavouriteFactory.php
Normal file
31
database/factories/Activity/Models/FavouriteFactory.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Activity\Models;
|
||||
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Activity\Models\Favourite>
|
||||
*/
|
||||
class FavouriteFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Activity\Models\Favourite::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$book = Book::query()->first();
|
||||
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'favouritable_id' => $book->id,
|
||||
'favouritable_type' => 'book',
|
||||
];
|
||||
}
|
||||
}
|
||||
33
database/factories/Activity/Models/WatchFactory.php
Normal file
33
database/factories/Activity/Models/WatchFactory.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Activity\Models;
|
||||
|
||||
use BookStack\Activity\WatchLevels;
|
||||
use BookStack\Entities\Models\Book;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Activity\Models\Watch>
|
||||
*/
|
||||
class WatchFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Activity\Models\Watch::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$book = Book::factory()->create();
|
||||
|
||||
return [
|
||||
'user_id' => User::factory(),
|
||||
'watchable_id' => $book->id,
|
||||
'watchable_type' => 'book',
|
||||
'level' => WatchLevels::NEW,
|
||||
];
|
||||
}
|
||||
}
|
||||
29
database/factories/Entities/Models/DeletionFactory.php
Normal file
29
database/factories/Entities/Models/DeletionFactory.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Entities\Models;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\BookStack\Entities\Models\Deletion>
|
||||
*/
|
||||
class DeletionFactory extends Factory
|
||||
{
|
||||
protected $model = \BookStack\Entities\Models\Deletion::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'deleted_by' => User::factory(),
|
||||
'deletable_id' => Page::factory(),
|
||||
'deletable_type' => 'page',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,8 @@ class PageFactory extends Factory
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
public function definition(): array
|
||||
{
|
||||
$html = '<p>' . implode('</p>', $this->faker->paragraphs(5)) . '</p>';
|
||||
|
||||
|
||||
40
database/factories/Entities/Models/PageRevisionFactory.php
Normal file
40
database/factories/Entities/Models/PageRevisionFactory.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Factories\Entities\Models;
|
||||
|
||||
use BookStack\Entities\Models\Page;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class PageRevisionFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = \BookStack\Entities\Models\PageRevision::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
$html = '<p>' . implode('</p>', $this->faker->paragraphs(5)) . '</p>';
|
||||
$page = Page::query()->first();
|
||||
|
||||
return [
|
||||
'page_id' => $page->id,
|
||||
'name' => $this->faker->sentence(),
|
||||
'html' => $html,
|
||||
'text' => strip_tags($html),
|
||||
'created_by' => User::factory(),
|
||||
'slug' => $page->slug,
|
||||
'book_slug' => $page->book->slug,
|
||||
'type' => 'version',
|
||||
'markdown' => strip_tags($html),
|
||||
'summary' => $this->faker->sentence(),
|
||||
'revision_number' => rand(1, 4000),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -62,8 +62,9 @@ return new class extends Migration
|
||||
});
|
||||
}
|
||||
|
||||
// Convert image zero values to null
|
||||
// Convert image and activity zero values to null
|
||||
DB::table('images')->where('uploaded_to', '=', 0)->update(['uploaded_to' => null]);
|
||||
DB::table('activities')->where('loggable_id', '=', 0)->update(['loggable_id' => null]);
|
||||
|
||||
// Rebuild joint permissions if needed
|
||||
// This was moved here from 2023_01_24_104625_refactor_joint_permissions_storage since the changes
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
protected static array $toNullify = [
|
||||
'activities' => ['user_id'],
|
||||
'attachments' => ['created_by', 'updated_by'],
|
||||
'comments' => ['created_by', 'updated_by'],
|
||||
'deletions' => ['deleted_by'],
|
||||
'entities' => ['created_by', 'updated_by', 'owned_by'],
|
||||
'images' => ['created_by', 'updated_by'],
|
||||
'imports' => ['created_by'],
|
||||
'joint_permissions' => ['owner_id'],
|
||||
'page_revisions' => ['created_by'],
|
||||
];
|
||||
|
||||
protected static array $toClean = [
|
||||
'api_tokens' => ['user_id'],
|
||||
'email_confirmations' => ['user_id'],
|
||||
'favourites' => ['user_id'],
|
||||
'mfa_values' => ['user_id'],
|
||||
'role_user' => ['user_id'],
|
||||
'sessions' => ['user_id'],
|
||||
'social_accounts' => ['user_id'],
|
||||
'user_invites' => ['user_id'],
|
||||
'views' => ['user_id'],
|
||||
'watches' => ['user_id'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$idSelectQuery = DB::table('users')->select('id');
|
||||
|
||||
foreach (self::$toNullify as $tableName => $columns) {
|
||||
Schema::table($tableName, function (Blueprint $table) use ($columns) {
|
||||
foreach ($columns as $columnName) {
|
||||
$table->unsignedInteger($columnName)->nullable()->change();
|
||||
}
|
||||
});
|
||||
|
||||
foreach ($columns as $columnName) {
|
||||
DB::table($tableName)->where($columnName, '=', 0)->update([$columnName => null]);
|
||||
DB::table($tableName)->whereNotIn($columnName, $idSelectQuery)->update([$columnName => null]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::$toClean as $tableName => $columns) {
|
||||
foreach ($columns as $columnName) {
|
||||
DB::table($tableName)->whereNotIn($columnName, $idSelectQuery)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - Ensure each is fully handled in user delete
|
||||
// Start by writing tests for each of these columns
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
foreach (self::$toNullify as $tableName => $columns) {
|
||||
foreach ($columns as $columnName) {
|
||||
DB::table($tableName)->whereNull($columnName)->update([$columnName => 0]);
|
||||
}
|
||||
|
||||
Schema::table($tableName, function (Blueprint $table) use ($columns) {
|
||||
foreach ($columns as $columnName) {
|
||||
$table->unsignedInteger($columnName)->nullable(false)->change();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user