feat: prohibitions (#1053)

This commit is contained in:
Kyrch
2026-01-21 16:38:31 -03:00
committed by GitHub
parent 4f7ab1ca5a
commit 4120a73228
50 changed files with 1837 additions and 72 deletions
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Events\Auth\Prohibition;
use App\Constants\Config\ServiceConstants;
use App\Contracts\Events\DiscordMessageEvent;
use App\Contracts\Events\NotifiesUsersEvent;
use App\Enums\Discord\EmbedColor;
use App\Models\Auth\Prohibition;
use App\Models\Auth\User;
use App\Notifications\Auth\ProhibitionOrSanctionNotification;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Config;
use Kyrch\Prohibition\Events\ModelProhibitionTriggered as BaseModelProhibitionTriggered;
use NotificationChannels\Discord\DiscordMessage;
class ModelProhibitionTriggered extends BaseModelProhibitionTriggered implements DiscordMessageEvent, NotifiesUsersEvent
{
/**
* @param User $model
* @param Prohibition $prohibition
* @param User $moderator
*/
public function __construct(
public Model $model,
public mixed $prohibition,
public ?DateTimeInterface $expiresAt = null,
public ?string $reason = null,
public ?Model $moderator = null,
) {
parent::__construct($model, $prohibition, $expiresAt, $reason, $moderator);
}
public function getDiscordMessage(): DiscordMessage
{
return DiscordMessage::create('', [
'description' => "Prohibition '**{$this->prohibition->name}**' triggered for user '**{$this->model->getName()}**'. Reason: {$this->reason}",
'color' => EmbedColor::GREEN->value,
]);
}
public function getDiscordChannel(): string
{
return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED);
}
public function shouldSendDiscordMessage(): bool
{
return true;
}
public function notify(): void
{
$this->model->notify(new ProhibitionOrSanctionNotification($this->prohibition, $this->reason, $this->expiresAt));
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Events\Auth\Prohibition;
use App\Constants\Config\ServiceConstants;
use App\Contracts\Events\DiscordMessageEvent;
use App\Contracts\Events\NotifiesUsersEvent;
use App\Enums\Discord\EmbedColor;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Notifications\Auth\ProhibitionOrSanctionNotification;
use DateTimeInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Config;
use Kyrch\Prohibition\Events\ModelSanctionTriggered as BaseModelSanctionTriggered;
use NotificationChannels\Discord\DiscordMessage;
class ModelSanctionTriggered extends BaseModelSanctionTriggered implements DiscordMessageEvent, NotifiesUsersEvent
{
/**
* @param User $model
* @param Sanction $sanction
* @param User $moderator
*/
public function __construct(
public Model $model,
public mixed $sanction,
public ?DateTimeInterface $expiresAt = null,
public ?string $reason = null,
public ?Model $moderator = null,
) {
parent::__construct($model, $sanction, $expiresAt, $reason, $moderator);
}
public function getDiscordMessage(): DiscordMessage
{
return DiscordMessage::create('', [
'description' => "Sanction '**{$this->sanction->name}**' triggered for user '**{$this->model->getName()}**'. Reason: {$this->reason}",
'color' => EmbedColor::GREEN->value,
]);
}
public function getDiscordChannel(): string
{
return Config::get(ServiceConstants::ADMIN_DISCORD_CHANNEL_QUALIFIED);
}
public function shouldSendDiscordMessage(): bool
{
return true;
}
public function notify(): void
{
$this->model->notify(new ProhibitionOrSanctionNotification($this->sanction, $this->reason, $this->expiresAt));
}
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Permission;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Role as RoleResource;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use Filament\Schemas\Schema;
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
class GiveRoleAction extends BaseAction
{
final public const string FIELD_ROLE = 'role';
final public const string FIELD_ROLES = 'roles';
public static function getDefaultName(): ?string
{
@@ -27,6 +28,8 @@ class GiveRoleAction extends BaseAction
$this->label(__('filament.actions.permission.give_role.name'));
$this->icon(RoleResource::getNavigationIcon());
$this->action(fn (Permission $record, array $data) => $this->handle($record, $data));
}
@@ -35,9 +38,9 @@ class GiveRoleAction extends BaseAction
*/
public function handle(Permission $permission, array $data): void
{
$role = Role::findById(intval(Arr::get($data, self::FIELD_ROLE)));
$roles = Arr::get($data, self::FIELD_ROLES);
$permission->assignRole($role);
$permission->assignRole($roles);
}
public function getSchema(Schema $schema): Schema
@@ -51,9 +54,10 @@ class GiveRoleAction extends BaseAction
return $schema
->components([
Select::make(self::FIELD_ROLE)
->label(__('filament.resources.singularLabel.role'))
Select::make(self::FIELD_ROLES)
->label(__('filament.resources.label.roles'))
->searchable()
->multiple()
->required()
->options($roles),
]);
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Permission;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Role as RoleResource;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use Filament\Schemas\Schema;
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
class RevokeRoleAction extends BaseAction
{
final public const string FIELD_ROLE = 'role';
final public const string FIELD_ROLES = 'roles';
public static function getDefaultName(): ?string
{
@@ -27,6 +28,8 @@ class RevokeRoleAction extends BaseAction
$this->label(__('filament.actions.permission.revoke_role.name'));
$this->icon(RoleResource::getNavigationIcon());
$this->action(fn (Permission $record, array $data) => $this->handle($record, $data));
}
@@ -35,9 +38,9 @@ class RevokeRoleAction extends BaseAction
*/
public function handle(Permission $permission, array $data): void
{
$role = Role::findById(intval(Arr::get($data, self::FIELD_ROLE)));
$roles = Arr::get($data, self::FIELD_ROLES);
$permission->removeRole($role);
$permission->removeRole($roles);
}
public function getSchema(Schema $schema): Schema
@@ -51,9 +54,10 @@ class RevokeRoleAction extends BaseAction
return $schema
->components([
Select::make(self::FIELD_ROLE)
->label(__('filament.resources.singularLabel.role'))
Select::make(self::FIELD_ROLES)
->label(__('filament.resources.label.roles'))
->searchable()
->multiple()
->required()
->options($roles),
]);
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Role;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Permission as PermissionResource;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use Filament\Schemas\Schema;
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
class GivePermissionAction extends BaseAction
{
final public const string FIELD_PERMISSION = 'permission';
final public const string FIELD_PERMISSIONS = 'permissions';
public static function getDefaultName(): ?string
{
@@ -27,6 +28,8 @@ class GivePermissionAction extends BaseAction
$this->label(__('filament.actions.role.give_permission.name'));
$this->icon(PermissionResource::getNavigationIcon());
$this->action(fn (Role $record, array $data) => $this->handle($record, $data));
}
@@ -37,9 +40,9 @@ class GivePermissionAction extends BaseAction
*/
public function handle(Role $role, array $data): void
{
$permission = Permission::findById(intval(Arr::get($data, self::FIELD_PERMISSION)));
$permissions = Arr::get($data, self::FIELD_PERMISSIONS);
$role->givePermissionTo($permission);
$role->givePermissionTo($permissions);
}
public function getSchema(Schema $schema): Schema
@@ -53,10 +56,11 @@ class GivePermissionAction extends BaseAction
return $schema
->components([
Select::make(self::FIELD_PERMISSION)
->label(__('filament.resources.singularLabel.permission'))
Select::make(self::FIELD_PERMISSIONS)
->label(__('filament.resources.label.permissions'))
->searchable()
->required()
->multiple()
->options($permissions),
]);
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\Role;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Permission as PermissionResource;
use App\Models\Auth\Permission;
use App\Models\Auth\Role;
use Filament\Schemas\Schema;
@@ -14,7 +15,7 @@ use Illuminate\Support\Arr;
class RevokePermissionAction extends BaseAction
{
final public const string FIELD_PERMISSION = 'permission';
final public const string FIELD_PERMISSIONS = 'permissions';
public static function getDefaultName(): ?string
{
@@ -27,6 +28,8 @@ class RevokePermissionAction extends BaseAction
$this->label(__('filament.actions.role.revoke_permission.name'));
$this->icon(PermissionResource::getNavigationIcon());
$this->action(fn (Role $record, array $data) => $this->handle($record, $data));
}
@@ -37,9 +40,9 @@ class RevokePermissionAction extends BaseAction
*/
public function handle(Role $role, array $data): void
{
$permission = Permission::findById(intval(Arr::get($data, self::FIELD_PERMISSION)));
$permissions = Arr::get($data, self::FIELD_PERMISSIONS);
$role->revokePermissionTo($permission);
$role->revokePermissionTo($permissions);
}
public function getSchema(Schema $schema): Schema
@@ -53,9 +56,10 @@ class RevokePermissionAction extends BaseAction
return $schema
->components([
Select::make(self::FIELD_PERMISSION)
->label(__('filament.resources.singularLabel.permission'))
Select::make(self::FIELD_PERMISSIONS)
->label(__('filament.resources.label.permissions'))
->searchable()
->multiple()
->required()
->options($permissions),
]);
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Models\Auth\Sanction;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
class GiveProhibitionAction extends BaseAction
{
final public const string FIELD_PROHIBITIONS = 'prohibitions';
public static function getDefaultName(): ?string
{
return 'sanction-give-prohibition';
}
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.sanction.give_prohibition.name'));
$this->icon(ProhibitionResource::getNavigationIcon());
$this->action(fn (Sanction $record, array $data) => $this->handle($record, $data));
}
/**
* Perform the action on the given model.
*
* @param array<string, mixed> $data
*/
public function handle(Sanction $sanction, array $data): void
{
$prohibitions = Arr::get($data, self::FIELD_PROHIBITIONS);
$sanction->prohibitions()->attach($prohibitions);
}
public function getSchema(Schema $schema): Schema
{
$prohibitions = Prohibition::query()
->whereDoesntHave(Prohibition::RELATION_SANCTIONS, fn (Builder $query) => $query->whereKey($this->getRecord()->getKey()))
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
->keyBy(Prohibition::ATTRIBUTE_ID)
->map(fn (Prohibition $prohibition) => $prohibition->name)
->toArray();
return $schema
->components([
Select::make(self::FIELD_PROHIBITIONS)
->label(__('filament.resources.label.prohibitions'))
->searchable()
->required()
->multiple()
->options($prohibitions),
]);
}
}
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Models\Auth\Sanction;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use Filament\Schemas\Schema;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
class RevokeProhibitionAction extends BaseAction
{
final public const string FIELD_PROHIBITIONS = 'prohibitions';
public static function getDefaultName(): ?string
{
return 'sanction-revoke-prohibition';
}
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.sanction.revoke_prohibition.name'));
$this->icon(ProhibitionResource::getNavigationIcon());
$this->action(fn (Sanction $record, array $data) => $this->handle($record, $data));
}
/**
* Perform the action on the given model.
*
* @param array<string, mixed> $data
*/
public function handle(Sanction $sanction, array $data): void
{
$prohibitions = Arr::get($data, self::FIELD_PROHIBITIONS);
$sanction->prohibitions()->detach($prohibitions);
}
public function getSchema(Schema $schema): Schema
{
$prohibitions = Prohibition::query()
->whereHas(Prohibition::RELATION_SANCTIONS, fn (Builder $query) => $query->whereKey($this->getRecord()->getKey()))
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
->keyBy(Prohibition::ATTRIBUTE_ID)
->map(fn (Prohibition $prohibition) => $prohibition->name)
->toArray();
return $schema
->components([
Select::make(self::FIELD_PROHIBITIONS)
->label(__('filament.resources.label.prohibitions'))
->searchable()
->multiple()
->required()
->options($prohibitions),
]);
}
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Permission as PermissionResource;
use App\Models\Auth\Permission;
use App\Models\Auth\User;
use Filament\Schemas\Schema;
@@ -27,6 +28,8 @@ class GivePermissionAction extends BaseAction
$this->label(__('filament.actions.user.give_permission.name'));
$this->icon(PermissionResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Models\Auth\User;
use App\Events\Auth\Prohibition\ModelProhibitionTriggered;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
use App\Models\Auth\Prohibition;
use App\Models\Auth\User;
use Filament\Forms\Components\DateTimePicker;
use Filament\Schemas\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
class GiveProhibitionAction extends BaseAction
{
final public const string FIELD_PROHIBITION = 'prohibition';
public static function getDefaultName(): ?string
{
return 'user-give-prohibition';
}
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.user.give_prohibition.name'));
$this->icon(ProhibitionResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
/**
* Perform the action on the given model.
*
* @param array<string, mixed> $data
*/
public function handle(User $user, array $data): void
{
/** @var Prohibition $prohibition */
$prohibition = Prohibition::query()->find(intval(Arr::get($data, self::FIELD_PROHIBITION)));
$reason = Arr::get($data, 'reason');
$expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at'));
$user->prohibit($prohibition, $expiresAt, $reason, Auth::user());
event(new ModelProhibitionTriggered($user, $prohibition, $expiresAt, $reason, Auth::user()));
}
public function getSchema(Schema $schema): Schema
{
$prohibitions = Prohibition::query()
->get([Prohibition::ATTRIBUTE_ID, Prohibition::ATTRIBUTE_NAME])
->keyBy(Prohibition::ATTRIBUTE_ID)
->map(fn (Prohibition $prohibition) => $prohibition->name)
->toArray();
return $schema
->components([
Select::make(self::FIELD_PROHIBITION)
->label(__('filament.resources.singularLabel.prohibition'))
->searchable()
->required()
->options($prohibitions),
DateTimePicker::make('expires_at')
->label(__('filament.actions.user.give_prohibition.expires_at.name'))
->helperText(__('filament.actions.user.give_prohibition.expires_at.help'))
->nullable(),
TextInput::make('reason')
->label(__('filament.actions.user.give_prohibition.reason.name'))
->helperText(__('filament.actions.user.give_prohibition.reason.help'))
->required(),
]);
}
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Role as RoleResource;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use Filament\Schemas\Schema;
@@ -27,6 +28,8 @@ class GiveRoleAction extends BaseAction
$this->label(__('filament.actions.user.give_role.name'));
$this->icon(RoleResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\Filament\Actions\Models\Auth\User;
use App\Events\Auth\Prohibition\ModelSanctionTriggered;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Resources\Auth\Sanction as SanctionResource;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use Filament\Forms\Components\DateTimePicker;
use Filament\Schemas\Schema;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Date;
class GiveSanctionAction extends BaseAction
{
final public const string FIELD_SANCTION = 'sanction';
public static function getDefaultName(): ?string
{
return 'user-give-sanction';
}
protected function setUp(): void
{
parent::setUp();
$this->label(__('filament.actions.user.give_sanction.name'));
$this->icon(SanctionResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
/**
* Perform the action on the given model.
*
* @param array<string, mixed> $data
*/
public function handle(User $user, array $data): void
{
$sanction = Sanction::query()->find(intval(Arr::get($data, self::FIELD_SANCTION)));
$reason = Arr::get($data, 'reason');
$expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at'));
$user->applySanction($sanction, $expiresAt, $reason, Auth::user());
event(new ModelSanctionTriggered($user, $sanction, $expiresAt, $reason, Auth::user()));
}
public function getSchema(Schema $schema): Schema
{
$sanctions = Sanction::query()
->get([Sanction::ATTRIBUTE_ID, Sanction::ATTRIBUTE_NAME])
->keyBy(Sanction::ATTRIBUTE_ID)
->map(fn (Sanction $sanction) => $sanction->name)
->toArray();
return $schema
->components([
Select::make(self::FIELD_SANCTION)
->label(__('filament.resources.singularLabel.sanction'))
->searchable()
->required()
->options($sanctions),
DateTimePicker::make('expires_at')
->label(__('filament.actions.user.give_sanction.expires_at.name'))
->helperText(__('filament.actions.user.give_sanction.expires_at.help'))
->nullable(),
TextInput::make('reason')
->label(__('filament.actions.user.give_sanction.reason.name'))
->helperText(__('filament.actions.user.give_sanction.reason.help'))
->required(),
]);
}
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Permission as PermissionResource;
use App\Models\Auth\Permission;
use App\Models\Auth\User;
use Filament\Schemas\Schema;
@@ -27,6 +28,8 @@ class RevokePermissionAction extends BaseAction
$this->label(__('filament.actions.user.revoke_permission.name'));
$this->icon(PermissionResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
@@ -6,6 +6,7 @@ namespace App\Filament\Actions\Models\Auth\User;
use App\Filament\Actions\BaseAction;
use App\Filament\Components\Fields\Select;
use App\Filament\Resources\Auth\Role as RoleResource;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use Filament\Schemas\Schema;
@@ -27,6 +28,8 @@ class RevokeRoleAction extends BaseAction
$this->label(__('filament.actions.user.revoke_role.name'));
$this->icon(RoleResource::getNavigationIcon());
$this->action(fn (User $record, array $data) => $this->handle($record, $data));
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Filament\RelationManagers\Auth;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\Resources\Auth\Prohibition as ProhibitionResource;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\Prohibition;
use Filament\Tables\Table;
abstract class ProhibitionRelationManager extends BaseRelationManager
{
/**
* The resource of the relation manager.
*
* @var class-string<BaseResource>|null
*/
protected static ?string $relatedResource = ProhibitionResource::class;
public function table(Table $table): Table
{
return parent::table(
$table
->recordTitleAttribute(Prohibition::ATTRIBUTE_NAME)
->defaultSort(Prohibition::TABLE.'.'.Prohibition::ATTRIBUTE_ID, 'desc')
);
}
/**
* @return \Filament\Actions\Action[]
*/
public static function getHeaderActions(): array
{
return ProhibitionResource::getTableActions();
}
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\Filament\RelationManagers\Auth;
use App\Filament\RelationManagers\BaseRelationManager;
use App\Filament\Resources\Auth\Sanction as SanctionResource;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\Sanction;
use Filament\Tables\Table;
abstract class SanctionRelationManager extends BaseRelationManager
{
/**
* The resource of the relation manager.
*
* @var class-string<BaseResource>|null
*/
protected static ?string $relatedResource = SanctionResource::class;
public function table(Table $table): Table
{
return parent::table(
$table
->recordTitleAttribute(Sanction::ATTRIBUTE_NAME)
->defaultSort(Sanction::TABLE.'.'.Sanction::ATTRIBUTE_ID, 'desc')
);
}
/**
* @return \Filament\Actions\Action[]
*/
public static function getHeaderActions(): array
{
return SanctionResource::getTableActions();
}
}
+134
View File
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth;
use App\Enums\Filament\NavigationGroup;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Components\Infolist\TimestampSection;
use App\Filament\Resources\Auth\Prohibition\Pages\ListProhibitions;
use App\Filament\Resources\Auth\Prohibition\Pages\ViewProhibition;
use App\Filament\Resources\Auth\Prohibition\RelationManagers\SanctionProhibitionRelationManager;
use App\Filament\Resources\Auth\Prohibition\RelationManagers\UserProhibitionRelationManager;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\Prohibition as ProhibitionModel;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class Prohibition extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Model>|null
*/
protected static ?string $model = ProhibitionModel::class;
public static function getModelLabel(): string
{
return __('filament.resources.singularLabel.prohibition');
}
public static function getPluralModelLabel(): string
{
return __('filament.resources.label.prohibitions');
}
public static function getNavigationGroup(): NavigationGroup
{
return NavigationGroup::AUTH;
}
public static function getNavigationIcon(): Heroicon
{
return Heroicon::OutlinedExclamationTriangle;
}
public static function getRecordSlug(): string
{
return 'prohibitions';
}
public static function getRecordTitleAttribute(): string
{
return ProhibitionModel::ATTRIBUTE_NAME;
}
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(ProhibitionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.prohibition.name'))
->required()
->maxLength(192)
->disabled(),
])
->columns(1);
}
public static function table(Table $table): Table
{
return parent::table($table)
->recordUrl(fn (ProhibitionModel $record): string => static::getUrl('view', ['record' => $record]))
->columns([
TextColumn::make(ProhibitionModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextColumn::make(ProhibitionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.prohibition.name'))
->searchable()
->copyableWithMessage(),
]);
}
public static function infolist(Schema $schema): Schema
{
return $schema
->components([
Section::make(static::getRecordTitle($schema->getRecord()))
->schema([
TextEntry::make(ProhibitionModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextEntry::make(ProhibitionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.prohibition.name'))
->copyableWithMessage(),
]),
TimestampSection::make(),
])
->columns(2);
}
/**
* @return array<int, RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
*/
public static function getRelations(): array
{
return [
RelationGroup::make(static::getModelLabel(), [
SanctionProhibitionRelationManager::class,
UserProhibitionRelationManager::class,
]),
];
}
/**
* @return array<string, \Filament\Resources\Pages\PageRegistration>
*/
public static function getPages(): array
{
return [
'index' => ListProhibitions::route('/'),
'view' => ViewProhibition::route('/{record:id}'),
];
}
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Prohibition\Pages;
use App\Filament\Resources\Auth\Prohibition;
use App\Filament\Resources\Base\BaseListResources;
class ListProhibitions extends BaseListResources
{
protected static string $resource = Prohibition::class;
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Prohibition\Pages;
use App\Filament\Resources\Auth\Prohibition;
use App\Filament\Resources\Base\BaseViewResource;
class ViewProhibition extends BaseViewResource
{
protected static string $resource = Prohibition::class;
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Prohibition\RelationManagers;
use App\Filament\RelationManagers\Auth\SanctionRelationManager;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use Filament\Tables\Table;
class SanctionProhibitionRelationManager extends SanctionRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = Prohibition::RELATION_SANCTIONS;
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(Sanction::RELATION_PROHIBITIONS)
);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Prohibition\RelationManagers;
use App\Filament\RelationManagers\Auth\UserRelationManager;
use App\Models\Auth\Prohibition;
use App\Models\Auth\User;
use Filament\Tables\Table;
class UserProhibitionRelationManager extends UserRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = Prohibition::RELATION_USERS;
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(User::RELATION_SANCTIONS)
);
}
}
+156
View File
@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth;
use App\Enums\Filament\NavigationGroup;
use App\Filament\Actions\Models\Auth\Sanction\GiveProhibitionAction;
use App\Filament\Actions\Models\Auth\Sanction\RevokeProhibitionAction;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\TextInput;
use App\Filament\Components\Infolist\TextEntry;
use App\Filament\Components\Infolist\TimestampSection;
use App\Filament\Resources\Auth\Sanction\Pages\ListSanctions;
use App\Filament\Resources\Auth\Sanction\Pages\ViewSanction;
use App\Filament\Resources\Auth\Sanction\RelationManagers\ProhibitionSanctionRelationManager;
use App\Filament\Resources\Auth\Sanction\RelationManagers\UserSanctionRelationManager;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\Sanction as SanctionModel;
use Filament\Resources\RelationManagers\RelationGroup;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class Sanction extends BaseResource
{
/**
* The model the resource corresponds to.
*
* @var class-string<Model>|null
*/
protected static ?string $model = SanctionModel::class;
public static function getModelLabel(): string
{
return __('filament.resources.singularLabel.sanction');
}
public static function getPluralModelLabel(): string
{
return __('filament.resources.label.sanctions');
}
public static function getNavigationGroup(): NavigationGroup
{
return NavigationGroup::AUTH;
}
public static function getNavigationIcon(): Heroicon
{
return Heroicon::OutlinedShieldExclamation;
}
public static function getRecordSlug(): string
{
return 'sanctions';
}
public static function getRecordTitleAttribute(): string
{
return SanctionModel::ATTRIBUTE_NAME;
}
public static function form(Schema $schema): Schema
{
return $schema
->components([
TextInput::make(SanctionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.sanction.name'))
->required()
->maxLength(192),
])
->columns(1);
}
public static function table(Table $table): Table
{
return parent::table($table)
->columns([
TextColumn::make(SanctionModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextColumn::make(SanctionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.sanction.name'))
->searchable()
->copyableWithMessage(),
]);
}
public static function infolist(Schema $schema): Schema
{
return $schema
->components([
Section::make(static::getRecordTitle($schema->getRecord()))
->schema([
TextEntry::make(SanctionModel::ATTRIBUTE_ID)
->label(__('filament.fields.base.id')),
TextEntry::make(SanctionModel::ATTRIBUTE_NAME)
->label(__('filament.fields.sanction.name'))
->copyableWithMessage(),
])
->columns(3),
TimestampSection::make(),
]);
}
/**
* @return array<int, RelationGroup|class-string<\Filament\Resources\RelationManagers\RelationManager>>
*/
public static function getRelations(): array
{
return [
RelationGroup::make(static::getModelLabel(), [
ProhibitionSanctionRelationManager::class,
UserSanctionRelationManager::class,
]),
];
}
/**
* @return array<int, \Filament\Tables\Filters\BaseFilter>
*/
public static function getFilters(): array
{
return [
...parent::getFilters(),
];
}
/**
* @return array<int, \Filament\Actions\Action|\Filament\Actions\ActionGroup>
*/
public static function getRecordActions(): array
{
return [
GiveProhibitionAction::make(),
RevokeProhibitionAction::make(),
];
}
/**
* @return array<string, \Filament\Resources\Pages\PageRegistration>
*/
public static function getPages(): array
{
return [
'index' => ListSanctions::route('/'),
'view' => ViewSanction::route('/{record:id}'),
];
}
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Sanction\Pages;
use App\Filament\Resources\Auth\Sanction;
use App\Filament\Resources\Base\BaseListResources;
class ListSanctions extends BaseListResources
{
protected static string $resource = Sanction::class;
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Sanction\Pages;
use App\Filament\Resources\Auth\Sanction;
use App\Filament\Resources\Base\BaseViewResource;
class ViewSanction extends BaseViewResource
{
protected static string $resource = Sanction::class;
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Sanction\RelationManagers;
use App\Filament\RelationManagers\Auth\ProhibitionRelationManager;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use Filament\Tables\Table;
class ProhibitionSanctionRelationManager extends ProhibitionRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = Sanction::RELATION_PROHIBITIONS;
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(Prohibition::RELATION_SANCTIONS)
);
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\Sanction\RelationManagers;
use App\Filament\RelationManagers\Auth\UserRelationManager;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use Filament\Tables\Table;
class UserSanctionRelationManager extends UserRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = Sanction::RELATION_USERS;
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(User::RELATION_SANCTIONS)
);
}
}
+10
View File
@@ -6,7 +6,9 @@ namespace App\Filament\Resources\Auth;
use App\Enums\Filament\NavigationGroup;
use App\Filament\Actions\Models\Auth\User\GivePermissionAction;
use App\Filament\Actions\Models\Auth\User\GiveProhibitionAction;
use App\Filament\Actions\Models\Auth\User\GiveRoleAction;
use App\Filament\Actions\Models\Auth\User\GiveSanctionAction;
use App\Filament\Actions\Models\Auth\User\RevokePermissionAction;
use App\Filament\Actions\Models\Auth\User\RevokeRoleAction;
use App\Filament\Components\Columns\TextColumn;
@@ -17,7 +19,9 @@ use App\Filament\Resources\Auth\User\Pages\ListUsers;
use App\Filament\Resources\Auth\User\Pages\ViewUser;
use App\Filament\Resources\Auth\User\RelationManagers\PermissionUserRelationManager;
use App\Filament\Resources\Auth\User\RelationManagers\PlaylistUserRelationManager;
use App\Filament\Resources\Auth\User\RelationManagers\ProhibitionUserRelationManager;
use App\Filament\Resources\Auth\User\RelationManagers\RoleUserRelationManager;
use App\Filament\Resources\Auth\User\RelationManagers\SanctionUserRelationManager;
use App\Filament\Resources\BaseResource;
use App\Models\Auth\User as UserModel;
use Filament\Infolists\Components\ImageEntry;
@@ -147,6 +151,8 @@ class User extends BaseResource
RelationGroup::make(static::getModelLabel(), [
RoleUserRelationManager::class,
PermissionUserRelationManager::class,
SanctionUserRelationManager::class,
ProhibitionUserRelationManager::class,
PlaylistUserRelationManager::class,
...parent::getBaseRelations(),
@@ -167,6 +173,10 @@ class User extends BaseResource
GivePermissionAction::make(),
RevokePermissionAction::make(),
GiveProhibitionAction::make(),
GiveSanctionAction::make(),
];
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\User\RelationManagers;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\TextInput;
use App\Filament\RelationManagers\Auth\ProhibitionRelationManager;
use App\Models\Auth\Prohibition;
use App\Models\Auth\User;
use Filament\Forms\Components\DateTimePicker;
use Filament\Tables\Table;
class ProhibitionUserRelationManager extends ProhibitionRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = User::RELATION_PROHIBITIONS;
public function getPivotComponents(): array
{
return [
DateTimePicker::make('expires_at')
->label(__('filament.actions.user.give_prohibition.expires_at.name'))
->helperText(__('filament.actions.user.give_prohibition.expires_at.help'))
->nullable(),
TextInput::make('reason')
->label(__('filament.actions.user.give_prohibition.reason.name'))
->helperText(__('filament.actions.user.give_prohibition.reason.help'))
->disabled(),
];
}
public function getPivotColumns(): array
{
return [
TextColumn::make('expires_at')
->date('M j, Y H:i:s')
->sortable(),
TextColumn::make('reason')
->sortable(),
];
}
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(Prohibition::RELATION_USERS)
);
}
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
{
return [];
}
}
@@ -7,7 +7,6 @@ namespace App\Filament\Resources\Auth\User\RelationManagers;
use App\Filament\RelationManagers\Auth\RoleRelationManager;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use Filament\Actions\EditAction;
use Filament\Tables\Table;
class RoleUserRelationManager extends RoleRelationManager
@@ -32,8 +31,11 @@ class RoleUserRelationManager extends RoleRelationManager
{
return [
...parent::getRecordActions(),
EditAction::make(),
];
}
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
{
return [];
}
}
@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace App\Filament\Resources\Auth\User\RelationManagers;
use App\Filament\Components\Columns\TextColumn;
use App\Filament\Components\Fields\TextInput;
use App\Filament\RelationManagers\Auth\SanctionRelationManager;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use Filament\Forms\Components\DateTimePicker;
use Filament\Tables\Table;
class SanctionUserRelationManager extends SanctionRelationManager
{
/**
* The relationship the relation manager corresponds to.
*/
protected static string $relationship = User::RELATION_SANCTIONS;
public function table(Table $table): Table
{
return parent::table(
$table
->inverseRelationship(Sanction::RELATION_USERS)
);
}
public function getPivotComponents(): array
{
return [
DateTimePicker::make('expires_at')
->label(__('filament.actions.user.give_sanction.expires_at.name'))
->helperText(__('filament.actions.user.give_sanction.expires_at.help'))
->nullable(),
TextInput::make('reason')
->label(__('filament.actions.user.give_sanction.reason.name'))
->helperText(__('filament.actions.user.give_sanction.reason.help'))
->disabled(),
];
}
public function getPivotColumns(): array
{
return [
TextColumn::make('expires_at')
->label(__('filament.actions.user.give_sanction.expires_at.name'))
->date('M j, Y H:i:s')
->sortable(),
TextColumn::make('reason')
->label(__('filament.actions.user.give_sanction.reason.name'))
->sortable(),
];
}
/**
* @return array<int, \Filament\Actions\Action>
*/
public static function getRecordActions(): array
{
return [
...parent::getRecordActions(),
];
}
public static function getBulkActions(?array $actionsIncludedInGroup = []): array
{
return [];
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Models\Auth;
use App\Contracts\Models\Nameable;
use Kyrch\Prohibition\Models\Prohibition as BaseProhibition;
class Prohibition extends BaseProhibition implements Nameable
{
final public const string TABLE = 'prohibitions';
final public const string ATTRIBUTE_ID = 'id';
final public const string ATTRIBUTE_NAME = 'name';
final public const string RELATION_SANCTIONS = 'sanctions';
final public const string RELATION_USERS = 'users';
public function getName(): string
{
return $this->name;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Models\Auth;
use App\Contracts\Models\Nameable;
use Kyrch\Prohibition\Models\Sanction as BaseSanction;
class Sanction extends BaseSanction implements Nameable
{
final public const string TABLE = 'sanctions';
final public const string ATTRIBUTE_ID = 'id';
final public const string ATTRIBUTE_NAME = 'name';
final public const string RELATION_PROHIBITIONS = 'prohibitions';
final public const string RELATION_USERS = 'users';
public function getName(): string
{
return $this->name;
}
}
+4
View File
@@ -39,6 +39,7 @@ use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Kyrch\Prohibition\Traits\HasSanctions;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
use Laravel\Sanctum\PersonalAccessToken;
@@ -69,6 +70,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
use HasApiTokens;
use HasFactory;
use HasRoles;
use HasSanctions;
use Notifiable;
use SoftDeletes;
use TwoFactorAuthenticatable;
@@ -89,7 +91,9 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasSubtit
final public const string RELATION_MANAGED_SUBMISSIONS = 'managedsubmissions';
final public const string RELATION_NOTIFICATIONS = 'notifications';
final public const string RELATION_PERMISSIONS = 'permissions';
final public const string RELATION_PROHIBITIONS = 'prohibitions';
final public const string RELATION_PLAYLISTS = 'playlists';
final public const string RELATION_SANCTIONS = 'sanctions';
final public const string RELATION_SUBMISSIONS = 'submissions';
final public const string RELATION_ROLES = 'roles';
final public const string RELATION_ROLES_PERMISSIONS = 'roles.permissions';
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace App\Notifications\Auth;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use DateTimeInterface;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ProhibitionOrSanctionNotification extends Notification
{
public function __construct(
public Prohibition|Sanction $issued,
public string $reason,
public ?DateTimeInterface $expiresAt = null,
) {}
/**
* Get the notification's channels.
*
* @param mixed $notifiable
*/
public function via($notifiable): array
{
return ['mail'];
}
/**
* Build the mail representation of the notification.
*
* @param mixed $notifiable
* @return MailMessage
*/
public function toMail($notifiable)
{
$type = $this->issued instanceof Prohibition ? 'prohibition' : 'sanction';
return new MailMessage()
->subject('Account Prohibition or Sanction Issued')
->greeting("Dear {$notifiable->getName()},")
->line("Your account has been issued a {$type} ({$this->issued->name}).")
->line($this->getExpiresAtLine($type))
->line("Reason: {$this->reason}");
}
protected function getExpiresAtLine(string $type): string
{
if (! $this->expiresAt instanceof DateTimeInterface) {
return "This {$type} is permanent.";
}
$expiresFormat = $this->expiresAt->format('Y-m-d H:i:s');
return "This {$type} will expire on {$expiresFormat}.";
}
}
+33 -16
View File
@@ -6,6 +6,7 @@ namespace App\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Auth\Role;
use App\Models\Auth\Permission;
use App\Models\Auth\User;
use App\Policies\BasePolicy;
@@ -61,43 +62,59 @@ class PermissionPolicy extends BasePolicy
: Response::deny();
}
public function attachAnyRole(): Response
public function attachAnyRole(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachRole(): Response
public function attachRole(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyRole(): Response
public function detachAnyRole(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachRole(): Response
public function detachRole(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachAnyUser(): Response
public function attachAnyUser(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachUser(): Response
public function attachUser(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyUser(): Response
public function detachAnyUser(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachUser(): Response
public function detachUser(User $user): Response
{
return Response::deny();
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace App\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Auth\Role;
use App\Models\Auth\Prohibition;
use App\Models\Auth\User;
use App\Policies\BasePolicy;
use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;
class ProhibitionPolicy extends BasePolicy
{
public function viewAny(?User $user, mixed $value = null): Response
{
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Prohibition::class))
? Response::allow()
: Response::deny();
}
/**
* @param Prohibition $Prohibition
*/
public function view(?User $user, Model $Prohibition): Response
{
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Prohibition::class))
? Response::allow()
: Response::deny();
}
/**
* @param Prohibition $Prohibition
*/
public function update(User $user, Model $Prohibition): Response
{
return $user->can(CrudPermission::UPDATE->format(Prohibition::class))
? Response::allow()
: Response::deny();
}
/**
* @param Prohibition $Prohibition
*/
public function delete(User $user, Model $Prohibition): Response
{
return $user->can(CrudPermission::DELETE->format(Prohibition::class))
? Response::allow()
: Response::deny();
}
/**
* @param Prohibition $Prohibition
*/
public function restore(User $user, Model $Prohibition): Response
{
return $user->can(ExtendedCrudPermission::RESTORE->format(Prohibition::class))
? Response::allow()
: Response::deny();
}
public function attachAnySanction(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachSanction(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnySanction(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachSanction(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachAnyUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
}
+33 -16
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role as RoleEnum;
use App\Models\Auth\Role;
use App\Models\Auth\User;
use App\Policies\BasePolicy;
@@ -30,43 +31,59 @@ class RolePolicy extends BasePolicy
: Response::deny();
}
public function attachAnyPermission(): Response
public function attachAnyPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachPermission(): Response
public function attachPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyPermission(): Response
public function detachAnyPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachPermission(): Response
public function detachPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachAnyUser(): Response
public function attachAnyUser(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachUser(): Response
public function attachUser(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyUser(): Response
public function detachAnyUser(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachUser(): Response
public function detachUser(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
}
+89
View File
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace App\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\Role;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Policies\BasePolicy;
use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;
class SanctionPolicy extends BasePolicy
{
public function viewAny(?User $user, mixed $value = null): Response
{
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Sanction::class))
? Response::allow()
: Response::deny();
}
/**
* @param Sanction $Sanction
*/
public function view(?User $user, Model $Sanction): Response
{
return $user instanceof User && $user->can(CrudPermission::VIEW->format(Sanction::class))
? Response::allow()
: Response::deny();
}
public function attachAnyProhibition(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachProhibition(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyProhibition(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachProhibition(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachAnyUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachUser(User $user): Response
{
return $user->hasRole(Role::ADMIN->value)
? Response::allow()
: Response::deny();
}
}
+32 -16
View File
@@ -30,44 +30,60 @@ class UserPolicy extends BasePolicy
: Response::deny();
}
public function attachAnyRole(): Response
public function attachAnyRole(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachRole(): Response
public function attachRole(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyRole(): Response
public function detachAnyRole(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachRole(): Response
public function detachRole(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachAnyPermission(): Response
public function attachAnyPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function attachPermission(): Response
public function attachPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachAnyPermission(): Response
public function detachAnyPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function detachPermission(): Response
public function detachPermission(User $user): Response
{
return Response::deny();
return $user->hasRole(RoleEnum::ADMIN->value)
? Response::allow()
: Response::deny();
}
public function addPlaylist(User $user): Response
+13
View File
@@ -33,12 +33,25 @@ abstract class BasePolicy
->__toString();
}
protected static function format(string $ability): string
{
return Str::of($ability)
->remove('Any')
->append(class_basename(static::getModel()))
->snake(' ')
->__toString();
}
public function before(User $user, string $ability): ?Response
{
if ($user->can(SpecialPermission::BYPASS_AUTHORIZATION->value)) {
return Response::allow();
}
if ($user->isProhibitedFrom(static::format($ability))) {
return Response::deny('You have been banned from performing this action. Please check your email for more details.');
}
return null;
}
+2
View File
@@ -29,6 +29,7 @@ use App\Pivots\Wiki\AnimeThemeEntryVideo;
use App\Pivots\Wiki\ArtistMember;
use App\Pivots\Wiki\ArtistSong;
use Database\Seeders\Auth\Permission\PermissionSeeder;
use Database\Seeders\Auth\Prohibition\ProhibitionSeeder;
use Database\Seeders\Auth\Role\AdminSeeder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Relation;
@@ -55,6 +56,7 @@ class AppServiceProvider extends ServiceProvider
ParallelTesting::setUpTestDatabase(function (string $database, int $token): void {
Artisan::call('db:seed', ['--class' => PermissionSeeder::class]);
Artisan::call('db:seed', ['--class' => AdminSeeder::class]);
Artisan::call('db:seed', ['--class' => ProhibitionSeeder::class]);
});
DB::listen(function (QueryExecuted $query): void {
+1
View File
@@ -32,6 +32,7 @@
"filament/filament": "^4.4.0",
"flowframe/laravel-trend": ">=0.4",
"guzzlehttp/guzzle": "^7.10.0",
"kyrch/laravel-prohibitions": "^1.1.0",
"larastan/larastan": "^3.8.1",
"laravel-notification-channels/discord": "^1.7",
"laravel/fortify": "^1.33.0",
Generated
+81 -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": "e17368d2e59c27ecf9080634a686d848",
"content-hash": "ad7bbfdc96fcdca090892cc0ee25a07f",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@@ -3342,6 +3342,86 @@
},
"time": "2025-12-17T00:37:48+00:00"
},
{
"name": "kyrch/laravel-prohibitions",
"version": "v1.1",
"source": {
"type": "git",
"url": "https://github.com/Kyrch/laravel-prohibitions.git",
"reference": "8874f07051b40994fd069950e28aa2f7ce39b3da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Kyrch/laravel-prohibitions/zipball/8874f07051b40994fd069950e28aa2f7ce39b3da",
"reference": "8874f07051b40994fd069950e28aa2f7ce39b3da",
"shasum": ""
},
"require": {
"illuminate/contracts": "^11.0||^12.0",
"php": "^8.4",
"spatie/laravel-package-tools": "^1.16"
},
"require-dev": {
"driftingly/rector-laravel": "^2.1",
"larastan/larastan": "^3.0",
"laravel/pint": "^1.14",
"nunomaduro/collision": "^8.8",
"orchestra/testbench": "^10.0.0||^9.0.0",
"pestphp/pest": "^4.0",
"pestphp/pest-plugin-arch": "^4.0",
"pestphp/pest-plugin-laravel": "^4.0",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"rector/rector": "^2.2"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Prohibition": "Kyrch\\Prohibition\\Facades\\Prohibition"
},
"providers": [
"Kyrch\\Prohibition\\ProhibitionServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Kyrch\\Prohibition\\": "src/",
"Kyrch\\Prohibition\\Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Kyrch",
"email": "kyrchk5@gmail.com",
"role": "Developer"
}
],
"description": "Prohibit your models from execute an action in a way similar to spatie/laravel-permission.",
"homepage": "https://github.com/kyrch/laravel-prohibitions",
"keywords": [
"Kyrch",
"laravel",
"laravel-prohibitions"
],
"support": {
"issues": "https://github.com/Kyrch/laravel-prohibitions/issues",
"source": "https://github.com/Kyrch/laravel-prohibitions/tree/v1.1"
},
"funding": [
{
"url": "https://github.com/Kyrch",
"type": "github"
}
],
"time": "2026-01-21T19:03:55+00:00"
},
{
"name": "laragraph/utils",
"version": "v2.2.0",
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use App\Models\Auth\Prohibition;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use Kyrch\Prohibition\Pivots\ModelProhibition;
use Kyrch\Prohibition\Pivots\ModelSanction;
return [
'events_enabled' => false,
'models' => [
'user' => User::class,
'prohibition' => Prohibition::class,
'sanction' => Sanction::class,
'model_prohibition' => ModelProhibition::class,
'model_sanction' => ModelSanction::class,
],
'table_names' => [
'prohibition' => 'prohibitions',
'sanction' => 'sanctions',
'sanction_prohibition' => 'sanction_prohibition',
'model_sanctions' => 'model_sanctions',
'model_prohibitions' => 'model_prohibitions',
],
];
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
/** @var array<string, string> $tableNames */
$tableNames = Config::array('prohibition.table_names');
Schema::create($tableNames['prohibition'], function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->timestamps();
$table->unique('name');
});
Schema::create($tableNames['sanction'], function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->timestamps();
$table->unique('name');
});
Schema::create($tableNames['sanction_prohibition'], function (Blueprint $table) use ($tableNames): void {
$table->unsignedBigInteger('sanction_id');
$table->foreign('sanction_id')->references('id')->on($tableNames['sanction'])->cascadeOnDelete();
$table->unsignedBigInteger('prohibition_id');
$table->foreign('prohibition_id')->references('id')->on($tableNames['prohibition'])->cascadeOnDelete();
$table->timestamps();
$table->primary(['sanction_id', 'prohibition_id']);
});
Schema::create($tableNames['model_sanctions'], function (Blueprint $table) use ($tableNames): void {
$table->morphs('model');
$table->unsignedBigInteger('sanction_id');
$table->foreign('sanction_id')->references('id')->on($tableNames['sanction'])->cascadeOnDelete();
$table->nullableMorphs('moderator');
$table->timestamp('expires_at')->nullable();
$table->text('reason')->nullable();
$table->timestamps();
$table->primary(['model_type', 'model_id', 'sanction_id']);
$table->index('expires_at');
});
Schema::create($tableNames['model_prohibitions'], function (Blueprint $table) use ($tableNames): void {
$table->morphs('model');
$table->unsignedBigInteger('prohibition_id');
$table->foreign('prohibition_id')->references('id')->on($tableNames['prohibition'])->cascadeOnDelete();
$table->nullableMorphs('moderator');
$table->timestamp('expires_at')->nullable();
$table->text('reason')->nullable();
$table->timestamps();
$table->primary(['model_type', 'model_id', 'prohibition_id']);
$table->index('expires_at');
});
}
};
@@ -12,7 +12,9 @@ use App\Models\Admin\Dump;
use App\Models\Admin\Feature;
use App\Models\Admin\FeaturedTheme;
use App\Models\Auth\Permission;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Role;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
@@ -61,7 +63,9 @@ class PermissionSeeder extends Seeder
// Auth Resources
$this->registerResource(Permission::class, [CrudPermission::VIEW]);
$this->registerResource(Prohibition::class, [CrudPermission::VIEW, CrudPermission::UPDATE]);
$this->registerResource(Role::class, CrudPermission::cases());
$this->registerResource(Sanction::class, CrudPermission::cases());
$this->registerResource(User::class, $extendedCrudPermissions);
// Discord Resources
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Database\Seeders\Auth\Prohibition;
use App\Models\Auth\Permission;
use Illuminate\Database\Seeder;
use Kyrch\Prohibition\Models\Prohibition;
class ProhibitionSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
Permission::query()->get([Permission::ATTRIBUTE_NAME])->each(
function (Permission $permission): void {
Prohibition::query()->firstOrCreate([
'name' => $permission->name,
]);
},
);
}
}
@@ -13,7 +13,9 @@ use App\Models\Admin\Dump;
use App\Models\Admin\Feature;
use App\Models\Admin\FeaturedTheme;
use App\Models\Auth\Permission;
use App\Models\Auth\Prohibition;
use App\Models\Auth\Role;
use App\Models\Auth\Sanction;
use App\Models\Auth\User;
use App\Models\Discord\DiscordThread;
use App\Models\Document\Page;
@@ -69,7 +71,9 @@ class AdminSeeder extends RoleSeeder
// Auth Resources
$this->configureResource($role, Permission::class, [CrudPermission::VIEW]);
$this->configureResource($role, Prohibition::class, [CrudPermission::VIEW, CrudPermission::UPDATE]);
$this->configureResource($role, Role::class, CrudPermission::cases());
$this->configureResource($role, Sanction::class, CrudPermission::cases());
$this->configureResource($role, User::class, $extendedCrudPermissions);
// Discord Resources
+2
View File
@@ -6,6 +6,7 @@ namespace Database\Seeders;
use Database\Seeders\Admin\Feature\FeatureSeeder;
use Database\Seeders\Auth\Permission\PermissionSeeder;
use Database\Seeders\Auth\Prohibition\ProhibitionSeeder;
use Database\Seeders\Auth\Role\RoleSeeder;
use Database\Seeders\Scout\ImportModelsSeeder;
use Database\Seeders\Wiki\Audio\AudioSeeder;
@@ -21,6 +22,7 @@ class DatabaseSeeder extends Seeder
{
$this->call(PermissionSeeder::class);
$this->call(RoleSeeder::class);
$this->call(ProhibitionSeeder::class);
$this->call(VideoSeeder::class);
$this->call(AudioSeeder::class);
$this->call(HashidsSeeder::class);
+41
View File
@@ -340,6 +340,14 @@ return [
'name' => 'Revoke Permission',
],
],
'sanction' => [
'give_prohibition' => [
'name' => 'Give Prohibition',
],
'revoke_prohibition' => [
'name' => 'Revoke Prohibition',
],
],
'storage' => [
'delete' => [
'confirmButtonText' => 'Remove',
@@ -405,12 +413,35 @@ return [
'give_role' => [
'name' => 'Give Role',
],
'give_prohibition' => [
'name' => 'Give Prohibition',
'expires_at' => [
'help' => 'The date and time the prohibition expires. Leave blank for a permanent prohibition',
'name' => 'Expires At',
],
'reason' => [
'help' => 'The reason for the prohibition. It will be sent to the email address associated with the user account.',
'name' => 'Reason',
],
],
'give_sanction' => [
'name' => 'Give Sanction',
'expires_at' => [
'help' => 'The date and time the sanction expires. Leave blank for a permanent sanction',
'name' => 'Expires At',
],
'reason' => [
'help' => 'The reason for the sanction. It will be sent to the email address associated with the user account.',
'name' => 'Reason',
],
],
'revoke_permission' => [
'name' => 'Revoke Permission',
],
'revoke_role' => [
'name' => 'Revoke Role',
],
],
'video_script' => [
'delete' => [
@@ -850,6 +881,9 @@ return [
'name' => 'Visibility',
],
],
'prohibition' => [
'name' => 'Name',
],
'submission' => [
'finished_at' => 'Finished At',
'locked' => 'Locked',
@@ -884,6 +918,9 @@ return [
'name' => 'Priority',
],
],
'sanction' => [
'name' => 'Name',
],
'series' => [
'name' => [
'help' => 'The display title of the Series. Ex: "Monogatari", "Code Geass", "Dungeon ni Deai wo Motomeru no wa Machigatteiru Darou ka".',
@@ -1007,9 +1044,11 @@ return [
'permissions' => 'Permissions',
'playlist_tracks' => 'Playlist Tracks',
'playlists' => 'Playlists',
'prohibitions' => 'Prohibitions',
'submissions' => 'Submissions',
'submission_stages' => 'Submission Stages',
'roles' => 'Roles',
'sanctions' => 'Sanctions',
'series' => 'Series',
'songs' => 'Songs',
'studios' => 'Studios',
@@ -1043,6 +1082,8 @@ return [
'permission' => 'Permission',
'playlist_track' => 'Playlist Track',
'playlist' => 'Playlist',
'prohibition' => 'Prohibition',
'sanction' => 'Sanction',
'submission' => 'Submission',
'submission_stage' => 'Submission Stage',
'role' => 'Role',