mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat(graphql): add middleware to mutations (#1120)
This commit is contained in:
@@ -24,7 +24,7 @@ class ManageSongPerformances
|
||||
/** @var Collection<int, array{alias: string|null, as: string|null}> */
|
||||
protected Collection $groups;
|
||||
|
||||
/** @var Collection<int, array<string, mixed>> */
|
||||
/** @var Collection<int, non-empty-array<string, mixed>> */
|
||||
protected Collection $performances;
|
||||
|
||||
/** @var Collection<int, array<string, mixed>> */
|
||||
@@ -99,7 +99,10 @@ class ManageSongPerformances
|
||||
DB::beginTransaction();
|
||||
|
||||
$this->performances = $this->performances->map(
|
||||
fn (array $performance) => $performance + [$performance[Performance::ATTRIBUTE_SONG] = $this->song->getKey()]
|
||||
fn (array $performance): array => [
|
||||
...$performance,
|
||||
Performance::ATTRIBUTE_SONG => $this->song->getKey(),
|
||||
]
|
||||
);
|
||||
|
||||
// Multiple queries because upsert does not dispatch an event.
|
||||
@@ -121,7 +124,7 @@ class ManageSongPerformances
|
||||
Performance::ATTRIBUTE_ARTIST_ID,
|
||||
$this->performances->filter(fn (array $performance): bool => $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class))
|
||||
->map(fn (array $performanceMembership) => Arr::get($performanceMembership, Performance::ATTRIBUTE_ARTIST_ID))
|
||||
->all(),
|
||||
->all(),
|
||||
);
|
||||
|
||||
$membershipPerformances->each(fn (Performance $performance) => $performance->delete());
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Filament\Actions\BaseAction;
|
||||
use App\Models\Admin\Dump;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class DownloadDumpAction extends BaseAction
|
||||
{
|
||||
@@ -29,6 +30,6 @@ class DownloadDumpAction extends BaseAction
|
||||
|
||||
$this->authorize('view');
|
||||
|
||||
$this->action(fn (Dump $record) => new DownloadAction($record)->download());
|
||||
$this->action(fn (Dump $record): StreamedResponse => new DownloadAction($record)->download());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class DumpResource extends BaseResource
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, \Filament\Actions\Action|\Filament\Actions\ActionGroup>
|
||||
* @return array<int, \Filament\Actions\Action|ActionGroup>
|
||||
*/
|
||||
public static function getRecordActions(): array
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\Error\Error as GraphQLError;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Server\RequestError;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Contracts\Debug\ExceptionHandler;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
@@ -92,6 +93,11 @@ class ErrorHandler
|
||||
$error['extensions']['validation'] = $previous->getValidatorMessages()->getMessages();
|
||||
}
|
||||
|
||||
if ($previous instanceof AuthorizationException) {
|
||||
$error['message'] = $previous->getMessage();
|
||||
$error['extensions']['category'] = 'authorization';
|
||||
}
|
||||
|
||||
if ($previous instanceof ProvidesErrorCategory) {
|
||||
$error['extensions']['category'] = $previous->getCategory();
|
||||
}
|
||||
|
||||
@@ -4,31 +4,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Resolvers;
|
||||
|
||||
use App\Actions\Http\Api\DestroyAction;
|
||||
use App\Actions\Http\Api\StoreAction;
|
||||
use App\Actions\Http\Api\UpdateAction;
|
||||
use App\GraphQL\Schema\Mutations\BaseMutation;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Routing\ControllerMiddlewareOptions;
|
||||
use Illuminate\Routing\FiltersControllerMiddleware;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* @template TModel of \Illuminate\Database\Eloquent\Model
|
||||
*/
|
||||
abstract class BaseResolver
|
||||
{
|
||||
final public const MODEL = 'model';
|
||||
|
||||
/**
|
||||
* @param StoreAction<TModel> $storeAction
|
||||
* @param UpdateAction<TModel> $updateAction
|
||||
* @param DestroyAction<TModel> $destroyAction
|
||||
* HTTP middleware stack that should be run before the resolver action.
|
||||
*
|
||||
* Each element is an array with keys `middleware` and `options`. The
|
||||
* `options` array is the same structure that the `ControllerMiddlewareOptions`
|
||||
* helper understands (see `only` / `except`). This mirrors the behaviour of
|
||||
* Laravel controllers so you can register middleware exactly the same way:
|
||||
*
|
||||
* $this->middleware('auth')->only('store');
|
||||
* $this->middleware(['auth', 'log'])->except('destroy');
|
||||
*
|
||||
* The resolver will evaluate the options against the current action name
|
||||
* when deciding which middleware to run.
|
||||
*
|
||||
* @var array<int, array{middleware:class-string|string,options:array}>
|
||||
*/
|
||||
public function __construct(
|
||||
protected StoreAction $storeAction,
|
||||
protected UpdateAction $updateAction,
|
||||
protected DestroyAction $destroyAction,
|
||||
) {}
|
||||
protected array $middleware = [];
|
||||
|
||||
/**
|
||||
* Get the attributes and values that were validated.
|
||||
@@ -50,4 +54,50 @@ abstract class BaseResolver
|
||||
'model' => Arr::get($args, self::MODEL),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the middleware pipeline for the given action.
|
||||
*/
|
||||
protected function runMiddleware(): void
|
||||
{
|
||||
$action = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
|
||||
|
||||
$stack = [];
|
||||
foreach ($this->middleware as $entry) {
|
||||
$options = $entry['options'] ?? [];
|
||||
|
||||
if (FiltersControllerMiddleware::methodExcludedByOptions($action, $options)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stack[] = $entry['middleware'];
|
||||
}
|
||||
|
||||
if ($stack === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(Pipeline::class)
|
||||
->send(request())
|
||||
->through($stack)
|
||||
->thenReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register middleware on the controller.
|
||||
*
|
||||
* @param \Closure|array|string $middleware
|
||||
* @return ControllerMiddlewareOptions
|
||||
*/
|
||||
public function middleware($middleware, array $options = [])
|
||||
{
|
||||
foreach ((array) $middleware as $m) {
|
||||
$this->middleware[] = [
|
||||
'middleware' => $m,
|
||||
'options' => &$options,
|
||||
];
|
||||
}
|
||||
|
||||
return new ControllerMiddlewareOptions($options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,26 +7,38 @@ namespace App\GraphQL\Resolvers\List\Playlist;
|
||||
use App\Actions\Http\Api\List\Playlist\Track\DestroyTrackAction;
|
||||
use App\Actions\Http\Api\List\Playlist\Track\StoreTrackAction;
|
||||
use App\Actions\Http\Api\List\Playlist\Track\UpdateTrackAction;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\GraphQL\Resolvers\BaseResolver;
|
||||
use App\GraphQL\Schema\Mutations\Models\List\Playlist\Track\CreatePlaylistTrackMutation;
|
||||
use App\GraphQL\Schema\Mutations\Models\List\Playlist\Track\UpdatePlaylistTrackMutation;
|
||||
use App\Http\Middleware\Models\List\PlaylistExceedsTrackLimit;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
|
||||
|
||||
/**
|
||||
* @extends BaseResolver<PlaylistTrack>
|
||||
*/
|
||||
class PlaylistTrackResolver extends BaseResolver
|
||||
{
|
||||
final public const string ATTRIBUTE_ENTRY = 'entryId';
|
||||
final public const string ATTRIBUTE_VIDEO = 'videoId';
|
||||
|
||||
public static Playlist $playlist;
|
||||
|
||||
public function __construct(Playlist $playlist)
|
||||
{
|
||||
static::$playlist = $playlist;
|
||||
|
||||
$this->middleware(EnsureFeaturesAreActive::using(AllowPlaylistManagement::class))->only(['store', 'update', 'destroy']);
|
||||
$this->middleware(PlaylistExceedsTrackLimit::class)->only(['store']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function store($root, array $args): PlaylistTrack
|
||||
public function store(array $args, StoreTrackAction $action): PlaylistTrack
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
$validated = $this->validated($args, CreatePlaylistTrackMutation::class);
|
||||
|
||||
$validated += [
|
||||
@@ -37,16 +49,16 @@ class PlaylistTrackResolver extends BaseResolver
|
||||
/** @var Playlist $playlist */
|
||||
$playlist = Arr::pull($validated, 'playlist');
|
||||
|
||||
$action = new StoreTrackAction();
|
||||
|
||||
return $action->store($playlist, PlaylistTrack::query(), $validated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function update($root, array $args): PlaylistTrack
|
||||
public function update(array $args, UpdateTrackAction $action): PlaylistTrack
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
$validated = $this->validated($args, UpdatePlaylistTrackMutation::class);
|
||||
|
||||
$validated += [
|
||||
@@ -57,21 +69,19 @@ class PlaylistTrackResolver extends BaseResolver
|
||||
/** @var PlaylistTrack $track */
|
||||
$track = Arr::pull($validated, self::MODEL);
|
||||
|
||||
$action = new UpdateTrackAction();
|
||||
|
||||
return $action->update($track->playlist, $track, $validated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function destroy($root, array $args): array
|
||||
public function destroy(array $args, DestroyTrackAction $action): array
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
/** @var PlaylistTrack $track */
|
||||
$track = Arr::get($args, self::MODEL);
|
||||
|
||||
$action = new DestroyTrackAction();
|
||||
|
||||
$message = $action->destroy($track->playlist, $track);
|
||||
|
||||
return [
|
||||
|
||||
@@ -4,23 +4,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Resolvers\List;
|
||||
|
||||
use App\Actions\Http\Api\DestroyAction;
|
||||
use App\Actions\Http\Api\StoreAction;
|
||||
use App\Actions\Http\Api\UpdateAction;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\GraphQL\Resolvers\BaseResolver;
|
||||
use App\GraphQL\Schema\Mutations\Models\List\Playlist\CreatePlaylistMutation;
|
||||
use App\GraphQL\Schema\Mutations\Models\List\Playlist\UpdatePlaylistMutation;
|
||||
use App\Http\Middleware\Models\List\UserExceedsPlaylistLimit;
|
||||
use App\Models\List\Playlist;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
|
||||
|
||||
/**
|
||||
* @extends BaseResolver<Playlist>
|
||||
*/
|
||||
class PlaylistResolver extends BaseResolver
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(EnsureFeaturesAreActive::using(AllowPlaylistManagement::class))->only(['store', 'update', 'destroy']);
|
||||
$this->middleware(UserExceedsPlaylistLimit::class)->only(['store']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
* @param StoreAction<Playlist> $action
|
||||
*/
|
||||
public function store($root, array $args): Playlist
|
||||
public function store(array $args, StoreAction $action): Playlist
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
$validated = $this->validated($args, CreatePlaylistMutation::class);
|
||||
|
||||
$parameters = [
|
||||
@@ -28,31 +40,37 @@ class PlaylistResolver extends BaseResolver
|
||||
Playlist::ATTRIBUTE_USER => Auth::id(),
|
||||
];
|
||||
|
||||
return $this->storeAction->store(Playlist::query(), $parameters);
|
||||
return $action->store(Playlist::query(), $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
* @param UpdateAction<Playlist> $action
|
||||
*/
|
||||
public function update($root, array $args): Playlist
|
||||
public function update(array $args, UpdateAction $action): Playlist
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
/** @var Playlist $playlist */
|
||||
$playlist = Arr::pull($args, self::MODEL);
|
||||
|
||||
$validated = $this->validated($args, UpdatePlaylistMutation::class);
|
||||
|
||||
return $this->updateAction->update($playlist, $validated);
|
||||
return $action->update($playlist, $validated);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
* @param DestroyAction<Playlist> $action
|
||||
*/
|
||||
public function destroy($root, array $args): array
|
||||
public function destroy(array $args, DestroyAction $action): array
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
/** @var Playlist $playlist */
|
||||
$playlist = Arr::get($args, self::MODEL);
|
||||
|
||||
$message = $this->destroyAction->forceDelete($playlist);
|
||||
$message = $action->forceDelete($playlist);
|
||||
|
||||
return [
|
||||
'message' => $message,
|
||||
|
||||
@@ -5,19 +5,28 @@ declare(strict_types=1);
|
||||
namespace App\GraphQL\Resolvers\List;
|
||||
|
||||
use App\Exceptions\GraphQL\ClientForbiddenException;
|
||||
use App\Features\AllowExternalProfileManagement;
|
||||
use App\GraphQL\Resolvers\BaseResolver;
|
||||
use App\Http\Middleware\Api\EnabledOnlyOnLocalhost;
|
||||
use App\Models\List\ExternalProfile;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laravel\Pennant\Middleware\EnsureFeaturesAreActive;
|
||||
|
||||
class SyncExternalProfileResolver extends BaseResolver
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(EnabledOnlyOnLocalhost::class);
|
||||
$this->middleware(EnsureFeaturesAreActive::using(AllowExternalProfileManagement::class))->only(['update']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new sync job.
|
||||
*
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function store($root, array $args): array
|
||||
public function update(array $args): array
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
/** @var ExternalProfile $profile */
|
||||
$profile = Arr::pull($args, self::MODEL);
|
||||
|
||||
|
||||
@@ -12,9 +12,6 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* @extends BaseResolver<Like>
|
||||
*/
|
||||
class ToggleLikeResolver extends BaseResolver
|
||||
{
|
||||
final public const string ATTRIBUTE_ENTRY = 'entry';
|
||||
@@ -23,8 +20,10 @@ class ToggleLikeResolver extends BaseResolver
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function store($root, array $args): ?Like
|
||||
public function store(array $args): ?Like
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
$validated = $this->validated($args, ToggleLikeMutation::class);
|
||||
|
||||
/** @var Model&Likeable $likeable */
|
||||
|
||||
@@ -4,16 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Resolvers\User;
|
||||
|
||||
use App\Actions\Http\Api\StoreAction;
|
||||
use App\GraphQL\Resolvers\BaseResolver;
|
||||
use App\GraphQL\Schema\Mutations\Models\User\WatchMutation;
|
||||
use App\Models\User\WatchHistory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
/**
|
||||
* @extends BaseResolver<WatchHistory>
|
||||
*/
|
||||
class WatchResolver extends BaseResolver
|
||||
{
|
||||
final public const string ATTRIBUTE_ENTRY = 'entryId';
|
||||
@@ -21,10 +18,12 @@ class WatchResolver extends BaseResolver
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
* @return WatchHistory
|
||||
* @param StoreAction<WatchHistory> $action
|
||||
*/
|
||||
public function store($root, array $args): Model
|
||||
public function store(array $args, StoreAction $action): WatchHistory
|
||||
{
|
||||
$this->runMiddleware();
|
||||
|
||||
$validated = $this->validated($args, WatchMutation::class);
|
||||
|
||||
$validated += [
|
||||
@@ -33,6 +32,6 @@ class WatchResolver extends BaseResolver
|
||||
WatchHistory::ATTRIBUTE_USER => Auth::id(),
|
||||
];
|
||||
|
||||
return $this->storeAction->store(WatchHistory::query(), $validated);
|
||||
return $action->store(WatchHistory::query(), $validated);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,12 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
/**
|
||||
* @extends BaseResolver<Anime>
|
||||
*/
|
||||
class AnimeYearsResolver extends BaseResolver
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function index(null $root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
public function index(array $args, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
$year = Arr::get($args, AnimeYearsQuery::ARGUMENT_YEAR);
|
||||
|
||||
@@ -64,7 +61,7 @@ class AnimeYearsResolver extends BaseResolver
|
||||
*
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function resolveSeasonField(array $root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
public function resolveSeasonField(array $root, array $args): mixed
|
||||
{
|
||||
$season = Arr::get($args, AnimeYearSeasonField::ARGUMENT_SEASON);
|
||||
$year = Arr::get($root, AnimeYearsQuery::ARGUMENT_YEAR);
|
||||
@@ -85,7 +82,7 @@ class AnimeYearsResolver extends BaseResolver
|
||||
/**
|
||||
* Resolve the AnimeYearSeasonAnimeField.
|
||||
*/
|
||||
public function resolveAnimeField(array $root, array $args, $context, ResolveInfo $resolveInfo): Paginator
|
||||
public function resolveAnimeField(array $root, array $args, ResolveInfo $resolveInfo, IndexAction $action): Paginator
|
||||
{
|
||||
$season = Arr::get($root, AnimeYearSeasonSeasonField::FIELD);
|
||||
$year = Arr::get($root, 'year');
|
||||
@@ -95,8 +92,6 @@ class AnimeYearsResolver extends BaseResolver
|
||||
->when($season !== null, fn (Builder $query) => $query->where(Anime::ATTRIBUTE_SEASON, $season->value))
|
||||
->where(Anime::ATTRIBUTE_YEAR, $year);
|
||||
|
||||
$action = new IndexAction();
|
||||
|
||||
return $action->index($builder, $args, new AnimeType(), $resolveInfo);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -77,7 +77,9 @@ class AnimeYearSeasonAnimeField extends Field implements DisplayableField
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(AnimeYearsResolver::class)
|
||||
->resolveAnimeField($root, $args, $context, $resolveInfo);
|
||||
return App::call(
|
||||
[App::make(AnimeYearsResolver::class), 'resolveAnimeField'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ class AnimeYearSeasonField extends Field implements DisplayableField
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(AnimeYearsResolver::class)
|
||||
->resolveSeasonField($root, $args, $context, $resolveInfo);
|
||||
return App::call(
|
||||
[App::make(AnimeYearsResolver::class), 'resolveSeasonField'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -62,7 +62,9 @@ class SyncExternalProfileMutation extends BaseMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(SyncExternalProfileResolver::class)
|
||||
->store($root, $args);
|
||||
return App::call(
|
||||
[App::make(SyncExternalProfileResolver::class), 'update'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ class CreatePlaylistMutation extends CreateMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistResolver::class)
|
||||
->store($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistResolver::class), 'store'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ class DeletePlaylistMutation extends DeleteMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistResolver::class)
|
||||
->destroy($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistResolver::class), 'destroy'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -9,6 +9,7 @@ use App\GraphQL\Schema\Mutations\Models\CreateMutation;
|
||||
use App\GraphQL\Schema\Types\List\Playlist\PlaylistTrackType;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class CreatePlaylistTrackMutation extends CreateMutation
|
||||
@@ -31,7 +32,9 @@ class CreatePlaylistTrackMutation extends CreateMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistTrackResolver::class)
|
||||
->store($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistTrackResolver::class, ['playlist' => Arr::get($args, 'playlist')]), 'store'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -11,6 +11,7 @@ use App\GraphQL\Schema\Types\MessageResponseType;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Rebing\GraphQL\Support\Facades\GraphQL;
|
||||
|
||||
@@ -39,7 +40,9 @@ class DeletePlaylistTrackMutation extends DeleteMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistTrackResolver::class)
|
||||
->destroy($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistTrackResolver::class, ['playlist' => Arr::get($args, 'playlist')]), 'destroy'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -9,6 +9,7 @@ use App\GraphQL\Schema\Mutations\Models\UpdateMutation;
|
||||
use App\GraphQL\Schema\Types\List\Playlist\PlaylistTrackType;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class UpdatePlaylistTrackMutation extends UpdateMutation
|
||||
@@ -31,7 +32,9 @@ class UpdatePlaylistTrackMutation extends UpdateMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistTrackResolver::class)
|
||||
->update($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistTrackResolver::class, ['playlist' => Arr::get($args, 'playlist')]), 'update'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ class UpdatePlaylistMutation extends UpdateMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(PlaylistResolver::class)
|
||||
->update($root, $args);
|
||||
return App::call(
|
||||
[App::make(PlaylistResolver::class), 'update'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,9 @@ class ToggleLikeMutation extends BaseMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(ToggleLikeResolver::class)
|
||||
->store($root, $args);
|
||||
return App::call(
|
||||
[App::make(ToggleLikeResolver::class), 'store'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ class WatchMutation extends CreateMutation
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
|
||||
{
|
||||
return App::make(WatchResolver::class)
|
||||
->store($root, $args);
|
||||
return App::call(
|
||||
[App::make(WatchResolver::class), 'store'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,9 @@ class AnimeYearsQuery extends BaseQuery
|
||||
*/
|
||||
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo)
|
||||
{
|
||||
return App::make(AnimeYearsResolver::class)
|
||||
->index($root, $args, $context, $resolveInfo);
|
||||
return App::call(
|
||||
[App::make(AnimeYearsResolver::class), 'index'],
|
||||
['root' => $root, 'args' => $args, 'context' => $context, 'resolveInfo' => $resolveInfo]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Http\Api\Field\Field;
|
||||
use App\Http\Api\Field\List\Playlist\Track\TrackEntryIdField;
|
||||
use App\Http\Api\Field\List\Playlist\Track\TrackHashidsField;
|
||||
use App\Http\Api\Field\List\Playlist\Track\TrackIdField;
|
||||
use App\Http\Api\Field\List\Playlist\Track\TrackPositionField;
|
||||
use App\Http\Api\Field\List\Playlist\Track\TrackVideoIdField;
|
||||
use App\Http\Api\Include\AllowedInclude;
|
||||
use App\Http\Api\Schema\EloquentSchema;
|
||||
@@ -55,6 +56,7 @@ class ForwardBackwardSchema extends EloquentSchema
|
||||
new TrackHashidsField($this),
|
||||
new TrackEntryIdField($this),
|
||||
new TrackVideoIdField($this),
|
||||
new TrackPositionField($this),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ class PlaylistForwardController extends BaseController
|
||||
$query->where(PlaylistTrack::ATTRIBUTE_ID, $playlist->first_id);
|
||||
};
|
||||
|
||||
$builder = ForwardPlaylistTrack::query()->treeOf($constraint);
|
||||
$builder = ForwardPlaylistTrack::query()
|
||||
->treeOf($constraint)
|
||||
->orderBy(PlaylistTrack::ATTRIBUTE_POSITION);
|
||||
|
||||
$resources = $action->index($builder, $query, $request->schema());
|
||||
|
||||
|
||||
@@ -37,9 +37,6 @@ class SyncExternalProfileController extends Controller
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new sync job.
|
||||
*/
|
||||
public function update(ExternalProfile $externalProfile): JsonResponse
|
||||
{
|
||||
$externalProfile->dispatchSyncJob();
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\Http\Middleware\Models\List;
|
||||
|
||||
use App\Constants\Config\PlaylistConstants;
|
||||
use App\Enums\Auth\SpecialPermission;
|
||||
use App\GraphQL\Resolvers\List\Playlist\PlaylistTrackResolver;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use Closure;
|
||||
@@ -22,10 +23,10 @@ class PlaylistExceedsTrackLimit
|
||||
$trackLimit = intval(Config::get(PlaylistConstants::MAX_TRACKS_QUALIFIED));
|
||||
|
||||
/** @var Playlist|null $playlist */
|
||||
$playlist = $request->route('playlist');
|
||||
$playlist = $request->route('playlist') ?? PlaylistTrackResolver::$playlist;
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $request->user('sanctum');
|
||||
$user = $request->user();
|
||||
|
||||
abort_if(intval($playlist?->tracks()?->count()) >= $trackLimit
|
||||
&& $user?->cannot(SpecialPermission::BYPASS_FEATURE_FLAGS->value), 403, "Playlists cannot contain more than '$trackLimit' tracks.");
|
||||
|
||||
@@ -21,7 +21,7 @@ class UserExceedsExternalProfileLimit
|
||||
$profileLimit = intval(Config::get(ExternalProfileConstants::MAX_PROFILES_QUALIFIED));
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $request->user('sanctum');
|
||||
$user = $request->user();
|
||||
|
||||
abort_if(intval($user?->externalprofiles()?->count()) >= $profileLimit
|
||||
&& $user?->cannot(SpecialPermission::BYPASS_FEATURE_FLAGS->value), 403, "User cannot have more than '$profileLimit' external profiles.");
|
||||
|
||||
@@ -21,7 +21,7 @@ class UserExceedsPlaylistLimit
|
||||
$playlistLimit = intval(Config::get(PlaylistConstants::MAX_PLAYLISTS_QUALIFIED));
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $request->user('sanctum');
|
||||
$user = $request->user();
|
||||
|
||||
abort_if(intval($user?->playlists()?->count()) >= $playlistLimit
|
||||
&& $user?->cannot(SpecialPermission::BYPASS_FEATURE_FLAGS->value), 403, "User cannot have more than '$playlistLimit' playlists.");
|
||||
|
||||
@@ -31,11 +31,10 @@ 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\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Events\QueryExecuted;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -51,7 +50,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
DB::prohibitDestructiveCommands(app()->isProduction());
|
||||
|
||||
EnsureFeaturesAreActive::whenInactive(fn (Request $request, array $features): Response => new Response(status: 403));
|
||||
EnsureFeaturesAreActive::whenInactive(fn () => throw new AuthorizationException(code: 403));
|
||||
|
||||
ParallelTesting::setUpTestDatabase(function (string $database, int $token): void {
|
||||
Artisan::call('db:seed', ['--class' => PermissionSeeder::class]);
|
||||
|
||||
@@ -29,8 +29,24 @@ class PlaylistTrackFactory extends Factory
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the model factory.
|
||||
*/
|
||||
public function configure(): static
|
||||
{
|
||||
return $this->afterCreating(function (PlaylistTrack $track): void {
|
||||
if ($track->playlist_id === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$position = PlaylistTrack::query()
|
||||
->whereBelongsTo($track->playlist)
|
||||
->max(PlaylistTrack::ATTRIBUTE_POSITION) ?? 0;
|
||||
|
||||
$track->update([PlaylistTrack::ATTRIBUTE_POSITION => $position + 1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Database\Factories\List;
|
||||
use App\Enums\Models\List\PlaylistVisibility;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
@@ -60,7 +58,7 @@ class PlaylistFactory extends Factory
|
||||
$last = Arr::last($tracks);
|
||||
|
||||
$entryVideo = AnimeThemeEntryVideo::factory()
|
||||
->for(AnimeThemeEntry::factory()->for(AnimeTheme::factory()->for(Anime::factory())))
|
||||
->for(AnimeThemeEntry::factory())
|
||||
->for(Video::factory())
|
||||
->createOne();
|
||||
|
||||
@@ -71,10 +69,12 @@ class PlaylistFactory extends Factory
|
||||
->createOne();
|
||||
|
||||
if ($index === 1) {
|
||||
$track->moveToStart();
|
||||
$playlist->first()->associate($track)->save();
|
||||
}
|
||||
|
||||
if ($last !== null) {
|
||||
$track->moveAfter($last);
|
||||
$last->next()->associate($track);
|
||||
$last->save();
|
||||
|
||||
@@ -83,6 +83,7 @@ class PlaylistFactory extends Factory
|
||||
}
|
||||
|
||||
if ($index === $count) {
|
||||
$track->moveToEnd();
|
||||
$playlist->last()->associate($track);
|
||||
$playlist->save();
|
||||
}
|
||||
|
||||
+25
@@ -5,10 +5,12 @@ declare(strict_types=1);
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Enums\Models\List\PlaylistVisibility;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -52,7 +54,30 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::CREATE->format(Playlist::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
'name' => fake()->word(),
|
||||
'visibility' => Arr::random(PlaylistVisibility::cases())->name,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
it('creates', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
+29
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -49,7 +51,32 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::DELETE->format(Playlist::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
// Needed for the bind resolver.
|
||||
'id' => Playlist::factory()->createOne()->hashid,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if not owner', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
@@ -70,6 +97,8 @@ test('forbidden if not owner', function () {
|
||||
});
|
||||
|
||||
it('deletes', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
+31
@@ -5,12 +5,14 @@ declare(strict_types=1);
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Events\List\Playlist\Track\TrackCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -57,7 +59,34 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::CREATE->format(PlaylistTrack::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
// Needed for the bind resolver.
|
||||
'playlist' => Playlist::factory()->createOne()->hashid,
|
||||
'entryId' => AnimeThemeEntry::factory()->createOne()->getKey(),
|
||||
'videoId' => Video::factory()->createOne()->getKey(),
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
it('fails if no entry video link', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
@@ -89,6 +118,8 @@ it('fails if no entry video link', function () {
|
||||
});
|
||||
|
||||
it('creates', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
+33
@@ -5,10 +5,12 @@ declare(strict_types=1);
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Events\List\Playlist\Track\TrackCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -55,7 +57,36 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::DELETE->format(PlaylistTrack::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$track = PlaylistTrack::factory()
|
||||
->for(Playlist::factory()->for($user))
|
||||
->createOne();
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
'playlist' => $track->playlist->hashid,
|
||||
'id' => $track->hashid,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if not owner', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
@@ -81,6 +112,8 @@ test('forbidden if not owner', function () {
|
||||
});
|
||||
|
||||
it('deletes', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
+33
@@ -5,12 +5,14 @@ declare(strict_types=1);
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Events\List\Playlist\Track\TrackCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use App\Models\List\Playlist\PlaylistTrack;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Video;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -62,7 +64,36 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::UPDATE->format(PlaylistTrack::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$track = PlaylistTrack::factory()
|
||||
->for(Playlist::factory()->for($user))
|
||||
->createOne();
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
'playlist' => $track->playlist->hashid,
|
||||
'id' => $track->hashid,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if not owner', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
@@ -88,6 +119,8 @@ test('forbidden if not owner', function () {
|
||||
});
|
||||
|
||||
it('updates', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept([PlaylistCreated::class, TrackCreated::class]);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
+29
@@ -4,9 +4,11 @@ declare(strict_types=1);
|
||||
|
||||
use App\Enums\Auth\CrudPermission;
|
||||
use App\Events\List\Playlist\PlaylistCreated;
|
||||
use App\Features\AllowPlaylistManagement;
|
||||
use App\Models\Auth\User;
|
||||
use App\Models\List\Playlist;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Laravel\Pennant\Feature;
|
||||
|
||||
use function Pest\Laravel\actingAs;
|
||||
|
||||
@@ -51,7 +53,32 @@ test('forbidden', function () {
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if feature flag is disabled', function () {
|
||||
Feature::deactivate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
->withPermissions(CrudPermission::DELETE->format(Playlist::class))
|
||||
->createOne();
|
||||
|
||||
actingAs($user);
|
||||
|
||||
$response = graphql([
|
||||
'query' => $this->mutation,
|
||||
'variables' => [
|
||||
// Needed for the bind resolver.
|
||||
'id' => Playlist::factory()->createOne()->hashid,
|
||||
],
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertJsonPath('errors.0.extensions.category', 'authorization');
|
||||
});
|
||||
|
||||
test('forbidden if not owner', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
@@ -72,6 +99,8 @@ test('forbidden if not owner', function () {
|
||||
});
|
||||
|
||||
it('updates', function () {
|
||||
Feature::activate(AllowPlaylistManagement::class);
|
||||
|
||||
Event::fakeExcept(PlaylistCreated::class);
|
||||
|
||||
$user = User::factory()
|
||||
|
||||
@@ -197,7 +197,7 @@ test('allowed include paths', function () {
|
||||
|
||||
$response = get(route('api.playlist.forward', ['playlist' => $playlist] + $parameters));
|
||||
|
||||
$tracks = PlaylistTrack::with($includedPaths->all())->get();
|
||||
$tracks = $playlist->tracks()->with($includedPaths->all())->get();
|
||||
|
||||
$response->assertJson(
|
||||
json_decode(
|
||||
|
||||
Reference in New Issue
Block a user