Skip to content

Commit

Permalink
Refactored code
Browse files Browse the repository at this point in the history
  • Loading branch information
VampireAotD committed Aug 27, 2024
1 parent f3437cd commit 3963511
Show file tree
Hide file tree
Showing 18 changed files with 214 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [ 20.x ]
pnpm-version: [ 9.3 ]
pnpm-version: [ 9.7 ]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node-version: [ 20.x ]
pnpm-version: [ 9.3 ]
pnpm-version: [ 9.7 ]

steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions src/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=example@gmail.com
MAIL_FROM_NAME="${APP_NAME}"
MAIL_TEMPORARY_DOMAIN=anilibrary-temporary.mail

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
Expand Down
1 change: 1 addition & 0 deletions src/.env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=testing@gmail.com
MAIL_FROM_NAME="${APP_NAME}"
MAIL_TEMPORARY_DOMAIN=anilibrary-testing-temporary.mail

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,35 @@

namespace App\DTO\Service\Telegram\User;

use App\DTO\Contracts\FromArray;
use Illuminate\Contracts\Support\Arrayable;

/**
* @template-implements Arrayable<string, mixed>
*/
final readonly class RegisterTelegramUserDTO implements Arrayable
final readonly class TelegramUserDTO implements FromArray, Arrayable
{
public function __construct(
public int $telegramId,
public ?string $firstName = null,
public ?string $lastName = null,
public ?string $userName = null
public ?string $username = null
) {
}

/**
* @param array{id: int, first_name?: string, last_name?: string, username?: string} $data
*/
public static function fromArray(array $data): self
{
return new self(
$data['id'],
$data['first_name'] ?? null,
$data['last_name'] ?? null,
$data['username'] ?? null
);
}

/**
* Get the instance as an array.
*
Expand All @@ -30,7 +44,7 @@ public function toArray(): array
'telegram_id' => $this->telegramId,
'first_name' => $this->firstName,
'last_name' => $this->lastName,
'username' => $this->userName,
'username' => $this->username,
];
}
}
15 changes: 15 additions & 0 deletions src/app/Exceptions/Service/Telegram/TelegramUserException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Exceptions\Service\Telegram;

use Exception;

final class TelegramUserException extends Exception
{
public static function userAlreadyRegistered(): self
{
return new self('User already registered');
}
}
29 changes: 4 additions & 25 deletions src/app/Http/Controllers/Telegram/TelegramController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

namespace App\Http\Controllers\Telegram;

use App\DTO\Service\Telegram\User\RegisterTelegramUserDTO;
use App\DTO\Service\Telegram\User\TelegramUserDTO;
use App\Http\Controllers\Controller;
use App\Http\Requests\Telegram\AssignRequest;
use App\Services\TelegramUserService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Throwable;

Expand All @@ -24,15 +23,10 @@ public function __construct(private readonly TelegramUserService $telegramUserSe
*/
public function assign(AssignRequest $request): RedirectResponse
{
$dto = new RegisterTelegramUserDTO(
(int) $request->get('id'),
$request->get('first_name'),
$request->get('last_name'),
$request->get('username'),
);

try {
$this->telegramUserService->createAndAttach($request->user(), $dto);
$this->telegramUserService->assign($request->user(), TelegramUserDTO::fromArray($request->validated()));

return back();
} catch (Throwable $e) {
Log::error('Failed to assign telegram user', [
'exception_trace' => $e->getTraceAsString(),
Expand All @@ -41,20 +35,5 @@ public function assign(AssignRequest $request): RedirectResponse

return back()->withErrors(['message' => $e->getMessage()]);
}

return back();
}

/**
* @param Request $request
* @return RedirectResponse
*/
public function detach(Request $request): RedirectResponse
{
if (!$request->user()?->telegramUser()?->delete()) {
return back()->withErrors(['message' => 'Could not revoke Telegram account']);
}

return back();
}
}
22 changes: 8 additions & 14 deletions src/app/Http/Middleware/Telegram/ValidateSignatureMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@

namespace App\Http\Middleware\Telegram;

use App\Services\TelegramUserService;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ValidateSignatureMiddleware
readonly class ValidateSignatureMiddleware
{
public function __construct(private TelegramUserService $telegramUserService)
{
}

/**
* Handle an incoming request.
*
Expand All @@ -22,19 +27,8 @@ public function handle(Request $request, Closure $next): Response
$telegramHash = $request->get('hash');
abort_if(!$telegramHash, Response::HTTP_BAD_REQUEST, 'Missing Telegram signature');

$hashedToken = hash('sha256', config('nutgram.token'), true);

// Transform all request fields into signature
$signature = $request->collect()
->except('hash')
->map(fn(mixed $value, string $key) => sprintf('%s=%s', $key, $value))
->values()
->sort()
->implode(PHP_EOL);

$hashedSignature = hash_hmac('sha256', $signature, $hashedToken);

abort_if(!hash_equals($telegramHash, $hashedSignature), Response::HTTP_FORBIDDEN);
$signature = $this->telegramUserService->generateSignature($request->toArray());
abort_if(!hash_equals($telegramHash, $signature), Response::HTTP_FORBIDDEN);

return $next($request);
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/Http/Requests/Telegram/AssignRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function authorize(): bool
public function rules(): array
{
return [
'id' => 'required|int',
'id' => 'required|int|unique:telegram_users,telegram_id',
'auth_date' => 'required|int',
'hash' => 'required|string',
'first_name' => 'nullable|string',
Expand Down
9 changes: 6 additions & 3 deletions src/app/Jobs/Telegram/RegisterTelegramUserJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

namespace App\Jobs\Telegram;

use App\DTO\Service\Telegram\User\RegisterTelegramUserDTO;
use App\DTO\Service\Telegram\User\TelegramUserDTO;
use App\Enums\QueueEnum;
use App\Exceptions\Service\Telegram\TelegramUserException;
use App\Services\TelegramUserService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;

class RegisterTelegramUserJob implements ShouldQueue
{
Expand All @@ -23,16 +25,17 @@ class RegisterTelegramUserJob implements ShouldQueue
/**
* Create a new job instance.
*/
public function __construct(public readonly RegisterTelegramUserDTO $dto)
public function __construct(public readonly TelegramUserDTO $dto)
{
$this->onQueue(QueueEnum::TELEGRAM_QUEUE->value)->onConnection('redis');
}

/**
* Execute the job.
* @throws TelegramUserException|Throwable
*/
public function handle(TelegramUserService $telegramUserService): void
{
$telegramUserService->upsert($this->dto);
$telegramUserService->register($this->dto);
}
}
12 changes: 12 additions & 0 deletions src/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Notifications\Auth\VerifyEmailNotification;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasOne;
Expand Down Expand Up @@ -56,4 +57,15 @@ public function sendEmailVerificationNotification(): void
{
$this->notify(new VerifyEmailNotification());
}

/**
* @psalm-suppress TooManyTemplateParams Suppressed because PHPStan needs description, but Psalm conflicts with it
* @return Attribute<bool, never>
*/
protected function hasTemporaryEmail(): Attribute
{
return Attribute::make(
get: fn(): bool => str_ends_with($this->email, config('mail.temporary_domain')),
)->shouldCache();
}
}
58 changes: 42 additions & 16 deletions src/app/Services/TelegramUserService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,69 @@

namespace App\Services;

use App\DTO\Service\Telegram\User\RegisterTelegramUserDTO;
use App\DTO\Service\Telegram\User\TelegramUserDTO;
use App\Exceptions\Service\Telegram\TelegramUserException;
use App\Models\TelegramUser;
use App\Models\User;
use App\Repositories\TelegramUser\TelegramUserRepositoryInterface;
use App\Repositories\User\UserRepositoryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;

final readonly class TelegramUserService
{
public function __construct(private TelegramUserRepositoryInterface $telegramUserRepository)
public function __construct(
private UserRepositoryInterface $userRepository,
private TelegramUserRepositoryInterface $telegramUserRepository
) {
}

public function generateSignature(array $data = []): string
{
$token = hash('sha256', config('nutgram.token'), true);

$signature = collect($data)
->except('hash')
->map(fn(mixed $value, string $key): string => sprintf('%s=%s', $key, $value))
->values()
->sort()
->implode(PHP_EOL);

return hash_hmac('sha256', $signature, $token);
}

public function upsert(RegisterTelegramUserDTO $dto): TelegramUser
public function upsert(TelegramUserDTO $dto): TelegramUser
{
return $this->telegramUserRepository->upsert($dto->toArray());
}

/**
* @throws Throwable
* @throws TelegramUserException|Throwable
*/
public function createAndAttach(User $user, RegisterTelegramUserDTO $dto): TelegramUser
public function register(TelegramUserDTO $dto): TelegramUser
{
return DB::transaction(function () use ($dto, $user): TelegramUser {
/** @var TelegramUser $telegramUser */
$telegramUser = TelegramUser::withTrashed()->updateOrCreate(
['telegram_id' => $dto->telegramId],
$dto->toArray()
);
if ($this->telegramUserRepository->findByTelegramId($dto->telegramId)) {
throw TelegramUserException::userAlreadyRegistered();
}

if ($telegramUser->trashed()) {
$telegramUser->restore();
}
$domain = config('mail.temporary_domain');

$telegramUser->user()->associate($user)->save();
return DB::transaction(function () use ($dto, $domain): TelegramUser {
$user = $this->userRepository->upsert([
'name' => $dto->telegramId,
'email' => "$dto->telegramId@$domain",
'password' => Str::random(),
]);

return $telegramUser;
$user->markEmailAsVerified();

return $user->telegramUser()->create($dto->toArray());
});
}

public function assign(User $user, TelegramUserDTO $dto): void
{
$user->telegramUser()->updateOrCreate(['telegram_id' => $dto->telegramId], $dto->toArray());
}
}
6 changes: 3 additions & 3 deletions src/app/Telegram/Commands/StartCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace App\Telegram\Commands;

use App\DTO\Service\Telegram\User\RegisterTelegramUserDTO;
use App\DTO\Service\Telegram\User\TelegramUserDTO;
use App\Enums\Telegram\Actions\ActionEnum;
use App\Enums\Telegram\Buttons\CommandButtonEnum;
use App\Jobs\Telegram\RegisterTelegramUserJob;
Expand All @@ -25,11 +25,11 @@ public function handle(Nutgram $bot): void

if ($user && !$user->is_bot) {
RegisterTelegramUserJob::dispatch(
new RegisterTelegramUserDTO(
new TelegramUserDTO(
telegramId: $user->id,
firstName : $user->first_name,
lastName : $user->last_name,
userName : $user->username
username : $user->username
)
);
}
Expand Down
9 changes: 9 additions & 0 deletions src/config/mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,13 @@
],
],

/*-------------------------------------------------------------------------
| Email Domain
|--------------------------------------------------------------------------
|
| Temporary email domain used for registration and verification.
|
*/
'temporary_domain' => env('MAIL_TEMPORARY_DOMAIN'),

];
Loading

0 comments on commit 3963511

Please sign in to comment.