From f68e87aa25e5c352d098eb94b0b9efcf33e868c6 Mon Sep 17 00:00:00 2001 From: Kyrch Date: Sun, 30 Jun 2024 02:49:52 -0300 Subject: [PATCH] feat: added entry id field on tracks resource (#705) --- .../Anime/BackfillAnimeSynonymsAction.php | 16 ++- .../Resources/Discord/DiscordThread.php | 2 +- app/Filament/Resources/List/Playlist.php | 2 +- .../Resources/List/Playlist/Track.php | 74 ++++++++++-- app/Filament/Resources/Wiki/Anime/Theme.php | 18 +-- .../Resources/Wiki/Anime/Theme/Entry.php | 4 + .../Resources/Wiki/ExternalResource.php | 2 +- .../List/Playlist/Track/TrackEntryIdField.php | 111 ++++++++++++++++++ .../List/Playlist/Track/TrackVideoIdField.php | 21 ++++ .../List/Playlist/ForwardBackwardSchema.php | 2 + .../Api/Schema/List/Playlist/TrackSchema.php | 8 ++ app/Models/List/Playlist/PlaylistTrack.php | 21 +++- app/Policies/Admin/AnnouncementPolicy.php | 12 +- app/Policies/Admin/DumpPolicy.php | 12 +- app/Policies/Admin/FeaturePolicy.php | 12 +- app/Policies/Admin/FeaturedThemePolicy.php | 12 +- .../List/Playlist/PlaylistTrackPolicy.php | 36 ++++-- app/Policies/List/PlaylistPolicy.php | 56 +++++---- database/factories/List/PlaylistFactory.php | 65 +++++++++- ...add_entry_id_attribute_to_tracks_table.php | 37 ++++++ 20 files changed, 446 insertions(+), 77 deletions(-) create mode 100644 app/Http/Api/Field/List/Playlist/Track/TrackEntryIdField.php create mode 100644 database/migrations/2024_06_29_021412_add_entry_id_attribute_to_tracks_table.php diff --git a/app/Actions/Models/Wiki/Anime/BackfillAnimeSynonymsAction.php b/app/Actions/Models/Wiki/Anime/BackfillAnimeSynonymsAction.php index 81dabeac3..b60bfd2c3 100644 --- a/app/Actions/Models/Wiki/Anime/BackfillAnimeSynonymsAction.php +++ b/app/Actions/Models/Wiki/Anime/BackfillAnimeSynonymsAction.php @@ -56,7 +56,11 @@ public function handle(): ActionResult } foreach ($titles as $type => $text) { - if ($type === 'romaji' && $text === $this->getModel()->getName()) continue; + if ( + $text === null + || empty($text) + || ($type === 'romaji' && $text === $this->getModel()->getName()) + ) continue; Log::info("Creating {$text}"); @@ -83,9 +87,9 @@ public function handle(): ActionResult * Get the enum related to the array map. * * @param string $title - * @return AnimeSynonymType|null + * @return AnimeSynonymType */ - protected static function getAnilistSynonymsMap($title): ?AnimeSynonymType + protected static function getAnilistSynonymsMap($title): AnimeSynonymType { return match ($title) { 'english' => AnimeSynonymType::ENGLISH, @@ -141,9 +145,9 @@ protected function getTitles(): ?array ]) ->throw() ->json(); - + $titles = Arr::get($response, 'data.Media.title'); - + return $titles; } @@ -169,4 +173,4 @@ protected function relation(): HasMany { return $this->getModel()->animesynonyms(); } -} \ No newline at end of file +} diff --git a/app/Filament/Resources/Discord/DiscordThread.php b/app/Filament/Resources/Discord/DiscordThread.php index 56b4fbeb5..a04d23a52 100644 --- a/app/Filament/Resources/Discord/DiscordThread.php +++ b/app/Filament/Resources/Discord/DiscordThread.php @@ -144,7 +144,7 @@ public static function form(Form $form): Form ->formatStateUsing(fn ($state) => strval($state)) ->required() ->rules(['required']) - ->live(true) + ->live() ->afterStateUpdated(fn (Set $set, string $state) => $set(DiscordThreadModel::ATTRIBUTE_NAME, Arr::get((new DiscordThreadAction())->get($state), 'thread.name'))), TextInput::make(DiscordThreadModel::ATTRIBUTE_NAME) diff --git a/app/Filament/Resources/List/Playlist.php b/app/Filament/Resources/List/Playlist.php index bfaf1254b..cea7bfb36 100644 --- a/app/Filament/Resources/List/Playlist.php +++ b/app/Filament/Resources/List/Playlist.php @@ -187,7 +187,7 @@ public static function table(Table $table): Table ->label(__('filament.resources.singularLabel.user')) ->toggleable() ->placeholder('-') - ->urlToRelated(UserResource::class, PlaylistModel::RELATION_USER), + ->urlToRelated(UserResource::class, PlaylistModel::RELATION_USER, true), TextColumn::make(PlaylistModel::ATTRIBUTE_ID) ->label(__('filament.fields.base.id')) diff --git a/app/Filament/Resources/List/Playlist/Track.php b/app/Filament/Resources/List/Playlist/Track.php index fe21412fe..84e2b039a 100644 --- a/app/Filament/Resources/List/Playlist/Track.php +++ b/app/Filament/Resources/List/Playlist/Track.php @@ -15,16 +15,21 @@ use App\Filament\Resources\List\Playlist\Track\Pages\EditTrack; use App\Filament\Resources\List\Playlist\Track\Pages\ListTracks; use App\Filament\Resources\List\Playlist\Track\Pages\ViewTrack; +use App\Filament\Resources\Wiki\Anime\Theme\Entry; use App\Filament\Resources\Wiki\Video as VideoResource; use App\Models\List\Playlist as PlaylistModel; use App\Models\List\Playlist\PlaylistTrack as TrackModel; +use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; use App\Models\Wiki\Video as VideoModel; +use App\Pivots\Wiki\AnimeThemeEntryVideo; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; +use Filament\Forms\Get; use Filament\Infolists\Components\Section; use Filament\Infolists\Infolist; use Filament\Resources\RelationManagers\RelationGroup; use Filament\Tables\Table; +use Illuminate\Validation\Rule; /** * Class Track. @@ -126,13 +131,56 @@ public static function form(Form $form): Form ->label(__('filament.resources.singularLabel.playlist')) ->relationship(TrackModel::RELATION_PLAYLIST, PlaylistModel::ATTRIBUTE_NAME) ->searchable() + ->required() + ->rules(['required']) + ->hiddenOn([TrackPlaylistRelationManager::class]) ->createOptionForm(PlaylistResource::form($form)->getComponents()), + Select::make(TrackModel::ATTRIBUTE_ENTRY) + ->label(__('filament.resources.singularLabel.anime_theme_entry')) + ->relationship(TrackModel::RELATION_ENTRY, AnimeThemeEntry::ATTRIBUTE_ID) + ->live(true) + ->useScout(AnimeThemeEntry::class, AnimeThemeEntry::RELATION_ANIME_SHALLOW) + ->rules([ + fn (Get $get) => function () use ($get) { + return [ + Rule::when( + !empty($get(TrackModel::RELATION_ENTRY)) && !empty($get(TrackModel::RELATION_VIDEO)), + [ + Rule::exists(AnimeThemeEntryVideo::class, AnimeThemeEntryVideo::ATTRIBUTE_ENTRY) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $get(TrackModel::RELATION_VIDEO)), + ] + ) + ]; + } + ]), + Select::make(TrackModel::ATTRIBUTE_VIDEO) ->label(__('filament.resources.singularLabel.video')) ->relationship(TrackModel::RELATION_VIDEO, VideoModel::ATTRIBUTE_FILENAME) - ->searchable(), - + ->rules([ + fn (Get $get) => function () use ($get) { + return [ + Rule::when( + !empty($get(TrackModel::RELATION_ENTRY)) && !empty($get(TrackModel::RELATION_VIDEO)), + [ + Rule::exists(AnimeThemeEntryVideo::class, AnimeThemeEntryVideo::ATTRIBUTE_VIDEO) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $get(TrackModel::RELATION_ENTRY)), + ] + ) + ]; + } + ]) + ->options(function (Get $get) { + return VideoModel::query() + ->whereHas(VideoModel::RELATION_ANIMETHEMEENTRIES, function ($query) use ($get) { + $query->where(AnimeThemeEntry::TABLE . '.' . AnimeThemeEntry::ATTRIBUTE_ID, $get(TrackModel::ATTRIBUTE_ENTRY)); + }) + ->get() + ->mapWithKeys(fn (VideoModel $video) => [$video->getKey() => $video->getName()]) + ->toArray(); + }), + TextInput::make(TrackModel::ATTRIBUTE_HASHID) ->label(__('filament.fields.playlist_track.hashid.name')) ->helperText(__('filament.fields.playlist_track.hashid.help')) @@ -165,13 +213,18 @@ public static function table(Table $table): Table { return parent::table($table) ->columns([ - TextColumn::make(TrackModel::RELATION_PLAYLIST.'.'.PlaylistModel::ATTRIBUTE_NAME) + TextColumn::make(TrackModel::RELATION_PLAYLIST . '.' . PlaylistModel::ATTRIBUTE_NAME) ->label(__('filament.resources.singularLabel.playlist')) ->toggleable() ->hiddenOn(TrackPlaylistRelationManager::class) ->urlToRelated(PlaylistResource::class, TrackModel::RELATION_PLAYLIST), - TextColumn::make(TrackModel::RELATION_VIDEO.'.'.VideoModel::ATTRIBUTE_FILENAME) + TextColumn::make(TrackModel::RELATION_ENTRY . '.' . AnimeThemeEntry::ATTRIBUTE_ID) + ->label(__('filament.resources.singularLabel.anime_theme_entry')) + ->toggleable() + ->urlToRelated(Entry::class, TrackModel::RELATION_ENTRY, true), + + TextColumn::make(TrackModel::RELATION_VIDEO . '.' . VideoModel::ATTRIBUTE_FILENAME) ->label(__('filament.resources.singularLabel.video')) ->toggleable() ->urlToRelated(VideoResource::class, TrackModel::RELATION_VIDEO), @@ -202,7 +255,7 @@ public static function infolist(Infolist $infolist): Infolist ->schema([ Section::make(static::getRecordTitle($infolist->getRecord())) ->schema([ - TextEntry::make(TrackModel::RELATION_PLAYLIST.'.'.PlaylistModel::ATTRIBUTE_NAME) + TextEntry::make(TrackModel::RELATION_PLAYLIST . '.' . PlaylistModel::ATTRIBUTE_NAME) ->label(__('filament.resources.singularLabel.playlist')) ->urlToRelated(PlaylistResource::class, TrackModel::RELATION_PLAYLIST), @@ -213,15 +266,15 @@ public static function infolist(Infolist $infolist): Infolist TextEntry::make(TrackModel::ATTRIBUTE_ID) ->label(__('filament.fields.base.id')), - TextEntry::make(TrackModel::RELATION_VIDEO.'.'.VideoModel::ATTRIBUTE_FILENAME) + TextEntry::make(TrackModel::RELATION_VIDEO . '.' . VideoModel::ATTRIBUTE_FILENAME) ->label(__('filament.resources.singularLabel.video')) ->urlToRelated(VideoResource::class, TrackModel::RELATION_VIDEO), - TextEntry::make(TrackModel::RELATION_PREVIOUS.'.'.TrackModel::RELATION_VIDEO.'.'.VideoModel::ATTRIBUTE_FILENAME) + TextEntry::make(TrackModel::RELATION_PREVIOUS . '.' . TrackModel::RELATION_VIDEO . '.' . VideoModel::ATTRIBUTE_FILENAME) ->label(__('filament.fields.playlist_track.previous.name')) ->urlToRelated(Track::class, TrackModel::RELATION_PREVIOUS), - - TextEntry::make(TrackModel::RELATION_NEXT.'.'.TrackModel::RELATION_VIDEO.'.'.VideoModel::ATTRIBUTE_FILENAME) + + TextEntry::make(TrackModel::RELATION_NEXT . '.' . TrackModel::RELATION_VIDEO . '.' . VideoModel::ATTRIBUTE_FILENAME) ->label(__('filament.fields.playlist_track.next.name')) ->urlToRelated(Track::class, TrackModel::RELATION_NEXT), ]) @@ -243,7 +296,8 @@ public static function infolist(Infolist $infolist): Infolist public static function getRelations(): array { return [ - RelationGroup::make(static::getLabel(), + RelationGroup::make( + static::getLabel(), array_merge( [], parent::getBaseRelations(), diff --git a/app/Filament/Resources/Wiki/Anime/Theme.php b/app/Filament/Resources/Wiki/Anime/Theme.php index 2f31f2dc5..2d7d56e42 100644 --- a/app/Filament/Resources/Wiki/Anime/Theme.php +++ b/app/Filament/Resources/Wiki/Anime/Theme.php @@ -176,14 +176,16 @@ public static function form(Form $form): Form ->label(__('filament.resources.singularLabel.anime')) ->relationship(ThemeModel::RELATION_ANIME, AnimeModel::ATTRIBUTE_NAME) ->searchable() - ->hiddenOn(ThemeRelationManager::class), + ->hiddenOn(ThemeRelationManager::class) + ->required() + ->rules(['required']), Select::make(ThemeModel::ATTRIBUTE_TYPE) ->label(__('filament.fields.anime_theme.type.name')) ->helperText(__('filament.fields.anime_theme.type.help')) ->options(ThemeType::asSelectArray()) ->required() - ->live(true) + ->live() ->afterStateUpdated(fn (Set $set, Get $get) => Theme::setThemeSlug($set, $get)) ->rules(['required', new Enum(ThemeType::class)]), @@ -191,7 +193,7 @@ public static function form(Form $form): Form ->label(__('filament.fields.anime_theme.sequence.name')) ->helperText(__('filament.fields.anime_theme.sequence.help')) ->numeric() - ->live(true) + ->live() ->afterStateUpdated(fn (Set $set, Get $get) => Theme::setThemeSlug($set, $get)) ->rules(['nullable', 'integer']), @@ -207,7 +209,7 @@ public static function form(Form $form): Form ->label(__('filament.resources.singularLabel.group')) ->relationship(ThemeModel::RELATION_GROUP, Group::ATTRIBUTE_NAME) ->searchable() - ->live(true) + ->live() ->afterStateUpdated(fn (Set $set, Get $get) => Theme::setThemeSlug($set, $get)) ->createOptionForm(GroupResource::form($form)->getComponents()), ]), @@ -218,7 +220,7 @@ public static function form(Form $form): Form Select::make(ThemeModel::ATTRIBUTE_SONG) ->label(__('filament.resources.singularLabel.song')) ->relationship(ThemeModel::RELATION_SONG, Song::ATTRIBUTE_TITLE) - ->live(true) + ->live() ->useScout(Song::class) ->createOptionForm(SongResource::form($form)->getComponents()) ->afterStateUpdated(function (Set $set, $state) { @@ -237,7 +239,7 @@ public static function form(Form $form): Form ->label(__('filament.resources.label.artists')) ->addActionLabel(__('filament.buttons.add').' '.__('filament.resources.singularLabel.artist')) ->hidden(fn (Get $get) => $get(ThemeModel::ATTRIBUTE_SONG) === null) - ->live(true) + ->live() ->key('song.artists') ->collapsible() ->defaultItems(0) @@ -351,10 +353,10 @@ public static function table(Table $table): Table TextColumn::make(ThemeModel::RELATION_SONG . '.' . Song::ATTRIBUTE_TITLE) ->label(__('filament.resources.singularLabel.song')) ->toggleable() - ->placeholder('-') ->hiddenOn(ThemeSongRelationManager::class) ->urlToRelated(SongResource::class, ThemeModel::RELATION_SONG, limit: 30) - ->tooltip(fn (TextColumn $column) => $column->getState()), + ->placeholder('-') + ->tooltip(fn (TextColumn $column) => is_array($column->getState()) ? null : $column->getState()), ]) ->searchable(); } diff --git a/app/Filament/Resources/Wiki/Anime/Theme/Entry.php b/app/Filament/Resources/Wiki/Anime/Theme/Entry.php index f7701f2f7..7d65a3641 100644 --- a/app/Filament/Resources/Wiki/Anime/Theme/Entry.php +++ b/app/Filament/Resources/Wiki/Anime/Theme/Entry.php @@ -162,6 +162,8 @@ public static function form(Form $form): Form ->label(__('filament.resources.singularLabel.anime')) ->relationship(EntryModel::RELATION_ANIME_SHALLOW, AnimeModel::ATTRIBUTE_NAME) ->searchable() + ->required() + ->rules(['required']) ->visibleOn([CreateEntry::class, EditEntry::class]) ->allowHtml() ->getOptionLabelUsing(fn ($state) => Select::getSearchLabelWithBlade(AnimeModel::find($state))) @@ -171,6 +173,8 @@ public static function form(Form $form): Form ->label(__('filament.resources.singularLabel.anime_theme')) ->relationship(EntryModel::RELATION_THEME, ThemeModel::ATTRIBUTE_ID) ->searchable() + ->required() + ->rules(['required']) ->visibleOn([CreateEntry::class, EditEntry::class]) ->allowHtml() ->getOptionLabelUsing(fn ($state) => Select::getSearchLabelWithBlade(ThemeModel::find($state))) diff --git a/app/Filament/Resources/Wiki/ExternalResource.php b/app/Filament/Resources/Wiki/ExternalResource.php index 82ea4683d..e571b7c09 100644 --- a/app/Filament/Resources/Wiki/ExternalResource.php +++ b/app/Filament/Resources/Wiki/ExternalResource.php @@ -138,7 +138,7 @@ public static function form(Form $form): Form ->label(__('filament.fields.external_resource.link.name')) ->helperText(__('filament.fields.external_resource.link.help')) ->required() - ->live(true) + ->live() ->afterStateUpdated(function (Set $set, ?string $state) { if ($state !== null) { $set(ExternalResourceModel::ATTRIBUTE_SITE, ResourceSite::valueOf($state) ?? ResourceSite::OFFICIAL_SITE); diff --git a/app/Http/Api/Field/List/Playlist/Track/TrackEntryIdField.php b/app/Http/Api/Field/List/Playlist/Track/TrackEntryIdField.php new file mode 100644 index 000000000..b535f6b1a --- /dev/null +++ b/app/Http/Api/Field/List/Playlist/Track/TrackEntryIdField.php @@ -0,0 +1,111 @@ +where(AnimeThemeEntryVideo::ATTRIBUTE_VIDEO, $this->resolveVideoId($request)), + ]; + } + + /** + * Get the filter that can be applied to the field. + * + * @return Filter + */ + public function getFilter(): Filter + { + return new IntFilter($this->getKey(), $this->getColumn()); + } + + /** + * Determine if the field should be included in the select clause of our query. + * + * @param Query $query + * @param Schema $schema + * @return bool + */ + public function shouldSelect(Query $query, Schema $schema): bool + { + // Needed to match video relation. + return true; + } + + /** + * Set the update validation rules for the field. + * + * @param Request $request + * @return array + */ + public function getUpdateRules(Request $request): array + { + return [ + 'sometimes', + 'required', + 'integer', + Rule::exists(AnimeThemeEntry::class, AnimeThemeEntry::ATTRIBUTE_ID), + ]; + } + + /** + * Get dependent video_id field. + * + * @param Request $request + * @return mixed + */ + private function resolveVideoId(Request $request): mixed + { + if ($request->has(PlaylistTrack::ATTRIBUTE_VIDEO)) { + return $request->get(PlaylistTrack::ATTRIBUTE_VIDEO); + } + + /** @var PlaylistTrack|null $track */ + $track = $request->route('track'); + + return $track?->video_id; + } +} diff --git a/app/Http/Api/Field/List/Playlist/Track/TrackVideoIdField.php b/app/Http/Api/Field/List/Playlist/Track/TrackVideoIdField.php index f27b8f2ad..0c05e3752 100644 --- a/app/Http/Api/Field/List/Playlist/Track/TrackVideoIdField.php +++ b/app/Http/Api/Field/List/Playlist/Track/TrackVideoIdField.php @@ -15,6 +15,7 @@ use App\Http\Api\Schema\Schema; use App\Models\List\Playlist\PlaylistTrack; use App\Models\Wiki\Video; +use App\Pivots\Wiki\AnimeThemeEntryVideo; use Illuminate\Http\Request; use Illuminate\Validation\Rule; @@ -45,6 +46,8 @@ public function getCreationRules(Request $request): array 'required', 'integer', Rule::exists(Video::class, Video::ATTRIBUTE_ID), + Rule::exists(AnimeThemeEntryVideo::class, AnimeThemeEntryVideo::ATTRIBUTE_VIDEO) + ->where(AnimeThemeEntryVideo::ATTRIBUTE_ENTRY, $this->resolveEntryId($request)), ]; } @@ -86,4 +89,22 @@ public function getUpdateRules(Request $request): array Rule::exists(Video::class, Video::ATTRIBUTE_ID), ]; } + + /** + * Get dependent entry_id field. + * + * @param Request $request + * @return mixed + */ + private function resolveEntryId(Request $request): mixed + { + if ($request->has(PlaylistTrack::ATTRIBUTE_ENTRY)) { + return $request->get(PlaylistTrack::ATTRIBUTE_ENTRY); + } + + /** @var PlaylistTrack|null $track */ + $track = $request->route('track'); + + return $track?->entry_id; + } } diff --git a/app/Http/Api/Schema/List/Playlist/ForwardBackwardSchema.php b/app/Http/Api/Schema/List/Playlist/ForwardBackwardSchema.php index d737ec248..e58d18853 100644 --- a/app/Http/Api/Schema/List/Playlist/ForwardBackwardSchema.php +++ b/app/Http/Api/Schema/List/Playlist/ForwardBackwardSchema.php @@ -5,6 +5,7 @@ namespace App\Http\Api\Schema\List\Playlist; use App\Http\Api\Field\Field; +use App\Http\Api\Field\List\Playlist\Track\TrackEntryIdField; use App\Http\Api\Field\List\Playlist\Track\TrackHashidsField; use App\Http\Api\Field\List\Playlist\Track\TrackIdField; use App\Http\Api\Field\List\Playlist\Track\TrackVideoIdField; @@ -61,6 +62,7 @@ public function fields(): array [ new TrackIdField($this), new TrackHashidsField($this), + new TrackEntryIdField($this), new TrackVideoIdField($this), ], ); diff --git a/app/Http/Api/Schema/List/Playlist/TrackSchema.php b/app/Http/Api/Schema/List/Playlist/TrackSchema.php index 31863e7a0..e09ef8a83 100644 --- a/app/Http/Api/Schema/List/Playlist/TrackSchema.php +++ b/app/Http/Api/Schema/List/Playlist/TrackSchema.php @@ -5,6 +5,7 @@ namespace App\Http\Api\Schema\List\Playlist; use App\Http\Api\Field\Field; +use App\Http\Api\Field\List\Playlist\Track\TrackEntryIdField; use App\Http\Api\Field\List\Playlist\Track\TrackHashidsField; use App\Http\Api\Field\List\Playlist\Track\TrackIdField; use App\Http\Api\Field\List\Playlist\Track\TrackNextHashidsField; @@ -16,6 +17,7 @@ use App\Http\Api\Include\AllowedInclude; use App\Http\Api\Schema\EloquentSchema; use App\Http\Api\Schema\List\PlaylistSchema; +use App\Http\Api\Schema\Wiki\Anime\Theme\EntrySchema; use App\Http\Api\Schema\Wiki\ArtistSchema; use App\Http\Api\Schema\Wiki\AudioSchema; use App\Http\Api\Schema\Wiki\GroupSchema; @@ -49,12 +51,17 @@ public function allowedIncludes(): array return [ new AllowedInclude(new ArtistSchema(), PlaylistTrack::RELATION_ARTISTS), new AllowedInclude(new AudioSchema(), PlaylistTrack::RELATION_AUDIO), + new AllowedInclude(new EntrySchema(), PlaylistTrack::RELATION_ENTRY), new AllowedInclude(new GroupSchema(), PlaylistTrack::RELATION_THEME_GROUP), new AllowedInclude(new ImageSchema(), PlaylistTrack::RELATION_IMAGES), new AllowedInclude(new PlaylistSchema(), PlaylistTrack::RELATION_PLAYLIST), new AllowedInclude(new TrackSchema(), PlaylistTrack::RELATION_NEXT), new AllowedInclude(new TrackSchema(), PlaylistTrack::RELATION_PREVIOUS), new AllowedInclude(new VideoSchema(), PlaylistTrack::RELATION_VIDEO), + + new AllowedInclude(new ArtistSchema(), 'video.animethemeentries.animetheme.song.artists'), + new AllowedInclude(new GroupSchema(), 'video.animethemeentries.animetheme.group'), + new AllowedInclude(new ImageSchema(), 'video.animethemeentries.animetheme.anime.images'), ]; } @@ -75,6 +82,7 @@ public function fields(): array new TrackPlaylistIdField($this), new TrackPreviousIdField($this), new TrackPreviousHashidsField($this), + new TrackEntryIdField($this), new TrackVideoIdField($this), ], ); diff --git a/app/Models/List/Playlist/PlaylistTrack.php b/app/Models/List/Playlist/PlaylistTrack.php index 8ebdc610a..914110612 100644 --- a/app/Models/List/Playlist/PlaylistTrack.php +++ b/app/Models/List/Playlist/PlaylistTrack.php @@ -11,6 +11,7 @@ use App\Events\List\Playlist\Track\TrackUpdated; use App\Models\BaseModel; use App\Models\List\Playlist; +use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; use App\Models\Wiki\Video; use Database\Factories\List\Playlist\PlaylistTrackFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -20,6 +21,8 @@ /** * Class PlaylistTrack. * + * @property int $entry_id + * @property AnimeThemeEntry $animethemeentry * @property PlaylistTrack|null $next * @property int $next_id * @property int $playlist_id @@ -40,18 +43,20 @@ class PlaylistTrack extends BaseModel implements HasHashids final public const TABLE = 'playlist_tracks'; final public const ATTRIBUTE_ID = 'track_id'; + final public const ATTRIBUTE_ENTRY = 'entry_id'; final public const ATTRIBUTE_NEXT = 'next_id'; final public const ATTRIBUTE_PLAYLIST = 'playlist_id'; final public const ATTRIBUTE_PREVIOUS = 'previous_id'; final public const ATTRIBUTE_VIDEO = 'video_id'; - final public const RELATION_ARTISTS = 'video.animethemeentries.animetheme.song.artists'; + final public const RELATION_ARTISTS = 'animethemeentry.animetheme.song.artists'; final public const RELATION_AUDIO = 'video.audio'; - final public const RELATION_IMAGES = 'video.animethemeentries.animetheme.anime.images'; + final public const RELATION_ENTRY = 'animethemeentry'; + final public const RELATION_IMAGES = 'animethemeentry.animetheme.anime.images'; final public const RELATION_NEXT = 'next'; final public const RELATION_PLAYLIST = 'playlist'; final public const RELATION_PREVIOUS = 'previous'; - final public const RELATION_THEME_GROUP = 'video.animethemeentries.animetheme.group'; + final public const RELATION_THEME_GROUP = 'animethemeentry.animetheme.group'; final public const RELATION_VIDEO = 'video'; /** @@ -139,6 +144,16 @@ public function getSubtitle(): string : $this->playlist->getName(); } + /** + * Get the entry of the track. + * + * @return BelongsTo + */ + public function animethemeentry(): BelongsTo + { + return $this->belongsTo(AnimeThemeEntry::class, PlaylistTrack::ATTRIBUTE_ENTRY); + } + /** * Get the playlist the track belongs to. * diff --git a/app/Policies/Admin/AnnouncementPolicy.php b/app/Policies/Admin/AnnouncementPolicy.php index 0034187e2..506f042cb 100644 --- a/app/Policies/Admin/AnnouncementPolicy.php +++ b/app/Policies/Admin/AnnouncementPolicy.php @@ -27,9 +27,13 @@ class AnnouncementPolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)) : true + fn (): bool => true ); } @@ -41,9 +45,13 @@ public function viewAny(?User $user): bool */ public function view(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Announcement::class)) : true + fn (): bool => true ); } diff --git a/app/Policies/Admin/DumpPolicy.php b/app/Policies/Admin/DumpPolicy.php index 6cfebf3e3..df5becf58 100644 --- a/app/Policies/Admin/DumpPolicy.php +++ b/app/Policies/Admin/DumpPolicy.php @@ -27,9 +27,13 @@ class DumpPolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)) : true + fn (): bool => true ); } @@ -41,9 +45,13 @@ public function viewAny(?User $user): bool */ public function view(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Dump::class)) : true + fn (): bool => true ); } diff --git a/app/Policies/Admin/FeaturePolicy.php b/app/Policies/Admin/FeaturePolicy.php index d12abf1dd..c5cb3704f 100644 --- a/app/Policies/Admin/FeaturePolicy.php +++ b/app/Policies/Admin/FeaturePolicy.php @@ -26,9 +26,13 @@ class FeaturePolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)) : true + fn (): bool => true ); } @@ -41,9 +45,13 @@ public function viewAny(?User $user): bool */ public function view(?User $user, Feature $feature): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(Feature::class)) : $feature->isNullScope() + fn (): bool => $feature->isNullScope() ); } diff --git a/app/Policies/Admin/FeaturedThemePolicy.php b/app/Policies/Admin/FeaturedThemePolicy.php index e49148190..2d25b4b92 100644 --- a/app/Policies/Admin/FeaturedThemePolicy.php +++ b/app/Policies/Admin/FeaturedThemePolicy.php @@ -28,9 +28,13 @@ class FeaturedThemePolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)) : true + fn (): bool => true ); } @@ -43,9 +47,13 @@ public function viewAny(?User $user): bool */ public function view(?User $user, FeaturedTheme $featuredtheme): bool { + if (Filament::isServing()) { + return $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)), - fn (): bool => Filament::isServing() ? $user !== null && $user->can(CrudPermission::VIEW->format(FeaturedTheme::class)) : $featuredtheme->start_at->isBefore(Date::now()) + fn (): bool => $featuredtheme->start_at->isBefore(Date::now()) ); } diff --git a/app/Policies/List/Playlist/PlaylistTrackPolicy.php b/app/Policies/List/Playlist/PlaylistTrackPolicy.php index 53d22fa3e..6c8410519 100644 --- a/app/Policies/List/Playlist/PlaylistTrackPolicy.php +++ b/app/Policies/List/Playlist/PlaylistTrackPolicy.php @@ -31,11 +31,13 @@ class PlaylistTrackPolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); @@ -57,11 +59,13 @@ function (Request $request) use ($user): bool { */ public function view(?User $user, PlaylistTrack $track): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); @@ -80,11 +84,13 @@ function (Request $request) use ($user): bool { */ public function create(User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); @@ -104,11 +110,13 @@ function (Request $request) use ($user): bool { */ public function update(User $user, PlaylistTrack $track): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user, $track): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); @@ -128,11 +136,13 @@ function (Request $request) use ($user, $track): bool { */ public function delete(User $user, PlaylistTrack $track): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user, $track): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); @@ -152,11 +162,13 @@ function (Request $request) use ($user, $track): bool { */ public function restore(User $user, PlaylistTrack $track): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), function (Request $request) use ($user, $track): bool { - if (Filament::isServing()) return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); - /** @var Playlist|null $playlist */ $playlist = $request->route('playlist'); diff --git a/app/Policies/List/PlaylistPolicy.php b/app/Policies/List/PlaylistPolicy.php index 24b5e7132..5debf7205 100644 --- a/app/Policies/List/PlaylistPolicy.php +++ b/app/Policies/List/PlaylistPolicy.php @@ -31,13 +31,13 @@ class PlaylistPolicy */ public function viewAny(?User $user): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->hasRole(RoleEnum::ADMIN->value), - function () use ($user): bool { - return Filament::isServing() - ? $user !== null && $user->hasRole(RoleEnum::ADMIN->value) - : $user === null || $user->can(CrudPermission::VIEW->format(Playlist::class)); - } + fn (): bool => $user === null || $user->can(CrudPermission::VIEW->format(Playlist::class)) ); } @@ -50,6 +50,10 @@ function () use ($user): bool { */ public function view(?User $user, Playlist $playlist): bool { + if (Filament::isServing()) { + return $user !== null && $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user !== null && $user->hasRole(RoleEnum::ADMIN->value), fn (): bool => $user !== null @@ -66,13 +70,13 @@ public function view(?User $user, Playlist $playlist): bool */ public function create(User $user): bool { + if (Filament::isServing()) { + return $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), - function () use ($user): bool { - return Filament::isServing() - ? $user->hasRole(RoleEnum::ADMIN->value) - : $user->can(CrudPermission::CREATE->format(Playlist::class)); - } + fn (): bool => $user->can(CrudPermission::CREATE->format(Playlist::class)) ); } @@ -85,13 +89,13 @@ function () use ($user): bool { */ public function update(User $user, Playlist $playlist): bool { + if (Filament::isServing()) { + return $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), - function () use ($user, $playlist): bool { - return Filament::isServing() - ? $user->hasRole(RoleEnum::ADMIN->value) - : ! $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(CrudPermission::UPDATE->format(Playlist::class)); - } + fn (): bool => ! $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(CrudPermission::UPDATE->format(Playlist::class)) ); } @@ -104,13 +108,13 @@ function () use ($user, $playlist): bool { */ public function delete(User $user, Playlist $playlist): bool { + if (Filament::isServing()) { + return $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), - function () use ($user, $playlist): bool { - return Filament::isServing() - ? $user->hasRole(RoleEnum::ADMIN->value) - : ! $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(CrudPermission::DELETE->format(Playlist::class)); - } + fn (): bool => ! $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(CrudPermission::DELETE->format(Playlist::class)) ); } @@ -123,13 +127,13 @@ function () use ($user, $playlist): bool { */ public function restore(User $user, Playlist $playlist): bool { + if (Filament::isServing()) { + return $user->hasRole(RoleEnum::ADMIN->value); + } + return Nova::whenServing( fn (): bool => $user->hasRole(RoleEnum::ADMIN->value), - function () use ($user, $playlist): bool { - return Filament::isServing() - ? $user->hasRole(RoleEnum::ADMIN->value) - : $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(ExtendedCrudPermission::RESTORE->format(Playlist::class)); - } + fn (): bool => $playlist->trashed() && $user->getKey() === $playlist->user_id && $user->can(ExtendedCrudPermission::RESTORE->format(Playlist::class)) ); } @@ -167,7 +171,7 @@ public function attachAnyImage(User $user): bool } /** - * Determine whether the user can attach a studio to the image. + * Determine whether the user can attach an image to the playlist. * * @param User $user * @param Playlist $playlist diff --git a/database/factories/List/PlaylistFactory.php b/database/factories/List/PlaylistFactory.php index 9661ab326..5a36f9382 100644 --- a/database/factories/List/PlaylistFactory.php +++ b/database/factories/List/PlaylistFactory.php @@ -7,7 +7,11 @@ use App\Enums\Models\List\PlaylistVisibility; use App\Models\List\Playlist; use App\Models\List\Playlist\PlaylistTrack; +use App\Models\Wiki\Anime; +use App\Models\Wiki\Anime\AnimeTheme; +use App\Models\Wiki\Anime\Theme\AnimeThemeEntry; use App\Models\Wiki\Video; +use App\Pivots\Wiki\AnimeThemeEntryVideo; use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Support\Arr; @@ -60,9 +64,21 @@ function (Playlist $playlist) use ($count) { /** @var PlaylistTrack|null $last */ $last = Arr::last($tracks); + $entry = AnimeThemeEntry::factory() + ->for(AnimeTheme::factory()->for(Anime::factory())) + ->create(); + + $video = Video::factory()->create(); + + AnimeThemeEntryVideo::query()->create([ + AnimeThemeEntryVideo::ATTRIBUTE_ENTRY => $entry->getKey(), + AnimeThemeEntryVideo::ATTRIBUTE_VIDEO => $video->getKey(), + ]); + $track = PlaylistTrack::factory() ->for($playlist) - ->for(Video::factory()) + ->for($video) + ->for($entry) ->createOne(); if ($index === 1) { @@ -87,4 +103,51 @@ function (Playlist $playlist) use ($count) { } ); } + + /** + * Define the model's track listing. + * + * @param array $videoIds + * @return static + */ + public function tracksForIds(array $videoIds): static + { + return $this->afterCreating( + function (Playlist $playlist) use ($videoIds) { + $tracks = []; + + for ($index = 0; $index < count($videoIds); $index++) { + $videoId = $videoIds[$index]; + + /** @var PlaylistTrack|null $last */ + $last = Arr::last($tracks); + + $track = PlaylistTrack::factory() + ->for($playlist) + ->for(Video::query()->find($videoId)) + ->for(Video::query()->find($videoId)->animethemeentries()->first()) + ->createOne(); + + if ($index === 1) { + $playlist->first()->associate($track)->save(); + } + + if ($last !== null) { + $last->next()->associate($track); + $last->save(); + + $track->previous()->associate($last); + $track->save(); + } + + if ($index === count($videoIds)) { + $playlist->last()->associate($track); + $playlist->save(); + } + + $tracks[] = $track; + } + } + ); + } } diff --git a/database/migrations/2024_06_29_021412_add_entry_id_attribute_to_tracks_table.php b/database/migrations/2024_06_29_021412_add_entry_id_attribute_to_tracks_table.php new file mode 100644 index 000000000..1acf05683 --- /dev/null +++ b/database/migrations/2024_06_29_021412_add_entry_id_attribute_to_tracks_table.php @@ -0,0 +1,37 @@ +unsignedBigInteger(PlaylistTrack::ATTRIBUTE_ENTRY)->nullable(); + $table->foreign(PlaylistTrack::ATTRIBUTE_ENTRY)->references(AnimeThemeEntry::ATTRIBUTE_ID)->on(AnimeThemeEntry::TABLE)->nullOnDelete(); + }); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + if (Schema::hasColumn(PlaylistTrack::TABLE, PlaylistTrack::ATTRIBUTE_ENTRY)) { + Schema::table(PlaylistTrack::TABLE, function (Blueprint $table) { + $table->dropConstrainedForeignId(PlaylistTrack::ATTRIBUTE_ENTRY); + }); + } + } +};