mirror of
https://github.com/koel/koel
synced 2024-09-20 06:11:53 +00:00
feat: better playlist handling
This commit is contained in:
parent
94f0528fca
commit
d90e7641f2
9 changed files with 96 additions and 14 deletions
|
@ -3,18 +3,23 @@
|
|||
namespace App\Http\Controllers\V6\API;
|
||||
|
||||
use App\Http\Controllers\API\Controller;
|
||||
use App\Http\Controllers\V6\Requests\AddSongsToPlaylistRequest;
|
||||
use App\Http\Controllers\V6\Requests\RemoveSongsFromPlaylistRequest;
|
||||
use App\Http\Resources\SongResource;
|
||||
use App\Models\Playlist;
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use App\Services\PlaylistService;
|
||||
use App\Services\SmartPlaylistService;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class PlaylistSongController extends Controller
|
||||
{
|
||||
/** @param User $user */
|
||||
public function __construct(
|
||||
private SongRepository $songRepository,
|
||||
private PlaylistService $playlistService,
|
||||
private SmartPlaylistService $smartPlaylistService,
|
||||
private ?Authenticatable $user
|
||||
) {
|
||||
|
@ -30,4 +35,22 @@ class PlaylistSongController extends Controller
|
|||
: $this->songRepository->getByStandardPlaylist($playlist, $this->user)
|
||||
);
|
||||
}
|
||||
|
||||
public function add(Playlist $playlist, AddSongsToPlaylistRequest $request)
|
||||
{
|
||||
$this->authorize('owner', $playlist);
|
||||
|
||||
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
|
||||
|
||||
$this->playlistService->addSongsToPlaylist($playlist, $request->songs);
|
||||
}
|
||||
|
||||
public function remove(Playlist $playlist, RemoveSongsFromPlaylistRequest $request)
|
||||
{
|
||||
$this->authorize('owner', $playlist);
|
||||
|
||||
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN);
|
||||
|
||||
$this->playlistService->removeSongsFromPlaylist($playlist, $request->songs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\V6\Requests;
|
||||
|
||||
use App\Http\Requests\API\Request;
|
||||
|
||||
/**
|
||||
* @property-read array<string> songs
|
||||
*/
|
||||
class AddSongsToPlaylistRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'songs' => 'required|array',
|
||||
'songs.*' => 'exists:songs,id',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\V6\Requests;
|
||||
|
||||
use App\Http\Requests\API\Request;
|
||||
|
||||
/**
|
||||
* @property-read array<string> songs
|
||||
*/
|
||||
class RemoveSongsFromPlaylistRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'songs' => 'required|array',
|
||||
'songs.*' => 'exists:songs,id',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -21,4 +21,14 @@ class PlaylistService
|
|||
|
||||
return $playlist;
|
||||
}
|
||||
|
||||
public function addSongsToPlaylist(Playlist $playlist, array $songIds): void
|
||||
{
|
||||
$playlist->songs()->syncWithoutDetaching($songIds);
|
||||
}
|
||||
|
||||
public function removeSongsFromPlaylist(Playlist $playlist, array $songIds): void
|
||||
{
|
||||
$playlist->songs()->detach($songIds);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,7 +94,7 @@ const handleDrop = async (event: DragEvent) => {
|
|||
if (type.value === 'favorites') {
|
||||
await favoriteStore.like(songs)
|
||||
} else if (type.value === 'playlist') {
|
||||
await playlistStore.addSongs(playlist, songs)
|
||||
await playlistStore.addSongs(playlist.value, songs)
|
||||
alerts.success(`Added ${pluralize(songs.length, 'song')} into "${playlist.value.name}."`)
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ const download = () => downloadService.fromPlaylist(playlist.value!)
|
|||
const editSmartPlaylist = () => eventBus.emit('MODAL_SHOW_EDIT_SMART_PLAYLIST_FORM', playlist.value)
|
||||
|
||||
const removeSelected = () => {
|
||||
if (!selectedSongs.value.length) return
|
||||
if (!selectedSongs.value.length || playlist.value.is_smart) return
|
||||
|
||||
playlistStore.removeSongs(playlist.value!, selectedSongs.value)
|
||||
songs.value = difference(songs.value, selectedSongs.value)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, Ref } from 'vue'
|
||||
import { Ref } from 'vue'
|
||||
import { favoriteStore, playlistStore, queueStore } from '@/stores'
|
||||
import { alerts, pluralize } from '@/utils'
|
||||
|
||||
|
@ -25,7 +25,7 @@ export const useSongMenuMethods = (songs: Ref<Song[]>, close: Closure) => {
|
|||
|
||||
const addSongsToExistingPlaylist = async (playlist: Playlist) => {
|
||||
close()
|
||||
await playlistStore.addSongs(ref(playlist), songs.value)
|
||||
await playlistStore.addSongs(playlist, songs.value)
|
||||
alerts.success(`Added ${pluralize(songs.value.length, 'song')} into "${playlist.name}."`)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { difference, orderBy, union } from 'lodash'
|
||||
import { reactive, Ref } from 'vue'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { httpService } from '@/services'
|
||||
import models from '@/config/smart-playlist/models'
|
||||
import operators from '@/config/smart-playlist/operators'
|
||||
import { Cache } from '@/services/cache'
|
||||
|
||||
export const playlistStore = {
|
||||
state: reactive({
|
||||
|
@ -54,23 +55,24 @@ export const playlistStore = {
|
|||
},
|
||||
|
||||
async delete (playlist: Playlist) {
|
||||
await httpService.delete(`playlist/${playlist.id}`)
|
||||
await httpService.delete(`playlists/${playlist.id}`)
|
||||
this.state.playlists = difference(this.state.playlists, [playlist])
|
||||
},
|
||||
|
||||
async addSongs (playlist: Ref<Playlist>, songs: Song[]) {
|
||||
if (playlist.value.is_smart) {
|
||||
async addSongs (playlist: Playlist, songs: Song[]) {
|
||||
if (playlist.is_smart) {
|
||||
return playlist
|
||||
}
|
||||
|
||||
const count = playlist.value.songs.length
|
||||
playlist.value.songs = union(playlist.value.songs, songs)
|
||||
const count = playlist.songs.length
|
||||
playlist.songs = union(playlist.songs, songs)
|
||||
|
||||
if (count === playlist.value.songs.length) {
|
||||
if (count === playlist.songs.length) {
|
||||
return playlist
|
||||
}
|
||||
|
||||
await httpService.put(`playlist/${playlist.value.id}/sync`, { songs: playlist.value.songs.map(song => song.id) })
|
||||
await httpService.post(`playlists/${playlist.id}/songs`, { songs: songs.map(song => song.id) })
|
||||
Cache.invalidate(['playlist.songs', playlist.id])
|
||||
|
||||
return playlist
|
||||
},
|
||||
|
@ -81,14 +83,15 @@ export const playlistStore = {
|
|||
}
|
||||
|
||||
playlist.songs = difference(playlist.songs, songs)
|
||||
await httpService.put(`playlist/${playlist.id}/sync`, { songs: playlist.songs.map(song => song.id) })
|
||||
await httpService.delete(`playlists/${playlist.id}/songs`, { songs: songs.map(song => song.id) })
|
||||
Cache.invalidate(['playlist.songs', playlist.id])
|
||||
|
||||
return playlist
|
||||
},
|
||||
|
||||
async update (playlist: Playlist) {
|
||||
const serializedRules = this.serializeSmartPlaylistRulesForStorage(playlist.rules)
|
||||
await httpService.put(`playlist/${playlist.id}`, { name: playlist.name, rules: serializedRules })
|
||||
await httpService.put(`playlists/${playlist.id}`, { name: playlist.name, rules: serializedRules })
|
||||
|
||||
return playlist
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use App\Facades\YouTube;
|
||||
use App\Http\Controllers\API\PlaylistController;
|
||||
use App\Http\Controllers\API\UserController;
|
||||
use App\Http\Controllers\V6\API\AlbumController;
|
||||
use App\Http\Controllers\V6\API\AlbumSongController;
|
||||
|
@ -28,7 +29,12 @@ Route::prefix('api')->middleware('api')->group(static function (): void {
|
|||
Route::apiResource('albums.songs', AlbumSongController::class);
|
||||
Route::apiResource('artists', ArtistController::class);
|
||||
Route::apiResource('artists.songs', ArtistSongController::class);
|
||||
|
||||
Route::apiResource('playlists', PlaylistController::class);
|
||||
Route::apiResource('playlists.songs', PlaylistSongController::class);
|
||||
Route::post('playlists/{playlist}/songs', [PlaylistSongController::class, 'add']);
|
||||
Route::delete('playlists/{playlist}/songs', [PlaylistSongController::class, 'remove']);
|
||||
|
||||
Route::apiResource('songs', SongController::class);
|
||||
Route::get('users', [UserController::class, 'index']);
|
||||
|
||||
|
|
Loading…
Reference in a new issue