chore(graphql): migrate to rebing/graphql (#921)

This commit is contained in:
Kyrch
2025-08-15 18:21:41 -03:00
committed by GitHub
parent b4542a50c8
commit 7513cf90fe
289 changed files with 3457 additions and 4530 deletions
+5 -8
View File
@@ -179,6 +179,11 @@ FORTIFY_HOME=http://localhost
FORTIFY_PATH=
FORTIFY_URL=http://localhost
# graphql
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
GRAPHIQL_ENABLED=true
# hashids
HASHIDS_SALT_MAIN=
HASHIDS_SALT_PLAYLISTS=
@@ -198,14 +203,6 @@ IMAGE_DISK_ROOT=
JETSTREAM_PATH=
JETSTREAM_URL=http://localhost
# lighthouse
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
GRAPHIQL_ENABLED=true
LIGHTHOUSE_DEBUG=true
LIGHTHOUSE_MAX_QUERY_COMPLEXITY=100
LIGHTHOUSE_MAX_QUERY_DEPTH=10
# logging
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null
+5 -8
View File
@@ -177,6 +177,11 @@ FORTIFY_HOME=http://localhost:3000
FORTIFY_PATH=
FORTIFY_URL=http://localhost
# graphql
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
GRAPHIQL_ENABLED=true
# hashids
HASHIDS_SALT_MAIN=
HASHIDS_SALT_PLAYLISTS=
@@ -196,14 +201,6 @@ IMAGE_DISK_ROOT=
JETSTREAM_PATH=
JETSTREAM_URL=http://localhost
# lighthouse
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
GRAPHIQL_ENABLED=true
LIGHTHOUSE_DEBUG=true
LIGHTHOUSE_MAX_QUERY_COMPLEXITY=100
LIGHTHOUSE_MAX_QUERY_DEPTH=10
# logging
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null
@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Actions\GraphQL;
use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Definition\Unions\BaseUnion;
use App\GraphQL\Support\Relations\Relation;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation as RelationLaravel;
use Illuminate\Support\Arr;
trait ConstrainsEagerLoads
{
use FiltersModels;
use SortsModels;
/**
* Apply eager loads with filters and sorting.
*/
protected function constrainEagerLoads(Builder $query, ResolveInfo|array $resolveInfo, BaseType $type): void
{
$fields = $resolveInfo instanceof ResolveInfo
? Arr::get($resolveInfo->getFieldSelectionWithAliases(100), 'data.data.selectionSet')
: $resolveInfo;
$this->processEagerLoadForType($query, $fields, $type);
}
/**
* Process recursively the relations available for the given type.
*/
private function processEagerLoadForType(Builder $builder, array $fields, BaseType|BaseUnion $type): void
{
$eagerLoadRelations = [];
/** @var array<int, Relation> $relations */
$relations = collect($type->relations())
->filter(fn (Relation $relation) => Arr::has($fields, $relation->getName()))
->toArray();
foreach ($relations as $relation) {
$name = $relation->getName();
$path = $relation->getRelationName();
$relationSelection = Arr::get($fields, "{$name}.{$name}");
$relationArgs = Arr::get($relationSelection, 'args');
$relationType = $relation->getBaseType();
$eagerLoadRelations[$path] = function (RelationLaravel $relationLaravel) use ($relationSelection, $relationArgs, $relationType) {
$relationQuery = $relationLaravel->getQuery();
if ($relationLaravel instanceof MorphTo) {
$unions = Arr::get($relationSelection, 'unions');
$subTypes = collect($relationType->baseTypes())
->filter(fn (BaseType $type) => Arr::has($unions, $type->getName()))
->toArray();
$morphConstrains = [];
foreach ($subTypes as $subType) {
$subTypeSelection = Arr::get($unions, "{$subType->getName()}.selectionSet");
$morphConstrains[$subType->model()] = function (Builder $subMorphQuery) use ($subTypeSelection, $subType) {
$this->processEagerLoadForType($subMorphQuery, $subTypeSelection, $subType);
};
}
$relationLaravel->constrain($morphConstrains);
} else {
$this->filter($relationQuery, $relationArgs, $relationType);
$this->sort($relationQuery, $relationArgs, $relationType);
$child = Arr::get($relationSelection, 'selectionSet.data.data.selectionSet')
?? Arr::get($relationSelection, 'selectionSet', []);
$this->processEagerLoadForType($relationQuery, $child, $relationType);
}
};
}
$builder->with($eagerLoadRelations);
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Actions\GraphQL;
use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Definition\Unions\BaseUnion;
use App\GraphQL\Support\Filter\Filter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
trait FiltersModels
{
public function filter(Builder $builder, array $args, BaseType|BaseUnion $type): Builder
{
// union not supported yet
if ($type instanceof BaseUnion) {
return $builder;
}
$resolvers = Filter::getValueWithResolvers($type);
foreach ($args as $arg => $value) {
$valueResolver = Arr::get($resolvers, $arg);
if ($valueResolver === null) {
continue;
}
/** @var Filter $filter */
$filter = Arr::get($valueResolver, 'filter');
$filter->apply($builder, $value);
}
return $builder;
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Actions\GraphQL;
use App\Exceptions\GraphQL\ClientValidationException;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Config;
trait PaginatesModels
{
public function paginate(Builder $builder, array $args): Paginator
{
$first = Arr::get($args, 'first');
$page = Arr::get($args, 'page');
$maxCount = Config::get('graphql.pagination_values.max_count');
if ($maxCount !== null && $first > $maxCount) {
throw new ClientValidationException("Maximum first value is {$maxCount}. Got {$first}. Fetch in smaller chuncks.");
}
return $builder->paginate($first, page: $page);
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Actions\GraphQL;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
trait SearchModels
{
/**
* Search models.
*
* @param array<string, mixed> $args
*/
public function search(Builder $builder, array $args): Builder
{
$search = Arr::get($args, 'search');
if ($search !== null) {
$model = $builder->getModel();
/** @phpstan-ignore-next-line */
$keys = $model::search($search)->keys();
$builder->whereIn($model->getKeyName(), $keys);
}
return $builder;
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace App\Concerns\Actions\GraphQL;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Definition\Unions\BaseUnion;
use App\GraphQL\Support\Argument\SortArgument;
use App\GraphQL\Support\Sort\RandomSort;
use App\GraphQL\Support\Sort\Sort;
use App\GraphQL\Support\SortableColumns;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use InvalidArgumentException;
trait SortsModels
{
/**
* Apply sorts to the query builder.
*/
public function sort(Builder $builder, array $args, BaseType|BaseUnion $type): Builder
{
$sorts = Arr::get($args, SortArgument::ARGUMENT);
if (blank($sorts)) {
return $builder;
}
$resolvers = new SortableColumns($type)->getValuesWithResolver();
$this->applySort($builder, $sorts, $resolvers);
return $builder;
}
/**
* @param array<string, string> $sorts
* @param array<string, array<string, mixed>> $resolvers
*
* @throws InvalidArgumentException
*/
protected function applySort(Builder $builder, array $sorts, array $resolvers): Builder
{
foreach ($sorts as $sort) {
if ($sort === RandomSort::CASE) {
$builder->inRandomOrder();
continue;
}
$resolver = Arr::get($resolvers, Str::remove('_DESC', $sort));
$direction = Sort::resolveFromEnumCase($sort);
$column = Arr::get($resolver, SortableColumns::RESOLVER_COLUMN);
$sortType = Arr::get($resolver, SortableColumns::RESOLVER_SORT_TYPE);
if ($sortType === SortType::ROOT) {
$builder->orderBy($column, $direction);
}
if ($sortType === SortType::AGGREGATE) {
$relation = Arr::get($resolver, SortableColumns::RESOLVER_RELATION);
if ($relation === null) {
throw new InvalidArgumentException("The 'relation' argument is required for the {$column} column with aggregate sort type.");
}
$builder->withAggregate([
"$relation as {$relation}_value" => function ($query) use ($direction) {
$query->orderBy('value', $direction);
},
], 'value');
$builder->orderBy("{$relation}_value", $direction);
}
}
return $builder;
}
}
+17 -19
View File
@@ -15,30 +15,28 @@ use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Support\Argument\Argument;
use App\GraphQL\Support\Argument\BindableArgument;
use App\GraphQL\Support\Argument\SortArgument;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
use Illuminate\Support\Arr;
trait ResolvesArguments
{
use ResolvesDirectives;
/**
* Build the arguments array into string.
* Resolve the args.
*
* @param Argument[] $arguments
* @return array<string, array<string, mixed>>
*/
protected function buildArguments(array $arguments): string
public function args(): array
{
if (blank($arguments)) {
return '';
}
return collect($this->arguments())
->mapWithKeys(fn (Argument $argument) => [
$argument->name => [
'name' => $argument->name,
'type' => $argument->getType(),
$arguments = collect($arguments)
->flatten()
->map(fn (Argument $argument) => $argument->__toString())
->implode(', ');
return sprintf('(%s)', $arguments);
...(! is_null($argument->getDefaultValue()) ? ['defaultValue' => $argument->getDefaultValue()] : []),
],
])
->toArray();
}
/**
@@ -52,8 +50,8 @@ trait ResolvesArguments
return collect($fields)
->filter(fn (Field $field) => $field instanceof FilterableField)
->map(
fn (FilterableField $field) => collect($field->filterDirectives())
->map(fn (FilterDirective $directive) => $directive->argument())
fn (FilterableField $field) => collect($field->getFilters())
->map(fn (Filter $filter) => $filter->argument())
->toArray()
)
->flatten()
@@ -81,7 +79,7 @@ trait ResolvesArguments
return collect($fields)
->filter(fn (Field $field) => $field instanceof CreatableField)
->map(
fn (Field $field) => new Argument($field->getColumn(), $field->type())
fn (Field $field) => new Argument($field->getColumn(), $field->baseType())
->required($field instanceof RequiredOnCreation)
)
->flatten()
@@ -99,7 +97,7 @@ trait ResolvesArguments
return collect($fields)
->filter(fn (Field $field) => $field instanceof UpdatableField)
->map(
fn (Field $field) => new Argument($field->getColumn(), $field->type())
fn (Field $field) => new Argument($field->getColumn(), $field->baseType())
->required($field instanceof RequiredOnUpdate)
)
->flatten()
-202
View File
@@ -1,202 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Concerns\GraphQL;
use App\GraphQL\Attributes\Deprecated;
use App\GraphQL\Attributes\Resolvers\UseAllDirective;
use App\GraphQL\Attributes\Resolvers\UseAuthDirective;
use App\GraphQL\Attributes\Resolvers\UseBuilderDirective;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Attributes\Resolvers\UseFindDirective;
use App\GraphQL\Attributes\Resolvers\UseFirstDirective;
use App\GraphQL\Attributes\Resolvers\UsePaginateDirective;
use App\GraphQL\Attributes\UseSearchDirective;
use Illuminate\Support\Arr;
use ReflectionClass;
trait ResolvesAttributes
{
/**
* Resolve the all directive as an attribute.
*/
protected function resolveAllAttribute(): bool
{
$reflection = new ReflectionClass($this);
$attributes = $reflection->getAttributes(UseAllDirective::class);
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return $instance->shouldUse;
}
return false;
}
/**
* Resolve the auth directive as an attribute.
*/
protected function resolveAuthAttribute(): bool
{
$reflection = new ReflectionClass($this);
$attributes = $reflection->getAttributes(UseAuthDirective::class);
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return $instance->shouldUse;
}
return false;
}
/**
* Resolve the builder directive as an attribute.
*/
protected function resolveBuilderAttribute(): ?string
{
$reflection = new ReflectionClass($this);
$attributes = [];
while ($reflection) {
$attributes = array_merge($attributes, $reflection->getAttributes(UseBuilderDirective::class));
$reflection = $reflection->getParentClass();
}
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return sprintf('%s@%s', $instance->controllerClass, $instance->method);
}
return null;
}
/**
* Resolve the deprecated directive as an attribute.
*/
protected function resolveDeprecatedAttribute(): ?string
{
$reflection = new ReflectionClass($this);
$attributes = $reflection->getAttributes(Deprecated::class);
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return $instance->reason;
}
return null;
}
/**
* Resolve the field directive as an attribute.
*/
protected function resolveFieldAttribute(): ?string
{
$reflection = new ReflectionClass($this);
$attributes = [];
while ($reflection) {
$attributes = array_merge($attributes, $reflection->getAttributes(UseFieldDirective::class));
$reflection = $reflection->getParentClass();
}
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return sprintf('%s@%s', $instance->fieldClass, $instance->method);
}
return null;
}
/**
* Resolve the first directive as an attribute.
*/
protected function resolveFirstAttribute(): bool
{
$reflection = new ReflectionClass($this);
$attributes = [];
while ($reflection) {
$attributes = array_merge($attributes, $reflection->getAttributes(UseFirstDirective::class));
$reflection = $reflection->getParentClass();
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return $instance->shouldUse;
}
}
return false;
}
/**
* Resolve the find directive as an attribute.
*/
protected function resolveFindAttribute(): bool
{
$reflection = new ReflectionClass($this);
$attributes = $reflection->getAttributes(UseFindDirective::class);
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
return $instance->shouldUse;
}
return false;
}
/**
* Resolve the paginate directive as an attribute.
*/
protected function resolvePaginateAttribute(): array|bool
{
$reflection = new ReflectionClass($this);
$attributes = $reflection->getAttributes(UsePaginateDirective::class);
if (filled($attributes)) {
$instance = Arr::first($attributes)->newInstance();
if ($instance->shouldUse && is_string($instance->builder)) {
return [
'builder' => $instance->builder,
];
}
return $instance->shouldUse;
}
return false;
}
/**
* Resolve the search directive as an attribute.
*/
protected function resolveSearchAttribute(): bool
{
$reflection = new ReflectionClass($this);
$attributes = [];
while ($reflection) {
$attributes = array_merge($attributes, $reflection->getAttributes(UseSearchDirective::class));
$reflection = $reflection->getParentClass();
}
return filled($attributes);
}
}
@@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Concerns\GraphQL;
trait ResolvesDirectives
{
/**
* Convert the array of directives into a string format for GraphQL.
*
* @param array<string, array<string, mixed>> $directives
*/
protected static function resolveDirectives(array $directives): string
{
return collect($directives)
->map(function ($args, $directive) {
if (blank($args)) {
return sprintf('@%s', $directive);
}
$argsString = collect($args)
->map(fn ($value, $key) => sprintf('%s: %s', $key, json_encode($value)))
->implode(', ');
return sprintf('@%s(%s)', $directive, $argsString);
})
->implode(' ');
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Console\Commands\GraphQL;
use App\Console\Commands\BaseCommand;
use GraphQL\Utils\SchemaPrinter;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Support\Facades\Validator as ValidatorFacade;
use Rebing\GraphQL\Support\Facades\GraphQL;
class GraphQLPrintSchemaCommand extends BaseCommand
{
/**
* The name and signature of the console command.
*/
protected $signature = 'graphql:print-schema {schema=default}';
/**
* The console command description.
*/
protected $description = 'Print the GraphQL schema into SDL';
/**
* Execute the console command.
*/
public function handle(): int
{
$schemaName = $this->argument('schema');
$schema = GraphQL::schema($schemaName);
$sdl = SchemaPrinter::doPrint($schema);
$this->line($sdl);
return 0;
}
/**
* Get the validator for options.
*/
protected function validator(): Validator
{
return ValidatorFacade::make($this->options(), []);
}
}
@@ -9,14 +9,7 @@ use Illuminate\Database\Eloquent\Model;
interface BindableField
{
/**
* Get the model that the field should bind to.
*
* @return class-string<Model>
* The resolver to cast the model.
*/
public function bindTo(): string;
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string;
public function bindResolver(array $args): ?Model;
}
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Contracts\GraphQL\Fields;
interface DeprecatedField
{
/**
* The reason which the field is deprecated.
*/
public function deprecationReason(): string;
}
@@ -4,14 +4,14 @@ declare(strict_types=1);
namespace App\Contracts\GraphQL\Fields;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
interface FilterableField
{
/**
* The directives available for this field.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array;
public function getFilters(): array;
}
+13
View File
@@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace App\Enums\GraphQL;
enum PaginationType
{
case NONE;
case SIMPLE;
case PAGINATOR;
case CONNECTION;
}
@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace App\Exceptions\GraphQL;
use Exception;
use GraphQL\Error\ClientAware;
/**
* Thrown when query is not allowed.
*/
class ClientForbiddenException extends Exception implements ClientAware
{
public function isClientSafe(): bool
{
return true;
}
}
-15
View File
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_ALL)]
class Deprecated
{
public function __construct(
public string $reason,
) {}
}
-15
View File
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_ALL)]
class Hidden
{
public function __construct(
public bool $hidden = true,
) {}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseAllDirective
{
public function __construct(
public bool $shouldUse = true,
) {}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseAuthDirective
{
public function __construct(
public bool $shouldUse = true,
) {}
}
@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use App\GraphQL\Controllers\BaseController;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseBuilderDirective
{
/**
* @param class-string<BaseController> $controllerClass
*/
public function __construct(
public string $controllerClass,
public string $method = 'index',
) {}
}
@@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseFieldDirective
{
/**
* @param class-string $fieldClass
*/
public function __construct(
public string $fieldClass,
public string $method = '__invoke',
) {}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseFindDirective
{
public function __construct(
public bool $shouldUse = true,
) {}
}
@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseFirstDirective
{
public function __construct(
public bool $shouldUse = true,
) {}
}
@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes\Resolvers;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UsePaginateDirective
{
public function __construct(
public bool $shouldUse = true,
public ?string $builder = null,
) {}
}
@@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS)]
class UseSearchDirective {}
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Admin;
use App\GraphQL\Controllers\BaseController;
use App\Models\Admin\Announcement;
use Illuminate\Database\Eloquent\Builder;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Announcement>
*/
class AnnouncementController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<Announcement> $builder
* @param array $args
* @return Builder<Announcement>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
/** @phpstan-ignore-next-line */
return $builder->public();
}
}
@@ -1,36 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Admin;
use App\GraphQL\Controllers\BaseController;
use App\Models\Admin\FeaturedTheme;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Date;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<FeaturedTheme>
*/
class CurrentFeaturedThemeController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the show query.
*
* @param Builder<FeaturedTheme> $builder
* @param array $args
* @return Builder<FeaturedTheme>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereValueBetween(Date::now(), [
FeaturedTheme::ATTRIBUTE_START_AT,
FeaturedTheme::ATTRIBUTE_END_AT,
]);
}
}
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Admin;
use App\GraphQL\Controllers\BaseController;
use App\Models\Admin\Dump;
use Illuminate\Database\Eloquent\Builder;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Dump>
*/
class DumpController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<Dump> $builder
* @param array $args
* @return Builder<Dump>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
/** @phpstan-ignore-next-line */
return $builder->onlySafeDumps();
}
}
@@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Admin;
use App\Constants\FeatureConstants;
use App\GraphQL\Controllers\BaseController;
use App\Models\Admin\Feature;
use Illuminate\Database\Eloquent\Builder;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Feature>
*/
class FeatureController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<Feature> $builder
* @param array $args
* @return Builder<Feature>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder->where(Feature::ATTRIBUTE_SCOPE, FeatureConstants::NULL_SCOPE);
}
}
@@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Admin;
use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\GraphQL\Controllers\BaseController;
use App\Models\Admin\FeaturedTheme;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Date;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<FeaturedTheme>
*/
class FeaturedThemeController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<FeaturedTheme> $builder
* @param array $args
* @return Builder<FeaturedTheme>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder->whereNotNull(FeaturedTheme::ATTRIBUTE_START_AT)
->whereDate(FeaturedTheme::ATTRIBUTE_START_AT, ComparisonOperator::LTE->value, Date::now());
}
}
+10 -1
View File
@@ -9,6 +9,7 @@ use App\Actions\Http\Api\StoreAction;
use App\Actions\Http\Api\UpdateAction;
use App\GraphQL\Definition\Mutations\BaseMutation;
use Dotenv\Exception\ValidationException;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Validator;
@@ -19,6 +20,8 @@ use Illuminate\Support\Facades\Validator;
*/
abstract class BaseController
{
final public const MODEL = 'model';
/**
* @param StoreAction<TModel> $storeAction
* @param UpdateAction<TModel> $updateAction
@@ -43,6 +46,12 @@ abstract class BaseController
{
$mutationInstance = App::make($mutation);
return Validator::make($args, $mutationInstance->rules($args))->validated();
$validated = Validator::make($args, $mutationInstance->rulesForValidation($args))->validated();
return [
...$validated,
'model' => Arr::get($args, self::MODEL),
];
}
}
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Document;
use App\GraphQL\Controllers\BaseController;
use App\Models\Document\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Page>
*/
class PageController extends BaseController
{
final public const ROUTE_SLUG = 'slug';
/**
* Apply the query builder to the show query.
*
* @param Builder<Page> $builder
* @param array $args
* @return Builder<Page>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->where(self::ROUTE_SLUG, Arr::get($args, self::ROUTE_SLUG));
}
}
@@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\List\External;
use App\GraphQL\Controllers\BaseController;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<ExternalEntry>
*/
class ExternalEntryController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<ExternalEntry> $builder
* @param array $args
* @return Builder<ExternalEntry>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
/** @var ExternalProfile $profile */
$profile = Arr::get($args, 'profile');
$builder->where(ExternalEntry::ATTRIBUTE_PROFILE, $profile->getKey());
return $builder;
}
}
@@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\List;
use App\Enums\Models\List\ExternalProfileVisibility;
use App\GraphQL\Controllers\BaseController;
use App\Models\List\ExternalProfile;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<ExternalProfile>
*/
class ExternalProfileController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<ExternalProfile> $builder
* @param array $args
* @return Builder<ExternalProfile>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
$builder->where(ExternalProfile::ATTRIBUTE_VISIBILITY, ExternalProfileVisibility::PUBLIC->value);
if ($user = Auth::user()) {
return $builder->orWhereBelongsTo($user, ExternalProfile::RELATION_USER);
}
return $builder;
}
}
@@ -12,56 +12,18 @@ use App\GraphQL\Definition\Mutations\Models\List\Playlist\Track\CreatePlaylistTr
use App\GraphQL\Definition\Mutations\Models\List\Playlist\Track\UpdatePlaylistTrackMutation;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<PlaylistTrack>
*/
class PlaylistTrackController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<PlaylistTrack> $builder
* @param array<string, mixed> $args
* @return Builder<PlaylistTrack>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, 'playlist');
$builder->where(PlaylistTrack::ATTRIBUTE_PLAYLIST, $playlist->getKey());
return $builder;
}
/**
* Apply the query builder to the show 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
{
$builder->whereRelation(PlaylistTrack::RELATION_PLAYLIST, Playlist::ATTRIBUTE_HASHID, Arr::get($args, 'playlist'));
$builder->where(PlaylistTrack::ATTRIBUTE_HASHID, Arr::get($args, self::ROUTE_SLUG));
return $builder;
}
/**
* Store a newly created resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function store($root, array $args): PlaylistTrack
{
@@ -81,14 +43,14 @@ class PlaylistTrackController extends BaseController
* Update the specified resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function update($root, array $args): PlaylistTrack
{
$validated = $this->validated($args, UpdatePlaylistTrackMutation::class);
/** @var PlaylistTrack $track */
$track = Arr::pull($validated, self::ROUTE_SLUG);
$track = Arr::pull($validated, self::MODEL);
$action = new UpdateTrackAction();
@@ -101,19 +63,19 @@ class PlaylistTrackController extends BaseController
* Remove the specified resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function destroy($root, array $args): JsonResponse
public function destroy($root, array $args): array
{
/** @var PlaylistTrack $track */
$track = Arr::get($args, self::ROUTE_SLUG);
$track = Arr::get($args, self::MODEL);
$action = new DestroyTrackAction();
$message = $action->destroy($track->playlist, $track);
return new JsonResponse([
return [
'message' => $message,
]);
];
}
}
@@ -4,61 +4,23 @@ declare(strict_types=1);
namespace App\GraphQL\Controllers\List;
use App\Enums\Models\List\PlaylistVisibility;
use App\GraphQL\Controllers\BaseController;
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\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Playlist>
*/
class PlaylistController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Apply the query builder to the index query.
*
* @param Builder<Playlist> $builder
* @param array $args
* @return Builder<Playlist>
*/
public function index(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
$builder->where(Playlist::ATTRIBUTE_VISIBILITY, PlaylistVisibility::PUBLIC->value);
if ($user = Auth::user()) {
return $builder->orWhereBelongsTo($user, Playlist::RELATION_USER);
}
return $builder;
}
/**
* Apply the query builder to the show query.
*
* @param Builder<Playlist> $builder
* @param array $args
* @return Builder<Playlist>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereKey(Arr::get($args, self::ROUTE_SLUG)->getKey());
}
/**
* Store a newly created resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function store($root, array $args): Playlist
{
@@ -78,12 +40,12 @@ class PlaylistController extends BaseController
* Update the specified resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function update($root, array $args): Playlist
{
/** @var Playlist $playlist */
$playlist = Arr::pull($args, self::ROUTE_SLUG);
$playlist = Arr::pull($args, self::MODEL);
$validated = $this->validated($args, UpdatePlaylistMutation::class);
@@ -96,17 +58,17 @@ class PlaylistController extends BaseController
* Remove the specified resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function destroy($root, array $args): JsonResponse
public function destroy($root, array $args): array
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, self::ROUTE_SLUG);
$playlist = Arr::get($args, self::MODEL);
$message = $this->destroyAction->forceDelete($playlist);
return new JsonResponse([
return [
'message' => $message,
]);
];
}
}
@@ -4,36 +4,32 @@ declare(strict_types=1);
namespace App\GraphQL\Controllers\List;
use App\Exceptions\GraphQL\ClientForbiddenException;
use App\GraphQL\Controllers\BaseController;
use App\Models\List\ExternalProfile;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
class SyncExternalProfileController extends BaseController
{
final public const ROUTE_SLUG = 'id';
/**
* Start a new sync job.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*/
public function store($root, array $args): JsonResponse
public function store($root, array $args): array
{
/** @var ExternalProfile $profile */
$profile = Arr::pull($args, self::ROUTE_SLUG);
$profile = Arr::pull($args, self::MODEL);
if (! $profile->canBeSynced()) {
return new JsonResponse([
'error' => 'This external profile cannot be synced at the moment.',
], 403);
throw new ClientForbiddenException('This external profile cannot be synced at the moment.');
}
$profile->dispatchSyncJob();
return new JsonResponse([
return [
'message' => 'Job dispatched.',
], 201);
];
}
}
@@ -28,7 +28,7 @@ class LikeController extends BaseController
* Store a newly created resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*
* @throws ClientValidationException
*/
@@ -58,7 +58,7 @@ class LikeController extends BaseController
* Remove the specified resource.
*
* @param null $root
* @param array $args
* @param array<string, mixed> $args
*
* @throws ClientValidationException
*/
@@ -4,7 +4,9 @@ declare(strict_types=1);
namespace App\GraphQL\Controllers\Wiki\Anime;
use App\Enums\Models\Wiki\AnimeSeason;
use App\Concerns\Actions\GraphQL\FiltersModels;
use App\Concerns\Actions\GraphQL\PaginatesModels;
use App\Concerns\Actions\GraphQL\SortsModels;
use App\Exceptions\GraphQL\ClientValidationException;
use App\GraphQL\Controllers\BaseController;
use App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear\AnimeYearSeason\AnimeYearSeasonSeasonField;
@@ -12,24 +14,29 @@ use App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear\AnimeYearSeasonField;
use App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear\AnimeYearSeasonsField;
use App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear\AnimeYearYearField;
use App\GraphQL\Definition\Queries\Wiki\AnimeYearsQuery;
use App\GraphQL\Definition\Types\Wiki\AnimeType;
use App\Models\Wiki\Anime;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Anime>
*/
class AnimeYearsController extends BaseController
{
use FiltersModels;
use PaginatesModels;
use SortsModels;
/**
* Apply the builder to the animeyears query.
*
* @param array<string, mixed> $args
*/
public function index(null $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
public function index(null $root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
$year = Arr::get($args, AnimeYearsQuery::ARGUMENT_YEAR);
@@ -38,7 +45,7 @@ class AnimeYearsController extends BaseController
// Restrict 'animes' field to a unique year.
if (
($year === null || count($year) > 1)
&& (Arr::get($fieldSelection, 'season.animes') || Arr::get($fieldSelection, 'seasons.animes'))
&& (Arr::get($fieldSelection, 'season.anime') || Arr::get($fieldSelection, 'seasons.anime'))
) {
throw new ClientValidationException("Please provide a unique 'year' argument to query the animes field.");
}
@@ -69,13 +76,13 @@ class AnimeYearsController extends BaseController
}
/**
* Apply the resolver to the AnimeYearSeasonField.
* Resolve the AnimeYearSeasonField.
*
* @param array<string, mixed> $args
*/
public function applyFieldToSeasonField(array $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
public function resolveSeasonField(array $root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
$season = AnimeSeason::from(Arr::get($args, AnimeYearSeasonField::ARGUMENT_SEASON));
$season = Arr::get($args, AnimeYearSeasonField::ARGUMENT_SEASON);
$year = Arr::get($root, AnimeYearsQuery::ARGUMENT_YEAR);
$seasons = collect(Arr::get($root, 'seasons'));
@@ -92,16 +99,22 @@ class AnimeYearsController extends BaseController
}
/**
* Apply the builder for the AnimeYearSeasonAnimeField.
* Resolve the AnimeYearSeasonAnimeField.
*/
public function applyBuilderToAnimeField(array $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
public function resolveAnimeField(array $root, array $args, $context, ResolveInfo $resolveInfo): Paginator
{
$season = Arr::get($root, AnimeYearSeasonSeasonField::FIELD);
$year = Arr::get($root, 'year');
return Anime::query()
$builder = Anime::query()
// season filter applies only on the 'season' field.
->when($season !== null, fn (Builder $query) => $query->where(Anime::ATTRIBUTE_SEASON, $season->value))
->where(Anime::ATTRIBUTE_YEAR, $year);
$this->filter($builder, $args, new AnimeType());
$this->sort($builder, $args, new AnimeType());
return $this->paginate($builder, $args);
}
}
@@ -11,8 +11,6 @@ use App\Models\Wiki\Anime;
use App\Models\Wiki\ExternalResource;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Anime>
@@ -20,14 +18,15 @@ use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
class FindAnimeByExternalSiteController extends BaseController
{
/**
* Apply the query builder to the show query.
* Apply the query builder to the index query.
*
* @param Builder<Anime> $builder
* @param array $args
* @return Builder<Anime>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
public function index(mixed $root, array $args, $context, $resolveInfo): Builder
{
$builder = Anime::query();
$site = Arr::get($args, FindAnimeByExternalSiteQuery::ATTRIBUTE_SITE);
$externalId = Arr::get($args, FindAnimeByExternalSiteQuery::ATTRIBUTE_ID);
$link = Arr::get($args, FindAnimeByExternalSiteQuery::ATTRIBUTE_LINK);
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Wiki;
use App\GraphQL\Controllers\BaseController;
use App\Models\Wiki\Anime;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Anime>
*/
class AnimeController extends BaseController
{
final public const ROUTE_SLUG = 'slug';
/**
* Apply the query builder to the show query.
*
* @param Builder<Anime> $builder
* @param array $args
* @return Builder<Anime>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereKey(Arr::get($args, self::ROUTE_SLUG)->getKey());
}
}
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Wiki;
use App\GraphQL\Controllers\BaseController;
use App\Models\Wiki\Artist;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Artist>
*/
class ArtistController extends BaseController
{
final public const ROUTE_SLUG = 'slug';
/**
* Apply the query builder to the show query.
*
* @param Builder<Artist> $builder
* @param array $args
* @return Builder<Artist>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereKey(Arr::get($args, self::ROUTE_SLUG)->getKey());
}
}
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Wiki;
use App\GraphQL\Controllers\BaseController;
use App\Models\Wiki\Series;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Series>
*/
class SeriesController extends BaseController
{
final public const ROUTE_SLUG = 'slug';
/**
* Apply the query builder to the show query.
*
* @param Builder<Series> $builder
* @param array $args
* @return Builder<Series>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereKey(Arr::get($args, self::ROUTE_SLUG)->getKey());
}
}
@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Controllers\Wiki;
use App\GraphQL\Controllers\BaseController;
use App\Models\Wiki\Studio;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* @extends BaseController<Studio>
*/
class StudioController extends BaseController
{
final public const ROUTE_SLUG = 'slug';
/**
* Apply the query builder to the show query.
*
* @param Builder<Studio> $builder
* @param array $args
* @return Builder<Studio>
*/
public function show(Builder $builder, mixed $value, mixed $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Builder
{
return $builder
->whereKey(Arr::get($args, self::ROUTE_SLUG)->getKey());
}
}
@@ -6,7 +6,7 @@ namespace App\GraphQL\Definition\Fields\Auth\User\Me;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
use App\Models\Auth\User;
class MeEmailField extends StringField
@@ -25,11 +25,11 @@ class MeEmailField extends StringField
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [];
}
@@ -6,7 +6,7 @@ namespace App\GraphQL\Definition\Fields\Auth\User\Me;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Definition\Fields\DateTimeTzField;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
use App\Models\Auth\User;
class MeEmailVerifiedAtField extends DateTimeTzField
@@ -25,11 +25,11 @@ class MeEmailVerifiedAtField extends DateTimeTzField
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [];
}
@@ -6,7 +6,7 @@ namespace App\GraphQL\Definition\Fields\Auth\User\Me;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
use App\Models\Auth\User;
class MeNameField extends StringField
@@ -25,11 +25,11 @@ class MeNameField extends StringField
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [];
}
@@ -6,7 +6,7 @@ namespace App\GraphQL\Definition\Fields\Auth\User\Me;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Definition\Fields\DateTimeTzField;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\Filter;
use App\Models\Auth\User;
class MeTwoFactorConfirmedAtField extends DateTimeTzField
@@ -25,11 +25,11 @@ class MeTwoFactorConfirmedAtField extends DateTimeTzField
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [];
}
@@ -7,12 +7,11 @@ namespace App\GraphQL\Definition\Fields\Base;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Resolvers\CountAggregateResolver;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Database\Eloquent\Model;
#[UseFieldDirective(CountAggregateResolver::class)]
class CountAggregateField extends Field implements DisplayableField, SortableField
{
public function __construct(
@@ -24,25 +23,10 @@ class CountAggregateField extends Field implements DisplayableField, SortableFie
parent::__construct($column, $name, $nullable);
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
return [
'with' => [
'relation' => $this->aggregateRelation,
],
...parent::directives(),
];
}
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -70,4 +54,14 @@ class CountAggregateField extends Field implements DisplayableField, SortableFie
{
return $this->aggregateRelation;
}
/**
* Resolve the field.
*
* @param Model $root
*/
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return (int) $root->{$this->aggregateRelation}?->value;
}
}
@@ -6,6 +6,7 @@ namespace App\GraphQL\Definition\Fields\Base;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
class CountField extends Field implements DisplayableField
@@ -19,25 +20,10 @@ class CountField extends Field implements DisplayableField
parent::__construct($column, $name, $nullable);
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
return [
'count' => [
'relation' => $this->relation,
],
...parent::directives(),
];
}
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -49,4 +35,12 @@ class CountField extends Field implements DisplayableField
{
return true;
}
/**
* Resolve the field.
*/
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return $root->{$this->relation}->count();
}
}
@@ -4,12 +4,9 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields\Base;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\DateTimeTzField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Models\BaseModel;
#[UseFieldDirective(PivotResolver::class)]
class CreatedAtField extends DateTimeTzField
{
public function __construct()
@@ -5,12 +5,11 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields\Base;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Resolvers\ExistsResolver;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Database\Eloquent\Model;
#[UseFieldDirective(ExistsResolver::class)]
class ExistsField extends Field implements DisplayableField
{
public function __construct(
@@ -21,25 +20,10 @@ class ExistsField extends Field implements DisplayableField
parent::__construct($relation.'Exists', $name, $nullable);
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
return [
'with' => [
'relation' => $this->relation,
],
...parent::directives(),
];
}
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::boolean();
}
@@ -51,4 +35,14 @@ class ExistsField extends Field implements DisplayableField
{
return true;
}
/**
* Resolve the field.
*
* @param Model $root
*/
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return $root->{$this->relation}->isNotEmpty();
}
}
+3 -13
View File
@@ -27,20 +27,10 @@ class IdField extends IntField implements BindableField
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Model>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return $this->model;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return $this->column;
return null;
}
}
@@ -4,12 +4,9 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields\Base;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\DateTimeTzField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Models\BaseModel;
#[UseFieldDirective(PivotResolver::class)]
class UpdatedAtField extends DateTimeTzField
{
public function __construct()
@@ -8,8 +8,8 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use GraphQL\Type\Definition\Type;
abstract class BooleanField extends Field implements DisplayableField, FilterableField, SortableField
@@ -17,7 +17,7 @@ abstract class BooleanField extends Field implements DisplayableField, Filterabl
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::boolean();
}
@@ -31,14 +31,14 @@ abstract class BooleanField extends Field implements DisplayableField, Filterabl
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new EqFilter($this),
];
}
@@ -4,54 +4,24 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\GreaterFilterDirective;
use App\GraphQL\Support\Directives\Filters\LesserFilterDirective;
use GraphQL\Type\Definition\Type;
use Nuwave\Lighthouse\Schema\TypeRegistry;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\GreaterFilter;
use App\GraphQL\Support\Filter\LesserFilter;
abstract class DateTimeTzField extends Field implements DisplayableField, FilterableField, SortableField
abstract class DateTimeTzField extends StringField
{
/**
* The type returned by the field.
*/
public function type(): Type
{
return app(TypeRegistry::class)->get('DateTimeTz');
}
/**
* Determine if the field should be displayed to the user.
*/
public function canBeDisplayed(): bool
{
return true;
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new LesserFilterDirective($this),
new GreaterFilterDirective($this),
new EqFilter($this),
new LesserFilter($this),
new GreaterFilter($this),
];
}
/**
* The sort type of the field.
*/
public function sortType(): SortType
{
return SortType::ROOT;
}
}
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields\Document\Page;
use App\Contracts\GraphQL\Fields\BindableField;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\RequiredOnCreation;
use App\Contracts\GraphQL\Fields\UpdatableField;
@@ -12,7 +13,7 @@ use App\Models\Document\Page;
use Illuminate\Support\Arr;
use Illuminate\Validation\Rule;
class PageSlugField extends StringField implements CreatableField, RequiredOnCreation, UpdatableField
class PageSlugField extends StringField implements BindableField, CreatableField, RequiredOnCreation, UpdatableField
{
public function __construct()
{
@@ -27,6 +28,14 @@ class PageSlugField extends StringField implements CreatableField, RequiredOnCre
return 'The URL slug & route key of the resource';
}
/**
* The resolver to cast the model.
*/
public function bindResolver(array $args): null
{
return null;
}
/**
* Set the creation validation rules for the field.
*
+15 -26
View File
@@ -8,13 +8,14 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\InFilterDirective;
use App\GraphQL\Support\Directives\Filters\NotInFilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\InFilter;
use App\GraphQL\Support\Filter\NotInFilter;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Schema\TypeRegistry;
use Rebing\GraphQL\Support\Facades\GraphQL;
abstract class EnumField extends Field implements DisplayableField, FilterableField, SortableField
{
@@ -38,42 +39,30 @@ abstract class EnumField extends Field implements DisplayableField, FilterableFi
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return app(TypeRegistry::class)->get(class_basename($this->enum));
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
return [
'enumField' => [],
];
return GraphQL::type(class_basename($this->enum));
}
/**
* Resolve the field.
*/
public function resolve(mixed $root): mixed
public function resolve(mixed $root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return Arr::get($root, $this->column)?->name;
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new InFilterDirective($this),
new NotInFilterDirective($this),
new EqFilter($this),
new InFilter($this),
new NotInFilter($this),
];
}
+45 -69
View File
@@ -5,19 +5,17 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields;
use App\Concerns\GraphQL\ResolvesArguments;
use App\Concerns\GraphQL\ResolvesAttributes;
use App\Concerns\GraphQL\ResolvesDirectives;
use App\Contracts\GraphQL\Fields\HasArgumentsField;
use App\GraphQL\Definition\Types\BaseType;
use App\GraphQL\Support\Argument\Argument;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Stringable;
use Rebing\GraphQL\Support\Facades\GraphQL;
abstract class Field implements Stringable
abstract class Field
{
use ResolvesArguments;
use ResolvesAttributes;
use ResolvesDirectives;
public function __construct(
protected string $column,
@@ -53,82 +51,60 @@ abstract class Field implements Stringable
/**
* The type returned by the field.
*/
public function getType(): Type
public function type(): Type
{
$baseType = $this->baseType();
$type = $baseType instanceof BaseType
? GraphQL::type($baseType->getName())
: $baseType;
if (! $this->nullable) {
return Type::nonNull($this->type());
return Type::nonNull($type);
}
return $this->type();
return $type;
}
/**
* The arguments of the type.
*
* @return Argument[]
*/
public function arguments(): array
{
return [];
}
/**
* The args for the field.
*
* @return array<string, array<string, mixed>>
*/
public function args(): array
{
return collect($this->arguments())
->mapWithKeys(fn (Argument $argument) => [
$argument->name => [
'name' => $argument->name,
'type' => $argument->getType(),
...(! is_null($argument->getDefaultValue()) ? ['defaultValue' => $argument->getDefaultValue()] : []),
],
])
->toArray();
}
/**
* The type returned by the field.
*/
abstract public function type(): Type;
abstract public function baseType(): Type|BaseType;
/**
* Resolve the field.
*/
public function resolve($root): mixed
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return Arr::get($root, $this->column);
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
$deprecated = $this->resolveDeprecatedAttribute();
$field = $this->resolveFieldAttribute();
$paginate = $this->resolvePaginateAttribute();
return [
...(is_string($deprecated) ? ['deprecated' => ['reason' => $deprecated]] : []),
...(is_string($field) ? ['field' => ['resolver' => $field]] : []),
...(is_array($paginate) ? ['paginate' => $paginate] : []),
];
}
/**
* Get the field as a string representation.
*/
public function __toString(): string
{
$string = Str::of($this->getName());
if ($this instanceof HasArgumentsField) {
$string = $string->append($this->buildArguments($this->arguments()));
}
$string = $string->append(': ')
->append($this->getType()->__toString());
if ($this->shouldRename()) {
$string = $string->append(" @rename(attribute: {$this->column})");
}
if (filled($this->directives())) {
$string = $string->append(' '.$this->resolveDirectives($this->directives()));
}
return $string->__toString();
}
/**
* Determine if the field is different from the column.
*/
public function shouldRename(): bool
{
if (Arr::has($this->directives(), 'field')) {
return false;
}
return $this->getName() !== $this->column;
}
}
+15 -15
View File
@@ -8,12 +8,12 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\GreaterFilterDirective;
use App\GraphQL\Support\Directives\Filters\InFilterDirective;
use App\GraphQL\Support\Directives\Filters\LesserFilterDirective;
use App\GraphQL\Support\Directives\Filters\NotInFilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\GreaterFilter;
use App\GraphQL\Support\Filter\InFilter;
use App\GraphQL\Support\Filter\LesserFilter;
use App\GraphQL\Support\Filter\NotInFilter;
use GraphQL\Type\Definition\Type;
abstract class FloatField extends Field implements DisplayableField, FilterableField, SortableField
@@ -21,7 +21,7 @@ abstract class FloatField extends Field implements DisplayableField, FilterableF
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::float();
}
@@ -35,18 +35,18 @@ abstract class FloatField extends Field implements DisplayableField, FilterableF
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new InFilterDirective($this),
new NotInFilterDirective($this),
new LesserFilterDirective($this),
new GreaterFilterDirective($this),
new EqFilter($this),
new InFilter($this),
new NotInFilter($this),
new LesserFilter($this),
new GreaterFilter($this),
];
}
+15 -15
View File
@@ -8,12 +8,12 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\GreaterFilterDirective;
use App\GraphQL\Support\Directives\Filters\InFilterDirective;
use App\GraphQL\Support\Directives\Filters\LesserFilterDirective;
use App\GraphQL\Support\Directives\Filters\NotInFilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\GreaterFilter;
use App\GraphQL\Support\Filter\InFilter;
use App\GraphQL\Support\Filter\LesserFilter;
use App\GraphQL\Support\Filter\NotInFilter;
use GraphQL\Type\Definition\Type;
abstract class IntField extends Field implements DisplayableField, FilterableField, SortableField
@@ -21,7 +21,7 @@ abstract class IntField extends Field implements DisplayableField, FilterableFie
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -35,18 +35,18 @@ abstract class IntField extends Field implements DisplayableField, FilterableFie
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new InFilterDirective($this),
new NotInFilterDirective($this),
new LesserFilterDirective($this),
new GreaterFilterDirective($this),
new EqFilter($this),
new InFilter($this),
new NotInFilter($this),
new LesserFilter($this),
new GreaterFilter($this),
];
}
@@ -16,20 +16,10 @@ class ExternalProfileIdField extends IdField implements BindableField
}
/**
* Get the model that the field should bind to.
*
* @return class-string<ExternalProfile>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return ExternalProfile::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return $this->column;
return null;
}
}
@@ -24,20 +24,10 @@ class PlaylistIdField extends StringField implements BindableField
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Playlist>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return Playlist::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return Playlist::ATTRIBUTE_HASHID;
return null;
}
}
@@ -33,7 +33,7 @@ class PlaylistTrackEntryIdField extends Field implements CreatableField, Require
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -24,20 +24,10 @@ class PlaylistTrackIdField extends StringField implements BindableField
}
/**
* Get the model that the field should bind to.
*
* @return class-string<PlaylistTrack>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return PlaylistTrack::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return PlaylistTrack::ATTRIBUTE_HASHID;
return null;
}
}
@@ -33,7 +33,7 @@ class PlaylistTrackNextField extends Field implements CreatableField, UpdatableF
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
@@ -13,6 +13,7 @@ use App\GraphQL\Definition\Fields\Field;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
class PlaylistTrackPlaylistField extends Field implements BindableField, CreatableField, RequiredOnCreation, RequiredOnUpdate, UpdatableField
{
@@ -34,27 +35,19 @@ class PlaylistTrackPlaylistField extends Field implements BindableField, Creatab
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Playlist>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): Playlist
{
return Playlist::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return Playlist::ATTRIBUTE_HASHID;
return Playlist::query()
->where(Playlist::ATTRIBUTE_HASHID, Arr::get($args, $this->getName()))
->firstOrFail();
}
/**
@@ -33,7 +33,7 @@ class PlaylistTrackPreviousField extends Field implements CreatableField, Updata
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
@@ -33,7 +33,7 @@ class PlaylistTrackVideoIdField extends Field implements CreatableField, Require
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields;
use App\Contracts\GraphQL\Fields\DisplayableField;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
@@ -27,31 +28,17 @@ class LocalizedEnumField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
/**
* Get the directives of the field.
*
* @return array
*/
public function directives(): array
{
return [
'enumField' => [
'localize' => true,
],
];
}
/**
* Resolve the field.
*
* @param mixed $root
*/
public function resolve(mixed $root): mixed
public function resolve(mixed $root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return Arr::get($root, $this->column)?->localize();
}
@@ -7,9 +7,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Base;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\EloquentType;
use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Str;
use Rebing\GraphQL\Support\Facades\GraphQL;
class NodeField extends Field implements DisplayableField
{
@@ -25,14 +25,13 @@ class NodeField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
$type = Str::of(class_basename($this->nodeType))
->remove('Type')
->__toString();
// Necessary to prevent memory leak at compile time.
return new ObjectType(['name' => $type, 'fields' => []]);
return Type::nonNull(GraphQL::type($type));
}
/**
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\AnimeResource;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\AnimeResource;
#[UseFieldDirective(PivotResolver::class)]
class AnimeResourceAsField extends StringField implements CreatableField, UpdatableField
{
public function __construct()
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\ArtistImage;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\IntField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\ArtistImage;
#[UseFieldDirective(PivotResolver::class)]
class ArtistImageDepthField extends IntField implements CreatableField, UpdatableField
{
public function __construct()
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\ArtistMember;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\ArtistMember;
#[UseFieldDirective(PivotResolver::class)]
class ArtistMemberAliasField extends StringField implements CreatableField, UpdatableField
{
public function __construct()
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\ArtistMember;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\ArtistMember;
#[UseFieldDirective(PivotResolver::class)]
class ArtistMemberAsField extends StringField implements CreatableField, UpdatableField
{
public function __construct()
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\ArtistMember;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\ArtistMember;
#[UseFieldDirective(PivotResolver::class)]
class ArtistMemberNotesField extends StringField implements CreatableField, UpdatableField
{
public function __construct()
@@ -6,12 +6,9 @@ namespace App\GraphQL\Definition\Fields\Pivot\Wiki\ArtistResource;
use App\Contracts\GraphQL\Fields\CreatableField;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Definition\Fields\StringField;
use App\GraphQL\Resolvers\PivotResolver;
use App\Pivots\Wiki\ArtistResource;
#[UseFieldDirective(PivotResolver::class)]
class ArtistResourceAsField extends StringField implements CreatableField, UpdatableField
{
public function __construct()
@@ -12,13 +12,13 @@ class MessageResponseField extends Field implements DisplayableField
{
public function __construct()
{
parent::__construct('message');
parent::__construct('message', nullable: false);
}
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\AnimeType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchAnimeField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchAnimeField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new AnimeType()));
return Type::listOf(Type::nonNull(GraphQL::type(new AnimeType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\Anime\AnimeThemeType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchAnimeThemesField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchAnimeThemesField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new AnimeThemeType()));
return Type::listOf(Type::nonNull(GraphQL::type(new AnimeThemeType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\ArtistType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchArtistsField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchArtistsField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new ArtistType()));
return Type::listOf(Type::nonNull(GraphQL::type(new ArtistType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\List\PlaylistType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchPlaylistsField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchPlaylistsField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new PlaylistType()));
return Type::listOf(Type::nonNull(GraphQL::type(new PlaylistType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\SeriesType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchSeriesField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchSeriesField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new SeriesType()));
return Type::listOf(Type::nonNull(GraphQL::type(new SeriesType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\SongType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchSongsField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchSongsField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new SongType()));
return Type::listOf(Type::nonNull(GraphQL::type(new SongType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\StudioType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchStudiosField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchStudiosField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new StudioType()));
return Type::listOf(Type::nonNull(GraphQL::type(new StudioType()->getName())));
}
/**
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\VideoType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class SearchVideosField extends Field implements DisplayableField
{
@@ -27,9 +28,9 @@ class SearchVideosField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new VideoType()));
return Type::listOf(Type::nonNull(GraphQL::type(new VideoType()->getName())));
}
/**
@@ -8,9 +8,9 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\FilterableField;
use App\Contracts\GraphQL\Fields\SortableField;
use App\Enums\GraphQL\SortType;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\LikeFilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\LikeFilter;
use GraphQL\Type\Definition\Type;
abstract class StringField extends Field implements DisplayableField, FilterableField, SortableField
@@ -18,7 +18,7 @@ abstract class StringField extends Field implements DisplayableField, Filterable
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
@@ -32,15 +32,15 @@ abstract class StringField extends Field implements DisplayableField, Filterable
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new LikeFilterDirective($this),
new EqFilter($this),
new LikeFilter($this),
];
}
@@ -11,6 +11,7 @@ use App\GraphQL\Controllers\User\LikeController;
use App\GraphQL\Definition\Fields\Field;
use App\Models\List\Playlist;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class LikePlaylistField extends Field implements BindableField, CreatableField, DeletableField
@@ -31,27 +32,19 @@ class LikePlaylistField extends Field implements BindableField, CreatableField,
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::string();
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Playlist>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): Playlist
{
return Playlist::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return Playlist::ATTRIBUTE_HASHID;
return Playlist::query()
->where(Playlist::ATTRIBUTE_HASHID, Arr::get($args, $this->getName()))
->firstOrFail();
}
/**
@@ -11,6 +11,7 @@ use App\GraphQL\Controllers\User\LikeController;
use App\GraphQL\Definition\Fields\Field;
use App\Models\Wiki\Video;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class LikeVideoField extends Field implements BindableField, CreatableField, DeletableField
@@ -31,27 +32,19 @@ class LikeVideoField extends Field implements BindableField, CreatableField, Del
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Video>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): Video
{
return Video::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return Video::ATTRIBUTE_ID;
return Video::query()
->where(Video::ATTRIBUTE_ID, Arr::get($args, $this->getName()))
->firstOrFail();
}
/**
@@ -27,7 +27,7 @@ class NotificationDataField extends JsonField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): NotificationDataType
{
return new NotificationDataType();
}
@@ -29,21 +29,11 @@ class AnimeSlugField extends StringField implements BindableField, CreatableFiel
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Anime>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return Anime::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return $this->column;
return null;
}
/**
@@ -6,15 +6,17 @@ namespace App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear\AnimeYearSeason;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\HasArgumentsField;
use App\GraphQL\Attributes\Resolvers\UsePaginateDirective;
use App\GraphQL\Controllers\Wiki\Anime\AnimeYearsController;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Queries\Models\Paginator\Wiki\AnimePaginatorQuery;
use App\GraphQL\Definition\Types\Wiki\AnimeType;
use App\GraphQL\Support\Argument\Argument;
use GraphQL\Type\Definition\ResolveInfo;
use GraphQL\Type\Definition\Type;
use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Support\Facades\App;
use Rebing\GraphQL\Support\Facades\GraphQL;
#[UsePaginateDirective(true, AnimeYearsController::class.'@applyBuilderToAnimeField')]
class AnimeYearSeasonAnimeField extends Field implements DisplayableField, HasArgumentsField
{
final public const FIELD = 'anime';
@@ -35,11 +37,19 @@ class AnimeYearSeasonAnimeField extends Field implements DisplayableField, HasAr
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): AnimeType
{
return new AnimeType();
}
/**
* The type returned by the field.
*/
public function type(): Type
{
return Type::nonNull(GraphQL::paginate($this->baseType()->getName()));
}
/**
* Determine if the field should be displayed to the user.
*/
@@ -57,4 +67,13 @@ class AnimeYearSeasonAnimeField extends Field implements DisplayableField, HasAr
{
return new AnimePaginatorQuery()->arguments();
}
/**
* @return Paginator
*/
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return App::make(AnimeYearsController::class)
->resolveAnimeField($root, $args, $context, $resolveInfo);
}
}
@@ -7,15 +7,15 @@ namespace App\GraphQL\Definition\Fields\Wiki\Anime\AnimeYear;
use App\Contracts\GraphQL\Fields\DisplayableField;
use App\Contracts\GraphQL\Fields\HasArgumentsField;
use App\Enums\Models\Wiki\AnimeSeason;
use App\GraphQL\Attributes\Resolvers\UseFieldDirective;
use App\GraphQL\Controllers\Wiki\Anime\AnimeYearsController;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\Anime\AnimeYear\AnimeYearSeasonType;
use App\GraphQL\Support\Argument\Argument;
use GraphQL\Type\Definition\Type;
use Nuwave\Lighthouse\Schema\TypeRegistry;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Rebing\GraphQL\Support\Facades\GraphQL;
#[UseFieldDirective(AnimeYearsController::class, 'applyFieldToSeasonField')]
class AnimeYearSeasonField extends Field implements DisplayableField, HasArgumentsField
{
final public const FIELD = 'season';
@@ -37,7 +37,7 @@ class AnimeYearSeasonField extends Field implements DisplayableField, HasArgumen
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): AnimeYearSeasonType
{
return new AnimeYearSeasonType();
}
@@ -57,10 +57,20 @@ class AnimeYearSeasonField extends Field implements DisplayableField, HasArgumen
*/
public function arguments(): array
{
$season = app(TypeRegistry::class)->get(class_basename(AnimeSeason::class));
$season = GraphQL::type(class_basename(AnimeSeason::class));
return [
new Argument(self::ARGUMENT_SEASON, Type::nonNull($season)),
new Argument(self::ARGUMENT_SEASON, $season)
->required(),
];
}
/**
* @return Collection
*/
public function resolve($root, array $args, $context, ResolveInfo $resolveInfo): mixed
{
return App::make(AnimeYearsController::class)
->resolveSeasonField($root, $args, $context, $resolveInfo);
}
}
@@ -8,6 +8,7 @@ use App\Contracts\GraphQL\Fields\DisplayableField;
use App\GraphQL\Definition\Fields\Field;
use App\GraphQL\Definition\Types\Wiki\Anime\AnimeYear\AnimeYearSeasonsType;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Facades\GraphQL;
class AnimeYearSeasonsField extends Field implements DisplayableField
{
@@ -29,9 +30,11 @@ class AnimeYearSeasonsField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::listOf(Type::nonNull(new AnimeYearSeasonsType()));
$type = GraphQL::type(new AnimeYearSeasonsType()->getName());
return Type::listOf(Type::nonNull($type));
}
/**
@@ -29,7 +29,7 @@ class AnimeYearYearField extends Field implements DisplayableField
/**
* The type returned by the field.
*/
public function type(): Type
public function baseType(): Type
{
return Type::int();
}
@@ -9,10 +9,10 @@ use App\Contracts\GraphQL\Fields\RequiredOnCreation;
use App\Contracts\GraphQL\Fields\UpdatableField;
use App\Enums\Models\Wiki\ThemeType;
use App\GraphQL\Definition\Fields\EnumField;
use App\GraphQL\Support\Directives\Filters\EqFilterDirective;
use App\GraphQL\Support\Directives\Filters\FilterDirective;
use App\GraphQL\Support\Directives\Filters\InFilterDirective;
use App\GraphQL\Support\Directives\Filters\NotInFilterDirective;
use App\GraphQL\Support\Filter\EqFilter;
use App\GraphQL\Support\Filter\Filter;
use App\GraphQL\Support\Filter\InFilter;
use App\GraphQL\Support\Filter\NotInFilter;
use App\Models\Wiki\Anime\AnimeTheme;
use Illuminate\Validation\Rules\Enum;
@@ -32,16 +32,16 @@ class AnimeThemeTypeField extends EnumField implements CreatableField, RequiredO
}
/**
* The directives available for this filter.
* The filters of the field.
*
* @return FilterDirective[]
* @return Filter[]
*/
public function filterDirectives(): array
public function getFilters(): array
{
return [
new EqFilterDirective($this),
new InFilterDirective($this, '[OP, ED]'),
new NotInFilterDirective($this),
new EqFilter($this),
new InFilter($this, [ThemeType::OP->value, ThemeType::ED->value]),
new NotInFilter($this),
];
}
@@ -29,21 +29,11 @@ class ArtistSlugField extends StringField implements BindableField, CreatableFie
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Artist>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return Artist::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return $this->column;
return null;
}
/**
@@ -4,12 +4,11 @@ declare(strict_types=1);
namespace App\GraphQL\Definition\Fields\Wiki\Audio;
use App\GraphQL\Attributes\Deprecated;
use App\Contracts\GraphQL\Fields\DeprecatedField;
use App\GraphQL\Definition\Fields\Base\CountAggregateField;
use App\Models\Wiki\Audio;
#[Deprecated('We will no longer track views.')]
class AudioViewsCountField extends CountAggregateField
class AudioViewsCountField extends CountAggregateField implements DeprecatedField
{
public function __construct()
{
@@ -23,4 +22,12 @@ class AudioViewsCountField extends CountAggregateField
{
return 'The number of views recorded for the resource';
}
/**
* The reason which the field is deprecated.
*/
public function deprecationReason(): string
{
return 'We will no longer track views.';
}
}
@@ -29,21 +29,11 @@ class SeriesSlugField extends StringField implements BindableField, CreatableFie
}
/**
* Get the model that the field should bind to.
*
* @return class-string<Series>
* The resolver to cast the model.
*/
public function bindTo(): string
public function bindResolver(array $args): null
{
return Series::class;
}
/**
* Get the column that the field should use to bind.
*/
public function bindUsingColumn(): string
{
return $this->column;
return null;
}
/**

Some files were not shown because too many files have changed in this diff Show More