diff --git a/backend/app/Email/EmailConfirmationToken/CreateEmailConfirmationTokenDto.php b/backend/app/Email/EmailConfirmationToken/CreateEmailConfirmationTokenDto.php new file mode 100644 index 0000000..58a17ed --- /dev/null +++ b/backend/app/Email/EmailConfirmationToken/CreateEmailConfirmationTokenDto.php @@ -0,0 +1,14 @@ + $dto->user->getId(), + 'token' => bin2hex(random_bytes(32)), + 'available_to' => $dto->availableTo, + ]); + + return $this->toDomain($model); + } + + public function findByToken(string $token): ?EmailConfirmationToken + { + $model = EmailConfirmationTokenModel::where( + 'token', $token, + )->first(); + + return $model === null ? null : $this->toDomain($model); + } + + public function findByUser(User $user): ?EmailConfirmationToken + { + $model = EmailConfirmationTokenModel::where( + 'user_id', $user->getId(), + )->first(); + + return $model === null ? null : $this->toDomain($model); + } + + public function delete(int $id): void + { + EmailConfirmationTokenModel::query()->where('id', $id)->delete(); + } + + private function toDomain( + EmailConfirmationTokenModel $model, + ): EmailConfirmationToken { + $user = $this->userRepo->find($model->user_id); + if ($user === null) { + throw new DomainException( + "User with id {$model->user_id} not found" + ); + } + $availableTo = new DateTimeImmutable( + $model->available_to->toDateTimeString(), + new DateTimeZone('UTC'), + ); + + return new EmailConfirmationToken( + id: $model->id, + user: $user, + availableTo: $availableTo, + token: $model->token, + ); + } +} diff --git a/backend/app/Email/EmailConfirmationToken/EmailConfirmationToken.php b/backend/app/Email/EmailConfirmationToken/EmailConfirmationToken.php new file mode 100644 index 0000000..d2fb638 --- /dev/null +++ b/backend/app/Email/EmailConfirmationToken/EmailConfirmationToken.php @@ -0,0 +1,36 @@ +id; + } + + public function getUser(): User + { + return $this->user; + } + + public function getAvailableTo(): DateTimeImmutable + { + return $this->availableTo; + } + + public function getToken(): string + { + return $this->token; + } +} diff --git a/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenModel.php b/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenModel.php new file mode 100644 index 0000000..b8214cd --- /dev/null +++ b/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenModel.php @@ -0,0 +1,40 @@ +|EmailConfirmationTokenModel newModelQuery() + * @method static Builder|EmailConfirmationTokenModel newQuery() + * @method static Builder|EmailConfirmationTokenModel query() + * @method static Builder|EmailConfirmationTokenModel whereId($value) + * @method static Builder|EmailConfirmationTokenModel whereUserId($value) + * @method static Builder|EmailConfirmationTokenModel whereToken($value) + * @method static Builder|EmailConfirmationTokenModel whereAvailableTo($value) + * + * @mixin \Eloquent + */ +class EmailConfirmationTokenModel extends Model +{ + protected $table = 'email_confirmation_tokens'; + + public $timestamps = false; + + protected $fillable = [ + 'user_id', + 'token', + 'available_to', + ]; + + protected $casts = [ + 'available_to' => 'immutable_datetime', + ]; +} diff --git a/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenRepository.php b/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenRepository.php new file mode 100644 index 0000000..ed71f62 --- /dev/null +++ b/backend/app/Email/EmailConfirmationToken/EmailConfirmationTokenRepository.php @@ -0,0 +1,18 @@ +id(); + $table->foreignId('user_id') + ->constrained('users') + ->cascadeOnDelete(); + $table->string('token')->unique(); + $table->dateTime('available_to'); + }); + } + + public function down(): void + { + Schema::dropIfExists('email_confirmation_tokens'); + } +}; diff --git a/backend/tests/Fakes/FakeEmailConfirmationTokenRepository.php b/backend/tests/Fakes/FakeEmailConfirmationTokenRepository.php new file mode 100644 index 0000000..8ef77ad --- /dev/null +++ b/backend/tests/Fakes/FakeEmailConfirmationTokenRepository.php @@ -0,0 +1,82 @@ +nextId(); + $token = new EmailConfirmationToken( + id: $id, + user: $dto->user, + availableTo: $dto->availableTo, + token: bin2hex(random_bytes(32)), + ); + $this->existingTokens[$id] = $token; + + return $this->copy($token); + } + + public function findByToken(string $token): ?EmailConfirmationToken + { + foreach ($this->existingTokens as $existing) { + if ($existing->getToken() === $token) { + return $this->copy($existing); + } + } + + return null; + } + + public function findByUser(User $user): ?EmailConfirmationToken + { + foreach ($this->existingTokens as $existing) { + if ($existing->getUser()->getId() === $user->getId()) { + return $this->copy($existing); + } + } + + return null; + } + + public function delete(int $id): void + { + unset($this->existingTokens[$id]); + } + + private function copy( + EmailConfirmationToken $token, + ): EmailConfirmationToken { + $user = $this->userRepo->find($token->getUser()->getId()) + ?? $token->getUser(); + + return new EmailConfirmationToken( + id: $token->getId(), + user: $user, + availableTo: $token->getAvailableTo(), + token: $token->getToken(), + ); + } + + private function nextId(): int + { + return count($this->existingTokens) + 1; + } +}