Стилизация для страницы логина, мидла для проверки роли, маршрут получения списка пользователей.
This commit is contained in:
parent
a33555abe2
commit
1eaf2fa744
73
src/app/Domain/Shared/Exceptions/AppException.php
Normal file
73
src/app/Domain/Shared/Exceptions/AppException.php
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Shared\Exceptions;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Support\Arrayable;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class AppException extends Exception implements Arrayable
|
||||||
|
{
|
||||||
|
protected string $errorSlug;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $errorSlug,
|
||||||
|
string $message = "",
|
||||||
|
int $code = 0,
|
||||||
|
Throwable|null $previous = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
$this->errorSlug = $errorSlug;
|
||||||
|
parent::__construct($message, $code, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSlug(): string
|
||||||
|
{
|
||||||
|
return $this->errorSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray()
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'timestamp' => Carbon::now(),
|
||||||
|
'error' => $this->getSlug(),
|
||||||
|
'status' => $this->getCode(),
|
||||||
|
'message' => $this->getMessage(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!app()->environment('production') || config('app.debug')) {
|
||||||
|
$data['trace'] = $this->getTrace();
|
||||||
|
|
||||||
|
if ($prev = $this->getPrevious()) {
|
||||||
|
$data['previous'] = [
|
||||||
|
'code' => $prev->getCode(),
|
||||||
|
'message' => $prev->getMessage(),
|
||||||
|
'trace' => $prev->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json(
|
||||||
|
$this->toArray(),
|
||||||
|
$this->getCode() ?: 500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function new(
|
||||||
|
string $slug,
|
||||||
|
string $message,
|
||||||
|
int $code = 500,
|
||||||
|
Throwable|null $previous = null
|
||||||
|
): never {
|
||||||
|
throw new self($slug, $message, $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,5 +6,8 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
{
|
{
|
||||||
//
|
public function index()
|
||||||
|
{
|
||||||
|
return view('user.index', []);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
src/app/Http/Middleware/CheckRoleMiddleware.php
Normal file
45
src/app/Http/Middleware/CheckRoleMiddleware.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Domain\Shared\Exceptions\AppException;
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Contracts\Auth\Guard;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class CheckRoleMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Guard $auth
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param Closure $next
|
||||||
|
* @param string ...$roles
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next, string ...$roles): Response
|
||||||
|
{
|
||||||
|
/** @var User|null $user */
|
||||||
|
$user = $this->auth->user();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
AppException::new('UNAUTHORIZED', 'UNAUTHORIZED', Response::HTTP_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
$userRole = $user->load('role')->role();
|
||||||
|
|
||||||
|
$hasRole = $userRole->whereIn('code', $roles)
|
||||||
|
->count();
|
||||||
|
|
||||||
|
if (!$hasRole) {
|
||||||
|
AppException::new('forbidden', 'Недостаточно прав');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,6 +12,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property string $uuid
|
||||||
|
* @property string $name
|
||||||
|
* @property string $email
|
||||||
|
* @property string $password
|
||||||
|
* @property string $role_uuid
|
||||||
|
* @property Carbon $created_at
|
||||||
|
* @property Carbon $updated_at
|
||||||
|
* @property-read Role $role
|
||||||
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Http\Middleware\CheckRoleMiddleware;
|
||||||
use Illuminate\Foundation\Application;
|
use Illuminate\Foundation\Application;
|
||||||
use Illuminate\Foundation\Configuration\Exceptions;
|
use Illuminate\Foundation\Configuration\Exceptions;
|
||||||
use Illuminate\Foundation\Configuration\Middleware;
|
use Illuminate\Foundation\Configuration\Middleware;
|
||||||
@ -13,7 +14,9 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->alias([
|
||||||
|
'role' => CheckRoleMiddleware::class,
|
||||||
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,47 +1,56 @@
|
|||||||
@extends('layouts.default')
|
@extends('layouts.default')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<form method="POST" action="{{ route('login') }}">
|
<div class="max-w-md w-full space-y-8 bg-white p-8 rounded-lg shadow-md">
|
||||||
@csrf
|
<div>
|
||||||
|
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
|
||||||
<!-- Поле Email -->
|
Вход в систему
|
||||||
<div class="sm:col-span-1">
|
</h2>
|
||||||
<label for="email">Email:</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
value="{{ old('email') }}"
|
|
||||||
placeholder="Enter your email"
|
|
||||||
required
|
|
||||||
autocomplete="username"
|
|
||||||
/>
|
|
||||||
@error('email')
|
|
||||||
<p class="w-full text-red-500">{{ $message }}</p>
|
|
||||||
@enderror
|
|
||||||
</div>
|
</div>
|
||||||
|
<form class="mt-8 space-y-6" method="POST" action="{{ route('login') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
<!-- Поле Пароль -->
|
<!-- Поле Email -->
|
||||||
<div class="sm:col-span-1 mt-4">
|
<div>
|
||||||
<label for="password">Password:</label>
|
<label for="email" class="block text-sm font-medium text-gray-700">Email:</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="email"
|
||||||
id="password"
|
id="email"
|
||||||
name="password"
|
name="email"
|
||||||
placeholder="Enter your password"
|
value="{{ old('email') }}"
|
||||||
required
|
placeholder="Enter your email"
|
||||||
autocomplete="current-password"
|
required
|
||||||
/>
|
autocomplete="username"
|
||||||
@error('password')
|
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
<p class="w-full text-red-500">{{ $message }}</p>
|
/>
|
||||||
@enderror
|
@error('email')
|
||||||
</div>
|
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Кнопка входа -->
|
<!-- Поле Пароль -->
|
||||||
<div class="mt-6">
|
<div class="mt-4">
|
||||||
<button type="submit">
|
<label for="password" class="block text-sm font-medium text-gray-700">Password:</label>
|
||||||
Войти
|
<input
|
||||||
</button>
|
type="password"
|
||||||
</div>
|
id="password"
|
||||||
</form>
|
name="password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
autocomplete="current-password"
|
||||||
|
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
|
||||||
|
/>
|
||||||
|
@error('password')
|
||||||
|
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопка входа -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<button type="submit" class="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@ -1,10 +1,5 @@
|
|||||||
@extends ('layouts.default')
|
@extends ('layouts.app')
|
||||||
|
|
||||||
@section ('content')
|
@section ('content')
|
||||||
<h1>dashboard</h1>
|
<h1>dashboard</h1>
|
||||||
|
|
||||||
<form method="POST" action="{{ route('logout')}}">
|
|
||||||
@csrf
|
|
||||||
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Выйти</button>
|
|
||||||
</form>
|
|
||||||
@endsection
|
@endsection
|
||||||
16
src/resources/views/layouts/app.blade.php
Normal file
16
src/resources/views/layouts/app.blade.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||||
|
<head>
|
||||||
|
@include('layouts.partials.head')
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="min-h-screen xl:flex">
|
||||||
|
<div class="flex-1 transition-all duration-300 ease-in-out">
|
||||||
|
@include('layouts.partials.app-header')
|
||||||
|
<div class="p-4 mx-auto max-w-(--breakpoint-2xl) md:p-6">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -3,6 +3,8 @@
|
|||||||
<head>
|
<head>
|
||||||
@include('layouts.partials.head')
|
@include('layouts.partials.head')
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-gray-50">
|
||||||
@yield('content')
|
<div class="min-h-screen flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
11
src/resources/views/layouts/partials/app-header.blade.php
Normal file
11
src/resources/views/layouts/partials/app-header.blade.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<header class="flex items-center justify-between px-4 py-3">
|
||||||
|
<nav class="flex-1">
|
||||||
|
<ul class="flex justify-center space-x-4">
|
||||||
|
<li><a href="{{ route('user.index') }}">Пользователи</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<form method="POST" action="{{ route('logout')}}">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">Выйти</button>
|
||||||
|
</form>
|
||||||
|
</header>
|
||||||
5
src/resources/views/user/index.blade.php
Normal file
5
src/resources/views/user/index.blade.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@extends ('layouts.app')
|
||||||
|
|
||||||
|
@section ('content')
|
||||||
|
<h1>Users index</h1>
|
||||||
|
@endsection
|
||||||
@ -3,6 +3,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Http\Controllers\DashboardController;
|
use App\Http\Controllers\DashboardController;
|
||||||
|
use App\Http\Controllers\UserController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
@ -10,5 +11,9 @@ Route::get('/', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->group(function () {
|
Route::middleware(['auth', 'verified'])->group(function () {
|
||||||
Route::get('/dashboard', [DashboardController::class, 'index']);
|
Route::get('/dashboard', [DashboardController::class, 'index'])
|
||||||
|
->name('dashboard');
|
||||||
|
Route::get('/users', [UserController::class, 'index'])
|
||||||
|
->name('user.index')
|
||||||
|
->middleware('role:admin');
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user