Version 0.7.0 #1
27
app/Dto/HttpUserData.php
Normal file
27
app/Dto/HttpUserData.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto;
|
||||||
|
|
||||||
|
final readonly class HttpUserData
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private ?string $clientIp = null,
|
||||||
|
private ?string $userAgent = null,
|
||||||
|
private ?string $referer = null
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getClientIp(): ?string
|
||||||
|
{
|
||||||
|
return $this->clientIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserAgent(): ?string
|
||||||
|
{
|
||||||
|
return $this->userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReferer(): ?string
|
||||||
|
{
|
||||||
|
return $this->referer;
|
||||||
|
}
|
||||||
|
}
|
25
app/Dto/Request/Api/V1/Captcha/CaptchaPublicToken.php
Normal file
25
app/Dto/Request/Api/V1/Captcha/CaptchaPublicToken.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Dto\Request\Api\V1\Captcha;
|
||||||
|
|
||||||
|
use App\Dto\HttpUserData;
|
||||||
|
use App\Dto\Request\Dto;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
|
||||||
|
final readonly class CaptchaPublicToken extends Dto
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CaptchaToken $captchaToken,
|
||||||
|
private HttpUserData $httpUserData
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getCaptchaToken(): CaptchaToken
|
||||||
|
{
|
||||||
|
return $this->captchaToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHttpUserData(): HttpUserData
|
||||||
|
{
|
||||||
|
return $this->httpUserData;
|
||||||
|
}
|
||||||
|
}
|
8
app/Enums/CaptchaLogType.php
Normal file
8
app/Enums/CaptchaLogType.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Enums;
|
||||||
|
|
||||||
|
enum CaptchaLogType: int
|
||||||
|
{
|
||||||
|
case Created = 1;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Exceptions\Repositories;
|
||||||
|
|
||||||
|
final class DataCaptchaRepositoryException extends \Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api\V1;
|
namespace App\Http\Controllers\Api\V1;
|
||||||
|
|
||||||
|
use App\Http\Requests\Api\V1\Captcha\CaptchaRequest;
|
||||||
use App\Http\Resources\Api\V1\Captcha;
|
use App\Http\Resources\Api\V1\Captcha;
|
||||||
use App\Services\Api\V1\CaptchaService;
|
use App\Services\Api\V1\CaptchaService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
@ -12,9 +13,11 @@ final class CaptchaController extends Controller
|
|||||||
private readonly CaptchaService $captchaService
|
private readonly CaptchaService $captchaService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public function getCaptcha(): JsonResponse
|
public function getCaptcha(CaptchaRequest $request): JsonResponse
|
||||||
{
|
{
|
||||||
$result = $this->captchaService->generate();
|
$captchaPublicToken = $request->getDto();
|
||||||
|
$expires = now()->addSeconds(config('captcha.waiting_for_captcha_verification_in_seconds'));
|
||||||
|
$result = $this->captchaService->createKeyWithCaptcha($captchaPublicToken, $expires);
|
||||||
if (!$result->isSuccess()) {
|
if (!$result->isSuccess()) {
|
||||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||||
}
|
}
|
||||||
|
57
app/Http/Requests/Api/V1/Captcha/CaptchaRequest.php
Normal file
57
app/Http/Requests/Api/V1/Captcha/CaptchaRequest.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Api\V1\Captcha;
|
||||||
|
|
||||||
|
use App\Contracts\FormRequestDto;
|
||||||
|
use App\Dto\HttpUserData;
|
||||||
|
use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Repositories\CaptchaTokenRepository;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
final class CaptchaRequest extends FormRequest implements FormRequestDto
|
||||||
|
{
|
||||||
|
private readonly CaptchaToken $captchaToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*/
|
||||||
|
public function authorize(CaptchaTokenRepository $captchaTokenRepository): bool
|
||||||
|
{
|
||||||
|
if (!$this->hasHeader('public-token')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$captchaToken = $captchaTokenRepository->getCaptchaTokenByPublicToken($this->header('public-token'));
|
||||||
|
if (is_null($captchaToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->captchaToken = $captchaToken;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDto(): CaptchaPublicToken
|
||||||
|
{
|
||||||
|
$httpUserData = new HttpUserData(
|
||||||
|
$this->getClientIp(),
|
||||||
|
$this->userAgent(),
|
||||||
|
$this->header('referer')
|
||||||
|
);
|
||||||
|
|
||||||
|
return new CaptchaPublicToken(
|
||||||
|
$this->captchaToken,
|
||||||
|
$httpUserData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -23,9 +23,17 @@ final class Captcha extends JsonResource
|
|||||||
public function toArray(Request $request): array
|
public function toArray(Request $request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'image_base64' => $this->resource->getImageBase64(),
|
'image_head' => [
|
||||||
'image_text_base64' => $this->resource->getImageTextBase64(),
|
'base64' => $this->resource->getImageHead()->getImageBase64(),
|
||||||
'captcha_key' => $this->resource->getKey()
|
'width' => $this->resource->getImageHead()->getWidth(),
|
||||||
|
'height' => $this->resource->getImageHead()->getHeight(),
|
||||||
|
],
|
||||||
|
'image_body' => [
|
||||||
|
'base64' => $this->resource->getImageBody()->getImageBase64(),
|
||||||
|
'width' => $this->resource->getImageBody()->getWidth(),
|
||||||
|
'height' => $this->resource->getImageBody()->getHeight(),
|
||||||
|
],
|
||||||
|
'captcha_key' => $this->resource->getKey()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
37
app/Models/Captcha.php
Normal file
37
app/Models/Captcha.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
final class Captcha extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'captchas';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'uuid',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'uuid',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function captchaLogs(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(CaptchaLog::class);
|
||||||
|
}
|
||||||
|
}
|
37
app/Models/CaptchaLog.php
Normal file
37
app/Models/CaptchaLog.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\CaptchaLogType;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
final class CaptchaLog extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'captcha_logs';
|
||||||
|
|
||||||
|
const UPDATED_AT = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'type',
|
||||||
|
'ip',
|
||||||
|
'user_agent',
|
||||||
|
'referer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be cast.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'type' => CaptchaLogType::class,
|
||||||
|
];
|
||||||
|
}
|
@ -4,6 +4,7 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
final class CaptchaToken extends Model
|
final class CaptchaToken extends Model
|
||||||
@ -30,4 +31,9 @@ final class CaptchaToken extends Model
|
|||||||
'public_token',
|
'public_token',
|
||||||
'private_token',
|
'private_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function captchas(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Captcha::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use App\Captcha\Images\Body;
|
|||||||
use App\Captcha\Images\Head;
|
use App\Captcha\Images\Head;
|
||||||
use App\Captcha\Images\ImageManager;
|
use App\Captcha\Images\ImageManager;
|
||||||
use App\Captcha\Images\Lines;
|
use App\Captcha\Images\Lines;
|
||||||
use App\Services\Api\V1\CaptchaService;
|
use App\Services\Api\V1\CaptchaGenerateService;
|
||||||
use App\Services\CaptchaToken\CaptchaTokenHandler;
|
use App\Services\CaptchaToken\CaptchaTokenHandler;
|
||||||
use App\Services\GenerateTokenCommand\GenerateTokenUlidCommand;
|
use App\Services\GenerateTokenCommand\GenerateTokenUlidCommand;
|
||||||
use App\Services\GenerateTokenCommand\GenerateTokenUuidCommand;
|
use App\Services\GenerateTokenCommand\GenerateTokenUuidCommand;
|
||||||
@ -39,8 +39,8 @@ final class AppServiceProvider extends ServiceProvider
|
|||||||
return new CreateSearchInstanceCommand(Search::class);
|
return new CreateSearchInstanceCommand(Search::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->app->bind(CaptchaService::class, function (Application $app) {
|
$this->app->bind(CaptchaGenerateService::class, function (Application $app) {
|
||||||
return new CaptchaService(
|
return new CaptchaGenerateService(
|
||||||
config: config('captcha', []),
|
config: config('captcha', []),
|
||||||
imageHead: $app->make(ImageHead::class),
|
imageHead: $app->make(ImageHead::class),
|
||||||
imageBody: $app->make(ImageBody::class)
|
imageBody: $app->make(ImageBody::class)
|
||||||
|
@ -36,4 +36,9 @@ final readonly class CaptchaTokenRepository
|
|||||||
|
|
||||||
return $this->createSearchInstanceCommand->execute($query);
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCaptchaTokenByPublicToken(string $token): ?CaptchaToken
|
||||||
|
{
|
||||||
|
return CaptchaToken::query()->where('public_token', '=', $token)->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
31
app/Repositories/DataCaptchaRepository.php
Normal file
31
app/Repositories/DataCaptchaRepository.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Exceptions\Repositories\DataCaptchaRepositoryException;
|
||||||
|
use App\Models\Captcha;
|
||||||
|
use App\Services\GenerateTokenCommand\GenerateTokenUlidCommand;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
final readonly class DataCaptchaRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private GenerateTokenUlidCommand $generateTokenUlidCommand,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function store(Captcha $captcha, array $coordinators, Carbon $expires): string
|
||||||
|
{
|
||||||
|
$key = $this->generateTokenUlidCommand->execute() . '_' . time();
|
||||||
|
$data = [
|
||||||
|
'id' => $captcha->id,
|
||||||
|
'coordinators' => $coordinators
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!Cache::driver('redis')->add($key, $data, $expires) ) {
|
||||||
|
throw new DataCaptchaRepositoryException('Could not create cache for captcha: ' . $captcha->id . ' key: ' . $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
}
|
25
app/ServiceResults/Api/V1/CaptchaGenerateService/Captcha.php
Normal file
25
app/ServiceResults/Api/V1/CaptchaGenerateService/Captcha.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\ServiceResults\Api\V1\CaptchaGenerateService;
|
||||||
|
|
||||||
|
use App\Captcha\Dto\ImageBody;
|
||||||
|
use App\Captcha\Dto\ImageHead;
|
||||||
|
use App\ServiceResults\ServiceResult;
|
||||||
|
|
||||||
|
final class Captcha extends ServiceResult
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ImageHead $imageHead,
|
||||||
|
private readonly ImageBody $imageBody
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getImageHead(): ImageHead
|
||||||
|
{
|
||||||
|
return $this->imageHead;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getImageBody(): ImageBody
|
||||||
|
{
|
||||||
|
return $this->imageBody;
|
||||||
|
}
|
||||||
|
}
|
@ -2,35 +2,27 @@
|
|||||||
|
|
||||||
namespace App\ServiceResults\Api\V1\CaptchaService;
|
namespace App\ServiceResults\Api\V1\CaptchaService;
|
||||||
|
|
||||||
|
use App\Captcha\Dto\Image;
|
||||||
use App\ServiceResults\ServiceResult;
|
use App\ServiceResults\ServiceResult;
|
||||||
|
|
||||||
final class Captcha extends ServiceResult
|
final class Captcha extends ServiceResult
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly string $imageBase64,
|
private readonly Image $imageHead,
|
||||||
private readonly string $imageTextBase64,
|
private readonly Image $imageBody,
|
||||||
private readonly string $key
|
private readonly string $key
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
public function getImageHead(): Image
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getImageBase64(): string
|
|
||||||
{
|
{
|
||||||
return $this->imageBase64;
|
return $this->imageHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getImageBody(): Image
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getImageTextBase64(): string
|
|
||||||
{
|
{
|
||||||
return $this->imageTextBase64;
|
return $this->imageBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getKey(): string
|
public function getKey(): string
|
||||||
{
|
{
|
||||||
return $this->key;
|
return $this->key;
|
||||||
|
89
app/Services/Api/V1/CaptchaGenerateService.php
Normal file
89
app/Services/Api/V1/CaptchaGenerateService.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Api\V1;
|
||||||
|
|
||||||
|
use App\Captcha\Contracts\ImageBody;
|
||||||
|
use App\Captcha\Contracts\ImageHead;
|
||||||
|
use App\Captcha\Contracts\Type;
|
||||||
|
use App\ServiceResults\Api\V1\CaptchaGenerateService\Captcha;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||||
|
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||||
|
|
||||||
|
final class CaptchaGenerateService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly array $config,
|
||||||
|
private readonly ImageHead $imageHead,
|
||||||
|
private readonly ImageBody $imageBody
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function generate(): ServiceResultError | Captcha
|
||||||
|
{
|
||||||
|
$types = $this->config['types'] ?? [];
|
||||||
|
if (empty($types)) {
|
||||||
|
$error = __('No captcha type settings!');
|
||||||
|
report($error);
|
||||||
|
return $this->errService($error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$type = Arr::random($types);
|
||||||
|
|
||||||
|
/** @var Type $captcha */
|
||||||
|
$typeCaptcha = new $type['class'](
|
||||||
|
$type['params'] ?? []
|
||||||
|
);
|
||||||
|
$symbols = $typeCaptcha->getSymbols();
|
||||||
|
|
||||||
|
$imageHeadConfig = $this->makeImageHeadConfig($this->config['image_head'] ?? []);
|
||||||
|
$imageHead = $this->imageHead->processing($symbols, $imageHeadConfig);
|
||||||
|
unset($imageHeadConfig);
|
||||||
|
|
||||||
|
$imageBodyConfig = $this->makeImageBodyConfig($this->config['image_body'] ?? []);
|
||||||
|
$imageBody = $this->imageBody->processing($symbols, $imageBodyConfig);
|
||||||
|
unset($imageBodyConfig);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService('Captcha service error!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Captcha(
|
||||||
|
imageHead: $imageHead,
|
||||||
|
imageBody: $imageBody
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeImageHeadConfig(array $params): ImageHeadConfig
|
||||||
|
{
|
||||||
|
return new ImageHeadConfig(
|
||||||
|
backgrounds: $params['backgrounds'] ?? [],
|
||||||
|
fonts: $params['fonts'] ?? [],
|
||||||
|
fontColors: $params['font_colors'] ?? [],
|
||||||
|
width: $params['width'] ?? 150,
|
||||||
|
height: $params['height'] ?? 40,
|
||||||
|
textPaddingTop: $params['text_padding_top'] ?? 5,
|
||||||
|
textPaddingLeft: $params['text_padding_left'] ?? 10,
|
||||||
|
angle: $params['angle'] ?? 20,
|
||||||
|
numberLines: $params['number_lines'] ?? 3,
|
||||||
|
lineColors: $params['line_colors'] ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function makeImageBodyConfig(array $params): ImageBodyConfig
|
||||||
|
{
|
||||||
|
return new ImageBodyConfig(
|
||||||
|
backgrounds: $params['backgrounds'] ?? [],
|
||||||
|
fonts: $params['fonts'] ?? [],
|
||||||
|
fontColors: $params['font_colors'] ?? [],
|
||||||
|
width: $params['width'] ?? 300,
|
||||||
|
height: $params['height'] ?? 240,
|
||||||
|
angle: $params['angle'] ?? 20,
|
||||||
|
fontSize: $params['font_size'] ?? [20, 50],
|
||||||
|
numberLines: $params['number_lines'] ?? 3,
|
||||||
|
lineColors: $params['line_colors'] ?? []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -2,96 +2,45 @@
|
|||||||
|
|
||||||
namespace App\Services\Api\V1;
|
namespace App\Services\Api\V1;
|
||||||
|
|
||||||
use App\Captcha\Contracts\ImageBody;
|
use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
|
||||||
use App\Captcha\Contracts\ImageHead;
|
use App\Repositories\DataCaptchaRepository;
|
||||||
use App\Captcha\Contracts\Type;
|
|
||||||
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
|
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
|
||||||
use App\ServiceResults\ServiceResultError;
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\Services\Captcha\CaptchaHandler;
|
||||||
use App\Services\Service;
|
use App\Services\Service;
|
||||||
use Illuminate\Support\Arr;
|
use Carbon\Carbon;
|
||||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
use Illuminate\Support\Facades\DB;
|
||||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
|
||||||
|
|
||||||
final class CaptchaService extends Service
|
final class CaptchaService extends Service
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly array $config,
|
private readonly CaptchaGenerateService $captchaGenerateService,
|
||||||
private readonly ImageHead $imageHead,
|
private readonly CaptchaHandler $captchaHandler,
|
||||||
private readonly ImageBody $imageBody
|
private readonly DataCaptchaRepository $dataCaptchaRepository,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public function generate(): ServiceResultError | Captcha
|
public function createKeyWithCaptcha(CaptchaPublicToken $captchaPublicToken, Carbon $expires): ServiceResultError | Captcha
|
||||||
{
|
{
|
||||||
$types = $this->config['types'] ?? [];
|
|
||||||
if (empty($types)) {
|
|
||||||
$error = __('No captcha type settings!');
|
|
||||||
report($error);
|
|
||||||
return $this->errService($error);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$type = Arr::random($types);
|
$captcha = $this->captchaGenerateService->generate();
|
||||||
|
if ($captcha->isError()) {
|
||||||
|
return $captcha;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var Type $captcha */
|
$modelCaptcha = DB::transaction(function () use ($captchaPublicToken) {
|
||||||
$typeCaptcha = new $type['class'](
|
return $this->captchaHandler->handleStore($captchaPublicToken->getCaptchaToken(), $captchaPublicToken->getHttpUserData());
|
||||||
$type['params'] ?? []
|
});
|
||||||
);
|
|
||||||
$symbols = $typeCaptcha->getSymbols();
|
|
||||||
|
|
||||||
$imageHeadConfig = $this->makeImageHeadConfig($this->config['image_head'] ?? []);
|
$captchaKey = $this->dataCaptchaRepository->store($modelCaptcha, $captcha->getImageBody()->getCoordinators(), $expires);
|
||||||
$imageHead = $this->imageHead->processing($symbols, $imageHeadConfig);
|
|
||||||
unset($imageHeadConfig);
|
|
||||||
|
|
||||||
$imageBodyConfig = $this->makeImageBodyConfig($this->config['image_body'] ?? []);
|
|
||||||
$imageBody = $this->imageBody->processing($symbols, $imageBodyConfig);
|
|
||||||
unset($imageBodyConfig);
|
|
||||||
|
|
||||||
|
|
||||||
dd($imageHead, $imageBody);
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
report($e);
|
report($e);
|
||||||
return $this->errService('Captcha service error!');
|
return $this->errService('Captcha service error!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return new Captcha(
|
return new Captcha(
|
||||||
imageBase64: $imageHead->getImage()->getImageBase64(),
|
imageHead: $captcha->getImageHead()->getImage(),
|
||||||
imageTextBase64: $imageBody->getImage()->getImageBase64(),
|
imageBody: $captcha->getImageBody()->getImage(),
|
||||||
key: 'dddd'
|
key: $captchaKey
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeImageHeadConfig(array $params): ImageHeadConfig
|
|
||||||
{
|
|
||||||
return new ImageHeadConfig(
|
|
||||||
backgrounds: $params['backgrounds'] ?? [],
|
|
||||||
fonts: $params['fonts'] ?? [],
|
|
||||||
fontColors: $params['font_colors'] ?? [],
|
|
||||||
width: $params['width'] ?? 150,
|
|
||||||
height: $params['height'] ?? 40,
|
|
||||||
textPaddingTop: $params['text_padding_top'] ?? 5,
|
|
||||||
textPaddingLeft: $params['text_padding_left'] ?? 10,
|
|
||||||
angle: $params['angle'] ?? 20,
|
|
||||||
numberLines: $params['number_lines'] ?? 3,
|
|
||||||
lineColors: $params['line_colors'] ?? []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function makeImageBodyConfig(array $params): ImageBodyConfig
|
|
||||||
{
|
|
||||||
return new ImageBodyConfig(
|
|
||||||
backgrounds: $params['backgrounds'] ?? [],
|
|
||||||
fonts: $params['fonts'] ?? [],
|
|
||||||
fontColors: $params['font_colors'] ?? [],
|
|
||||||
width: $params['width'] ?? 300,
|
|
||||||
height: $params['height'] ?? 240,
|
|
||||||
angle: $params['angle'] ?? 20,
|
|
||||||
fontSize: $params['font_size'] ?? [20, 50],
|
|
||||||
numberLines: $params['number_lines'] ?? 3,
|
|
||||||
lineColors: $params['line_colors'] ?? []
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
app/Services/Captcha/CaptchaHandler.php
Normal file
28
app/Services/Captcha/CaptchaHandler.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Captcha;
|
||||||
|
|
||||||
|
use App\Dto\HttpUserData;
|
||||||
|
use App\Enums\CaptchaLogType;
|
||||||
|
use App\Models\Captcha;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Services\CaptchaLog\CaptchaLogHandler;
|
||||||
|
use App\Services\GenerateTokenCommand\GenerateTokenUuidCommand;
|
||||||
|
|
||||||
|
final readonly class CaptchaHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private GenerateTokenUuidCommand $uuidCommand,
|
||||||
|
private CaptchaLogHandler $captchaLogHandler
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function handleStore(CaptchaToken $captchaToken, HttpUserData $httpUserData): Captcha
|
||||||
|
{
|
||||||
|
$captcha = $captchaToken->captchas()->create([
|
||||||
|
'uuid' => $this->uuidCommand->unique($captchaToken->captchas()->getQuery(), 'uuid')
|
||||||
|
]);
|
||||||
|
$this->captchaLogHandler->handleStore($captcha, CaptchaLogType::Created, $httpUserData);
|
||||||
|
|
||||||
|
return $captcha;
|
||||||
|
}
|
||||||
|
}
|
22
app/Services/CaptchaLog/CaptchaLogHandler.php
Normal file
22
app/Services/CaptchaLog/CaptchaLogHandler.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\CaptchaLog;
|
||||||
|
|
||||||
|
use App\Dto\HttpUserData;
|
||||||
|
use App\Enums\CaptchaLogType;
|
||||||
|
use App\Models\Captcha;
|
||||||
|
use App\Models\CaptchaLog;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class CaptchaLogHandler
|
||||||
|
{
|
||||||
|
public function handleStore(Captcha $captcha, CaptchaLogType $captchaLogType, HttpUserData $httpUserData): CaptchaLog
|
||||||
|
{
|
||||||
|
return $captcha->captchaLogs()->create([
|
||||||
|
'type' => $captchaLogType,
|
||||||
|
'ip' => $httpUserData->getClientIp(),
|
||||||
|
'user_agent' => Str::limit($httpUserData->getUserAgent(), 255, ''),
|
||||||
|
'referer' => Str::limit($httpUserData->getReferer(), 10000, ''),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
'waiting_for_captcha_verification_in_seconds' => env('WAITING_FOR_CAPTCHA_VERIFICATION_IN_SECONDS', 900),
|
||||||
'imageClass' => \App\Captcha\Images\Image::class,
|
'imageClass' => \App\Captcha\Images\Image::class,
|
||||||
'types' => [
|
'types' => [
|
||||||
'string' => [
|
'string' => [
|
||||||
|
44
database/migrations/2023_08_27_163058_create_captcha.php
Normal file
44
database/migrations/2023_08_27_163058_create_captcha.php
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('captchas', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->uuid('uuid');
|
||||||
|
$table->unsignedBigInteger('captcha_token_id')->index();
|
||||||
|
$table->foreign('captcha_token_id')->references('id')->on('captcha_tokens');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->index('created_at');
|
||||||
|
$table->unique(['uuid', 'captcha_token_id']);
|
||||||
|
});
|
||||||
|
Schema::create('captcha_logs', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('captcha_id')->index();
|
||||||
|
$table->foreign('captcha_id')->references('id')->on('captchas');
|
||||||
|
$table->unsignedInteger('type')->index();
|
||||||
|
$table->ipAddress('ip')->nullable();
|
||||||
|
$table->string('user_agent')->nullable();
|
||||||
|
$table->text('referer')->nullable();
|
||||||
|
$table->timestamp('created_at')->index();
|
||||||
|
$table->index(['captcha_id', 'type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('captcha_logs');
|
||||||
|
Schema::dropIfExists('captchas');
|
||||||
|
}
|
||||||
|
};
|
@ -157,20 +157,42 @@
|
|||||||
"Captcha": {
|
"Captcha": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"image_base64": {
|
"image_head": {
|
||||||
"type": "string",
|
"type": "array",
|
||||||
"format": "byte"
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Image",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"image_text_base64": {
|
"image_body": {
|
||||||
"type": "string",
|
"type": "array",
|
||||||
"format": "byte"
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Image"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"captcha_key": {
|
"captcha_key": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"image_base64", "image_text_base64", "captcha_key"
|
"image_head", "image_body", "captcha_key"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Image": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"base64": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"base64", "width", "height"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"CaptchaVerification": {
|
"CaptchaVerification": {
|
||||||
|
Loading…
Reference in New Issue
Block a user