mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: add anime dates & mod notes (#1213)
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Actions\Models\Wiki\Anime;
|
||||
|
||||
use App\Actions\ActionResult;
|
||||
use App\Enums\Actions\ActionStatus;
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class AnimeDateAction
|
||||
{
|
||||
/**
|
||||
* @param Collection<int, Anime> $anime
|
||||
*/
|
||||
public function handle(Collection $anime): ActionResult
|
||||
{
|
||||
$anilistIds = $anime
|
||||
->map(fn (Anime $anime) => $anime->resources->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST->value)->first()?->external_id)
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$query = <<<'GRAPHQL'
|
||||
query AnimeDates($ids: [Int]) {
|
||||
Page(perPage: 20) {
|
||||
media(id_in: $ids, type: ANIME) {
|
||||
id
|
||||
startDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
endDate {
|
||||
year
|
||||
month
|
||||
day
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GRAPHQL;
|
||||
|
||||
$response = Http::post('https://graphql.anilist.co', [
|
||||
'query' => $query,
|
||||
'variables' => [
|
||||
'ids' => $anilistIds,
|
||||
],
|
||||
])
|
||||
->throw();
|
||||
|
||||
if (! $response->ok() || $response->json('errors')) {
|
||||
return new ActionResult(ActionStatus::FAILED, $response->json('errors.0.message'));
|
||||
}
|
||||
|
||||
foreach ($response->json('data.Page.media') as $media) {
|
||||
$animeToUpdate = $anime->first(
|
||||
fn (Anime $anime): bool => $anime->resources->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST->value)->first()?->external_id === Arr::integer($media, 'id')
|
||||
);
|
||||
|
||||
$animeToUpdate?->update([
|
||||
'start_date' => Arr::get($media, 'startDate'),
|
||||
'end_date' => Arr::get($media, 'endDate'),
|
||||
]);
|
||||
}
|
||||
|
||||
return new ActionResult(ActionStatus::PASSED);
|
||||
}
|
||||
}
|
||||
@@ -196,11 +196,9 @@ class BackfillAudioAction extends BackfillAction
|
||||
|
||||
$orderByNameQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_NAME]);
|
||||
$orderBySeasonQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_SEASON]);
|
||||
$orderByYearQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_YEAR]);
|
||||
$orderByformatQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_FORMAT]);
|
||||
$orderByYearQuery = $sortRelation->getRelationExistenceQuery($sortRelation->getQuery(), $builder, [Anime::ATTRIBUTE_START_DATE]);
|
||||
|
||||
return $builder->whereHas(AnimeTheme::RELATION_VIDEOS, fn (Builder $relationBuilder) => $relationBuilder->whereKey($this->getModel()))
|
||||
->orderBy($orderByformatQuery->toBase())
|
||||
->orderBy($orderByYearQuery->toBase())
|
||||
->orderBy($orderBySeasonQuery->toBase())
|
||||
->orderBy($orderByNameQuery->toBase())
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Casts;
|
||||
|
||||
use App\ValueObjects\FuzzyDate;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class AsFuzzyDate implements CastsAttributes
|
||||
{
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ?FuzzyDate
|
||||
{
|
||||
return FuzzyDate::fromString($value);
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): ?string
|
||||
{
|
||||
if (blank($value) || $value === '00000000') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value instanceof FuzzyDate) {
|
||||
return $value->toString();
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return FuzzyDate::fromString($value)?->toString();
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return new FuzzyDate(
|
||||
(int) Arr::get($value, 'year') ?: null,
|
||||
(int) Arr::get($value, 'month') ?: null,
|
||||
(int) Arr::get($value, 'day') ?: null,
|
||||
)->toString();
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid FuzzyDate value.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Console\Commands\Models;
|
||||
|
||||
use App\Actions\ActionResult;
|
||||
use App\Actions\Models\Wiki\Anime\AnimeDateAction;
|
||||
use App\Console\Commands\BaseCommand;
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Facades\Validator as ValidatorFacade;
|
||||
use Illuminate\Support\Sleep;
|
||||
|
||||
#[Signature('anime:update-dates')]
|
||||
#[Description('Updates anime dates')]
|
||||
class UpdateAnimeDateCommand extends BaseCommand
|
||||
{
|
||||
public function handle(): int
|
||||
{
|
||||
$failed = false;
|
||||
|
||||
Anime::query()
|
||||
->where(function (Builder $query): void {
|
||||
$query
|
||||
->whereNull(Anime::ATTRIBUTE_START_DATE)
|
||||
->orWhereRaw('RIGHT('.Anime::ATTRIBUTE_START_DATE.', 2) = ?', ['00'])
|
||||
->orWhereNull(Anime::ATTRIBUTE_END_DATE)
|
||||
->orWhereRaw('RIGHT('.Anime::ATTRIBUTE_END_DATE.', 2) = ?', ['00']);
|
||||
})
|
||||
->with([
|
||||
Anime::RELATION_RESOURCES => fn (Relation $query) => $query->where(ExternalResource::ATTRIBUTE_SITE, ResourceSite::ANILIST->value),
|
||||
])
|
||||
->orderBy(Anime::ATTRIBUTE_ID)
|
||||
->chunk(20, function (Collection $anime) use (&$failed) {
|
||||
$ids = $anime->pluck(Anime::ATTRIBUTE_ID)->values()->implode(', ');
|
||||
|
||||
$this->info('Anime IDs: '.$ids);
|
||||
|
||||
$action = new AnimeDateAction();
|
||||
|
||||
$result = Anime::withoutEvents(fn () => Anime::withoutTimestamps(fn (): ActionResult => $action->handle($anime)));
|
||||
|
||||
if ($result->hasFailed()) {
|
||||
$this->error('Action failed: '.$result->getMessage());
|
||||
|
||||
$failed = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Sleep::sleep(5);
|
||||
});
|
||||
|
||||
return $failed ? 1 : 0;
|
||||
}
|
||||
|
||||
protected function validator(): Validator
|
||||
{
|
||||
return ValidatorFacade::make($this->options(), []);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace App\Console;
|
||||
|
||||
use App\Console\Commands\Models\SyncLikeAggregatesCommand;
|
||||
use App\Console\Commands\Models\UpdateAnimeDateCommand;
|
||||
use App\Console\Commands\Repositories\Storage\Admin\DumpReconcileCommand;
|
||||
use App\Console\Commands\Storage\Admin\AdminDumpCommand;
|
||||
use App\Console\Commands\Storage\Admin\AuthDumpCommand;
|
||||
@@ -144,6 +145,12 @@ class Kernel extends ConsoleKernel
|
||||
->storeOutput()
|
||||
->daily();
|
||||
|
||||
$schedule->command(UpdateAnimeDateCommand::class)
|
||||
->withoutOverlapping()
|
||||
->runInBackground()
|
||||
->storeOutput()
|
||||
->dailyAt('23:30');
|
||||
|
||||
$schedule->command(UpdateDisposableDomainsCommand::class)
|
||||
->withoutOverlapping()
|
||||
->runInBackground()
|
||||
|
||||
@@ -9,10 +9,12 @@ use App\Enums\Models\Wiki\AnimeFormat;
|
||||
use App\Enums\Models\Wiki\AnimeSeason;
|
||||
use App\GraphQL\Filter\EnumFilter;
|
||||
use App\GraphQL\Filter\Filter;
|
||||
use App\GraphQL\Filter\FuzzyDateFilter;
|
||||
use App\GraphQL\Filter\IntFilter;
|
||||
use App\GraphQL\Filter\StringFilter;
|
||||
use App\GraphQL\Filter\TimestampFilter;
|
||||
use App\Models\Wiki\Anime;
|
||||
use GraphQL\Type\Definition\Deprecated;
|
||||
|
||||
enum AnimeFilterableColumns implements EnumFilterableColumns
|
||||
{
|
||||
@@ -20,7 +22,10 @@ enum AnimeFilterableColumns implements EnumFilterableColumns
|
||||
case NAME;
|
||||
case FORMAT;
|
||||
case SEASON;
|
||||
case START_DATE;
|
||||
case END_DATE;
|
||||
case SYNOPSIS;
|
||||
#[Deprecated('Use the `START_DATE` filter instead.')]
|
||||
case YEAR;
|
||||
case CREATED_AT;
|
||||
case UPDATED_AT;
|
||||
@@ -32,6 +37,8 @@ enum AnimeFilterableColumns implements EnumFilterableColumns
|
||||
self::NAME => new StringFilter($this->name, Anime::ATTRIBUTE_NAME),
|
||||
self::FORMAT => new EnumFilter($this->name, AnimeFormat::class, Anime::ATTRIBUTE_FORMAT),
|
||||
self::SEASON => new EnumFilter($this->name, AnimeSeason::class, Anime::ATTRIBUTE_SEASON),
|
||||
self::START_DATE => new FuzzyDateFilter($this->name, Anime::ATTRIBUTE_START_DATE),
|
||||
self::END_DATE => new FuzzyDateFilter($this->name, Anime::ATTRIBUTE_END_DATE),
|
||||
self::YEAR => new IntFilter($this->name, Anime::ATTRIBUTE_YEAR),
|
||||
self::SYNOPSIS => new StringFilter($this->name, Anime::ATTRIBUTE_SYNOPSIS),
|
||||
self::CREATED_AT => new TimestampFilter($this->name, Anime::ATTRIBUTE_CREATED_AT),
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\GraphQL\Sort\FieldSortCriteria;
|
||||
use App\GraphQL\Sort\RandomSortCriteria;
|
||||
use App\GraphQL\Sort\SortCriteria;
|
||||
use App\Models\Wiki\Anime;
|
||||
use GraphQL\Type\Definition\Deprecated;
|
||||
|
||||
enum AnimeSort implements EnumSort
|
||||
{
|
||||
@@ -17,7 +18,13 @@ enum AnimeSort implements EnumSort
|
||||
case ID_DESC;
|
||||
case NAME;
|
||||
case NAME_DESC;
|
||||
case START_DATE;
|
||||
case START_DATE_DESC;
|
||||
case END_DATE;
|
||||
case END_DATE_DESC;
|
||||
#[Deprecated('Use the `START_DATE` sort instead.')]
|
||||
case YEAR;
|
||||
#[Deprecated('Use the `START_DATE_DESC` sort instead.')]
|
||||
case YEAR_DESC;
|
||||
case CREATED_AT;
|
||||
case CREATED_AT_DESC;
|
||||
@@ -34,6 +41,10 @@ enum AnimeSort implements EnumSort
|
||||
self::NAME_DESC => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_NAME, SortDirection::DESC),
|
||||
self::YEAR => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_YEAR),
|
||||
self::YEAR_DESC => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_YEAR, SortDirection::DESC),
|
||||
self::START_DATE => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_START_DATE),
|
||||
self::START_DATE_DESC => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_START_DATE, SortDirection::DESC),
|
||||
self::END_DATE => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_END_DATE),
|
||||
self::END_DATE_DESC => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_END_DATE, SortDirection::DESC),
|
||||
self::CREATED_AT => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_CREATED_AT),
|
||||
self::CREATED_AT_DESC => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_CREATED_AT, SortDirection::DESC),
|
||||
self::UPDATED_AT => new FieldSortCriteria($this->name, Anime::ATTRIBUTE_UPDATED_AT),
|
||||
|
||||
@@ -27,10 +27,12 @@ use App\Filament\Resources\Wiki\Anime\RelationManagers\SynonymAnimeRelationManag
|
||||
use App\Filament\Resources\Wiki\Anime\RelationManagers\ThemeAnimeRelationManager;
|
||||
use App\Models\Wiki\Anime;
|
||||
use Filament\Forms\Components\MarkdownEditor;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\QueryBuilder\Constraints\NumberConstraint;
|
||||
use Filament\QueryBuilder\Constraints\SelectConstraint;
|
||||
use Filament\QueryBuilder\Constraints\TextConstraint;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
use Filament\Schemas\Components\Fieldset;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Schema;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
@@ -99,16 +101,6 @@ class AnimeResource extends BaseResource
|
||||
->label(__('filament.fields.anime.slug.name'))
|
||||
->helperText(__('filament.fields.anime.slug.help')),
|
||||
|
||||
TextInput::make(Anime::ATTRIBUTE_YEAR)
|
||||
->label(__('filament.fields.anime.year.name'))
|
||||
->helperText(__('filament.fields.anime.year.help'))
|
||||
->required()
|
||||
->integer()
|
||||
->length(4)
|
||||
->default(date('Y'))
|
||||
->minValue(1960)
|
||||
->maxValue(intval(date('Y')) + 1),
|
||||
|
||||
Select::make(Anime::ATTRIBUTE_SEASON)
|
||||
->label(__('filament.fields.anime.season.name'))
|
||||
->helperText(__('filament.fields.anime.season.help'))
|
||||
@@ -125,11 +117,74 @@ class AnimeResource extends BaseResource
|
||||
->options(AnimeFormat::class)
|
||||
->required(),
|
||||
|
||||
Fieldset::make(Anime::ATTRIBUTE_START_DATE)
|
||||
->label(__('filament.fields.anime.start_date.name'))
|
||||
->columns([
|
||||
'xl' => 3,
|
||||
])
|
||||
->statePath('start_date')
|
||||
->formatStateUsing(fn (?Anime $record) => $record?->start_date?->toArray())
|
||||
->schema([
|
||||
TextInput::make('year')
|
||||
->label(__('filament.fields.base.year'))
|
||||
->required()
|
||||
->integer()
|
||||
->default(intval(date('Y')))
|
||||
->minValue(1960)
|
||||
->maxValue(intval(date('Y')) + 4),
|
||||
|
||||
TextInput::make('month')
|
||||
->label(__('filament.fields.base.month'))
|
||||
->integer()
|
||||
->minValue(1)
|
||||
->maxValue(12),
|
||||
|
||||
TextInput::make('day')
|
||||
->label(__('filament.fields.base.day'))
|
||||
->integer()
|
||||
->minValue(1)
|
||||
->maxValue(31),
|
||||
]),
|
||||
|
||||
Fieldset::make(Anime::ATTRIBUTE_END_DATE)
|
||||
->label(__('filament.fields.anime.end_date.name'))
|
||||
->columns([
|
||||
'xl' => 3,
|
||||
])
|
||||
->statePath('end_date')
|
||||
->formatStateUsing(fn (?Anime $record) => $record->end_date?->toArray())
|
||||
->schema([
|
||||
TextInput::make('year')
|
||||
->label(__('filament.fields.base.year'))
|
||||
->integer()
|
||||
->length(4)
|
||||
->minValue(1960)
|
||||
->maxValue(intval(date('Y')) + 4),
|
||||
|
||||
TextInput::make('month')
|
||||
->label(__('filament.fields.base.month'))
|
||||
->integer()
|
||||
->minValue(1)
|
||||
->maxValue(12),
|
||||
|
||||
TextInput::make('day')
|
||||
->label(__('filament.fields.base.day'))
|
||||
->integer()
|
||||
->minValue(1)
|
||||
->maxValue(31),
|
||||
]),
|
||||
|
||||
MarkdownEditor::make(Anime::ATTRIBUTE_SYNOPSIS)
|
||||
->label(__('filament.fields.anime.synopsis.name'))
|
||||
->helperText(__('filament.fields.anime.synopsis.help'))
|
||||
->columnSpan(2)
|
||||
->maxLength(65535),
|
||||
|
||||
Textarea::make(Anime::ATTRIBUTE_MOD_NOTES)
|
||||
->label(__('filament.fields.anime.mod_notes.name'))
|
||||
->helperText(__('filament.fields.anime.mod_notes.help'))
|
||||
->columnSpan(2)
|
||||
->maxLength(65535),
|
||||
])
|
||||
->columns(2);
|
||||
}
|
||||
@@ -152,8 +207,8 @@ class AnimeResource extends BaseResource
|
||||
->limit(20)
|
||||
->tooltip(fn (string $state): string => $state),
|
||||
|
||||
TextColumn::make(Anime::ATTRIBUTE_YEAR)
|
||||
->label(__('filament.fields.anime.year.name')),
|
||||
TextColumn::make('year')
|
||||
->label(__('filament.fields.base.year')),
|
||||
|
||||
TextColumn::make(Anime::ATTRIBUTE_SEASON)
|
||||
->label(__('filament.fields.anime.season.name'))
|
||||
@@ -184,9 +239,6 @@ class AnimeResource extends BaseResource
|
||||
->label(__('filament.fields.anime.slug.name'))
|
||||
->limit(60),
|
||||
|
||||
TextEntry::make(Anime::ATTRIBUTE_YEAR)
|
||||
->label(__('filament.fields.anime.year.name')),
|
||||
|
||||
TextEntry::make(Anime::ATTRIBUTE_SEASON)
|
||||
->label(__('filament.fields.anime.season.name'))
|
||||
->formatStateUsing(fn (AnimeSeason $state): string => $state->localizeStyled())
|
||||
@@ -196,10 +248,52 @@ class AnimeResource extends BaseResource
|
||||
->label(__('filament.fields.anime.format.name'))
|
||||
->formatStateUsing(fn (AnimeFormat $state): ?string => $state->localize()),
|
||||
|
||||
Fieldset::make(Anime::ATTRIBUTE_START_DATE)
|
||||
->label(__('filament.fields.anime.start_date.name'))
|
||||
->columns([
|
||||
'xl' => 3,
|
||||
])
|
||||
->columnSpanFull()
|
||||
->statePath('start_date')
|
||||
->formatStateUsing(fn (?Anime $record) => $record?->start_date?->toArray())
|
||||
->schema([
|
||||
TextEntry::make('year')
|
||||
->label(__('filament.fields.base.year')),
|
||||
|
||||
TextEntry::make('month')
|
||||
->label(__('filament.fields.base.month')),
|
||||
|
||||
TextEntry::make('day')
|
||||
->label(__('filament.fields.base.day')),
|
||||
]),
|
||||
|
||||
Fieldset::make(Anime::ATTRIBUTE_END_DATE)
|
||||
->label(__('filament.fields.anime.end_date.name'))
|
||||
->columns([
|
||||
'xl' => 3,
|
||||
])
|
||||
->columnSpanFull()
|
||||
->statePath('end_date')
|
||||
->formatStateUsing(fn (?Anime $record) => $record->end_date?->toArray())
|
||||
->schema([
|
||||
TextEntry::make('year')
|
||||
->label(__('filament.fields.base.year')),
|
||||
|
||||
TextEntry::make('month')
|
||||
->label(__('filament.fields.base.month')),
|
||||
|
||||
TextEntry::make('day')
|
||||
->label(__('filament.fields.base.day')),
|
||||
]),
|
||||
|
||||
TextEntry::make(Anime::ATTRIBUTE_SYNOPSIS)
|
||||
->label(__('filament.fields.anime.synopsis.name'))
|
||||
->markdown()
|
||||
->columnSpanFull(),
|
||||
|
||||
TextEntry::make(Anime::ATTRIBUTE_MOD_NOTES)
|
||||
->label(__('filament.fields.anime.mod_notes.name'))
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->columns(3),
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Filter;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class FuzzyDateFilter extends Filter
|
||||
{
|
||||
/**
|
||||
* Convert filter values if needed.
|
||||
*/
|
||||
protected function convertFilterValues(array $filterValues): array
|
||||
{
|
||||
return Arr::map(
|
||||
$filterValues,
|
||||
fn (string|int $filterValue): ?int => filter_var($filterValue, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules for the filter.
|
||||
*/
|
||||
protected function getRules(): array
|
||||
{
|
||||
return [
|
||||
'required',
|
||||
'integer',
|
||||
'min:0',
|
||||
'max:99991231',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -20,19 +20,28 @@ class AnimeYearsQuery
|
||||
/** @param array{} $args */
|
||||
public function __invoke(null $_, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Collection
|
||||
{
|
||||
$year = Arr::get($args, 'year');
|
||||
$years = Arr::wrap(Arr::get($args, 'year'));
|
||||
|
||||
$fieldSelection = $resolveInfo->getFieldSelection(1);
|
||||
|
||||
Validator::make(['year' => $year], ['year' => new AnimeYearRule($fieldSelection)])
|
||||
Validator::make(['year' => $years], ['year' => new AnimeYearRule($fieldSelection)])
|
||||
->validate();
|
||||
|
||||
return Anime::query()
|
||||
->distinct([Anime::ATTRIBUTE_YEAR, Anime::ATTRIBUTE_SEASON])
|
||||
->orderBy(Anime::ATTRIBUTE_YEAR)
|
||||
->when($year !== null, fn (Builder $query) => $query->whereIn(Anime::ATTRIBUTE_YEAR, $year))
|
||||
->get([Anime::ATTRIBUTE_YEAR, Anime::ATTRIBUTE_SEASON])
|
||||
->groupBy(Anime::ATTRIBUTE_YEAR)
|
||||
->whereNotNull(Anime::ATTRIBUTE_START_DATE)
|
||||
->whereNotNull(Anime::ATTRIBUTE_SEASON)
|
||||
->when($years !== null, function (Builder $query) use ($years): void {
|
||||
$query->where(function (Builder $query) use ($years): void {
|
||||
foreach ($years as $singleYear) {
|
||||
$query->orWhereBetween(Anime::ATTRIBUTE_START_DATE, [
|
||||
"{$singleYear}0000",
|
||||
"{$singleYear}1231",
|
||||
]);
|
||||
}
|
||||
});
|
||||
})
|
||||
->get([Anime::ATTRIBUTE_START_DATE, Anime::ATTRIBUTE_SEASON])
|
||||
->groupBy(fn (Anime $anime): ?int => $anime->start_date?->year)
|
||||
->map(fn (Collection $items, int $year): array => [
|
||||
'year' => $year,
|
||||
|
||||
@@ -40,19 +49,20 @@ class AnimeYearsQuery
|
||||
->map(fn (Anime $anime): array => [
|
||||
'season' => $anime->season,
|
||||
'seasonLocalized' => $anime->season->localize(),
|
||||
'year' => $year, // Needed to query animes on the 'seasons' field.
|
||||
'year' => $year,
|
||||
])
|
||||
->unique(Anime::ATTRIBUTE_SEASON)
|
||||
->unique('season')
|
||||
->values()
|
||||
->toArray(),
|
||||
])->values();
|
||||
])
|
||||
->values();
|
||||
}
|
||||
|
||||
/** @param array{} $args */
|
||||
public function resolveSeasonField(array $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): ?array
|
||||
{
|
||||
$season = Arr::get($args, 'season');
|
||||
$year = Arr::get($root, 'year');
|
||||
$year = Arr::integer($root, 'year');
|
||||
|
||||
$seasons = collect(Arr::get($root, 'seasons'));
|
||||
|
||||
@@ -71,7 +81,7 @@ class AnimeYearsQuery
|
||||
public function resolveAnimeField(array $root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): Paginator
|
||||
{
|
||||
$season = Arr::get($root, 'season');
|
||||
$year = Arr::get($root, 'year');
|
||||
$year = Arr::integer($root, 'year');
|
||||
|
||||
$builder = Anime::query()
|
||||
// season filter applies only on the 'season' field.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Scalars;
|
||||
|
||||
use App\ValueObjects\FuzzyDate;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Language\AST\IntValueNode;
|
||||
use GraphQL\Language\AST\Node;
|
||||
use GraphQL\Language\AST\StringValueNode;
|
||||
use GraphQL\Type\Definition\ScalarType;
|
||||
|
||||
class FuzzyDateInt extends ScalarType
|
||||
{
|
||||
/**
|
||||
* Serializes an internal value to include in a response.
|
||||
*/
|
||||
public function serialize(mixed $value): ?int
|
||||
{
|
||||
return $this->normalize($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an externally provided value from query variables.
|
||||
*/
|
||||
public function parseValue(mixed $value): ?int
|
||||
{
|
||||
return $this->normalize($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an externally provided literal value hardcoded in the GraphQL query.
|
||||
*
|
||||
* @param \GraphQL\Language\AST\ValueNode&Node $valueNode
|
||||
* @param array<string, mixed>|null $variables
|
||||
*/
|
||||
public function parseLiteral(Node $valueNode, ?array $variables = null): ?int
|
||||
{
|
||||
if (! $valueNode instanceof IntValueNode && ! $valueNode instanceof StringValueNode) {
|
||||
throw new Error("FuzzyDateInt cannot represent non-integer value: {$valueNode->kind}", $valueNode);
|
||||
}
|
||||
|
||||
return $this->normalize($valueNode->value);
|
||||
}
|
||||
|
||||
private function normalize(mixed $value): int
|
||||
{
|
||||
return (int) FuzzyDate::fromString(str_pad((string) $value, 8, '0', STR_PAD_RIGHT))->toString();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Models\Wiki;
|
||||
|
||||
use App\Casts\AsFuzzyDate;
|
||||
use App\Concerns\Models\SoftDeletes;
|
||||
use App\Concerns\Models\Submitable;
|
||||
use App\Contracts\Models\HasImages;
|
||||
@@ -22,17 +23,21 @@ use App\Http\Resources\Pivot\Wiki\Resource\AnimeStudioJsonResource;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\List\External\ExternalEntry;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Observers\Wiki\AnimeObserver;
|
||||
use App\Pivots\Morph\Imageable;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use App\Pivots\Wiki\AnimeSeries;
|
||||
use App\Pivots\Wiki\AnimeStudio;
|
||||
use App\Scout\Elasticsearch\Models\Wiki\AnimeElasticModel;
|
||||
use App\Scout\Typesense\Models\Wiki\AnimeTypesenseModel;
|
||||
use App\ValueObjects\FuzzyDate;
|
||||
use Database\Factories\Wiki\AnimeFactory;
|
||||
use Deprecated;
|
||||
use Elastic\ScoutDriverPlus\Searchable;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Attributes\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
@@ -47,14 +52,17 @@ use RuntimeException;
|
||||
/**
|
||||
* @property int $anime_id
|
||||
* @property Collection<int, AnimeTheme> $animethemes
|
||||
* @property FuzzyDate|null $end_date
|
||||
* @property Collection<int, ExternalEntry> $externalentries
|
||||
* @property AnimeFormat|null $format
|
||||
* @property Collection<int, Image> $images
|
||||
* @property string|null $mod_notes
|
||||
* @property string $name
|
||||
* @property Collection<int, ExternalResource> $resources
|
||||
* @property AnimeSeason|null $season
|
||||
* @property Collection<int, Series> $series
|
||||
* @property string $slug
|
||||
* @property FuzzyDate|null $start_date
|
||||
* @property Collection<int, Studio> $studios
|
||||
* @property Collection<int, Synonym> $synonyms
|
||||
* @property string|null $synopsis
|
||||
@@ -62,6 +70,7 @@ use RuntimeException;
|
||||
*
|
||||
* @method static AnimeFactory factory(...$parameters)
|
||||
*/
|
||||
#[ObservedBy(AnimeObserver::class)]
|
||||
#[Table(Anime::TABLE, Anime::ATTRIBUTE_ID)]
|
||||
class Anime extends BaseModel implements Auditable, HasImages, HasResources, HasSynonyms, SoftDeletable
|
||||
{
|
||||
@@ -74,10 +83,13 @@ class Anime extends BaseModel implements Auditable, HasImages, HasResources, Has
|
||||
final public const string TABLE = 'anime';
|
||||
|
||||
final public const string ATTRIBUTE_ID = 'anime_id';
|
||||
final public const string ATTRIBUTE_END_DATE = 'end_date';
|
||||
final public const string ATTRIBUTE_FORMAT = 'format';
|
||||
final public const string ATTRIBUTE_MOD_NOTES = 'mod_notes';
|
||||
final public const string ATTRIBUTE_NAME = 'name';
|
||||
final public const string ATTRIBUTE_SEASON = 'season';
|
||||
final public const string ATTRIBUTE_SLUG = 'slug';
|
||||
final public const string ATTRIBUTE_START_DATE = 'start_date';
|
||||
final public const string ATTRIBUTE_SYNOPSIS = 'synopsis';
|
||||
final public const string ATTRIBUTE_YEAR = 'year';
|
||||
|
||||
@@ -121,13 +133,15 @@ class Anime extends BaseModel implements Auditable, HasImages, HasResources, Has
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
Anime::ATTRIBUTE_END_DATE,
|
||||
Anime::ATTRIBUTE_FORMAT,
|
||||
Anime::ATTRIBUTE_MOD_NOTES,
|
||||
Anime::ATTRIBUTE_NAME,
|
||||
Anime::ATTRIBUTE_SEASON,
|
||||
Anime::ATTRIBUTE_SLUG,
|
||||
Anime::ATTRIBUTE_START_DATE,
|
||||
Anime::ATTRIBUTE_SYNOPSIS,
|
||||
Anime::ATTRIBUTE_YEAR,
|
||||
'media_format',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -138,15 +152,25 @@ class Anime extends BaseModel implements Auditable, HasImages, HasResources, Has
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
Anime::ATTRIBUTE_END_DATE => AsFuzzyDate::class,
|
||||
Anime::ATTRIBUTE_FORMAT => AnimeFormat::class,
|
||||
Anime::ATTRIBUTE_MOD_NOTES => 'string',
|
||||
Anime::ATTRIBUTE_NAME => 'string',
|
||||
Anime::ATTRIBUTE_SEASON => AnimeSeason::class,
|
||||
Anime::ATTRIBUTE_SLUG => 'string',
|
||||
Anime::ATTRIBUTE_START_DATE => AsFuzzyDate::class,
|
||||
Anime::ATTRIBUTE_SYNOPSIS => 'string',
|
||||
Anime::ATTRIBUTE_YEAR => 'int',
|
||||
];
|
||||
}
|
||||
|
||||
protected function year(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn ($value) => $this->start_date->year ?? $value,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the query used to retrieve models when making all of the models searchable.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Observers\Wiki;
|
||||
|
||||
use App\Models\Wiki\Anime;
|
||||
|
||||
class AnimeObserver
|
||||
{
|
||||
/**
|
||||
* Handle the Anime "creating" event.
|
||||
*/
|
||||
public function creating(Anime $anime): void
|
||||
{
|
||||
$anime->year = $anime->start_date?->year;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the Anime "updating" event.
|
||||
*/
|
||||
public function updating(Anime $anime): void
|
||||
{
|
||||
$anime->year = $anime->start_date?->year;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class Typesense extends Search
|
||||
): Collection|Paginator {
|
||||
$model = $schema->model();
|
||||
|
||||
/** @var ScoutBuilder $builder */
|
||||
/** @var \Laravel\Scout\Builder $builder */
|
||||
/** @phpstan-ignore-next-line */
|
||||
$builder = $model::search($this->criteria->getTerm());
|
||||
$scope = ScopeParser::parse($schema->type());
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\ValueObjects;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class FuzzyDate
|
||||
{
|
||||
public function __construct(
|
||||
public ?int $year = null,
|
||||
public ?int $month = null,
|
||||
public ?int $day = null,
|
||||
) {
|
||||
$this->validate();
|
||||
}
|
||||
|
||||
public static function fromString(?string $value): ?FuzzyDate
|
||||
{
|
||||
if (blank($value) || $value === '00000000') {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw_unless(preg_match('/^\d{8}$/', $value), InvalidArgumentException::class, 'FuzzyDate must be an 8-digit string.');
|
||||
|
||||
return new FuzzyDate(
|
||||
(int) substr($value, 0, 4) ?: null,
|
||||
(int) substr($value, 4, 2) ?: null,
|
||||
(int) substr($value, 6, 2) ?: null,
|
||||
);
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return str_pad((string) ($this->year ?? 0), 4, '0', STR_PAD_LEFT)
|
||||
.str_pad((string) ($this->month ?? 0), 2, '0', STR_PAD_LEFT)
|
||||
.str_pad((string) ($this->day ?? 0), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'year' => $this->year,
|
||||
'month' => $this->month,
|
||||
'day' => $this->day,
|
||||
];
|
||||
}
|
||||
|
||||
private function validate(): void
|
||||
{
|
||||
throw_if($this->year !== null && ($this->year < 1 || $this->year > 9999), InvalidArgumentException::class, 'Invalid year.');
|
||||
|
||||
throw_if($this->month !== null && ($this->month < 1 || $this->month > 12), InvalidArgumentException::class, 'Invalid month.');
|
||||
|
||||
throw_if($this->day !== null && ($this->day < 1 || $this->day > 31), InvalidArgumentException::class, 'Invalid day.');
|
||||
|
||||
throw_if($this->day !== null && $this->month === null, InvalidArgumentException::class, 'Month is required when day is present.');
|
||||
|
||||
throw_if($this->month !== null && $this->year === null, InvalidArgumentException::class, 'Year is required when month is present.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasColumns('anime', ['start_date', 'end_date'])) {
|
||||
Schema::table('anime', function (Blueprint $table) {
|
||||
$table->char('start_date', 8)->nullable()->after('year');
|
||||
$table->char('end_date', 8)->nullable()->after('start_date');
|
||||
$table->text('mod_notes')->nullable()->after('synopsis');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -29,7 +29,13 @@ type Anime @model(class: "App\\Models\\Wiki\\Anime") {
|
||||
synopsis: String
|
||||
|
||||
"The premiere year of the anime"
|
||||
year: Int
|
||||
year: Int @deprecated(reason: "Use the `startDate.year` field instead.")
|
||||
|
||||
"The premiere date of the anime, with a fuzzy date representation"
|
||||
startDate: FuzzyDate @rename(attribute: "start_date")
|
||||
|
||||
"The end date of the anime, with a fuzzy date representation"
|
||||
endDate: FuzzyDate @rename(attribute: "end_date")
|
||||
|
||||
"The URL for the anime page on the website"
|
||||
siteUrl: String! @field(resolver: "App\\GraphQL\\Types\\AnimeType@resolveSiteUrlAttribute")
|
||||
@@ -128,9 +134,15 @@ extend type Query {
|
||||
season: AnimeSeason @eq
|
||||
season_in: [AnimeSeason!] @in(key: "season")
|
||||
slug: String @eq
|
||||
year: Int @eq
|
||||
year_lesser: Int @where(operator: "<", key: "year")
|
||||
year_greater: Int @where(operator: ">", key: "year")
|
||||
year: Int @eq @deprecated(reason: "Use the `startDate` filter instead.")
|
||||
year_lesser: Int @where(operator: "<", key: "year") @deprecated(reason: "Use the `startDate_lesser` filter instead.")
|
||||
year_greater: Int @where(operator: ">", key: "year") @deprecated(reason: "Use the `startDate_greater` filter instead.")
|
||||
startDate: FuzzyDateInt @eq(key: "start_date")
|
||||
startDate_lesser: FuzzyDateInt @where(operator: "<", key: "start_date")
|
||||
startDate_greater: FuzzyDateInt @where(operator: ">", key: "start_date")
|
||||
endDate: FuzzyDateInt @eq(key: "end_date")
|
||||
endDate_lesser: FuzzyDateInt @where(operator: "<", key: "end_date")
|
||||
endDate_greater: FuzzyDateInt @where(operator: ">", key: "end_date")
|
||||
where: _ @whereConditions(columnsEnum: AnimeFilterableColumns, handler: "App\\GraphQL\\WhereConditions\\WhereConditionsHandler")
|
||||
sort: [AnimeSort!] @sort
|
||||
): [Anime!]! @paginate
|
||||
|
||||
@@ -25,9 +25,12 @@ type AnimeYearSeasons {
|
||||
format_in: [AnimeFormat!] @in(key: "format")
|
||||
season: AnimeSeason @eq
|
||||
season_in: [AnimeSeason!] @in(key: "season")
|
||||
year: Int @eq
|
||||
year_lesser: Int @where(operator: "<", key: "year")
|
||||
year_greater: Int @where(operator: ">", key: "year")
|
||||
year: Int @eq @deprecated(reason: "Use the `startDate` filter instead.")
|
||||
year_lesser: Int @where(operator: "<", key: "year") @deprecated(reason: "Use the `startDate_lesser` filter instead.")
|
||||
year_greater: Int @where(operator: ">", key: "year") @deprecated(reason: "Use the `startDate_greater` filter instead.")
|
||||
startDate: FuzzyDateInt @eq(key: "start_date")
|
||||
startDate_lesser: FuzzyDateInt @where(operator: "<", key: "start_date")
|
||||
startDate_greater: FuzzyDateInt @where(operator: ">", key: "start_date")
|
||||
where: _ @whereConditions(columnsEnum: AnimeFilterableColumns, handler: "App\\GraphQL\\WhereConditions\\WhereConditionsHandler")
|
||||
sort: [AnimeSort!] @sort
|
||||
first: Int
|
||||
@@ -50,9 +53,12 @@ type AnimeYearSeason {
|
||||
format_in: [AnimeFormat!] @in(key: "format")
|
||||
season: AnimeSeason @eq
|
||||
season_in: [AnimeSeason!] @in(key: "season")
|
||||
year: Int @eq
|
||||
year_lesser: Int @where(operator: "<", key: "year")
|
||||
year_greater: Int @where(operator: ">", key: "year")
|
||||
year: Int @eq @deprecated(reason: "Use the `startDate` filter instead.")
|
||||
year_lesser: Int @where(operator: "<", key: "year") @deprecated(reason: "Use the `startDate_lesser` filter instead.")
|
||||
year_greater: Int @where(operator: ">", key: "year") @deprecated(reason: "Use the `startDate_greater` filter instead.")
|
||||
startDate: FuzzyDateInt @eq(key: "start_date")
|
||||
startDate_lesser: FuzzyDateInt @where(operator: "<", key: "start_date")
|
||||
startDate_greater: FuzzyDateInt @where(operator: ">", key: "start_date")
|
||||
sort: [AnimeSort!] @sort
|
||||
first: Int
|
||||
page: Int = 1
|
||||
|
||||
@@ -28,9 +28,12 @@ type Series @model(class: "App\\Models\\Wiki\\Series") {
|
||||
format_in: [AnimeFormat!] @in(key: "format")
|
||||
season: AnimeSeason @eq
|
||||
season_in: [AnimeSeason!] @in(key: "season")
|
||||
year: Int @eq
|
||||
year_lesser: Int @where(operator: "<", key: "year")
|
||||
year_greater: Int @where(operator: ">", key: "year")
|
||||
year: Int @eq @deprecated(reason: "Use the `startDate` filter instead.")
|
||||
year_lesser: Int @where(operator: "<", key: "year") @deprecated(reason: "Use the `startDate_lesser` filter instead.")
|
||||
year_greater: Int @where(operator: ">", key: "year") @deprecated(reason: "Use the `startDate_greater` filter instead.")
|
||||
startDate: FuzzyDateInt @eq(key: "start_date")
|
||||
startDate_lesser: FuzzyDateInt @where(operator: "<", key: "start_date")
|
||||
startDate_greater: FuzzyDateInt @where(operator: ">", key: "start_date")
|
||||
where: _ @whereConditions(columnsEnum: AnimeFilterableColumns, handler: "App\\GraphQL\\WhereConditions\\WhereConditionsHandler")
|
||||
sort: [AnimeSort!] @sort
|
||||
): [Anime!]! @belongsToManyCustom(type: CONNECTION, edgeType: "SeriesAnimeEdge")
|
||||
|
||||
@@ -28,9 +28,12 @@ type Studio @model(class: "App\\Models\\Wiki\\Studio") {
|
||||
format_in: [AnimeFormat!] @in(key: "format")
|
||||
season: AnimeSeason @eq
|
||||
season_in: [AnimeSeason!] @in(key: "season")
|
||||
year: Int @eq
|
||||
year_lesser: Int @where(operator: "<", key: "year")
|
||||
year_greater: Int @where(operator: ">", key: "year")
|
||||
year: Int @eq @deprecated(reason: "Use the `startDate` filter instead.")
|
||||
year_lesser: Int @where(operator: "<", key: "year") @deprecated(reason: "Use the `startDate_lesser` filter instead.")
|
||||
year_greater: Int @where(operator: ">", key: "year") @deprecated(reason: "Use the `startDate_greater` filter instead.")
|
||||
startDate: FuzzyDateInt @eq(key: "start_date")
|
||||
startDate_lesser: FuzzyDateInt @where(operator: "<", key: "start_date")
|
||||
startDate_greater: FuzzyDateInt @where(operator: ">", key: "start_date")
|
||||
where: _ @whereConditions(columnsEnum: AnimeFilterableColumns, handler: "App\\GraphQL\\WhereConditions\\WhereConditionsHandler")
|
||||
sort: [AnimeSort!] @sort
|
||||
): [Anime!]! @belongsToManyCustom(type: CONNECTION, edgeType: "StudioAnimeEdge")
|
||||
|
||||
@@ -22,11 +22,32 @@ dealing with really large numbers to be on the safe side.
|
||||
"""
|
||||
scalar Mixed @scalar(class: "MLL\\GraphQLScalars\\MixedScalar")
|
||||
|
||||
"""
|
||||
A partial date encoded as an integer in YYYYMMDD format.
|
||||
|
||||
Unknown parts are represented with 00:
|
||||
- 20260709 = July 9, 2026
|
||||
- 20260700 = July 2026
|
||||
- 20260000 = 2026
|
||||
|
||||
Null means the date is unknown.
|
||||
"""
|
||||
scalar FuzzyDateInt @scalar(class: "App\\GraphQL\\Scalars\\FuzzyDateInt")
|
||||
|
||||
"Represents a response containing a message."
|
||||
type MessageResponse {
|
||||
message: String!
|
||||
}
|
||||
|
||||
"""
|
||||
A fuzzy date in YYYYMMDD format.
|
||||
"""
|
||||
type FuzzyDate {
|
||||
year: Int
|
||||
month: Int
|
||||
day: Int
|
||||
}
|
||||
|
||||
type Mutation @guard {
|
||||
ToggleLike(
|
||||
entryId: Int
|
||||
|
||||
+19
-4
@@ -568,6 +568,18 @@ return [
|
||||
],
|
||||
],
|
||||
'anime' => [
|
||||
'end_date' => [
|
||||
'help' => 'The date in which the Anime finished airing.',
|
||||
'name' => 'End Date',
|
||||
],
|
||||
'format' => [
|
||||
'help' => 'The Format of the Anime. By default, we will use the Type Field on the MAL page.',
|
||||
'name' => 'Format',
|
||||
],
|
||||
'mod_notes' => [
|
||||
'help' => 'Any additional information not included in other fields that may be useful for moderators.',
|
||||
'name' => 'Moderator Notes',
|
||||
],
|
||||
'name' => [
|
||||
'help' => 'The display title of the Anime. By default, we will use the same title as MAL. Ex: "Bakemonogatari", "Code Geass: Hangyaku no Lelouch", "Dungeon ni Deai wo Motomeru no wa Machigatteiru Darou ka".',
|
||||
'name' => 'Name',
|
||||
@@ -580,6 +592,10 @@ return [
|
||||
'help' => 'Used as the URL Slug / Model Route Key. By default, this should be the Name lowercased and "_" replacing spaces. Shortenings/Abbreviations are also accepted. Ex: "monogatari", "code_geass", "danmachi".',
|
||||
'name' => 'Slug',
|
||||
],
|
||||
'start_date' => [
|
||||
'help' => 'The date in which the Anime premiered.',
|
||||
'name' => 'Start Date',
|
||||
],
|
||||
'synopsis' => [
|
||||
'help' => 'The brief description of the Anime.',
|
||||
'name' => 'Synopsis',
|
||||
@@ -588,10 +604,6 @@ return [
|
||||
'help' => 'The Year in which the Anime premiered. By default, we will use the Premiered Field on the MAL page.',
|
||||
'name' => 'Year',
|
||||
],
|
||||
'format' => [
|
||||
'help' => 'The Format of the Anime. By default, we will use the Type Field on the MAL page.',
|
||||
'name' => 'Format',
|
||||
],
|
||||
],
|
||||
'artist' => [
|
||||
'groups' => [
|
||||
@@ -676,11 +688,14 @@ return [
|
||||
'base' => [
|
||||
'attached_at' => 'Attached At',
|
||||
'created_at' => 'Created At',
|
||||
'day' => 'Day',
|
||||
'deleted_at' => 'Deleted At',
|
||||
'file_properties' => 'File Properties',
|
||||
'id' => 'ID',
|
||||
'month' => 'Month',
|
||||
'timestamps' => 'Timestamps',
|
||||
'updated_at' => 'Updated At',
|
||||
'year' => 'Year',
|
||||
],
|
||||
'dump' => [
|
||||
'path' => 'Path',
|
||||
|
||||
Reference in New Issue
Block a user