From 30705fd1945c7720ca966abd6afa26b5756d8a07 Mon Sep 17 00:00:00 2001 From: AfonsoCMSousa Date: Tue, 23 Dec 2025 21:39:38 +0000 Subject: [PATCH] SyntaxSquad/DADProject#10 feat: Account deletion (soft delete) --- api/app/Models/User.php | 30 +++--- api/routes/api.php | 16 ++- frontend/src/pages/user/UserPage.vue | 147 ++++++++++++++++++++++++++- 3 files changed, 173 insertions(+), 20 deletions(-) diff --git a/api/app/Models/User.php b/api/app/Models/User.php index ae8f8d0..9209a76 100644 --- a/api/app/Models/User.php +++ b/api/app/Models/User.php @@ -2,7 +2,6 @@ namespace App\Models; -// use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -11,30 +10,27 @@ use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { - /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, HasApiTokens, SoftDeletes; + use HasApiTokens, HasFactory, Notifiable, SoftDeletes; /** * The attributes that are mass assignable. * - * @var list + * @var array */ protected $fillable = [ 'name', 'email', 'password', 'nickname', - 'photo_avatar_filename', 'type', - 'blocked', + 'photo_avatar_filename', 'coins_balance', - 'custom', ]; /** * The attributes that should be hidden for serialization. * - * @var list + * @var array */ protected $hidden = [ 'password', @@ -42,16 +38,14 @@ class User extends Authenticatable ]; /** - * Get the attributes that should be cast. + * The attributes that should be cast. * - * @return array + * @var array */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - 'blocked' => 'boolean', - ]; - } + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'blocked' => 'boolean', + 'deleted_at' => 'datetime', + ]; } diff --git a/api/routes/api.php b/api/routes/api.php index e3a0fe3..e14d021 100644 --- a/api/routes/api.php +++ b/api/routes/api.php @@ -1,5 +1,4 @@ group(function () { $user = $request->user(); + // Delete old avatar if exists if ($user->photo_avatar_filename) { \Storage::disk('public')->delete('photos_avatars/' . $user->photo_avatar_filename); } + // Store new avatar $file = $request->file('avatar'); $filename = str_pad($user->id, 5, '0', STR_PAD_LEFT) . '_' . \Str::random(10) . '.' . $file->extension(); $file->storeAs('photos_avatars', $filename, 'public'); @@ -79,6 +80,19 @@ Route::middleware('auth:sanctum')->group(function () { return response()->json($user); }); + // Delete account + Route::delete('/me', function (Request $request) { + $user = $request->user(); + + $user->tokens()->delete(); + + $user->delete(); + + return response()->json([ + 'message' => 'Account deleted successfully' + ]); + }); + Route::get('/{user}/matches', [UserController::class, 'getMatches']); }); diff --git a/frontend/src/pages/user/UserPage.vue b/frontend/src/pages/user/UserPage.vue index 0de4d6c..db6654f 100644 --- a/frontend/src/pages/user/UserPage.vue +++ b/frontend/src/pages/user/UserPage.vue @@ -87,6 +87,12 @@ > Change Password + @@ -275,11 +281,124 @@ + + +
+
+
+
+ + + + + +
+

Danger Zone

+

+ Deleting your account is permanent and cannot be undone. This action will: +

+
    +
  • Permanently delete all your account data
  • +
  • Forfeit your current coin balance of {{ authStore.currentUser.coins_balance }} coins
  • +
  • Remove your access to all games and matches
  • +
  • Delete your profile and all associated information
  • +
+
+
+
+ +
+

+ Are you absolutely sure? +

+

+ To confirm deletion, please type your email address: {{ authStore.currentUser.email }} +

+ + + +
+ + +
+
+
+
+ + + + +
+
+
+

Final Confirmation

+
+ +
+
+ + + + + +
+

+ This is your last chance to cancel. +

+

+ Once you click "Yes, Delete My Account", your account and all associated data will be permanently deleted. You will lose your {{ authStore.currentUser.coins_balance }} coins forever. +

+
+
+ +
+
+

Deleting your account...

+
+
+ +
+ + +
-
+

Redirecting to login...

@@ -324,6 +443,11 @@ const updatingPassword = ref(false) const passwordMessage = ref('') const passwordMessageType = ref('') +// Delete account +const deleteConfirmEmail = ref('') +const showDeleteConfirmation = ref(false) +const deletingAccount = ref(false) + const formatDate = (dateString) => { const date = new Date(dateString) return date.toLocaleDateString('en-US', { @@ -457,6 +581,27 @@ const handleAvatarUpload = async (event) => { } } +const deleteAccount = async () => { + deletingAccount.value = true + + try { + await axios.delete(`${API_BASE_URL}/users/me`) + + // Logout user + await authStore.logout() + + // Close modal + showDeleteConfirmation.value = false + + // Redirect to home/login + router.push('/login') + } catch (err) { + deletingAccount.value = false + showDeleteConfirmation.value = false + alert(err.response?.data?.message || 'Failed to delete account') + } +} + onMounted(async () => { if (!authStore.isLoggedIn) { router.push('/login') -- 2.54.0