[Feature] User role (#762)

This commit is contained in:
Alex Justesen
2023-09-12 21:54:21 -04:00
committed by GitHub
parent 705e2f2ec8
commit 2e14e7ad47
16 changed files with 343 additions and 69 deletions
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\info;
use function Laravel\Prompts\select;
use function Laravel\Prompts\text;
class UpdateUserRole extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:update-user-role';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Change the role for a given user.';
/**
* Execute the console command.
*/
public function handle()
{
$email = text(
label: 'What is the email address?',
required: true,
validate: fn (string $value) => match (true) {
! User::firstWhere('email', $value) => 'User not found.',
default => null
}
);
$role = select(
label: 'What role should the user have?',
options: [
'admin' => 'Admin',
'guest' => 'Guest',
'user' => 'User',
],
default: 'guest'
);
$confirmed = confirm(
label: 'Are you sure?',
required: true
);
if ($confirmed) {
User::firstWhere('email', $email)
->update([
'role' => $role,
]);
info('User role updated.');
}
}
}
+2 -6
View File
@@ -18,17 +18,13 @@ class Dashboard extends BasePage
protected static string $view = 'filament.pages.dashboard';
protected function getPollingInterval(): ?string
{
return null;
}
protected function getHeaderActions(): array
{
return [
Action::make('speedtest')
->label('Queue Speedtest')
->action('queueSpeedtest'),
->action('queueSpeedtest')
->hidden(fn (): bool => ! auth()->user()->is_admin && ! auth()->user()->is_user),
];
}
+10
View File
@@ -21,6 +21,16 @@ class DeleteData extends Page
protected ?string $maxContentWidth = '3xl';
public function mount(): void
{
abort_unless(auth()->user()->is_admin, 403);
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->is_admin;
}
public function getHeaderActions(): array
{
return [
@@ -25,6 +25,16 @@ class GeneralPage extends SettingsPage
protected static string $settings = GeneralSettings::class;
public function mount(): void
{
abort_unless(auth()->user()->is_admin, 403);
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->is_admin;
}
public function form(Form $form): Form
{
return $form
@@ -21,6 +21,16 @@ class InfluxDbPage extends SettingsPage
protected static string $settings = InfluxDbSettings::class;
public function mount(): void
{
abort_unless(auth()->user()->is_admin, 403);
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->is_admin;
}
public function form(Form $form): Form
{
return $form
@@ -29,6 +29,16 @@ class NotificationPage extends SettingsPage
protected static string $settings = NotificationSettings::class;
public function mount(): void
{
abort_unless(auth()->user()->is_admin, 403);
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->is_admin;
}
public function form(Form $form): Form
{
return $form
@@ -21,6 +21,16 @@ class ThresholdsPage extends SettingsPage
protected static string $settings = ThresholdSettings::class;
public function mount(): void
{
abort_unless(auth()->user()->is_admin, 403);
}
public static function shouldRegisterNavigation(): bool
{
return auth()->user()->is_admin;
}
public function form(Form $form): Form
{
return $form
+2 -2
View File
@@ -26,8 +26,6 @@ class ResultResource extends Resource
protected static ?string $navigationIcon = 'heroicon-o-table-cells';
protected static ?string $navigationLabel = 'Results';
public static function form(Form $form): Form
{
$settings = new GeneralSettings();
@@ -168,6 +166,7 @@ class ResultResource extends Resource
Tables\Actions\ViewAction::make(),
Tables\Actions\Action::make('updateComments')
->icon('heroicon-o-chat-bubble-bottom-center-text')
->hidden(fn (): bool => ! auth()->user()->is_admin && ! auth()->user()->is_user)
->mountUsing(fn (Forms\ComponentContainer $form, Result $record) => $form->fill([
'comments' => $record->comments,
]))
@@ -188,6 +187,7 @@ class ResultResource extends Resource
Tables\Actions\BulkAction::make('export')
->label('Export selected')
->icon('heroicon-o-arrow-down-tray')
->hidden(fn (): bool => ! auth()->user()->is_admin)
->action(function (Collection $records) {
$export = new ResultsSelectedBulkExport($records->toArray());
+57 -26
View File
@@ -19,13 +19,7 @@ class UserResource extends Resource
{
protected static ?string $model = User::class;
protected static ?string $navigationGroup = 'System';
protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack';
protected static ?int $navigationSort = 0;
protected static ?string $slug = 'system/users';
protected static ?string $navigationIcon = 'heroicon-o-users';
public static function form(Form $form): Form
{
@@ -65,20 +59,48 @@ class UserResource extends Resource
->visible(fn ($livewire) => $livewire instanceof EditUser)
->dehydrated(false),
])
->columns('full')
->columns(1)
->columnSpan([
'md' => 2,
]),
Forms\Components\Section::make()
Forms\Components\Grid::make([
'default' => 1,
])
->schema([
Forms\Components\Placeholder::make('created_at')
->content(fn ($record) => $record?->created_at?->diffForHumans() ?? new HtmlString('&mdash;')),
Forms\Components\Placeholder::make('updated_at')
->content(fn ($record) => $record?->updated_at?->diffForHumans() ?? new HtmlString('&mdash;')),
Forms\Components\Section::make()
->schema([
Forms\Components\Select::make('role')
->options([
'admin' => 'Admin',
'guest' => 'Guest',
'user' => 'User',
])
->default('guest')
->disabled(fn (): bool => ! auth()->user()->is_admin || auth()->user()->is_user)
->required(),
])
->columns(1)
->columnSpan([
'md' => 1,
]),
Forms\Components\Section::make()
->schema([
Forms\Components\Placeholder::make('created_at')
->content(fn ($record) => $record?->created_at?->diffForHumans() ?? new HtmlString('&mdash;')),
Forms\Components\Placeholder::make('updated_at')
->content(fn ($record) => $record?->updated_at?->diffForHumans() ?? new HtmlString('&mdash;')),
])
->columns(1)
->columnSpan([
'md' => 1,
]),
])
->columns('full')
->columnSpan(1),
->columns(1)
->columnSpan([
'md' => 1,
]),
]),
]);
}
@@ -87,27 +109,36 @@ class UserResource extends Resource
{
return $table
->columns([
Tables\Columns\TextColumn::make('id')
->label('ID'),
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('email')
->searchable(),
Tables\Columns\TextColumn::make('email_verified_at')
->dateTime(),
Tables\Columns\TextColumn::make('created_at')
->dateTime(),
Tables\Columns\TextColumn::make('role')
->badge()
->color(fn (string $state): string => match ($state) {
'admin' => 'success',
'guest' => 'gray',
'user' => 'info',
}),
Tables\Columns\TextColumn::make('updated_at')
->label('Last updated')
->dateTime(),
])
->filters([
//
Tables\Filters\SelectFilter::make('role')
->options([
'admin' => 'Admin',
'guest' => 'Guest',
'user' => 'User',
]),
])
->actions([
Tables\Actions\EditAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->requiresConfirmation(),
Tables\Actions\ActionGroup::make([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
]),
]);
}
+32
View File
@@ -6,6 +6,7 @@ namespace App\Models;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -25,6 +26,7 @@ class User extends Authenticatable implements FilamentUser
'email',
'email_verified_at',
'password',
'role',
];
/**
@@ -53,4 +55,34 @@ class User extends Authenticatable implements FilamentUser
{
return true;
}
/**
* Determine if the user has an admin role.
*/
protected function isAdmin(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes): bool => $attributes['role'] == 'admin',
);
}
/**
* Determine if the user has a guest role.
*/
protected function isGuest(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes): bool => $attributes['role'] == 'guest' || blank($attributes['role']),
);
}
/**
* Determine if the user has a user role.
*/
protected function isUser(): Attribute
{
return Attribute::make(
get: fn (mixed $value, array $attributes): bool => $attributes['role'] == 'user',
);
}
}
+14 -32
View File
@@ -4,16 +4,11 @@ namespace App\Policies;
use App\Models\Result;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ResultPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function viewAny(User $user): bool
{
@@ -22,8 +17,6 @@ class ResultPolicy
/**
* Determine whether the user can view the model.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function view(User $user, Result $result): bool
{
@@ -32,61 +25,50 @@ class ResultPolicy
/**
* Determine whether the user can create models.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function create(User $user): bool
{
//
return false;
}
/**
* Determine whether the user can update the model.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function update(User $user, Result $result): bool
{
return true;
return $user->is_admin
|| $user->is_user;
}
/**
* Determine whether the user can bulk delete any model.
*/
public function deleteAny(User $user)
{
return $user->is_admin;
}
/**
* Determine whether the user can delete the model.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function delete(User $user, Result $result): bool
{
return true;
}
/**
* Determine whether the user can delete multiple models.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function deleteAny(User $user)
{
return true;
return $user->is_admin;
}
/**
* Determine whether the user can restore the model.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function restore(User $user, Result $result): bool
{
//
return false; // soft deletes not used
}
/**
* Determine whether the user can permanently delete the model.
*
* @return \Illuminate\Auth\Access\Response|bool
*/
public function forceDelete(User $user, Result $result): bool
{
//
return false; // soft deletes not used
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace App\Policies;
use App\Models\User;
class UserPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->is_admin;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, User $model): bool
{
return $user->is_admin;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->is_admin;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, User $model): bool
{
if ($user->id == $model->id) {
return true;
}
return $user->is_admin;
}
/**
* Determine whether the user can bulk delete any model.
*/
public function deleteAny(User $user): bool
{
return false;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, User $model): bool
{
return $user->is_admin
&& ! $model->is_admin;
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, User $model): bool
{
return false; // soft deletes not used
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, User $model): bool
{
return false; // soft deletes not used
}
}
+1
View File
@@ -16,6 +16,7 @@
"influxdata/influxdb-client-php": "^3.4",
"laravel-notification-channels/telegram": "^4.0",
"laravel/framework": "^10.22.0",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^3.3.0",
"laravel/tinker": "^2.8.2",
"livewire/livewire": "^3.0.2",
Generated
+1 -1
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "bc918703a67702c619b5683108121250",
"content-hash": "2b460311fff6a639d5c28d2c49023941",
"packages": [
{
"name": "awcodes/filament-versions",
@@ -0,0 +1,39 @@
<?php
use App\Models\User;
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::table('users', function (Blueprint $table) {
$table->string('role')
->nullable()
->after('remember_token');
});
$user = User::first();
if ($user) {
$user->role = 'admin';
$user->save();
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};
+1 -2
View File
@@ -4,5 +4,4 @@ use Illuminate\Support\Facades\Route;
Route::prefix('test')->group(function () {
// silence is golden
}
);
});