Compare commits
91 Commits
da9a0d7f42
...
5ea5d4d0ba
Author | SHA1 | Date | |
---|---|---|---|
5ea5d4d0ba | |||
e72dc03589 | |||
6bf2bc793b | |||
8ccbd5000d | |||
a52b148101 | |||
b5db913c24 | |||
ebc2dfd944 | |||
63d3c8cc6e | |||
630c316aaf | |||
fcb8532b9d | |||
2a7aaed0db | |||
9221e089dd | |||
10425db5e0 | |||
520a3ba068 | |||
18899c81f2 | |||
17fcc6e976 | |||
35c734d840 | |||
c3e4c68a41 | |||
27046e6674 | |||
56cd409301 | |||
d8bf5dd116 | |||
61cc272e2f | |||
d72d6ff614 | |||
9bfd3fef1a | |||
742b0feaf0 | |||
d2b29e2225 | |||
8f143c031f | |||
3e18ca6510 | |||
faa706a79a | |||
941699f473 | |||
9c1ed593b5 | |||
73d99a0432 | |||
52c6fd88d7 | |||
dc6b6b0d42 | |||
4083e2ec5e | |||
ba7e52f8ac | |||
1facb19efb | |||
919f6e1e42 | |||
ec5be8167e | |||
d10cc8603e | |||
adb20f70a5 | |||
2121af2914 | |||
a3fa966643 | |||
9319c2d92d | |||
907bac5586 | |||
b1c9dac3cc | |||
79112680bd | |||
524bf569e3 | |||
e5d0cac07d | |||
39eae7f196 | |||
00a0624eea | |||
55cd927f12 | |||
24098415b5 | |||
398f5e2097 | |||
92206a028a | |||
6dd24ac1d3 | |||
91810190b7 | |||
c18e7e54b7 | |||
00910831a4 | |||
5d61ab425e | |||
f481ee765d | |||
6b2aff910b | |||
d0bd480dc1 | |||
0073dffc28 | |||
719d4c7f10 | |||
fbb56d8191 | |||
37e5c6f8a6 | |||
aed3e0c803 | |||
fe967d71a7 | |||
2ebc9e3218 | |||
6b8554d6d8 | |||
b9f88ba2eb | |||
9b56522f02 | |||
90cab02d62 | |||
34319e5724 | |||
cf449eb8e2 | |||
fcf7cfa584 | |||
a35b8db281 | |||
81635b4efa | |||
c1cf5a1ae9 | |||
f2ecdfcf97 | |||
f6669e93dc | |||
00177a134e | |||
20fbdd7a34 | |||
e189137f6a | |||
3ddfb77508 | |||
f49cc60f9a | |||
587679a33c | |||
795945326e | |||
15225e860c | |||
5545a3e1c5 |
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 4
|
66
.env.example
Normal file
66
.env.example
Normal file
@ -0,0 +1,66 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_DEMO_MODE=false
|
||||
APP_DEMO_EMAIL=
|
||||
APP_DEMO_PASSWORD=
|
||||
|
||||
APP_DEFAULT_USER_TIMEZONE=UTC
|
||||
# Valid languages: ru | en
|
||||
APP_DEFAULT_LOCALE=ru
|
||||
|
||||
LOG_CHANNEL=daily
|
||||
LOG_DEPRECATIONS_CHANNEL=deprecations
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=laravel
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=file
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=mailpit
|
||||
MAIL_PORT=1025
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_ENCRYPTION=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_APP_KEY=
|
||||
PUSHER_APP_SECRET=
|
||||
PUSHER_HOST=
|
||||
PUSHER_PORT=443
|
||||
PUSHER_SCHEME=https
|
||||
PUSHER_APP_CLUSTER=mt1
|
||||
|
||||
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
|
||||
VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.blade.php diff=html
|
||||
*.css diff=css
|
||||
*.html diff=html
|
||||
*.md diff=markdown
|
||||
*.php diff=php
|
||||
|
||||
/.github export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
.styleci.yml export-ignore
|
40
.gitignore
vendored
40
.gitignore
vendored
@ -1,25 +1,19 @@
|
||||
# ---> Laravel
|
||||
/vendor/
|
||||
node_modules/
|
||||
/.phpunit.cache
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/vendor
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpunit.result.cache
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
auth.json
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Laravel 4 specific
|
||||
bootstrap/compiled.php
|
||||
app/storage/
|
||||
|
||||
# Laravel 5 & Lumen specific
|
||||
public/storage
|
||||
public/hot
|
||||
|
||||
# Laravel 5 & Lumen specific with changed public path
|
||||
public_html/storage
|
||||
public_html/hot
|
||||
|
||||
storage/*.key
|
||||
.env
|
||||
Homestead.yaml
|
||||
Homestead.json
|
||||
/.vagrant
|
||||
.phpunit.result.cache
|
||||
|
||||
/.fleet
|
||||
/.idea
|
||||
/.vscode
|
||||
|
9
LICENSE.md
Normal file
9
LICENSE.md
Normal file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Leonid Nikitin (kor-elf)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
50
README.md
50
README.md
@ -1,2 +1,50 @@
|
||||
# Service_captcha
|
||||
## О проекте
|
||||
|
||||
Захотелось написать свой независимый сервис защиты от роботов. Сервис каптча написан на фреймворке Laravel. Вдохновлялся, а так же брал картинки с проекта <a href="https://github.com/wenlng/go-captcha" target="_blank">Go Captcha</a>.
|
||||
|
||||
## Зависимости
|
||||
|
||||
php 8.2 (модули: redis, gd)
|
||||
|
||||
redis
|
||||
|
||||
mysql 8
|
||||
|
||||
## Демострация
|
||||
Демо сервис каптча: https://captcha-admin-demo.tut-site.net/
|
||||
|
||||
Email: demo@tut-site.net
|
||||
|
||||
Пароль: demodemo
|
||||
|
||||
Демо каптча: https://captcha-demo.tut-site.net/
|
||||
|
||||
## API
|
||||
https://captcha-admin-demo.tut-site.net/api-docs/
|
||||
|
||||
## Javascript клиент для сайта
|
||||
https://git.kor-elf.net/kor-elf/captcha-rule-for-laravel
|
||||
|
||||
## Как проверять со стороны бэкенда
|
||||
Для Laravel 10 есть готовый пакет: https://git.kor-elf.net/kor-elf/captcha-rule-for-laravel
|
||||
|
||||
Можно установить этот пакет так: composer require kor-elf/captcha-rule-for-laravel
|
||||
|
||||
<br><b>Curl:</b>
|
||||
|
||||
curl -X POST 'https://captcha-admin-demo.tut-site.net/api/v1/captcha/{captcha-token}' -H 'private-token: {your-private-token}' -H 'Content-Type: application/json' -d '{"user_agent": "{user-agent}"}' --max-time 10
|
||||
|
||||
Где {captcha-token} - токен получил пользователь от сервиса каптча после успешной проверки.
|
||||
|
||||
Где {your-private-token} - приватный токен, который мы создали в админке.
|
||||
|
||||
Где {user-agent} - передаём user agent от пользователя, который проходил каптчу.
|
||||
|
||||
Успешная проверка пользователя вернёт ответ код 200 и status = true. Иначе считаем, что пользователь не прошёл проверку на робота.
|
||||
|
||||
## Репозиторий с демо
|
||||
https://git.kor-elf.net/kor-elf/service-captcha-demo
|
||||
|
||||
## Лицензия
|
||||
|
||||
[MIT license](https://opensource.org/licenses/MIT).
|
||||
|
144
app/Captcha/Config/ImageBody.php
Normal file
144
app/Captcha/Config/ImageBody.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Config;
|
||||
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final readonly class ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private array $backgrounds,
|
||||
private array $fonts,
|
||||
private array $fontColors,
|
||||
private int $width = 300,
|
||||
private int $height = 240,
|
||||
private int $angle = 20,
|
||||
private array | int $fontSize = [20, 50],
|
||||
private int $numberLines = 3,
|
||||
private array $lineColors = []
|
||||
) {
|
||||
if ($this->width <= 0) {
|
||||
throw new CaptchaException('Incorrect $width settings.');
|
||||
}
|
||||
if ($this->height <= 0) {
|
||||
throw new CaptchaException('Incorrect $height settings.');
|
||||
}
|
||||
if (count($this->backgrounds) < 1) {
|
||||
throw new CaptchaException('Invalid $backgrounds parameter.');
|
||||
}
|
||||
if (count($this->fonts) < 1) {
|
||||
throw new CaptchaException('Invalid $fonts parameter.');
|
||||
}
|
||||
if (count($this->fontColors) < 1) {
|
||||
throw new CaptchaException('Invalid $fontColors parameter.');
|
||||
}
|
||||
if ($this->angle < 0) {
|
||||
throw new CaptchaException('Incorrect $angle settings.');
|
||||
}
|
||||
|
||||
if (!is_integer($this->fontSize) && !is_array($this->fontSize)) {
|
||||
throw new CaptchaException('The $fontSize parameter is not an array or integer.');
|
||||
} elseif (is_array($this->fontSize)) {
|
||||
if (count($this->fontSize) > 2) {
|
||||
throw new CaptchaException('The array of $fontSize parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($this->fontSize[0] > $this->fontSize[1]) {
|
||||
throw new CaptchaException('The number of $fontSize[1] is less than $fontSize[0].');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBackgrounds(): array
|
||||
{
|
||||
return $this->backgrounds;
|
||||
}
|
||||
|
||||
public function randomBackground(): string
|
||||
{
|
||||
return Arr::random($this->getBackgrounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFonts(): array
|
||||
{
|
||||
return $this->fonts;
|
||||
}
|
||||
|
||||
public function randomFont(): string
|
||||
{
|
||||
return Arr::random($this->getFonts());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFontColors(): array
|
||||
{
|
||||
return $this->fontColors;
|
||||
}
|
||||
|
||||
public function randomFontColor(): string
|
||||
{
|
||||
return Arr::random($this->getFontColors());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAngle(): int
|
||||
{
|
||||
return $this->angle;
|
||||
}
|
||||
|
||||
public function randomAngle(): int
|
||||
{
|
||||
return mt_rand($this->getAngle() * -1, $this->getAngle());
|
||||
}
|
||||
|
||||
public function getNumberLines(): int
|
||||
{
|
||||
return $this->numberLines;
|
||||
}
|
||||
|
||||
public function fontSize(): int
|
||||
{
|
||||
$fontSize = $this->fontSize;
|
||||
if (is_integer($fontSize)) {
|
||||
return $fontSize;
|
||||
}
|
||||
if (empty($fontSize[1])) {
|
||||
return $fontSize[0];
|
||||
}
|
||||
return random_int($fontSize[0], $fontSize[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLineColors(): array
|
||||
{
|
||||
return $this->lineColors;
|
||||
}
|
||||
}
|
147
app/Captcha/Config/ImageHead.php
Normal file
147
app/Captcha/Config/ImageHead.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Config;
|
||||
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final readonly class ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private array $backgrounds,
|
||||
private array $fonts,
|
||||
private array $fontColors,
|
||||
private int $width = 150,
|
||||
private int $height = 40,
|
||||
private int $textPaddingTop = 5,
|
||||
private int $textPaddingLeft = 10,
|
||||
private int $angle = 20,
|
||||
private int $numberLines = 3,
|
||||
private array $lineColors = []
|
||||
) {
|
||||
if ($this->width <= 0) {
|
||||
throw new CaptchaException('Incorrect $width settings.');
|
||||
}
|
||||
if ($this->height <= 0) {
|
||||
throw new CaptchaException('Incorrect $height settings.');
|
||||
}
|
||||
if ($this->textPaddingTop < 0) {
|
||||
throw new CaptchaException('Incorrect $textPaddingTop settings.');
|
||||
}
|
||||
if ($this->textPaddingLeft < 0) {
|
||||
throw new CaptchaException('Incorrect $textPaddingLeft settings.');
|
||||
}
|
||||
if (count($this->backgrounds) < 1) {
|
||||
throw new CaptchaException('Invalid $backgrounds parameter.');
|
||||
}
|
||||
if (count($this->fonts) < 1) {
|
||||
throw new CaptchaException('Invalid $fonts parameter.');
|
||||
}
|
||||
if (count($this->fontColors) < 1) {
|
||||
throw new CaptchaException('Invalid $fontColors parameter.');
|
||||
}
|
||||
if ($this->angle < 0) {
|
||||
throw new CaptchaException('Incorrect $angle settings.');
|
||||
}
|
||||
if ($this->numberLines < 0) {
|
||||
throw new CaptchaException('Incorrect $numberLines settings.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getBackgrounds(): array
|
||||
{
|
||||
return $this->backgrounds;
|
||||
}
|
||||
|
||||
public function randomBackground(): string
|
||||
{
|
||||
return Arr::random($this->getBackgrounds());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFonts(): array
|
||||
{
|
||||
return $this->fonts;
|
||||
}
|
||||
|
||||
public function randomFont(): string
|
||||
{
|
||||
return Arr::random($this->getFonts());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFontColors(): array
|
||||
{
|
||||
return $this->fontColors;
|
||||
}
|
||||
|
||||
public function randomFontColor(): string
|
||||
{
|
||||
return Arr::random($this->getFontColors());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTextPaddingTop(): int
|
||||
{
|
||||
return $this->textPaddingTop;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getTextPaddingLeft(): int
|
||||
{
|
||||
return $this->textPaddingLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAngle(): int
|
||||
{
|
||||
return $this->angle;
|
||||
}
|
||||
|
||||
public function randomAngle(): int
|
||||
{
|
||||
return mt_rand($this->getAngle() * -1, $this->getAngle());
|
||||
}
|
||||
|
||||
public function getNumberLines(): int
|
||||
{
|
||||
return $this->numberLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLineColors(): array
|
||||
{
|
||||
return $this->lineColors;
|
||||
}
|
||||
}
|
17
app/Captcha/Contracts/Image.php
Normal file
17
app/Captcha/Contracts/Image.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Dto\Coordinators;
|
||||
|
||||
interface Image
|
||||
{
|
||||
public function __construct(int $width, int $height);
|
||||
public function getWidth(): int;
|
||||
public function getHeight(): int;
|
||||
public function insertBackground(string $pathToFile): self;
|
||||
public function addText(string $text, int $x, int $y, float $size, float $angle, string $hexColor, string $fontName): Coordinators;
|
||||
public function addLine(int $x1, int $y1, int $x2, int $y2, string $hexColor): self;
|
||||
public function encode(): string;
|
||||
public function __destruct();
|
||||
}
|
12
app/Captcha/Contracts/ImageBody.php
Normal file
12
app/Captcha/Contracts/ImageBody.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||
use App\Captcha\Dto\ImageBody as ImageBodyDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
interface ImageBody
|
||||
{
|
||||
public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto;
|
||||
}
|
12
app/Captcha/Contracts/ImageHead.php
Normal file
12
app/Captcha/Contracts/ImageHead.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Dto\ImageHead as ImageHeadDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||
|
||||
interface ImageHead
|
||||
{
|
||||
public function processing(Symbols $symbols, ImageHeadConfig $config): ImageHeadDto;
|
||||
}
|
8
app/Captcha/Contracts/ImageLines.php
Normal file
8
app/Captcha/Contracts/ImageLines.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
interface ImageLines
|
||||
{
|
||||
public function processing(Image $image, array $colors, int $lines = 3): Image;
|
||||
}
|
8
app/Captcha/Contracts/ImageManager.php
Normal file
8
app/Captcha/Contracts/ImageManager.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
interface ImageManager
|
||||
{
|
||||
public function createImage(int $width, int $height): Image;
|
||||
}
|
10
app/Captcha/Contracts/Type.php
Normal file
10
app/Captcha/Contracts/Type.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Contracts;
|
||||
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
interface Type
|
||||
{
|
||||
public function getSymbols(): Symbols;
|
||||
}
|
89
app/Captcha/Dto/Coordinators.php
Normal file
89
app/Captcha/Dto/Coordinators.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Coordinators
|
||||
{
|
||||
public function __construct(
|
||||
private int $x1,
|
||||
private int $y1,
|
||||
private int $x2,
|
||||
private int $y2,
|
||||
private int $x3,
|
||||
private int $y3,
|
||||
private int $x4,
|
||||
private int $y4
|
||||
) { }
|
||||
|
||||
/**
|
||||
* lower left x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX1(): int
|
||||
{
|
||||
return $this->x1;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower left y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY1(): int
|
||||
{
|
||||
return $this->y1;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower right x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX2(): int
|
||||
{
|
||||
return $this->x2;
|
||||
}
|
||||
|
||||
/**
|
||||
* lower right y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY2(): int
|
||||
{
|
||||
return $this->y2;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper right x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX3(): int
|
||||
{
|
||||
return $this->x3;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper right y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY3(): int
|
||||
{
|
||||
return $this->y3;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper left x-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getX4(): int
|
||||
{
|
||||
return $this->x4;
|
||||
}
|
||||
|
||||
/**
|
||||
* upper left y-coordinate
|
||||
* @return int
|
||||
*/
|
||||
public function getY4(): int
|
||||
{
|
||||
return $this->y4;
|
||||
}
|
||||
}
|
36
app/Captcha/Dto/Image.php
Normal file
36
app/Captcha/Dto/Image.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Image
|
||||
{
|
||||
public function __construct(
|
||||
private string $imageBase64,
|
||||
private int $width,
|
||||
private int $height
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getImageBase64(): string
|
||||
{
|
||||
return $this->imageBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
27
app/Captcha/Dto/ImageBody.php
Normal file
27
app/Captcha/Dto/ImageBody.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private Image $image,
|
||||
private array $coordinators
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return Image
|
||||
*/
|
||||
public function getImage(): Image
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCoordinators(): array
|
||||
{
|
||||
return $this->coordinators;
|
||||
}
|
||||
}
|
18
app/Captcha/Dto/ImageHead.php
Normal file
18
app/Captcha/Dto/ImageHead.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private Image $image
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return Image
|
||||
*/
|
||||
public function getImage(): Image
|
||||
{
|
||||
return $this->image;
|
||||
}
|
||||
}
|
45
app/Captcha/Dto/Sector.php
Normal file
45
app/Captcha/Dto/Sector.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final readonly class Sector
|
||||
{
|
||||
public function __construct(
|
||||
private int $x,
|
||||
private int $y,
|
||||
private int $width,
|
||||
private int $height,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getX(): int
|
||||
{
|
||||
return $this->x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getY(): int
|
||||
{
|
||||
return $this->y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWidth(): int
|
||||
{
|
||||
return $this->width;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getHeight(): int
|
||||
{
|
||||
return $this->height;
|
||||
}
|
||||
}
|
24
app/Captcha/Dto/Sectors.php
Normal file
24
app/Captcha/Dto/Sectors.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
final class Sectors
|
||||
{
|
||||
private array $points = [];
|
||||
|
||||
public function add(int|float $x, int|float $y, int|float $width, int|float $height): self
|
||||
{
|
||||
$this->points[] = new Sector((int) $x, (int) $y, (int) $width, (int) $height);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function random(): Sector
|
||||
{
|
||||
$key = array_rand($this->points);
|
||||
$sector = $this->points[$key];
|
||||
unset($this->points[$key]);
|
||||
|
||||
return $sector;
|
||||
}
|
||||
}
|
38
app/Captcha/Dto/Symbols.php
Normal file
38
app/Captcha/Dto/Symbols.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Dto;
|
||||
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
|
||||
final readonly class Symbols
|
||||
{
|
||||
public function __construct(
|
||||
private array $success,
|
||||
private array $fakes,
|
||||
private SymbolType $type
|
||||
) { }
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSuccess(): array
|
||||
{
|
||||
return $this->success;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFakes(): array
|
||||
{
|
||||
return $this->fakes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SymbolType
|
||||
*/
|
||||
public function getType(): SymbolType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
8
app/Captcha/Enums/SymbolType.php
Normal file
8
app/Captcha/Enums/SymbolType.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Enums;
|
||||
|
||||
Enum SymbolType {
|
||||
case String;
|
||||
case ImagePath;
|
||||
}
|
8
app/Captcha/Exceptions/CaptchaException.php
Normal file
8
app/Captcha/Exceptions/CaptchaException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Exceptions;
|
||||
|
||||
final class CaptchaException extends \Exception
|
||||
{
|
||||
|
||||
}
|
117
app/Captcha/Images/Body.php
Normal file
117
app/Captcha/Images/Body.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Config\ImageBody as ImageBodyConfig;
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use App\Captcha\Dto\Coordinators;
|
||||
use App\Captcha\Dto\Image as ImageDto;
|
||||
use App\Captcha\Dto\ImageBody as ImageBodyDto;
|
||||
use App\Captcha\Dto\Sector;
|
||||
use App\Captcha\Dto\Sectors;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Contracts\ImageBody;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Contracts\ImageManager;
|
||||
|
||||
final class Body implements ImageBody
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImageManager $imageManager,
|
||||
private readonly ImageLines $imageLines
|
||||
) { }
|
||||
|
||||
public function processing(Symbols $symbols, ImageBodyConfig $config): ImageBodyDto
|
||||
{
|
||||
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
|
||||
$image->insertBackground($config->randomBackground());
|
||||
|
||||
$imageData = match ($symbols->getType()) {
|
||||
SymbolType::String => $this->processingString($image, $symbols, $config),
|
||||
};
|
||||
$image = $imageData['image'];
|
||||
|
||||
if ($config->getNumberLines() > 0) {
|
||||
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
|
||||
}
|
||||
|
||||
$image = new ImageDto(
|
||||
imageBase64: $image->encode(),
|
||||
width: $image->getWidth(),
|
||||
height: $image->getHeight()
|
||||
);
|
||||
|
||||
return new ImageBodyDto(
|
||||
image: $image,
|
||||
coordinators: $imageData['coordinators']
|
||||
);
|
||||
}
|
||||
|
||||
private function processingString(Image $image, Symbols $symbols, ImageBodyConfig $config): array
|
||||
{
|
||||
$sectors = $this->calculateSectors($symbols, $image->getWidth(), $image->getHeight());
|
||||
$coordinators = [];
|
||||
foreach ($symbols->getSuccess() as $number => $symbol) {
|
||||
$coordinators[] = $this->processingStringSymbol($image, $symbol, $config, $sectors->random());
|
||||
}
|
||||
|
||||
if (!empty($symbols->getFakes())) {
|
||||
foreach ($symbols->getFakes() as $number => $symbol) {
|
||||
$this->processingStringSymbol($image, $symbol, $config, $sectors->random());
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'image' => $image,
|
||||
'coordinators' => $coordinators
|
||||
];
|
||||
}
|
||||
|
||||
private function processingStringSymbol(Image &$image, string|int $symbol, ImageBodyConfig $config, Sector $sector): Coordinators
|
||||
{
|
||||
$fontSize = $config->fontSize();
|
||||
if ($fontSize > $sector->getHeight()) {
|
||||
$fontSize = $sector->getHeight();
|
||||
}
|
||||
$fontPathFile = $config->randomFont();
|
||||
$fontColor = $config->randomFontColor();
|
||||
$angle = $config->randomAngle();
|
||||
$marginLeft = mt_rand($sector->getX(), $sector->getX() + $sector->getWidth() - $fontSize);
|
||||
$marginTop = mt_rand($sector->getY(), $sector->getY() + $sector->getHeight() - $fontSize);
|
||||
|
||||
return $image->addText(
|
||||
text: $symbol,
|
||||
x: $marginLeft,
|
||||
y: $marginTop,
|
||||
size: $fontSize,
|
||||
angle: $angle,
|
||||
hexColor: $fontColor,
|
||||
fontName: $fontPathFile
|
||||
);
|
||||
}
|
||||
|
||||
private function calculateSectors(Symbols $symbols, int $width, int $height): Sectors
|
||||
{
|
||||
$points = count($symbols->getSuccess()) + count($symbols->getFakes());
|
||||
$sumFloors = 3;
|
||||
$sumRooms = ceil($points / $sumFloors);
|
||||
|
||||
$heightFloor = floor($height / $sumFloors);
|
||||
$widthFloor = floor($width / $sumRooms);
|
||||
|
||||
$sectors = new Sectors();
|
||||
for ($floor = 0; $floor < $sumFloors; $floor++) {
|
||||
$y = $heightFloor * $floor;
|
||||
for ($room = 0; $room < $sumRooms; $room++) {
|
||||
$sectors->add(
|
||||
x: ($widthFloor * $room),
|
||||
y: $y,
|
||||
width: $widthFloor,
|
||||
height: $heightFloor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sectors;
|
||||
}
|
||||
}
|
72
app/Captcha/Images/Head.php
Normal file
72
app/Captcha/Images/Head.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Config\ImageHead as ImageHeadConfig;
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use App\Captcha\Dto\Image as ImageDto;
|
||||
use App\Captcha\Dto\ImageHead as ImageHeadDto;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Contracts\ImageHead;
|
||||
use \App\Captcha\Contracts\Image as ImageContract;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Contracts\ImageManager;
|
||||
|
||||
final class Head implements ImageHead
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ImageManager $imageManager,
|
||||
private readonly ImageLines $imageLines
|
||||
) { }
|
||||
|
||||
public function processing(Symbols $symbols, ImageHeadConfig $config): ImageHeadDto
|
||||
{
|
||||
$image = $this->imageManager->createImage($config->getWidth(), $config->getHeight());
|
||||
$image->insertBackground($config->randomBackground());
|
||||
|
||||
$image = match ($symbols->getType()) {
|
||||
SymbolType::String => $this->processingString($image, $symbols, $config),
|
||||
};
|
||||
if ($config->getNumberLines() > 0) {
|
||||
$this->imageLines->processing(image: $image, colors: $config->getLineColors(), lines: $config->getNumberLines());
|
||||
}
|
||||
|
||||
$image = new ImageDto(
|
||||
imageBase64: $image->encode(),
|
||||
width: $image->getWidth(),
|
||||
height: $image->getHeight()
|
||||
);
|
||||
|
||||
return new ImageHeadDto(image: $image);
|
||||
}
|
||||
|
||||
private function processingString(ImageContract $image, Symbols $symbols, ImageHeadConfig $config): Image
|
||||
{
|
||||
$textLeftPadding = $config->getTextPaddingLeft();
|
||||
$textTopPadding = $config->getTextPaddingTop();
|
||||
$countSymbols = count($symbols->getSuccess());
|
||||
$widthPrefix = $image->getWidth() - $textLeftPadding;
|
||||
$imageHeight = $image->getHeight() - $textTopPadding;
|
||||
|
||||
foreach ($symbols->getSuccess() as $number => $symbol) {
|
||||
$marginLeft = $textLeftPadding + $number * $widthPrefix / $countSymbols;
|
||||
|
||||
$image->addText(
|
||||
text: $symbol,
|
||||
x: intval($marginLeft),
|
||||
y: $textTopPadding,
|
||||
size: $this->randomFontSize($imageHeight),
|
||||
angle: $config->randomAngle(),
|
||||
hexColor: $config->randomFontColor(),
|
||||
fontName: $config->randomFont()
|
||||
);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function randomFontSize(int $imageHeight): int
|
||||
{
|
||||
return mt_rand($imageHeight - 10, $imageHeight);
|
||||
}
|
||||
}
|
138
app/Captcha/Images/Image.php
Normal file
138
app/Captcha/Images/Image.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Contracts\Image as ImageContract;
|
||||
use App\Captcha\Dto\Coordinators;
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
|
||||
final class Image implements ImageContract
|
||||
{
|
||||
private readonly \GdImage $image;
|
||||
|
||||
public function __construct(int $width, int $height) {
|
||||
$this->image = imagecreatetruecolor($width, $height);
|
||||
}
|
||||
|
||||
public function getWidth(): int
|
||||
{
|
||||
return imagesx($this->image);
|
||||
}
|
||||
|
||||
public function getHeight(): int
|
||||
{
|
||||
return imagesy($this->image);
|
||||
}
|
||||
|
||||
public function insertBackground(string $pathToFile): ImageContract
|
||||
{
|
||||
list($backgroundWidth, $backgroundHeight) = getimagesize($pathToFile);
|
||||
$background = $this->createImageFromPathToFile($pathToFile);
|
||||
imagecopyresampled(
|
||||
dst_image: $this->image,
|
||||
src_image: $background,
|
||||
dst_x: 0,
|
||||
dst_y: 0,
|
||||
src_x: 0,
|
||||
src_y: 0,
|
||||
dst_width: $this->getWidth(),
|
||||
dst_height: $this->getHeight(),
|
||||
src_width: $backgroundWidth,
|
||||
src_height: $backgroundHeight
|
||||
);
|
||||
imagedestroy($background);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addText(string $text, int $x, int $y, float $size, float $angle, string $hexColor, string $fontName): Coordinators
|
||||
{
|
||||
$y += intval($size);
|
||||
$result = imagefttext(
|
||||
image: $this->image,
|
||||
size: $size,
|
||||
angle: $angle,
|
||||
x: $x,
|
||||
y: $y,
|
||||
color: $this->colorConvertHexToColorIndex($hexColor),
|
||||
font_filename: $fontName,
|
||||
text: $text
|
||||
);
|
||||
|
||||
return new Coordinators(
|
||||
x1: $result[0],
|
||||
y1: $result[1],
|
||||
x2: $result[2],
|
||||
y2: $result[3],
|
||||
x3: $result[4],
|
||||
y3: $result[5],
|
||||
x4: $result[6],
|
||||
y4: $result[7]
|
||||
);
|
||||
}
|
||||
|
||||
public function addLine(int $x1, int $y1, int $x2, int $y2, string $hexColor): self
|
||||
{
|
||||
imageline(
|
||||
image: $this->image,
|
||||
x1: $x1,
|
||||
y1: $y1,
|
||||
x2: $x2,
|
||||
y2: $y2,
|
||||
color: $this->colorConvertHexToColorIndex($hexColor)
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function encode(): string
|
||||
{
|
||||
ob_start();
|
||||
imagealphablending($this->image, false);
|
||||
imagesavealpha($this->image, true);
|
||||
imagepng($this->image, null, -1);
|
||||
$mime = image_type_to_mime_type(IMAGETYPE_PNG);
|
||||
$buffer = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return sprintf('data:%s;base64,%s',
|
||||
$mime,
|
||||
base64_encode($buffer)
|
||||
);
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
imagedestroy($this->image);
|
||||
}
|
||||
|
||||
private function createImageFromPathToFile(string $pathToFile): \GdImage
|
||||
{
|
||||
return match (mime_content_type($pathToFile)) {
|
||||
'image/jpeg' => imagecreatefromjpeg($pathToFile),
|
||||
'image/png' => imagecreatefrompng($pathToFile),
|
||||
default => throw new CaptchaException('Couldn\'t open the file. Not a valid type. File ' . $pathToFile . '.')
|
||||
};
|
||||
}
|
||||
|
||||
private function colorConvertHexToColorIndex(string $hexColor): int
|
||||
{
|
||||
$hexColor = str_replace('#', '', $hexColor);
|
||||
|
||||
if (strlen($hexColor) === 3) {
|
||||
return imagecolorexact(
|
||||
image: $this->image,
|
||||
red: hexdec(str_repeat(substr($hexColor, 0, 1), 2)),
|
||||
blue: hexdec(str_repeat(substr($hexColor, 1, 1), 2)),
|
||||
green: hexdec(str_repeat(substr($hexColor, 2, 1), 2))
|
||||
);
|
||||
}
|
||||
|
||||
$colorVal = hexdec($hexColor);
|
||||
return imagecolorexact(
|
||||
image: $this->image,
|
||||
red: 0xFF & ($colorVal >> 0x10),
|
||||
blue: 0xFF & ($colorVal >> 0x8),
|
||||
green: 0xFF & $colorVal
|
||||
);
|
||||
}
|
||||
}
|
18
app/Captcha/Images/ImageManager.php
Normal file
18
app/Captcha/Images/ImageManager.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Contracts\Image;
|
||||
use App\Captcha\Contracts\ImageManager as ImageManagerContract;
|
||||
|
||||
final class ImageManager implements ImageManagerContract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $imageClassName
|
||||
) { }
|
||||
public function createImage(int $width, int $height): Image
|
||||
{
|
||||
return new $this->imageClassName($width, $height);
|
||||
}
|
||||
|
||||
}
|
36
app/Captcha/Images/Lines.php
Normal file
36
app/Captcha/Images/Lines.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Images;
|
||||
|
||||
use App\Captcha\Contracts\ImageLines;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Captcha\Contracts\Image as ImageContract;
|
||||
|
||||
class Lines implements ImageLines
|
||||
{
|
||||
public function processing(ImageContract $image, array $colors, $lines = 3): ImageContract
|
||||
{
|
||||
$imageWidth = $image->getWidth();
|
||||
$imageHeight = $image->getHeight();
|
||||
for ($i = 0; $i <= $lines; $i++) {
|
||||
$image->addLine(
|
||||
x1: mt_rand(0, $imageWidth) + $i * mt_rand(0, $imageHeight),
|
||||
y1: mt_rand(0, $imageHeight),
|
||||
x2: mt_rand(0, $imageWidth),
|
||||
y2: mt_rand(0, $imageHeight),
|
||||
hexColor: $this->lineColors($colors)
|
||||
);
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function lineColors(array $colors): string
|
||||
{
|
||||
if (!empty($colors)) {
|
||||
return Arr::random($colors);
|
||||
}
|
||||
|
||||
return '#' . str_pad(dechex(mt_rand(0, 0xFFFFFF)), 6, '0', STR_PAD_LEFT);
|
||||
}
|
||||
}
|
26
app/Captcha/Type.php
Normal file
26
app/Captcha/Type.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha;
|
||||
|
||||
use App\Captcha\Contracts\Type as TypeContract;
|
||||
use App\Captcha\Dto\Symbols;
|
||||
|
||||
abstract class Type implements TypeContract
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $config
|
||||
) { }
|
||||
|
||||
abstract public function getSymbols(): Symbols;
|
||||
|
||||
final public function getConfig(): array
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
final public function getConfigValue(string | int $name, mixed $default = null): mixed
|
||||
{
|
||||
$config = $this->getConfig();
|
||||
return $config[$name] ?? $default;
|
||||
}
|
||||
}
|
104
app/Captcha/Types/StringType.php
Normal file
104
app/Captcha/Types/StringType.php
Normal file
@ -0,0 +1,104 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Captcha\Types;
|
||||
|
||||
use App\Captcha\Dto\Symbols;
|
||||
use App\Captcha\Enums\SymbolType;
|
||||
use App\Captcha\Exceptions\CaptchaException;
|
||||
use App\Captcha\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
final class StringType extends Type
|
||||
{
|
||||
|
||||
public function getSymbols(): Symbols
|
||||
{
|
||||
$lengthSymbols = $this->lengthSymbols();
|
||||
$lengthFakeSymbols = $this->lengthFakeSymbols();
|
||||
|
||||
$success = $this->randomSymbols($lengthSymbols);
|
||||
$fakes = [];
|
||||
if (!empty($lengthFakeSymbols)) {
|
||||
$fakes = $this->randomSymbols($lengthFakeSymbols, $success);
|
||||
}
|
||||
|
||||
return new Symbols(
|
||||
success: $success,
|
||||
fakes: $fakes,
|
||||
type: SymbolType::String
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @param array $except
|
||||
* @return array
|
||||
* @throws CaptchaException
|
||||
*/
|
||||
private function randomSymbols(int $lenght, array $except = []): array
|
||||
{
|
||||
$symbols = $this->symbols();
|
||||
if (!empty($except)) {
|
||||
$symbols = array_diff($symbols, $except);
|
||||
}
|
||||
|
||||
if (count($symbols) < $lenght) {
|
||||
throw new CaptchaException('The number of characters is less than $lenght.');
|
||||
}
|
||||
|
||||
return Arr::random($symbols, $lenght);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws CaptchaException
|
||||
*/
|
||||
private function symbols(): array
|
||||
{
|
||||
$symbols = $this->getConfigValue('symbols');
|
||||
if (!is_array($symbols)) {
|
||||
throw new CaptchaException('The symbols parameter is not an array.');
|
||||
}
|
||||
return $symbols;
|
||||
}
|
||||
|
||||
private function lengthSymbols(): int
|
||||
{
|
||||
$length = $this->getConfigValue('length_symbols');
|
||||
if (is_integer($length)) {
|
||||
return $length;
|
||||
}
|
||||
if (!is_array($length)) {
|
||||
throw new CaptchaException('The length_symbols parameter is not an array or integer.');
|
||||
}
|
||||
if (count($length) > 2) {
|
||||
throw new CaptchaException('The array of length_symbols parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($length[0] > $length[1]) {
|
||||
throw new CaptchaException('The number of length_symbols[1] is less than length_symbols[0].');
|
||||
}
|
||||
return random_int($length[0], $length[1]);
|
||||
}
|
||||
|
||||
private function lengthFakeSymbols(): int
|
||||
{
|
||||
$length = $this->getConfigValue('length_fake_symbols');
|
||||
if (is_integer($length)) {
|
||||
return $length;
|
||||
}
|
||||
if (is_null($length) || $length === false) {
|
||||
return 0;
|
||||
}
|
||||
if (!is_array($length)) {
|
||||
throw new CaptchaException('The length_fake_symbols parameter is not an array or integer or null or false.');
|
||||
}
|
||||
if (count($length) > 2) {
|
||||
throw new CaptchaException('The array of length_fake_symbols parameters contains more than 2 keys.');
|
||||
}
|
||||
if ($length[0] > $length[1]) {
|
||||
throw new CaptchaException('The number of length_fake_symbols[1] is less than length_fake_symbols[0].');
|
||||
}
|
||||
return random_int($length[0], $length[1]);
|
||||
}
|
||||
}
|
94
app/Console/Commands/CreateUserAdmin.php
Normal file
94
app/Console/Commands/CreateUserAdmin.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Dto\User\ManyRoleDto;
|
||||
use App\Enums\SystemRole;
|
||||
use App\Repositories\RoleRepository;
|
||||
use App\Services\User\UserCommandHandler;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\Validator;
|
||||
|
||||
final class CreateUserAdmin extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'app:create-user-admin {email} {password}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Create admin user.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(UserCommandHandler $userCommandHandler, RoleRepository $roleRepository): void
|
||||
{
|
||||
$validator = $this->getData();
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->errorMessageAndStop($validator->errors()->all());
|
||||
}
|
||||
$data = $validator->valid();
|
||||
|
||||
try {
|
||||
$role = $roleRepository->getRoleByCode(SystemRole::Admin->value);
|
||||
if (is_null($role)) {
|
||||
$this->errorMessageAndStop('Administrator role not found.');
|
||||
}
|
||||
$user = DB::transaction(function () use($data, $userCommandHandler, $role) {
|
||||
$data['name'] = 'Administrator';
|
||||
$user = $userCommandHandler->handleStore($data, $data['password']);
|
||||
$userCommandHandler->handleConfirmationByEmail($user);
|
||||
$roles = new ManyRoleDto([$role->id]);
|
||||
$userCommandHandler->handleSyncRoles($user, $roles);
|
||||
|
||||
return $user;
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
$this->errorMessageAndStop($e->getMessage());
|
||||
}
|
||||
|
||||
$this->info('The command was successful!');
|
||||
}
|
||||
|
||||
private function getData(): Validator
|
||||
{
|
||||
return ValidatorFacade::make([
|
||||
'email' => $this->argument('email'),
|
||||
'password' => $this->argument('password'),
|
||||
], [
|
||||
'email' => ['required', 'email', 'unique:users,email'],
|
||||
'password' => ['required', Password::default()],
|
||||
]);
|
||||
}
|
||||
|
||||
private function stop(): never
|
||||
{
|
||||
exit;
|
||||
}
|
||||
|
||||
private function errorMessageAndStop(string | array $error): never
|
||||
{
|
||||
$this->info('User not created. See error messages below:');
|
||||
|
||||
if (is_array($error)) {
|
||||
foreach ($error as $err) {
|
||||
$this->error($err);
|
||||
}
|
||||
} else {
|
||||
$this->error($error);
|
||||
}
|
||||
|
||||
$this->stop();
|
||||
}
|
||||
}
|
27
app/Console/Kernel.php
Normal file
27
app/Console/Kernel.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*/
|
||||
protected function schedule(Schedule $schedule): void
|
||||
{
|
||||
// $schedule->command('inspire')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the commands for the application.
|
||||
*/
|
||||
protected function commands(): void
|
||||
{
|
||||
$this->load(__DIR__.'/Commands');
|
||||
|
||||
require base_path('routes/console.php');
|
||||
}
|
||||
}
|
9
app/Contracts/CryptographyContract.php
Normal file
9
app/Contracts/CryptographyContract.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface CryptographyContract
|
||||
{
|
||||
public function encrypt(string $text): string;
|
||||
public function decrypt(string $encryptedText): string;
|
||||
}
|
10
app/Contracts/FormRequestDto.php
Normal file
10
app/Contracts/FormRequestDto.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
interface FormRequestDto
|
||||
{
|
||||
public function getDto(): Dto;
|
||||
}
|
11
app/Contracts/GenerateTokenCommand.php
Normal file
11
app/Contracts/GenerateTokenCommand.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
interface GenerateTokenCommand
|
||||
{
|
||||
public function execute(): string;
|
||||
public function unique(Builder $builder, string $field, int $numberAttempts = 10): string;
|
||||
}
|
18
app/Contracts/Search.php
Normal file
18
app/Contracts/Search.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Pagination\CursorPaginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface Search
|
||||
{
|
||||
public function __construct(Relation | Builder $query);
|
||||
public function all(): Collection;
|
||||
public function get(int $limit): Collection;
|
||||
public function pagination(int $limit, int $page = 1): LengthAwarePaginator;
|
||||
public function cursorPaginate(int $limit): CursorPaginator;
|
||||
}
|
10
app/Contracts/ServiceResult.php
Normal file
10
app/Contracts/ServiceResult.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface ServiceResult
|
||||
{
|
||||
public function isSuccess(): bool;
|
||||
public function isError(): bool;
|
||||
}
|
12
app/Contracts/ServiceResultError.php
Normal file
12
app/Contracts/ServiceResultError.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
interface ServiceResultError
|
||||
{
|
||||
public function getCode(): ?int;
|
||||
public function getMessage(): string;
|
||||
public function getErrors(): array;
|
||||
public function getErrorsOrMessage(): array|string;
|
||||
public function getData(): array;
|
||||
}
|
10
app/Dto/Builder/CaptchaToken.php
Normal file
10
app/Dto/Builder/CaptchaToken.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class CaptchaToken
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
10
app/Dto/Builder/Role.php
Normal file
10
app/Dto/Builder/Role.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class Role
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
10
app/Dto/Builder/User.php
Normal file
10
app/Dto/Builder/User.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Builder;
|
||||
|
||||
final readonly class User
|
||||
{
|
||||
public function __construct(
|
||||
|
||||
) { }
|
||||
}
|
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;
|
||||
}
|
||||
}
|
27
app/Dto/QuerySettingsDto.php
Normal file
27
app/Dto/QuerySettingsDto.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto;
|
||||
|
||||
final readonly class QuerySettingsDto
|
||||
{
|
||||
public function __construct(
|
||||
private int $limit,
|
||||
private int $page = 1,
|
||||
private array $queryWith = []
|
||||
) { }
|
||||
|
||||
public function getLimit(): int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getQueryWith(): array
|
||||
{
|
||||
return $this->queryWith;
|
||||
}
|
||||
}
|
21
app/Dto/Repository/DataCaptchaRepository/DataCaptcha.php
Normal file
21
app/Dto/Repository/DataCaptchaRepository/DataCaptcha.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Repository\DataCaptchaRepository;
|
||||
|
||||
final readonly class DataCaptcha
|
||||
{
|
||||
public function __construct(
|
||||
private int $captchaId,
|
||||
private array $coordinators,
|
||||
) { }
|
||||
|
||||
public function getCaptchaId(): int
|
||||
{
|
||||
return $this->captchaId;
|
||||
}
|
||||
|
||||
public function getCoordinators(): array
|
||||
{
|
||||
return $this->coordinators;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
29
app/Dto/Request/Api/V1/Captcha/CheckingDto.php
Normal file
29
app/Dto/Request/Api/V1/Captcha/CheckingDto.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Api\V1\Captcha;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class CheckingDto extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private CaptchaPublicToken $captchaPublicToken,
|
||||
private string $captchaKey,
|
||||
private array $coordinators,
|
||||
) { }
|
||||
|
||||
public function getCaptchaPublicToken(): CaptchaPublicToken
|
||||
{
|
||||
return $this->captchaPublicToken;
|
||||
}
|
||||
|
||||
public function getCaptchaKey(): string
|
||||
{
|
||||
return $this->captchaKey;
|
||||
}
|
||||
|
||||
public function getCoordinators(): array
|
||||
{
|
||||
return $this->coordinators;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?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 VerificationInformationDto extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private CaptchaToken $captchaToken,
|
||||
private HttpUserData $httpUserData,
|
||||
private ?string $userAgent = null,
|
||||
) { }
|
||||
|
||||
public function getCaptchaToken(): CaptchaToken
|
||||
{
|
||||
return $this->captchaToken;
|
||||
}
|
||||
|
||||
public function getUserAgent(): ?string
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
public function getHttpUserData(): HttpUserData
|
||||
{
|
||||
return $this->httpUserData;
|
||||
}
|
||||
}
|
27
app/Dto/Request/Authorization.php
Normal file
27
app/Dto/Request/Authorization.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request;
|
||||
|
||||
final readonly class Authorization extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $email,
|
||||
private string $password,
|
||||
private bool $remember = false
|
||||
) { }
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getRemember(): bool
|
||||
{
|
||||
return $this->remember;
|
||||
}
|
||||
}
|
8
app/Dto/Request/Dto.php
Normal file
8
app/Dto/Request/Dto.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request;
|
||||
|
||||
abstract readonly class Dto
|
||||
{
|
||||
|
||||
}
|
24
app/Dto/Request/Private/CaptchaToken/Index.php
Normal file
24
app/Dto/Request/Private/CaptchaToken/Index.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\CaptchaToken;
|
||||
|
||||
use App\Dto\Builder\CaptchaToken;
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private CaptchaToken $captchaTokenDto,
|
||||
private int $page
|
||||
) { }
|
||||
|
||||
public function getCaptchaTokenDto(): CaptchaToken
|
||||
{
|
||||
return $this->captchaTokenDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
17
app/Dto/Request/Private/CaptchaToken/StoreUpdate.php
Normal file
17
app/Dto/Request/Private/CaptchaToken/StoreUpdate.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\CaptchaToken;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $title
|
||||
) { }
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
}
|
17
app/Dto/Request/Private/Profile/Update.php
Normal file
17
app/Dto/Request/Private/Profile/Update.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Profile;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class Update extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
24
app/Dto/Request/Private/Profile/UpdateSettings.php
Normal file
24
app/Dto/Request/Private/Profile/UpdateSettings.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Profile;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
use App\Enums\Lang;
|
||||
|
||||
final readonly class UpdateSettings extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private ?Lang $lang,
|
||||
private ?string $timezone
|
||||
) { }
|
||||
|
||||
public function getLang(): ?Lang
|
||||
{
|
||||
return $this->lang;
|
||||
}
|
||||
|
||||
public function getTimezone(): ?string
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
}
|
24
app/Dto/Request/Private/Role/Index.php
Normal file
24
app/Dto/Request/Private/Role/Index.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Role;
|
||||
|
||||
use App\Dto\Builder\Role;
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private Role $roleBuilderDto,
|
||||
private int $page
|
||||
) { }
|
||||
|
||||
public function getRoleBuilderDto(): Role
|
||||
{
|
||||
return $this->roleBuilderDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
29
app/Dto/Request/Private/Role/StoreUpdate.php
Normal file
29
app/Dto/Request/Private/Role/StoreUpdate.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\Role;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private ?string $code,
|
||||
private array $permissions,
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
}
|
24
app/Dto/Request/Private/User/Index.php
Normal file
24
app/Dto/Request/Private/User/Index.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\User;
|
||||
|
||||
use App\Dto\Builder\User;
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class Index extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private User $userBuilderDto,
|
||||
private int $page
|
||||
) { }
|
||||
|
||||
public function getUserBuilderDto(): User
|
||||
{
|
||||
return $this->userBuilderDto;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
}
|
42
app/Dto/Request/Private/User/StoreUpdate.php
Normal file
42
app/Dto/Request/Private/User/StoreUpdate.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\User;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
use App\Dto\User\ManyRoleDto;
|
||||
|
||||
final readonly class StoreUpdate extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $name,
|
||||
private string $email,
|
||||
private bool $isActive,
|
||||
private ManyRoleDto $roles,
|
||||
private ?string $password = null
|
||||
) { }
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getRoles(): ManyRoleDto
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
|
||||
public function isActive(): bool
|
||||
{
|
||||
return $this->isActive;
|
||||
}
|
||||
}
|
17
app/Dto/Request/Private/User/UpdatePassword.php
Normal file
17
app/Dto/Request/Private/User/UpdatePassword.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request\Private\User;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
final readonly class UpdatePassword extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $password
|
||||
) { }
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
}
|
32
app/Dto/User/ManyRoleDto.php
Normal file
32
app/Dto/User/ManyRoleDto.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\User;
|
||||
|
||||
use App\Exceptions\Dto\User\ManyRoleDtoException;
|
||||
|
||||
final class ManyRoleDto
|
||||
{
|
||||
private array $roles = [];
|
||||
|
||||
public function __construct(array $roles = []) {
|
||||
foreach ($roles as $role) {
|
||||
if (!is_numeric($role) || is_float($role)) {
|
||||
throw new ManyRoleDtoException('Not an integer: ' . $role . '.');
|
||||
}
|
||||
$this->add((int) $role);
|
||||
}
|
||||
}
|
||||
|
||||
public function add(int $id): void
|
||||
{
|
||||
if ($id < 1) {
|
||||
throw new ManyRoleDtoException('Only Integer > 0.');
|
||||
}
|
||||
$this->roles[] = $id;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->roles;
|
||||
}
|
||||
}
|
11
app/Enums/CaptchaLogType.php
Normal file
11
app/Enums/CaptchaLogType.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum CaptchaLogType: int
|
||||
{
|
||||
case Created = 1;
|
||||
case Error = 2;
|
||||
case Verified = 3;
|
||||
case ReadVerified = 4;
|
||||
}
|
46
app/Enums/Lang.php
Normal file
46
app/Enums/Lang.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
enum Lang: int
|
||||
{
|
||||
case Ru = 1;
|
||||
case En = 2;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Ru => 'Русский',
|
||||
self::En => 'English'
|
||||
};
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::Ru => 'ru',
|
||||
self::En => 'en'
|
||||
};
|
||||
}
|
||||
|
||||
public static function toArray(): array
|
||||
{
|
||||
$choices = [];
|
||||
foreach (self::cases() as $lang) {
|
||||
$choices[] = [
|
||||
'name' => $lang->name,
|
||||
'value' => $lang->value,
|
||||
'title' => $lang->getTitle(),
|
||||
'locale' => $lang->getLocale()
|
||||
];
|
||||
}
|
||||
return $choices;
|
||||
}
|
||||
|
||||
public static function toCollection(): Collection
|
||||
{
|
||||
return collect(self::toArray());
|
||||
}
|
||||
}
|
67
app/Enums/Permission.php
Normal file
67
app/Enums/Permission.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum Permission: string
|
||||
{
|
||||
case Role = 'role';
|
||||
case User = 'user';
|
||||
case CaptchaToken = 'captcha_token';
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
$permissions = match ($this) {
|
||||
self::CaptchaToken => [
|
||||
'view' => __('permissions.Allowed to watch all tokens'),
|
||||
'view_own' => __('permissions.Allowed to view own tokens'),
|
||||
'create' => __('permissions.Allowed to create tokens'),
|
||||
'update' => __('permissions.Allowed to edit all tokens'),
|
||||
'update_own' => __('permissions.Allowed to edit own tokens'),
|
||||
'delete' => __('permissions.Allowed to delete all tokens'),
|
||||
'delete_own' => __('permissions.Allowed to delete own tokens'),
|
||||
],
|
||||
default => $this->getBasePermissions()
|
||||
};
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('permissions.' . $this->name);
|
||||
}
|
||||
|
||||
public function formatValue(string $permission): string
|
||||
{
|
||||
return $this->value . '.' . $permission;
|
||||
}
|
||||
|
||||
public static function toArrayList(): array
|
||||
{
|
||||
$permissions = [];
|
||||
foreach (self::cases() as $permissionEnum) {
|
||||
foreach ($permissionEnum->getPermissions() as $permissionName => $permissionTitle) {
|
||||
$name = $permissionEnum->formatValue($permissionName);
|
||||
$title = $permissionEnum->getTitle() . ' - ' . $permissionTitle;
|
||||
$permissions[$name] = $title;
|
||||
}
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
public static function toArrayListCodes(): array
|
||||
{
|
||||
return \array_keys(self::toArrayList());
|
||||
}
|
||||
|
||||
private function getBasePermissions(): array
|
||||
{
|
||||
return [
|
||||
'view' => __('permissions.Allowed to watch'),
|
||||
'create' => __('permissions.Allowed to create'),
|
||||
'update' => __('permissions.Allowed to edit'),
|
||||
'delete' => __('permissions.Allowed to delete'),
|
||||
];
|
||||
}
|
||||
}
|
8
app/Enums/SystemRole.php
Normal file
8
app/Enums/SystemRole.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum SystemRole: string
|
||||
{
|
||||
case Admin = 'admin';
|
||||
}
|
8
app/Exceptions/Dto/User/ManyRoleDtoException.php
Normal file
8
app/Exceptions/Dto/User/ManyRoleDtoException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Dto\User;
|
||||
|
||||
final class ManyRoleDtoException extends \Exception
|
||||
{
|
||||
|
||||
}
|
48
app/Exceptions/Handler.php
Normal file
48
app/Exceptions/Handler.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of exception types with their corresponding custom log levels.
|
||||
*
|
||||
* @var array<class-string<\Throwable>, \Psr\Log\LogLevel::*>
|
||||
*/
|
||||
protected $levels = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the exception types that are not reported.
|
||||
*
|
||||
* @var array<int, class-string<\Throwable>>
|
||||
*/
|
||||
protected $dontReport = [
|
||||
//
|
||||
];
|
||||
|
||||
/**
|
||||
* A list of the inputs that are never flashed to the session on validation exceptions.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $dontFlash = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
|
||||
/**
|
||||
* Register the exception handling callbacks for the application.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$this->reportable(function (Throwable $e) {
|
||||
//
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Repositories;
|
||||
|
||||
final class DataCaptchaRepositoryException extends \Exception
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Rule;
|
||||
|
||||
final class RoleSyncPermissionsCommandHandlerException extends \Exception
|
||||
{
|
||||
|
||||
}
|
8
app/Exceptions/Service/GenerateTokenCommandException.php
Normal file
8
app/Exceptions/Service/GenerateTokenCommandException.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Exceptions\Service;
|
||||
|
||||
final class GenerateTokenCommandException extends \Exception
|
||||
{
|
||||
|
||||
}
|
39
app/Helpers/Helpers.php
Normal file
39
app/Helpers/Helpers.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
final readonly class Helpers
|
||||
{
|
||||
public static function getTimeZoneList(): Collection
|
||||
{
|
||||
return Cache::rememberForever('timezones_list_collection', function () {
|
||||
$timezone = [];
|
||||
foreach (timezone_identifiers_list(\DateTimeZone::ALL) as $key => $value) {
|
||||
$timezone[$value] = $value . ' (UTC ' . now($value)->format('P') . ')';
|
||||
}
|
||||
return collect($timezone)->sortKeys();
|
||||
});
|
||||
}
|
||||
|
||||
public static function getUserTimeZone() {
|
||||
return auth()->user()?->timezone ?? config('app.user_timezone');
|
||||
}
|
||||
|
||||
public static function isDemoMode(): bool
|
||||
{
|
||||
return config('app.demo_mode', false);
|
||||
}
|
||||
|
||||
public static function isDemoModeAndUserDenyUpdate(User $user): bool
|
||||
{
|
||||
if (self::isDemoMode() !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $user->email === config('app.demo_email');
|
||||
}
|
||||
}
|
57
app/Http/Controllers/Api/V1/CaptchaController.php
Normal file
57
app/Http/Controllers/Api/V1/CaptchaController.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Requests\Api\V1\Captcha\CaptchaRequest;
|
||||
use App\Http\Requests\Api\V1\Captcha\CheckingRequest;
|
||||
use App\Http\Requests\Api\V1\Captcha\VerificationInformationRequest;
|
||||
use App\Http\Resources\Api\V1\Captcha;
|
||||
use App\Http\Resources\Api\V1\CaptchaVerificationInformation;
|
||||
use App\Http\Resources\Api\V1\CaptchaVerified;
|
||||
use App\Services\Api\V1\CaptchaService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
final class CaptchaController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CaptchaService $captchaService
|
||||
) { }
|
||||
|
||||
public function getCaptcha(CaptchaRequest $request): JsonResponse
|
||||
{
|
||||
$captchaPublicToken = $request->getDto();
|
||||
$expires = now()->addSeconds(config('captcha.waiting_for_captcha_verification_in_seconds'));
|
||||
$result = $this->captchaService->createKeyWithCaptcha($captchaPublicToken, $expires);
|
||||
if (!$result->isSuccess()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new Captcha($result));
|
||||
}
|
||||
|
||||
public function checking(CheckingRequest $request): JsonResponse
|
||||
{
|
||||
$maxCountError = config('captcha.validate_max_count_errors');
|
||||
$params = $request->getDto();
|
||||
$result = $this->captchaService->checking($params, $maxCountError);
|
||||
if (!$result->isSuccess()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new CaptchaVerified($result));
|
||||
}
|
||||
|
||||
public function verificationInformation(string $captchaUuid, VerificationInformationRequest $request): JsonResponse
|
||||
{
|
||||
$params = $request->getDto();
|
||||
$expiresMinutes = config('captcha.verification_data_view_limit_in_minutes');
|
||||
$maxInfoDisplayCount = config('captcha.max_info_display_count');
|
||||
$result = $this->captchaService->verificationInformation($captchaUuid, $params, $expiresMinutes, $maxInfoDisplayCount);
|
||||
|
||||
if (!$result->isSuccess()) {
|
||||
return response()->json($result->getData())->setStatusCode($result->getCode());
|
||||
}
|
||||
|
||||
return response()->json(new CaptchaVerificationInformation($result));
|
||||
}
|
||||
}
|
12
app/Http/Controllers/Api/V1/Controller.php
Normal file
12
app/Http/Controllers/Api/V1/Controller.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, ValidatesRequests;
|
||||
}
|
47
app/Http/Controllers/AuthController.php
Normal file
47
app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\AuthorizationRequest;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
final class AuthController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthService $authService
|
||||
) { }
|
||||
|
||||
public function login(): View
|
||||
{
|
||||
return view('public/login');
|
||||
}
|
||||
|
||||
public function authorization(AuthorizationRequest $request): RedirectResponse
|
||||
{
|
||||
$authorization = $request->getDto();
|
||||
$result = $this->authService->authorization($authorization);
|
||||
if ($result->isError()) {
|
||||
if ($result->getCode() === Response::HTTP_UNAUTHORIZED) {
|
||||
Log::warning('Unauthorized ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||
}
|
||||
return redirect()->route('login')->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
$request->session()->regenerate();
|
||||
Log::notice('Logged in ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']');
|
||||
return redirect()->route('home');
|
||||
}
|
||||
|
||||
public function logout(Request $request): RedirectResponse
|
||||
{
|
||||
Auth::logout();
|
||||
$request->session()->invalidate();
|
||||
$request->session()->regenerateToken();
|
||||
return redirect(route('login'));
|
||||
}
|
||||
}
|
13
app/Http/Controllers/Controller.php
Normal file
13
app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
}
|
93
app/Http/Controllers/Private/CaptchaTokensController.php
Normal file
93
app/Http/Controllers/Private/CaptchaTokensController.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Private\CaptchaTokens\IndexRequest;
|
||||
use App\Http\Requests\Private\CaptchaTokens\StoreUpdateRequest;
|
||||
use App\Services\Private\CaptchaTokenService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class CaptchaTokensController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CaptchaTokenService $captchaTokenService
|
||||
) { }
|
||||
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->captchaTokenService->index($data->getCaptchaTokenDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/captcha_tokens/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->captchaTokenService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/captcha_tokens/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->captchaTokenService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/captcha_tokens/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->captchaTokenService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('captcha-tokens.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->captchaTokenService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('captcha-tokens.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->captchaTokenService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('captcha-tokens.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
24
app/Http/Controllers/Private/Controller.php
Normal file
24
app/Http/Controllers/Private/Controller.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Contracts\ServiceResultError as ServiceResultErrorContract;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
|
||||
final protected function errors(ServiceResultErrorContract $result): never
|
||||
{
|
||||
if ($result->getCode() === Response::HTTP_UNPROCESSABLE_ENTITY) {
|
||||
redirect()->back()->withInput()->withErrors($result->getErrors());
|
||||
exit;
|
||||
}
|
||||
abort($result->getCode(), $result->getMessage());
|
||||
}
|
||||
}
|
13
app/Http/Controllers/Private/DashboardController.php
Normal file
13
app/Http/Controllers/Private/DashboardController.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class DashboardController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
return view('private/dashboard/index');
|
||||
}
|
||||
}
|
72
app/Http/Controllers/Private/ProfileController.php
Normal file
72
app/Http/Controllers/Private/ProfileController.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Enums\Lang;
|
||||
use App\Helpers\Helpers;
|
||||
use App\Http\Requests\Private\Profile\UpdatePasswordRequest;
|
||||
use App\Http\Requests\Private\Profile\UpdateRequest;
|
||||
use App\Http\Requests\Private\Profile\UpdateSettingsRequest;
|
||||
use App\Services\Private\ProfileService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ProfileController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProfileService $profileService
|
||||
) { }
|
||||
|
||||
public function profile(Request $request): View
|
||||
{
|
||||
return view('private/profile/profile', [
|
||||
'user' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(Request $request): View
|
||||
{
|
||||
return view('private/profile/settings', [
|
||||
'user' => $request->user(),
|
||||
'languages' => Lang::toCollection()->pluck(value: 'title', key: 'value')->toArray(),
|
||||
'timezone' => Helpers::getTimeZoneList()->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->update($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updatePassword(UpdatePasswordRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->updatePassword($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.edit')->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updateSettings(UpdateSettingsRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
|
||||
$result = $this->profileService->updateSettings($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getMessage());
|
||||
}
|
||||
return redirect()->route('profile.settings')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
92
app/Http/Controllers/Private/RolesController.php
Normal file
92
app/Http/Controllers/Private/RolesController.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Private\Roles\IndexRequest;
|
||||
use App\Http\Requests\Private\Roles\StoreUpdateRequest;
|
||||
use App\Services\Private\RoleService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class RolesController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly RoleService $roleService
|
||||
) { }
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->roleService->index($data->getRoleBuilderDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/roles/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->roleService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('roles.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
106
app/Http/Controllers/Private/UsersController.php
Normal file
106
app/Http/Controllers/Private/UsersController.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use App\Dto\QuerySettingsDto;
|
||||
use App\Http\Requests\Private\Users\IndexRequest;
|
||||
use App\Http\Requests\Private\Users\StoreUpdateRequest;
|
||||
use App\Http\Requests\Private\Users\UpdatePasswordRequest;
|
||||
use App\Services\Private\UserService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class UsersController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserService $userService
|
||||
) { }
|
||||
|
||||
public function index(IndexRequest $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$data = $request->getDto();
|
||||
$querySettingsDto = new QuerySettingsDto(
|
||||
limit: 20,
|
||||
page: $data->getPage(),
|
||||
queryWith: []
|
||||
);
|
||||
|
||||
$result = $this->userService->index($data->getUserBuilderDto(), $querySettingsDto, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/users/index', $result->getData());
|
||||
}
|
||||
|
||||
public function create(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->create($user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/users/create', $result->getData());
|
||||
}
|
||||
|
||||
public function edit(int $id, Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->edit($id, $user);
|
||||
if ($result->isError()) {
|
||||
$this->errors($result);
|
||||
}
|
||||
|
||||
return view('private/users/edit', $result->getData());
|
||||
}
|
||||
|
||||
public function store(StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->store($data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function update(int $id, StoreUpdateRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->update($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function updatePassword(int $id, UpdatePasswordRequest $request): RedirectResponse
|
||||
{
|
||||
$data = $request->getDto();
|
||||
$user = $request->user();
|
||||
$result = $this->userService->updatePassword($id, $data, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('users.edit', $result->getModel())->withSuccess($result->getMessage());
|
||||
}
|
||||
|
||||
public function destroy(int $id, Request $request): RedirectResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
$result = $this->userService->destroy($id, $user);
|
||||
if ($result->isError()) {
|
||||
return redirect()->back()->withInput()->withErrors($result->getErrorsOrMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('users.index')->withSuccess($result->getMessage());
|
||||
}
|
||||
}
|
68
app/Http/Kernel.php
Normal file
68
app/Http/Kernel.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
/**
|
||||
* The application's global HTTP middleware stack.
|
||||
*
|
||||
* These middleware are run during every request to your application.
|
||||
*
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
|
||||
\App\Http\Middleware\TrimStrings::class,
|
||||
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's route middleware groups.
|
||||
*
|
||||
* @var array<string, array<int, class-string|string>>
|
||||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
|
||||
'api' => [
|
||||
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
|
||||
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The application's middleware aliases.
|
||||
*
|
||||
* Aliases may be used to conveniently assign middleware to routes and groups.
|
||||
*
|
||||
* @var array<string, class-string|string>
|
||||
*/
|
||||
protected $middlewareAliases = [
|
||||
'auth' => \App\Http\Middleware\Authenticate::class,
|
||||
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
|
||||
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
|
||||
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
|
||||
'can' => \Illuminate\Auth\Middleware\Authorize::class,
|
||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
|
||||
'verified' => \App\Http\Middleware\EnsureUserIsVerified::class,
|
||||
'user.locale' => \App\Http\Middleware\UserLocale::class,
|
||||
];
|
||||
}
|
17
app/Http/Middleware/Authenticate.php
Normal file
17
app/Http/Middleware/Authenticate.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Auth\Middleware\Authenticate as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Authenticate extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the path the user should be redirected to when they are not authenticated.
|
||||
*/
|
||||
protected function redirectTo(Request $request): ?string
|
||||
{
|
||||
return $request->expectsJson() ? null : route('login');
|
||||
}
|
||||
}
|
17
app/Http/Middleware/EncryptCookies.php
Normal file
17
app/Http/Middleware/EncryptCookies.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
|
||||
|
||||
class EncryptCookies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the cookies that should not be encrypted.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
43
app/Http/Middleware/EnsureUserIsVerified.php
Normal file
43
app/Http/Middleware/EnsureUserIsVerified.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
|
||||
final class EnsureUserIsVerified
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $redirectToRoute
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function handle($request, Closure $next, $redirectToRoute = null)
|
||||
{
|
||||
if (! $request->user()) {
|
||||
return $request->expectsJson()
|
||||
? abort(403)
|
||||
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
|
||||
}
|
||||
|
||||
|
||||
if ($request->user() instanceof MustVerifyEmail && ! $request->user()->hasVerifiedEmail()) {
|
||||
return $request->expectsJson()
|
||||
? abort(403, 'Your email address is not verified.')
|
||||
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
|
||||
}
|
||||
|
||||
if ($request->user()->is_active === false) {
|
||||
return $request->expectsJson()
|
||||
? abort(403, 'User disabled.')
|
||||
: Redirect::guest(URL::route($redirectToRoute ?: 'verification.notice'));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
17
app/Http/Middleware/PreventRequestsDuringMaintenance.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
|
||||
|
||||
class PreventRequestsDuringMaintenance extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be reachable while maintenance mode is enabled.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
30
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
30
app/Http/Middleware/RedirectIfAuthenticated.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class RedirectIfAuthenticated
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
$guards = empty($guards) ? [null] : $guards;
|
||||
|
||||
foreach ($guards as $guard) {
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect(RouteServiceProvider::HOME);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
19
app/Http/Middleware/TrimStrings.php
Normal file
19
app/Http/Middleware/TrimStrings.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
|
||||
|
||||
class TrimStrings extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the attributes that should not be trimmed.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
'current_password',
|
||||
'password',
|
||||
'password_confirmation',
|
||||
];
|
||||
}
|
20
app/Http/Middleware/TrustHosts.php
Normal file
20
app/Http/Middleware/TrustHosts.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts(): array
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
28
app/Http/Middleware/TrustProxies.php
Normal file
28
app/Http/Middleware/TrustProxies.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustProxies as Middleware;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TrustProxies extends Middleware
|
||||
{
|
||||
/**
|
||||
* The trusted proxies for this application.
|
||||
*
|
||||
* @var array<int, string>|string|null
|
||||
*/
|
||||
protected $proxies;
|
||||
|
||||
/**
|
||||
* The headers that should be used to detect proxies.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $headers =
|
||||
Request::HEADER_X_FORWARDED_FOR |
|
||||
Request::HEADER_X_FORWARDED_HOST |
|
||||
Request::HEADER_X_FORWARDED_PORT |
|
||||
Request::HEADER_X_FORWARDED_PROTO |
|
||||
Request::HEADER_X_FORWARDED_AWS_ELB;
|
||||
}
|
19
app/Http/Middleware/UserLocale.php
Normal file
19
app/Http/Middleware/UserLocale.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
final class UserLocale
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
if ($request->user() && $request->user()->lang) {
|
||||
App::setLocale($request->user()->lang->getLocale());
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
22
app/Http/Middleware/ValidateSignature.php
Normal file
22
app/Http/Middleware/ValidateSignature.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Routing\Middleware\ValidateSignature as Middleware;
|
||||
|
||||
class ValidateSignature extends Middleware
|
||||
{
|
||||
/**
|
||||
* The names of the query string parameters that should be ignored.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
// 'fbclid',
|
||||
// 'utm_campaign',
|
||||
// 'utm_content',
|
||||
// 'utm_medium',
|
||||
// 'utm_source',
|
||||
// 'utm_term',
|
||||
];
|
||||
}
|
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
17
app/Http/Middleware/VerifyCsrfToken.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
|
||||
|
||||
class VerifyCsrfToken extends Middleware
|
||||
{
|
||||
/**
|
||||
* The URIs that should be excluded from CSRF verification.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $except = [
|
||||
//
|
||||
];
|
||||
}
|
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
|
||||
);
|
||||
}
|
||||
}
|
68
app/Http/Requests/Api/V1/Captcha/CheckingRequest.php
Normal file
68
app/Http/Requests/Api/V1/Captcha/CheckingRequest.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?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\Dto\Request\Api\V1\Captcha\CheckingDto;
|
||||
use App\Models\CaptchaToken;
|
||||
use App\Repositories\CaptchaTokenRepository;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class CheckingRequest 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 [
|
||||
'captcha_key' => ['required', 'string', 'max:75'],
|
||||
'verification' => ['required', 'array'],
|
||||
'verification.*' => ['required', 'array', 'size:2'],
|
||||
'verification.*.x' => ['required', 'numeric', 'min:0'],
|
||||
'verification.*.y' => ['required', 'numeric', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): CheckingDto
|
||||
{
|
||||
$httpUserData = new HttpUserData(
|
||||
$this->getClientIp(),
|
||||
$this->userAgent(),
|
||||
$this->header('referer')
|
||||
);
|
||||
|
||||
$captchaPublicToken = new CaptchaPublicToken(
|
||||
$this->captchaToken,
|
||||
$httpUserData
|
||||
);
|
||||
|
||||
return new CheckingDto(
|
||||
captchaPublicToken: $captchaPublicToken,
|
||||
captchaKey: $this->input('captcha_key'),
|
||||
coordinators: $this->input('verification'),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
<?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\VerificationInformationDto;
|
||||
use App\Models\CaptchaToken;
|
||||
use App\Repositories\CaptchaTokenRepository;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class VerificationInformationRequest 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('private-token')) {
|
||||
return false;
|
||||
}
|
||||
$captchaToken = $captchaTokenRepository->getCaptchaTokenByPrivateToken($this->header('private-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 [
|
||||
'user_agent' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): VerificationInformationDto
|
||||
{
|
||||
$httpUserData = new HttpUserData(
|
||||
$this->getClientIp(),
|
||||
$this->userAgent(),
|
||||
$this->header('referer')
|
||||
);
|
||||
|
||||
return new VerificationInformationDto(
|
||||
captchaToken: $this->captchaToken,
|
||||
httpUserData: $httpUserData,
|
||||
userAgent: $this->input('user_agent', null),
|
||||
);
|
||||
}
|
||||
}
|
31
app/Http/Requests/AuthorizationRequest.php
Normal file
31
app/Http/Requests/AuthorizationRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Authorization;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AuthorizationRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['required', 'email', 'max:255'],
|
||||
'password' => ['required', 'min:3'],
|
||||
'remember' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Authorization
|
||||
{
|
||||
return new Authorization(
|
||||
email: $this->input('email'),
|
||||
password: $this->input('password'),
|
||||
remember: (bool) $this->input('remember', false)
|
||||
);
|
||||
}
|
||||
}
|
30
app/Http/Requests/Private/CaptchaTokens/IndexRequest.php
Normal file
30
app/Http/Requests/Private/CaptchaTokens/IndexRequest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\CaptchaTokens;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Builder\CaptchaToken;
|
||||
use App\Dto\Request\Private\CaptchaToken\Index;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class IndexRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$this->redirect = route('users.index');
|
||||
return [
|
||||
'page' => ['nullable', 'numeric', 'min:1']
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Index
|
||||
{
|
||||
return new Index(
|
||||
captchaTokenDto: new CaptchaToken(),
|
||||
page: (int) $this->input('page', 1)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\CaptchaTokens;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Private\CaptchaToken\StoreUpdate;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class StoreUpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function getDto(): StoreUpdate
|
||||
{
|
||||
return new StoreUpdate(
|
||||
title: $this->input('title'),
|
||||
);
|
||||
}
|
||||
}
|
26
app/Http/Requests/Private/Profile/UpdatePasswordRequest.php
Normal file
26
app/Http/Requests/Private/Profile/UpdatePasswordRequest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Private\User\UpdatePassword;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
final class UpdatePasswordRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'password' => ['required', 'confirmed', Password::default()],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): UpdatePassword
|
||||
{
|
||||
return new UpdatePassword(password: $this->input('password'));
|
||||
}
|
||||
}
|
25
app/Http/Requests/Private/Profile/UpdateRequest.php
Normal file
25
app/Http/Requests/Private/Profile/UpdateRequest.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Private\Profile\Update;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class UpdateRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'max:255'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): Update
|
||||
{
|
||||
return new Update(name: $this->input('name'));
|
||||
}
|
||||
}
|
38
app/Http/Requests/Private/Profile/UpdateSettingsRequest.php
Normal file
38
app/Http/Requests/Private/Profile/UpdateSettingsRequest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Private\Profile;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Private\Profile\UpdateSettings;
|
||||
use App\Enums\Lang;
|
||||
use App\Helpers\Helpers;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
|
||||
final class UpdateSettingsRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'lang' => ['nullable', new Enum(Lang::class)],
|
||||
'timezone' => ['nullable', Rule::in(Helpers::getTimeZoneList()->keys()->toArray())]
|
||||
];
|
||||
}
|
||||
|
||||
public function getDto(): UpdateSettings
|
||||
{
|
||||
$lang = $this->input('lang', null);
|
||||
if (!is_null($lang)) {
|
||||
$lang = Lang::from((int) $lang);
|
||||
}
|
||||
|
||||
return new UpdateSettings(
|
||||
lang: $lang,
|
||||
timezone: $this->input('timezone', null),
|
||||
);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user