feat(monorepo): migrate to typescript monorepo

This commit is contained in:
2025-11-07 17:09:29 +02:00
committed by BernardNganduDev
parent 3e09956f05
commit 075a388ccb
745 changed files with 2341 additions and 5082 deletions
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Console;
use Basango\IdentityAndAccess\Application\UseCase\Command\Register;
use Basango\IdentityAndAccess\Domain\Model\ValueObject\Roles;
use Basango\SharedKernel\Application\Messaging\CommandBus;
use Basango\SharedKernel\Domain\Model\ValueObject\EmailAddress;
use Basango\SharedKernel\Presentation\Console\AskArgumentFeature;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Class RegisterConsole.
*
* @author bernard-ng <bernard@devscast.tech>
*/
#[AsCommand('app:user-register', 'register a new user')]
final class RegisterConsole extends Command
{
use AskArgumentFeature;
private SymfonyStyle $io;
public function __construct(
private readonly CommandBus $commandBus,
) {
parent::__construct();
}
#[\Override]
protected function configure(): void
{
$this
->setDescription('Creates users and stores them in the database')
->addArgument('name', InputArgument::OPTIONAL, 'The name of the new user')
->addArgument('email', InputArgument::OPTIONAL, 'The email of the new user')
->addArgument('password', InputArgument::OPTIONAL, 'The plain password of the new user')
->addOption('admin', null, InputOption::VALUE_NONE, 'If set, the user is created as an administrator');
}
#[\Override]
protected function initialize(InputInterface $input, OutputInterface $output): void
{
$this->io = new SymfonyStyle($input, $output);
}
#[\Override]
protected function interact(InputInterface $input, OutputInterface $output): void
{
if (
$input->getArgument('name') !== null &&
$input->getArgument('email') !== null &&
$input->getArgument('password') !== null
) {
return;
}
$this->askArgument($input, 'name');
$this->askArgument($input, 'email');
$this->askArgument($input, 'password', true);
}
#[\Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
/** @var string $name */
$name = $input->getArgument('name');
/** @var string $email */
$email = $input->getArgument('email');
/** @var string $password */
$password = $input->getArgument('password');
/** @var bool $admin */
$admin = $input->getOption('admin');
$command = new Register($name, EmailAddress::from($email), $password, $admin ? Roles::admin() : Roles::user());
$this->commandBus->handle($command);
$this->io->success(\sprintf('%s was created: %s', $admin ? 'ADMIN' : 'USER', $email));
return Command::SUCCESS;
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\ConfirmAccount;
use Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
/**
* Class UnlockAccountController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class ConfirmAccountController extends AbstractController
{
#[Route(
path: '/api/account/confirm/{token}',
name: 'identity_and_access_confirm_account',
requirements: [
'token' => Requirement::ASCII_SLUG,
],
methods: ['GET']
)]
public function __invoke(string $token): JsonResponse
{
$token = new GeneratedToken($token);
$this->handleCommand(new ConfirmAccount($token));
return new JsonResponse(status: 200);
}
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Query\GetUserProfile;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
/**
* Class GetUserProfileController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class GetUserProfileController extends AbstractController
{
#[Route(
path: '/api/me',
name: 'identity_and_access_me',
methods: ['GET']
)]
public function __invoke(): JsonResponse
{
$security = $this->getSecurityUser();
$data = $this->handleQuery(new GetUserProfile($security->userId));
return JsonResponse::fromJsonString($this->serialize($data));
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\Register;
use Basango\IdentityAndAccess\Presentation\WriteModel\RegisterModel;
use Basango\SharedKernel\Domain\Model\ValueObject\EmailAddress;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
/**
* Class RegisterController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class RegisterController extends AbstractController
{
#[Route(
path: '/api/register',
name: 'identity_and_access_register',
methods: ['POST']
)]
public function __invoke(#[MapRequestPayload] RegisterModel $model): JsonResponse
{
$this->handleCommand(new Register(
$model->name,
EmailAddress::from($model->email),
$model->password
));
return new JsonResponse(status: 201);
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\RequestPassword;
use Basango\IdentityAndAccess\Presentation\WriteModel\RequestPasswordModel;
use Basango\SharedKernel\Domain\Model\ValueObject\EmailAddress;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
/**
* Class RequestPasswordController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class RequestPasswordController extends AbstractController
{
#[Route(
path: '/api/password/request',
name: 'identity_and_access_request_password',
methods: ['POST']
)]
public function __invoke(#[MapRequestPayload] RequestPasswordModel $model): JsonResponse
{
$email = EmailAddress::from($model->email);
$this->handleCommand(new RequestPassword($email));
return new JsonResponse(status: 200);
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\ResetPassword;
use Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken;
use Basango\IdentityAndAccess\Presentation\WriteModel\ResetPasswordModel;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
/**
* Class RequestPasswordController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class ResetPasswordController extends AbstractController
{
#[Route(
path: '/api/password/reset/{token}',
name: 'identity_and_access_reset_password',
requirements: [
'token' => Requirement::ASCII_SLUG,
],
methods: ['POST']
)]
public function __invoke(#[MapRequestPayload] ResetPasswordModel $model, string $token): JsonResponse
{
$token = new GeneratedToken($token);
$this->handleCommand(new ResetPassword($token, $model->password));
return new JsonResponse(status: 200);
}
}
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\UnlockAccount;
use Basango\IdentityAndAccess\Domain\Model\ValueObject\Secret\GeneratedToken;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Routing\Requirement\Requirement;
/**
* Class UnlockAccountController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class UnlockAccountController extends AbstractController
{
#[Route(
path: '/api/account/unlock/{token}',
name: 'identity_and_access_unlock_account',
requirements: [
'token' => Requirement::ASCII_SLUG,
],
methods: ['GET']
)]
public function __invoke(string $token): JsonResponse
{
$token = new GeneratedToken($token);
$this->handleCommand(new UnlockAccount($token));
return new JsonResponse(status: 200);
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\Web\Controller;
use Basango\IdentityAndAccess\Application\UseCase\Command\UpdatePassword;
use Basango\IdentityAndAccess\Presentation\WriteModel\UpdatePasswordModel;
use Basango\SharedKernel\Presentation\Web\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
/**
* Class UpdatePasswordController.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class UpdatePasswordController extends AbstractController
{
#[Route(
path: '/api/password/update',
name: 'identity_and_access_update_password',
methods: ['POST']
)]
public function __invoke(#[MapRequestPayload] UpdatePasswordModel $model): JsonResponse
{
$securityUser = $this->getSecurityUser();
$this->handleCommand(new UpdatePassword(
$securityUser->userId,
$model->current,
$model->password
));
return new JsonResponse(status: 200);
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\WriteModel;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class RegisterModel.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class RegisterModel
{
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
public string $name;
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\NotBlank]
#[Assert\Length(max: 512)]
public string $password;
}
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\WriteModel;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class RequestPasswordModel.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class RequestPasswordModel
{
#[Assert\NotBlank]
#[Assert\Email]
#[Assert\Length(max: 255)]
public string $email;
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\WriteModel;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class RequestPasswordModel.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class ResetPasswordModel
{
#[Assert\NotBlank]
#[Assert\Length(max: 512)]
#[Assert\PasswordStrength]
public string $password;
#[Assert\EqualTo(
propertyPath: 'password',
message: 'identity_and_access.exceptions.passwords_do_not_match',
)]
public string $confirm;
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Basango\IdentityAndAccess\Presentation\WriteModel;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Class RegisterModel.
*
* @author bernard-ng <bernard@devscast.tech>
*/
final class UpdatePasswordModel
{
#[Assert\NotBlank]
public string $current;
#[Assert\NotBlank]
#[Assert\Length(max: 512)]
#[Assert\PasswordStrength]
public string $password;
#[Assert\EqualTo(
propertyPath: 'password',
message: 'identity_and_access.exceptions.passwords_do_not_match',
)]
public string $confirm;
}