From 5bcdc2fb5dc48a064daf8268de427adf01670c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=BCnther?= Date: Sun, 26 Apr 2026 01:15:58 +0200 Subject: [PATCH] Personal data and password change --- .ai/conventions.md | 10 + .../UpdatePersonalDataCommand.php | 23 +++ .../UpdatePersonalDataRequest.php | 29 +++ .../UpdatePersonalDataResponse.php | 8 + .../Controllers/MessagesController.php | 14 ++ .../Controllers/PersonalDataController.php | 45 +++++ .../StorePersonalDataController.php | 43 ++++ app/Domains/Dashboard/Routes/api.php | 4 + app/Domains/Dashboard/Routes/web.php | 15 ++ app/Domains/Dashboard/Views/Messages.vue | 25 +++ .../Widgets/MyParticipationsShort.vue | 4 +- app/Domains/Dashboard/Views/PersonalData.vue | 186 ++++++++++++++++++ .../Controllers/ProfileController.php | 24 +++ .../Controllers/StoreProfileController.php | 37 ++++ app/Domains/UserManagement/Routes/api.php | 4 +- app/Domains/UserManagement/Routes/web.php | 3 + app/Domains/UserManagement/Views/Profile.vue | 100 ++++++++++ app/Repositories/UserRepository.php | 48 +++++ routes/api.php | 4 - routes/web.php | 20 +- 20 files changed, 620 insertions(+), 26 deletions(-) create mode 100644 app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataCommand.php create mode 100644 app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataRequest.php create mode 100644 app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataResponse.php create mode 100644 app/Domains/Dashboard/Controllers/MessagesController.php create mode 100644 app/Domains/Dashboard/Controllers/PersonalDataController.php create mode 100644 app/Domains/Dashboard/Controllers/StorePersonalDataController.php create mode 100644 app/Domains/Dashboard/Views/Messages.vue create mode 100644 app/Domains/Dashboard/Views/PersonalData.vue create mode 100644 app/Domains/UserManagement/Controllers/ProfileController.php create mode 100644 app/Domains/UserManagement/Controllers/StoreProfileController.php create mode 100644 app/Domains/UserManagement/Views/Profile.vue diff --git a/.ai/conventions.md b/.ai/conventions.md index 45ac0f3..f703cdc 100644 --- a/.ai/conventions.md +++ b/.ai/conventions.md @@ -28,6 +28,8 @@ Pfad: `app/Domains/{Domain}/Actions/{ActionName}/` - `$this->users` → `UserRepository` - `$this->tenant` → aktueller `Tenant` +- Die Controller besitzen ausschließlich eine __invoke() - Funktion +- Für die Speichern-Actions werden separate Controller-Klassen erstellt (z. B. `StoreEventParticipantController`) --- ## Repositories @@ -69,3 +71,11 @@ Pfad: `app/Domains/{Domain}/Actions/{ActionName}/` - Attachments werden über `Attachment::fromData(fn () => $content, $filename)->withMime(...)` angehängt - Werden Daten sowohl in `content()` als auch in `attachments()` benötigt, wird eine **private Hilfsmethode mit Lazy-Caching** verwendet (einmaliges Berechnen, Ergebnis in private Property speichern) - Blade-Templates referenzieren Mail-Attachments per `cid:`-Link: `...` + +## Actions + - Die Actions sind in `app/Domains/{Domain}/Actions/{ActionName}/`-Verzeichnissen organisiert + - Jede Action besitzt einen Request, einen Command und einen Response + - Der Request besitzt auschließlich einen Konstruktor, der die notwendigen Parameter annimmt + - Die Response-Klasse enthält ausschließlich die notwendigen Daten für die Antwort + - Die Action-Klasse enthält die Logik für die Verarbeitung der Anfrage und die Generierung der Antwort + - Die Logik wird in einer execute() - Funktion innerhalb des Commands impplementiert. Private Funktionen, für ausgelagerte Prozesse sind zulässig, wenn der Code damit lesbarer wird. diff --git a/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataCommand.php b/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataCommand.php new file mode 100644 index 0000000..c95b7f6 --- /dev/null +++ b/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataCommand.php @@ -0,0 +1,23 @@ +users->updatePersonalData($this->request); + + $response = new UpdatePersonalDataResponse(); + $response->success = true; + + return $response; + } +} diff --git a/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataRequest.php b/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataRequest.php new file mode 100644 index 0000000..25046aa --- /dev/null +++ b/app/Domains/Dashboard/Actions/UpdatePersonalData/UpdatePersonalDataRequest.php @@ -0,0 +1,29 @@ +render(); + } +} diff --git a/app/Domains/Dashboard/Controllers/PersonalDataController.php b/app/Domains/Dashboard/Controllers/PersonalDataController.php new file mode 100644 index 0000000..849784e --- /dev/null +++ b/app/Domains/Dashboard/Controllers/PersonalDataController.php @@ -0,0 +1,45 @@ +checkAuth()) { + return redirect()->intended('/login'); + } + + $user = auth()->user(); + $data = $this->users->getPersonalData($user); + + $inertiaProvider = new InertiaProvider('Dashboard/PersonalData', [ + 'personalData' => [ + 'firstname' => $data['firstname'], + 'lastname' => $data['lastname'], + 'birthday' => $data['birthday'], + 'nickname' => $data['nickname'], + 'email' => $data['email'], + 'phone' => $data['phone'], + 'address1' => $data['address_1'], + 'address2' => $data['address_2'], + 'postcode' => $data['postcode'], + 'city' => $data['city'], + 'medications' => $data['medications'], + 'allergies' => $data['allergies'], + 'intolerances' => $data['intolerances'], + 'eatingHabits' => $data['eating_habits'], + 'swimmingPermission' => $data['swimming_permission'], + 'firstAidPermission' => $data['first_aid_permission'], + 'bankAccountOwner' => $data['bank_account_owner'], + 'bankAccountIban' => $data['bank_account_iban'], + 'tetanusVaccination' => $data['tetanus_vaccination'], + ], + ]); + + return $inertiaProvider->render(); + } +} diff --git a/app/Domains/Dashboard/Controllers/StorePersonalDataController.php b/app/Domains/Dashboard/Controllers/StorePersonalDataController.php new file mode 100644 index 0000000..c809e82 --- /dev/null +++ b/app/Domains/Dashboard/Controllers/StorePersonalDataController.php @@ -0,0 +1,43 @@ +user(); + + $actionRequest = new UpdatePersonalDataRequest( + user: $user, + nickname: $request->input('nickname'), + email: $request->input('email'), + phone: $request->input('phone'), + address1: $request->input('address1'), + address2: $request->input('address2'), + postcode: $request->input('postcode'), + city: $request->input('city'), + medications: $request->input('medications'), + allergies: $request->input('allergies'), + intolerances: $request->input('intolerances'), + eatingHabits: $request->input('eatingHabits'), + swimmingPermission: $request->input('swimmingPermission'), + firstAidPermission: $request->input('firstAidPermission'), + bankAccountOwner: $request->input('bankAccountOwner'), + bankAccountIban: $request->input('bankAccountIban'), + birthday: $request->input('birthday'), + tetanusVaccination: $request->input('tetanusVaccination'), + ); + + $command = new UpdatePersonalDataCommand($actionRequest, $this->users); + $command->execute(); + + return response()->json(['success' => true, 'message' => 'Deine Daten wurden erfolgreich gespeichert.']); + } +} diff --git a/app/Domains/Dashboard/Routes/api.php b/app/Domains/Dashboard/Routes/api.php index a9d4cc9..73f0df6 100644 --- a/app/Domains/Dashboard/Routes/api.php +++ b/app/Domains/Dashboard/Routes/api.php @@ -1,6 +1,7 @@ group(function () { Route::get('/open-cost-units', [DashboardController::class, 'getOpenCostUnits']); Route::get('/upcoming-events', [DashboardController::class, 'getUpcomingEvents']); Route::get('/my-participations', [DashboardController::class, 'getMyParticipations']); + Route::post('/personal-data', StorePersonalDataController::class); }); + + }); }); diff --git a/app/Domains/Dashboard/Routes/web.php b/app/Domains/Dashboard/Routes/web.php index b3d9bbc..0945f7d 100644 --- a/app/Domains/Dashboard/Routes/web.php +++ b/app/Domains/Dashboard/Routes/web.php @@ -1 +1,16 @@ group(function () { + Route::middleware(['auth'])->group(function () { + Route::get('/personal-data', PersonalDataController::class); + Route::get('/messages', MessagesController::class); + + + }); +}); diff --git a/app/Domains/Dashboard/Views/Messages.vue b/app/Domains/Dashboard/Views/Messages.vue new file mode 100644 index 0000000..31915c0 --- /dev/null +++ b/app/Domains/Dashboard/Views/Messages.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/app/Domains/Dashboard/Views/Partials/Widgets/MyParticipationsShort.vue b/app/Domains/Dashboard/Views/Partials/Widgets/MyParticipationsShort.vue index f453d67..b174a33 100644 --- a/app/Domains/Dashboard/Views/Partials/Widgets/MyParticipationsShort.vue +++ b/app/Domains/Dashboard/Views/Partials/Widgets/MyParticipationsShort.vue @@ -27,8 +27,8 @@ function navigateTo(url) { {{participation.arrivalDateReadable}} - {{participation.departureDateReadable}} - - + + diff --git a/app/Domains/Dashboard/Views/PersonalData.vue b/app/Domains/Dashboard/Views/PersonalData.vue new file mode 100644 index 0000000..6e52699 --- /dev/null +++ b/app/Domains/Dashboard/Views/PersonalData.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/app/Domains/UserManagement/Controllers/ProfileController.php b/app/Domains/UserManagement/Controllers/ProfileController.php new file mode 100644 index 0000000..d2a7578 --- /dev/null +++ b/app/Domains/UserManagement/Controllers/ProfileController.php @@ -0,0 +1,24 @@ +checkAuth()) { + return redirect()->intended('/login'); + } + + $user = auth()->user(); + + $inertiaProvider = new InertiaProvider('UserManagement/Profile', [ + 'username' => $user->username, + ]); + + return $inertiaProvider->render(); + } +} diff --git a/app/Domains/UserManagement/Controllers/StoreProfileController.php b/app/Domains/UserManagement/Controllers/StoreProfileController.php new file mode 100644 index 0000000..b8edeaf --- /dev/null +++ b/app/Domains/UserManagement/Controllers/StoreProfileController.php @@ -0,0 +1,37 @@ +checkAuth()) { + return response()->json(['success' => false, 'message' => 'Unauthorized'], 401); + } + + $password = $request->input('password'); + $passwordConfirmation = $request->input('password_confirmation'); + + if (empty($password)) { + return response()->json(['success' => false, 'message' => 'Bitte ein Passwort eingeben.'], 422); + } + + if ($password !== $passwordConfirmation) { + return response()->json(['success' => false, 'message' => 'Die Passwörter stimmen nicht überein.'], 422); + } + + $actionRequest = new UserChangePasswordRequest(auth()->user(), $password); + $command = new UserChangePasswordCommand($actionRequest); + $command->execute(); + + auth()->logout(); + return response()->json(['success' => true, 'message' => 'Dein Passwort wurde erfolgreich geändert.']); + } +} diff --git a/app/Domains/UserManagement/Routes/api.php b/app/Domains/UserManagement/Routes/api.php index bb8f8f6..9451d35 100644 --- a/app/Domains/UserManagement/Routes/api.php +++ b/app/Domains/UserManagement/Routes/api.php @@ -3,13 +3,15 @@ use App\Domains\UserManagement\Controllers\EmailVerificationController; use App\Domains\UserManagement\Controllers\RegistrationController; use App\Domains\UserManagement\Controllers\ResetPasswordController; +use App\Domains\UserManagement\Controllers\StoreProfileController; use App\Middleware\IdentifyTenant; use App\Providers\GlobalDataProvider; use Illuminate\Support\Facades\Route; use Inertia\Inertia; -Route::prefix('v1') +Route::prefix('/api/v1') ->group(function () { Route::middleware(IdentifyTenant::class)->group(function () { + Route::post('/profile', StoreProfileController::class); }); }); diff --git a/app/Domains/UserManagement/Routes/web.php b/app/Domains/UserManagement/Routes/web.php index 70ed29e..adfcd86 100644 --- a/app/Domains/UserManagement/Routes/web.php +++ b/app/Domains/UserManagement/Routes/web.php @@ -3,6 +3,7 @@ use App\Domains\UserManagement\Controllers\EmailVerificationController; use App\Domains\UserManagement\Controllers\LoginController; use App\Domains\UserManagement\Controllers\LogOutController; +use App\Domains\UserManagement\Controllers\ProfileController; use App\Domains\UserManagement\Controllers\RegistrationController; use App\Domains\UserManagement\Controllers\ResetPasswordController; use App\Middleware\IdentifyTenant; @@ -20,6 +21,8 @@ Route::middleware(IdentifyTenant::class)->group(function () { Route::middleware(['auth'])->group(function () { Route::post('/logout', [LogoutController::class, 'logout']); + Route::get('/profile', ProfileController::class); + }); }); diff --git a/app/Domains/UserManagement/Views/Profile.vue b/app/Domains/UserManagement/Views/Profile.vue new file mode 100644 index 0000000..6b20d08 --- /dev/null +++ b/app/Domains/UserManagement/Views/Profile.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php index 48788ae..c41639a 100644 --- a/app/Repositories/UserRepository.php +++ b/app/Repositories/UserRepository.php @@ -2,6 +2,7 @@ namespace App\Repositories; +use App\Domains\Dashboard\Actions\UpdatePersonalData\UpdatePersonalDataRequest; use App\Models\User; use DateTime; @@ -47,4 +48,51 @@ class UserRepository { return $return; } + + public function getPersonalData(User $user): array + { + return [ + 'firstname' => $user->firstname ?? '', + 'lastname' => $user->lastname ?? '', + 'birthday' => $user->birthday ?? null, + 'nickname' => $user->nickname ?? null, + 'email' => $user->email ?? null, + 'phone' => $user->phone ?? null, + 'address_1' => $user->address_1 ?? null, + 'address_2' => $user->address_2 ?? null, + 'postcode' => $user->postcode ?? null, + 'city' => $user->city ?? null, + 'medications' => $user->medications ?? null, + 'allergies' => $user->allergies ?? null, + 'intolerances' => $user->intolerances ?? null, + 'tetanus_vaccination' => $user->tetanus_vaccination ?? null, + 'eating_habits' => $user->eating_habits ?? null, + 'swimming_permission' => $user->swimming_permission ?? null, + 'first_aid_permission' => $user->first_aid_permission ?? null, + 'bank_account_owner' => $user->bank_account_owner ?? null, + 'bank_account_iban' => $user->bank_account_iban ?? null, + ]; + } + + public function updatePersonalData(UpdatePersonalDataRequest $request): void + { + $request->user->update([ + 'nickname' => $request->nickname, + 'email' => $request->email, + 'phone' => $request->phone, + 'address_1' => $request->address1, + 'address_2' => $request->address2, + 'postcode' => $request->postcode, + 'city' => $request->city, + 'medications' => $request->medications, + 'allergies' => $request->allergies, + 'tetanus_vaccination' => $request->tetanusVaccination, + 'intolerances' => $request->intolerances, + 'eating_habits' => $request->eatingHabits, + 'swimming_permission' => $request->swimmingPermission, + 'first_aid_permission' => $request->firstAidPermission, + 'bank_account_owner' => $request->bankAccountOwner, + 'bank_account_iban' => $request->bankAccountIban, + ]); + } } diff --git a/routes/api.php b/routes/api.php index 41361f1..6c51b9e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,14 +1,10 @@ group(function () { diff --git a/routes/web.php b/routes/web.php index e73d547..6c8364c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Route; require_once __DIR__ . '/../app/Domains/Dashboard/Routes/web.php'; require_once __DIR__ . '/../app/Domains/Dashboard/Routes/api.php'; require_once __DIR__ . '/../app/Domains/UserManagement/Routes/web.php'; +require_once __DIR__ . '/../app/Domains/UserManagement/Routes/api.php'; require_once __DIR__ . '/../app/Domains/CostUnit/Routes/web.php'; require_once __DIR__ . '/../app/Domains/CostUnit/Routes/api.php'; require_once __DIR__ . '/../app/Domains/Invoice/Routes/web.php'; @@ -38,25 +39,6 @@ Route::middleware(IdentifyTenant::class)->group(function () { Route::get('/retrieve-event-setting-data', [GlobalDataProvider::class, 'getEventSettingData']); }); - - - - - Route::middleware(['auth'])->group(function () { - - Route::get('/messages', fn () => inertia('Messages')); - - }); - - - - - - - - - - Route::get('/messages', [TestRenderInertiaProvider::class, 'index']); });