add email signup flow
This commit is contained in:
parent
9945163a2f
commit
3dc9204979
27 changed files with 629 additions and 24 deletions
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken;
|
||||
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class CreateEmailConfirmationTokenDto
|
||||
{
|
||||
public function __construct(
|
||||
public User $user,
|
||||
public DateTimeImmutable $availableTo,
|
||||
public string $token,
|
||||
) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken;
|
||||
|
||||
use App\User\User;
|
||||
use App\User\UserRepository;
|
||||
use DomainException;
|
||||
|
||||
class EloquentEmailConfirmationTokenRepository implements
|
||||
EmailConfirmationTokenRepository
|
||||
{
|
||||
public function __construct(
|
||||
private UserRepository $userRepository,
|
||||
) {}
|
||||
|
||||
public function create(
|
||||
CreateEmailConfirmationTokenDto $dto,
|
||||
): EmailConfirmationToken {
|
||||
$model = EmailConfirmationTokenModel::create([
|
||||
'user_id' => $dto->user->getId(),
|
||||
'token' => $dto->token,
|
||||
'available_to' => $dto->availableTo,
|
||||
]);
|
||||
|
||||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
public function findByToken(string $token): ?EmailConfirmationToken
|
||||
{
|
||||
$model = EmailConfirmationTokenModel::query()
|
||||
->where('token', $token)
|
||||
->first();
|
||||
|
||||
return $model === null ? null : $this->toDomain($model);
|
||||
}
|
||||
|
||||
public function findByUser(User $user): ?EmailConfirmationToken
|
||||
{
|
||||
$model = EmailConfirmationTokenModel::query()
|
||||
->where('user_id', $user->getId())
|
||||
->first();
|
||||
|
||||
return $model === null ? null : $this->toDomain($model);
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
EmailConfirmationTokenModel::where('id', $id)->delete();
|
||||
}
|
||||
|
||||
private function toDomain(
|
||||
EmailConfirmationTokenModel $model,
|
||||
): EmailConfirmationToken {
|
||||
$user = $this->userRepository->find($model->user_id);
|
||||
if ($user === null) {
|
||||
throw new DomainException(
|
||||
"User with id {$model->user_id} not found",
|
||||
);
|
||||
}
|
||||
|
||||
return new EmailConfirmationToken(
|
||||
id: $model->id,
|
||||
user: $user,
|
||||
availableTo: $model->available_to,
|
||||
token: $model->token,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken;
|
||||
|
||||
use App\User\User;
|
||||
use DateTimeImmutable;
|
||||
|
||||
final readonly class EmailConfirmationToken
|
||||
{
|
||||
public function __construct(
|
||||
private int $id,
|
||||
private User $user,
|
||||
private DateTimeImmutable $availableTo,
|
||||
private string $token,
|
||||
) {}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUser(): User
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getAvailableTo(): DateTimeImmutable
|
||||
{
|
||||
return $this->availableTo;
|
||||
}
|
||||
|
||||
public function getToken(): string
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $user_id
|
||||
* @property string $token
|
||||
* @property DateTimeImmutable $available_to
|
||||
*
|
||||
* @method static Builder<static>|EmailConfirmationTokenModel newModelQuery()
|
||||
* @method static Builder<static>|EmailConfirmationTokenModel newQuery()
|
||||
* @method static Builder<static>|EmailConfirmationTokenModel query()
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
#[Fillable([
|
||||
'user_id',
|
||||
'token',
|
||||
'available_to',
|
||||
])]
|
||||
class EmailConfirmationTokenModel extends Model
|
||||
{
|
||||
protected $table = 'email_confirmation_tokens';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'available_to' => 'immutable_datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken;
|
||||
|
||||
use App\User\User;
|
||||
|
||||
interface EmailConfirmationTokenRepository
|
||||
{
|
||||
public function create(
|
||||
CreateEmailConfirmationTokenDto $dto,
|
||||
): EmailConfirmationToken;
|
||||
|
||||
public function findByToken(string $token): ?EmailConfirmationToken;
|
||||
|
||||
public function findByUser(User $user): ?EmailConfirmationToken;
|
||||
|
||||
public function delete(int $id): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken\UseCases;
|
||||
|
||||
use App\Auth\Clock;
|
||||
use App\Auth\TokenGenerator;
|
||||
use App\Email\EmailConfirmationToken\CreateEmailConfirmationTokenDto;
|
||||
use App\Email\EmailConfirmationToken\EmailConfirmationToken;
|
||||
use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository;
|
||||
use App\Exceptions\BadRequestException;
|
||||
|
||||
class CreateEmailConfirmationToken
|
||||
{
|
||||
public function __construct(
|
||||
private EmailConfirmationTokenRepository $tokenRepository,
|
||||
private Clock $clock,
|
||||
private TokenGenerator $tokenGenerator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function execute(
|
||||
CreateEmailConfirmationTokenRequest $request,
|
||||
): EmailConfirmationToken {
|
||||
if ($request->user === null) {
|
||||
throw new BadRequestException('user is required');
|
||||
}
|
||||
if ($request->minuteOffset === null) {
|
||||
throw new BadRequestException('minuteOffset is required');
|
||||
}
|
||||
|
||||
$existingToken = $this->tokenRepository->findByUser($request->user);
|
||||
if ($existingToken !== null) {
|
||||
$this->tokenRepository->delete($existingToken->getId());
|
||||
}
|
||||
|
||||
return $this->tokenRepository->create(
|
||||
new CreateEmailConfirmationTokenDto(
|
||||
user: $request->user,
|
||||
availableTo: $this->clock->now()->modify(
|
||||
"+{$request->minuteOffset} minutes",
|
||||
),
|
||||
token: $this->tokenGenerator->generate(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Email\EmailConfirmationToken\UseCases;
|
||||
|
||||
use App\User\User;
|
||||
|
||||
final readonly class CreateEmailConfirmationTokenRequest
|
||||
{
|
||||
public function __construct(
|
||||
public ?User $user,
|
||||
public ?int $minuteOffset,
|
||||
) {}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue