From dc87d0d41591e7530eb71e45fcc6a0be708e9c9a Mon Sep 17 00:00:00 2001 From: "Ralph J. Smit" <59207045+ralphjsmit@users.noreply.github.com> Date: Tue, 16 Aug 2022 13:20:23 +0200 Subject: [PATCH] Allow completely deleting a user with all the sites etc. --- app/Actions/User/DeleteUserAction.php | 50 ++++++++++++++ app/DataTransferObjects/UserData.php | 31 +++++---- .../Resources/UserResource/Pages/EditUser.php | 24 ++++++- .../Controllers/SiteDatabaseController.php | 16 ++--- app/Jobs/Databases/CreateDatabase.php | 9 ++- app/Models/Site.php | 66 ++++++++++++++----- app/Models/User.php | 17 +++-- app/Services/Ploi/Resources/Database.php | 12 ++-- 8 files changed, 168 insertions(+), 57 deletions(-) create mode 100644 app/Actions/User/DeleteUserAction.php diff --git a/app/Actions/User/DeleteUserAction.php b/app/Actions/User/DeleteUserAction.php new file mode 100644 index 0000000..7524eb0 --- /dev/null +++ b/app/Actions/User/DeleteUserAction.php @@ -0,0 +1,50 @@ +removeAllData($user); + } + + // The next items are already being deleted by the "deleting" event: + // systemLogs, servers detached, sites detached, supportTickets, supportTicketReplies, userProviders + $user->delete(); + } + + protected function removeAllData(User $user): void + { + $user + ->sites() + ->withCount('users') + ->get() + ->filter(fn (Site $site) => $site->users_count === 1) + ->each(function (Site $site) { + dispatch(new DeleteSite($site->server->ploi_id, $site->ploi_id)); + + // Deletes databases, redirects, cronjobs, certificates. + $site->delete(); + }); + + $user + ->servers() + ->withCount('users') + ->get() + ->filter(fn (Server $server) => $server->users_count === 1) + ->each(function (Server $server) { + dispatch(new DeleteServer($server->ploi_id)); + + // Deletes databases, redirects, cronjobs, certificates. + $server->delete(); + }); + } +} diff --git a/app/DataTransferObjects/UserData.php b/app/DataTransferObjects/UserData.php index 8b0233c..77c6c42 100644 --- a/app/DataTransferObjects/UserData.php +++ b/app/DataTransferObjects/UserData.php @@ -2,36 +2,35 @@ namespace App\DataTransferObjects; -use App\Models\User; -use App\Models\Package; -use Illuminate\Support\Carbon; use App\DataTransferObjects\Support\Data; -use Spatie\LaravelData\Attributes\Validation\Max; +use App\Models\Package; +use App\Models\User; +use Illuminate\Support\Carbon; +use Spatie\LaravelData\Attributes\Validation\BooleanType; use Spatie\LaravelData\Attributes\Validation\Email; use Spatie\LaravelData\Attributes\Validation\Exists; -use Spatie\LaravelData\Attributes\Validation\Unique; -use Spatie\LaravelData\Attributes\Validation\StringType; use Spatie\LaravelData\Attributes\Validation\IntegerType; +use Spatie\LaravelData\Attributes\Validation\Max; +use Spatie\LaravelData\Attributes\Validation\StringType; +use Spatie\LaravelData\Attributes\Validation\Unique; class UserData extends Data { public function __construct( public ?int $id = null, public ?string $avatar = null, - #[StringType, - Max(255)] + #[StringType, Max( 255 )] public ?string $name = null, - #[StringType, - Email, - Max(255), - Unique(User::class)] + #[StringType, Email, Max( 255 ), Unique( User::class )] public ?string $email = null, - #[Exists(Package::class, 'id'), - IntegerType] + #[Exists( Package::class, 'id' ), IntegerType] public ?int $package_id = null, #[StringType] public ?string $blocked = null, + #[StringType] + public ?string $language = 'en', + #[BooleanType] + public ?bool $requires_password_for_ftp = true, public ?Carbon $created_at = null, - ) { - } + ) {} } diff --git a/app/Filament/Resources/UserResource/Pages/EditUser.php b/app/Filament/Resources/UserResource/Pages/EditUser.php index 6fb46d4..e007d6d 100644 --- a/app/Filament/Resources/UserResource/Pages/EditUser.php +++ b/app/Filament/Resources/UserResource/Pages/EditUser.php @@ -2,9 +2,11 @@ namespace App\Filament\Resources\UserResource\Pages; -use Filament\Pages\Actions; +use App\Actions\User\DeleteUserAction; use App\Filament\Resources\UserResource; +use Filament\Forms\Components\Toggle; use Filament\Notifications\Notification; +use Filament\Pages\Actions; use Filament\Resources\Pages\EditRecord; class EditUser extends EditRecord @@ -27,7 +29,25 @@ class EditUser extends EditRecord }) ->visible(fn () => $this->record->hasTwoFactorEnabled()) ->requiresConfirmation(), - Actions\DeleteAction::make(), + Actions\Action::make('delete') + ->form([ + Toggle::make('remove_all_data') + ->label(__('Delete all sites, databases, etc.')) + ->default(true) + ->helperText(__('This will delete all the sites, databases, etc. associated with this user. Sites that belong to multiple users will not be deleted. This action cannot be undone.')), + ]) + ->requiresConfirmation() + ->action(function (array $data) { + app(DeleteUserAction::class)->execute($this->getRecord(), $data['remove_all_data']); + + Notification::make() + ->body(__('User deleted')) + ->success() + ->send(); + + $this->redirectRoute('filament.resources.users.index'); + }) + ->color('danger'), ]; } } diff --git a/app/Http/Controllers/SiteDatabaseController.php b/app/Http/Controllers/SiteDatabaseController.php index 4ba8499..4200ccf 100644 --- a/app/Http/Controllers/SiteDatabaseController.php +++ b/app/Http/Controllers/SiteDatabaseController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers; -use Illuminate\Support\Str; -use App\Jobs\Databases\CreateDatabase; -use App\Jobs\Databases\DeleteDatabase; use App\Http\Requests\SiteDatabaseRequest; use App\Http\Resources\SiteDatabaseResource; +use App\Jobs\Databases\CreateDatabase; +use App\Jobs\Databases\DeleteDatabase; +use Illuminate\Support\Str; class SiteDatabaseController extends Controller { @@ -16,7 +16,7 @@ class SiteDatabaseController extends Controller return inertia('Sites/Databases', [ 'site' => $site, - 'databases' => SiteDatabaseResource::collection($site->databases()->latest()->paginate()) + 'databases' => SiteDatabaseResource::collection($site->databases()->latest()->paginate()), ]); } @@ -25,21 +25,21 @@ class SiteDatabaseController extends Controller $site = auth()->user()->sites()->findOrFail($id); $database = $site->databases()->create([ - 'name' => Str::of($site->domain)->limit(8)->remove(['.', '-'])->lower()->append('_')->append($request->input('name')) + 'name' => Str::of($site->domain)->limit(8)->remove(['.', '-'])->lower()->append('_')->append($request->input('name')), ]); $database->users()->create([ - 'name' => $request->input('user_name', ), + 'name' => $request->input('user_name',), ]); $database->server_id = $site->server_id; $database->save(); - dispatch(new CreateDatabase($database, $request->input('password'))); + dispatch_sync(new CreateDatabase($database, $request->input('password'))); $request->user()->systemLogs()->create([ 'title' => 'New database :database created', - 'description' => 'A new database has been created' + 'description' => 'A new database has been created', ])->model()->associate($database)->save(); return redirect()->route('sites.databases.index', $id)->with('success', __('New database has been created')); diff --git a/app/Jobs/Databases/CreateDatabase.php b/app/Jobs/Databases/CreateDatabase.php index 28c2b0a..fafe58d 100644 --- a/app/Jobs/Databases/CreateDatabase.php +++ b/app/Jobs/Databases/CreateDatabase.php @@ -2,19 +2,20 @@ namespace App\Jobs\Databases; -use App\Traits\HasPloi; use App\Models\Database; +use App\Traits\HasPloi; use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\SerializesModels; class CreateDatabase implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, HasPloi; public $database; + public $password; /** @@ -43,9 +44,11 @@ class CreateDatabase implements ShouldQueue ->databases() ->create($this->database->name, $databaseUser->name, $this->password); + ray($ploiDatabase); $this->database->ploi_id = $ploiDatabase->id; $this->database->save(); + ray($ploiDatabase); $databaseUser->ploi_id = $ploiDatabase->users[0]->id; $databaseUser->save(); diff --git a/app/Models/Site.php b/app/Models/Site.php index ea898ac..574a9b0 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -2,12 +2,16 @@ namespace App\Models; -use DateTimeInterface; use App\Casts\SiteAlias; -use Illuminate\Support\Str; -use Illuminate\Database\Eloquent\Model; +use App\Jobs\Certificates\DeleteCertificate; +use App\Jobs\Cronjobs\DeleteCronjob; +use App\Jobs\Databases\DeleteDatabase; +use App\Jobs\Redirects\DeleteRedirect; +use DateTimeInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOneThrough; +use Illuminate\Support\Str; class Site extends Model { @@ -25,7 +29,7 @@ class Site extends Model 'domain', 'dns_id', 'project', - 'aliases' + 'aliases', ]; public $casts = [ @@ -34,7 +38,7 @@ class Site extends Model public function setDnsIdAttribute($value) { - if (!$value) { + if ( ! $value ) { return; } @@ -100,7 +104,7 @@ class Site extends Model public function getSystemUser($withPassword = true) { - if (setting('isolate_per_site_per_user') && $this->systemUsers()->first()) { + if ( setting('isolate_per_site_per_user') && $this->systemUsers()->first() ) { $user = $this->systemUsers()->first(); } else { $user = $this->users()->first(); @@ -108,7 +112,7 @@ class Site extends Model return [ 'user_name' => $user->user_name, - ] + ($withPassword ? ['ftp_password' => $user->ftp_password] : []); + ] + ( $withPassword ? ['ftp_password' => $user->ftp_password] : [] ); } public function addAlias($alias) @@ -135,14 +139,19 @@ class Site extends Model static::created(function (self $site) { $site->systemUsers()->create([ - 'user_name' => Str::of($site->domain)->remove(['.', '-'])->limit(8, '')->lower() + 'user_name' => Str::of($site->domain)->remove(['.', '-'])->limit(8, '')->lower(), ]); }); static::deleting(function (self $site) { - foreach ($site->databases as $database) { - $database->delete(); - } + $site + ->databases() + ->get() + ->each(function (Database $database) { + dispatch(new DeleteDatabase($database->server->ploi_id, $database->ploi_id)); + + $database->delete(); + }); $ids = $site->systemUsers->pluck('id'); // Detach all db users @@ -151,15 +160,42 @@ class Site extends Model // Loop through ids an remove old users. foreach ($ids as $id) { $record = SiteSystemUser::find($id); - if ($record) { + + if ( $record ) { $record->delete(); } } - $site->redirects()->delete(); - $site->cronjobs()->delete(); - $site->certificates()->delete(); + // MOETEN HIER OOK JOBS VOOR WORDEN GEDISPATCHET? + $site + ->redirects() + ->get() + ->each(function (Redirect $redirect) { + dispatch(new DeleteRedirect($redirect->server->ploi_id, $redirect->site->ploi_id, $redirect->ploi_id)); + + $redirect->delete(); + }); + + $site + ->cronjobs() + ->get() + ->each(function (Cronjob $cronJob) { + dispatch(new DeleteCronjob($cronJob->server->ploi_id, $cronJob->ploi_id)); + + $cronJob->delete(); + }); + + $site + ->certificates() + ->get() + ->each(function (Certificate $certificate) { + dispatch(new DeleteCertificate($certificate->server->ploi_id, $certificate->site->ploi_id, $certificate->ploi_id)); + + $certificate->delete(); + }); + $site->logs()->delete(); + $site->users()->detach(); }); } diff --git a/app/Models/User.php b/app/Models/User.php index fefecd9..d8a30e6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -39,7 +39,7 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth 'theme', 'keyboard_shortcuts', 'requires_password_for_ftp', - 'package_id' + 'package_id', ]; protected $hidden = [ @@ -57,12 +57,12 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth ]; protected $appends = [ - 'avatar' + 'avatar', ]; public function setPasswordAttribute($value) { - if (!$value) { + if ( ! $value ) { $this->attributes['password'] = null; } else { $this->attributes['password'] = bcrypt($value); @@ -86,7 +86,7 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth public function getGravatar($size = 150) { - return 'https://www.gravatar.com/avatar/' . md5(strtolower(trim(Arr::get($this->attributes, 'email')))) . '?s=' . (int)$size; + return 'https://www.gravatar.com/avatar/' . md5(strtolower(trim(Arr::get($this->attributes, 'email')))) . '?s=' . (int) $size; } public function isAdmin() @@ -144,11 +144,11 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth $user->user_name = strtolower(Str::random(10)); $user->ftp_password = Str::random(); - if (!$user->language) { + if ( ! $user->language ) { $user->language = setting('default_language', 'en'); } - if ($days = setting('trial')) { + if ( $days = setting('trial') ) { $user->trial_ends_at = now()->addDays($days); } }); @@ -156,7 +156,7 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth static::created(function (self $user) { // Usually I don't like using such conditions. However, otherwise when using Mail::fake(), // this would fake all emails going out leading to possible unexpected results as well. - if (! app()->runningUnitTests()) { + if ( ! app()->runningUnitTests() ) { Mail::to($user)->send(new WelcomeEmail($user)); } }); @@ -165,10 +165,13 @@ class User extends Authenticatable implements HasLocalePreference, TwoFactorAuth $user->systemLogs()->delete(); $user->servers()->detach(); $user->sites()->detach(); + foreach ($user->supportTickets as $supportTicket) { $supportTicket->replies()->delete(); } + $user->supportTickets()->delete(); + $user->providers()->delete(); }); } } diff --git a/app/Services/Ploi/Resources/Database.php b/app/Services/Ploi/Resources/Database.php index 5867f9c..853af89 100644 --- a/app/Services/Ploi/Resources/Database.php +++ b/app/Services/Ploi/Resources/Database.php @@ -2,8 +2,8 @@ namespace App\Services\Ploi\Resources; -use stdClass; use App\Services\Ploi\Exceptions\Http\NotValid; +use stdClass; class Database extends Resource { @@ -24,7 +24,7 @@ class Database extends Resource { $this->setEndpoint($this->getServer()->getEndpoint() . '/' . $this->getServer()->getId() . '/databases'); - if ($this->getId()) { + if ( $this->getId() ) { $this->setEndpoint($this->getEndpoint() . '/' . $this->getId()); } @@ -33,7 +33,7 @@ class Database extends Resource public function get(int $id = null) { - if ($id) { + if ( $id ) { $this->setId($id); } @@ -66,15 +66,15 @@ class Database extends Resource } // Set the id of the site - $this->setId($response->getJson()->data->id); + $this->setId($response->getData()->id); // Return the data - return $response->getJson()->data; + return $response->getData(); } public function delete(int $id): bool { - if ($id) { + if ( $id ) { $this->setId($id); }