Skip to content

Commit

Permalink
feat(plus): song visibility behaviors for collaborative playlists
Browse files Browse the repository at this point in the history
  • Loading branch information
phanan committed Jul 6, 2024
1 parent e874c80 commit 5c5c538
Show file tree
Hide file tree
Showing 75 changed files with 893 additions and 376 deletions.
16 changes: 16 additions & 0 deletions app/Events/NewPlaylistCollaboratorJoined.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace App\Events;

use App\Models\PlaylistCollaborationToken;
use App\Models\User;
use Illuminate\Queue\SerializesModels;

class NewPlaylistCollaboratorJoined extends Event
{
use SerializesModels;

public function __construct(public User $collaborator, public PlaylistCollaborationToken $token)
{
}
}
9 changes: 9 additions & 0 deletions app/Exceptions/CannotRemoveOwnerFromPlaylistException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class CannotRemoveOwnerFromPlaylistException extends Exception
{
}
9 changes: 9 additions & 0 deletions app/Exceptions/KoelPlusRequiredException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class KoelPlusRequiredException extends Exception
{
}
9 changes: 9 additions & 0 deletions app/Exceptions/NotAPlaylistCollaboratorException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class NotAPlaylistCollaboratorException extends Exception
{
}
9 changes: 9 additions & 0 deletions app/Exceptions/PlaylistCollaborationTokenExpiredException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class PlaylistCollaborationTokenExpiredException extends Exception
{
}
9 changes: 9 additions & 0 deletions app/Exceptions/SmartPlaylistsAreNotCollaborativeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace App\Exceptions;

use Exception;

class SmartPlaylistsAreNotCollaborativeException extends Exception
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,22 @@

namespace App\Http\Controllers\API\PlaylistCollaboration;

use App\Facades\License;
use App\Exceptions\CannotRemoveOwnerFromPlaylistException;
use App\Exceptions\KoelPlusRequiredException;
use App\Exceptions\NotAPlaylistCollaboratorException;
use App\Http\Controllers\Controller;
use App\Http\Requests\API\PlaylistCollaboration\PlaylistCollaboratorDestroyRequest;
use App\Models\Playlist;
use App\Models\User;
use App\Repositories\UserRepository;
use App\Services\PlaylistCollaborationService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Response;

class PlaylistCollaboratorController extends Controller
{
/** @param User $user */
public function __construct(
private PlaylistCollaborationService $service,
private UserRepository $userRepository,
private ?Authenticatable $user
) {
public function __construct(private PlaylistCollaborationService $service, private UserRepository $userRepository)
{
}

public function index(Playlist $playlist)
Expand All @@ -36,20 +34,16 @@ public function destroy(Playlist $playlist, PlaylistCollaboratorDestroyRequest $
/** @var User $collaborator */
$collaborator = $this->userRepository->getOne($request->collaborator);

abort_unless(License::isPlus(), Response::HTTP_FORBIDDEN, 'This feature is only available for Plus users.');

abort_if(
$collaborator->is($this->user),
Response::HTTP_FORBIDDEN,
'You cannot remove yourself from your own playlist.'
);

abort_unless(
$playlist->hasCollaborator($collaborator),
Response::HTTP_NOT_FOUND,
'This user is not a collaborator of this playlist.'
);

$this->service->removeCollaborator($playlist, $collaborator);
try {
$this->service->removeCollaborator($playlist, $collaborator);

return response()->noContent();
} catch (KoelPlusRequiredException) {
abort(Response::HTTP_FORBIDDEN, 'This feature is only available for Plus users.');
} catch (CannotRemoveOwnerFromPlaylistException) {
abort(Response::HTTP_FORBIDDEN, 'You cannot remove yourself from your own playlist.');
} catch (NotAPlaylistCollaboratorException) {
abort(Response::HTTP_NOT_FOUND, 'This user is not a collaborator of this playlist.');
}
}
}
20 changes: 11 additions & 9 deletions app/Http/Controllers/API/PlaylistSongController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
use App\Services\PlaylistService;
use App\Services\SmartPlaylistService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;

class PlaylistSongController extends Controller
{
Expand All @@ -36,11 +38,7 @@ public function index(Playlist $playlist)

$this->authorize('collaborate', $playlist);

$songs = $this->songRepository->getByStandardPlaylist($playlist, $this->user);

return License::isPlus()
? CollaborativeSongResource::collection($songs)
: SongResource::collection($songs);
return self::createResourceCollection($this->songRepository->getByStandardPlaylist($playlist, $this->user));
}

public function store(Playlist $playlist, AddSongsToPlaylistRequest $request)
Expand All @@ -50,17 +48,21 @@ public function store(Playlist $playlist, AddSongsToPlaylistRequest $request)
$this->authorize('collaborate', $playlist);

$songs = $this->songRepository->getMany(ids: $request->songs, scopedUser: $this->user);
$songs->each(fn ($song) => $this->authorize('access', $song));

$this->playlistService->addSongsToPlaylist($playlist, $songs, $this->user);
return self::createResourceCollection($this->playlistService->addSongsToPlaylist($playlist, $songs, $this->user));
}

return response()->noContent();
private static function createResourceCollection(Collection $songs): ResourceCollection
{
return License::isPlus()
? CollaborativeSongResource::collection($songs)
: SongResource::collection($songs);
}

