mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
fix: only consider episodes accessible if subscribed to podcasts
This commit is contained in:
parent
ee4c97168b
commit
0b622ba0d7
7 changed files with 58 additions and 14 deletions
|
@ -69,16 +69,27 @@ class SongBuilder extends Builder
|
|||
);
|
||||
}
|
||||
|
||||
public function accessibleBy(User $user, bool $withTableName = true): self
|
||||
public function accessibleBy(User $user): self
|
||||
{
|
||||
if (License::isCommunity()) {
|
||||
// In the Community Edition, all songs are accessible by all users.
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->where(static function (Builder $query) use ($user, $withTableName): void {
|
||||
$query->where(($withTableName ? 'songs.' : '') . 'is_public', true)
|
||||
->orWhere(($withTableName ? 'songs.' : '') . 'owner_id', $user->id);
|
||||
// We want to alias both podcasts and podcast_user tables to avoid possible conflicts with other joins.
|
||||
return $this->leftJoin('podcasts as podcasts_a11y', 'songs.podcast_id', 'podcasts_a11y.id')
|
||||
->leftJoin('podcast_user as podcast_user_a11y', static function (JoinClause $join) use ($user): void {
|
||||
$join->on('podcasts_a11y.id', 'podcast_user_a11y.podcast_id')
|
||||
->where('podcast_user_a11y.user_id', $user->id);
|
||||
})
|
||||
->where(static function (Builder $query) use ($user): void {
|
||||
// Songs must be public or owned by the user.
|
||||
$query->where('songs.is_public', true)
|
||||
->orWhere('songs.owner_id', $user->id);
|
||||
})->whereNot(static function (Builder $query): void {
|
||||
// Episodes must belong to a podcast that the user is not subscribed to.
|
||||
$query->whereNotNull('songs.podcast_id')
|
||||
->whereNull('podcast_user_a11y.podcast_id');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ enum SmartPlaylistModel: string
|
|||
public function toColumnName(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::TITLE => 'songs.title',
|
||||
self::LENGTH => 'songs.length',
|
||||
self::GENRE => 'songs.genre',
|
||||
self::YEAR => 'songs.year',
|
||||
self::ALBUM_NAME => 'albums.name',
|
||||
self::ARTIST_NAME => 'artists.name',
|
||||
self::DATE_ADDED => 'songs.created_at',
|
||||
|
|
|
@ -47,10 +47,10 @@ class PlaylistSongController extends Controller
|
|||
|
||||
$this->authorize('collaborate', $playlist);
|
||||
|
||||
$songs = $this->songRepository->getMany(ids: $request->songs, scopedUser: $this->user);
|
||||
$playables = $this->songRepository->getMany(ids: $request->songs, scopedUser: $this->user);
|
||||
|
||||
return self::createResourceCollection(
|
||||
$this->playlistService->addPlayablesToPlaylist($playlist, $songs, $this->user)
|
||||
$this->playlistService->addPlayablesToPlaylist($playlist, $playables, $this->user)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,19 +2,18 @@
|
|||
|
||||
namespace App\Http\Requests\API;
|
||||
|
||||
use App\Models\Song;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Rules\AllPlayablesAreAccessibleBy;
|
||||
|
||||
/**
|
||||
* @property-read array<string> $songs
|
||||
*/
|
||||
class AddSongsToPlaylistRequest extends Request
|
||||
{
|
||||
/** @return array<mixed> */
|
||||
/** @inheritdoc */
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'songs' => ['required', 'array', Rule::exists(Song::class, 'id')],
|
||||
'songs' => ['required', 'array', new AllPlayablesAreAccessibleBy($this->user())],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace App\Http\Requests\API;
|
||||
|
||||
use App\Models\PlaylistFolder;
|
||||
use App\Models\Song;
|
||||
use App\Rules\AllPlayablesAreAccessibleBy;
|
||||
use App\Rules\ValidSmartPlaylistRulePayload;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
|
@ -21,8 +21,7 @@ class PlaylistStoreRequest extends Request
|
|||
{
|
||||
return [
|
||||
'name' => 'required',
|
||||
'songs' => 'array',
|
||||
'songs.*' => [Rule::exists(Song::class, 'id')],
|
||||
'songs' => ['array', new AllPlayablesAreAccessibleBy($this->user())],
|
||||
'rules' => ['array', 'nullable', new ValidSmartPlaylistRulePayload()],
|
||||
'folder_id' => ['nullable', 'sometimes', Rule::exists(PlaylistFolder::class, 'id')],
|
||||
'own_songs_only' => 'sometimes',
|
||||
|
|
|
@ -54,6 +54,7 @@ use Throwable;
|
|||
* // The following are only available for collaborative playlists
|
||||
* @property-read ?string $collaborator_email The email of the user who added the song to the playlist
|
||||
* @property-read ?string $collaborator_name The name of the user who added the song to the playlist
|
||||
* @property-read ?string $collaborator_avatar The avatar of the user who added the song to the playlist
|
||||
* @property-read ?int $collaborator_id The ID of the user who added the song to the playlist
|
||||
* @property-read ?string $added_at The date the song was added to the playlist
|
||||
* @property-read PlayableType $type
|
||||
|
|
30
app/Rules/AllPlayablesAreAccessibleBy.php
Normal file
30
app/Rules/AllPlayablesAreAccessibleBy.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Repositories\SongRepository;
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class AllPlayablesAreAccessibleBy implements ValidationRule
|
||||
{
|
||||
public function __construct(private readonly User $user)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param array<string> $value
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
$ids = array_unique(Arr::wrap($value));
|
||||
|
||||
if ($ids && app(SongRepository::class)->getMany(ids: $ids, scopedUser: $this->user)->count() !== count($ids)) {
|
||||
$fail('Not all songs or episodes exist in the database or are accessible by the user.');
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue