From 8c789924aee609634bbe119aaccdf4e2d75799b2 Mon Sep 17 00:00:00 2001 From: Kyrch Date: Mon, 30 Oct 2023 14:55:43 -0300 Subject: [PATCH] feat: songresource pivot (#602) --- .../Wiki/SongResource/SongResourceCreated.php | 41 +++++ .../Wiki/SongResource/SongResourceDeleted.php | 41 +++++ .../Wiki/SongResource/SongResourceUpdated.php | 42 +++++ .../Wiki/SongResource/SongResourceAsField.php | 58 +++++++ .../SongResourceResourceIdField.php | 40 +++++ .../SongResource/SongResourceSongIdField.php | 40 +++++ .../Schema/Pivot/Wiki/SongResourceSchema.php | 65 ++++++++ .../Schema/Wiki/ExternalResourceSchema.php | 4 + app/Http/Api/Schema/Wiki/SongSchema.php | 1 + .../Api/Pivot/Wiki/SongResourceController.php | 145 ++++++++++++++++++ .../Collection/SongResourceCollection.php | 36 +++++ .../Wiki/Resource/SongResourceResource.php | 32 ++++ app/Models/Wiki/ExternalResource.php | 17 ++ app/Models/Wiki/Song.php | 17 ++ app/Nova/Resources/Wiki/ExternalResource.php | 22 +++ app/Nova/Resources/Wiki/Song.php | 22 +++ app/Pivots/Wiki/SongResource.php | 101 ++++++++++++ ...0_30_141848_create_song_resource_table.php | 43 ++++++ lang/en/nova.php | 6 + routes/api.php | 2 + 20 files changed, 775 insertions(+) create mode 100644 app/Events/Pivot/Wiki/SongResource/SongResourceCreated.php create mode 100644 app/Events/Pivot/Wiki/SongResource/SongResourceDeleted.php create mode 100644 app/Events/Pivot/Wiki/SongResource/SongResourceUpdated.php create mode 100644 app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceAsField.php create mode 100644 app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceResourceIdField.php create mode 100644 app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceSongIdField.php create mode 100644 app/Http/Api/Schema/Pivot/Wiki/SongResourceSchema.php create mode 100644 app/Http/Controllers/Api/Pivot/Wiki/SongResourceController.php create mode 100644 app/Http/Resources/Pivot/Wiki/Collection/SongResourceCollection.php create mode 100644 app/Http/Resources/Pivot/Wiki/Resource/SongResourceResource.php create mode 100644 app/Pivots/Wiki/SongResource.php create mode 100644 database/migrations/2023_10_30_141848_create_song_resource_table.php diff --git a/app/Events/Pivot/Wiki/SongResource/SongResourceCreated.php b/app/Events/Pivot/Wiki/SongResource/SongResourceCreated.php new file mode 100644 index 000000000..3c95ce5b3 --- /dev/null +++ b/app/Events/Pivot/Wiki/SongResource/SongResourceCreated.php @@ -0,0 +1,41 @@ + + */ +class SongResourceCreated extends PivotCreatedEvent +{ + /** + * Create a new event instance. + * + * @param SongResource $songResource + */ + public function __construct(SongResource $songResource) + { + parent::__construct($songResource->song, $songResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been attached to Song '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/Wiki/SongResource/SongResourceDeleted.php b/app/Events/Pivot/Wiki/SongResource/SongResourceDeleted.php new file mode 100644 index 000000000..2fec78a94 --- /dev/null +++ b/app/Events/Pivot/Wiki/SongResource/SongResourceDeleted.php @@ -0,0 +1,41 @@ + + */ +class SongResourceDeleted extends PivotDeletedEvent +{ + /** + * Create a new event instance. + * + * @param SongResource $songResource + */ + public function __construct(SongResource $songResource) + { + parent::__construct($songResource->song, $songResource->resource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' has been detached from Song '**{$related->getName()}**'."; + } +} diff --git a/app/Events/Pivot/Wiki/SongResource/SongResourceUpdated.php b/app/Events/Pivot/Wiki/SongResource/SongResourceUpdated.php new file mode 100644 index 000000000..2aa6364f9 --- /dev/null +++ b/app/Events/Pivot/Wiki/SongResource/SongResourceUpdated.php @@ -0,0 +1,42 @@ + + */ +class SongResourceUpdated extends PivotUpdatedEvent +{ + /** + * Create a new event instance. + * + * @param SongResource $songResource + */ + public function __construct(SongResource $songResource) + { + parent::__construct($songResource->song, $songResource->resource); + $this->initializeEmbedFields($songResource); + } + + /** + * Get the description for the Discord message payload. + * + * @return string + */ + protected function getDiscordMessageDescription(): string + { + $foreign = $this->getForeign(); + $related = $this->getRelated(); + + return "Resource '**{$foreign->getName()}**' for Song '**{$related->getName()}**' has been updated."; + } +} diff --git a/app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceAsField.php b/app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceAsField.php new file mode 100644 index 000000000..8d9345dad --- /dev/null +++ b/app/Http/Api/Field/Pivot/Wiki/SongResource/SongResourceAsField.php @@ -0,0 +1,58 @@ +validated()); + + $resources = $action->index(SongResource::query(), $query, $request->schema()); + + return new SongResourceCollection($resources, $query); + } + + /** + * Store a newly created resource. + * + * @param StoreRequest $request + * @param Song $song + * @param ExternalResource $resource + * @param StoreAction $action + * @return SongResourceResource + */ + public function store(StoreRequest $request, Song $song, ExternalResource $resource, StoreAction $action): SongResourceResource + { + $validated = array_merge( + $request->validated(), + [ + SongResource::ATTRIBUTE_SONG => $song->getKey(), + SongResource::ATTRIBUTE_RESOURCE => $resource->getKey(), + ] + ); + + $songResource = $action->store(SongResource::query(), $validated); + + return new SongResourceResource($songResource, new Query()); + } + + /** + * Display the specified resource. + * + * @param ShowRequest $request + * @param Song $song + * @param ExternalResource $resource + * @param ShowAction $action + * @return SongResourceResource + */ + public function show(ShowRequest $request, Song $song, ExternalResource $resource, ShowAction $action): SongResourceResource + { + $songResource = SongResource::query() + ->where(SongResource::ATTRIBUTE_SONG, $song->getKey()) + ->where(SongResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->firstOrFail(); + + $query = new Query($request->validated()); + + $show = $action->show($songResource, $query, $request->schema()); + + return new SongResourceResource($show, $query); + } + + /** + * Update the specified resource. + * + * @param UpdateRequest $request + * @param Song $song + * @param ExternalResource $resource + * @param UpdateAction $action + * @return SongResourceResource + */ + public function update(UpdateRequest $request, Song $song, ExternalResource $resource, UpdateAction $action): SongResourceResource + { + $songResource = SongResource::query() + ->where(SongResource::ATTRIBUTE_SONG, $song->getKey()) + ->where(SongResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->firstOrFail(); + + $query = new Query($request->validated()); + + $updated = $action->update($songResource, $request->validated()); + + return new SongResourceResource($updated, $query); + } + + /** + * Remove the specified resource. + * + * @param Song $song + * @param ExternalResource $resource + * @param DestroyAction $action + * @return JsonResponse + */ + public function destroy(Song $song, ExternalResource $resource, DestroyAction $action): JsonResponse + { + $songResource = SongResource::query() + ->where(SongResource::ATTRIBUTE_SONG, $song->getKey()) + ->where(SongResource::ATTRIBUTE_RESOURCE, $resource->getKey()) + ->firstOrFail(); + + $action->destroy($songResource); + + return new JsonResponse([ + 'message' => "Resource '{$resource->getName()}' has been detached from Song '{$song->getName()}'.", + ]); + } +} diff --git a/app/Http/Resources/Pivot/Wiki/Collection/SongResourceCollection.php b/app/Http/Resources/Pivot/Wiki/Collection/SongResourceCollection.php new file mode 100644 index 000000000..fcf71c425 --- /dev/null +++ b/app/Http/Resources/Pivot/Wiki/Collection/SongResourceCollection.php @@ -0,0 +1,36 @@ +collection->map(fn (SongResource $animeResource) => new SongResourceResource($animeResource, $this->query))->all(); + } +} diff --git a/app/Http/Resources/Pivot/Wiki/Resource/SongResourceResource.php b/app/Http/Resources/Pivot/Wiki/Resource/SongResourceResource.php new file mode 100644 index 000000000..f1dbe9c79 --- /dev/null +++ b/app/Http/Resources/Pivot/Wiki/Resource/SongResourceResource.php @@ -0,0 +1,32 @@ +withTimestamps(); } + /** + * Get the song that reference this resource. + * + * @return BelongsToMany + */ + public function song(): BelongsToMany + { + return $this->belongsToMany(Song::class, SongResource::TABLE, ExternalResource::ATTRIBUTE_ID, Song::ATTRIBUTE_ID) + ->using(SongResource::class) + ->withPivot(SongResource::ATTRIBUTE_AS) + ->as(SongResourceResource::$wrap) + ->withTimestamps(); + } + /** * Get the studios that reference this resource. * diff --git a/app/Models/Wiki/Song.php b/app/Models/Wiki/Song.php index cd1dd69a5..25f60504d 100644 --- a/app/Models/Wiki/Song.php +++ b/app/Models/Wiki/Song.php @@ -10,9 +10,11 @@ use App\Events\Wiki\Song\SongRestored; use App\Events\Wiki\Song\SongUpdated; use App\Http\Resources\Pivot\Wiki\Resource\ArtistSongResource; +use App\Http\Resources\Pivot\Wiki\Resource\SongResourceResource; use App\Models\BaseModel; use App\Models\Wiki\Anime\AnimeTheme; use App\Pivots\Wiki\ArtistSong; +use App\Pivots\Wiki\SongResource; use Database\Factories\Wiki\SongFactory; use Elastic\ScoutDriverPlus\Searchable; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -43,6 +45,7 @@ class Song extends BaseModel final public const RELATION_ANIME = 'animethemes.anime'; final public const RELATION_ANIMETHEMES = 'animethemes'; final public const RELATION_ARTISTS = 'artists'; + final public const RELATION_RESOURCES = 'resources'; final public const RELATION_VIDEOS = 'animethemes.animethemeentries.videos'; /** @@ -120,4 +123,18 @@ public function artists(): BelongsToMany ->as(ArtistSongResource::$wrap) ->withTimestamps(); } + + /** + * Get the resources for the song. + * + * @return BelongsToMany + */ + public function resources(): BelongsToMany + { + return $this->belongsToMany(ExternalResource::class, SongResource::TABLE, Song::ATTRIBUTE_ID, ExternalResource::ATTRIBUTE_ID) + ->using(SongResource::class) + ->withPivot(SongResource::ATTRIBUTE_AS) + ->as(SongResourceResource::$wrap) + ->withTimestamps(); + } } diff --git a/app/Nova/Resources/Wiki/ExternalResource.php b/app/Nova/Resources/Wiki/ExternalResource.php index 16c22f2aa..fccb3049d 100644 --- a/app/Nova/Resources/Wiki/ExternalResource.php +++ b/app/Nova/Resources/Wiki/ExternalResource.php @@ -11,6 +11,7 @@ use App\Pivots\BasePivot; use App\Pivots\Wiki\AnimeResource; use App\Pivots\Wiki\ArtistResource; +use App\Pivots\Wiki\SongResource; use App\Pivots\Wiki\StudioResource; use App\Rules\Wiki\Resource\ResourceLinkFormatRule; use Illuminate\Validation\Rule; @@ -230,6 +231,27 @@ function (Text $field, NovaRequest $novaRequest, FormData $formData) { ->hideWhenUpdating(), ]), + BelongsToMany::make(__('nova.resources.label.songs'), ExternalResourceModel::RELATION_SONG, Song::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.fields.song.resources.as.name'), SongResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.fields.song.resources.as.help')), + + DateTime::make(__('nova.fields.base.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.fields.base.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + BelongsToMany::make(__('nova.resources.label.studios'), ExternalResourceModel::RELATION_STUDIOS, Studio::class) ->searchable() ->filterable() diff --git a/app/Nova/Resources/Wiki/Song.php b/app/Nova/Resources/Wiki/Song.php index 7e23f864d..a027b6748 100644 --- a/app/Nova/Resources/Wiki/Song.php +++ b/app/Nova/Resources/Wiki/Song.php @@ -11,6 +11,7 @@ use App\Nova\Resources\Wiki\Anime\Theme; use App\Pivots\BasePivot; use App\Pivots\Wiki\ArtistSong; +use App\Pivots\Wiki\SongResource; use Exception; use Illuminate\Database\Eloquent\Builder; use Laravel\Nova\Fields\BelongsToMany; @@ -190,6 +191,27 @@ public function fields(NovaRequest $request): array ->hideWhenUpdating(), ]), + BelongsToMany::make(__('nova.resources.label.external_resources'), SongModel::RELATION_RESOURCES, ExternalResource::class) + ->searchable() + ->filterable() + ->withSubtitles() + ->showCreateRelationButton() + ->fields(fn () => [ + Text::make(__('nova.fields.song.resources.as.name'), SongResource::ATTRIBUTE_AS) + ->nullable() + ->copyable() + ->rules(['nullable', 'max:192']) + ->help(__('nova.fields.song.resources.as.help')), + + DateTime::make(__('nova.fields.base.created_at'), BasePivot::ATTRIBUTE_CREATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + + DateTime::make(__('nova.fields.base.updated_at'), BasePivot::ATTRIBUTE_UPDATED_AT) + ->hideWhenCreating() + ->hideWhenUpdating(), + ]), + HasMany::make(__('nova.resources.label.anime_themes'), SongModel::RELATION_ANIMETHEMES, Theme::class), Panel::make(__('nova.fields.base.timestamps'), $this->timestamps()) diff --git a/app/Pivots/Wiki/SongResource.php b/app/Pivots/Wiki/SongResource.php new file mode 100644 index 000000000..d2ed2c95f --- /dev/null +++ b/app/Pivots/Wiki/SongResource.php @@ -0,0 +1,101 @@ + SongResourceCreated::class, + 'deleted' => SongResourceDeleted::class, + 'updated' => SongResourceUpdated::class, + ]; + + /** + * Gets the song that owns the anime resource. + * + * @return BelongsTo + */ + public function song(): BelongsTo + { + return $this->belongsTo(Song::class, SongResource::ATTRIBUTE_SONG); + } + + /** + * Gets the resource that owns the anime resource. + * + * @return BelongsTo + */ + public function resource(): BelongsTo + { + return $this->belongsTo(ExternalResource::class, AnimeResource::ATTRIBUTE_RESOURCE); + } +} diff --git a/database/migrations/2023_10_30_141848_create_song_resource_table.php b/database/migrations/2023_10_30_141848_create_song_resource_table.php new file mode 100644 index 000000000..2f5c036fc --- /dev/null +++ b/database/migrations/2023_10_30_141848_create_song_resource_table.php @@ -0,0 +1,43 @@ +timestamps(6); + $table->unsignedBigInteger(SongResource::ATTRIBUTE_SONG); + $table->foreign(SongResource::ATTRIBUTE_SONG)->references(Song::ATTRIBUTE_ID)->on(Song::TABLE)->cascadeOnDelete(); + $table->unsignedBigInteger(SongResource::ATTRIBUTE_RESOURCE); + $table->foreign(SongResource::ATTRIBUTE_RESOURCE)->references(ExternalResource::ATTRIBUTE_ID)->on(ExternalResource::TABLE)->cascadeOnDelete(); + $table->primary([SongResource::ATTRIBUTE_SONG, SongResource::ATTRIBUTE_RESOURCE]); + $table->string(SongResource::ATTRIBUTE_AS)->nullable(); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::dropIfExists(SongResource::TABLE); + } +}; diff --git a/lang/en/nova.php b/lang/en/nova.php index 5a72eb880..81d7164d0 100644 --- a/lang/en/nova.php +++ b/lang/en/nova.php @@ -657,6 +657,12 @@ 'help' => 'The title of the song', 'name' => 'Title', ], + 'resources' => [ + 'as' => [ + 'help' => 'Used to distinguish resources that map to the same song.', + 'name' => 'As' + ] + ] ], 'studio' => [ 'name' => [ diff --git a/routes/api.php b/routes/api.php index de6a206d1..923919fd9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -30,6 +30,7 @@ use App\Http\Controllers\Api\Pivot\Wiki\ArtistMemberController; use App\Http\Controllers\Api\Pivot\Wiki\ArtistResourceController; use App\Http\Controllers\Api\Pivot\Wiki\ArtistSongController; +use App\Http\Controllers\Api\Pivot\Wiki\SongResourceController; use App\Http\Controllers\Api\Pivot\Wiki\StudioImageController; use App\Http\Controllers\Api\Pivot\Wiki\StudioResourceController; use App\Http\Controllers\Api\SearchController; @@ -275,6 +276,7 @@ function apiPivotResourceUri(string $name, string $related, string $foreign): st apiEditablePivotResource('artistmember', 'artist', 'member', ArtistMemberController::class); apiEditablePivotResource('artistresource', 'artist', 'resource', ArtistResourceController::class); apiPivotResource('playlistimage', 'playlist', 'image', PlaylistImageController::class); +apiEditablePivotResource('songresource', 'song', 'resource', SongResourceController::class); apiPivotResource('studioimage', 'studio', 'image', StudioImageController::class); apiEditablePivotResource('studioresource', 'studio', 'resource', StudioResourceController::class); apiEditablePivotResource('artistsong', 'artist', 'song', ArtistSongController::class);