diff --git a/app/Enums/Models/List/ExternalProfileSite.php b/app/Enums/Models/List/ExternalProfileSite.php index e3b8626db..80b856831 100644 --- a/app/Enums/Models/List/ExternalProfileSite.php +++ b/app/Enums/Models/List/ExternalProfileSite.php @@ -45,7 +45,7 @@ public function getAuthorizeUrl(): ?string if ($this === static::MAL) { $codeVerifier = bin2hex(random_bytes(64)); - $id = Str::uuid()->toString(); + $id = Str::uuid()->__toString(); Cache::set("mal-external-token-request-{$id}", $codeVerifier); diff --git a/app/Filament/Actions/Base/AttachAction.php b/app/Filament/Actions/Base/AttachAction.php index e92e525de..5e2c1c96f 100644 --- a/app/Filament/Actions/Base/AttachAction.php +++ b/app/Filament/Actions/Base/AttachAction.php @@ -9,6 +9,9 @@ use App\Filament\RelationManagers\BaseRelationManager; use Filament\Tables\Actions\AttachAction as DefaultAttachAction; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Str; /** * Class AttachAction. @@ -26,9 +29,23 @@ protected function setUp(): void { parent::setUp(); - $this->authorize('create'); + $this->visible(function (BaseRelationManager $livewire) { + if (!$livewire->getRelationship() instanceof BelongsToMany) { + return false; + } - $this->hidden(fn (BaseRelationManager $livewire) => !($livewire->getRelationship() instanceof BelongsToMany)); + $ownerRecord = $livewire->getOwnerRecord(); + + $gate = Gate::getPolicyFor($ownerRecord); + + $ability = Str::of('attachAny') + ->append(Str::singular(class_basename($livewire->getTable()->getModel()))) + ->__toString(); + + return is_object($gate) & method_exists($gate, $ability) + ? Gate::forUser(Auth::user())->any($ability, $ownerRecord) + : true; + }); $this->recordSelect(function (BaseRelationManager $livewire) { /** @var string */ diff --git a/app/Filament/Actions/Base/CreateAction.php b/app/Filament/Actions/Base/CreateAction.php index cb322696b..6f266589f 100644 --- a/app/Filament/Actions/Base/CreateAction.php +++ b/app/Filament/Actions/Base/CreateAction.php @@ -8,12 +8,14 @@ use App\Enums\Auth\Role; use App\Filament\RelationManagers\BaseRelationManager; use App\Filament\RelationManagers\Wiki\ResourceRelationManager; +use App\Models\Auth\User; use Filament\Forms\Form; use Filament\Tables\Actions\CreateAction as DefaultCreateAction; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Http\Request; -use Livewire\Component; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Str; /** * Class CreateAction. @@ -49,12 +51,26 @@ protected function setUp(): void } }); - $this->hidden(function (Component $livewire, Request $request) { + $this->visible(function (BaseRelationManager $livewire) { if ($livewire instanceof ResourceRelationManager) { - return !$request->user()->hasRole(Role::ADMIN->value); + /** @var User $user */ + $user = Auth::user(); + return $user->hasRole(Role::ADMIN->value); } - return false; + $ownerRecord = $livewire->getOwnerRecord(); + + $gate = Gate::getPolicyFor($ownerRecord); + + $method = $livewire->getRelationship() instanceof BelongsToMany ? 'attachAny' : 'addAny'; + + $ability = Str::of($method) + ->append(Str::singular(class_basename($livewire->getTable()->getModel()))) + ->__toString(); + + return is_object($gate) & method_exists($gate, $ability) + ? Gate::forUser(Auth::user())->any($ability, $ownerRecord) + : true; }); } } diff --git a/app/Filament/Actions/Base/DetachAction.php b/app/Filament/Actions/Base/DetachAction.php index 809385644..139e93a15 100644 --- a/app/Filament/Actions/Base/DetachAction.php +++ b/app/Filament/Actions/Base/DetachAction.php @@ -8,6 +8,9 @@ use App\Filament\RelationManagers\BaseRelationManager; use Filament\Tables\Actions\DetachAction as DefaultDetachAction; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Str; /** * Class DetachAction. @@ -27,9 +30,32 @@ protected function setUp(): void $this->label(__('filament.actions.base.detach')); - $this->hidden(fn ($livewire) => !($livewire instanceof BaseRelationManager && $livewire->getRelationship() instanceof BelongsToMany)); + $this->visible(function (mixed $livewire) { + if ( + !($livewire instanceof BaseRelationManager) + || !($livewire->getRelationship() instanceof BelongsToMany) + ) { + return false; + } + + $ownerRecord = $livewire->getOwnerRecord(); + + $gate = Gate::getPolicyFor($ownerRecord); + + $model = Str::singular(class_basename($livewire->getTable()->getModel())); - $this->authorize('delete'); + $detachAny = Str::of('detachAny') + ->append($model) + ->__toString(); + + $detach = Str::of('detach') + ->append($model) + ->__toString(); + + return is_object($gate) & method_exists($gate, $detachAny) + ? Gate::forUser(Auth::user())->any([$detachAny, $detach], [$ownerRecord, $this->getRecord()]) + : true; + }); $this->after(function ($livewire, $record) { $relationship = $livewire->getRelationship(); diff --git a/app/Http/Controllers/Api/Pivot/PivotController.php b/app/Http/Controllers/Api/Pivot/PivotController.php index 8dacfecf6..c5e72593a 100644 --- a/app/Http/Controllers/Api/Pivot/PivotController.php +++ b/app/Http/Controllers/Api/Pivot/PivotController.php @@ -8,6 +8,7 @@ use App\Http\Api\Schema\Schema; use App\Http\Controllers\Controller; use App\Http\Middleware\Auth\Authenticate; +use App\Http\Middleware\Models\AuthorizesPivot; use Illuminate\Support\Str; /** @@ -25,8 +26,7 @@ abstract class PivotController extends Controller implements InteractsWithSchema */ public function __construct(string $foreignModel, string $foreignParameter, string $relatedModel, string $relatedParameter) { - $this->authorizeResource($foreignModel, $foreignParameter); - $this->authorizeResource($relatedModel, $relatedParameter); + $this->middleware(AuthorizesPivot::class.":{$foreignModel},{$foreignParameter},{$relatedModel},{$relatedParameter}"); $this->middleware(Authenticate::using('sanctum'))->except(['index', 'show']); } diff --git a/app/Http/Middleware/LogRequest.php b/app/Http/Middleware/LogRequest.php index dd484d4fa..2963926b5 100644 --- a/app/Http/Middleware/LogRequest.php +++ b/app/Http/Middleware/LogRequest.php @@ -24,7 +24,7 @@ class LogRequest */ public function handle(Request $request, Closure $next): mixed { - $requestId = Str::uuid()->toString(); + $requestId = Str::uuid()->__toString(); Log::withContext([ 'request-id' => $requestId, diff --git a/app/Http/Middleware/Models/AuthorizesPivot.php b/app/Http/Middleware/Models/AuthorizesPivot.php new file mode 100644 index 000000000..6e5de7107 --- /dev/null +++ b/app/Http/Middleware/Models/AuthorizesPivot.php @@ -0,0 +1,133 @@ +route($foreignParameter); + + /** @var Model $relatedModel */ + $relatedModel = $request->route($relatedParameter); + + $isAuthorized = match ($request->route()->getActionMethod()) { + 'index' => $this->forIndex($request->user(), $foreignClass, $relatedClass), + 'show' => $this->forShow($request->user(), $foreignModel, $relatedModel), + 'create', 'store' => $this->forStore($request->user(), $foreignModel, $relatedModel), + 'destroy' => $this->forDestroy($request->user(), $foreignModel, $relatedModel), + 'edit', 'update' => $this->forUpdate($request->user(), $foreignModel, $relatedModel), + default => false, + }; + + if (!$isAuthorized) { + abort(403); + } + + return $next($request); + } + + /** + * Get the authorization to index. + * + * @param User|null $user + * @param string $foreignClass + * @param string $relatedClass + * @return bool + */ + protected function forIndex(?User $user, string $foreignClass, string $relatedClass): bool + { + return Gate::forUser($user)->check('viewAny', $foreignClass) + && Gate::forUser($user)->check('viewAny', $relatedClass); + } + + /** + * Get the authorization to show a pivot. + * + * @param User|null $user + * @param Model $foreignModel + * @param Model $relatedModel + * @return bool + */ + protected function forShow(?User $user, Model $foreignModel, Model $relatedModel): bool + { + return Gate::forUser($user)->check('view', $foreignModel) + && Gate::forUser($user)->check('view', $relatedModel); + } + + /** + * Get the authorization to store a pivot. + * + * @param User $user + * @param Model $foreignModel + * @param Model $relatedModel + * @return bool + */ + protected function forStore(User $user, Model $foreignModel, Model $relatedModel): bool + { + $attach = Str::of('attach') + ->append(Str::singular(class_basename($relatedModel))) + ->__toString(); + + return Gate::forUser($user)->check($attach, [$foreignModel, $relatedModel]); + } + + /** + * Get the authorization to destroy a pivot. + * + * @param User $user + * @param Model $foreignModel + * @param Model $relatedModel + * @return bool + */ + protected function forDestroy(User $user, Model $foreignModel, Model $relatedModel): bool + { + $detachAny = Str::of('detachAny') + ->append(Str::singular(class_basename($relatedModel))) + ->__toString(); + + $detach = Str::of('detach') + ->append(Str::singular(class_basename($relatedModel))) + ->__toString(); + + return Gate::forUser($user)->any([$detach, $detachAny], $foreignModel); + } + + /** + * Get the authorization to update a pivot. + * + * @param User $user + * @param Model $foreignModel + * @param Model $relatedModel + * @return bool + */ + protected function forUpdate(User $user, Model $foreignModel, Model $relatedModel): bool + { + return Gate::forUser($user)->check('update', $foreignModel) + && Gate::forUser($user)->check('update', $relatedModel); + } +} diff --git a/app/Policies/Auth/PermissionPolicy.php b/app/Policies/Auth/PermissionPolicy.php index abe68d054..9d54a58d0 100644 --- a/app/Policies/Auth/PermissionPolicy.php +++ b/app/Policies/Auth/PermissionPolicy.php @@ -103,6 +103,16 @@ public function attachRole(): bool return false; } + /** + * Determine whether the user can detach any role from the permission. + * + * @return bool + */ + public function detachAnyRole(): bool + { + return false; + } + /** * Determine whether the user can detach a role from the permission. * @@ -133,6 +143,16 @@ public function attachUser(): bool return false; } + /** + * Determine whether the user can detach any user from the permission. + * + * @return bool + */ + public function detachAnyUser(): bool + { + return false; + } + /** * Determine whether the user can detach a user from the permission. * diff --git a/app/Policies/Auth/RolePolicy.php b/app/Policies/Auth/RolePolicy.php index f41fbd5f3..3e98c1eae 100644 --- a/app/Policies/Auth/RolePolicy.php +++ b/app/Policies/Auth/RolePolicy.php @@ -45,7 +45,7 @@ public function view(?User $user, Model $role): bool * @param User $user * @param Role $role * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function update(User $user, Model $role): bool @@ -59,7 +59,7 @@ public function update(User $user, Model $role): bool * @param User $user * @param Role $role * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function delete(User $user, Model $role): bool @@ -73,7 +73,7 @@ public function delete(User $user, Model $role): bool * @param User $user * @param Role $role * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function restore(User $user, Model $role): bool @@ -101,6 +101,16 @@ public function attachPermission(): bool return false; } + /** + * Determine whether the user can detach any permission from the role. + * + * @return bool + */ + public function detachAnyPermission(): bool + { + return false; + } + /** * Determine whether the user can detach a permission from the role. * @@ -131,6 +141,16 @@ public function attachUser(): bool return false; } + /** + * Determine whether the user can detach any user from the role. + * + * @return bool + */ + public function detachAnyUser(): bool + { + return false; + } + /** * Determine whether the user can detach a user from the role. * diff --git a/app/Policies/Auth/UserPolicy.php b/app/Policies/Auth/UserPolicy.php index 1bfebc6b5..9714a7223 100644 --- a/app/Policies/Auth/UserPolicy.php +++ b/app/Policies/Auth/UserPolicy.php @@ -33,7 +33,7 @@ public function viewAny(?User $user): bool * @param User|null $user * @param User $userModel * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function view(?User $user, Model $userModel): bool @@ -47,7 +47,7 @@ public function view(?User $user, Model $userModel): bool * @param User $user * @param User $userModel * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function update(User $user, Model $userModel): bool @@ -61,7 +61,7 @@ public function update(User $user, Model $userModel): bool * @param User $user * @param User $userModel * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function delete(User $user, Model $userModel): bool @@ -75,7 +75,7 @@ public function delete(User $user, Model $userModel): bool * @param User $user * @param User $userModel * @return bool - * + * * @noinspection PhpUnusedParameterInspection */ public function restore(User $user, Model $userModel): bool @@ -103,6 +103,16 @@ public function attachRole(): bool return false; } + /** + * Determine whether the user can detach any role from the user. + * + * @return bool + */ + public function detachAnyRole(): bool + { + return false; + } + /** * Determine whether the user can detach a role from the user. * @@ -133,6 +143,16 @@ public function attachPermission(): bool return false; } + /** + * Determine whether the user can detach any permission from the user. + * + * @return bool + */ + public function detachAnyPermission(): bool + { + return false; + } + /** * Determine whether the user can detach a permission from the user. * diff --git a/app/Policies/BasePolicy.php b/app/Policies/BasePolicy.php index 425f8be57..e0e06c5ee 100644 --- a/app/Policies/BasePolicy.php +++ b/app/Policies/BasePolicy.php @@ -15,6 +15,9 @@ /** * Class BasePolicy. + * + * Filament and API will read any attach{model}, attachAny{model}, detach{model}, detachAny{model} + * to make the validation for pivots. {model} must be the name of the model. */ abstract class BasePolicy { diff --git a/app/Policies/List/External/ExternalEntryPolicy.php b/app/Policies/List/External/ExternalEntryPolicy.php index e128a3f56..f6b9b4fd7 100644 --- a/app/Policies/List/External/ExternalEntryPolicy.php +++ b/app/Policies/List/External/ExternalEntryPolicy.php @@ -5,6 +5,7 @@ namespace App\Policies\List\External; use App\Enums\Auth\CrudPermission; +use App\Enums\Auth\Role; use App\Enums\Models\List\ExternalProfileVisibility; use App\Models\Auth\User; use App\Models\BaseModel; @@ -28,7 +29,7 @@ class ExternalEntryPolicy extends BasePolicy public function viewAny(?User $user): bool { if (Filament::isServing()) { - return $user !== null && $user->hasRole('Admin'); + return $user !== null && $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ @@ -51,7 +52,7 @@ public function viewAny(?User $user): bool public function view(?User $user, BaseModel|Model $entry): bool { if (Filament::isServing()) { - return $user !== null && $user->hasRole('Admin'); + return $user !== null && $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ @@ -71,7 +72,7 @@ public function view(?User $user, BaseModel|Model $entry): bool public function create(User $user): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ @@ -92,7 +93,7 @@ public function create(User $user): bool public function update(User $user, BaseModel|Model $entry): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ @@ -113,7 +114,7 @@ public function update(User $user, BaseModel|Model $entry): bool public function delete(User $user, BaseModel|Model $entry): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ @@ -134,7 +135,7 @@ public function delete(User $user, BaseModel|Model $entry): bool public function restore(User $user, BaseModel|Model $entry): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } /** @var ExternalProfile|null $profile */ diff --git a/app/Policies/List/ExternalProfilePolicy.php b/app/Policies/List/ExternalProfilePolicy.php index 92a51a011..f556a3e7b 100644 --- a/app/Policies/List/ExternalProfilePolicy.php +++ b/app/Policies/List/ExternalProfilePolicy.php @@ -6,6 +6,7 @@ use App\Enums\Auth\CrudPermission; use App\Enums\Auth\ExtendedCrudPermission; +use App\Enums\Auth\Role; use App\Enums\Models\List\ExternalProfileVisibility; use App\Models\Auth\User; use App\Models\BaseModel; @@ -28,7 +29,7 @@ class ExternalProfilePolicy extends BasePolicy public function viewAny(?User $user): bool { if (Filament::isServing()) { - return $user !== null && $user->hasRole('Admin'); + return $user !== null && $user->hasRole(Role::ADMIN->value); } return $user === null || $user->can(CrudPermission::VIEW->format(ExternalProfile::class)); @@ -44,7 +45,7 @@ public function viewAny(?User $user): bool public function view(?User $user, BaseModel|Model $profile): bool { if (Filament::isServing()) { - return $user !== null && $user->hasRole('Admin'); + return $user !== null && $user->hasRole(Role::ADMIN->value); } return $user !== null @@ -61,7 +62,7 @@ public function view(?User $user, BaseModel|Model $profile): bool public function create(User $user): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } return $user->can(CrudPermission::CREATE->format(ExternalProfile::class)); @@ -77,7 +78,7 @@ public function create(User $user): bool public function update(User $user, BaseModel|Model $profile): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } return !$profile->trashed() && $user->getKey() === $profile->user_id && $user->can(CrudPermission::UPDATE->format(ExternalProfile::class)); @@ -93,7 +94,7 @@ public function update(User $user, BaseModel|Model $profile): bool public function delete(User $user, BaseModel|Model $profile): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } return !$profile->trashed() && $user->getKey() === $profile->user_id && $user->can(CrudPermission::DELETE->format(ExternalProfile::class)); @@ -109,7 +110,7 @@ public function delete(User $user, BaseModel|Model $profile): bool public function restore(User $user, BaseModel|Model $profile): bool { if (Filament::isServing()) { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } return $profile->trashed() && $user->getKey() === $profile->user_id && $user->can(ExtendedCrudPermission::RESTORE->format(ExternalProfile::class)); @@ -121,8 +122,8 @@ public function restore(User $user, BaseModel|Model $profile): bool * @param User $user * @return bool */ - public function addEntry(User $user): bool + public function addExternalEntry(User $user): bool { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } } diff --git a/app/Policies/List/PlaylistPolicy.php b/app/Policies/List/PlaylistPolicy.php index 89d164f5c..654574bf3 100644 --- a/app/Policies/List/PlaylistPolicy.php +++ b/app/Policies/List/PlaylistPolicy.php @@ -124,7 +124,7 @@ public function restore(User $user, BaseModel|Model $playlist): bool * @param User $user * @return bool */ - public function addTrack(User $user): bool + public function addPlaylistTrack(User $user): bool { return $user->hasRole(RoleEnum::ADMIN->value); } @@ -150,22 +150,42 @@ public function attachAnyImage(User $user): bool */ public function attachImage(User $user, Playlist $playlist, Image $image): bool { + if ($playlist->user()->isNot($user)) { + return false; + } + $attached = PlaylistImage::query() - ->where($image->getKeyName(), $image->getKey()) - ->where($playlist->getKeyName(), $playlist->getKey()) + ->where(PlaylistImage::ATTRIBUTE_PLAYLIST, $playlist->getKey()) + ->where(PlaylistImage::ATTRIBUTE_IMAGE, $image->getKey()) ->exists(); - return !$attached && $user->hasRole(RoleEnum::ADMIN->value); + return !$attached + && $user->can(CrudPermission::CREATE->format(Playlist::class)) + && $user->can(CrudPermission::CREATE->format(Image::class)); } /** - * Determine whether the user can detach an image from the playlist. + * Determine whether the user can detach any image from the playlist. * * @param User $user * @return bool */ - public function detachImage(User $user): bool + public function detachAnyImage(User $user): bool { return $user->hasRole(RoleEnum::ADMIN->value); } + + /** + * Determine whether the user can detach an image from the playlist. + * + * @param User $user + * @param Playlist $playlist + * @return bool + */ + public function detachImage(User $user, Playlist $playlist): bool + { + return $playlist->user()->is($user) + && $user->can(CrudPermission::DELETE->format(Playlist::class)) + && $user->can(CrudPermission::DELETE->format(Image::class)); + } } diff --git a/app/Policies/Wiki/Anime/AnimeThemePolicy.php b/app/Policies/Wiki/Anime/AnimeThemePolicy.php index b20f914bd..a844a01b3 100644 --- a/app/Policies/Wiki/Anime/AnimeThemePolicy.php +++ b/app/Policies/Wiki/Anime/AnimeThemePolicy.php @@ -4,6 +4,9 @@ namespace App\Policies\Wiki\Anime; +use App\Enums\Auth\CrudPermission; +use App\Models\Auth\User; +use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; use App\Policies\BasePolicy; /** @@ -11,4 +14,14 @@ */ class AnimeThemePolicy extends BasePolicy { + /** + * Determine whether the user can add a entry to the theme. + * + * @param User $user + * @return bool + */ + public function addAnyAnimeThemeEntry(User $user): bool + { + return $user->can(CrudPermission::UPDATE->format(AnimeThemeEntry::class)); + } } diff --git a/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php b/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php index dd482e3f7..30881c21d 100644 --- a/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php +++ b/app/Policies/Wiki/Anime/Theme/AnimeThemeEntryPolicy.php @@ -24,11 +24,11 @@ class AnimeThemeEntryPolicy extends BasePolicy */ public function attachAnyVideo(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(AnimeThemeEntry::class)); + return $user->can(CrudPermission::CREATE->format(AnimeThemeEntry::class)) && $user->can(CrudPermission::CREATE->format(Video::class)); } /** - * Determine whether the user can attach a video to the entry. + * Determine whether the user can attach an entry to the video. * * @param User $user * @param AnimeThemeEntry $entry @@ -38,21 +38,23 @@ public function attachAnyVideo(User $user): bool public function attachVideo(User $user, AnimeThemeEntry $entry, Video $video): bool { $attached = AnimeThemeEntryVideo::query() - ->where($entry->getKeyName(), $entry->getKey()) - ->where($video->getKeyName(), $video->getKey()) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $entry->getKey()) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $video->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(AnimeThemeEntry::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(AnimeThemeEntry::class)) + && $user->can(CrudPermission::CREATE->format(Video::class)); } /** - * Determine whether the user can detach a video from the entry. + * Determine whether the user can detach any video from the entry. * * @param User $user * @return bool */ - public function detachVideo(User $user): bool + public function detachAnyVideo(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(AnimeThemeEntry::class)); + return $user->can(CrudPermission::DELETE->format(AnimeThemeEntry::class)) && $user->can(CrudPermission::DELETE->format(Video::class)); } } diff --git a/app/Policies/Wiki/AnimePolicy.php b/app/Policies/Wiki/AnimePolicy.php index df1a9132a..8e9ee371c 100644 --- a/app/Policies/Wiki/AnimePolicy.php +++ b/app/Policies/Wiki/AnimePolicy.php @@ -5,12 +5,17 @@ namespace App\Policies\Wiki; use App\Enums\Auth\CrudPermission; +use App\Enums\Auth\Role; use App\Models\Auth\User; use App\Models\Wiki\Anime; +use App\Models\Wiki\Anime\AnimeSynonym; +use App\Models\Wiki\Anime\AnimeTheme; +use App\Models\Wiki\ExternalResource; use App\Models\Wiki\Image; use App\Models\Wiki\Series; use App\Models\Wiki\Studio; use App\Pivots\Wiki\AnimeImage; +use App\Pivots\Wiki\AnimeResource; use App\Pivots\Wiki\AnimeSeries; use App\Pivots\Wiki\AnimeStudio; use App\Policies\BasePolicy; @@ -20,6 +25,28 @@ */ class AnimePolicy extends BasePolicy { + /** + * Determine whether the user can associate any synonym to the anime. + * + * @param User $user + * @return bool + */ + public function addAnyAnimeSynonym(User $user): bool + { + return $user->can(CrudPermission::UPDATE->format(AnimeSynonym::class)); + } + + /** + * Determine whether the user can associate any theme to the anime. + * + * @param User $user + * @return bool + */ + public function addAnyAnimeTheme(User $user): bool + { + return $user->can(CrudPermission::UPDATE->format(AnimeTheme::class)); + } + /** * Determine whether the user can attach any series to the anime. * @@ -28,7 +55,7 @@ class AnimePolicy extends BasePolicy */ public function attachAnySeries(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::CREATE->format(Anime::class)) && $user->can(CrudPermission::CREATE->format(Series::class)); } /** @@ -42,22 +69,24 @@ public function attachAnySeries(User $user): bool public function attachSeries(User $user, Anime $anime, Series $series): bool { $attached = AnimeSeries::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($series->getKeyName(), $series->getKey()) + ->where(AnimeSeries::ATTRIBUTE_ANIME, $anime->getKey()) + ->where(AnimeSeries::ATTRIBUTE_SERIES, $series->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Anime::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Anime::class)) + && $user->can(CrudPermission::CREATE->format(Series::class)); } /** - * Determine whether the user can detach a series from the anime. + * Determine whether the user can detach any series from the anime. * * @param User $user * @return bool */ - public function detachSeries(User $user): bool + public function detachAnySeries(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::DELETE->format(Anime::class)) && $user->can(CrudPermission::DELETE->format(Series::class)); } /** @@ -68,29 +97,38 @@ public function detachSeries(User $user): bool */ public function attachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::CREATE->format(Anime::class)) && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** * Determine whether the user can attach a resource to the anime. * * @param User $user + * @param Anime $anime + * @param ExternalResource $resource * @return bool */ - public function attachExternalResource(User $user): bool + public function attachExternalResource(User $user, Anime $anime, ExternalResource $resource): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + $attached = AnimeResource::query() + ->where(AnimeResource::ATTRIBUTE_ANIME, $anime->getKey()) + ->where(AnimeResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Anime::class)) + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** - * Determine whether the user can detach a resource from the anime. + * Determine whether the user can detach any resource from the anime. * * @param User $user * @return bool */ - public function detachExternalResource(User $user): bool + public function detachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::DELETE->format(Anime::class)) && $user->can(CrudPermission::DELETE->format(ExternalResource::class)); } /** @@ -101,7 +139,7 @@ public function detachExternalResource(User $user): bool */ public function attachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::CREATE->format(Anime::class)) && $user->can(CrudPermission::CREATE->format(Image::class)); } /** @@ -115,22 +153,24 @@ public function attachAnyImage(User $user): bool public function attachImage(User $user, Anime $anime, Image $image): bool { $attached = AnimeImage::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($image->getKeyName(), $image->getKey()) + ->where(AnimeImage::ATTRIBUTE_ANIME, $anime->getKey()) + ->where(AnimeImage::ATTRIBUTE_IMAGE, $image->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Anime::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Anime::class)) + && $user->can(CrudPermission::CREATE->format(Image::class)); } /** - * Determine whether the user can detach an image from the anime. + * Determine whether the user can detach any image from the anime. * * @param User $user * @return bool */ - public function detachImage(User $user): bool + public function detachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::DELETE->format(Anime::class)) && $user->can(CrudPermission::DELETE->format(Image::class)); } /** @@ -141,7 +181,7 @@ public function detachImage(User $user): bool */ public function attachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::CREATE->format(Anime::class)) && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** @@ -155,32 +195,34 @@ public function attachAnyStudio(User $user): bool public function attachStudio(User $user, Anime $anime, Studio $studio): bool { $attached = AnimeStudio::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($studio->getKeyName(), $studio->getKey()) + ->where(AnimeStudio::ATTRIBUTE_ANIME, $anime->getKey()) + ->where(AnimeStudio::ATTRIBUTE_STUDIO, $studio->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Anime::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Anime::class)) + && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** - * Determine whether the user can detach a studio from the anime. + * Determine whether the user can detach any studio from the anime. * * @param User $user * @return bool */ - public function detachStudio(User $user): bool + public function detachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Anime::class)); + return $user->can(CrudPermission::DELETE->format(Anime::class)) && $user->can(CrudPermission::DELETE->format(Studio::class)); } /** - * Determine whether the user can add a entry to the anime. + * Determine whether the user can add an entry to the anime. * * @param User $user * @return bool */ public function addEntry(User $user): bool { - return $user->hasRole('Admin'); + return $user->hasRole(Role::ADMIN->value); } } diff --git a/app/Policies/Wiki/ArtistPolicy.php b/app/Policies/Wiki/ArtistPolicy.php index b377f7e5f..9d5caf6d2 100644 --- a/app/Policies/Wiki/ArtistPolicy.php +++ b/app/Policies/Wiki/ArtistPolicy.php @@ -7,8 +7,13 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; use App\Models\Wiki\Artist; +use App\Models\Wiki\ExternalResource; use App\Models\Wiki\Image; +use App\Models\Wiki\Song; use App\Pivots\Wiki\ArtistImage; +use App\Pivots\Wiki\ArtistMember; +use App\Pivots\Wiki\ArtistResource; +use App\Pivots\Wiki\ArtistSong; use App\Policies\BasePolicy; /** @@ -17,69 +22,87 @@ class ArtistPolicy extends BasePolicy { /** - * Determine whether the user can attach any resource to the artist. + * Determine whether the user can attach any song to the artist. * * @param User $user * @return bool */ - public function attachAnyExternalResource(User $user): bool + public function attachAnySong(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::CREATE->format(Artist::class)) && $user->can(CrudPermission::CREATE->format(Song::class)); } /** - * Determine whether the user can attach a resource to the artist. + * Determine whether the user can attach any song to the artist. * * @param User $user + * @param Artist $artist + * @param Song $song * @return bool */ - public function attachExternalResource(User $user): bool + public function attachSong(User $user, Artist $artist, Song $song): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + $attached = ArtistSong::query() + ->where(ArtistSong::ATTRIBUTE_ARTIST, $artist->getKey()) + ->where(ArtistSong::ATTRIBUTE_SONG, $song->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Artist::class)) + && $user->can(CrudPermission::CREATE->format(Song::class)); } /** - * Determine whether the user can detach a resource from the artist. + * Determine whether the user can detach any song from the artist. * * @param User $user * @return bool */ - public function detachExternalResource(User $user): bool + public function detachAnySong(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::DELETE->format(Artist::class)) && $user->can(CrudPermission::DELETE->format(Song::class)); } /** - * Determine whether the user can attach any song to the artist. + * Determine whether the user can attach any resource to the artist. * * @param User $user * @return bool */ - public function attachAnySong(User $user): bool + public function attachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::CREATE->format(Artist::class)) && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** - * Determine whether the user can attach a song to the artist. + * Determine whether the user can attach a resource to the artist. * * @param User $user + * @param Artist $artist + * @param ExternalResource $resource * @return bool */ - public function attachSong(User $user): bool + public function attachExternalResource(User $user, Artist $artist, ExternalResource $resource): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + $attached = ArtistResource::query() + ->where(ArtistResource::ATTRIBUTE_ARTIST, $artist->getKey()) + ->where(ArtistResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Artist::class)) + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** - * Determine whether the user can detach a song from the artist. + * Determine whether the user can detach a resource from the artist. * * @param User $user * @return bool */ - public function detachSong(User $user): bool + public function detachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::DELETE->format(Artist::class)) && $user->can(CrudPermission::DELETE->format(ExternalResource::class)); } /** @@ -90,29 +113,41 @@ public function detachSong(User $user): bool */ public function attachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::CREATE->format(Artist::class)); } /** * Determine whether the user can attach a group/member to the artist. * * @param User $user + * @param Artist $artist + * @param Artist $artist2 * @return bool */ - public function attachArtist(User $user): bool + public function attachArtist(User $user, Artist $artist, Artist $artist2): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + if ($artist->is($artist2)) { + // An artist cannot be a member/group of themselves + return false; + } + + $attached = ArtistMember::query() + ->where(ArtistMember::ATTRIBUTE_ARTIST, $artist->getKey()) + ->where(ArtistMember::ATTRIBUTE_MEMBER, $artist2->getKey()) + ->exists(); + + return !$attached && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** - * Determine whether the user can detach a group/member from the artist. + * Determine whether the user can detach any group/member from the artist. * * @param User $user * @return bool */ - public function detachArtist(User $user): bool + public function detachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::DELETE->format(Artist::class)); } /** @@ -123,7 +158,7 @@ public function detachArtist(User $user): bool */ public function attachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::CREATE->format(Artist::class)) && $user->can(CrudPermission::CREATE->format(Image::class)); } /** @@ -137,21 +172,23 @@ public function attachAnyImage(User $user): bool public function attachImage(User $user, Artist $artist, Image $image): bool { $attached = ArtistImage::query() - ->where($artist->getKeyName(), $artist->getKey()) - ->where($image->getKeyName(), $image->getKey()) + ->where(ArtistImage::ATTRIBUTE_ARTIST, $artist->getKey()) + ->where(ArtistImage::ATTRIBUTE_IMAGE, $image->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Artist::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Artist::class)) + && $user->can(CrudPermission::CREATE->format(Image::class)); } /** - * Determine whether the user can detach an image from the artist. + * Determine whether the user can detach any image from the artist. * * @param User $user * @return bool */ - public function detachImage(User $user): bool + public function detachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Artist::class)); + return $user->can(CrudPermission::DELETE->format(Artist::class)) && $user->can(CrudPermission::DELETE->format(Image::class)); } } diff --git a/app/Policies/Wiki/AudioPolicy.php b/app/Policies/Wiki/AudioPolicy.php index 7d9c1fdb7..6a14f76ec 100644 --- a/app/Policies/Wiki/AudioPolicy.php +++ b/app/Policies/Wiki/AudioPolicy.php @@ -6,7 +6,7 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; -use App\Models\Wiki\Audio; +use App\Models\Wiki\Video; use App\Policies\BasePolicy; /** @@ -22,6 +22,6 @@ class AudioPolicy extends BasePolicy */ public function addVideo(User $user): bool { - return $user->can(CrudPermission::CREATE->format(Audio::class)); + return $user->can(CrudPermission::CREATE->format(Video::class)); } } diff --git a/app/Policies/Wiki/ExternalResourcePolicy.php b/app/Policies/Wiki/ExternalResourcePolicy.php index c86650bce..8a5f8395e 100644 --- a/app/Policies/Wiki/ExternalResourcePolicy.php +++ b/app/Policies/Wiki/ExternalResourcePolicy.php @@ -6,7 +6,15 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; +use App\Models\Wiki\Anime; +use App\Models\Wiki\Artist; use App\Models\Wiki\ExternalResource; +use App\Models\Wiki\Song; +use App\Models\Wiki\Studio; +use App\Pivots\Wiki\AnimeResource; +use App\Pivots\Wiki\ArtistResource; +use App\Pivots\Wiki\SongResource; +use App\Pivots\Wiki\StudioResource; use App\Policies\BasePolicy; /** @@ -14,6 +22,48 @@ */ class ExternalResourcePolicy extends BasePolicy { + /** + * Determine whether the user can attach any anime to the resource. + * + * @param User $user + * @return bool + */ + public function attachAnyAnime(User $user): bool + { + return $user->can(CrudPermission::CREATE->format(ExternalResource::class)) && $user->can(CrudPermission::CREATE->format(Anime::class)); + } + + /** + * Determine whether the user can attach an anime to the resource. + * + * @param User $user + * @param ExternalResource $resource + * @param Anime $anime + * @return bool + */ + public function attachAnime(User $user, ExternalResource $resource, Anime $anime): bool + { + $attached = AnimeResource::query() + ->where(AnimeResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->where(AnimeResource::ATTRIBUTE_ANIME, $anime->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)) + && $user->can(CrudPermission::CREATE->format(Anime::class)); + } + + /** + * Determine whether the user can detach any anime from the resource. + * + * @param User $user + * @return bool + */ + public function detachAnyAnime(User $user): bool + { + return $user->can(CrudPermission::DELETE->format(ExternalResource::class)) && $user->can(CrudPermission::DELETE->format(Anime::class)); + } + /** * Determine whether the user can attach any artist to the resource. * @@ -22,62 +72,80 @@ class ExternalResourcePolicy extends BasePolicy */ public function attachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::CREATE->format(ExternalResource::class)) && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** * Determine whether the user can attach an artist to the resource. * * @param User $user + * @param ExternalResource $resource + * @param Artist $artist * @return bool */ - public function attachArtist(User $user): bool + public function attachArtist(User $user, ExternalResource $resource, Artist $artist): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + $attached = ArtistResource::query() + ->where(ArtistResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->where(ArtistResource::ATTRIBUTE_ARTIST, $artist->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)) + && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** - * Determine whether the user can detach an artist from the resource. + * Determine whether the user can detach any artist from the resource. * * @param User $user * @return bool */ - public function detachArtist(User $user): bool + public function detachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::DELETE->format(ExternalResource::class)) && $user->can(CrudPermission::DELETE->format(Artist::class)); } /** - * Determine whether the user can attach any anime to the resource. + * Determine whether the user can attach any song to the resource. * * @param User $user * @return bool */ - public function attachAnyAnime(User $user): bool + public function attachAnySong(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::CREATE->format(ExternalResource::class)) && $user->can(CrudPermission::CREATE->format(Song::class)); } /** - * Determine whether the user can attach an anime to the resource. + * Determine whether the user can attach a song to the resource. * * @param User $user + * @param ExternalResource $resource + * @param Song $song * @return bool */ - public function attachAnime(User $user): bool + public function attachSong(User $user, ExternalResource $resource, Song $song): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + $attached = SongResource::query() + ->where(SongResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->where(SongResource::ATTRIBUTE_SONG, $song->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)) + && $user->can(CrudPermission::CREATE->format(Song::class)); } /** - * Determine whether the user can detach an anime from the resource. + * Determine whether the user can detach any song from the resource. * * @param User $user * @return bool */ - public function detachAnime(User $user): bool + public function detachAnySong(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::DELETE->format(ExternalResource::class)) && $user->can(CrudPermission::DELETE->format(Song::class)); } /** @@ -88,28 +156,37 @@ public function detachAnime(User $user): bool */ public function attachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::CREATE->format(ExternalResource::class)) && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** * Determine whether the user can attach a studio to the resource. * * @param User $user + * @param ExternalResource $resource + * @param Studio $studio * @return bool */ - public function attachStudio(User $user): bool + public function attachStudio(User $user, ExternalResource $resource, Studio $studio): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + $attached = StudioResource::query() + ->where(StudioResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->where(StudioResource::ATTRIBUTE_STUDIO, $studio->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)) + && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** - * Determine whether the user can detach a studio from the resource. + * Determine whether the user can detach any studio from the resource. * * @param User $user * @return bool */ - public function detachStudio(User $user): bool + public function detachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(ExternalResource::class)); + return $user->can(CrudPermission::DELETE->format(ExternalResource::class)) && $user->can(CrudPermission::DELETE->format(Studio::class)); } } diff --git a/app/Policies/Wiki/GroupPolicy.php b/app/Policies/Wiki/GroupPolicy.php index 64b7df85f..793a1c2bc 100644 --- a/app/Policies/Wiki/GroupPolicy.php +++ b/app/Policies/Wiki/GroupPolicy.php @@ -6,7 +6,7 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; -use App\Models\Wiki\Group; +use App\Models\Wiki\Anime\AnimeTheme; use App\Policies\BasePolicy; /** @@ -22,6 +22,6 @@ class GroupPolicy extends BasePolicy */ public function addAnimeTheme(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Group::class)); + return $user->can(CrudPermission::UPDATE->format(AnimeTheme::class)); } } diff --git a/app/Policies/Wiki/ImagePolicy.php b/app/Policies/Wiki/ImagePolicy.php index 4460bf8ec..b0a3096e7 100644 --- a/app/Policies/Wiki/ImagePolicy.php +++ b/app/Policies/Wiki/ImagePolicy.php @@ -7,12 +7,10 @@ use App\Enums\Auth\CrudPermission; use App\Enums\Auth\Role as RoleEnum; use App\Models\Auth\User; -use App\Models\List\Playlist; use App\Models\Wiki\Anime; use App\Models\Wiki\Artist; use App\Models\Wiki\Image; use App\Models\Wiki\Studio; -use App\Pivots\List\PlaylistImage; use App\Pivots\Wiki\AnimeImage; use App\Pivots\Wiki\ArtistImage; use App\Pivots\Wiki\StudioImage; @@ -31,7 +29,7 @@ class ImagePolicy extends BasePolicy */ public function attachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::CREATE->format(Image::class)) && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** @@ -45,22 +43,24 @@ public function attachAnyArtist(User $user): bool public function attachArtist(User $user, Image $image, Artist $artist): bool { $attached = ArtistImage::query() - ->where($artist->getKeyName(), $artist->getKey()) - ->where($image->getKeyName(), $image->getKey()) + ->where(ArtistImage::ATTRIBUTE_IMAGE, $image->getKey()) + ->where(ArtistImage::ATTRIBUTE_ARTIST, $artist->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Image::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Image::class)) + && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** - * Determine whether the user can detach an artist from the image. + * Determine whether the user can detach any artist from the image. * * @param User $user * @return bool */ - public function detachArtist(User $user): bool + public function detachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::DELETE->format(Image::class)) && $user->can(CrudPermission::DELETE->format(Artist::class)); } /** @@ -71,7 +71,7 @@ public function detachArtist(User $user): bool */ public function attachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::CREATE->format(Image::class)) && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** @@ -85,22 +85,24 @@ public function attachAnyAnime(User $user): bool public function attachAnime(User $user, Image $image, Anime $anime): bool { $attached = AnimeImage::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($image->getKeyName(), $image->getKey()) + ->where(AnimeImage::ATTRIBUTE_IMAGE, $image->getKey()) + ->where(AnimeImage::ATTRIBUTE_ANIME, $anime->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Image::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Image::class)) + && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** - * Determine whether the user can detach an anime from the image. + * Determine whether the user can detach any anime from the image. * * @param User $user * @return bool */ - public function detachAnime(User $user): bool + public function detachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::DELETE->format(Image::class)) && $user->can(CrudPermission::DELETE->format(Anime::class)); } /** @@ -111,7 +113,7 @@ public function detachAnime(User $user): bool */ public function attachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::CREATE->format(Image::class)) && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** @@ -125,22 +127,24 @@ public function attachAnyStudio(User $user): bool public function attachStudio(User $user, Image $image, Studio $studio): bool { $attached = StudioImage::query() - ->where($image->getKeyName(), $image->getKey()) - ->where($studio->getKeyName(), $studio->getKey()) + ->where(StudioImage::ATTRIBUTE_IMAGE, $image->getKey()) + ->where(StudioImage::ATTRIBUTE_STUDIO, $studio->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Image::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Image::class)) + && $user->can(CrudPermission::CREATE->format(Studio::class)); } /** - * Determine whether the user can detach a studio from the image. + * Determine whether the user can detach any studio from the image. * * @param User $user * @return bool */ - public function detachStudio(User $user): bool + public function detachAnyStudio(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Image::class)); + return $user->can(CrudPermission::DELETE->format(Image::class)) && $user->can(CrudPermission::DELETE->format(Studio::class)); } /** @@ -158,27 +162,20 @@ public function attachAnyPlaylist(User $user): bool * Determine whether the user can attach a playlist to the image. * * @param User $user - * @param Image $image - * @param Playlist $playlist * @return bool */ - public function attachPlaylist(User $user, Image $image, Playlist $playlist): bool + public function attachPlaylist(User $user): bool { - $attached = PlaylistImage::query() - ->where($image->getKeyName(), $image->getKey()) - ->where($playlist->getKeyName(), $playlist->getKey()) - ->exists(); - - return !$attached && $user->hasRole(RoleEnum::ADMIN->value); + return $user->hasRole(RoleEnum::ADMIN->value); } /** - * Determine whether the user can detach a playlist from the image. + * Determine whether the user can detach any playlist from the image. * * @param User $user * @return bool */ - public function detachPlaylist(User $user): bool + public function detachAnyPlaylist(User $user): bool { return $user->hasRole(RoleEnum::ADMIN->value); } diff --git a/app/Policies/Wiki/SeriesPolicy.php b/app/Policies/Wiki/SeriesPolicy.php index 18930f427..2212a1d47 100644 --- a/app/Policies/Wiki/SeriesPolicy.php +++ b/app/Policies/Wiki/SeriesPolicy.php @@ -24,7 +24,7 @@ class SeriesPolicy extends BasePolicy */ public function attachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Series::class)); + return $user->can(CrudPermission::CREATE->format(Series::class)) && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** @@ -38,21 +38,23 @@ public function attachAnyAnime(User $user): bool public function attachAnime(User $user, Series $series, Anime $anime): bool { $attached = AnimeSeries::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($series->getKeyName(), $series->getKey()) + ->where(AnimeSeries::ATTRIBUTE_SERIES, $series->getKey()) + ->where(AnimeSeries::ATTRIBUTE_ANIME, $anime->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Series::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Series::class)) + && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** - * Determine whether the user can detach an anime from the series. + * Determine whether the user can detach any anime from the series. * * @param User $user * @return bool */ - public function detachAnime(User $user): bool + public function detachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Series::class)); + return $user->can(CrudPermission::DELETE->format(Series::class)) && $user->can(CrudPermission::DELETE->format(Anime::class)); } } diff --git a/app/Policies/Wiki/SongPolicy.php b/app/Policies/Wiki/SongPolicy.php index 60193b400..9ccb88380 100644 --- a/app/Policies/Wiki/SongPolicy.php +++ b/app/Policies/Wiki/SongPolicy.php @@ -6,7 +6,12 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; +use App\Models\Wiki\Anime\AnimeTheme; +use App\Models\Wiki\Artist; +use App\Models\Wiki\ExternalResource; use App\Models\Wiki\Song; +use App\Pivots\Wiki\ArtistSong; +use App\Pivots\Wiki\SongResource; use App\Policies\BasePolicy; /** @@ -15,47 +20,56 @@ class SongPolicy extends BasePolicy { /** - * Determine whether the user can add a theme to the song. + * Determine whether the user can attach any artist to the song. * * @param User $user * @return bool */ - public function addAnimeTheme(User $user): bool + public function attachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + return $user->can(CrudPermission::CREATE->format(Song::class)) && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** - * Determine whether the user can attach any artist to the song. + * Determine whether the user can attach an artist to the song. * * @param User $user + * @param Song $song + * @param Artist $artist * @return bool */ - public function attachAnyArtist(User $user): bool + public function attachArtist(User $user, Song $song, Artist $artist): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + $attached = ArtistSong::query() + ->where(ArtistSong::ATTRIBUTE_SONG, $song->getKey()) + ->where(ArtistSong::ATTRIBUTE_ARTIST, $artist->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Song::class)) + && $user->can(CrudPermission::CREATE->format(Artist::class)); } /** - * Determine whether the user can attach an artist to the song. + * Determine whether the user can detach any artist from the song. * * @param User $user * @return bool */ - public function attachArtist(User $user): bool + public function detachAnyArtist(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + return $user->can(CrudPermission::DELETE->format(Song::class)) && $user->can(CrudPermission::DELETE->format(Artist::class)); } /** - * Determine whether the user can detach an artist from the song. + * Determine whether the user can add a theme to the song. * * @param User $user * @return bool */ - public function detachArtist(User $user): bool + public function addAnimeTheme(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + return $user->can(CrudPermission::UPDATE->format(AnimeTheme::class)); } /** @@ -66,28 +80,37 @@ public function detachArtist(User $user): bool */ public function attachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + return $user->can(CrudPermission::CREATE->format(Song::class)) && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** * Determine whether the user can attach a resource to the song. * * @param User $user + * @param Song $song + * @param ExternalResource $resource * @return bool */ - public function attachExternalResource(User $user): bool + public function attachExternalResource(User $user, Song $song, ExternalResource $resource): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + $attached = SongResource::query() + ->where(SongResource::ATTRIBUTE_SONG, $song->getKey()) + ->where(SongResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Song::class)) + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** - * Determine whether the user can detach a resource from the song. + * Determine whether the user can detach any resource from the song. * * @param User $user * @return bool */ - public function detachExternalResource(User $user): bool + public function detachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Song::class)); + return $user->can(CrudPermission::DELETE->format(Song::class)) && $user->can(CrudPermission::DELETE->format(ExternalResource::class)); } } diff --git a/app/Policies/Wiki/StudioPolicy.php b/app/Policies/Wiki/StudioPolicy.php index 4642bf98b..a4da7672b 100644 --- a/app/Policies/Wiki/StudioPolicy.php +++ b/app/Policies/Wiki/StudioPolicy.php @@ -7,10 +7,12 @@ use App\Enums\Auth\CrudPermission; use App\Models\Auth\User; use App\Models\Wiki\Anime; +use App\Models\Wiki\ExternalResource; use App\Models\Wiki\Image; use App\Models\Wiki\Studio; use App\Pivots\Wiki\AnimeStudio; use App\Pivots\Wiki\StudioImage; +use App\Pivots\Wiki\StudioResource; use App\Policies\BasePolicy; /** @@ -26,7 +28,7 @@ class StudioPolicy extends BasePolicy */ public function attachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::CREATE->format(Studio::class)) && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** @@ -40,22 +42,24 @@ public function attachAnyAnime(User $user): bool public function attachAnime(User $user, Studio $studio, Anime $anime): bool { $attached = AnimeStudio::query() - ->where($anime->getKeyName(), $anime->getKey()) - ->where($studio->getKeyName(), $studio->getKey()) + ->where(AnimeStudio::ATTRIBUTE_STUDIO, $studio->getKey()) + ->where(AnimeStudio::ATTRIBUTE_ANIME, $anime->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Studio::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Studio::class)) + && $user->can(CrudPermission::CREATE->format(Anime::class)); } /** - * Determine whether the user can detach an anime from the studio. + * Determine whether the user can detach any anime from the studio. * * @param User $user * @return bool */ - public function detachAnime(User $user): bool + public function detachAnyAnime(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::DELETE->format(Studio::class)) && $user->can(CrudPermission::DELETE->format(Anime::class)); } /** @@ -66,29 +70,38 @@ public function detachAnime(User $user): bool */ public function attachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::CREATE->format(Studio::class)) && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** * Determine whether the user can attach a resource to the studio. * * @param User $user + * @param Studio $studio + * @param ExternalResource $resource * @return bool */ - public function attachExternalResource(User $user): bool + public function attachExternalResource(User $user, Studio $studio, ExternalResource $resource): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + $attached = StudioResource::query() + ->where(StudioResource::ATTRIBUTE_STUDIO, $studio->getKey()) + ->where(StudioResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Studio::class)) + && $user->can(CrudPermission::CREATE->format(ExternalResource::class)); } /** - * Determine whether the user can detach a resource from the studio. + * Determine whether the user can detach any resource from the studio. * * @param User $user * @return bool */ - public function detachExternalResource(User $user): bool + public function detachAnyExternalResource(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::DELETE->format(Studio::class)) && $user->can(CrudPermission::DELETE->format(ExternalResource::class)); } /** @@ -99,7 +112,7 @@ public function detachExternalResource(User $user): bool */ public function attachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::CREATE->format(Studio::class)) && $user->can(CrudPermission::CREATE->format(Image::class)); } /** @@ -113,21 +126,23 @@ public function attachAnyImage(User $user): bool public function attachImage(User $user, Studio $studio, Image $image): bool { $attached = StudioImage::query() - ->where($studio->getKeyName(), $studio->getKey()) - ->where($image->getKeyName(), $image->getKey()) + ->where(StudioImage::ATTRIBUTE_STUDIO, $studio->getKey()) + ->where(StudioImage::ATTRIBUTE_IMAGE, $image->getKey()) ->exists(); - return !$attached && $user->can(CrudPermission::UPDATE->format(Studio::class)); + return !$attached + && $user->can(CrudPermission::CREATE->format(Studio::class)) + && $user->can(CrudPermission::CREATE->format(Image::class)); } /** - * Determine whether the user can detach an image from the studio. + * Determine whether the user can detach any image from the studio. * * @param User $user * @return bool */ - public function detachImage(User $user): bool + public function detachAnyImage(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Studio::class)); + return $user->can(CrudPermission::DELETE->format(Studio::class)) && $user->can(CrudPermission::DELETE->format(Image::class)); } } diff --git a/app/Policies/Wiki/VideoPolicy.php b/app/Policies/Wiki/VideoPolicy.php index b0f8c808d..c42a6b683 100644 --- a/app/Policies/Wiki/VideoPolicy.php +++ b/app/Policies/Wiki/VideoPolicy.php @@ -7,7 +7,9 @@ use App\Enums\Auth\CrudPermission; use App\Enums\Auth\Role as RoleEnum; use App\Models\Auth\User; +use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; use App\Models\Wiki\Video; +use App\Pivots\Wiki\AnimeThemeEntryVideo; use App\Policies\BasePolicy; /** @@ -23,29 +25,38 @@ class VideoPolicy extends BasePolicy */ public function attachAnyAnimeThemeEntry(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Video::class)); + return $user->can(CrudPermission::CREATE->format(Video::class)) && $user->can(CrudPermission::CREATE->format(AnimeThemeEntry::class)); } /** - * Determine whether the user can attach an entry to a video. + * Determine whether the user can attach an entry to the video. * * @param User $user + * @param Video $video + * @param AnimeThemeEntry $entry * @return bool */ - public function attachAnimeThemeEntry(User $user): bool + public function attachAnimeThemeEntry(User $user, Video $video, AnimeThemeEntry $entry): bool { - return $user->can(CrudPermission::UPDATE->format(Video::class)); + $attached = AnimeThemeEntryVideo::query() + ->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $video->getKey()) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $entry->getKey()) + ->exists(); + + return !$attached + && $user->can(CrudPermission::CREATE->format(Video::class)) + && $user->can(CrudPermission::CREATE->format(AnimeThemeEntry::class)); } /** - * Determine whether the user can detach an entry from a video. + * Determine whether the user can detach any entry from a video. * * @param User $user * @return bool */ - public function detachAnimeThemeEntry(User $user): bool + public function detachAnyAnimeThemeEntry(User $user): bool { - return $user->can(CrudPermission::UPDATE->format(Video::class)); + return $user->can(CrudPermission::DELETE->format(Video::class)) && $user->can(CrudPermission::DELETE->format(AnimeThemeEntry::class)); } /** diff --git a/routes/api.php b/routes/api.php index 56536100d..dba0cfeb3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -160,7 +160,7 @@ function apiResourceUri(string $ability, string $name): string ->append('}'); } - return $uri->toString(); + return $uri->__toString(); } } @@ -218,7 +218,7 @@ function apiPivotResourceUri(string $name, string $related, string $foreign): st ->append('}/{') ->append($foreign) ->append('}') - ->toString(); + ->__toString(); } } diff --git a/tests/Feature/Http/Api/Pivot/List/PlaylistImage/PlaylistImageStoreTest.php b/tests/Feature/Http/Api/Pivot/List/PlaylistImage/PlaylistImageStoreTest.php index aad343c78..a8bbfb155 100644 --- a/tests/Feature/Http/Api/Pivot/List/PlaylistImage/PlaylistImageStoreTest.php +++ b/tests/Feature/Http/Api/Pivot/List/PlaylistImage/PlaylistImageStoreTest.php @@ -107,7 +107,6 @@ public function testCreate(): void Feature::activate(AllowPlaylistManagement::class); - $playlist = Playlist::factory()->createOne(); $image = Image::factory()->createOne(); $user = User::factory() @@ -117,6 +116,10 @@ public function testCreate(): void ) ->createOne(); + $playlist = Playlist::factory() + ->for($user) + ->createOne(); + Sanctum::actingAs($user); $response = $this->post(route('api.playlistimage.store', ['playlist' => $playlist, 'image' => $image])); @@ -137,7 +140,6 @@ public function testCreatePermittedForBypass(): void Feature::activate(AllowPlaylistManagement::class, $this->faker->boolean()); - $playlist = Playlist::factory()->createOne(); $image = Image::factory()->createOne(); $user = User::factory() @@ -148,6 +150,10 @@ public function testCreatePermittedForBypass(): void ) ->createOne(); + $playlist = Playlist::factory() + ->for($user) + ->createOne(); + Sanctum::actingAs($user); $response = $this->post(route('api.playlistimage.store', ['playlist' => $playlist, 'image' => $image]));