validated(); $avatarPath = null; if ($request->hasFile('avatar')) { $avatarPath = $request->file('avatar')->store('avatars', 'public'); } $userAttributes = $this->userAttributesFromRequestData($data); $userAttributes['email'] = strtolower($data['email']); $userAttributes['password'] = Hash::make($data['password']); $userAttributes['avatar_url'] = $avatarPath; $user = User::create($userAttributes); $user->sendEmailVerificationNotification(); $token = $user->createToken('api', ['user'])->plainTextToken; return response()->json([ 'message' => __('api.auth.registered_unverified'), 'user' => new UserResource($user), ], 201)->cookie('token', $token, 60 * 24 * 30); } public function login(LoginRequest $request): JsonResponse { $data = $request->validated(); $user = User::where('email', strtolower($data['email']))->first(); if (! $user || ! Hash::check($data['password'], $user->password)) { return response()->json([ 'message' => __('api.auth.invalid_credentials'), ], 401); } if (! $user->hasVerifiedEmail()) { return response()->json([ 'message' => __('api.auth.email_not_verified'), ], 403); } $token = $user->createToken('api')->plainTextToken; return response()->json([ 'user' => new UserResource($user), ])->cookie('token', $token, 60 * 24 * 30); } public function verifyEmail(Request $request, string $id, string $hash): JsonResponse|View { $user = User::findOrFail($id); if (! hash_equals($hash, sha1($user->getEmailForVerification()))) { if ($request->expectsJson()) { return response()->json([ 'message' => __('api.auth.invalid_verification_link'), ], 403); } abort(403, __('api.auth.invalid_verification_link')); } $alreadyVerified = $user->hasVerifiedEmail(); if (! $alreadyVerified) { $user->markEmailAsVerified(); event(new Verified($user)); } if ($request->expectsJson()) { return response()->json([ 'message' => $alreadyVerified ? __('api.auth.already_verified') : __('api.auth.verified'), 'user' => new UserResource($user->fresh()), ]); } return view('auth.email-verified', [ 'alreadyVerified' => $alreadyVerified, 'appName' => config('app.name'), ]); } public function sendVerificationEmail(EmailVerificationNotificationRequest $request): JsonResponse { $email = strtolower($request->validated('email')); $user = User::where('email', $email)->first(); if ($user && ! $user->hasVerifiedEmail()) { $user->sendEmailVerificationNotification(); } return response()->json([ 'message' => __('api.auth.verification_sent_if_unverified'), ]); } public function logout(): JsonResponse { $request = request(); $request->user()->currentAccessToken()->delete(); return response()->json([ 'message' => __('api.auth.logout'), ])->withoutCookie('token'); } public function me(): JsonResource|JsonResponse { $user = auth()->user(); if (! $user) { return response()->json(['message' => __('api.auth.unauthenticated')], 401); } return new UserResource($user); } public function update(UpdateUserRequest $request): JsonResource { $user = $request->user(); $data = $request->validated(); $nutritionGoals = $data['nutritionGoals'] ?? null; if ($request->hasFile('avatar')) { if ($user->avatar_url) { Storage::disk('public')->delete($user->avatar_url); } $path = $request->file('avatar')->store('avatars', 'public'); $data['avatar_url'] = $path; } $userAttributes = $this->userAttributesFromRequestData($data); if (is_array($nutritionGoals)) { $userAttributes['daily_calorie_goal'] = $nutritionGoals['calories']; $userAttributes['daily_protein_goal'] = $nutritionGoals['proteins']; $userAttributes['daily_carbs_goal'] = $nutritionGoals['carbs']; $userAttributes['daily_fats_goal'] = $nutritionGoals['fats']; } if (! empty($userAttributes)) { $user->update($userAttributes); } 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' => __('api.auth.deleted'), ])->withoutCookie('token'); } private function isPublicStoragePath(?string $path): bool { return filled($path) && ! Str::startsWith($path, ['http://', 'https://']); } /** * @param array $data * @return array */ private function userAttributesFromRequestData(array $data): array { $attributes = Arr::only($data, [ 'name', 'avatar_url', 'height', 'bio', 'sex', 'date_of_birth', ]); $attributeMap = [ 'physicalActivityLevel' => 'physical_activity_level', 'weightGoal' => 'weight_goal', 'pacePreference' => 'pace_preference', 'dateOfBirth' => 'date_of_birth', ]; foreach ($attributeMap as $requestKey => $attribute) { if (array_key_exists($requestKey, $data)) { $attributes[$attribute] = $data[$requestKey]; } } return $attributes; } }