Skip to content

Commit

Permalink
Fix foreign keys and deletion service
Browse files Browse the repository at this point in the history
  • Loading branch information
korridor committed Nov 5, 2024
1 parent 4224fdd commit 3b3f593
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 5 deletions.
18 changes: 18 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Jetstream\HasTeams;
use Laravel\Passport\AuthCode;
use Laravel\Passport\HasApiTokens;
use Laravel\Passport\Token;
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;

/**
Expand Down Expand Up @@ -178,6 +180,22 @@ public function projectMembers(): HasMany
return $this->hasMany(ProjectMember::class, 'user_id');
}

/**
* @return HasMany<Token>
*/
public function accessTokens(): HasMany
{
return $this->hasMany(Token::class);
}

/**
* @return HasMany<AuthCode>
*/
public function authCodes(): HasMany
{
return $this->hasMany(AuthCode::class);
}

/**
* @param Builder<User> $builder
*/
Expand Down
6 changes: 5 additions & 1 deletion app/Service/DeletionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ public function deleteUser(User $user, bool $inTransaction = true): void
->get();

foreach ($members as $member) {
/** @var Member $member */
if ($member->role === Role::Owner->value && $member->organization->users()->count() > 1) {
throw new CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
}
Expand All @@ -154,10 +155,13 @@ public function deleteUser(User $user, bool $inTransaction = true): void
if ($member->role === Role::Owner->value) {
$this->deleteOrganization($member->organization, false, $user);
} else {
$this->memberService->makeMemberToPlaceholder($member);
$this->memberService->makeMemberToPlaceholder($member, false);
}
}

$user->accessTokens()->delete();
$user->authCodes()->delete();

// Note: Since the deletion of the profile photo is not reversible via a database rollback this needs to be done last
$user->deleteProfilePhoto();

Expand Down
6 changes: 4 additions & 2 deletions app/Service/MemberService.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public function changeOwnership(Organization $organization, Member $newOwner): v
}
}

