Skip to content

Commit

Permalink
Added a new system API to create a user
Browse files Browse the repository at this point in the history
  • Loading branch information
deeravenger committed Dec 16, 2024
1 parent c558140 commit f4adb54
Show file tree
Hide file tree
Showing 16 changed files with 360 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace App\EconumoBundle\Application\System\Assembler;

use App\EconumoBundle\Application\System\Dto\CreateUserV1ResultDto;
use App\EconumoBundle\Domain\Entity\User;

readonly class CreateUserV1ResultAssembler
{
public function assemble(User $user): CreateUserV1ResultDto
{
$result = new CreateUserV1ResultDto();
$result->id = $user->getId()->getValue();

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace App\EconumoBundle\Application\System\Dto;

use OpenApi\Annotations as OA;

/**
* @OA\Schema(
* required={"email", "name", "password"}
* )
*/
class CreateUserV1RequestDto
{
/**
* @OA\Property(example="[email protected]")
*/
public string $email;

/**
* @OA\Property(example="John")
*/
public string $name;

/**
* @OA\Property(example="qwerty123")
*/
public string $password;
}
20 changes: 20 additions & 0 deletions src/EconumoBundle/Application/System/Dto/CreateUserV1ResultDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace App\EconumoBundle\Application\System\Dto;

use OpenApi\Annotations as OA;

/**
* @OA\Schema(
* required={"id"}
* )
*/
class CreateUserV1ResultDto
{
/**
* @OA\Property(example="2acbfdcf-ff94-4c07-9819-7d3f1b8d20ce")
*/
public string $id;
}
47 changes: 47 additions & 0 deletions src/EconumoBundle/Application/System/UserService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace App\EconumoBundle\Application\System;

use App\EconumoBundle\Application\Exception\ValidationException;
use App\EconumoBundle\Application\System\Dto\CreateUserV1RequestDto;
use App\EconumoBundle\Application\System\Dto\CreateUserV1ResultDto;
use App\EconumoBundle\Application\System\Assembler\CreateUserV1ResultAssembler;
use App\EconumoBundle\Domain\Entity\ValueObject\Email;
use App\EconumoBundle\Domain\Exception\UserRegisteredException;
use App\EconumoBundle\Domain\Service\EmailServiceInterface;
use App\EconumoBundle\Domain\Service\Translation\TranslationServiceInterface;
use App\EconumoBundle\Domain\Service\UserServiceInterface;

readonly class UserService
{
public function __construct(
private CreateUserV1ResultAssembler $createUserV1ResultAssembler,
private UserServiceInterface $userService,
private EmailServiceInterface $emailService,
private TranslationServiceInterface $translationService,
) {
}

public function createUser(
string $baseUrl,
CreateUserV1RequestDto $dto
): CreateUserV1ResultDto {
$email = new Email($dto->email);
$password = $dto->password;
$name = $dto->name;
try {
$user = $this->userService->register($email, $password, $name);
$this->emailService->sendWelcomeEmailWithPassword($email, $password, $baseUrl);
} catch (UserRegisteredException $userRegisteredException) {
throw new ValidationException(
$this->translationService->trans('user.user.already_exists'),
400,
$userRegisteredException
);
}

return $this->createUserV1ResultAssembler->assemble($user);
}
}
24 changes: 20 additions & 4 deletions src/EconumoBundle/Application/User/UserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,24 @@
use App\EconumoBundle\Domain\Exception\UserRegisteredException;
use App\EconumoBundle\Domain\Exception\UserRegistrationDisabledException;
use App\EconumoBundle\Domain\Service\Translation\TranslationServiceInterface;
use App\EconumoBundle\Domain\Service\User\UserRegistrationServiceInterface;
use App\EconumoBundle\Domain\Service\UserServiceInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
use App\EconumoBundle\Application\User\Dto\RegisterUserV1RequestDto;
use App\EconumoBundle\Application\User\Dto\RegisterUserV1ResultDto;
use App\EconumoBundle\Application\User\Assembler\RegisterUserV1ResultAssembler;

class UserService
readonly class UserService
{
public function __construct(private readonly LoginUserV1ResultAssembler $loginUserV1ResultAssembler, private readonly LogoutUserV1ResultAssembler $logoutUserV1ResultAssembler, private readonly JWTTokenManagerInterface $authToken, private readonly RegisterUserV1ResultAssembler $registerUserV1ResultAssembler, private readonly UserServiceInterface $userService, private readonly TranslationServiceInterface $translationService)
{
public function __construct(
private LoginUserV1ResultAssembler $loginUserV1ResultAssembler,
private LogoutUserV1ResultAssembler $logoutUserV1ResultAssembler,
private JWTTokenManagerInterface $authToken,
private RegisterUserV1ResultAssembler $registerUserV1ResultAssembler,
private UserServiceInterface $userService,
private TranslationServiceInterface $translationService,
private UserRegistrationServiceInterface $userRegistrationService
) {
}

public function loginUser(
Expand All @@ -44,10 +52,18 @@ public function registerUser(
RegisterUserV1RequestDto $dto
): RegisterUserV1ResultDto {
try {
if (!$this->userRegistrationService->isRegistrationAllowed()) {
throw new UserRegistrationDisabledException();
}

$user = $this->userService->register(new Email($dto->email), $dto->password, $dto->name);
return $this->registerUserV1ResultAssembler->assemble($dto, $user);
} catch (UserRegisteredException $userRegisteredException) {
throw new ValidationException($this->translationService->trans('user.user.already_exists'), 400, $userRegisteredException);
throw new ValidationException(
$this->translationService->trans('user.user.already_exists'),
400,
$userRegisteredException
);
} catch (UserRegistrationDisabledException $userRegistrationDisabledException) {
throw new ValidationException('Registration disabled', 400, $userRegistrationDisabledException);
}
Expand Down
2 changes: 2 additions & 0 deletions src/EconumoBundle/Domain/Service/EmailServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
interface EmailServiceInterface
{
public function sendResetPasswordConfirmationCode(Email $recipient, Id $userId): void;

public function sendWelcomeEmailWithPassword(Email $recipient, string $password, string $baseUrl): void;
}
12 changes: 1 addition & 11 deletions src/EconumoBundle/Domain/Service/UserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use App\EconumoBundle\Domain\Entity\ValueObject\ReportPeriod;
use App\EconumoBundle\Domain\Exception\NotFoundException;
use App\EconumoBundle\Domain\Exception\UserRegisteredException;
use App\EconumoBundle\Domain\Exception\UserRegistrationDisabledException;
use App\EconumoBundle\Domain\Factory\ConnectionInviteFactoryInterface;
use App\EconumoBundle\Domain\Factory\FolderFactoryInterface;
use App\EconumoBundle\Domain\Factory\UserFactoryInterface;
Expand All @@ -23,11 +22,7 @@
use App\EconumoBundle\Domain\Repository\FolderRepositoryInterface;
use App\EconumoBundle\Domain\Repository\UserOptionRepositoryInterface;
use App\EconumoBundle\Domain\Repository\UserRepositoryInterface;
use App\EconumoBundle\Domain\Service\AntiCorruptionServiceInterface;
use App\EconumoBundle\Domain\Service\EventDispatcherInterface;
use App\EconumoBundle\Domain\Service\Translation\TranslationServiceInterface;
use App\EconumoBundle\Domain\Service\User\UserRegistrationServiceInterface;
use App\EconumoBundle\Domain\Service\UserServiceInterface;

readonly class UserService implements UserServiceInterface
{
Expand All @@ -42,18 +37,13 @@ public function __construct(
private ConnectionInviteFactoryInterface $connectionInviteFactory,
private ConnectionInviteRepositoryInterface $connectionInviteRepository,
private UserOptionFactoryInterface $userOptionFactory,
private UserOptionRepositoryInterface $userOptionRepository,
private UserRegistrationServiceInterface $userRegistrationService
private UserOptionRepositoryInterface $userOptionRepository
)
{
}

public function register(Email $email, string $password, string $name): User
{
if (!$this->userRegistrationService->isRegistrationAllowed()) {
throw new UserRegistrationDisabledException();
}

try {
$this->userRepository->getByEmail($email);
throw new UserRegisteredException();
Expand Down
1 change: 0 additions & 1 deletion src/EconumoBundle/Domain/Service/UserServiceInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ interface UserServiceInterface
* @param string $password
* @param string $name
* @return User
* @throws UserRegistrationDisabledException
*/
public function register(Email $email, string $password, string $name): User;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use App\EconumoBundle\Domain\Exception\NotFoundException;
use App\EconumoBundle\Domain\Exception\UserDeactivatedException;
use App\EconumoBundle\Domain\Repository\UserRepositoryInterface;
use App\EconumoBundle\Domain\Service\EncodeServiceInterface;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\Exception\ORMException;
use Doctrine\ORM\ORMInvalidArgumentException;
Expand All @@ -29,8 +30,10 @@
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface, UserRepositoryInterface
{
public function __construct(ManagerRegistry $registry)
{
public function __construct(
ManagerRegistry $registry,
private readonly EncodeServiceInterface $encodeService
) {
parent::__construct($registry, User::class);
}

Expand Down Expand Up @@ -67,7 +70,7 @@ public function save(array $users): void
}

$this->getEntityManager()->flush();
} catch (ORMException | ORMInvalidArgumentException $e) {
} catch (ORMException|ORMInvalidArgumentException $e) {
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
}
}
Expand Down Expand Up @@ -102,7 +105,8 @@ public function get(Id $id): User

public function getByEmail(Email $email): User
{
$user = $this->findOneBy(['email' => $email->getValue()]);
$encodedEmail = $this->encodeService->hash($email->getValue());
$user = $this->findOneBy(['identifier' => $encodedEmail]);
if (!$user instanceof User) {
throw new NotFoundException(sprintf('User with email %s not found', $email));
}
Expand Down
17 changes: 15 additions & 2 deletions src/EconumoBundle/Infrastructure/Symfony/Mailer/EmailService.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ public function __construct(
) {
}


public function sendResetPasswordConfirmationCode(Email $recipient, Id $userId): void
{
$user = $this->userRepository->get($userId);
$passwordRequest = $this->userPasswordRequestRepository->getByUser($userId);
$this->translationService->trans('email.reset_password_confirmation_code.subject');
$container = (new EmailContainer())
->to($recipient->getValue())
->subject($this->translationService->trans('email.reset_password_confirmation_code.subject'))
Expand All @@ -44,6 +42,21 @@ public function sendResetPasswordConfirmationCode(Email $recipient, Id $userId):
$this->send($container);
}

public function sendWelcomeEmailWithPassword(Email $recipient, string $password, string $baseUrl): void
{
$container = (new EmailContainer())
->to($recipient->getValue())
->subject($this->translationService->trans('email.welcome_with_password.subject'))
->text(
$this->translationService->trans('email.welcome_with_password.body', [
'{{email}}' => $recipient->getValue(),
'{{password}}' => $password,
'{{baseUrl}}' => $baseUrl,
])
);
$this->send($container);
}

/**
* @throws TransportExceptionInterface
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace App\EconumoBundle\UI\Controller\Api\System\User;

use App\EconumoBundle\Application\System\UserService;
use App\EconumoBundle\Application\System\Dto\CreateUserV1RequestDto;
use App\EconumoBundle\UI\Controller\Api\System\User\Validation\CreateUserV1Form;
use App\EconumoBundle\Application\Exception\ValidationException;
use App\EconumoBundle\UI\Middleware\ProtectSystemApi\SystemApiInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use App\EconumoBundle\UI\Service\Validator\ValidatorInterface;
use App\EconumoBundle\UI\Service\Response\ResponseFactory;
use Symfony\Component\Routing\Annotation\Route;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;

class CreateUserV1Controller extends AbstractController implements SystemApiInterface
{
public function __construct(private readonly UserService $userService, private readonly ValidatorInterface $validator)
{
}

/**
* Create user
*
* @OA\Tag(name="System"),
* @OA\RequestBody(@OA\JsonContent(ref=@Model(type=\App\EconumoBundle\Application\System\Dto\CreateUserV1RequestDto::class))),
* @OA\Response(
* response=200,
* description="OK",
* @OA\JsonContent(
* type="object",
* allOf={
* @OA\Schema(ref="#/components/schemas/JsonResponseOk"),
* @OA\Schema(
* @OA\Property(
* property="data",
* ref=@Model(type=\App\EconumoBundle\Application\System\Dto\CreateUserV1ResultDto::class)
* )
* )
* }
* )
* ),
* @OA\Response(response=400, description="Bad Request", @OA\JsonContent(ref="#/components/schemas/JsonResponseError")),
* @OA\Response(response=401, description="Unauthorized", @OA\JsonContent(ref="#/components/schemas/JsonResponseUnauthorized")),
* @OA\Response(response=500, description="Internal Server Error", @OA\JsonContent(ref="#/components/schemas/JsonResponseException")),
*
*
* @return Response
* @throws ValidationException
*/
#[Route(path: '/api/v1/system/create-user', methods: ['POST'])]
public function __invoke(Request $request): Response
{
$dto = new CreateUserV1RequestDto();
$this->validator->validate(CreateUserV1Form::class, $request->request->all(), $dto);
$baseUrl = $request->getSchemeAndHttpHost();
$result = $this->userService->createUser($baseUrl, $dto);

return ResponseFactory::createOkResponse($request, $result);
}
}
Loading

0 comments on commit f4adb54

Please sign in to comment.