feat: better playlist handling

This commit is contained in:
Phan An 2022-07-04 12:39:02 +02:00
parent 94f0528fca
commit d90e7641f2
No known key found for this signature in database
GPG key ID: A81E4477F0BB6FDC
9 changed files with 96 additions and 14 deletions

View file

@ -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);
}
}

View file

@ -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',
];
}
}

View file

@ -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',
];
}
}

View file

@ -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);
}
}

View file

@ -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}."`)
}

View file

@ -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)

View file

@ -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}."`)
}

View file

@ -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
},

View file

@ -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']);