Add captcha tokens management.
This commit is contained in:
parent
d2b29e2225
commit
742b0feaf0
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;
|
||||||
|
}
|
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(
|
||||||
|
|
||||||
|
) { }
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,24 @@ enum Permission: string
|
|||||||
{
|
{
|
||||||
case Role = 'role';
|
case Role = 'role';
|
||||||
case User = 'user';
|
case User = 'user';
|
||||||
|
case CaptchaToken = 'captcha_token';
|
||||||
|
|
||||||
public function getPermissions(): array
|
public function getPermissions(): array
|
||||||
{
|
{
|
||||||
return $this->getBasePermissions();
|
$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
|
public function getTitle(): string
|
||||||
|
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
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
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());
|
||||||
|
}
|
||||||
|
}
|
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');
|
||||||
|
}
|
||||||
|
}
|
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'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
33
app/Models/CaptchaToken.php
Normal file
33
app/Models/CaptchaToken.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
final class CaptchaToken extends Model
|
||||||
|
{
|
||||||
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'captcha_tokens';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that should be hidden for serialization.
|
||||||
|
*
|
||||||
|
* @var array<int, string>
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'public_token',
|
||||||
|
'private_token',
|
||||||
|
];
|
||||||
|
}
|
47
app/Policies/CaptchaTokenPolicy.php
Normal file
47
app/Policies/CaptchaTokenPolicy.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class CaptchaTokenPolicy extends Policy
|
||||||
|
{
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.view') || $user->hasPermission('captcha_token.view_own');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnyAll(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.view');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewAnyOwn(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.view_own');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view(User $user, CaptchaToken $captchaToken): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.view') ||
|
||||||
|
$user->hasPermission('captcha_token.view_own') && $captchaToken->user_id === $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.create');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(User $user, CaptchaToken $captchaToken): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.update') ||
|
||||||
|
$user->hasPermission('captcha_token.update_own') && $captchaToken->user_id === $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(User $user, CaptchaToken $captchaToken): bool
|
||||||
|
{
|
||||||
|
return $user->hasPermission('captcha_token.delete') ||
|
||||||
|
$user->hasPermission('captcha_token.delete_own') && $captchaToken->user_id === $user->id;
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,9 @@ use App\Captcha\Images\Head;
|
|||||||
use App\Captcha\Images\ImageManager;
|
use App\Captcha\Images\ImageManager;
|
||||||
use App\Captcha\Images\Lines;
|
use App\Captcha\Images\Lines;
|
||||||
use App\Services\Api\V1\CaptchaService;
|
use App\Services\Api\V1\CaptchaService;
|
||||||
|
use App\Services\CaptchaToken\CaptchaTokenHandler;
|
||||||
|
use App\Services\GenerateTokenCommand\GenerateTokenUlidCommand;
|
||||||
|
use App\Services\GenerateTokenCommand\GenerateTokenUuidCommand;
|
||||||
use App\Services\Search\CreateSearchInstanceCommand;
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
use App\Services\Search\Search;
|
use App\Services\Search\Search;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
@ -43,6 +46,13 @@ final class AppServiceProvider extends ServiceProvider
|
|||||||
imageBody: $app->make(ImageBody::class)
|
imageBody: $app->make(ImageBody::class)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$this->app->bind(CaptchaTokenHandler::class, function (Application $app) {
|
||||||
|
return new CaptchaTokenHandler(
|
||||||
|
generatePublicTokenCommand: $app->make(GenerateTokenUlidCommand::class),
|
||||||
|
generatePrivateTokenCommand: $app->make(GenerateTokenUuidCommand::class)
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
// use Illuminate\Support\Facades\Gate;
|
// use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
final class AuthServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The model to policy mappings for the application.
|
* The model to policy mappings for the application.
|
||||||
@ -13,7 +13,9 @@ class AuthServiceProvider extends ServiceProvider
|
|||||||
* @var array<class-string, class-string>
|
* @var array<class-string, class-string>
|
||||||
*/
|
*/
|
||||||
protected $policies = [
|
protected $policies = [
|
||||||
\App\Models\Role::class => \App\Policies\RolePolicy::class,
|
\App\Models\Role::class => \App\Policies\RolePolicy::class,
|
||||||
|
\App\Models\User::class => \App\Policies\UserPolicy::class,
|
||||||
|
\App\Models\CaptchaToken::class => \App\Policies\CaptchaTokenPolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
39
app/Repositories/CaptchaTokenRepository.php
Normal file
39
app/Repositories/CaptchaTokenRepository.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Repositories;
|
||||||
|
|
||||||
|
use App\Contracts\Search;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Services\CaptchaToken\BuilderCommand;
|
||||||
|
use App\Services\Search\CreateSearchInstanceCommand;
|
||||||
|
use App\Dto\Builder\CaptchaToken as CaptchaTokenDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
final readonly class CaptchaTokenRepository
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private CreateSearchInstanceCommand $createSearchInstanceCommand,
|
||||||
|
private BuilderCommand $builderCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function getCaptchaTokenById(int $id): ?CaptchaToken
|
||||||
|
{
|
||||||
|
return CaptchaToken::query()->where('id', $id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCaptchaTokens(CaptchaTokenDto $captchaTokenDto, array $with = [], ?int $userId = null): Search
|
||||||
|
{
|
||||||
|
$query = CaptchaToken::query()
|
||||||
|
->when($userId, function (Builder $query, int $userId) {
|
||||||
|
$query->where('user_id', $userId);
|
||||||
|
})
|
||||||
|
->with($with);
|
||||||
|
|
||||||
|
$query = $this->builderCommand->execute(
|
||||||
|
query: $query,
|
||||||
|
captchaTokenDto: $captchaTokenDto
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->createSearchInstanceCommand->execute($query);
|
||||||
|
}
|
||||||
|
}
|
15
app/Services/CaptchaToken/BuilderCommand.php
Normal file
15
app/Services/CaptchaToken/BuilderCommand.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\CaptchaToken;
|
||||||
|
|
||||||
|
use App\Dto\Builder\CaptchaToken as CaptchaTokenDto;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
|
|
||||||
|
final readonly class BuilderCommand
|
||||||
|
{
|
||||||
|
public function execute(Relation | Builder $query, CaptchaTokenDto $captchaTokenDto): Relation | Builder
|
||||||
|
{
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
39
app/Services/CaptchaToken/CaptchaTokenHandler.php
Normal file
39
app/Services/CaptchaToken/CaptchaTokenHandler.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\CaptchaToken;
|
||||||
|
|
||||||
|
use App\Contracts\GenerateTokenCommand;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Models\User;
|
||||||
|
|
||||||
|
final readonly class CaptchaTokenHandler
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private GenerateTokenCommand $generatePublicTokenCommand,
|
||||||
|
private GenerateTokenCommand $generatePrivateTokenCommand
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function handleStore(array $data, User $user): CaptchaToken
|
||||||
|
{
|
||||||
|
$captchaToken = new CaptchaToken();
|
||||||
|
$captchaToken->public_token = $this->generatePublicTokenCommand->unique(CaptchaToken::query(), 'public_token');
|
||||||
|
$captchaToken->private_token = $this->generatePrivateTokenCommand->unique(CaptchaToken::query(), 'private_token');
|
||||||
|
$captchaToken->user_id = $user->id;
|
||||||
|
$captchaToken->fill($data)->save();
|
||||||
|
|
||||||
|
return $captchaToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleUpdate(CaptchaToken $captchaToken, array $data): CaptchaToken
|
||||||
|
{
|
||||||
|
$captchaToken->update($data);
|
||||||
|
$captchaToken->touch();
|
||||||
|
|
||||||
|
return $captchaToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleDestroy(CaptchaToken $captchaToken): void
|
||||||
|
{
|
||||||
|
$captchaToken->delete();
|
||||||
|
}
|
||||||
|
}
|
34
app/Services/GenerateTokenCommand/GenerateTokenCommand.php
Normal file
34
app/Services/GenerateTokenCommand/GenerateTokenCommand.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\GenerateTokenCommand;
|
||||||
|
|
||||||
|
use App\Contracts\GenerateTokenCommand as GenerateTokenCommandContract;
|
||||||
|
use App\Exceptions\Service\GenerateTokenCommandException;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
|
abstract readonly class GenerateTokenCommand implements GenerateTokenCommandContract
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
abstract public function execute(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Builder $builder
|
||||||
|
* @param string $field
|
||||||
|
* @param int $numberAttempts
|
||||||
|
* @return string
|
||||||
|
* @throws GenerateTokenCommandException
|
||||||
|
*/
|
||||||
|
public function unique(Builder $builder, string $field, int $numberAttempts = 10): string
|
||||||
|
{
|
||||||
|
for ($attempt = 0; $attempt < $numberAttempts; ++$attempt) {
|
||||||
|
$token = $this->execute();
|
||||||
|
if ($builder->where($field, '=', $token)->doesntExist()) {
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new GenerateTokenCommandException(__('Failed to generate token.'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\GenerateTokenCommand;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class GenerateTokenUlidCommand extends GenerateTokenCommand
|
||||||
|
{
|
||||||
|
public function execute(): string
|
||||||
|
{
|
||||||
|
return (string) Str::ulid();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\GenerateTokenCommand;
|
||||||
|
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
final readonly class GenerateTokenUuidCommand extends GenerateTokenCommand
|
||||||
|
{
|
||||||
|
public function execute(): string
|
||||||
|
{
|
||||||
|
return (string) Str::orderedUuid();
|
||||||
|
}
|
||||||
|
}
|
154
app/Services/Private/CaptchaTokenService.php
Normal file
154
app/Services/Private/CaptchaTokenService.php
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Services\Private;
|
||||||
|
|
||||||
|
use App\Dto\Builder\CaptchaToken as CaptchaTokenDto;
|
||||||
|
use App\Dto\QuerySettingsDto;
|
||||||
|
use App\Dto\Request\Private\CaptchaToken\StoreUpdate;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\CaptchaToken;
|
||||||
|
use App\Repositories\CaptchaTokenRepository;
|
||||||
|
use App\ServiceResults\ServiceResultArray;
|
||||||
|
use App\ServiceResults\ServiceResultError;
|
||||||
|
use App\ServiceResults\ServiceResultSuccess;
|
||||||
|
use App\ServiceResults\StoreUpdateResult;
|
||||||
|
use App\Services\CaptchaToken\CaptchaTokenHandler;
|
||||||
|
use App\Services\Service;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
final class CaptchaTokenService extends Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly CaptchaTokenRepository $captchaTokenRepository,
|
||||||
|
private readonly CaptchaTokenHandler $captchaTokenHandler,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public function index(CaptchaTokenDto $captchaTokenDto, QuerySettingsDto $querySettingsDto, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('viewAny', CaptchaToken::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = null;
|
||||||
|
if ($user->cannot('viewAnyAll', CaptchaToken::class)) {
|
||||||
|
$userId = $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
$captchaTokens = $this->captchaTokenRepository->getCaptchaTokens(
|
||||||
|
$captchaTokenDto,
|
||||||
|
$querySettingsDto->getQueryWith(),
|
||||||
|
$userId
|
||||||
|
)->pagination(
|
||||||
|
$querySettingsDto->getLimit(),
|
||||||
|
$querySettingsDto->getPage()
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'captchaTokens' => $captchaTokens,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', CaptchaToken::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'captchaToken' => new CaptchaToken(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit(int $id, User $user): ServiceResultError | ServiceResultArray
|
||||||
|
{
|
||||||
|
$modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
|
||||||
|
|
||||||
|
if (is_null($modelCaptchaToken)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('view', $modelCaptchaToken)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->result([
|
||||||
|
'captchaToken' => $modelCaptchaToken,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
if ($user->cannot('create', CaptchaToken::class)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$modelCaptchaToken = DB::transaction(function () use ($data, $user) {
|
||||||
|
$dataCaptchaToken = $this->getDataCaptchaToken($data);
|
||||||
|
return $this->captchaTokenHandler->handleStore($dataCaptchaToken, $user);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($modelCaptchaToken, __('Captcha token created successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, StoreUpdate $data, User $user): ServiceResultError | StoreUpdateResult
|
||||||
|
{
|
||||||
|
$modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
|
||||||
|
|
||||||
|
if (is_null($modelCaptchaToken)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('update', $modelCaptchaToken)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$modelCaptchaToken = DB::transaction(function () use ($data, $modelCaptchaToken) {
|
||||||
|
$dataCaptchaToken = $this->getDataCaptchaToken($data);
|
||||||
|
|
||||||
|
return $this->captchaTokenHandler->handleUpdate($modelCaptchaToken, $dataCaptchaToken);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->resultStoreUpdateModel($modelCaptchaToken, __('Captcha token updated successfully'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(int $id, User $user): ServiceResultError | ServiceResultSuccess
|
||||||
|
{
|
||||||
|
$modelCaptchaToken = $this->captchaTokenRepository->getCaptchaTokenById($id);
|
||||||
|
|
||||||
|
if (is_null($modelCaptchaToken)) {
|
||||||
|
return $this->errNotFound(__('Not Found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($user->cannot('delete', $modelCaptchaToken)) {
|
||||||
|
return $this->errFobidden(__('Access is denied'));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
DB::transaction(function () use ($modelCaptchaToken) {
|
||||||
|
$this->captchaTokenHandler->handleDestroy($modelCaptchaToken);
|
||||||
|
});
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
report($e);
|
||||||
|
return $this->errService(__('Server Error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ok(__('The captcha token has been removed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getDataCaptchaToken(StoreUpdate $data): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $data->getTitle(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('captcha_tokens', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title');
|
||||||
|
$table->ulid('public_token')->unique();
|
||||||
|
$table->uuid('private_token')->unique();
|
||||||
|
$table->unsignedBigInteger('user_id')->index();
|
||||||
|
$table->foreign('user_id')->references('id')->on('users');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('captcha_tokens');
|
||||||
|
}
|
||||||
|
};
|
@ -58,5 +58,9 @@
|
|||||||
"no": "no",
|
"no": "no",
|
||||||
"The user was successfully created": "The user was successfully created",
|
"The user was successfully created": "The user was successfully created",
|
||||||
"The user was successfully updated": "The user was successfully updated",
|
"The user was successfully updated": "The user was successfully updated",
|
||||||
"The user has been deleted": "The user has been deleted"
|
"The user has been deleted": "The user has been deleted",
|
||||||
|
"Captcha token created successfully": "Captcha token created successfully",
|
||||||
|
"Captcha token updated successfully": "Captcha token updated successfully",
|
||||||
|
"The captcha token has been removed": "The captcha token has been removed",
|
||||||
|
"Failed to generate token.": "Failed to generate token."
|
||||||
}
|
}
|
||||||
|
@ -7,4 +7,11 @@ return [
|
|||||||
'Allowed to create' => 'Allowed to create',
|
'Allowed to create' => 'Allowed to create',
|
||||||
'Allowed to edit' => 'Allowed to edit',
|
'Allowed to edit' => 'Allowed to edit',
|
||||||
'Allowed to delete' => 'Allowed to delete',
|
'Allowed to delete' => 'Allowed to delete',
|
||||||
|
'Allowed to watch all tokens' => 'Allowed to watch all tokens',
|
||||||
|
'Allowed to view own tokens' => 'Allowed to view own tokens',
|
||||||
|
'Allowed to create tokens' => 'Allowed to create tokens',
|
||||||
|
'Allowed to edit all tokens' => 'Allowed to edit all tokens',
|
||||||
|
'Allowed to edit own tokens' => 'Allowed to edit own tokens',
|
||||||
|
'Allowed to delete own tokens' => 'Allowed to delete own tokens',
|
||||||
|
'Allowed to delete all tokens' => 'Allowed to delete all tokens',
|
||||||
];
|
];
|
||||||
|
@ -4,4 +4,5 @@ return [
|
|||||||
'Dashboard' => 'Dashboard',
|
'Dashboard' => 'Dashboard',
|
||||||
'User group' => 'User group',
|
'User group' => 'User group',
|
||||||
'Users' => 'Users',
|
'Users' => 'Users',
|
||||||
|
'Captcha tokens' => 'Captcha tokens',
|
||||||
];
|
];
|
||||||
|
@ -220,5 +220,7 @@ return [
|
|||||||
'permissions' => 'permissions',
|
'permissions' => 'permissions',
|
||||||
'is_active' => 'is active',
|
'is_active' => 'is active',
|
||||||
'roles' => 'user group',
|
'roles' => 'user group',
|
||||||
|
'public_token' => 'public token',
|
||||||
|
'private_token' => 'private token',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
@ -58,5 +58,9 @@
|
|||||||
"no": "нет",
|
"no": "нет",
|
||||||
"The user was successfully created": "Пользователь был успешно создан",
|
"The user was successfully created": "Пользователь был успешно создан",
|
||||||
"The user was successfully updated": "Пользователь был успешно обновлен",
|
"The user was successfully updated": "Пользователь был успешно обновлен",
|
||||||
"The user has been deleted": "Пользователь был удален"
|
"The user has been deleted": "Пользователь был удален",
|
||||||
|
"Captcha token created successfully": "Токен капчи успешно создан",
|
||||||
|
"Captcha token updated successfully": "Токен капчи успешно обновлен",
|
||||||
|
"The captcha token has been removed": "Токен капчи был удален",
|
||||||
|
"Failed to generate token.": "Не удалось сгенерировать токен."
|
||||||
}
|
}
|
||||||
|
@ -7,4 +7,11 @@ return [
|
|||||||
'Allowed to create' => 'Разрешено создать',
|
'Allowed to create' => 'Разрешено создать',
|
||||||
'Allowed to edit' => 'Разрешено редактировать',
|
'Allowed to edit' => 'Разрешено редактировать',
|
||||||
'Allowed to delete' => 'Разрешено удалять',
|
'Allowed to delete' => 'Разрешено удалять',
|
||||||
|
'Allowed to watch all tokens' => 'Разрешено смотреть все токены',
|
||||||
|
'Allowed to view own tokens' => 'Разрешено просматривать собственные токены',
|
||||||
|
'Allowed to create tokens' => 'Разрешено создавать токены',
|
||||||
|
'Allowed to edit all tokens' => 'Разрешено редактировать все токены',
|
||||||
|
'Allowed to edit own tokens' => 'Разрешено редактировать собственные токены',
|
||||||
|
'Allowed to delete all tokens' => 'Разрешено удалять все токены',
|
||||||
|
'Allowed to delete own tokens' => 'Разрешено удалять собственные токены',
|
||||||
];
|
];
|
||||||
|
@ -4,5 +4,6 @@ return [
|
|||||||
'Dashboard' => 'Dashboard',
|
'Dashboard' => 'Dashboard',
|
||||||
'User group' => 'Группа пользователей',
|
'User group' => 'Группа пользователей',
|
||||||
'Users' => 'Пользователи',
|
'Users' => 'Пользователи',
|
||||||
|
'Captcha tokens' => 'Токены от капчи',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -220,5 +220,7 @@ return [
|
|||||||
'permissions' => 'разрешения',
|
'permissions' => 'разрешения',
|
||||||
'is_active' => 'активен',
|
'is_active' => 'активен',
|
||||||
'roles' => 'группа пользователей',
|
'roles' => 'группа пользователей',
|
||||||
|
'public_token' => 'публичный токен',
|
||||||
|
'private_token' => 'приватный токен',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
5
resources/views/private/captcha_tokens/_from.blade.php
Normal file
5
resources/views/private/captcha_tokens/_from.blade.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@csrf
|
||||||
|
<x-private.forms.input :title="__('validation.attributes.title')" name="title" type="text" :value="$captchaToken->title" required autofocus />
|
||||||
|
@canany(['create', 'update'], $captchaToken)
|
||||||
|
<button class="btn btn-primary" type="submit">{{ __('Save') }}</button>
|
||||||
|
@endcanany
|
8
resources/views/private/captcha_tokens/_top.blade.php
Normal file
8
resources/views/private/captcha_tokens/_top.blade.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
@can('create', \App\Models\CaptchaToken::class)
|
||||||
|
<div class="mb-4">
|
||||||
|
<a href="{{ route('captcha-tokens.create') }}" class="btn btn-secondary d-inline-flex align-items-center me-2">
|
||||||
|
<svg class="icon icon-xs me-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path></svg>
|
||||||
|
{{ __('Create') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endcan
|
16
resources/views/private/captcha_tokens/create.blade.php
Normal file
16
resources/views/private/captcha_tokens/create.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
@section('meta_title', __('sections.Captcha tokens'))
|
||||||
|
@section('h1', __('sections.Captcha tokens'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.captcha_tokens._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="{{ route('captcha-tokens.store') }}">
|
||||||
|
@include('private.captcha_tokens._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-private.layout>
|
19
resources/views/private/captcha_tokens/edit.blade.php
Normal file
19
resources/views/private/captcha_tokens/edit.blade.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
@section('meta_title', __('sections.Captcha tokens'))
|
||||||
|
@section('h1', __('sections.Captcha tokens'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.captcha_tokens._top')
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-4">
|
||||||
|
<div class="card border-0 shadow components-section">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="{{ route('captcha-tokens.update', $captchaToken) }}">
|
||||||
|
@method('PUT')
|
||||||
|
<x-private.forms.input :title="__('validation.attributes.public_token')" name="public_token" type="text" :value="$captchaToken->public_token" disabled />
|
||||||
|
<x-private.forms.input :title="__('validation.attributes.private_token')" name="private_token" type="text" :value="$captchaToken->private_token" disabled />
|
||||||
|
@include('private.captcha_tokens._from')
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-private.layout>
|
50
resources/views/private/captcha_tokens/index.blade.php
Normal file
50
resources/views/private/captcha_tokens/index.blade.php
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
@section('meta_title', __('sections.Captcha tokens'))
|
||||||
|
@section('h1', __('sections.Captcha tokens'))
|
||||||
|
<x-private.layout>
|
||||||
|
@include('private.captcha_tokens._top')
|
||||||
|
<div class="card border-0 shadow mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-centered table-nowrap mb-0 rounded">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th class="border-0">{{ __('validation.attributes.title') }}</th>
|
||||||
|
<th class="border-0 rounded-end" style="width: 150px"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($captchaTokens as $captchaToken)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('captcha-tokens.edit', $captchaToken) }}" class="fw-bold">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="align-text-top" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||||
|
</svg>
|
||||||
|
{{ $captchaToken->title }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@can('delete', $captchaToken)
|
||||||
|
<form method="post" action="{{ route('captcha-tokens.destroy', $captchaToken) }}">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="btn btn-danger click-confirm">
|
||||||
|
{{ __('Delete') }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="card-footer border-0">
|
||||||
|
{{ $captchaTokens->links() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@push('scripts')
|
||||||
|
@include('private._scripts._click-confirm', ['alert' => __('Do you want to delete?')])
|
||||||
|
@endpush
|
||||||
|
</x-private.layout>
|
5
resources/views/private/dashboard/index.blade.php
Normal file
5
resources/views/private/dashboard/index.blade.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@section('meta_title', __('sections.Dashboard'))
|
||||||
|
@section('h1', __('sections.Dashboard'))
|
||||||
|
<x-private.layout>
|
||||||
|
|
||||||
|
</x-private.layout>
|
@ -19,6 +19,20 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@can('viewAny', \App\Models\CaptchaToken::class)
|
||||||
|
<li @class([
|
||||||
|
'nav-item',
|
||||||
|
'active' => request()->route()->named('captcha-tokens.*'),
|
||||||
|
])>
|
||||||
|
<a href="{{ route('captcha-tokens.index') }}" class="nav-link">
|
||||||
|
<span class="sidebar-icon">
|
||||||
|
<svg class="icon icon-xs me-2" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8.06 6.5a.5.5 0 0 1 .5.5v.776a11.5 11.5 0 0 1-.552 3.519l-1.331 4.14a.5.5 0 0 1-.952-.305l1.33-4.141a10.5 10.5 0 0 0 .504-3.213V7a.5.5 0 0 1 .5-.5Z"/><path d="M6.06 7a2 2 0 1 1 4 0 .5.5 0 1 1-1 0 1 1 0 1 0-2 0v.332c0 .409-.022.816-.066 1.221A.5.5 0 0 1 6 8.447c.04-.37.06-.742.06-1.115V7Zm3.509 1a.5.5 0 0 1 .487.513 11.5 11.5 0 0 1-.587 3.339l-1.266 3.8a.5.5 0 0 1-.949-.317l1.267-3.8a10.5 10.5 0 0 0 .535-3.048A.5.5 0 0 1 9.569 8Zm-3.356 2.115a.5.5 0 0 1 .33.626L5.24 14.939a.5.5 0 1 1-.955-.296l1.303-4.199a.5.5 0 0 1 .625-.329Z"/><path d="M4.759 5.833A3.501 3.501 0 0 1 11.559 7a.5.5 0 0 1-1 0 2.5 2.5 0 0 0-4.857-.833.5.5 0 1 1-.943-.334Zm.3 1.67a.5.5 0 0 1 .449.546 10.72 10.72 0 0 1-.4 2.031l-1.222 4.072a.5.5 0 1 1-.958-.287L4.15 9.793a9.72 9.72 0 0 0 .363-1.842.5.5 0 0 1 .546-.449Zm6 .647a.5.5 0 0 1 .5.5c0 1.28-.213 2.552-.632 3.762l-1.09 3.145a.5.5 0 0 1-.944-.327l1.089-3.145c.382-1.105.578-2.266.578-3.435a.5.5 0 0 1 .5-.5Z"/><path d="M3.902 4.222a4.996 4.996 0 0 1 5.202-2.113.5.5 0 0 1-.208.979 3.996 3.996 0 0 0-4.163 1.69.5.5 0 0 1-.831-.556Zm6.72-.955a.5.5 0 0 1 .705-.052A4.99 4.99 0 0 1 13.059 7v1.5a.5.5 0 1 1-1 0V7a3.99 3.99 0 0 0-1.386-3.028.5.5 0 0 1-.051-.705ZM3.68 5.842a.5.5 0 0 1 .422.568c-.029.192-.044.39-.044.59 0 .71-.1 1.417-.298 2.1l-1.14 3.923a.5.5 0 1 1-.96-.279L2.8 8.821A6.531 6.531 0 0 0 3.058 7c0-.25.019-.496.054-.736a.5.5 0 0 1 .568-.422Zm8.882 3.66a.5.5 0 0 1 .456.54c-.084 1-.298 1.986-.64 2.934l-.744 2.068a.5.5 0 0 1-.941-.338l.745-2.07a10.51 10.51 0 0 0 .584-2.678.5.5 0 0 1 .54-.456Z"/><path d="M4.81 1.37A6.5 6.5 0 0 1 14.56 7a.5.5 0 1 1-1 0 5.5 5.5 0 0 0-8.25-4.765.5.5 0 0 1-.5-.865Zm-.89 1.257a.5.5 0 0 1 .04.706A5.478 5.478 0 0 0 2.56 7a.5.5 0 0 1-1 0c0-1.664.626-3.184 1.655-4.333a.5.5 0 0 1 .706-.04ZM1.915 8.02a.5.5 0 0 1 .346.616l-.779 2.767a.5.5 0 1 1-.962-.27l.778-2.767a.5.5 0 0 1 .617-.346Zm12.15.481a.5.5 0 0 1 .49.51c-.03 1.499-.161 3.025-.727 4.533l-.07.187a.5.5 0 0 1-.936-.351l.07-.187c.506-1.35.634-2.74.663-4.202a.5.5 0 0 1 .51-.49Z"/></svg>
|
||||||
|
</span>
|
||||||
|
<span class="sidebar-text">{{ __('sections.Captcha tokens') }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
@endcan
|
||||||
|
|
||||||
@can('viewAny', \App\Models\User::class)
|
@can('viewAny', \App\Models\User::class)
|
||||||
<li @class([
|
<li @class([
|
||||||
'nav-item',
|
'nav-item',
|
||||||
|
@ -33,4 +33,5 @@ Route::middleware(['auth', 'verified', 'user.locale'])->group(function () {
|
|||||||
Route::put('users/{id}/password', [\App\Http\Controllers\Private\UsersController::class, 'updatePassword'])->name('users.update-password')->where(['id' => '[0-9]+']);
|
Route::put('users/{id}/password', [\App\Http\Controllers\Private\UsersController::class, 'updatePassword'])->name('users.update-password')->where(['id' => '[0-9]+']);
|
||||||
|
|
||||||
Route::resource('roles', \App\Http\Controllers\Private\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
|
Route::resource('roles', \App\Http\Controllers\Private\RolesController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['role' => '[0-9]+']);
|
||||||
|
Route::resource('captcha-tokens', \App\Http\Controllers\Private\CaptchaTokensController::class)->only(['index', 'create', 'store', 'edit', 'update', 'destroy'])->where(['captcha_token' => '[0-9]+']);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user