fix(user-profile): resolve profile by user uuid and auto-create when missing

GET/PATCH /api/v1/user-profile/{uuid} treated {uuid} as the profile's own
uuid, but clients pass the user's uuid — and a freshly OTP-registered user
has no profile row, so the call always 404'd. Add resolveProfile(): try
profile uuid, then user uuid → that user's profile, and (for the current
user or an admin) lazy-create an empty profile so the client always gets
an editable one. Foreign/unknown uuids still 404 with no leak.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-06-15 19:01:27 +03:30
co-authored by Claude Opus 4.8
parent 593b4decf4
commit da57ac9c5b
2 changed files with 190 additions and 2 deletions
@@ -3,6 +3,7 @@
namespace App\UserProfile\Controller;
use App\Auth\Entity\User;
use App\Auth\Repository\UserRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\UserProfile\Entity\UserProfile;
@@ -20,6 +21,7 @@ class UserProfileController extends BaseController
{
public function __construct(
private readonly UserProfileRepository $repository,
private readonly UserRepository $userRepository,
) {}
#[Route('/api/v1/user-profile', methods: ['POST'])]
@@ -40,7 +42,9 @@ class UserProfileController extends BaseController
#[Route('/api/v1/user-profile/{uuid}', methods: ['GET'])]
public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
{
$profile = $this->repository->findByUuid($uuid);
// {uuid} may be a profile uuid or a user uuid; a user with no profile yet
// gets an empty one created so the client always has an editable profile.
$profile = $this->resolveProfile($uuid, $user, true);
if ($profile === null) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
@@ -56,7 +60,7 @@ class UserProfileController extends BaseController
#[Route('/api/v1/user-profile/{uuid}', methods: ['PATCH'])]
public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
{
$profile = $this->repository->findByUuid($uuid);
$profile = $this->resolveProfile($uuid, $user, true);
if ($profile === null) {
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
@@ -88,6 +92,43 @@ class UserProfileController extends BaseController
return $this->success(['message' => 'پروفایل با موفقیت حذف شد']);
}
/**
* Resolve a profile from a uuid that may be the profile's own uuid or the
* owning user's uuid. When $createIfMissing is true and the uuid belongs to
* a user (the current user or, for admins, anyone) without a profile, an
* empty profile is created and persisted.
*/
private function resolveProfile(string $uuid, User $currentUser, bool $createIfMissing): ?UserProfile
{
$profile = $this->repository->findByUuid($uuid);
if ($profile !== null) {
return $profile;
}
$targetUser = $this->userRepository->findByUuid($uuid);
if ($targetUser === null) {
return null;
}
$profile = $this->repository->findByUser($targetUser);
if ($profile !== null) {
return $profile;
}
if (!$createIfMissing) {
return null;
}
if ($targetUser->getId() !== $currentUser->getId() && !$currentUser->hasRole('ROLE_ADMIN')) {
return null;
}
$profile = new UserProfile($targetUser);
$this->repository->save($profile);
return $profile;
}
private function canAccess(UserProfile $profile, User $currentUser): bool
{
return $profile->getUser()->getId() === $currentUser->getId()