mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-14 19:22:14 +01:00
Added user and organization deletion system; Added coverage annotations
This commit is contained in:
committed by
Constantin Graf
parent
8857befc6c
commit
86f5ea47bb
162
app/Service/DeletionService.php
Normal file
162
app/Service/DeletionService.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Enums\Role;
|
||||
use App\Events\BeforeOrganizationDeletion;
|
||||
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
||||
use App\Models\Client;
|
||||
use App\Models\Member;
|
||||
use App\Models\Organization;
|
||||
use App\Models\OrganizationInvitation;
|
||||
use App\Models\Project;
|
||||
use App\Models\ProjectMember;
|
||||
use App\Models\Tag;
|
||||
use App\Models\Task;
|
||||
use App\Models\TimeEntry;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeletionService
|
||||
{
|
||||
private UserService $userService;
|
||||
|
||||
public function __construct(UserService $userService)
|
||||
{
|
||||
$this->userService = $userService;
|
||||
}
|
||||
|
||||
public function deleteOrganization(Organization $organization, bool $inTransaction = true): void
|
||||
{
|
||||
if ($inTransaction) {
|
||||
DB::transaction(function () use ($organization) {
|
||||
$this->deleteOrganization($organization, false);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::debug('Start deleting organization', [
|
||||
'organization_id' => $organization->getKey(),
|
||||
'name' => $organization->name,
|
||||
'owner_id' => $organization->user_id,
|
||||
]);
|
||||
|
||||
BeforeOrganizationDeletion::dispatch($organization);
|
||||
|
||||
// Delete all organization invitations
|
||||
OrganizationInvitation::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Delete all time entries
|
||||
TimeEntry::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Delete all tags
|
||||
Tag::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Delete all tasks
|
||||
Task::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Delete all project members
|
||||
ProjectMember::query()->whereBelongsToOrganization($organization)->delete();
|
||||
|
||||
// Delete all projects
|
||||
Project::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Delete all clients
|
||||
Client::query()->whereBelongsTo($organization, 'organization')->delete();
|
||||
|
||||
// Reset the current organization
|
||||
$organization->owner()
|
||||
->where('current_team_id', $organization->getKey())
|
||||
->update(['current_team_id' => null]);
|
||||
|
||||
$organization->users()
|
||||
->where('current_team_id', $organization->getKey())
|
||||
->update(['current_team_id' => null]);
|
||||
|
||||
// Delete all members
|
||||
$users = $organization->users()
|
||||
->with([
|
||||
'currentOrganization',
|
||||
])
|
||||
->get();
|
||||
$organization->users()->sync([]);
|
||||
|
||||
// Make sure all users have at least one organization
|
||||
foreach ($users as $user) {
|
||||
if ($user->is_placeholder) {
|
||||
$user->delete();
|
||||
} else {
|
||||
$this->userService->makeSureUserHasAtLeastOneOrganization($user);
|
||||
$this->userService->makeSureUserHasCurrentOrganization($user);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete organization
|
||||
$organization->delete();
|
||||
|
||||
Log::debug('Finished deleting organization', [
|
||||
'organization_id' => $organization->getKey(),
|
||||
'name' => $organization->name,
|
||||
'owner_id' => $organization->user_id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers
|
||||
*/
|
||||
public function deleteUser(User $user, bool $inTransaction = true): void
|
||||
{
|
||||
if ($inTransaction) {
|
||||
DB::transaction(function () use ($user) {
|
||||
$this->deleteUser($user, false);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Log::debug('Start deleting user', [
|
||||
'id' => $user->getKey(),
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
]);
|
||||
|
||||
$members = Member::query()->whereBelongsTo($user, 'user')
|
||||
->with([
|
||||
'organization',
|
||||
'user',
|
||||
])
|
||||
->get();
|
||||
|
||||
foreach ($members as $member) {
|
||||
if ($member->role === Role::Owner->value && $member->organization->users()->count() > 1) {
|
||||
throw new CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Member $member */
|
||||
foreach ($members as $member) {
|
||||
if ($member->role === Role::Owner->value) {
|
||||
// Note: The member needs to be deleted first, otherwise the organization delete function will recreate a new personal organization for the user
|
||||
$member->delete();
|
||||
$this->deleteOrganization($member->organization, false);
|
||||
} else {
|
||||
$this->userService->makeMemberToPlaceholder($member);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Since the deletion of the profile photo is not reversible via a database rollback this needs to be done last
|
||||
$user->deleteProfilePhoto();
|
||||
|
||||
$user->delete();
|
||||
|
||||
Log::debug('Finished deleting user', [
|
||||
'id' => $user->getKey(),
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,11 @@ class PermissionStore
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->userHas($organization, $user, $permission);
|
||||
}
|
||||
|
||||
public function userHas(Organization $organization, User $user, string $permission): bool
|
||||
{
|
||||
if (! isset($this->permissionCache[$user->getKey().'|'.$organization->getKey()])) {
|
||||
if (! $user->belongsToTeam($organization)) {
|
||||
return false;
|
||||
|
||||
@@ -28,6 +28,11 @@ class UserService
|
||||
throw new \InvalidArgumentException('User is not a member of the organization');
|
||||
}
|
||||
|
||||
$this->assignOrganizationEntitiesToDifferentMember($organization, $fromUser, $toUser, $toMember);
|
||||
}
|
||||
|
||||
private function assignOrganizationEntitiesToDifferentMember(Organization $organization, User $fromUser, User $toUser, Member $toMember): void
|
||||
{
|
||||
// Time entries
|
||||
TimeEntry::query()
|
||||
->whereBelongsTo($organization, 'organization')
|
||||
@@ -47,6 +52,53 @@ class UserService
|
||||
]);
|
||||
}
|
||||
|
||||
public function makeMemberToPlaceholder(Member $member): void
|
||||
{
|
||||
$user = $member->user;
|
||||
$placeholderUser = $user->replicate();
|
||||
$placeholderUser->is_placeholder = true;
|
||||
$placeholderUser->save();
|
||||
|
||||
$member->user()->associate($placeholderUser);
|
||||
$member->role = Role::Placeholder->value;
|
||||
$member->save();
|
||||
|
||||
$this->assignOrganizationEntitiesToDifferentMember($member->organization, $user, $placeholderUser, $member);
|
||||
$this->makeSureUserHasAtLeastOneOrganization($user);
|
||||
}
|
||||
|
||||
public function makeSureUserHasAtLeastOneOrganization(User $user): void
|
||||
{
|
||||
if ($user->organizations()->count() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new organization
|
||||
$organization = new Organization();
|
||||
$organization->name = $user->name."'s Organization";
|
||||
$organization->personal_team = true;
|
||||
$organization->user_id = $user->id;
|
||||
$organization->save();
|
||||
|
||||
// Attach the user to the organization
|
||||
$organization->users()->attach($user, ['role' => Role::Owner->value]);
|
||||
|
||||
// Set the organization as the user's current organization
|
||||
$user->currentOrganization()->associate($organization);
|
||||
$user->save();
|
||||
}
|
||||
|
||||
public function makeSureUserHasCurrentOrganization(User $user): void
|
||||
{
|
||||
if ($user->currentOrganization !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$organization = $user->organizations()->first();
|
||||
$user->currentOrganization()->associate($organization);
|
||||
$user->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the ownership of an organization to a new user.
|
||||
* The previous owner will be demoted to an admin.
|
||||
|
||||
Reference in New Issue
Block a user