public function destroy(Playlist $playlist, RemoveSongsFromPlaylistRequest $request)
{
abort_if($playlist->is_smart, Response::HTTP_FORBIDDEN, 'Smart playlist content is automatically generated');

$this->authorize('collaborate', $playlist);
$this->playlistService->removeSongsFromPlaylist($playlist, $request->songs);

Expand Down
4 changes: 1 addition & 3 deletions app/Http/Controllers/API/PrivatizeSongsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ public function __invoke(ChangeSongsVisibilityRequest $request, SongService $son
$songs = Song::query()->findMany($request->songs);
$songs->each(fn ($song) => $this->authorize('own', $song));

$songService->privatizeSongs($songs);

return response()->noContent();
return response()->json($songService->markSongsAsPrivate($songs));
}
}
2 changes: 1 addition & 1 deletion app/Http/Controllers/API/PublicizeSongsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __invoke(ChangeSongsVisibilityRequest $request, SongService $son
$songs = Song::query()->findMany($request->songs);
$songs->each(fn ($song) => $this->authorize('own', $song));

$songService->publicizeSongs($songs);
$songService->markSongsAsPublic($songs);

return response()->noContent();
}
Expand Down
6 changes: 3 additions & 3 deletions app/Http/Controllers/API/UserInvitationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use App\Http\Requests\API\GetUserInvitationRequest;
use App\Http\Requests\API\InviteUserRequest;
use App\Http\Requests\API\RevokeUserInvitationRequest;
use App\Http\Resources\UserResource;
use App\Http\Resources\UserProspectResource;
use App\Models\User;
use App\Services\AuthenticationService;
use App\Services\UserInvitationService;
Expand Down Expand Up @@ -37,13 +37,13 @@ public function invite(InviteUserRequest $request)
$this->invitor
);

return UserResource::collection($invitees);
return UserProspectResource::collection($invitees);
}

public function get(GetUserInvitationRequest $request)
{
try {
return UserResource::make($this->invitationService->getUserProspectByToken($request->token));
return UserProspectResource::make($this->invitationService->getUserProspectByToken($request->token));
} catch (InvitationNotFoundException) {
abort(Response::HTTP_NOT_FOUND, 'The invitation token is invalid.');
}
Expand Down
29 changes: 29 additions & 0 deletions app/Http/Resources/AlbumResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@

class AlbumResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'id',
'name',
'artist_id',
'artist_name',
'cover',
'created_at',
];

public const PAGINATION_JSON_STRUCTURE = [
'data' => [
'*' => self::JSON_STRUCTURE,
],
'links' => [
'first',
'last',
'prev',
'next',
],
'meta' => [
'current_page',
'from',
'path',
'per_page',
'to',
],
];

public function __construct(private Album $album)
{
parent::__construct($album);
Expand Down
27 changes: 27 additions & 0 deletions app/Http/Resources/ArtistResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@

class ArtistResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'id',
'name',
'image',
'created_at',
];

public const PAGINATION_JSON_STRUCTURE = [
'data' => [
'*' => self::JSON_STRUCTURE,
],
'links' => [
'first',
'last',
'prev',
'next',
],
'meta' => [
'current_page',
'from',
'path',
'per_page',
'to',
],
];

public function __construct(private Artist $artist)
{
parent::__construct($artist);
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Resources/CollaborativeSongResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

class CollaborativeSongResource extends SongResource
{
public const JSON_STRUCTURE = SongResource::JSON_STRUCTURE + [
'collaboration' => [
'user' => PlaylistCollaboratorResource::JSON_STRUCTURE,
'added_at',
'fmt_added_at',
],
];

/** @return array<mixed> */
public function toArray($request): array
{
Expand Down
12 changes: 12 additions & 0 deletions app/Http/Resources/ExcerptSearchResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@

class ExcerptSearchResource extends JsonResource
{
public const JSON_STRUCTURE = [
'songs' => [
SongResource::JSON_STRUCTURE,
],
'artists' => [
ArtistResource::JSON_STRUCTURE,
],
'albums' => [
AlbumResource::JSON_STRUCTURE,
],
];

public function __construct(private ExcerptSearchResult $result)
{
parent::__construct($result);
Expand Down
7 changes: 7 additions & 0 deletions app/Http/Resources/GenreResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@

class GenreResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'name',
'song_count',
'length',
];

public function __construct(private Genre $genre)
{
parent::__construct($genre);
Expand Down
10 changes: 10 additions & 0 deletions app/Http/Resources/InteractionResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@

class InteractionResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'id',
'songId',
'song_id',
'liked',
'playCount',
'play_count',
];

public function __construct(private Interaction $interaction)
{
parent::__construct($interaction);
Expand Down
5 changes: 5 additions & 0 deletions app/Http/Resources/PlaylistCollaborationTokenResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@

class PlaylistCollaborationTokenResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'token',
];

public function __construct(private PlaylistCollaborationToken $token)
{
parent::__construct($token);
Expand Down
7 changes: 7 additions & 0 deletions app/Http/Resources/PlaylistCollaboratorResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@

class PlaylistCollaboratorResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'id',
'name',
'avatar',
];

public function __construct(private PlaylistCollaborator $collaborator)
{
parent::__construct($collaborator);
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Resources/PlaylistFolderResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@

class PlaylistFolderResource extends JsonResource
{
public const JSON_STRUCTURE = [
'type',
'id',
'name',
'user_id',
'created_at',
];

public function __construct(private PlaylistFolder $folder)
{
parent::__construct($folder);
Expand Down
Loading

0 comments on commit 5c5c538

Please sign in to comment.