diff --git a/app/Actions/Models/Wiki/Song/ManageSongPerformances.php b/app/Actions/Models/Wiki/Song/ManageSongPerformances.php index 07759106a..ae9105c7f 100644 --- a/app/Actions/Models/Wiki/Song/ManageSongPerformances.php +++ b/app/Actions/Models/Wiki/Song/ManageSongPerformances.php @@ -21,32 +21,20 @@ class ManageSongPerformances { protected Song $song; - /** @var Collection */ - protected Collection $groups; - - /** @var Collection> */ - protected Collection $performances; - - /** @var Collection> */ - protected Collection $memberships; - - public function __construct() - { - $this->groups = new Collection(); - $this->performances = new Collection(); - $this->memberships = new Collection(); - } - - public function forSong(Song|int $song): static - { + public function __construct( + Song|int $song, + /** @var Collection> */ + protected Collection $performances = new Collection(), + /** @var Collection> */ + protected Collection $memberships = new Collection(), + ) { $this->song = $song instanceof Song ? $song : Song::query()->find($song); - - return $this; } public function addSingleArtist(int $artist, ?string $alias = null, ?string $as = null): static { $this->performances->push([ + Performance::ATTRIBUTE_SONG => $this->song->getKey(), Performance::ATTRIBUTE_ARTIST_TYPE => Relation::getMorphAlias(Artist::class), Performance::ATTRIBUTE_ARTIST_ID => $artist, Performance::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null, @@ -56,38 +44,29 @@ class ManageSongPerformances return $this; } - public function addGroupData(int $group, ?string $alias = null, ?string $as = null): static - { - $this->groups->put($group, [ - Performance::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null, - Performance::ATTRIBUTE_AS => filled($as) ? trim($as) : null, - ]); - - return $this; - } - - public function addMembership(int $group, int $member, ?string $alias = null, ?string $as = null): static - { - $membership = Membership::query()->firstOrCreate([ + public function addMembership( + int $group, + int $member, + ?string $alias = null, + ?string $as = null, + ?string $groupAlias = null, + ?string $groupAs = null + ): static { + $membership = Membership::query()->firstOrCreate($attributes = [ Membership::ATTRIBUTE_ARTIST => $group, Membership::ATTRIBUTE_MEMBER => $member, Membership::ATTRIBUTE_ALIAS => filled($alias) ? trim($alias) : null, Membership::ATTRIBUTE_AS => filled($as) ? trim($as) : null, ]); - $this->memberships->put( - $membership->getKey(), - Arr::only( - $membership->attributesToArray(), - [Membership::ATTRIBUTE_ARTIST, Membership::ATTRIBUTE_MEMBER, Membership::ATTRIBUTE_ALIAS, Membership::ATTRIBUTE_AS] - ) - ); + $this->memberships->put($membership->getKey(), $attributes); $this->performances->push([ + Performance::ATTRIBUTE_SONG => $this->song->getKey(), Performance::ATTRIBUTE_ARTIST_TYPE => $membership->getMorphClass(), Performance::ATTRIBUTE_ARTIST_ID => $membership->getKey(), - Performance::ATTRIBUTE_ALIAS => Arr::get($this->groups->get($group), Performance::ATTRIBUTE_ALIAS), - Performance::ATTRIBUTE_AS => Arr::get($this->groups->get($group), Performance::ATTRIBUTE_AS), + Performance::ATTRIBUTE_ALIAS => filled($groupAlias) ? trim($groupAlias) : null, + Performance::ATTRIBUTE_AS => filled($groupAs) ? trim($groupAs) : null, ]); return $this; @@ -98,59 +77,24 @@ class ManageSongPerformances try { DB::beginTransaction(); - $this->performances = $this->performances->map( - fn (array $performance): array => [ - ...$performance, - Performance::ATTRIBUTE_SONG => $this->song->getKey(), - ] + $new = collect($this->performances) + ->keyBy(fn (array $p): string => $p[Performance::ATTRIBUTE_ARTIST_TYPE].':'.$p[Performance::ATTRIBUTE_ARTIST_ID]); + + $existing = Performance::query() + ->whereBelongsTo($this->song) + ->get() + ->keyBy(fn (Performance $p): string => $p->artist_type.':'.$p->artist_id); + + $models = $new->map( + fn (array $performance) => Performance::query()->updateOrCreate( + Arr::only($performance, [Performance::ATTRIBUTE_SONG, Performance::ATTRIBUTE_ARTIST_TYPE, Performance::ATTRIBUTE_ARTIST_ID]), + Arr::only($performance, [Performance::ATTRIBUTE_ALIAS, Performance::ATTRIBUTE_AS]) + ) ); - // Multiple queries because upsert does not dispatch an event. - foreach ($this->performances->all() as $performance) { - Performance::query()->updateOrCreate( - Arr::only($performance, [Performance::ATTRIBUTE_SONG, Performance::ATTRIBUTE_ARTIST_TYPE, Performance::ATTRIBUTE_ARTIST_ID]), - [ - Performance::ATTRIBUTE_ALIAS => Arr::get($performance, Performance::ATTRIBUTE_ALIAS), - Performance::ATTRIBUTE_AS => Arr::get($performance, Performance::ATTRIBUTE_AS), - ] - ); - } + $existing->diffKeys($new)->each->delete(); - // Delete membership performances that are not in the new list. - $membershipPerformances = Performance::query() - ->whereBelongsTo($this->song) - ->where(Performance::ATTRIBUTE_ARTIST_TYPE, Relation::getMorphAlias(Membership::class)) - ->whereNotIn( - Performance::ATTRIBUTE_ARTIST_ID, - $this->performances->filter(fn (array $performance): bool => $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class)) - ->map(fn (array $performanceMembership) => Arr::get($performanceMembership, Performance::ATTRIBUTE_ARTIST_ID)) - ->all(), - ); - - $membershipPerformances->each(fn (Performance $performance) => $performance->delete()); - - // Delete solo performances that are not in the new list. - $soloPerformances = Performance::query() - ->whereBelongsTo($this->song) - ->where(Performance::ATTRIBUTE_ARTIST_TYPE, Relation::getMorphAlias(Artist::class)) - ->whereNotIn( - Performance::ATTRIBUTE_ARTIST_ID, - $this->performances->filter(fn (array $performance): bool => $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Artist::class)) - ->map(fn (array $solo) => Arr::get($solo, Performance::ATTRIBUTE_ARTIST_ID)) - ->all(), - ); - - $soloPerformances->each(fn (Performance $performance) => $performance->delete()); - - Performance::withoutEvents(function (): void { - foreach ($this->performances->all() as $index => $performance) { - Performance::query() - ->whereBelongsTo($this->song) - ->where(Performance::ATTRIBUTE_ARTIST_TYPE, Arr::get($performance, Performance::ATTRIBUTE_ARTIST_TYPE)) - ->where(Performance::ATTRIBUTE_ARTIST_ID, Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID)) - ->update([Performance::ATTRIBUTE_RELEVANCE => $index + 1]); - } - }); + Performance::setNewOrder($models->pluck(Performance::ATTRIBUTE_ID)->all()); // Update artist_member table to match memberships ArtistMember::query()->upsert( @@ -214,25 +158,16 @@ class ManageSongPerformances */ protected function syncSongArtist(): void { - $songArtists = []; - foreach ($this->performances->all() as $performance) { - if ($performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class)) { - $groupOrArtist = Arr::get( - $this->memberships->get(Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID)), - Membership::ATTRIBUTE_ARTIST - ); - } else { - $groupOrArtist = Arr::get($performance, Performance::ATTRIBUTE_ARTIST_ID); - } + $songArtists = $this->performances->mapWithKeys(function (array $performance): array { + $artistId = $performance[Performance::ATTRIBUTE_ARTIST_TYPE] === Relation::getMorphAlias(Membership::class) + ? Arr::get($this->memberships->get($performance[Performance::ATTRIBUTE_ARTIST_ID]), Membership::ATTRIBUTE_ARTIST) + : $performance[Performance::ATTRIBUTE_ARTIST_ID]; - $songArtists[$groupOrArtist] = [ - ArtistSong::ATTRIBUTE_ALIAS => Arr::get($performance, Performance::ATTRIBUTE_ALIAS), - ArtistSong::ATTRIBUTE_AS => Arr::get($performance, Performance::ATTRIBUTE_AS), + return [ + $artistId => Arr::only($performance, [Performance::ATTRIBUTE_ALIAS, Performance::ATTRIBUTE_AS]), ]; - } - - ArtistSong::withoutEvents(function () use ($songArtists): void { - $this->song->artists()->sync($songArtists); }); + + ArtistSong::withoutEvents(fn () => $this->song->artists()->sync($songArtists)); } } diff --git a/app/Filament/Resources/Wiki/Song/RelationManagers/PerformanceSongRelationManager.php b/app/Filament/Resources/Wiki/Song/RelationManagers/PerformanceSongRelationManager.php index 5209cd344..1926725af 100644 --- a/app/Filament/Resources/Wiki/Song/RelationManagers/PerformanceSongRelationManager.php +++ b/app/Filament/Resources/Wiki/Song/RelationManagers/PerformanceSongRelationManager.php @@ -58,7 +58,7 @@ class PerformanceSongRelationManager extends PerformanceRelationManager $song->load(Song::RELATION_PERFORMANCE_ARTISTS); - $performances = $song->performances; + $performances = $song->performances->sortBy(Performance::ATTRIBUTE_RELEVANCE); $artists = []; $memberships = []; @@ -105,9 +105,7 @@ class PerformanceSongRelationManager extends PerformanceRelationManager return; } - $action = new ManageSongPerformances(); - - $action->forSong($song); + $action = new ManageSongPerformances($song); foreach ($data as $artist) { $groupOrArtist = intval(Arr::get($artist, Artist::ATTRIBUTE_ID)); @@ -123,11 +121,8 @@ class PerformanceSongRelationManager extends PerformanceRelationManager $group = $groupOrArtist; - $action->addGroupData( - $group, - Arr::get($artist, Performance::ATTRIBUTE_ALIAS), - Arr::get($artist, Performance::ATTRIBUTE_AS), - ); + $groupAlias = Arr::get($artist, Performance::ATTRIBUTE_ALIAS); + $groupAs = Arr::get($artist, Performance::ATTRIBUTE_AS); foreach (Arr::get($artist, PerformanceForm::REPEATER_MEMBERSHIPS) as $membership) { $action->addMembership( @@ -135,6 +130,8 @@ class PerformanceSongRelationManager extends PerformanceRelationManager intval(Arr::get($membership, Membership::ATTRIBUTE_MEMBER)), Arr::get($membership, Membership::ATTRIBUTE_ALIAS), Arr::get($membership, Membership::ATTRIBUTE_AS), + $groupAlias, + $groupAs ); } } diff --git a/app/Models/Wiki/Song/Performance.php b/app/Models/Wiki/Song/Performance.php index 0edf5ac2c..0890f4918 100644 --- a/app/Models/Wiki/Song/Performance.php +++ b/app/Models/Wiki/Song/Performance.php @@ -16,12 +16,15 @@ use App\Models\BaseModel; use App\Models\Wiki\Artist; use App\Models\Wiki\Song; use Database\Factories\Wiki\Song\PerformanceFactory; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\Relation; use OwenIt\Auditing\Auditable as HasAudits; use OwenIt\Auditing\Contracts\Auditable; +use Spatie\EloquentSortable\Sortable; +use Spatie\EloquentSortable\SortableTrait; /** * @property int $performance_id @@ -35,11 +38,12 @@ use OwenIt\Auditing\Contracts\Auditable; * * @method static PerformanceFactory factory(...$parameters) */ -class Performance extends BaseModel implements Auditable, SoftDeletable +class Performance extends BaseModel implements Auditable, SoftDeletable, Sortable { use HasAudits; use HasFactory; use SoftDeletes; + use SortableTrait; use Submitable; final public const string TABLE = 'performances'; @@ -102,6 +106,11 @@ class Performance extends BaseModel implements Auditable, SoftDeletable Performance::ATTRIBUTE_RELEVANCE, ]; + public $sortable = [ + 'order_column_name' => Performance::ATTRIBUTE_RELEVANCE, + 'sort_when_creating' => false, + ]; + public function getName(): string { return strval($this->getKey()); @@ -117,6 +126,11 @@ class Performance extends BaseModel implements Auditable, SoftDeletable return $this->artist_type === Relation::getMorphAlias(Membership::class); } + public function buildSortQuery(): Builder + { + return static::query()->whereBelongsTo($this->song); + } + /** * @return BelongsTo */ diff --git a/app/Scout/Elasticsearch/Api/Query/Wiki/Anime/ThemeQuery.php b/app/Scout/Elasticsearch/Api/Query/Wiki/Anime/ThemeQuery.php index b638ebe53..ab2881fa6 100644 --- a/app/Scout/Elasticsearch/Api/Query/Wiki/Anime/ThemeQuery.php +++ b/app/Scout/Elasticsearch/Api/Query/Wiki/Anime/ThemeQuery.php @@ -72,7 +72,7 @@ class ThemeQuery extends ElasticQuery 'bool' => [ 'boost' => 0.7, 'should' => $this->createTextQuery('synonym_slug', $criteria->getTerm()), - ] + ], ], ], ], diff --git a/database/migrations/2026_03_17_000917_fix_unique_performance_constraint.php b/database/migrations/2026_03_17_000917_fix_unique_performance_constraint.php new file mode 100644 index 000000000..3f3e6ff62 --- /dev/null +++ b/database/migrations/2026_03_17_000917_fix_unique_performance_constraint.php @@ -0,0 +1,23 @@ +index('song_id'); + $table->dropUnique('unique_performance'); + $table->unique(['song_id', 'artist_type', 'artist_id', 'deleted_at'], 'unique_performance'); + $table->dropIndex('performances_song_id_index'); + }); + } +};