mirror of https://github.com/kcal-app/kcal.git
Add basic User CRUD abilities
This commit is contained in:
parent
3bccde1a35
commit
a9fad1bff0
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Requests\UpdateUserRequest;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class UserController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index(): View
|
||||||
|
{
|
||||||
|
return view('users.index')->with('users', User::all());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
public function create(): View
|
||||||
|
{
|
||||||
|
return $this->edit(new User());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(UpdateUserRequest $request): RedirectResponse
|
||||||
|
{
|
||||||
|
return $this->update($request, new User());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(User $user): View
|
||||||
|
{
|
||||||
|
return view('users.edit')->with('user', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(UpdateUserRequest $request, User $user): RedirectResponse
|
||||||
|
{
|
||||||
|
$attributes = $request->validated();
|
||||||
|
$attributes['remember_token'] = Str::random(10);
|
||||||
|
$attributes['password'] = Hash::make($attributes['password']);
|
||||||
|
$user->fill($attributes)->save();
|
||||||
|
session()->flash('message', "User {$user->name} updated!");
|
||||||
|
return redirect()->route('users.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm removal of specified resource.
|
||||||
|
*/
|
||||||
|
public function delete(User $user): View
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $user);
|
||||||
|
return view('users.delete')->with('user', $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(User $user): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $user);
|
||||||
|
$user->delete();
|
||||||
|
return redirect(route('users.index'))
|
||||||
|
->with('message', "User {$user->name} deleted!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class UpdateUserRequest extends FormRequest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'username' => ['required', 'string', Rule::unique('users')->ignore($this->user)],
|
||||||
|
'name' => ['nullable', 'string'],
|
||||||
|
'password' => ['nullable', 'string', 'confirmed'],
|
||||||
|
'password_confirmation' => ['nullable', 'string'],
|
||||||
|
];
|
||||||
|
if (!$this->user) {
|
||||||
|
$rules['password'] = ['required', 'string', 'confirmed'];
|
||||||
|
$rules['password_confirmation'] = ['required', 'string'];
|
||||||
|
}
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Models\Traits\Sluggable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
|
@ -41,7 +42,7 @@ use Illuminate\Support\Facades\Auth;
|
||||||
*/
|
*/
|
||||||
final class User extends Authenticatable
|
final class User extends Authenticatable
|
||||||
{
|
{
|
||||||
use HasFactory, Notifiable;
|
use HasFactory, Notifiable, Sluggable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||||
|
|
||||||
|
class UserPolicy
|
||||||
|
{
|
||||||
|
use HandlesAuthorization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, User $model): bool
|
||||||
|
{
|
||||||
|
return $user->id !== $model->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Policies\UserPolicy;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
class AuthServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
|
@ -13,7 +14,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $policies = [
|
protected $policies = [
|
||||||
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
User::class => UserPolicy::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -24,7 +25,5 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
$this->registerPolicies();
|
$this->registerPolicies();
|
||||||
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,6 @@ return [
|
||||||
* Only set this to true if you understand the possible consequences.
|
* Only set this to true if you understand the possible consequences.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'onUpdate' => false,
|
'onUpdate' => true,
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class CreateUsersTable extends Migration
|
||||||
{
|
{
|
||||||
Schema::create('users', function (Blueprint $table) {
|
Schema::create('users', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
|
$table->string('slug')->unique();
|
||||||
$table->string('username')->unique();
|
$table->string('username')->unique();
|
||||||
$table->string('password');
|
$table->string('password');
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Settings Dropdown -->
|
<!-- User Menu -->
|
||||||
<div class="flex items-center sm:ml-6">
|
<div class="flex items-center sm:ml-6">
|
||||||
<x-dropdown align="right" width="48">
|
<x-dropdown align="right" width="48">
|
||||||
<x-slot name="trigger">
|
<x-slot name="trigger">
|
||||||
|
|
@ -32,17 +32,9 @@
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
|
||||||
<x-slot name="content">
|
<x-slot name="content">
|
||||||
<div class="ml-3">
|
<div class="space-y-2">
|
||||||
<div class="font-medium text-base text-gray-800">{{ Auth::user()->name }}</div>
|
|
||||||
<div class="font-medium text-sm text-gray-500">{{ Auth::user()->username }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
|
<x-dropdown-link :href="route('goals.index')">Goals</x-dropdown-link>
|
||||||
</div>
|
<x-dropdown-link :href="route('users.index')">Users</x-dropdown-link>
|
||||||
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<!-- Authentication -->
|
|
||||||
<form method="POST" action="{{ route('logout') }}" x-data>
|
<form method="POST" action="{{ route('logout') }}" x-data>
|
||||||
@csrf
|
@csrf
|
||||||
<x-dropdown-link :href="route('logout')" @click.prevent="$el.closest('form').submit();">Logout</x-dropdown-link>
|
<x-dropdown-link :href="route('logout')" @click.prevent="$el.closest('form').submit();">Logout</x-dropdown-link>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="title">Delete {{ $user->name }}</x-slot>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h1 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||||
|
Delete {{ $user->name }}?
|
||||||
|
</h1>
|
||||||
|
</x-slot>
|
||||||
|
<form method="POST" action="{{ route('users.destroy', $user) }}">
|
||||||
|
@method('delete')
|
||||||
|
@csrf
|
||||||
|
<div class="pb-3">
|
||||||
|
<div class="text-lg">Are you sure what to delete <span class="font-extrabold">{{ $user->name }}</span>?</div>
|
||||||
|
</div>
|
||||||
|
<x-inputs.button class="bg-red-800 hover:bg-red-700">
|
||||||
|
Yes, delete
|
||||||
|
</x-inputs.button>
|
||||||
|
<a class="ml-3 text-gray-500 hover:text-gray-700" href="{{ route('users.index') }}">
|
||||||
|
No, do not delete</a>
|
||||||
|
</form>
|
||||||
|
</x-app-layout>
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<x-app-layout>
|
||||||
|
@php($title = ($user->exists ? "Edit {$user->name}" : 'Add User'))
|
||||||
|
<x-slot name="title">{{ $title }}</x-slot>
|
||||||
|
<x-slot name="header">
|
||||||
|
<h1 class="font-semibold text-xl text-gray-800 leading-tight">{{ $title }}</h1>
|
||||||
|
</x-slot>
|
||||||
|
<form method="POST" action="{{ ($user->exists ? route('users.update', $user) : route('users.store')) }}">
|
||||||
|
@if ($user->exists)@method('put')@endif
|
||||||
|
@csrf
|
||||||
|
<div class="flex flex-col space-y-4">
|
||||||
|
<div class="flex flex-col space-y-4 md:flex-row md:space-x-4 md:space-y-0">
|
||||||
|
<!-- Username -->
|
||||||
|
<div class="flex-auto">
|
||||||
|
<x-inputs.label for="username" value="Username"/>
|
||||||
|
|
||||||
|
<x-inputs.input name="username"
|
||||||
|
type="text"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
autocapitalize="none"
|
||||||
|
:value="old('username', $user->username)"
|
||||||
|
:hasError="$errors->has('username')"
|
||||||
|
required />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="flex-auto">
|
||||||
|
<x-inputs.label for="name" value="Name"/>
|
||||||
|
|
||||||
|
<x-inputs.input name="name"
|
||||||
|
type="text"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
:value="old('name', $user->name)"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<div>
|
||||||
|
<x-inputs.label for="password" value="Password"/>
|
||||||
|
|
||||||
|
<x-inputs.input name="password"
|
||||||
|
type="password"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
:hasError="$errors->has('password')"
|
||||||
|
:required="!$user->exists"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Password confirm -->
|
||||||
|
<div>
|
||||||
|
<x-inputs.label for="password_confirmation" value="Confirm Password"/>
|
||||||
|
|
||||||
|
<x-inputs.input name="password_confirmation"
|
||||||
|
type="password"
|
||||||
|
class="block mt-1 w-full"
|
||||||
|
:hasError="$errors->has('password')"
|
||||||
|
:required="!$user->exists"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end mt-4">
|
||||||
|
<x-inputs.button>{{ ($user->exists ? 'Save' : 'Add') }}</x-inputs.button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</x-app-layout>
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<x-app-layout>
|
||||||
|
<x-slot name="title">Users</x-slot>
|
||||||
|
<x-slot name="header">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="font-semibold text-2xl text-gray-800 leading-tight">Users</h1>
|
||||||
|
<x-button-link.green href="{{ route('users.create') }}">
|
||||||
|
Add User
|
||||||
|
</x-button-link.green>
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
<table class="min-w-max w-full table-auto">
|
||||||
|
<thead>
|
||||||
|
<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
|
||||||
|
<th class="py-3 px-6 text-left">Username</th>
|
||||||
|
<th class="py-3 px-6 text-left">Name</th>
|
||||||
|
<th class="py-3 px-6 text-left">Created</th>
|
||||||
|
<th class="py-3 px-6 text-left">Updated</th>
|
||||||
|
<th class="py-3 px-6 text-left"> </th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($users as $user)
|
||||||
|
<tr class="border-b border-gray-200">
|
||||||
|
<td class="py-3 px-6">{{ $user->username }}</td>
|
||||||
|
<td class="py-3 px-6">{{ $user->name }}</td>
|
||||||
|
<td class="py-3 px-6">{{ $user->created_at }}</td>
|
||||||
|
<td class="py-3 px-6">{{ $user->updated_at }}</td>
|
||||||
|
<td class="py-3 px-6">
|
||||||
|
<div class="flex space-x-2 justify-end">
|
||||||
|
<x-button-link.gray href="{{ route('users.edit', $user) }}">
|
||||||
|
Edit
|
||||||
|
</x-button-link.gray>
|
||||||
|
@can('delete', $user)
|
||||||
|
<x-button-link.red href="{{ route('users.delete', $user) }}">
|
||||||
|
Delete
|
||||||
|
</x-button-link.red>
|
||||||
|
@endcan
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</x-app-layout>
|
||||||
|
|
@ -5,6 +5,7 @@ use App\Http\Controllers\GoalController;
|
||||||
use App\Http\Controllers\IngredientPickerController;
|
use App\Http\Controllers\IngredientPickerController;
|
||||||
use App\Http\Controllers\JournalEntryController;
|
use App\Http\Controllers\JournalEntryController;
|
||||||
use App\Http\Controllers\RecipeController;
|
use App\Http\Controllers\RecipeController;
|
||||||
|
use App\Http\Controllers\UserController;
|
||||||
use App\Providers\RouteServiceProvider;
|
use App\Providers\RouteServiceProvider;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
@ -45,4 +46,8 @@ Route::get('/journal-entries/{journal_entry}/delete', [JournalEntryController::c
|
||||||
Route::resource('recipes', RecipeController::class)->middleware(['auth']);
|
Route::resource('recipes', RecipeController::class)->middleware(['auth']);
|
||||||
Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->middleware(['auth'])->name('recipes.delete');
|
Route::get('/recipes/{recipe}/delete', [RecipeController::class, 'delete'])->middleware(['auth'])->name('recipes.delete');
|
||||||
|
|
||||||
|
// Users.
|
||||||
|
Route::resource('users', UserController::class)->middleware(['auth']);
|
||||||
|
Route::get('/users/{user}/delete', [UserController::class, 'delete'])->middleware(['auth'])->name('users.delete');
|
||||||
|
|
||||||
require __DIR__.'/auth.php';
|
require __DIR__.'/auth.php';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue