Стилизация для страницы логина, мидла для проверки роли, маршрут получения списка пользователей.

This commit is contained in:
Toy Rik 2026-02-13 12:17:16 +03:00
parent a33555abe2
commit 1eaf2fa744
12 changed files with 229 additions and 52 deletions

View 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);
}
}

View File

@ -6,5 +6,8 @@ namespace App\Http\Controllers;
class UserController extends Controller class UserController extends Controller
{ {
// public function index()
{
return view('user.index', []);
}
} }

View 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);
}
}

View File

@ -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> */

View File

@ -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 {
// //

View File

@ -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

View File

@ -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

View 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>

View File

@ -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>

View 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>

View File

@ -0,0 +1,5 @@
@extends ('layouts.app')
@section ('content')
<h1>Users index</h1>
@endsection

View File

@ -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');
}); });