diff --git a/src/Core/src/App/src/Entity/AbstractEntity.php b/src/Core/src/App/src/Entity/AbstractEntity.php index 82e1dea..f2848a5 100644 --- a/src/Core/src/App/src/Entity/AbstractEntity.php +++ b/src/Core/src/App/src/Entity/AbstractEntity.php @@ -30,6 +30,14 @@ public function getUuid(): UuidInterface return $this->uuid; } + /** + * Override this method in soft-deletable entities + */ + public function isDeleted(): bool + { + return false; + } + public function exchangeArray(array $array): void { foreach ($array as $property => $values) { diff --git a/src/Core/src/App/src/Entity/EntityInterface.php b/src/Core/src/App/src/Entity/EntityInterface.php index 418e0fd..80ce609 100644 --- a/src/Core/src/App/src/Entity/EntityInterface.php +++ b/src/Core/src/App/src/Entity/EntityInterface.php @@ -4,6 +4,20 @@ namespace Core\App\Entity; +use DateTimeImmutable; +use Ramsey\Uuid\UuidInterface; + interface EntityInterface { + public function getUuid(): UuidInterface; + + public function getCreated(): ?DateTimeImmutable; + + public function getCreatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): string; + + public function getUpdated(): ?DateTimeImmutable; + + public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string; + + public function isDeleted(): bool; } diff --git a/src/Core/src/App/src/Entity/RoleInterface.php b/src/Core/src/App/src/Entity/RoleInterface.php index b1893e3..0c2b80b 100644 --- a/src/Core/src/App/src/Entity/RoleInterface.php +++ b/src/Core/src/App/src/Entity/RoleInterface.php @@ -5,12 +5,9 @@ namespace Core\App\Entity; use BackedEnum; -use Ramsey\Uuid\UuidInterface; -interface RoleInterface +interface RoleInterface extends EntityInterface { - public function getUuid(): UuidInterface; - public function getName(): ?BackedEnum; public function setName(BackedEnum $name): RoleInterface; diff --git a/src/Core/src/App/src/Message.php b/src/Core/src/App/src/Message.php index c65460d..f3d7a8d 100644 --- a/src/Core/src/App/src/Message.php +++ b/src/Core/src/App/src/Message.php @@ -4,6 +4,7 @@ namespace Core\App; +use function count; use function implode; use function sprintf; @@ -34,18 +35,23 @@ class Message . 'you will receive an email with further instructions on resetting your account\'s password.'; public const MAIL_SENT_USER_ACTIVATION = 'User activation mail has been successfully sent to "%s"'; public const MISSING_CONFIG = 'Missing configuration value: "%s".'; + public const NOT_ACCEPTABLE = 'Not acceptable.'; public const RESET_PASSWORD_EXPIRED = 'Reset password hash is invalid (expired).'; public const RESET_PASSWORD_NOT_FOUND = 'Reset password request not found.'; public const RESET_PASSWORD_OK = 'Password successfully modified.'; public const RESET_PASSWORD_USED = 'Reset password hash is invalid (used).'; public const RESET_PASSWORD_VALID = 'Reset password hash is valid.'; + public const RESOURCE_ALREADY_REGISTERED = 'Resource "%s" is already registered.'; public const RESOURCE_NOT_ALLOWED = 'You are not allowed to access this resource.'; + public const RESOURCE_NOT_FOUND = '%s not found.'; public const RESTRICTION_DEPRECATION = 'Cannot use both "%s" and "%s" attributes on the same object.'; public const RESTRICTION_IMAGE = 'File must be an image> Accepted mim type(s): %s'; public const RESTRICTION_ROLES = 'At least one role is required.'; public const ROLE_NOT_FOUND = 'Role not found.'; public const SERVICE_NOT_FOUND = 'Service %s not found in the container.'; public const SETTING_NOT_FOUND = 'Setting "%s" not found.'; + public const TEMPLATE_NOT_FOUND = 'Template "%s" not found.'; + public const UNSUPPORTED_MEDIA_TYPE = 'Unsupported Media Type.'; public const USER_ACTIVATED = 'User account has been activated.'; public const USER_ALREADY_ACTIVATED = 'User account is already active.'; public const USER_ALREADY_DEACTIVATED = 'User account is already inactive.'; @@ -93,6 +99,25 @@ public static function mailSentUserActivation(string $email): string return sprintf(self::MAIL_SENT_USER_ACTIVATION, $email); } + public static function notAcceptable(array $types = []): string + { + if (count($types) === 0) { + return self::NOT_ACCEPTABLE; + } + + return sprintf('%s Supported types: %s', self::NOT_ACCEPTABLE, implode(', ', $types)); + } + + public static function resourceAlreadyRegistered(string $resource): string + { + return sprintf(self::RESOURCE_ALREADY_REGISTERED, $resource); + } + + public static function resourceNotFound(string $resource = 'Resource'): string + { + return sprintf(self::RESOURCE_NOT_FOUND, $resource); + } + public static function restrictionDeprecation(string $first, string $second): string { return sprintf(self::RESTRICTION_DEPRECATION, $first, $second); @@ -113,6 +138,20 @@ public static function settingNotFound(string $identifier): string return sprintf(self::SETTING_NOT_FOUND, $identifier); } + public static function templateNotFound(string $template): string + { + return sprintf(self::TEMPLATE_NOT_FOUND, $template); + } + + public static function unsupportedMediaType(array $types = []): string + { + if (count($types) === 0) { + return self::UNSUPPORTED_MEDIA_TYPE; + } + + return sprintf('%s Supported types: %s', self::UNSUPPORTED_MEDIA_TYPE, implode(', ', $types)); + } + public static function validatorLengthMax(int $max): string { return sprintf(self::VALIDATOR_LENGTH_MAX, $max); diff --git a/src/Core/src/App/src/Service/MailService.php b/src/Core/src/App/src/Service/MailService.php index 510c28e..80f0272 100644 --- a/src/Core/src/App/src/Service/MailService.php +++ b/src/Core/src/App/src/Service/MailService.php @@ -9,7 +9,6 @@ use Dot\DependencyInjection\Attribute\Inject; use Dot\Log\LoggerInterface; use Dot\Mail\Exception\MailException; -use Mezzio\Template\TemplateRendererInterface; use Symfony\Component\Mailer\Exception\TransportExceptionInterface; use function sprintf; @@ -17,13 +16,11 @@ class MailService { #[Inject( - TemplateRendererInterface::class, 'dot-mail.service.default', 'dot-log.default_logger', 'config', )] public function __construct( - private readonly TemplateRendererInterface $templateRenderer, protected \Dot\Mail\Service\MailService $mailService, protected LoggerInterface $logger, private readonly array $config, @@ -33,7 +30,7 @@ public function __construct( /** * @throws MailException */ - public function sendActivationMail(User $user): bool + public function sendActivationMail(User $user, string $body): bool { if ($user->isActive()) { return false; @@ -41,12 +38,7 @@ public function sendActivationMail(User $user): bool $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); - $this->mailService->setBody( - $this->templateRenderer->render('user::activate', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); + $this->mailService->setBody($body); try { return $this->mailService->send()->isValid(); @@ -59,18 +51,13 @@ public function sendActivationMail(User $user): bool /** * @throws MailException */ - public function sendResetPasswordRequestedMail(User $user): bool + public function sendResetPasswordRequestedMail(User $user, string $body): bool { $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); $this->mailService->setSubject( 'Reset password instructions for your ' . $this->config['application']['name'] . ' account' ); - $this->mailService->setBody( - $this->templateRenderer->render('user::reset-password-requested', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); + $this->mailService->setBody($body); try { return $this->mailService->send()->isValid(); @@ -83,18 +70,13 @@ public function sendResetPasswordRequestedMail(User $user): bool /** * @throws MailException */ - public function sendResetPasswordCompletedMail(User $user): bool + public function sendResetPasswordCompletedMail(User $user, string $body): bool { $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); $this->mailService->setSubject( 'You have successfully reset the password for your ' . $this->config['application']['name'] . ' account' ); - $this->mailService->setBody( - $this->templateRenderer->render('user::reset-password-completed', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); + $this->mailService->setBody($body); try { return $this->mailService->send()->isValid(); @@ -107,16 +89,13 @@ public function sendResetPasswordCompletedMail(User $user): bool /** * @throws MailException */ - public function sendWelcomeMail(User $user): bool + public function sendRecoverIdentityMail(User $user, string $body): bool { $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); - $this->mailService->setBody( - $this->templateRenderer->render('user::welcome', [ - 'config' => $this->config, - 'user' => $user, - ]) + $this->mailService->setSubject( + 'Recover identity for your ' . $this->config['application']['name'] . ' account' ); + $this->mailService->setBody($body); try { return $this->mailService->send()->isValid(); @@ -129,18 +108,11 @@ public function sendWelcomeMail(User $user): bool /** * @throws MailException */ - public function sendRecoverIdentityMail(User $user): bool + public function sendWelcomeMail(User $user, string $body): bool { $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject( - 'Recover identity for your ' . $this->config['application']['name'] . ' account' - ); - $this->mailService->setBody( - $this->templateRenderer->render('user::recover-identity-requested', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); + $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); + $this->mailService->setBody($body); try { return $this->mailService->send()->isValid(); diff --git a/src/Core/src/Setting/src/ConfigProvider.php b/src/Core/src/Setting/src/ConfigProvider.php index c82dea1..9cfda07 100644 --- a/src/Core/src/Setting/src/ConfigProvider.php +++ b/src/Core/src/Setting/src/ConfigProvider.php @@ -19,7 +19,7 @@ public function __invoke(): array ]; } - public function getDependencies(): array + private function getDependencies(): array { return [ 'factories' => [ @@ -28,7 +28,7 @@ public function getDependencies(): array ]; } - public function getDoctrineConfig(): array + private function getDoctrineConfig(): array { return [ 'driver' => [ diff --git a/src/Core/src/User/src/Entity/User.php b/src/Core/src/User/src/Entity/User.php index e33552e..220a0f2 100644 --- a/src/Core/src/User/src/Entity/User.php +++ b/src/Core/src/User/src/Entity/User.php @@ -18,6 +18,7 @@ use function array_map; use function bin2hex; use function md5; +use function trim; use function uniqid; #[ORM\Entity(repositoryClass: UserRepository::class)] @@ -116,17 +117,6 @@ public function addResetPassword(UserResetPassword $resetPassword): void $this->resetPasswords->add($resetPassword); } - public function createResetPassword(): self - { - $this->resetPasswords->add( - (new UserResetPassword()) - ->setHash(self::generateHash()) - ->setUser($this) - ); - - return $this; - } - public function getResetPasswords(): Collection { return $this->resetPasswords; @@ -261,7 +251,7 @@ public static function generateHash(): string public function getName(): string { - return $this->getDetail()->getFirstName() . ' ' . $this->getDetail()->getLastName(); + return trim($this->getDetail()->getFirstName() . ' ' . $this->getDetail()->getLastName()); } public function isActive(): bool diff --git a/src/User/src/Handler/PostUserCreateHandler.php b/src/User/src/Handler/PostUserCreateHandler.php index b96b02f..557f2c3 100644 --- a/src/User/src/Handler/PostUserCreateHandler.php +++ b/src/User/src/Handler/PostUserCreateHandler.php @@ -35,6 +35,7 @@ class PostUserCreateHandler implements RequestHandlerInterface CreateUserForm::class, MailService::class, 'dot-log.default_logger', + 'config', )] public function __construct( protected UserServiceInterface $userService, @@ -44,6 +45,7 @@ public function __construct( protected CreateUserForm $createUserForm, protected MailService $mailService, protected Logger $logger, + protected array $config, ) { } @@ -58,7 +60,11 @@ public function handle(ServerRequestInterface $request): ResponseInterface $user = $this->userService->saveUser((array) $this->createUserForm->getData()); $this->messenger->addSuccess(Message::USER_CREATED); if ($user->getDetail()->hasEmail()) { - $this->mailService->sendWelcomeMail($user); + $body = $this->template->render('user::welcome', [ + 'config' => $this->config, + 'user' => $user, + ]); + $this->mailService->sendWelcomeMail($user, $body); } return new EmptyResponse(StatusCodeInterface::STATUS_CREATED); diff --git a/test/Unit/App/Entity/AbstractEntityTest.php b/test/Unit/App/Entity/AbstractEntityTest.php index 1757ad5..bbd828d 100644 --- a/test/Unit/App/Entity/AbstractEntityTest.php +++ b/test/Unit/App/Entity/AbstractEntityTest.php @@ -7,6 +7,7 @@ use AdminTest\Unit\UnitTest; use Core\App\Entity\AbstractEntity; use Core\App\Entity\EntityInterface; +use DateTimeImmutable; use Ramsey\Uuid\UuidInterface; class AbstractEntityTest extends UnitTest @@ -18,6 +19,26 @@ public function getArrayCopy(): array { return []; } + + public function getCreated(): ?DateTimeImmutable + { + return null; + } + + public function getCreatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): string + { + return ''; + } + + public function getUpdated(): ?DateTimeImmutable + { + return null; + } + + public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string + { + return null; + } }; $this->assertContainsOnlyInstancesOf(EntityInterface::class, [$entity]);