public function makeMemberToPlaceholder(Member $member): void
public function makeMemberToPlaceholder(Member $member, bool $makeSureUserHasAtLeastOneOrganization = true): void
{
$user = $member->user;
$placeholderUser = $user->replicate();
Expand All @@ -56,6 +56,8 @@ public function makeMemberToPlaceholder(Member $member): void
$member->save();

$this->userService->assignOrganizationEntitiesToDifferentMember($member->organization, $user, $placeholderUser, $member);
$this->userService->makeSureUserHasAtLeastOneOrganization($user);
if ($makeSureUserHasAtLeastOneOrganization) {
$this->userService->makeSureUserHasAtLeastOneOrganization($user);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$foreignKeyProblems = DB::table('organizations')
->select(['organizations.id', 'organizations.user_id'])
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('users')
->whereColumn('organizations.user_id', 'users.id');
})
->get();
foreach ($foreignKeyProblems as $foreignKeyProblem) {
Log::error('Organization with ID '.$foreignKeyProblem->id.' has non-existing owner with ID '.$foreignKeyProblem->user_id);
}
if ($foreignKeyProblems->count() > 0) {
throw new Exception('There are organizations with non-existing owners, check the logs for more information');
}
Schema::table('organizations', function (Blueprint $table): void {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict')
->onUpdate('cascade');
});
$foreignKeyProblems = DB::table('members')
->select(['members.id', 'members.organization_id'])
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('organizations')
->whereColumn('members.organization_id', 'organizations.id');
})
->get();
foreach ($foreignKeyProblems as $foreignKeyProblem) {
Log::error('Member with ID '.$foreignKeyProblem->id.' has non-existing organization with ID '.$foreignKeyProblem->organization_id);
}
if ($foreignKeyProblems->count() > 0) {
throw new Exception('There are members with non-existing organizations, check the logs for more information');
}
$foreignKeyProblems = DB::table('members')
->select(['members.id', 'members.user_id'])
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('users')
->whereColumn('members.user_id', 'users.id');
})
->get();
foreach ($foreignKeyProblems as $foreignKeyProblem) {
Log::error('Member with ID '.$foreignKeyProblem->id.' has non-existing user with ID '.$foreignKeyProblem->user_id);
}
if ($foreignKeyProblems->count() > 0) {
throw new Exception('There are members with non-existing users, check the logs for more information');
}
Schema::table('members', function (Blueprint $table): void {
$table->foreign('organization_id')
->references('id')
->on('organizations')
->onDelete('restrict')
->onUpdate('cascade');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict')
->onUpdate('cascade');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('organizations', function (Blueprint $table): void {
$table->dropForeign(['user_id']);
});
Schema::table('members', function (Blueprint $table): void {
$table->dropForeign(['organization_id']);
$table->dropForeign(['user_id']);
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Builder;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
DB::table('oauth_access_tokens')
->whereNotNull('user_id')
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('users')
->whereColumn('oauth_access_tokens.user_id', 'users.id');
})
->delete();
DB::table('oauth_access_tokens')
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('oauth_clients')
->whereColumn('oauth_access_tokens.client_id', 'oauth_clients.id');
})
->delete();
Schema::table('oauth_access_tokens', function (Blueprint $table): void {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict')
->onUpdate('cascade');
$table->foreign('client_id')
->references('id')
->on('oauth_clients')
->onDelete('restrict')
->onUpdate('cascade');
});
DB::table('oauth_auth_codes')
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('users')
->whereColumn('oauth_auth_codes.user_id', 'users.id');
})
->delete();
DB::table('oauth_auth_codes')
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('oauth_clients')
->whereColumn('oauth_auth_codes.client_id', 'oauth_clients.id');
})
->delete();
Schema::table('oauth_auth_codes', function (Blueprint $table): void {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict')
->onUpdate('cascade');
$table->foreign('client_id')
->references('id')
->on('oauth_clients')
->onDelete('restrict')
->onUpdate('cascade');
});
DB::table('oauth_clients')
->whereNotNull('user_id')
->whereNotExists(function (Builder $query): void {
$query->select('id')
->from('users')
->whereColumn('oauth_clients.user_id', 'users.id');
})
->delete();
Schema::table('oauth_clients', function (Blueprint $table): void {
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('restrict')
->onUpdate('cascade');
});
Schema::table('oauth_personal_access_clients', function (Blueprint $table): void {
$table->foreign('client_id')
->references('id')
->on('oauth_clients')
->onDelete('restrict')
->onUpdate('cascade');
});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('oauth_access_tokens', function (Blueprint $table): void {
$table->dropForeign(['user_id']);
$table->dropForeign(['client_id']);
});
Schema::table('oauth_auth_codes', function (Blueprint $table): void {
$table->dropForeign(['user_id']);
$table->dropForeign(['client_id']);
});
Schema::table('oauth_clients', function (Blueprint $table): void {
$table->dropForeign(['user_id']);
});
Schema::table('oauth_personal_access_clients', function (Blueprint $table): void {
$table->dropForeign(['client_id']);
});
}
};
34 changes: 33 additions & 1 deletion database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
use Laravel\Passport\AuthCode;
use Laravel\Passport\Client as PassportClient;
use Laravel\Passport\ClientRepository;
use Laravel\Passport\PersonalAccessClient;
use Laravel\Passport\RefreshToken;
use Laravel\Passport\Token;

class DatabaseSeeder extends Seeder
{
Expand Down Expand Up @@ -150,19 +156,45 @@ public function run(): void
User::factory()->withPersonalOrganization()->create([
'email' => 'admin@example.com',
]);

app(ClientRepository::class)->create(
null,
'desktop',
'solidtime://oauth/callback',
null,
false,
false,
false
);
}

private function deleteAll(): void
{
// Laravel Passport tables
DB::table((new RefreshToken)->getTable())->delete();
DB::table((new Token)->getTable())->delete();
DB::table((new AuthCode)->getTable())->delete();
DB::table((new PersonalAccessClient)->getTable())->delete();
DB::table((new PassportClient)->getTable())->delete();

// Internal tables
DB::table('cache')->delete();
DB::table('cache_locks')->delete();
DB::table('jobs')->delete();
DB::table('failed_jobs')->delete();
DB::table('sessions')->delete();

// Application tables
DB::table((new Audit)->getTable())->delete();
DB::table((new TimeEntry)->getTable())->delete();
DB::table((new Task)->getTable())->delete();
DB::table((new Tag)->getTable())->delete();
DB::table((new ProjectMember)->getTable())->delete();
DB::table((new Project)->getTable())->delete();
DB::table((new Client)->getTable())->delete();
DB::table((new User)->getTable())->delete();
DB::table((new Member)->getTable())->delete();
DB::table((new OrganizationInvitation)->getTable())->delete();
DB::table((new Organization)->getTable())->delete();
DB::table((new User)->getTable())->delete();
}
}
Loading

0 comments on commit 3b3f593

Please sign in to comment.