mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat: added entry <-> resource relation (#927)
This commit is contained in:
@@ -25,7 +25,7 @@ class AttachResourceAction
|
||||
foreach ($sites as $resourceSite) {
|
||||
$url = Arr::get($fields, $resourceSite->name);
|
||||
|
||||
if (empty($url)) {
|
||||
if (blank($url)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,10 +7,12 @@ namespace App\Enums\Models\Wiki;
|
||||
use App\Concerns\Enums\LocalizesName;
|
||||
use App\Contracts\Models\HasResources;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\Song;
|
||||
use App\Models\Wiki\Studio;
|
||||
use App\Rules\Wiki\Resource\AnimeResourceLinkFormatRule;
|
||||
use App\Rules\Wiki\Resource\AnimeThemeEntryResourceLinkFormatRule;
|
||||
use App\Rules\Wiki\Resource\ArtistResourceLinkFormatRule;
|
||||
use App\Rules\Wiki\Resource\SongResourceLinkFormatRule;
|
||||
use App\Rules\Wiki\Resource\StudioResourceLinkFormatRule;
|
||||
@@ -197,6 +199,7 @@ enum ResourceSite: int implements HasLabel
|
||||
{
|
||||
return match (true) {
|
||||
$model instanceof Anime => new AnimeResourceLinkFormatRule($this),
|
||||
$model instanceof AnimeThemeEntry => new AnimeThemeEntryResourceLinkFormatRule($this),
|
||||
$model instanceof Artist => new ArtistResourceLinkFormatRule($this),
|
||||
$model instanceof Song => new SongResourceLinkFormatRule($this),
|
||||
$model instanceof Studio => new StudioResourceLinkFormatRule($this),
|
||||
@@ -232,6 +235,9 @@ enum ResourceSite: int implements HasLabel
|
||||
ResourceSite::WIKI,
|
||||
ResourceSite::LIVECHART,
|
||||
],
|
||||
AnimeThemeEntry::class => [
|
||||
ResourceSite::YOUTUBE,
|
||||
],
|
||||
Artist::class => [
|
||||
ResourceSite::X,
|
||||
ResourceSite::ANIDB,
|
||||
@@ -297,6 +303,13 @@ enum ResourceSite: int implements HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
if ($modelClass === AnimeThemeEntry::class) {
|
||||
return match ($this) {
|
||||
ResourceSite::YOUTUBE => "https://www.youtube.com/watch?v=$slug",
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
if ($modelClass === Artist::class) {
|
||||
return match ($this) {
|
||||
ResourceSite::X => "https://x.com/$slug",
|
||||
@@ -377,6 +390,13 @@ enum ResourceSite: int implements HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
if ($model instanceof AnimeThemeEntry) {
|
||||
return match ($this) {
|
||||
ResourceSite::YOUTUBE => '/^https:\/\/www\.youtube\.com\/(watch)\?v=([\w-]+)$/',
|
||||
default => '/^$/',
|
||||
};
|
||||
}
|
||||
|
||||
if ($model instanceof Artist) {
|
||||
return match ($this) {
|
||||
ResourceSite::X => '/^https:\/\/(x)\.com\/(\w+)$/',
|
||||
@@ -449,6 +469,13 @@ enum ResourceSite: int implements HasLabel
|
||||
};
|
||||
}
|
||||
|
||||
if ($modelClass === AnimeThemeEntry::class) {
|
||||
return match ($this) {
|
||||
ResourceSite::YOUTUBE => '/^https:\/\/www\.youtube\.com\/watch\?v=[\w-]+$/',
|
||||
default => '/$.^/',
|
||||
};
|
||||
}
|
||||
|
||||
if ($modelClass === Artist::class) {
|
||||
return match ($this) {
|
||||
ResourceSite::X => '/^https:\/\/x\.com\/\w+$/',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Actions\Models\Wiki\Anime\Theme\Entry;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Filament\Actions\Models\Wiki\AttachResourceAction;
|
||||
|
||||
class AttachEntryResourceAction extends AttachResourceAction
|
||||
{
|
||||
/**
|
||||
* The default name of the action.
|
||||
*/
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'attach-entry-resource';
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial setup for the action.
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->sites([
|
||||
ResourceSite::YOUTUBE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Wiki\Anime\Theme;
|
||||
|
||||
use App\Actions\Models\Wiki\AttachResourceAction;
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Enums\Models\Wiki\ThemeType;
|
||||
use App\Filament\Actions\Models\Wiki\Anime\Theme\Entry\AttachEntryResourceAction;
|
||||
use App\Filament\Components\Columns\BelongsToColumn;
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\BelongsTo;
|
||||
@@ -16,6 +19,7 @@ use App\Filament\Components\Filters\TextFilter;
|
||||
use App\Filament\Components\Infolist\BelongsToEntry;
|
||||
use App\Filament\Components\Infolist\TextEntry;
|
||||
use App\Filament\Components\Infolist\TimestampSection;
|
||||
use App\Filament\RelationManagers\Wiki\ResourceRelationManager;
|
||||
use App\Filament\Resources\BaseResource;
|
||||
use App\Filament\Resources\Wiki\Anime as AnimeResource;
|
||||
use App\Filament\Resources\Wiki\Anime\Theme as ThemeResource;
|
||||
@@ -23,9 +27,11 @@ use App\Filament\Resources\Wiki\Anime\Theme\Entry\Pages\ListEntries;
|
||||
use App\Filament\Resources\Wiki\Anime\Theme\Entry\Pages\ViewEntry;
|
||||
use App\Filament\Resources\Wiki\Anime\Theme\Entry\RelationManagers\VideoEntryRelationManager;
|
||||
use App\Filament\Resources\Wiki\Anime\Theme\RelationManagers\EntryThemeRelationManager;
|
||||
use App\Filament\Resources\Wiki\Song as SongResource;
|
||||
use App\Models\Wiki\Anime\AnimeTheme as ThemeModel;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry as EntryModel;
|
||||
use App\Rules\Wiki\Resource\AnimeThemeEntryResourceLinkFormatRule;
|
||||
use Filament\Forms\Components\Checkbox;
|
||||
use Filament\Infolists\Components\IconEntry;
|
||||
use Filament\Resources\RelationManagers\RelationGroup;
|
||||
@@ -37,6 +43,7 @@ use Filament\Tables\Filters\Filter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Uri;
|
||||
|
||||
class Entry extends BaseResource
|
||||
{
|
||||
@@ -129,7 +136,11 @@ class Entry extends BaseResource
|
||||
$query = parent::getEloquentQuery();
|
||||
|
||||
// Necessary to prevent lazy loading when loading related resources
|
||||
return $query->with([AnimeThemeEntry::RELATION_ANIME_SHALLOW, AnimeThemeEntry::RELATION_THEME]);
|
||||
return $query->with([
|
||||
AnimeThemeEntry::RELATION_ANIME_SHALLOW,
|
||||
AnimeThemeEntry::RELATION_SONG,
|
||||
AnimeThemeEntry::RELATION_THEME,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -185,6 +196,21 @@ class Entry extends BaseResource
|
||||
->label(__('filament.fields.anime_theme_entry.notes.name'))
|
||||
->helperText(__('filament.fields.anime_theme_entry.notes.help'))
|
||||
->maxLength(192),
|
||||
|
||||
TextInput::make(ResourceSite::YOUTUBE->name)
|
||||
->label(ResourceSite::YOUTUBE->localize())
|
||||
->helperText(__('filament.fields.anime_theme_entry.youtube.help'))
|
||||
->url()
|
||||
->maxLength(255)
|
||||
->rule(new AnimeThemeEntryResourceLinkFormatRule(ResourceSite::YOUTUBE))
|
||||
->uri()
|
||||
->saveRelationshipsUsing(function (EntryModel $record, AttachResourceAction $action, Uri $state) {
|
||||
$fields = [
|
||||
ResourceSite::YOUTUBE->name => $state,
|
||||
];
|
||||
|
||||
$action->handle($record, $fields, [ResourceSite::YOUTUBE]);
|
||||
}),
|
||||
])
|
||||
->columns(1);
|
||||
}
|
||||
@@ -222,6 +248,9 @@ class Entry extends BaseResource
|
||||
->label(__('filament.fields.anime_theme_entry.notes.name'))
|
||||
->limit(50)
|
||||
->tooltip(fn (TextColumn $column) => $column->getState()),
|
||||
|
||||
BelongsToColumn::make(AnimeThemeEntry::RELATION_SONG, SongResource::class)
|
||||
->hiddenOn(EntryThemeRelationManager::class),
|
||||
])
|
||||
->searchable();
|
||||
}
|
||||
@@ -241,6 +270,8 @@ class Entry extends BaseResource
|
||||
|
||||
BelongsToEntry::make(EntryModel::RELATION_THEME, ThemeResource::class, true),
|
||||
|
||||
BelongsToEntry::make(EntryModel::RELATION_SONG, SongResource::class, true),
|
||||
|
||||
TextEntry::make(EntryModel::ATTRIBUTE_VERSION)
|
||||
->label(__('filament.fields.anime_theme_entry.version.name')),
|
||||
|
||||
@@ -280,6 +311,7 @@ class Entry extends BaseResource
|
||||
return [
|
||||
RelationGroup::make(static::getModelLabel(), [
|
||||
VideoEntryRelationManager::class,
|
||||
ResourceRelationManager::class,
|
||||
|
||||
...parent::getBaseRelations(),
|
||||
]),
|
||||
@@ -315,6 +347,18 @@ class Entry extends BaseResource
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actions available for the resource.
|
||||
*
|
||||
* @return array<int, \Filament\Actions\Action|\Filament\Actions\ActionGroup>
|
||||
*/
|
||||
public static function getRecordActions(): array
|
||||
{
|
||||
return [
|
||||
AttachEntryResourceAction::make(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pages available for the resource.
|
||||
*
|
||||
|
||||
@@ -16,6 +16,7 @@ use App\Filament\Resources\Wiki\ExternalResource\Pages\ListExternalResources;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\Pages\ViewExternalResource;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\AnimeResourceRelationManager;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\ArtistResourceRelationManager;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\EntryResourceRelationManager;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\SongResourceRelationManager;
|
||||
use App\Filament\Resources\Wiki\ExternalResource\RelationManagers\StudioResourceRelationManager;
|
||||
use App\Models\Wiki\ExternalResource as ExternalResourceModel;
|
||||
@@ -195,6 +196,7 @@ class ExternalResource extends BaseResource
|
||||
return [
|
||||
RelationGroup::make(static::getModelLabel(), [
|
||||
AnimeResourceRelationManager::class,
|
||||
EntryResourceRelationManager::class,
|
||||
ArtistResourceRelationManager::class,
|
||||
SongResourceRelationManager::class,
|
||||
StudioResourceRelationManager::class,
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Filament\Resources\Wiki\ExternalResource\RelationManagers;
|
||||
|
||||
use App\Filament\Components\Columns\TextColumn;
|
||||
use App\Filament\Components\Fields\TextInput;
|
||||
use App\Filament\RelationManagers\Wiki\Anime\Theme\EntryRelationManager;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use Filament\Schemas\Components\Component;
|
||||
use Filament\Tables\Columns\Column;
|
||||
use Filament\Tables\Table;
|
||||
|
||||
class EntryResourceRelationManager extends EntryRelationManager
|
||||
{
|
||||
/**
|
||||
* The relationship the relation manager corresponds to.
|
||||
*/
|
||||
protected static string $relationship = ExternalResource::RELATION_ANIMETHEMEENTRIES;
|
||||
|
||||
/**
|
||||
* Get the pivot components of the relation.
|
||||
*
|
||||
* @return Component[]
|
||||
*/
|
||||
public function getPivotComponents(): array
|
||||
{
|
||||
return [
|
||||
TextInput::make(Resourceable::ATTRIBUTE_AS)
|
||||
->label(__('filament.fields.resourceable.as.name'))
|
||||
->helperText(__('filament.fields.resourceable.as.help')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot columns of the relation.
|
||||
*
|
||||
* @return Column[]
|
||||
*/
|
||||
public function getPivotColumns(): array
|
||||
{
|
||||
return [
|
||||
TextColumn::make(Resourceable::ATTRIBUTE_AS)
|
||||
->label(__('filament.fields.resourceable.as.name')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* The index page of the resource.
|
||||
*/
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return parent::table(
|
||||
$table
|
||||
->inverseRelationship(AnimeThemeEntry::RELATION_RESOURCES)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,11 +16,14 @@ use App\GraphQL\Definition\Fields\Wiki\Anime\Theme\Entry\AnimeThemeEntryNsfwFiel
|
||||
use App\GraphQL\Definition\Fields\Wiki\Anime\Theme\Entry\AnimeThemeEntrySpoilerField;
|
||||
use App\GraphQL\Definition\Fields\Wiki\Anime\Theme\Entry\AnimeThemeEntryVersionField;
|
||||
use App\GraphQL\Definition\Types\EloquentType;
|
||||
use App\GraphQL\Definition\Types\Pivot\Morph\ResourceableType;
|
||||
use App\GraphQL\Definition\Types\Pivot\Wiki\AnimeThemeEntryVideoType;
|
||||
use App\GraphQL\Definition\Types\Wiki\Anime\AnimeThemeType;
|
||||
use App\GraphQL\Definition\Types\Wiki\ExternalResourceType;
|
||||
use App\GraphQL\Definition\Types\Wiki\VideoType;
|
||||
use App\GraphQL\Support\Relations\BelongsToManyRelation;
|
||||
use App\GraphQL\Support\Relations\BelongsToRelation;
|
||||
use App\GraphQL\Support\Relations\MorphToManyRelation;
|
||||
use App\GraphQL\Support\Relations\Relation;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
|
||||
@@ -45,6 +48,7 @@ class AnimeThemeEntryType extends EloquentType implements ReportableType
|
||||
new BelongsToRelation(new AnimeThemeType(), AnimeThemeEntry::RELATION_THEME)
|
||||
->notNullable(),
|
||||
new BelongsToManyRelation($this, VideoType::class, AnimeThemeEntry::RELATION_VIDEOS, AnimeThemeEntryVideoType::class),
|
||||
new MorphToManyRelation($this, ExternalResourceType::class, AnimeThemeEntry::RELATION_RESOURCES, ResourceableType::class),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Models\Wiki\Anime\Theme;
|
||||
use App\Concerns\Models\Reportable;
|
||||
use App\Concerns\Models\SoftDeletes;
|
||||
use App\Contracts\Http\Api\InteractsWithSchema;
|
||||
use App\Contracts\Models\HasResources;
|
||||
use App\Contracts\Models\SoftDeletable;
|
||||
use App\Enums\Models\Wiki\ThemeType;
|
||||
use App\Events\Wiki\Anime\Theme\Entry\EntryCreated;
|
||||
@@ -20,7 +21,9 @@ use App\Http\Resources\Pivot\Wiki\Resource\AnimeThemeEntryVideoResource;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use App\Scopes\WithoutInsertSongScope;
|
||||
use Database\Factories\Wiki\Anime\Theme\AnimeThemeEntryFactory;
|
||||
@@ -29,6 +32,7 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Znck\Eloquent\Relations\BelongsToThrough;
|
||||
@@ -43,6 +47,7 @@ use Znck\Eloquent\Traits\BelongsToThrough as ZnckBelongsToThrough;
|
||||
* @property string|null $episodes
|
||||
* @property string|null $notes
|
||||
* @property bool $nsfw
|
||||
* @property Collection<int, ExternalResource> $resources
|
||||
* @property bool $spoiler
|
||||
* @property int $theme_id
|
||||
* @property int|null $version
|
||||
@@ -50,7 +55,7 @@ use Znck\Eloquent\Traits\BelongsToThrough as ZnckBelongsToThrough;
|
||||
*
|
||||
* @method static AnimeThemeEntryFactory factory(...$parameters)
|
||||
*/
|
||||
class AnimeThemeEntry extends BaseModel implements InteractsWithSchema, SoftDeletable
|
||||
class AnimeThemeEntry extends BaseModel implements HasResources, InteractsWithSchema, SoftDeletable
|
||||
{
|
||||
use HasFactory;
|
||||
use Reportable;
|
||||
@@ -70,6 +75,7 @@ class AnimeThemeEntry extends BaseModel implements InteractsWithSchema, SoftDele
|
||||
|
||||
final public const RELATION_ANIME = 'animetheme.anime';
|
||||
final public const RELATION_ANIME_SHALLOW = 'anime';
|
||||
final public const RELATION_RESOURCES = 'resources';
|
||||
final public const RELATION_SONG = 'animetheme.song';
|
||||
final public const RELATION_SYNONYMS = 'animetheme.anime.animesynonyms';
|
||||
final public const RELATION_THEME = 'animetheme';
|
||||
@@ -245,6 +251,20 @@ class AnimeThemeEntry extends BaseModel implements InteractsWithSchema, SoftDele
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resources for the entry through the resourceable morph pivot.
|
||||
*
|
||||
* @return MorphToMany
|
||||
*/
|
||||
public function resources(): MorphToMany
|
||||
{
|
||||
return $this->morphToMany(ExternalResource::class, Resourceable::RELATION_RESOURCEABLE, Resourceable::TABLE, Resourceable::ATTRIBUTE_RESOURCEABLE_ID, Resourceable::ATTRIBUTE_RESOURCE)
|
||||
->using(Resourceable::class)
|
||||
->withPivot(Resourceable::ATTRIBUTE_AS)
|
||||
->as('entryresource')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the anime that owns the entry through the theme.
|
||||
*
|
||||
|
||||
@@ -13,6 +13,7 @@ use App\Events\Wiki\ExternalResource\ExternalResourceDeleted;
|
||||
use App\Events\Wiki\ExternalResource\ExternalResourceRestored;
|
||||
use App\Events\Wiki\ExternalResource\ExternalResourceUpdated;
|
||||
use App\Models\BaseModel;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use Database\Factories\Wiki\ExternalResourceFactory;
|
||||
use Illuminate\Database\Eloquent\Casts\AsUri;
|
||||
@@ -49,6 +50,7 @@ class ExternalResource extends BaseModel implements SoftDeletable
|
||||
final public const ATTRIBUTE_SITE = 'site';
|
||||
|
||||
final public const RELATION_ANIME = 'anime';
|
||||
final public const RELATION_ANIMETHEMEENTRIES = 'animethemeentries';
|
||||
final public const RELATION_ARTISTS = 'artists';
|
||||
final public const RELATION_SONGS = 'songs';
|
||||
final public const RELATION_STUDIOS = 'studios';
|
||||
@@ -136,6 +138,20 @@ class ExternalResource extends BaseModel implements SoftDeletable
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entry that reference this resource.
|
||||
*
|
||||
* @return MorphToMany
|
||||
*/
|
||||
public function animethemeentries(): MorphToMany
|
||||
{
|
||||
return $this->morphedByMany(AnimeThemeEntry::class, Resourceable::RELATION_RESOURCEABLE, Resourceable::TABLE, Resourceable::ATTRIBUTE_RESOURCE, Resourceable::ATTRIBUTE_RESOURCEABLE_ID)
|
||||
->using(Resourceable::class)
|
||||
->withPivot(Resourceable::ATTRIBUTE_AS)
|
||||
->as('entryresource')
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the artists that reference this resource.
|
||||
*
|
||||
|
||||
@@ -10,6 +10,7 @@ use App\Events\Pivot\Morph\Resourceable\ResourceableCreated;
|
||||
use App\Events\Pivot\Morph\Resourceable\ResourceableDeleted;
|
||||
use App\Events\Pivot\Morph\Resourceable\ResourceableUpdated;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Song;
|
||||
@@ -51,6 +52,7 @@ class Resourceable extends BaseMorphPivot
|
||||
*/
|
||||
public static $resourceables = [
|
||||
Anime::class,
|
||||
AnimeThemeEntry::class,
|
||||
Artist::class,
|
||||
Song::class,
|
||||
Studio::class,
|
||||
|
||||
@@ -64,7 +64,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
|
||||
DB::listen(function (QueryExecuted $query) {
|
||||
if (app()->isLocal()) {
|
||||
Log::debug($query->toRawSql());
|
||||
Log::debug($query->sql);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Rules\Wiki\Resource;
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Translation\PotentiallyTranslatedString;
|
||||
|
||||
readonly class AnimeThemeEntryResourceLinkFormatRule implements ValidationRule
|
||||
{
|
||||
public function __construct(protected ResourceSite $site) {}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param Closure(string): PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$pattern = $this->site->getPattern(AnimeThemeEntry::class);
|
||||
|
||||
if ($pattern !== null && Str::match($pattern, $value) !== $value) {
|
||||
$fail(__('validation.regex'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -516,6 +516,9 @@ return [
|
||||
'help' => 'The Version number of the Theme. Can be left blank if there is only one version. Version is only required if there exist at least 2 in the sequence.',
|
||||
'name' => 'Version',
|
||||
],
|
||||
'youtube' => [
|
||||
'help' => 'An YouTube URL to attach to the entry.',
|
||||
],
|
||||
],
|
||||
'anime_theme' => [
|
||||
'sequence' => [
|
||||
|
||||
@@ -5,10 +5,13 @@ declare(strict_types=1);
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\AnimeTheme;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Video;
|
||||
use App\Pivots\Morph\Resourceable;
|
||||
use App\Pivots\Wiki\AnimeThemeEntryVideo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Znck\Eloquent\Relations\BelongsToThrough;
|
||||
|
||||
@@ -55,6 +58,20 @@ test('theme', function () {
|
||||
$this->assertInstanceOf(AnimeTheme::class, $entry->animetheme()->first());
|
||||
});
|
||||
|
||||
test('external resources', function () {
|
||||
$resourcesCount = fake()->randomDigitNotNull();
|
||||
|
||||
$entry = AnimeThemeEntry::factory()
|
||||
->for(AnimeTheme::factory()->for(Anime::factory()))
|
||||
->has(ExternalResource::factory()->count($resourcesCount))
|
||||
->createOne();
|
||||
|
||||
$this->assertInstanceOf(MorphToMany::class, $entry->resources());
|
||||
$this->assertEquals($resourcesCount, $entry->resources()->count());
|
||||
$this->assertInstanceOf(ExternalResource::class, $entry->resources()->first());
|
||||
$this->assertEquals(Resourceable::class, $entry->resources()->getPivotClass());
|
||||
});
|
||||
|
||||
test('videos', function () {
|
||||
$videoCount = fake()->randomDigitNotNull();
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Models\Wiki\Artist;
|
||||
use App\Models\Wiki\ExternalResource;
|
||||
use App\Models\Wiki\Song;
|
||||
@@ -47,6 +48,19 @@ test('anime', function () {
|
||||
$this->assertEquals(Resourceable::class, $resource->anime()->getPivotClass());
|
||||
});
|
||||
|
||||
test('anime theme entry', function () {
|
||||
$entryCount = fake()->randomDigitNotNull();
|
||||
|
||||
$resource = ExternalResource::factory()
|
||||
->has(AnimeThemeEntry::factory()->forAnime()->count($entryCount))
|
||||
->createOne();
|
||||
|
||||
$this->assertInstanceOf(MorphToMany::class, $resource->animethemeentries());
|
||||
$this->assertEquals($entryCount, $resource->animethemeentries()->count());
|
||||
$this->assertInstanceOf(AnimeThemeEntry::class, $resource->animethemeentries()->first());
|
||||
$this->assertEquals(Resourceable::class, $resource->animethemeentries()->getPivotClass());
|
||||
});
|
||||
|
||||
test('artists', function () {
|
||||
$artistCount = fake()->randomDigitNotNull();
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Models\Wiki\ResourceSite;
|
||||
use App\Models\Wiki\Anime\Theme\AnimeThemeEntry;
|
||||
use App\Rules\Wiki\Resource\AnimeThemeEntryResourceLinkFormatRule;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
uses(Illuminate\Foundation\Testing\WithFaker::class);
|
||||
|
||||
test('fails for no pattern', function () {
|
||||
$attribute = fake()->word();
|
||||
|
||||
$validator = Validator::make(
|
||||
[$attribute => fake()->url()],
|
||||
[$attribute => new AnimeThemeEntryResourceLinkFormatRule(ResourceSite::YOUTUBE_MUSIC)],
|
||||
);
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
});
|
||||
|
||||
test('passes for pattern', function () {
|
||||
/** @var ResourceSite $site */
|
||||
$site = Arr::random(ResourceSite::getForModel(AnimeThemeEntry::class));
|
||||
|
||||
$url = $site->formatResourceLink(AnimeThemeEntry::class, fake()->randomDigitNotNull(), fake()->word(), 'null');
|
||||
|
||||
$attribute = fake()->word();
|
||||
|
||||
$validator = Validator::make(
|
||||
[$attribute => $url],
|
||||
[$attribute => new AnimeThemeEntryResourceLinkFormatRule($site)],
|
||||
);
|
||||
|
||||
$this->assertTrue($validator->passes());
|
||||
});
|
||||
|
||||
test('fails for trailing slash', function () {
|
||||
/** @var ResourceSite $site */
|
||||
$site = Arr::random(ResourceSite::getForModel(AnimeThemeEntry::class));
|
||||
|
||||
$url = $site->formatResourceLink(AnimeThemeEntry::class, fake()->randomDigitNotNull(), fake()->word());
|
||||
|
||||
$url = Str::of($url)
|
||||
->append('/')
|
||||
->__toString();
|
||||
|
||||
$attribute = fake()->word();
|
||||
|
||||
$validator = Validator::make(
|
||||
[$attribute => $url],
|
||||
[$attribute => new AnimeThemeEntryResourceLinkFormatRule($site)],
|
||||
);
|
||||
|
||||
$this->assertFalse($site->getPattern(AnimeThemeEntry::class) && $validator->passes());
|
||||
});
|
||||
|
||||
test('fails for trailing slug', function () {
|
||||
/** @var ResourceSite $site */
|
||||
$site = Arr::random(ResourceSite::getForModel(AnimeThemeEntry::class));
|
||||
|
||||
$url = $site->formatResourceLink(AnimeThemeEntry::class, fake()->randomDigitNotNull(), fake()->word());
|
||||
|
||||
$url = Str::of($url)
|
||||
->append('/')
|
||||
->append(fake()->word())
|
||||
->__toString();
|
||||
|
||||
$attribute = fake()->word();
|
||||
|
||||
$validator = Validator::make(
|
||||
[$attribute => $url],
|
||||
[$attribute => new AnimeThemeEntryResourceLinkFormatRule($site)],
|
||||
);
|
||||
|
||||
$this->assertFalse($site->getPattern(AnimeThemeEntry::class) && $validator->passes());
|
||||
});
|
||||
|
||||
test('fails for other resources', function () {
|
||||
/** @var ResourceSite $site */
|
||||
$site = Arr::random(
|
||||
array_filter(
|
||||
ResourceSite::cases(),
|
||||
fn ($value) => ! in_array($value, ResourceSite::getForModel(AnimeThemeEntry::class))
|
||||
)
|
||||
);
|
||||
|
||||
$url = $site->formatResourceLink(AnimeThemeEntry::class, fake()->randomDigitNotNull(), fake()->word());
|
||||
|
||||
$attribute = fake()->word();
|
||||
|
||||
$validator = Validator::make(
|
||||
[$attribute => $url],
|
||||
[$attribute => new AnimeThemeEntryResourceLinkFormatRule($site)],
|
||||
);
|
||||
|
||||
$this->assertFalse($validator->passes());
|
||||
});
|
||||
Reference in New Issue
Block a user