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 @@
+
+
+
+
+
+ Diese Funktion steht aktuell nicht zur Verfügung.
+ Bitte versuche es später noch einmal.
+
+
+
+
+
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 @@
+
+
+
+
+
+ Mein Profil
+
+
+
+
+
+
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']);
});