feat(graphql): eloquent singular queries (#889)

This commit is contained in:
Kyrch
2025-07-25 23:08:05 -03:00
committed by GitHub
parent c8266936e5
commit 8065d567f3
47 changed files with 418 additions and 214 deletions
+33 -34
View File
@@ -24,7 +24,7 @@ trait ResolvesArguments
*
* @param Argument[] $arguments
*/
public function buildArguments(array $arguments): string
protected function buildArguments(array $arguments): string
{
if (blank($arguments)) {
return '';
@@ -44,7 +44,7 @@ trait ResolvesArguments
* @param Field[] $fields
* @return Argument[]
*/
public function resolveFilterArguments(array $fields): array
protected function resolveFilterArguments(array $fields): array
{
return collect($fields)
->filter(fn (Field $field) => $field instanceof FilterableField)
@@ -63,7 +63,7 @@ trait ResolvesArguments
* @param Field[] $fields
* @return Argument[]
*/
public function resolveSortArguments(array $fields): array
protected function resolveSortArguments(array $fields): array
{
$columns = collect($fields)
->filter(fn (Field $field) => $field instanceof SortableField)
@@ -75,16 +75,12 @@ trait ResolvesArguments
->toArray();
return [
new Argument(
'sort',
'[SortInput!]',
false,
[
new Argument('sort', '[SortInput!]')
->directives([
'sortCustom' => [
'columns' => json_encode($columns),
],
],
),
]),
];
}
@@ -94,15 +90,17 @@ trait ResolvesArguments
* @param Field[] $fields
* @return Argument[]
*/
public function resolveCreateMutationArguments(array $fields): array
protected function resolveCreateMutationArguments(array $fields): array
{
return collect($fields)
->filter(fn (Field $field) => $field instanceof CreatableField)
->map(fn (Field $field) => new Argument(
$field->getColumn(),
$field->type(),
$field instanceof RequiredOnCreation,
))
->map(
fn (Field $field) => new Argument(
$field->getColumn(),
$field->type()
)
->required($field instanceof RequiredOnCreation)
)
->flatten()
->toArray();
}
@@ -113,15 +111,17 @@ trait ResolvesArguments
* @param Field[] $fields
* @return Argument[]
*/
public function resolveUpdateMutationArguments(array $fields): array
protected function resolveUpdateMutationArguments(array $fields): array
{
return collect($fields)
->filter(fn (Field $field) => $field instanceof UpdatableField)
->map(fn (Field $field) => new Argument(
$field->getColumn(),
$field->type(),
$field instanceof RequiredOnUpdate,
))
->map(
fn (Field $field) => new Argument(
$field->getColumn(),
$field->type()
)
->required($field instanceof RequiredOnUpdate)
)
->flatten()
->toArray();
}
@@ -132,21 +132,20 @@ trait ResolvesArguments
* @param Field[] $fields
* @return Argument[]
*/
public function resolveBindArguments(array $fields, bool $shouldRequire = true): array
protected function resolveBindArguments(array $fields, bool $shouldRequire = true): array
{
return collect($fields)
->filter(fn (Field $field) => $field instanceof BindableField)
->map(fn (Field&BindableField $field) => new Argument(
$field->getName(),
$field->type(),
$shouldRequire,
[
'bind' => [
'class' => $field->bindTo(),
'column' => $field->bindUsingColumn(),
],
],
))
->map(
fn (Field&BindableField $field) => new Argument($field->getName(), $field->type())
->required($shouldRequire)
->directives([
'bind' => [
'class' => $field->bindTo(),
'column' => $field->bindUsingColumn(),
],
])
)
->toArray();
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ trait ResolvesDirectives
*
* @param array<string, array<string, mixed>> $directives
*/
public function resolveDirectives(array $directives): string
protected function resolveDirectives(array $directives): string
{
return collect($directives)
->map(function ($args, $directive) {
+3 -3
View File
@@ -4,8 +4,8 @@ declare(strict_types=1);
namespace App\Enums\GraphQL;
enum SortDirection: int
enum SortDirection: string
{
case ASC = 0;
case DESC = 1;
case ASC = 'asc';
case DESC = 'desc';
}
@@ -35,9 +35,10 @@ class FeaturedThemeBuilder
*/
public function current(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder->whereNotNull(FeaturedTheme::ATTRIBUTE_START_AT)
->whereNotNull(FeaturedTheme::ATTRIBUTE_END_AT)
->whereDate(FeaturedTheme::ATTRIBUTE_START_AT, ComparisonOperator::LTE->value, Date::now())
->whereDate(FeaturedTheme::ATTRIBUTE_END_AT, ComparisonOperator::GT->value, Date::now());
return $builder
->whereValueBetween(Date::now(), [
FeaturedTheme::ATTRIBUTE_START_AT,
FeaturedTheme::ATTRIBUTE_END_AT,
]);
}
}
@@ -17,7 +17,7 @@ class PlaylistTrackBuilder
* Apply the query builder to the index query.
*
* @param Builder<PlaylistTrack> $builder
* @param array $args
* @param array<string, mixed> $args
* @return Builder<PlaylistTrack>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
@@ -29,4 +29,25 @@ class PlaylistTrackBuilder
return $builder;
}
/**
* Apply the query builder to the index query.
*
* @param Builder<PlaylistTrack> $builder
* @param array<string, mixed> $args
* @return Builder<PlaylistTrack>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, 'playlist');
/** @var PlaylistTrack $track */
$track = Arr::get($args, 'id');
$builder->where(PlaylistTrack::ATTRIBUTE_PLAYLIST, $playlist->getKey());
$builder->whereKey($track->getKey());
return $builder;
}
}
@@ -8,8 +8,8 @@ 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\GraphQL\Controllers\BaseController;
use App\GraphQL\Definition\Mutations\Rest\List\Playlist\Track\CreatePlaylistTrackMutation;
use App\GraphQL\Definition\Mutations\Rest\List\Playlist\Track\UpdatePlaylistTrackMutation;
use App\GraphQL\Definition\Mutations\Models\List\Playlist\Track\CreatePlaylistTrackMutation;
use App\GraphQL\Definition\Mutations\Models\List\Playlist\Track\UpdatePlaylistTrackMutation;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use Illuminate\Http\JsonResponse;
@@ -27,10 +27,10 @@ class PlaylistTrackController extends BaseController
/**
* Store a newly created resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function store($_, array $args): PlaylistTrack
public function store($root, array $args): PlaylistTrack
{
$validated = $this->validated($args, CreatePlaylistTrackMutation::class);
@@ -47,10 +47,10 @@ class PlaylistTrackController extends BaseController
/**
* Update the specified resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function update($_, array $args): PlaylistTrack
public function update($root, array $args): PlaylistTrack
{
$validated = $this->validated($args, UpdatePlaylistTrackMutation::class);
@@ -67,10 +67,10 @@ class PlaylistTrackController extends BaseController
/**
* Remove the specified resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function destroy($_, array $args): JsonResponse
public function destroy($root, array $args): JsonResponse
{
/** @var PlaylistTrack $track */
$track = Arr::get($args, self::ROUTE_SLUG);
@@ -5,8 +5,8 @@ declare(strict_types=1);
namespace App\GraphQL\Controllers\List;
use App\GraphQL\Controllers\BaseController;
use App\GraphQL\Definition\Mutations\Rest\List\Playlist\CreatePlaylistMutation;
use App\GraphQL\Definition\Mutations\Rest\List\Playlist\UpdatePlaylistMutation;
use App\GraphQL\Definition\Mutations\Models\List\Playlist\CreatePlaylistMutation;
use App\GraphQL\Definition\Mutations\Models\List\Playlist\UpdatePlaylistMutation;
use App\Models\List\Playlist;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
@@ -24,10 +24,10 @@ class PlaylistController extends BaseController
/**
* Store a newly created resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function store($_, array $args): Playlist
public function store($root, array $args): Playlist
{
$validated = $this->validated($args, CreatePlaylistMutation::class);
@@ -44,10 +44,10 @@ class PlaylistController extends BaseController
/**
* Update the specified resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function update($_, array $args): Playlist
public function update($root, array $args): Playlist
{
/** @var Playlist $playlist */
$playlist = Arr::pull($args, self::ROUTE_SLUG);
@@ -62,10 +62,10 @@ class PlaylistController extends BaseController
/**
* Remove the specified resource.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function destroy($_, array $args): JsonResponse
public function destroy($root, array $args): JsonResponse
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, self::ROUTE_SLUG);
@@ -16,10 +16,10 @@ class SyncExternalProfileController extends BaseController
/**
* Start a new sync job.
*
* @param null $_
* @param null $root
* @param array $args
*/
public function store($_, array $args): JsonResponse
public function store($root, array $args): JsonResponse
{
/** @var ExternalProfile $profile */
$profile = Arr::pull($args, self::ROUTE_SLUG);
@@ -30,7 +30,7 @@ class SyncExternalProfileController extends BaseController
], 403);
}
$profile->startSyncJob();
$profile->dispatchSyncJob();
return new JsonResponse([
'message' => 'Job dispatched.',
+11 -11
View File
@@ -4,13 +4,13 @@ declare(strict_types=1);
namespace App\GraphQL\Controllers\User;
use App\Exceptions\GraphQL\ClientValidationException;
use App\GraphQL\Controllers\BaseController;
use App\GraphQL\Definition\Mutations\User\LikeMutation;
use App\GraphQL\Definition\Mutations\User\UnlikeMutation;
use App\GraphQL\Definition\Mutations\Models\User\LikeMutation;
use App\GraphQL\Definition\Mutations\Models\User\UnlikeMutation;
use App\Models\List\Playlist;
use App\Models\User\Like;
use App\Models\Wiki\Video;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
@@ -27,12 +27,12 @@ class LikeController extends BaseController
/**
* Store a newly created resource.
*
* @param null $_
* @param null $root
* @param array $args
*
* @throws Exception
* @throws ClientValidationException
*/
public function store($_, array $args): Model
public function store($root, array $args): Model
{
$validated = $this->validated($args, LikeMutation::class);
@@ -51,18 +51,18 @@ class LikeController extends BaseController
return $video;
}
throw new Exception('None models detected to like.');
throw new ClientValidationException('One resource is required to like.');
}
/**
* Remove the specified resource.
*
* @param null $_
* @param null $root
* @param array $args
*
* @throws Exception
* @throws ClientValidationException
*/
public function destroy($_, array $args): Model
public function destroy($root, array $args): Model
{
$validated = $this->validated($args, UnlikeMutation::class);
@@ -81,6 +81,6 @@ class LikeController extends BaseController
return $video;
}
throw new Exception('None models detected to unlike.');
throw new ClientValidationException('One resource is required to unlike.');
}
}
+29 -5
View File
@@ -9,24 +9,48 @@ use GraphQL\Type\Definition\Type;
use Illuminate\Support\Str;
use Stringable;
readonly class Argument implements Stringable
class Argument implements Stringable
{
use ResolvesDirectives;
protected bool $required = false;
/**
* @param array<string, array> $directives
* @var array<string, array>
*/
protected array $directives = [];
public function __construct(
public string $name,
public Type|string $returnType,
public bool $required,
public array $directives = [],
) {}
/**
* Mark the argument as required.
*/
public function required(bool $condition = true): static
{
$this->required = $condition;
return $this;
}
/**
* Set the directives of the argument.
*
* @param array<string, array> $directives
*/
public function directives(array $directives = []): static
{
$this->directives = $directives;
return $this;
}
/**
* Get the resolved directives as a string.
*/
public function getResolvedDirectives(): string
protected function getResolvedDirectives(): string
{
return $this->resolveDirectives($this->directives);
}
@@ -13,15 +13,11 @@ class EqFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName(),
$this->type,
false,
[
return new Argument($this->field->getName(), $this->type)
->directives([
'eq' => [
'key' => $this->field->getColumn(),
],
],
);
], );
}
}
@@ -14,16 +14,12 @@ class GreaterFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName().'_greater',
$this->type,
false,
[
return new Argument($this->field->getName().'_greater', $this->type)
->directives([
'where' => [
'operator' => ComparisonOperator::GT->value,
'key' => $this->field->getColumn(),
],
],
);
]);
}
}
@@ -14,15 +14,11 @@ class InFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName().'_in',
Type::listOf($this->type),
false,
[
return new Argument($this->field->getName().'_in', Type::listOf($this->type))
->directives([
'in' => [
'key' => $this->field->getColumn(),
],
],
);
]);
}
}
@@ -14,16 +14,12 @@ class LesserFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName().'_lesser',
$this->type,
false,
[
return new Argument($this->field->getName().'_lesser', $this->type)
->directives([
'where' => [
'operator' => ComparisonOperator::LT->value,
'key' => $this->field->getColumn(),
],
],
);
]);
}
}
@@ -14,16 +14,12 @@ class LikeFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName().'_like',
$this->type,
false,
[
return new Argument($this->field->getName().'_like', $this->type)
->directives([
'where' => [
'operator' => ComparisonOperator::LIKE->value,
'key' => $this->field->getColumn(),
],
],
);
]);
}
}
@@ -14,15 +14,11 @@ class NotInFilterDirective extends FilterDirective
*/
public function argument(): Argument
{
return new Argument(
$this->field->getName().'_not_in',
Type::listOf($this->type),
false,
[
return new Argument($this->field->getName().'_not_in', Type::listOf($this->type))
->directives([
'notIn' => [
'key' => $this->field->getColumn(),
],
],
);
]);
}
}
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest;
namespace App\GraphQL\Definition\Mutations\Models;
use App\Contracts\GraphQL\Fields\BindableField;
use App\Contracts\GraphQL\Fields\CreatableField;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest;
namespace App\GraphQL\Definition\Mutations\Models;
use App\Contracts\GraphQL\HasFields;
use App\GraphQL\Definition\Argument\Argument;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\List;
namespace App\GraphQL\Definition\Mutations\Models\List\ExternalProfile;
use App\Contracts\GraphQL\Fields\BindableField;
use App\Features\AllowExternalProfileManagement;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\PlaylistController;
use App\GraphQL\Definition\Mutations\Rest\CreateMutation;
use App\GraphQL\Definition\Mutations\Models\CreateMutation;
use App\GraphQL\Definition\Types\List\PlaylistType;
use App\Models\List\Playlist;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\PlaylistController;
use App\GraphQL\Definition\Mutations\Rest\DeleteMutation;
use App\GraphQL\Definition\Mutations\Models\DeleteMutation;
use App\GraphQL\Definition\Types\List\PlaylistType;
use App\GraphQL\Definition\Types\MessageResponseType;
use App\Models\List\Playlist;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist\Track;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist\Track;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\Playlist\PlaylistTrackController;
use App\GraphQL\Definition\Mutations\Rest\CreateMutation;
use App\GraphQL\Definition\Mutations\Models\CreateMutation;
use App\GraphQL\Definition\Types\List\Playlist\PlaylistTrackType;
use App\Models\List\Playlist\PlaylistTrack;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist\Track;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist\Track;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\Playlist\PlaylistTrackController;
use App\GraphQL\Definition\Mutations\Rest\DeleteMutation;
use App\GraphQL\Definition\Mutations\Models\DeleteMutation;
use App\GraphQL\Definition\Types\List\Playlist\PlaylistTrackType;
use App\GraphQL\Definition\Types\MessageResponseType;
use App\Models\List\Playlist\PlaylistTrack;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist\Track;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist\Track;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\Playlist\PlaylistTrackController;
use App\GraphQL\Definition\Mutations\Rest\UpdateMutation;
use App\GraphQL\Definition\Mutations\Models\UpdateMutation;
use App\GraphQL\Definition\Types\List\Playlist\PlaylistTrackType;
use App\Models\List\Playlist\PlaylistTrack;
@@ -2,11 +2,11 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest\List\Playlist;
namespace App\GraphQL\Definition\Mutations\Models\List\Playlist;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\List\PlaylistController;
use App\GraphQL\Definition\Mutations\Rest\UpdateMutation;
use App\GraphQL\Definition\Mutations\Models\UpdateMutation;
use App\GraphQL\Definition\Types\List\PlaylistType;
use App\Models\List\Playlist;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\Rest;
namespace App\GraphQL\Definition\Mutations\Models;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\Contracts\GraphQL\HasFields;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\User;
namespace App\GraphQL\Definition\Mutations\Models\User;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
@@ -2,7 +2,7 @@
declare(strict_types=1);
namespace App\GraphQL\Definition\Mutations\User;
namespace App\GraphQL\Definition\Mutations\Models\User;
use App\Contracts\GraphQL\Fields\DeletableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
+1 -1
View File
@@ -81,7 +81,7 @@ abstract class BaseQuery
$baseType = $this->baseType();
if ($this->resolveSearchAttribute()) {
$arguments[] = new Argument('search', Type::string(), false, ['search' => []]);
$arguments[] = new Argument('search', Type::string())->directives(['search' => []]);
}
if ($baseType instanceof BaseType && $baseType instanceof HasFields) {
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Definition\Queries;
use App\Contracts\GraphQL\HasFields;
use App\GraphQL\Definition\Argument\Argument;
use App\GraphQL\Definition\Fields\Base\DeletedAtField;
use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Definition\Types\EloquentType;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
abstract class EloquentSingularQuery extends BaseQuery
{
public function __construct(
protected string $name,
) {
parent::__construct($name, nullable: true, isList: false);
}
/**
* The directives of the type.
*
* @return array<string, array>
*/
public function directives(): array
{
return [
...parent::directives(),
...($this->isTrashable() ? ['softDeletes' => []] : []),
...$this->canModelDirective(),
];
}
/**
* Build the canModel directive for authorization.
*
* @return array
*/
protected function canModelDirective(): array
{
return [
'canModel' => [
'ability' => 'view',
'injectArgs' => 'true',
'model' => $this->model(),
],
];
}
/**
* The arguments of the type.
*
* @return Argument[]
*/
public function arguments(): array
{
$arguments = [];
$baseType = $this->baseType();
if ($baseType instanceof BaseType && $baseType instanceof HasFields) {
$arguments[] = $this->resolveBindArguments($baseType->fields());
}
return Arr::flatten($arguments);
}
/**
* Get the model related to the query.
*
* @return class-string<Model>
*
* @throws Exception
*/
public function model(): string
{
$baseType = $this->baseType();
if ($baseType instanceof EloquentType) {
return $baseType->model();
}
throw new Exception('The base return type must be an instance of EloquentType, '.get_class($baseType).' given.');
}
/**
* Determine if the return model is trashable.
*/
protected function isTrashable(): bool
{
$baseType = $this->baseType();
if ($baseType instanceof EloquentType && $baseType instanceof HasFields) {
return in_array(new DeletedAtField(), $baseType->fields());
}
return false;
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Definition\Queries\List\Playlist;
use App\GraphQL\Attributes\Resolvers\UseBuilderDirective;
use App\GraphQL\Attributes\Resolvers\UseFindDirective;
use App\GraphQL\Builders\List\Playlist\PlaylistTrackBuilder;
use App\GraphQL\Definition\Queries\EloquentSingularQuery;
use App\GraphQL\Definition\Types\List\Playlist\PlaylistTrackType;
#[UseBuilderDirective(PlaylistTrackBuilder::class, 'show')]
#[UseFindDirective]
class PlaylistTrackQuery extends EloquentSingularQuery
{
public function __construct()
{
parent::__construct('playlisttrack');
}
/**
* The description of the type.
*/
public function description(): string
{
return 'Returns a playlist track resource.';
}
/**
* The base return type of the query.
*/
public function baseType(): PlaylistTrackType
{
return new PlaylistTrackType();
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Definition\Queries\List\Playlist;
use App\GraphQL\Attributes\Resolvers\UseBuilderDirective;
use App\GraphQL\Attributes\Resolvers\UsePaginateDirective;
use App\GraphQL\Builders\List\Playlist\PlaylistTrackBuilder;
use App\GraphQL\Definition\Argument\Argument;
use App\GraphQL\Definition\Fields\List\Playlist\PlaylistTrack\PlaylistTrackPlaylistField;
use App\GraphQL\Definition\Queries\EloquentQuery;
use App\GraphQL\Definition\Types\List\Playlist\PlaylistTrackType;
#[UseBuilderDirective(PlaylistTrackBuilder::class)]
#[UsePaginateDirective]
class PlaylistTracksQuery extends EloquentQuery
{
public function __construct()
{
parent::__construct('playlisttracks');
}
/**
* The description of the type.
*/
public function description(): string
{
return 'Returns a listing of tracks for the playlist.';
}
/**
* The arguments of the type.
*
* @return Argument[]
*/
public function arguments(): array
{
return [
...parent::arguments(),
...($this->resolveBindArguments([new PlaylistTrackPlaylistField()])),
];
}
/**
* The base return type of the query.
*/
public function baseType(): PlaylistTrackType
{
return new PlaylistTrackType();
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Definition\Queries\List;
use App\GraphQL\Attributes\Resolvers\UseFindDirective;
use App\GraphQL\Definition\Queries\EloquentSingularQuery;
use App\GraphQL\Definition\Types\List\PlaylistType;
#[UseFindDirective]
class PlaylistQuery extends EloquentSingularQuery
{
public function __construct()
{
parent::__construct('playlist');
}
/**
* The description of the type.
*/
public function description(): string
{
return 'Returns a playlist resource.';
}
/**
* The base return type of the query.
*/
public function baseType(): PlaylistType
{
return new PlaylistType();
}
}
@@ -54,7 +54,8 @@ class AnimeYearQuery extends BaseQuery
public function arguments(): array
{
return [
new Argument('year', Type::int(), true),
new Argument('year', Type::int())
->required(),
];
}
@@ -62,9 +62,12 @@ class FindAnimesByExternalSiteQuery extends BaseQuery
public function arguments(): array
{
return [
new Argument(self::ATTRIBUTE_SITE, app(TypeRegistry::class)->get(class_basename(ResourceSite::class)), true),
new Argument(self::ATTRIBUTE_ID, Type::int(), false),
new Argument(self::ATTRIBUTE_LINK, Type::string(), false),
new Argument(self::ATTRIBUTE_SITE, app(TypeRegistry::class)->get(class_basename(ResourceSite::class)))
->required(),
new Argument(self::ATTRIBUTE_ID, Type::int()),
new Argument(self::ATTRIBUTE_LINK, Type::string()),
];
}
@@ -8,6 +8,7 @@ use App\Concerns\GraphQL\ResolvesArguments;
use App\Concerns\GraphQL\ResolvesDirectives;
use App\Contracts\GraphQL\HasFields;
use App\Enums\GraphQL\RelationType;
use App\GraphQL\Definition\Argument\Argument;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Str;
use Stringable;
@@ -41,7 +42,7 @@ abstract class Relation implements Stringable
);
return Str::of($this->field ?? $this->relationName)
->append($this->getArguments())
->append($this->buildArguments($this->arguments()))
->append(': ')
->append($this->type()->__toString())
->append(' ')
@@ -51,8 +52,10 @@ abstract class Relation implements Stringable
/**
* Resolve the arguments of the sub-query.
*
* @return Argument[]
*/
protected function getArguments(): string
protected function arguments(): array
{
$arguments = [];
@@ -60,7 +63,7 @@ abstract class Relation implements Stringable
$arguments[] = $this->resolveFilterArguments($this->type->fields());
}
return $this->buildArguments($arguments);
return $arguments;
}
/**
@@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\GraphQL\Directives;
use App\Enums\GraphQL\SortDirection;
use App\Enums\GraphQL\SortType;
use App\Exceptions\GraphQL\ClientValidationException;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
@@ -36,6 +35,7 @@ class SortCustomDirective extends BaseDirective implements ArgBuilderDirective
/**
* @param array<string, mixed> $sortByColumns
*
* @throws ClientValidationException
* @throws InvalidArgumentException
*/
public function handleBuilder(QueryBuilder|EloquentBuilder|Relation $builder, $sortByColumns): QueryBuilder|EloquentBuilder|Relation
@@ -44,7 +44,7 @@ class SortCustomDirective extends BaseDirective implements ArgBuilderDirective
foreach ($sortByColumns as $sortByColumn) {
$column = Arr::pull($sortByColumn, 'column');
$direction = SortDirection::from(intval(Arr::pull($sortByColumn, 'direction')));
$direction = Arr::pull($sortByColumn, 'direction');
$object = collect($sortableColumns)
->first(fn (array $value) => $value['column'] === $column);
@@ -56,7 +56,7 @@ class SortCustomDirective extends BaseDirective implements ArgBuilderDirective
$sortType = SortType::from(Arr::get($object, 'sortType'));
if ($sortType === SortType::ROOT) {
$builder->orderBy($column, $direction->name);
$builder->orderBy($column, $direction);
}
if ($sortType === SortType::AGGREGATE) {
@@ -67,11 +67,11 @@ class SortCustomDirective extends BaseDirective implements ArgBuilderDirective
$builder->withAggregate([
"$relation as {$relation}_value" => function ($query) use ($direction) {
$query->orderBy('value', $direction->name);
$query->orderBy('value', $direction);
},
], 'value');
$builder->orderBy("{$relation}_value", $direction->name);
$builder->orderBy("{$relation}_value", $direction);
}
}
@@ -11,6 +11,7 @@ use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\User;
use App\Models\List\Playlist;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Log;
class PlaylistPolicy extends BasePolicy
{
@@ -23,6 +24,7 @@ class PlaylistPolicy extends BasePolicy
{
/** @var Playlist $playlist */
$playlist = Arr::get($injected, $keyName);
Log::info($injected);
if ($user !== null) {
return ($playlist->user()->is($user) || $playlist->visibility !== PlaylistVisibility::PRIVATE)
-22
View File
@@ -1,22 +0,0 @@
extend type Query {
"Returns an external entry resource."
externalentry(
profile: Int! @bind(class: "App\\Models\\List\\ExternalProfile")
id: Int! @bind(class: "App\\Models\\List\\External\\ExternalEntry")
): ExternalEntry @first
@canModel(ability: "view", injectArgs: true)
"Returns a listing of entries for the profile."
externalentries(
score_lesser: Int @where(key: "score", operator: "<")
score_greater: Int @where(key: "score", operator: ">")
watchStatus: [ExternalEntryWatchStatus] @in(key: "watch_status")
createdAt_lesser: DateTimeTz @where(key: "created_at", operator: "<")
createdAt_greater: DateTimeTz @where(key: "created_at", operator: ">")
updatedAt_lesser: DateTimeTz @where(key: "updated_at", operator: "<")
updatedAt_greater: DateTimeTz @where(key: "updated_at", operator: ">")
): [ExternalEntry!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\External\\ExternalEntryBuilder@index")
@paginate
}
@@ -1,21 +0,0 @@
extend type Query {
"Returns a playlist track resource."
playlisttrack(
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
id: String! @bind(class: "App\\Models\\List\\Playlist\\PlaylistTrack", column: "hashid")
): PlaylistTrack @first
@canModel(ability: "view", injectArgs: true)
"Returns a listing of tracks for the playlist."
playlisttracks(
createdAt_lesser: DateTimeTz @where(key: "created_at", operator: "<")
createdAt_greater: DateTimeTz @where(key: "created_at", operator: ">")
updatedAt_lesser: DateTimeTz @where(key: "updated_at", operator: "<")
updatedAt_greater: DateTimeTz @where(key: "updated_at", operator: ">")
deletedAt_lesser: DateTimeTz @where(key: "deleted_at", operator: "<")
deletedAt_greater: DateTimeTz @where(key: "deleted_at", operator: ">")
): [PlaylistTrack!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\Playlist\\PlaylistTrackBuilder@index")
@paginate
}
@@ -1,7 +0,0 @@
extend type Query {
"Returns a playlist resource."
playlist(
id: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): Playlist @first
@canModel(ability: "view", injectArgs: true)
}
+1 -5
View File
@@ -13,8 +13,4 @@ input WhereHasConditions {
where: [WhereConditions!]
AND: [WhereHasConditions!]
OR: [WhereHasConditions!]
}
#import List/*.graphql
#import List/External/*.graphql
#import List/Playlist/*.graphql
}
@@ -6,7 +6,6 @@ namespace App\Http\Controllers\Api\Admin;
use App\Actions\Http\Api\ShowAction;
use App\Contracts\Http\Api\InteractsWithSchema;
use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\Http\Api\Query\Query;
use App\Http\Api\Schema\Admin\FeaturedThemeSchema;
use App\Http\Api\Schema\Schema;
@@ -28,10 +27,10 @@ class CurrentFeaturedThemeController extends Controller implements InteractsWith
$query = new Query($request->validated());
$featuredtheme = FeaturedTheme::query()
->whereNotNull(FeaturedTheme::ATTRIBUTE_START_AT)
->whereNotNull(FeaturedTheme::ATTRIBUTE_END_AT)
->whereDate(FeaturedTheme::ATTRIBUTE_START_AT, ComparisonOperator::LTE->value, Date::now())
->whereDate(FeaturedTheme::ATTRIBUTE_END_AT, ComparisonOperator::GT->value, Date::now())
->whereValueBetween(Date::now(), [
FeaturedTheme::ATTRIBUTE_START_AT,
FeaturedTheme::ATTRIBUTE_END_AT,
])
->firstOrFail();
$show = $action->show($featuredtheme, $query, $request->schema());
@@ -46,7 +46,7 @@ class ExternalTokenCallbackController extends Controller
$profile = $action->store($validated);
$profile->startSyncJob();
$profile->dispatchSyncJob();
return Redirect::to($profile->getClientUrl());
}
@@ -50,7 +50,7 @@ class SyncExternalProfileController extends Controller
], 403);
}
$externalProfile->startSyncJob();
$externalProfile->dispatchSyncJob();
return new JsonResponse([
'message' => 'Job dispatched.',
+1 -1
View File
@@ -191,7 +191,7 @@ class ExternalProfile extends BaseModel
/**
* Dispatch the sync external profile job.
*/
public function startSyncJob(): void
public function dispatchSyncJob(): void
{
SyncExternalProfileJob::dispatch($this);
}
+1
View File
@@ -114,6 +114,7 @@
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"lint": "pint",
"test:graphql": "@php artisan lighthouse:validate-schema",
"test:types": "./vendor/bin/phpstan analyse --memory-limit=-1"
}
}