feat: delete account
This commit is contained in:
parent
fd3d3959bd
commit
5c436fd65b
|
|
@ -8,9 +8,12 @@ use App\Http\Requests\UpdateUserRequest;
|
||||||
use App\Http\Resources\UserResource;
|
use App\Http\Resources\UserResource;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Resources\Json\JsonResource;
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class AuthController extends Controller
|
class AuthController extends Controller
|
||||||
{
|
{
|
||||||
|
|
@ -108,4 +111,42 @@ class AuthController extends Controller
|
||||||
|
|
||||||
return new UserResource($user->fresh());
|
return new UserResource($user->fresh());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
$storagePaths = collect([$user->avatar_url])
|
||||||
|
->merge($user->mealPosts()->withTrashed()->pluck('image_url'))
|
||||||
|
->filter(fn (?string $path): bool => $this->isPublicStoragePath($path))
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
DB::transaction(function () use ($user): void {
|
||||||
|
$user->tokens()->delete();
|
||||||
|
$user->notifications()->delete();
|
||||||
|
|
||||||
|
DB::table('sessions')
|
||||||
|
->where('user_id', $user->getKey())
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
DB::table('password_reset_tokens')
|
||||||
|
->where('email', $user->email)
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
$user->delete();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (! empty($storagePaths)) {
|
||||||
|
Storage::disk('public')->delete($storagePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Compte supprimé avec succès',
|
||||||
|
])->withoutCookie('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function isPublicStoragePath(?string $path): bool
|
||||||
|
{
|
||||||
|
return filled($path) && ! Str::startsWith($path, ['http://', 'https://']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ Route::prefix('auth')->group(function (): void {
|
||||||
Route::post('logout', [AuthController::class, 'logout']);
|
Route::post('logout', [AuthController::class, 'logout']);
|
||||||
Route::get('me', [AuthController::class, 'me']);
|
Route::get('me', [AuthController::class, 'me']);
|
||||||
Route::patch('me', [AuthController::class, 'update']);
|
Route::patch('me', [AuthController::class, 'update']);
|
||||||
|
Route::delete('me', [AuthController::class, 'destroy']);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\MealPosts;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
|
uses(RefreshDatabase::class);
|
||||||
|
|
||||||
|
it('deletes the authenticated account and related data', function () {
|
||||||
|
Storage::fake('public');
|
||||||
|
|
||||||
|
$user = User::factory()->create([
|
||||||
|
'avatar_url' => 'avatars/profile.jpg',
|
||||||
|
]);
|
||||||
|
|
||||||
|
Storage::disk('public')->put('avatars/profile.jpg', 'avatar');
|
||||||
|
Storage::disk('public')->put('meal-posts/local.jpg', 'meal');
|
||||||
|
|
||||||
|
$localMealPost = MealPosts::factory()->for($user, 'user')->create([
|
||||||
|
'image_url' => 'meal-posts/local.jpg',
|
||||||
|
]);
|
||||||
|
$remoteMealPost = MealPosts::factory()->for($user, 'user')->create([
|
||||||
|
'image_url' => 'https://example.com/meal.jpg',
|
||||||
|
]);
|
||||||
|
$deviceToken = $user->deviceTokens()->create([
|
||||||
|
'expo_push_token' => 'ExponentPushToken[account-deletion]',
|
||||||
|
'platform' => 'ios',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->createToken('api');
|
||||||
|
|
||||||
|
DB::table('notifications')->insert([
|
||||||
|
'id' => (string) Str::uuid(),
|
||||||
|
'type' => 'test',
|
||||||
|
'notifiable_type' => User::class,
|
||||||
|
'notifiable_id' => $user->getKey(),
|
||||||
|
'data' => json_encode(['message' => 'test'], JSON_THROW_ON_ERROR),
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]);
|
||||||
|
DB::table('sessions')->insert([
|
||||||
|
'id' => 'account-deletion-session',
|
||||||
|
'user_id' => $user->getKey(),
|
||||||
|
'ip_address' => '127.0.0.1',
|
||||||
|
'user_agent' => 'Pest',
|
||||||
|
'payload' => 'payload',
|
||||||
|
'last_activity' => now()->timestamp,
|
||||||
|
]);
|
||||||
|
DB::table('password_reset_tokens')->insert([
|
||||||
|
'email' => $user->email,
|
||||||
|
'token' => 'reset-token',
|
||||||
|
'created_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Sanctum::actingAs($user);
|
||||||
|
|
||||||
|
$this->deleteJson('/api/auth/me')
|
||||||
|
->assertOk()
|
||||||
|
->assertJsonPath('message', 'Compte supprimé avec succès');
|
||||||
|
|
||||||
|
$this->assertDatabaseMissing('users', ['id' => $user->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('meal_posts', ['id' => $localMealPost->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('meal_posts', ['id' => $remoteMealPost->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('device_tokens', ['id' => $deviceToken->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('personal_access_tokens', ['tokenable_id' => $user->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('notifications', ['notifiable_id' => $user->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('sessions', ['user_id' => $user->getKey()]);
|
||||||
|
$this->assertDatabaseMissing('password_reset_tokens', ['email' => $user->email]);
|
||||||
|
|
||||||
|
Storage::disk('public')->assertMissing('avatars/profile.jpg');
|
||||||
|
Storage::disk('public')->assertMissing('meal-posts/local.jpg');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('requires authentication to delete an account', function () {
|
||||||
|
$this->deleteJson('/api/auth/me')
|
||||||
|
->assertUnauthorized();
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue