feat: GraphQL (#814)

This commit is contained in:
Kyrch
2025-05-12 02:07:27 -03:00
committed by GitHub
parent 453cc1e72a
commit 38f0e2fa76
119 changed files with 5923 additions and 145 deletions
+7
View File
@@ -198,6 +198,13 @@ IMAGE_DISK_ROOT=
JETSTREAM_PATH=
JETSTREAM_URL=http://localhost
# lighthouse
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
LIGHTHOUSE_DEBUG=true
LIGHTHOUSE_MAX_QUERY_COMPLEXITY=100
LIGHTHOUSE_MAX_QUERY_DEPTH=10
# logging
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null
+7
View File
@@ -196,6 +196,13 @@ IMAGE_DISK_ROOT=
JETSTREAM_PATH=
JETSTREAM_URL=http://localhost
# lighthouse
GRAPHQL_DOMAIN_NAME=
GRAPHQL_PATH=/graphql
LIGHTHOUSE_DEBUG=true
LIGHTHOUSE_MAX_QUERY_COMPLEXITY=100
LIGHTHOUSE_MAX_QUERY_DEPTH=10
# logging
LOG_CHANNEL=daily
LOG_DEPRECATIONS_CHANNEL=null
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace App\Contracts\Models;
use App\Models\Service\ViewAggregate;
use Illuminate\Database\Eloquent\Relations\MorphOne;
/**
* Interface HasAggregateViews.
*
* @property ViewAggregate|null $viewAggregate
*/
interface HasAggregateViews
{
/**
* Get the views count of the model.
*
* @return MorphOne
*/
public function viewAggregate(): MorphOne;
}
+2
View File
@@ -13,6 +13,8 @@ enum SpecialPermission: string
case BYPASS_FEATURE_FLAGS = 'bypass feature flags';
case BYPASS_GRAPHQL_RATE_LIMITER = 'bypass graphql rate limiter';
case VIEW_FILAMENT = 'view filament';
case VIEW_HORIZON = 'view horizon';
+4
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Enums\Models\Wiki;
use App\Concerns\Enums\LocalizesName;
use App\GraphQL\Types\Definition\Hidden;
/**
* Enum ThemeType.
@@ -14,6 +15,9 @@ enum ThemeType: int
use LocalizesName;
case OP = 0;
case ED = 1;
#[Hidden]
case IN = 2;
}
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\Admin;
use App\Constants\FeatureConstants;
use App\Models\Admin\Feature;
use Illuminate\Database\Eloquent\Builder;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class FeatureBuilder.
*/
class FeatureBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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);
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\Admin;
use App\Enums\Http\Api\Filter\ComparisonOperator;
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;
/**
* Class FeaturedThemeBuilder.
*/
class FeaturedThemeBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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());
}
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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());
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\List\External;
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;
/**
* Class ExternalEntryBuilder.
*/
class ExternalEntryBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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;
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\List;
use App\Enums\Models\List\ExternalProfileVisibility;
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;
/**
* Class ExternalProfileBuilder.
*/
class ExternalProfileBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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;
}
}
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\List\Playlist;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class PlaylistTrackBuilder.
*/
class PlaylistTrackBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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;
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Builders\List;
use App\Enums\Models\List\PlaylistVisibility;
use App\Models\List\Playlist;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class PlaylistBuilder.
*/
class PlaylistBuilder
{
/**
* Apply the query builder to the index query.
*
* @param Builder $builder
* @param mixed $value
* @param mixed $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return Builder
*/
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;
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Directives;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\FieldValue;
use Nuwave\Lighthouse\Support\Contracts\FieldMiddleware;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class LocalizedEnumDirective.
*/
class LocalizedEnumDirective extends BaseDirective implements FieldMiddleware
{
/**
* Define the directive.
*
* @return string
*/
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Translate the enum value.
"""
directive @localizedEnum on FIELD_DEFINITION
GRAPHQL;
}
/**
* Wrap around the final field resolver.
*
* @param FieldValue $fieldValue
* @return void
*/
public function handleField(FieldValue $fieldValue): void
{
$fieldValue->wrapResolver(
fn (callable $resolver) => function ($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo) use ($resolver) {
/** @phpstan-ignore-next-line */
$enum = $resolver($root, $args, $context, $resolveInfo);
return $enum->localize();
});
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Directives;
use Illuminate\Support\Facades\App;
use Nuwave\Lighthouse\Schema\Directives\BaseDirective;
use Nuwave\Lighthouse\Schema\Values\TypeValue;
use Nuwave\Lighthouse\Support\Contracts\TypeMiddleware;
/**
* Class MiddlewareDirective.
*/
class MiddlewareDirective extends BaseDirective implements TypeMiddleware
{
/**
* Define the directive.
*
* @return string
*/
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
directive @middleware(class: String!) on OBJECT
GRAPHQL;
}
/**
* Handle a type AST as it is converted to an executable type.
*
* @param TypeValue $value
* @return void
*/
public function handleNode(TypeValue $value): void
{
$class = $this->directiveArgValue('class');
App::make($class)->handle(request(), fn () => null);
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
/**
* Class GraphQLPolicy.
*/
class GraphQLPolicy
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): mixed $next
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
Gate::guessPolicyNamesUsing(
fn (string $modelClass) => Str::of($modelClass)
->replace('Models', 'GraphQL\\Policies')
->append('Policy')
->__toString()
);
return $next($request);
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Middleware;
use Closure;
use Illuminate\Http\Request;
/**
* Class GraphqlLocalhost.
*/
class GraphqlLocalhost
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): mixed $next
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
$ip = $request->ip();
if ($ip !== '127.0.0.1' && app()->isProduction()) {
abort(403, "GraphQL is only enabled for localhost");
}
return $next($request);
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
/**
* Class MaxCount.
*/
class MaxCount
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure(Request): mixed $next
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
Config::set('lighthouse.pagination.max_count', $request->ip() === '127.0.0.1' ? null : 100);
return $next($request);
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Mutations\List;
use App\Models\List\ExternalProfile;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
/**
* Class ExternalProfileMutator.
*/
class ExternalProfileMutator
{
final public const ROUTE_SLUG = 'id';
/**
* Start a new sync job.
*
* @param null $_
* @param array $args
* @return JsonResponse
*/
public function sync($_, array $args): JsonResponse
{
/** @var ExternalProfile $profile */
$profile = Arr::pull($args, self::ROUTE_SLUG);
if (!$profile->canBeSynced()) {
return new JsonResponse([
'error' => 'This external profile cannot be synced at the moment.'
], 403);
}
$profile->startSyncJob();
return new JsonResponse([
'message' => 'Job dispatched.'
], 201);
}
}
@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Mutations\List\Playlist;
use App\Actions\Http\Api\List\Playlist\Track\DestroyTrackAction;
use App\Actions\Http\Api\List\Playlist\Track\ForceDeleteTrackAction;
use App\Actions\Http\Api\List\Playlist\Track\RestoreTrackAction;
use App\Actions\Http\Api\List\Playlist\Track\StoreTrackAction;
use App\Actions\Http\Api\List\Playlist\Track\UpdateTrackAction;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
/**
* Class PlaylistTrackMutator.
*/
class PlaylistTrackMutator
{
final public const ROUTE_SLUG = 'id';
/**
* Store a newly created resource.
*
* @param null $_
* @param array $args
* @return PlaylistTrack
*/
public function store($_, array $args): PlaylistTrack
{
/** @var Playlist $playlist */
$playlist = Arr::pull($args, 'playlist');
$action = new StoreTrackAction();
/** @var PlaylistTrack $stored */
$stored = $action->store($playlist, PlaylistTrack::query(), $args);
return $stored;
}
/**
* Update the specified resource.
*
* @param null $_
* @param array $args
* @return PlaylistTrack
*/
public function update($_, array $args): PlaylistTrack
{
/** @var PlaylistTrack $track */
$track = Arr::pull($args, self::ROUTE_SLUG);
$action = new UpdateTrackAction();
/** @var PlaylistTrack $updated */
$updated = $action->update($track->playlist, $track, $args);
return $updated;
}
/**
* Remove the specified resource.
*
* @param null $_
* @param array $args
* @return PlaylistTrack
*/
public function destroy($_, array $args): PlaylistTrack
{
/** @var PlaylistTrack $track */
$track = Arr::get($args, self::ROUTE_SLUG);
$action = new DestroyTrackAction();
/** @var PlaylistTrack $destroyed */
$destroyed = $action->destroy($track->playlist, $track);
return $destroyed;
}
/**
* Restore the specified resource.
*
* @param null $_
* @param array $args
* @return PlaylistTrack
*/
public function restore($_, array $args): PlaylistTrack
{
/** @var PlaylistTrack $track */
$track = Arr::get($args, self::ROUTE_SLUG);
$action = new RestoreTrackAction();
/** @var PlaylistTrack $restored */
$restored = $action->restore($track->playlist, $track);
return $restored;
}
/**
* Hard-delete the specified resource.
*
* @param null $_
* @param array $args
* @return JsonResponse
*/
public function forceDelete($_, array $args): JsonResponse
{
/** @var PlaylistTrack $track */
$track = Arr::get($args, self::ROUTE_SLUG);
$action = new ForceDeleteTrackAction();
$message = $action->forceDelete($track->playlist, $track);
return new JsonResponse([
'message' => $message,
]);
}
}
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Mutations\List;
use App\Actions\Http\Api\DestroyAction;
use App\Actions\Http\Api\ForceDeleteAction;
use App\Actions\Http\Api\RestoreAction;
use App\Actions\Http\Api\StoreAction;
use App\Actions\Http\Api\UpdateAction;
use App\Models\List\Playlist;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Auth;
/**
* Class PlaylistMutator.
*/
class PlaylistMutator
{
final public const ROUTE_SLUG = 'id';
/**
* Store a newly created resource.
*
* @param null $_
* @param array $args
* @return Playlist
*/
public function store($_, array $args): Playlist
{
$parameters = [
...$args,
Playlist::ATTRIBUTE_USER => Auth::id()
];
$action = new StoreAction();
/** @var Playlist $stored */
$stored = $action->store(Playlist::query(), $parameters);
return $stored;
}
/**
* Update the specified resource.
*
* @param null $_
* @param array $args
* @return Playlist
*/
public function update($_, array $args): Playlist
{
/** @var Playlist $playlist */
$playlist = Arr::pull($args, self::ROUTE_SLUG);
$action = new UpdateAction();
/** @var Playlist $updated */
$updated = $action->update($playlist, $args);
return $updated;
}
/**
* Remove the specified resource.
*
* @param null $_
* @param array $args
* @return Playlist
*/
public function destroy($_, array $args): Playlist
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, self::ROUTE_SLUG);
$action = new DestroyAction();
/** @var Playlist $destroyed */
$destroyed = $action->destroy($playlist);
return $destroyed;
}
/**
* Restore the specified resource.
*
* @param null $_
* @param array $args
* @return Playlist
*/
public function restore($_, array $args): Playlist
{
$hashId = Arr::get($args, self::ROUTE_SLUG);
$playlist = Playlist::withTrashed()->firstWhere(Playlist::ATTRIBUTE_HASHID, $hashId);
$action = new RestoreAction();
/** @var Playlist $restored */
$restored = $action->restore($playlist);
return $restored;
}
/**
* Hard-delete the specified resource.
*
* @param null $_
* @param array $args
* @return JsonResponse
*/
public function forceDelete($_, array $args): JsonResponse
{
/** @var Playlist $playlist */
$playlist = Arr::get($args, self::ROUTE_SLUG);
$action = new ForceDeleteAction();
$message = $action->forceDelete($playlist);
return new JsonResponse([
'message' => $message,
]);
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Admin;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AnnouncementPolicy.
*/
class AnnouncementPolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Admin;
use App\GraphQL\Policies\BasePolicy;
/**
* Class DumpPolicy.
*/
class DumpPolicy extends BasePolicy
{
}
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Admin;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Admin\Feature;
use App\Models\Auth\User;
use Illuminate\Support\Arr;
/**
* Class FeaturePolicy.
*/
class FeaturePolicy extends BasePolicy
{
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var Feature $feature */
$feature = Arr::get($injected, 'id');
return $feature->isNullScope();
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Admin;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Admin\FeaturedTheme;
use App\Models\Auth\User;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Date;
/**
* Class FeaturedThemePolicy.
*/
class FeaturedThemePolicy extends BasePolicy
{
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var FeaturedTheme $featuredtheme */
$featuredtheme = Arr::get($injected, 'id');
return $featuredtheme->start_at->isBefore(Date::now());
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\Permission;
use App\Models\Auth\User;
/**
* Class PermissionPolicy.
*/
class PermissionPolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(Permission::class));
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function view(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(Permission::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function update(User $user, array $injected): bool
{
return $user->can(CrudPermission::UPDATE->format(Permission::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function delete(User $user, array $injected): bool
{
return $user->can(CrudPermission::DELETE->format(Permission::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function restore(User $user, array $injected): bool
{
return $user->can(ExtendedCrudPermission::RESTORE->format(Permission::class));
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\Role;
use App\Models\Auth\User;
/**
* Class RolePolicy.
*/
class RolePolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(Role::class));
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function view(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(Role::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function update(User $user, array $injected): bool
{
return $user->can(CrudPermission::UPDATE->format(Role::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function delete(User $user, array $injected): bool
{
return $user->can(CrudPermission::DELETE->format(Role::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function restore(User $user, array $injected): bool
{
return $user->can(ExtendedCrudPermission::RESTORE->format(Role::class));
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Auth;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\Role;
use App\Models\Auth\User;
/**
* Class UserPolicy.
*/
class UserPolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(User::class));
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function view(?User $user, ?array $injected = null): bool
{
return $user !== null && $user->can(CrudPermission::VIEW->format(User::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function update(User $user, array $injected): bool
{
return $user->can(CrudPermission::UPDATE->format(User::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function delete(User $user, array $injected): bool
{
return $user->can(CrudPermission::DELETE->format(User::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function restore(User $user, array $injected): bool
{
return $user->can(ExtendedCrudPermission::RESTORE->format(User::class));
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Models\Auth\User;
use App\Models\BaseModel;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
/**
* Class BasePolicy.
*
* GraphQL will read any attach{model}, attachAny{model}, detach{model}, detachAny{model}
* to make the validation for pivots. {model} must be the name of the model.
*/
abstract class BasePolicy
{
use HandlesAuthorization;
/**
* Get the model class of the policy.
*
* @return class-string
*/
protected static function getModel(): string
{
return Str::of(get_called_class())
->replace('GraphQL\\Policies', 'Models')
->remove('Policy')
->__toString();
}
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function view(?User $user, ?array $injected = null): bool
{
return true;
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @param array|null $injected
* @return bool
*/
public function create(User $user, ?array $injected = null): bool
{
return $user->can(CrudPermission::CREATE->format(static::getModel()));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function update(User $user, array $injected): bool
{
/** @var BaseModel $model */
$model = Arr::get($injected, 'id');
return (!($model instanceof BaseModel) || !$model->trashed()) && $user->can(CrudPermission::UPDATE->format(static::getModel()));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function delete(User $user, array $injected): bool
{
/** @var BaseModel $model */
$model = Arr::get($injected, 'id');
return (!($model instanceof BaseModel) || !$model->trashed()) && $user->can(CrudPermission::DELETE->format(static::getModel()));
}
/**
* Determine whether the user can permanently delete the model.
*
* @param User $user
* @return bool
*/
public function forceDelete(User $user): bool
{
return $user->can(ExtendedCrudPermission::FORCE_DELETE->format(static::getModel()));
}
/**
* Determine whether the user can permanently delete any model.
*
* @param User $user
* @return bool
*/
public function forceDeleteAny(User $user): bool
{
return $user->can(ExtendedCrudPermission::FORCE_DELETE->format(static::getModel()));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function restore(User $user, array $injected): bool
{
/** @var BaseModel $model */
$model = Arr::get($injected, 'id');
return (!($model instanceof BaseModel) || $model->trashed()) && $user->can(ExtendedCrudPermission::RESTORE->format(static::getModel()));
}
/**
* Determine whether the user can restore any model.
*
* @param User $user
* @return bool
*/
public function restoreAny(User $user): bool
{
return $user->can(ExtendedCrudPermission::RESTORE->format(static::getModel()));
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Discord;
use App\GraphQL\Policies\BasePolicy;
/**
* Class DiscordThreadPolicy.
*/
class DiscordThreadPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Document;
use App\GraphQL\Policies\BasePolicy;
/**
* Class PagePolicy.
*/
class PagePolicy extends BasePolicy
{
}
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\List\External;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Models\List\ExternalProfileVisibility;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\User;
use App\Models\List\External\ExternalEntry;
use App\Models\List\ExternalProfile;
use Illuminate\Support\Arr;
/**
* Class ExternalEntryPolicy.
*/
class ExternalEntryPolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
return $user !== null
? ($profile?->user()->is($user) || ExternalProfileVisibility::PRIVATE !== $profile?->visibility) && $user->can(CrudPermission::VIEW->format(ExternalEntry::class))
: ExternalProfileVisibility::PRIVATE !== $profile?->visibility;
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
return $user !== null
? ($profile?->user()->is($user) || ExternalProfileVisibility::PRIVATE !== $profile?->visibility) && $user->can(CrudPermission::VIEW->format(ExternalEntry::class))
: ExternalProfileVisibility::PRIVATE !== $profile?->visibility;
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @param array|null $injected
* @return bool
*/
public function create(User $user, ?array $injected = null): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
return $profile?->user()->is($user);
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function update(User $user, array $injected): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
/** @var ExternalEntry $entry */
$entry = Arr::get($injected, 'id');
return !$entry->trashed() && $profile?->user()->is($user) && $user->can(CrudPermission::UPDATE->format(ExternalEntry::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function delete(User $user, array $injected): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
/** @var ExternalEntry $entry */
$entry = Arr::get($injected, 'id');
return !$entry->trashed() && $profile?->user()->is($user) && $user->can(CrudPermission::DELETE->format(ExternalEntry::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function restore(User $user, array $injected): bool
{
/** @var ExternalProfile|null $profile */
$profile = Arr::get($injected, 'profile');
/** @var ExternalEntry $entry */
$entry = Arr::get($injected, 'id');
return $entry->trashed() && $profile?->user()->is($user) && $user->can(ExtendedCrudPermission::RESTORE->format(ExternalEntry::class));
}
}
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\List;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Models\List\ExternalProfileVisibility;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\User;
use App\Models\List\ExternalProfile;
use Illuminate\Support\Arr;
/**
* Class ExternalProfilePolicy.
*/
class ExternalProfilePolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return $user === null || $user->can(CrudPermission::VIEW->format(ExternalProfile::class));
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var ExternalProfile $profile */
$profile = Arr::get($injected, 'id');
return $user !== null
? ($profile->user()->is($user) || ExternalProfileVisibility::PRIVATE !== $profile->visibility) && $user->can(CrudPermission::VIEW->format(ExternalProfile::class))
: ExternalProfileVisibility::PRIVATE !== $profile->visibility;
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @param array|null $injected
* @return bool
*/
public function create(User $user, ?array $injected = null): bool
{
return $user->can(CrudPermission::CREATE->format(ExternalProfile::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function update(User $user, array $injected): bool
{
/** @var ExternalProfile $profile */
$profile = Arr::get($injected, 'id');
return !$profile->trashed() && $profile->user()->is($user) && $user->can(CrudPermission::UPDATE->format(ExternalProfile::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function delete(User $user, array $injected): bool
{
/** @var ExternalProfile $profile */
$profile = Arr::get($injected, 'id');
return !$profile->trashed() && $profile->user()->is($user) && $user->can(CrudPermission::DELETE->format(ExternalProfile::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function restore(User $user, array $injected): bool
{
/** @var ExternalProfile $profile */
$profile = Arr::get($injected, 'id');
return $profile->trashed() && $profile->user()->is($user) && $user->can(ExtendedCrudPermission::RESTORE->format(ExternalProfile::class));
}
}
@@ -0,0 +1,123 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\List\Playlist;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Models\List\PlaylistVisibility;
use App\GraphQL\Mutations\List\Playlist\PlaylistTrackMutator;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\User;
use App\Models\List\Playlist;
use App\Models\List\Playlist\PlaylistTrack;
use Illuminate\Support\Arr;
/**
* Class PlaylistTrackPolicy.
*/
class PlaylistTrackPolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
return $user !== null
? ($playlist?->user()->is($user) || PlaylistVisibility::PRIVATE !== $playlist?->visibility) && $user->can(CrudPermission::VIEW->format(PlaylistTrack::class))
: PlaylistVisibility::PRIVATE !== $playlist?->visibility;
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*
* @noinspection PhpUnusedParameterInspection
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
return $user !== null
? ($playlist?->user()->is($user) || PlaylistVisibility::PRIVATE !== $playlist?->visibility) && $user->can(CrudPermission::VIEW->format(PlaylistTrack::class))
: PlaylistVisibility::PRIVATE !== $playlist?->visibility;
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @param array|null $injected
* @return bool
*/
public function create(User $user, ?array $injected = null): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
return $playlist?->user()->is($user);
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function update(User $user, array $injected): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
/** @var PlaylistTrack $track */
$track = Arr::get($injected, PlaylistTrackMutator::ROUTE_SLUG);
return !$track->trashed() && $playlist?->user()->is($user) && $user->can(CrudPermission::UPDATE->format(PlaylistTrack::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function delete(User $user, array $injected): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
/** @var PlaylistTrack $track */
$track = Arr::get($injected, PlaylistTrackMutator::ROUTE_SLUG);
return !$track->trashed() && $playlist?->user()->is($user) && $user->can(CrudPermission::DELETE->format(PlaylistTrack::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function restore(User $user, array $injected): bool
{
/** @var Playlist|null $playlist */
$playlist = Arr::get($injected, 'playlist');
/** @var PlaylistTrack $track */
$track = Arr::get($injected, PlaylistTrackMutator::ROUTE_SLUG);
return $track->trashed() && $playlist?->user()->is($user) && $user->can(ExtendedCrudPermission::RESTORE->format(PlaylistTrack::class));
}
}
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\List;
use App\Enums\Auth\CrudPermission;
use App\Enums\Auth\ExtendedCrudPermission;
use App\Enums\Models\List\PlaylistVisibility;
use App\GraphQL\Mutations\List\PlaylistMutator;
use App\GraphQL\Policies\BasePolicy;
use App\Models\Auth\User;
use App\Models\List\Playlist;
use Illuminate\Support\Arr;
/**
* Class PlaylistPolicy.
*/
class PlaylistPolicy extends BasePolicy
{
/**
* Determine whether the user can view any models.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function viewAny(?User $user, ?array $injected = null): bool
{
return $user === null || $user->can(CrudPermission::VIEW->format(Playlist::class));
}
/**
* Determine whether the user can view the model.
*
* @param User|null $user
* @param array|null $injected
* @return bool
*/
public function view(?User $user, ?array $injected = null): bool
{
/** @var Playlist $playlist */
$playlist = Arr::get($injected, PlaylistMutator::ROUTE_SLUG);
return $user !== null
? ($playlist->user()->is($user) || PlaylistVisibility::PRIVATE !== $playlist->visibility) && $user->can(CrudPermission::VIEW->format(Playlist::class))
: PlaylistVisibility::PRIVATE !== $playlist->visibility;
}
/**
* Determine whether the user can create models.
*
* @param User $user
* @param array|null $injected
* @return bool
*/
public function create(User $user, ?array $injected = null): bool
{
return $user->can(CrudPermission::CREATE->format(Playlist::class));
}
/**
* Determine whether the user can update the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function update(User $user, array $injected): bool
{
/** @var Playlist $playlist */
$playlist = Arr::get($injected, PlaylistMutator::ROUTE_SLUG);
return !$playlist->trashed() && $playlist->user()->is($user) && $user->can(CrudPermission::UPDATE->format(Playlist::class));
}
/**
* Determine whether the user can delete the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function delete(User $user, array $injected): bool
{
/** @var Playlist $playlist */
$playlist = Arr::get($injected, PlaylistMutator::ROUTE_SLUG);
return !$playlist->trashed() && $playlist->user()->is($user) && $user->can(CrudPermission::DELETE->format(Playlist::class));
}
/**
* Determine whether the user can restore the model.
*
* @param User $user
* @param array $injected
* @return bool
*/
public function restore(User $user, array $injected): bool
{
/** @var Playlist $playlist */
$playlist = Arr::get($injected, PlaylistMutator::ROUTE_SLUG);
return $playlist->trashed() && $playlist->user()->is($user) && $user->can(ExtendedCrudPermission::RESTORE->format(Playlist::class));
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki\Anime;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AnimeSynonymPolicy.
*/
class AnimeSynonymPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki\Anime;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AnimeThemePolicy.
*/
class AnimeThemePolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki\Anime\Theme;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AnimeThemeEntryPolicy.
*/
class AnimeThemeEntryPolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AnimePolicy.
*/
class AnimePolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class ArtistPolicy.
*/
class ArtistPolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class AudioPolicy.
*/
class AudioPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class ExternalResourcePolicy.
*/
class ExternalResourcePolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class GroupPolicy.
*/
class GroupPolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class ImagePolicy.
*/
class ImagePolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class SeriesPolicy.
*/
class SeriesPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki\Song;
use App\GraphQL\Policies\BasePolicy;
/**
* Class MembershipPolicy.
*/
class MembershipPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki\Song;
use App\GraphQL\Policies\BasePolicy;
/**
* Class PerformancePolicy.
*/
class PerformancePolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class SongPolicy.
*/
class SongPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class StudioPolicy.
*/
class StudioPolicy extends BasePolicy
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class VideoScriptPolicy.
*/
class VideoScriptPolicy extends BasePolicy
{
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Policies\Wiki;
use App\GraphQL\Policies\BasePolicy;
/**
* Class VideoPolicy.
*/
class VideoPolicy extends BasePolicy
{
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Queries;
use App\Models\Wiki\Anime;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class AnimeYear.
*/
class AnimeYear
{
/**
* Return a value for the field.
*
* @param null $root Always null, since this field has no parent.
* @param array $args The field arguments passed by the client.
* @param GraphQLContext $context Shared between all fields.
* @param ResolveInfo $resolveInfo Metadata for advanced query resolution.
* @return mixed The result of resolving the field, matching what was promised in the schema.
*/
public function years(null $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
return Anime::query()
->distinct(Anime::ATTRIBUTE_YEAR)
->orderBy(Anime::ATTRIBUTE_YEAR)
->pluck(Anime::ATTRIBUTE_YEAR);
}
/**
* Return a value for the field.
*
* @param null $root Always null, since this field has no parent.
* @param array $args The field arguments passed by the client.
* @param GraphQLContext $context Shared between all fields.
* @param ResolveInfo $resolveInfo Metadata for advanced query resolution.
* @return mixed The result of resolving the field, matching what was promised in the schema.
*/
public function year(null $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
$year = Arr::get($args, 'year');
return Anime::query()
->where(Anime::ATTRIBUTE_YEAR, $year)
->orderBy(Anime::ATTRIBUTE_NAME)
->get()
->groupBy(fn (Anime $anime) => Str::lower($anime->season->localize()));
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Queries;
use App\Models\List\Playlist;
use App\Models\Wiki\Anime;
use App\Models\Wiki\Anime\AnimeTheme;
use App\Models\Wiki\Artist;
use App\Models\Wiki\Series;
use App\Models\Wiki\Song;
use App\Models\Wiki\Studio;
use App\Models\Wiki\Video;
use Illuminate\Support\Arr;
use Nuwave\Lighthouse\Execution\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class Search.
*/
class Search
{
/**
* Return a value for the field.
*
* @param null $root Always null, since this field has no parent.
* @param array $args The field arguments passed by the client.
* @param GraphQLContext $context Shared between all fields.
* @param ResolveInfo $resolveInfo Metadata for advanced query resolution.
* @return mixed The result of resolving the field, matching what was promised in the schema.
*/
public function __invoke(null $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
$result = [];
$fields = $resolveInfo->getFieldSelection();
$term = Arr::get($args, 'search');
$page = Arr::get($args, 'page');
$perPage = Arr::get($args, 'perPage');
if (Arr::get($fields, 'anime')) {
$result['anime'] = Anime::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'artists')) {
$result['artists'] = Artist::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'animethemes')) {
$result['animethemes'] = AnimeTheme::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'playlists')) {
$result['playlists'] = Playlist::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'series')) {
$result['series'] = Series::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'songs')) {
$result['songs'] = Song::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'studios')) {
$result['studios'] = Studio::search($term)->paginate($perPage, $page)->items();
}
if (Arr::get($fields, 'videos')) {
$result['videos'] = Video::search($term)->paginate($perPage, $page)->items();
}
return $result;
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Resolvers;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class ExistsResolver.
*/
class ExistsResolver
{
/**
* Resolve the relation exists field.
*
* @param Model $model
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return mixed
*/
public function resolve(Model $model, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
// Field expected to be {relation}Exists
$relation = Str::remove('Exists', $resolveInfo->fieldName);
return $model->{$relation}->isNotEmpty();
}
}
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Resolvers;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Database\Eloquent\Model;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class ImplodeArrayResolver.
*/
class ImplodeArrayResolver
{
/**
* Resolve the relation count field.
*
* @param Model $model
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return mixed
*/
public function resolve(Model $model, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
return implode('', $model->getAttribute($resolveInfo->fieldName));
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Resolvers;
use GraphQL\Type\Definition\ResolveInfo;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class PivotResolver.
*/
class PivotResolver
{
/**
* Resolve the pivot field.
*
* @param array $root
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return mixed
*/
public function resolve(array $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
/** @var Model $model */
$model = Arr::get($root, 'node');
$pivot = current($model->getRelations());
return $pivot->{Str::snake($resolveInfo->fieldName)};
}
}
@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Resolvers;
use App\Contracts\Models\HasAggregateViews;
use App\Models\Service\ViewAggregate;
use GraphQL\Type\Definition\ResolveInfo;
use Nuwave\Lighthouse\Support\Contracts\GraphQLContext;
/**
* Class ViewsCountResolver.
*/
class ViewsCountResolver
{
/**
* Resolve views count field.
*
* @param HasAggregateViews $viewable
* @param array $args
* @param GraphQLContext $context
* @param ResolveInfo $resolveInfo
* @return mixed
*/
public function resolve(HasAggregateViews $viewable, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): mixed
{
/** @var ViewAggregate|null $view */
/** @phpstan-ignore-next-line */
$view = $viewable->viewAggregate;
return (int) $view?->value;
}
}
@@ -0,0 +1,37 @@
"Represents a site-wide message to be broadcasted on the homepage."
type Announcement {
"The primary key of the resource"
id: Int! @rename(attribute: "announcement_id")
"The announcement text"
content: String!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum AnnouncementColumnsOrderable {
ID @enum(value: "announcement_id")
CONTENT
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of announcement resources."
announcements(
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: ">")
orderBy: _ @orderBy(columnsEnum: "AnnouncementColumnsOrderable")
): [Announcement!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+44
View File
@@ -0,0 +1,44 @@
"""
Represents a database dump of selected tables at a given point in time.
For example, the animethemes-db-dump-wiki-1663559663946.sql dump represents the database dump of wiki tables performed at 2022-09-19.
"""
type Dump {
"The primary key of the resource"
id: Int! @rename(attribute: "dump_id")
"The path of the file in storage"
path: String!
"The URL to download the file from storage"
link: String!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum DumpColumnsOrderable {
ID @enum(value: "dump_id")
PATH
LINK
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of dump resources given fields."
dumps(
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: ">")
orderBy: _ @orderBy(columnsEnum: "DumpColumnsOrderable")
): [Dump!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+39
View File
@@ -0,0 +1,39 @@
"""
Represents a feature flag that enable/disable site functionalities.
For example, the 'allow_discord_notifications' feature enables/disables discord notifications for the configured bot.
"""
type Feature {
"The primary key of the resource"
id: Int! @rename(attribute: "feature_id")
"The title of the resource"
name: String!
"The value of the resource"
value: String!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
enum FeatureColumnsOrderable {
ID @enum(value: "feature_id")
NAME
VALUE
CREATED_AT
UPDATED_AT
}
extend type Query {
"Returns a listing of feature resources given fields."
features(
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: ">")
orderBy: _ @orderBy(columnsEnum: "FeatureColumnsOrderable")
): [Feature!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\Admin\\FeatureBuilder@index")
@paginate
}
@@ -0,0 +1,53 @@
"Represents a video to be featured on the homepage of the site for a specified amount of time."
type FeaturedTheme {
"The primary key of the resource"
id: Int! @rename(attribute: "featured_theme_id")
"The start date of the resource"
startAt: DateTimeTz! @rename(attribute: "start_at")
"The end date of the resource"
endAt: DateTimeTz! @rename(attribute: "end_at")
animethemeentry: AnimeThemeEntry @belongsTo
user: User @belongsTo
video: Video @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum FeaturedThemeColumnsOrderable {
ID @enum(value: "featured_theme_id")
START_AT
END_AT
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns the first featured theme where the current date is between start_at and end_at dates."
currentfeaturedtheme: FeaturedTheme @find
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\Admin\\FeaturedThemeBuilder@current")
"Returns a listing of featured theme resources given fields."
featuredthemes(
startAt_lesser: DateTimeTz @where(key: "start_at", operator: "<")
startAt_greater: DateTimeTz @where(key: "start_at", operator: ">")
endAt_lesser: DateTimeTz @where(key: "end_at", operator: "<")
endAt_greater: DateTimeTz @where(key: "end_at", operator: ">")
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: ">")
orderBy: _ @orderBy(columnsEnum: "FeaturedThemeColumnsOrderable")
): [FeaturedTheme!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\Admin\\FeaturedThemeBuilder@index")
@softDeletes
@paginate
}
@@ -0,0 +1,15 @@
"""
Represents an assignable label for users and roles that authorizes a particular action in AnimeThemes.
"""
type Permission {
"The primary key of the resource"
id: Int!
"The label of the resource"
name: String!
"The authentication guard of the resource"
guardName: String! @rename(attribute: "guard_name")
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
+30
View File
@@ -0,0 +1,30 @@
"""
Represents an assignable label for users that provides a configured group of permissions.
"""
type Role {
"The primary key of the resource"
id: Int! @rename(attribute: "role_id")
"The hex representation of the color used to distinguish the resource"
color: String!
"The label of the resource"
name: String!
"Is the role assigned on account verification?"
default: Boolean!
"The authentication guard of the resource"
guardName: String! @rename(attribute: "guard_name")
"The weight assigned to the resource, where higher values correspond to higher priority"
priority: Int!
permissions: [Permission] @belongsToMany(type: CONNECTION, edgeType: RolePermissionEdge)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
type RolePermissionEdge {
node: Permission!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
+39
View File
@@ -0,0 +1,39 @@
"Represents an AnimeThemes account."
type User {
"The username of the resource"
name: String!
playlists: [Playlist] @hasMany
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Represents the currently authenticated user."
type Me {
"The primary key of the resource"
id: Int
"The username of the resource"
name: String!
"The email of the user"
email: String!
"The date the user verified their email"
email_verified_at: DateTimeTz
"The date the user confirmed their two-factor authentication"
two_factor_confirmed_at: DateTimeTz
notifications: [Notification] @morphMany
playlists: [Playlist] @hasMany
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
extend type Query {
"Returns the data of the currently authenticated user."
me: Me @auth
}
+46
View File
@@ -0,0 +1,46 @@
"""
Represents a static markdown page used for guides and other documentation.
For example, the 'encoding/audio_normalization' page represents the documentation for audio normalization.
"""
type Page {
"The primary key of the resource"
id: Int! @rename(attribute: "page_id")
"The primary title of the page"
name: String!
"The URL slug & route key of the resource"
slug: String!
"The body content of the resource"
body: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum PageColumnsOrderable {
ID @enum(value: "page_id")
NAME
SLUG
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of page resources given fields."
pages(
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: ">")
orderBy: _ @orderBy(columnsEnum: "PageColumnsOrderable")
): [Page!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+59
View File
@@ -0,0 +1,59 @@
"""
Represents an anime entry on the external profile.
For example, Hibike Euphonium! is marked as completed on the profile AnimeThemes.
"""
type ExternalEntry {
"The primary key of the resource"
id: Int! @rename(attribute: "entry_id")
"The score of the entry on the external site"
score: Float
"The favorite state of the entry on the external site"
isFavorite: Boolean! @rename(attribute: "is_favorite")
"The watch status of the entry on the external site"
watchStatus: ExternalEntryWatchStatus! @rename(attribute: "watch_status") @localizedEnum
anime: Anime @belongsTo
externalprofile: ExternalProfile @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum ExternalEntryColumnsOrderable {
ID @enum(value: "entry_id")
SCORE
IS_FAVORITE
WATCH_STATUS
CREATED_AT
UPDATED_AT
DELETED_AT
}
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: ">")
deletedAt_lesser: DateTimeTz @where(key: "deleted_at", operator: "<")
deletedAt_greater: DateTimeTz @where(key: "deleted_at", operator: ">")
orderBy: _ @orderBy(columnsEnum: "ExternalEntryColumnsOrderable")
): [ExternalEntry!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\External\\ExternalEntryBuilder@index")
@paginate
}
@@ -0,0 +1,100 @@
"""
Represents an entry in a playlist.
For example, a "/r/anime's Best OPs and EDs of 2022" playlist may contain a track for the ParipiKoumei-OP1.webm video.
"""
type PlaylistTrack {
"The primary key of the resource"
id: String! @rename(attribute: "hashid")
animethemeentry: AnimeThemeEntry @belongsTo
next: PlaylistTrack @belongsTo
playlist: Playlist @belongsTo
previous: PlaylistTrack @belongsTo
video: Video @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum PlaylistTrackColumnsOrderable {
ID @enum(value: "track_id")
CREATED_AT
UPDATED_AT
DELETED_AT
}
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: ">")
orderBy: _ @orderBy(columnsEnum: "PlaylistTrackColumnsOrderable")
): [PlaylistTrack!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\Playlist\\PlaylistTrackBuilder@index")
@paginate
}
extend type Mutation @guard {
createPlaylistTrack(
entry_id: Int!
video_id: Int!
next: String
previous: String
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): PlaylistTrack!
@canModel(ability: "create", injectArgs: true)
@validator(class: "App\\GraphQL\\Validators\\Mutation\\List\\Playlist\\CreatePlaylistTrackValidator")
@field(resolver: "App\\GraphQL\\Mutations\\List\\Playlist\\PlaylistTrackMutator@store")
updatePlaylistTrack(
id: String! @bind(class: "App\\Models\\List\\Playlist\\PlaylistTrack", column: "hashid")
entry_id: Int
video_id: Int
next: String
previous: String
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): PlaylistTrack!
@canModel(ability: "update", injectArgs: true)
@validator(class: "App\\GraphQL\\Validators\\Mutation\\List\\Playlist\\UpdatePlaylistTrackValidator")
@field(resolver: "App\\GraphQL\\Mutations\\List\\Playlist\\PlaylistTrackMutator@update")
deletePlaylistTrack(
id: String! @bind(class: "App\\Models\\List\\Playlist\\PlaylistTrack", column: "hashid")
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): PlaylistTrack!
@canModel(ability: "delete", injectArgs: true)
@field(resolver: "App\\GraphQL\\Mutations\\List\\Playlist\\PlaylistTrackMutator@destroy")
restorePlaylistTrack(
id: String! @bind(class: "App\\Models\\List\\Playlist\\PlaylistTrack", column: "hashid")
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): PlaylistTrack!
@canModel(ability: "restore", injectArgs: true)
@field(resolver: "App\\GraphQL\\Mutations\\List\\Playlist\\PlaylistTrackMutator@restore")
forceDeletePlaylistTrack(
id: String! @bind(class: "App\\Models\\List\\Playlist\\PlaylistTrack", column: "hashid")
playlist: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): MessageResponse!
@canModel(ability: "forceDelete", injectArgs: true, model: "App\\Models\\List\\Playlist\\PlaylisTrack")
@field(resolver: "App\\GraphQL\\Mutations\\List\\Playlist\\PlaylistTrackMutator@forceDelete")
}
@@ -0,0 +1,62 @@
"Represents a user profile on the external site like MAL."
type ExternalProfile {
"The primary key of the resource"
id: Int! @rename(attribute: "profile_id")
"The title of the profile"
name: String!
"The site the profile belongs to"
site: ExternalProfileSite! @localizedEnum
"The state of who can see the profile"
visibility: ExternalProfileVisibility! @localizedEnum
externalentries: [ExternalEntry] @hasMany
user: User @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum ExternalProfileColumnsOrderable {
ID @enum(value: "profile_id")
NAME
SITE
VISIBILITY
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query @middleware(class: "App\\Http\\Middleware\\Api\\EnabledOnlyOnLocalhost") {
"Returns an external profile resource."
externalprofile(
id: String! @bind(class: "App\\Models\\List\\ExternalProfile")
): ExternalProfile @first
@canModel(ability: "view", injectArgs: true)
"Returns a listing of external profile resources."
externalprofiles(
name: String @eq
site: [ExternalProfileSite] @in
visibility: [ExternalProfileVisibility] @in
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: ">")
orderBy: _ @orderBy(columnsEnum: "ExternalProfileColumnsOrderable")
): [ExternalProfile!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\ExternalProfileBuilder@index")
@paginate
}
extend type Mutation @guard @middleware(class: "App\\Http\\Middleware\\Api\\EnabledOnlyOnLocalhost") @feature(name: "App\\Features\\AllowExternalProfileManagement") {
syncExternalProfile(
id: Int! @bind(class: "App\\Models\\List\\ExternalProfile", column: "profile_id")
): MessageResponse!
@canModel(ability: "update", injectArgs: true, model: "App\\Models\\List\\ExternalProfile")
@field(resolver: "App\\GraphQL\\Mutations\\List\\ExternalProfileMutator@sync")
}
+117
View File
@@ -0,0 +1,117 @@
"""
Represents a list of ordered tracks intended for continuous playback.
For example, a "/r/anime's Best OPs and EDs of 2022" playlist may contain a collection of tracks allowing the continuous playback of Best OP and ED nominations for the /r/anime Awards.
"""
type Playlist {
"The primary key of the resource"
id: String! @rename(attribute: "hashid")
"The title of the playlist"
name: String!
"The description of the playlist"
description: String
"The number of tracks belonging to the resource"
tracksCount: Int! @count(relation: "tracks")
"The existence of tracks belonging to the resource"
tracksExists: Boolean! @with(relation: "tracks") @field(resolver: "App\\GraphQL\\Resolvers\\ExistsResolver@resolve")
"The state of who can see the playlist"
visibility: PlaylistVisibility! @localizedEnum
"The number of views recorded for the resource"
viewsCount: Int! @with(relation: "viewAggregate") @field(resolver: "App\\GraphQL\\Resolvers\\ViewsCountResolver@resolve")
first: PlaylistTrack @belongsTo
images: [Image] @belongsToMany(type: CONNECTION, edgeType: PlaylistImageEdge)
last: PlaylistTrack @belongsTo
tracks: [PlaylistTrack] @hasMany
user: User @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
type PlaylistImageEdge {
node: Image!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum PlaylistColumnsOrderable {
ID @enum(value: "playlist_id")
NAME
DESCRIPTION
VISIBILITY
CREATED_AT
UPDATED_AT
DELETED_AT
}
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)
"Returns a listing of playlist resources."
playlists(
name: String @eq
visibility: [PlaylistVisibility] @in
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: ">")
orderBy: _ @orderBy(columnsEnum: "PlaylistColumnsOrderable")
): [Playlist!]!
@canModel(ability: "viewAny", injectArgs: true)
@builder(method: "App\\GraphQL\\Builders\\List\\PlaylistBuilder@index")
@paginate
}
extend type Mutation @guard {
createPlaylist(
name: String!
description: String
visibility: PlaylistVisibility!
): Playlist!
@canModel(ability: "create")
@validator(class: "App\\GraphQL\\Validators\\Mutation\\List\\CreatePlaylistValidator")
@field(resolver: "App\\GraphQL\\Mutations\\List\\PlaylistMutator@store")
updatePlaylist(
id: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
name: String
description: String
visibility: PlaylistVisibility
): Playlist!
@canModel(ability: "update", injectArgs: true)
@validator(class: "App\\GraphQL\\Validators\\Mutation\\List\\UpdatePlaylistValidator")
@field(resolver: "App\\GraphQL\\Mutations\\List\\PlaylistMutator@update")
deletePlaylist(
id: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): Playlist!
@canModel(ability: "delete", injectArgs: true)
@field(resolver: "App\\GraphQL\\Mutations\\List\\PlaylistMutator@destroy")
restorePlaylist(
id: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): Playlist!
@canModel(ability: "restore", injectArgs: true)
@field(resolver: "App\\GraphQL\\Mutations\\List\\PlaylistMutator@restore")
forceDeletePlaylist(
id: String! @bind(class: "App\\Models\\List\\Playlist", column: "hashid")
): MessageResponse!
@canModel(ability: "forceDelete", injectArgs: true, model: "App\\Models\\List\\Playlist")
@field(resolver: "App\\GraphQL\\Mutations\\List\\PlaylistMutator@forceDelete")
}
@@ -0,0 +1,9 @@
"Represents the association between a playlist and an image."
type PlaylistImage {
playlist: Playlist!
image: Image!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,9 @@
"Represents the association between an anime and an image."
type AnimeImage {
anime: Anime!
image: Image!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,11 @@
"Represents the association between an anime and an external resource."
type AnimeResource {
anime: Anime!
resource: ExternalResource!
"Used to distinguish resources that map to the same anime"
as: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,9 @@
"Represents the association between an anime and a series."
type AnimeSeries {
anime: Anime!
series: Series!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,9 @@
"Represents the association between an anime and a studio."
type AnimeStudio {
anime: Anime!
studio: Studio!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,9 @@
"Represents the association between an anime theme entry and a video."
type AnimeThemeEntryVideo {
animethemeentry: AnimeThemeEntry!
video: Video!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,11 @@
"Represents the association between an artist and an image."
type ArtistImage {
artist: Artist!
image: Image!
"Used to sort the artist images"
depth: Int
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,15 @@
"Represents the association of an artist and a group/unit."
type ArtistMember {
artist: Artist!
member: Artist!
"Used to distinguish member by alias"
alias: String
"Used to distinguish member by character"
as: String
"Used to extra annotation, like member role"
notes: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,11 @@
"Represents the association between an artist and an external resource."
type ArtistResource {
artist: Artist!
resource: ExternalResource!
"Used to distinguish resources that map to the same artist"
as: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,11 @@
"Represents the association between an song and an external resource."
type SongResource {
song: Song!
resource: ExternalResource!
"Used to distinguish resources that map to the same song"
as: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,9 @@
"Represents the association between a studio and an image."
type StudioImage {
studio: Studio!
image: Image!
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,11 @@
"Represents the association between a studio and an external resource."
type StudioResource {
studio: Studio!
resource: ExternalResource!
"Used to distinguish resources that map to the same studio"
as: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
}
@@ -0,0 +1,19 @@
"Represents a notification that is sent to the user."
type Notification {
"The date that the user read the notification"
readAt: DateTimeTz @rename(attribute: "read_at")
"The JSON data of the notification"
data: NotificationData!
"The user the notification belongs to"
user: User! @morphTo(relation: "notifiable")
}
"Represents the JSON data of the notification"
type NotificationData {
"The title of the notification"
title: String!
"The content of the notification"
body: String!
"The image URL to display with the notification"
image: String
}
@@ -0,0 +1,68 @@
"""
Represents a version of an anime theme.
For example, the ED theme of the Bakemonogatari anime has three anime theme entries to represent three versions.
"""
type AnimeThemeEntry {
"The primary key of the resource"
id: Int! @rename(attribute: "entry_id")
"The episodes that the theme is used for"
episodes: String
"Any additional information for this sequence"
notes: String
"Is not safe for work content included?"
nsfw: Boolean!
"Is content included that may spoil the viewer?"
spoiler: Boolean!
"The version number of the theme"
version: Int
animetheme: AnimeTheme @belongsTo
videos: [Video] @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type AnimeThemeEntryEdge {
node: AnimeThemeEntry!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum AnimeThemeEntryColumnsOrderable {
ID @enum(value: "entry_id")
EPISODES
NOTES
NSFW
SPOILER
VERSION
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of anime theme entry resources given fields."
animethemeentries(
isNsfw: Boolean @eq
isSpoiler: Boolean @eq
version_lesser: Int @where(key: "version", operator: "<")
version_greater: Int @where(key: "version", operator: ">")
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: ">")
orderBy: _ @orderBy(columnsEnum: "AnimeThemeEntryColumnsOrderable")
): [AnimeThemeEntry!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,46 @@
"""
Represents an alternate title or common abbreviation for an anime.
For example, the anime Bakemonogatari has the anime synonym "Monstory".
"""
type AnimeSynonym {
"The primary key of the resource"
id: Int! @rename(attribute: "synonym_id")
"The alternate title or common abbreviations"
name: String!
"The type of the synonym"
type: AnimeSynonymType! @localizedEnum
anime: Anime @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum AnimeSynonymColumnsOrderable {
ID @enum(value: "synonym_id")
NAME
TYPE
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of anime synonym resources given fields."
animesynonyms(
type: [AnimeSynonymType] @in
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: ">")
orderBy: _ @orderBy(columnsEnum: "AnimeSynonymColumnsOrderable")
): [AnimeSynonym!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,51 @@
"""
Represents an OP or ED sequence for an anime.
For example, the anime Bakemonogatari has five OP anime themes and one ED anime theme.
"""
type AnimeTheme {
"The primary key of the resource"
id: Int! @rename(attribute: "theme_id")
"The type of the sequence"
type: ThemeType! @localizedEnum
"The numeric ordering of the theme"
sequence: Int
anime: Anime @belongsTo
animethemeentries: [AnimeThemeEntry] @hasMany
group: ThemeGroup @belongsTo
song: Song @belongsTo
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum AnimeThemeColumnsOrderable {
ID @enum(value: "theme_id")
TYPE
SEQUENCE
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of anime theme resources given fields."
animethemes(
type: [ThemeType] @in
sequence_lesser: Int @where(key: "sequence", operator: "<")
sequence_greater: Int @where(key: "sequence", operator: ">")
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: ">")
orderBy: _ @orderBy(columnsEnum: "AnimeThemeColumnsOrderable")
): [AnimeTheme!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,49 @@
"""
Represents the link between an artist and a group related to the song credits.
For example, Sayuri Date is a member of Liella and has performed using the membership.
"""
type Membership {
"The primary key of the resource"
id: Int! @rename(attribute: "membership_id")
"The alias the artist is using for this membership"
alias: String
"The character the artist is performing as"
as: String
group: Artist! @belongsTo(relation: "artist")
member: Artist! @belongsTo
performances: [Performance] @morphMany
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum MembershipColumnsOrderable {
ID @enum(value: "membership_id")
ALIAS
AS
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of membership resources given fields."
memberships(
alias: String @eq
as: String @eq
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: ">")
orderBy: _ @orderBy(columnsEnum: "MembershipColumnsOrderable")
): [Membership!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,54 @@
"""
Represents the link between a song and an artist or membership.
For example, Liella has performed using memberships, with its members credited.
"""
type Performance {
"The primary key of the resource"
id: Int! @rename(attribute: "performance_id")
"The song the artist is performing"
song: Song @belongsTo
"The artist or membership performing"
artist: PerformanceArtistUnion! @morphTo
"The alias the artist is using for this performance"
alias: String
"The character the artist is performing as"
as: String
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Represents the resource type performing"
union PerformanceArtistUnion = Artist | Membership
enum PerformanceColumnsOrderable {
ID @enum(value: "performance_id")
ALIAS
AS
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of performance resources given fields."
performances(
alias: String @eq
as: String @eq
hasSong: [WhereHasConditions!] @whereHasConditions(relation: "song", columns: ["title"])
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: ">")
orderBy: _ @orderBy(columnsEnum: "PerformanceColumnsOrderable")
): [Performance!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,45 @@
"""
Represents an encoding script used to produce a video.
For example, the 2009/Summer/Bakemonogatari-OP1.txt video script represents the encoding script of the Bakemonogatari-OP1.webm video.
"""
type VideoScript {
"The primary key of the resource"
id: Int! @rename(attribute: "script_id")
"The path of the file in storage"
path: String!
"The URL to download the file from storage"
link: String!
video: Video @hasOne
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum VideoScriptColumnsOrderable {
ID @enum(value: "script_id")
PATH
LINK
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of script resources given fields."
scripts(
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: ">")
orderBy: _ @orderBy(columnsEnum: "VideoScriptColumnsOrderable")
): [VideoScript!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+104
View File
@@ -0,0 +1,104 @@
"""
Represents a production with at least one opening or ending sequence.
For example, Bakemonogatari is an anime production with five opening sequences and one ending sequence."
"""
type Anime {
"The primary key of the resource"
id: Int! @rename(attribute: "anime_id")
"The media format of the anime"
mediaFormat: AnimeMediaFormat @rename(attribute: "media_format") @localizedEnum
"The primary title of the anime"
name: String!
"The premiere season of the anime"
season: AnimeSeason @localizedEnum
"The URL slug & route key of the resource"
slug: String!
"The brief summary of the anime"
synopsis: String
"The premiere year of the anime"
year: Int
animesynonyms: [AnimeSynonym] @hasMany
animethemes: [AnimeTheme] @hasMany
images: [Image]! @belongsToMany(type: CONNECTION)
resources: [ExternalResource]! @belongsToMany(type: CONNECTION)
series: [Series]! @belongsToMany(type: CONNECTION)
studios: [Studio]! @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type AnimeEdge {
node: Anime!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
type AnimeYear {
winter: [Anime]
spring: [Anime]
summer: [Anime]
fall: [Anime]
}
enum AnimeColumnsOrderable {
ID @enum(value: "anime_id")
NAME
MEDIA_FORMAT
SEASON
SLUG
YEAR
CREATED_AT
UPDATED_AT
DELETED_AT
}
enum HasResources {
SITE
EXTERNAL_ID
}
extend type Query {
"Returns a listing of anime resources given fields."
animes(
search: String @search
name: String @where(operator: "like")
mediaFormat: [AnimeMediaFormat] @in(key: "media_format")
season: [AnimeSeason] @in
slug: String @eq
year_in: [Int] @in(key: "year")
year_notin: [Int] @notIn(key: "year")
year_lesser: Int @where(key: "year", operator: "<")
year_greater: Int @where(key: "year", operator: ">")
where: _ @whereConditions(columns: ["name"])
whereHas: [WhereHasConditions!] @whereHasConditions(columns: ["*"])
hasResources: [WhereHasConditions!] @whereHasConditions(columnsEnum: "HasResources")
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: ">")
orderBy: _ @orderBy(columnsEnum: "AnimeColumnsOrderable")
): [Anime!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
"Returns a listing of anime resources for a given year grouped by season and ordered by name."
animeyear(year: Int!): AnimeYear
@canModel(ability: "viewAny", model: "App\\Models\\Wiki\\Anime", injectArgs: true)
@field(resolver: "App\\GraphQL\\Queries\\AnimeYear@year")
"Returns a list of unique years from all anime resources."
animeyears: [Int!]!
@canModel(ability: "viewAny", model: "App\\Models\\Wiki\\Anime", injectArgs: true)
@field(resolver: "App\\GraphQL\\Queries\\AnimeYear@years")
}
+83
View File
@@ -0,0 +1,83 @@
"""
Represents a musical performer of anime sequences.
For example, Chiwa Saitou is the musical performer of the Bakemonogatari OP1 theme, among many others.
"""
type Artist {
"The primary key of the resource"
id: Int! @rename(attribute: "artist_id")
"The primary title of the artist"
name: String!
"The URL slug & route key of the resource"
slug: String!
"The brief information of the resource"
information: String
groups: [Artist] @belongsToMany(type: CONNECTION, edgeType: ArtistMemberEdge)
images: [Image] @belongsToMany(type: CONNECTION, edgeType: ArtistImageEdge)
members: [Artist] @belongsToMany(type: CONNECTION, edgeType: ArtistMemberEdge)
memberships: [Membership] @hasMany
performances: [Performance] @morphMany
resources: [ExternalResource]! @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type ArtistEdge {
node: Artist!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"Artist Member has extra pivots"
type ArtistMemberEdge {
node: Artist!
"Used to distinguish member by alias"
alias: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"Used to distinguish member by character"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"Used to extra annotation, like member role"
notes: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"Artist Image has an extra pivot that others image relationships don't"
type ArtistImageEdge {
node: Image!
"Used to sort the artist images"
depth: Int @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum ArtistColumnsOrderable {
ID @enum(value: "artist_id")
NAME
SLUG
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of artist resources given fields."
artists(
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: ">")
orderBy: _ @orderBy(columnsEnum: "ArtistColumnsOrderable")
): [Artist!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+58
View File
@@ -0,0 +1,58 @@
"""
Represents the audio track of a video.
For example, the audio Bakemonogatari-OP1.ogg represents the audio track of the Bakemonogatari-OP1.webm video.
"""
type Audio {
"The primary key of the resource"
id: Int! @rename(attribute: "audio_id")
"The basename of the file in storage"
basename: String!
"The filename of the file in storage"
filename: String!
"The media type of the file in storage"
mimetype: String!
"The path of the file in storage"
path: String!
"The size of the file in storage in Bytes"
size: Int!
"The URL to stream the file from storage"
link: String!
"The number of views recorded for the resource"
viewsCount: Int! @with(relation: "viewAggregate") @field(resolver: "App\\GraphQL\\Resolvers\\ViewsCountResolver@resolve")
videos: [Video] @hasMany
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum AudioColumnsOrderable {
ID @enum(value: "audio_id")
FILENAME
BASENAME
SIZE
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of audio resources given fields."
audios(
size_lesser: Int @where(key: "size", operator: "<")
size_greater: Int @where(key: "size", operator: ">")
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: ">")
orderBy: _ @orderBy(columnsEnum: "AudioColumnsOrderable")
): [Audio!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
@@ -0,0 +1,108 @@
"""
Represents a site with supplementary information for another resource such as an anime or artist.
For example, the Bakemonogatari anime has MyAnimeList, AniList and AniDB resources.
"""
type ExternalResource {
"The primary key of the resource"
id: Int! @rename(attribute: "resource_id")
"The primary key of the resource in the external site"
externalId: Int @rename(attribute: "external_id")
"The URL of the external site"
link: String!
"The external site that the resource belongs to"
site: ResourceSite! @localizedEnum
anime: [Anime] @belongsToMany(type: CONNECTION, edgeType: ExternalResourceAnimeEdge)
artists: [Artist] @belongsToMany(type: CONNECTION, edgeType: ExternalResourceArtistEdge)
songs: [Song] @belongsToMany(type: CONNECTION, edgeType: ExternalResourceSongEdge)
studios: [Studio] @belongsToMany(type: CONNECTION, edgeType: ExternalResourceStudioEdge)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type ExternalResourceEdge {
node: ExternalResource!
"Used to distinguish resources that map to the same resource"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"External Resource has an extra pivot field"
type ExternalResourceAnimeEdge {
node: Anime!
"Used to distinguish resources that map to the same anime"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"External Resource has an extra pivot field"
type ExternalResourceArtistEdge {
node: Artist!
"Used to distinguish resources that map to the same artist"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"External Resource has an extra pivot field"
type ExternalResourceSongEdge {
node: Song!
"Used to distinguish resources that map to the same song"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"External Resource has an extra pivot field"
type ExternalResourceStudioEdge {
node: Studio!
"Used to distinguish resources that map to the same studio"
as: String @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum ExternalResourceColumnsOrderable {
ID @enum(value: "resource_id")
SITE
EXTERNAL_ID
LINK
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of resources given fields."
externalresources(
externalId: Int @eq
site: [ResourceSite] @in
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: ">")
orderBy: _ @orderBy(columnsEnum: "ExternalResourceColumnsOrderable")
): [ExternalResource!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+46
View File
@@ -0,0 +1,46 @@
"""
Represents the group that accompanies a Theme.
For example, English Version is the group for english dubbed Theme.
"""
type ThemeGroup @model(class: "App\\Models\\Wiki\\Group") {
"The primary key of the resource"
id: Int! @rename(attribute: "group_id")
"The name of the group"
name: String!
"The slug of the group"
slug: String!
animethemes: [AnimeTheme] @hasMany
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
enum ThemeGroupColumnsOrderable {
ID @enum(value: "group_id")
NAME
SLUG
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of theme groups resources given fields."
themegroups(
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: ">")
orderBy: _ @orderBy(columnsEnum: "ThemeGroupColumnsOrderable")
): [ThemeGroup!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+70
View File
@@ -0,0 +1,70 @@
"""
Represents a visual component for another resource such as an anime or artist.
For example, the Bakemonogatari anime has two images to represent small and large cover images.
"""
type Image {
"The primary key of the resource"
id: Int! @rename(attribute: "image_id")
"The component that the resource is intended for"
facet: ImageFacet! @localizedEnum
"The path of the file in storage"
path: String!
"The URL to stream the file from storage"
link: String!
anime: [Anime] @belongsToMany(type: CONNECTION)
artists: [Artist] @belongsToMany(type: CONNECTION, edgeType: ImageArtistEdge)
studios: [Studio] @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type ImageEdge {
node: Image!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
"Image Artist has an extra pivot that others image relationships don't"
type ImageArtistEdge {
node: Artist!
"Used to sort the resource images"
depth: Int @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum ImageColumnsOrderable {
ID @enum(value: "image_id")
FACET
PATH
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of image resources given fields."
images(
facet: [ImageFacet] @in
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: ">")
orderBy: _ @orderBy(columnsEnum: "ImageColumnsOrderable")
): [Image!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+54
View File
@@ -0,0 +1,54 @@
"""
Represents a collection of related anime.
For example, the Monogatari series is the collection of the Bakemonogatari anime and its related productions.
"""
type Series {
"The primary key of the resource"
id: Int! @rename(attribute: "series_id")
"The primary title of the series"
name: String!
"The URL slug & route key of the resource"
slug: String!
anime: [Anime] @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type SeriesEdge {
node: Series!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum SeriesColumnsOrderable {
ID @enum(value: "series_id")
NAME
SLUG
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of series resources given fields."
series(
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: ">")
orderBy: _ @orderBy(columnsEnum: "SeriesColumnsOrderable")
): [Series!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+54
View File
@@ -0,0 +1,54 @@
"""
Represents the composition that accompanies an AnimeTheme.
For example, Staple Stable is the song for the Bakemonogatari OP1 AnimeTheme.
"""
type Song {
"The primary key of the resource"
id: Int! @rename(attribute: "song_id")
"The name of the composition"
title: String
animethemes: [AnimeTheme] @hasMany
performances: [Performance] @hasMany
resources: ExternalResourceConnection! @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type SongEdge {
node: Song!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum SongColumnsOrderable {
ID @enum(value: "song_id")
TITLE
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of song resources given fields."
songs(
title: String @eq
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: ">")
orderBy: _ @orderBy(columnsEnum: "SongColumnsOrderable")
): [Song!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+56
View File
@@ -0,0 +1,56 @@
"""
Represents a company that produces anime.
For example, Shaft is the studio that produced the anime Bakemonogatari.
"""
type Studio {
"The primary key of the resource"
id: Int! @rename(attribute: "studio_id")
"The primary title of the studio"
name: String!
"The URL slug & route key of the resource"
slug: String!
anime: [Anime] @belongsToMany(type: CONNECTION)
images: [Image] @belongsToMany(type: CONNECTION)
resources: [ExternalResource] @belongsToMany(type: CONNECTION)
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type StudioEdge {
node: Studio!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum StudioColumnsOrderable {
ID @enum(value: "studio_id")
NAME
SLUG
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of studio resources given fields."
studios(
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: ">")
orderBy: _ @orderBy(columnsEnum: "StudioColumnsOrderable")
): [Studio!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+101
View File
@@ -0,0 +1,101 @@
"""
Represents a WebM of an anime theme.
For example, the video Bakemonogatari-OP1.webm represents the WebM of the Bakemonogatari OP1 theme.
"""
type Video {
"The primary key of the resource"
id: Int! @rename(attribute: "video_id")
"The basename of the file in storage"
basename: String!
"The filename of the file in storage"
filename: String!
"Does the video include subtitles of song lyrics?"
lyrics: Boolean!
"The media type of the file in storage"
mimetype: String
"Is the video creditless?"
nc: Boolean!
"The degree to which the sequence and episode content overlap"
overlap: VideoOverlap @localizedEnum
"The path of the file in storage"
path: String!
"The frame height of the file in storage"
resolution: Int
"The size of the file in storage in Bytes"
size: Int
"Where did this video come from?"
source: VideoSource @localizedEnum
"Does the video include subtitles of dialogue?"
subbed: Boolean!
"Is the video an uncensored version of a censored sequence?"
uncen: Boolean!
"The attributes used to distinguish the file within the context of a theme"
tags: String @field(resolver: "App\\GraphQL\\Resolvers\\ImplodeArrayResolver@resolve")
"The URL to stream the file from storage"
link: String!
"The number of views recorded for the resource"
viewsCount: Int! @with(relation: "viewAggregate") @field(resolver: "App\\GraphQL\\Resolvers\\ViewsCountResolver@resolve")
animethemeentries: [AnimeThemeEntry] @belongsToMany(type: CONNECTION)
videoscript: VideoScript @hasOne
"The date that the resource was created"
createdAt: DateTimeTz @rename(attribute: "created_at")
"The date that the resource was last modified"
updatedAt: DateTimeTz @rename(attribute: "updated_at")
"The date that the resource was deleted"
deletedAt: DateTimeTz @rename(attribute: "deleted_at")
}
"Default edge to use in simple belongs to many relationships"
type VideoEdge {
node: Video!
"The date that the resource was last modified"
createdAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
"The date that the resource was deleted"
updatedAt: DateTimeTz @field(resolver: "App\\GraphQL\\Resolvers\\PivotResolver@resolve")
}
enum VideoColumnsOrderable {
ID @enum(value: "video_id")
FILENAME
BASENAME
LYRICS
NC
OVERLAP
RESOLUTION
SIZE
SOURCE
SUBBED
TAGS
UNCEN
CREATED_AT
UPDATED_AT
DELETED_AT
}
extend type Query {
"Returns a listing of video resources given fields."
videos(
isLyrics: Boolean @eq
isNc: Boolean @eq
isSubbed: Boolean @eq
isUncen: Boolean @eq
overlap: [VideoOverlap] @in
source: [VideoSource] @in
resolution_in: [Int] @in(key: "resolution")
resolution_lesser: Int @where(key: "resolution", operator: "<")
resolution_greater: Int @where(key: "resolution", operator: ">")
size_lesser: Int @where(key: "size", operator: "<")
size_greater: Int @where(key: "size", operator: ">")
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: ">")
orderBy: _ @orderBy(columnsEnum: "VideoColumnsOrderable")
): [Video!]!
@canModel(ability: "viewAny", injectArgs: true)
@softDeletes
@paginate
}
+47
View File
@@ -0,0 +1,47 @@
"A datetime and timezone string in ISO 8601 format `Y-m-dTH:i:sP`, e.g. `2020-04-20T13:53:12+02:00`."
scalar DateTimeTz @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTimeTz")
"Indicates what fields are available at the top level of a query operation."
type Query @middleware(class: "App\\GraphQL\\Middleware\\MaxCount") {
"Returns a listing of resources that match a given search term."
search(search: String @search, perPage: Int, page: Int): Search!
@field(resolver: "App\\GraphQL\\Queries\\Search")
}
type Search {
anime: [Anime!]!
artists: [Artist!]!
animethemes: [AnimeTheme!]!
playlists: [Playlist!]!
series: [Series!]!
songs: [Song!]!
studios: [Studio!]!
videos: [Video!]!
}
type MessageResponse {
message: String
}
input WhereHasConditions {
relation: String!
where: [WhereConditions!]
AND: [WhereHasConditions!]
OR: [WhereHasConditions!]
}
#import Admin/*.graphql
#import Auth/*.graphql
#import Document/*.graphql
#import List/*.graphql
#import List/External/*.graphql
#import List/Playlist/*.graphql
#import User/*.graphql
#import Wiki/*.graphql
#import Wiki/Anime/*.graphql
#import Wiki/Anime/Theme/*.graphql
#import Wiki/Song/*.graphql
#import Wiki/Video/*.graphql
#import Pivot/List/*.graphql
#import Pivot/Wiki/*.graphql
+15
View File
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Types\Definition;
use Attribute;
#[Attribute(Attribute::TARGET_ALL)]
class Hidden
{
public function __construct(
public bool $hidden = true,
) {}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Types;
use App\GraphQL\Types\Definition\Hidden;
use Exception;
use GraphQL\Language\AST\EnumTypeDefinitionNode;
use GraphQL\Type\Definition\Deprecated;
use GraphQL\Type\Definition\Description;
use GraphQL\Type\Definition\EnumType as BaseEnumType;
use GraphQL\Utils\PhpDoc;
use ReflectionClass;
use ReflectionClassConstant;
use UnitEnum;
/**
* Class EnumType
*/
class EnumType extends BaseEnumType
{
public const MULTIPLE_DESCRIPTIONS_DISALLOWED = 'Using more than 1 Description attribute is not supported.';
public const MULTIPLE_DEPRECATIONS_DISALLOWED = 'Using more than 1 Deprecated attribute is not supported.';
/**
* The enum class.
*
* @var class-string<UnitEnum>
*/
protected string $enumClass;
public function __construct(
string $enumClass,
?string $name = null,
?string $description = null,
?EnumTypeDefinitionNode $astNode = null,
?array $extensionASTNodes = null
) {
$this->enumClass = $enumClass;
$reflection = new \ReflectionEnum($enumClass);
$enumDefinitions = [];
foreach ($enumClass::cases() as $case) {
if ($this->hidden($reflection->getCase($case->name))) {
continue;
}
$enumDefinitions[$case->name] = [
'value' => $case->value,
'description' => $this->extractDescription($reflection->getCase($case->name)),
'deprecationReason' => $this->deprecationReason($reflection->getCase($case->name)),
];
}
parent::__construct([
'name' => $name ?? $this->baseName($enumClass),
'values' => $enumDefinitions,
'description' => $description ?? $this->extractDescription($reflection),
'astNode' => $astNode,
'extensionASTNodes' => $extensionASTNodes,
]);
}
/**
* @param mixed $value
* @return string
*/
public function serialize($value): string
{
return $value;
}
/**
* @param class-string $class
* @return string
*/
protected function baseName(string $class): string
{
$parts = explode('\\', $class);
return end($parts);
}
/**
* @param ReflectionClassConstant|ReflectionClass<UnitEnum> $reflection
* @return string|null
*
* @throws Exception
*/
protected function extractDescription(ReflectionClassConstant|ReflectionClass $reflection): ?string
{
$attributes = $reflection->getAttributes(Description::class);
if (count($attributes) === 1) {
return $attributes[0]->newInstance()->description;
}
if (count($attributes) > 1) {
throw new Exception(self::MULTIPLE_DESCRIPTIONS_DISALLOWED);
}
$comment = $reflection->getDocComment();
$unpadded = PhpDoc::unpad($comment);
return PhpDoc::unwrap($unpadded);
}
/**
* @param ReflectionClassConstant $reflection
* @return string|null
*
* @throws Exception
*/
protected function deprecationReason(ReflectionClassConstant $reflection): ?string
{
$attributes = $reflection->getAttributes(Deprecated::class);
if (count($attributes) === 1) {
return $attributes[0]->newInstance()->reason;
}
if (count($attributes) > 1) {
throw new Exception(self::MULTIPLE_DEPRECATIONS_DISALLOWED);
}
return null;
}
/**
* Determine whether the enum case should be hidden.
*
* @param ReflectionClassConstant $reflection
* @return bool
*
* @throws Exception
*/
protected function hidden(ReflectionClassConstant $reflection): bool
{
$attributes = $reflection->getAttributes(Hidden::class);
if (count($attributes) === 1) {
return $attributes[0]->newInstance()->hidden;
}
if (count($attributes) > 1) {
throw new Exception(self::MULTIPLE_DESCRIPTIONS_DISALLOWED);
}
return false;
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace App\GraphQL\Validators\Mutation\List;
use App\Enums\Models\List\PlaylistVisibility;
use App\Models\List\Playlist;
use App\Rules\ModerationRule;
use Illuminate\Validation\Rules\Enum;
use Nuwave\Lighthouse\Validation\Validator;
/**
* Class CreatePlaylistValidator.
*/
class CreatePlaylistValidator extends Validator
{
/**
* Specify validation rules for the arguments.
*
* @return array<string, array<int, mixed>>
*/
public function rules(): array
{
return [
Playlist::ATTRIBUTE_NAME => [
'required',
'string',
'max:192',
new ModerationRule(),
],
Playlist::ATTRIBUTE_DESCRIPTION => [
'nullable',
'string',
'max:1000',
new ModerationRule(),
],
Playlist::ATTRIBUTE_VISIBILITY => [
'required',
new Enum(PlaylistVisibility::class),
],
];
}
}

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