Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions app/Helpers/StreamingServicePlaylistDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace App\Helpers;

use Illuminate\Support\Arr;

class StreamingServicePlaylistDto
{
public string $id;
public string $name;
public string $tracks;
public array $owner;
public int $number_of_tracks;
public string $image_uri;

public function __construct(array $params)
{
$this->id = Arr::get($params, 'id');
$this->name = Arr::get($params, 'name');
$this->tracks = Arr::get($params, 'tracks');
$this->owner = Arr::get($params, 'owner');
$this->number_of_tracks = Arr::get($params, 'number_of_tracks');
$this->image_uri = Arr::get($params, 'image_uri');
}
}
13 changes: 12 additions & 1 deletion app/Http/Controllers/TriggerPlaylistTransferController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,21 @@ public function __invoke(Request $request): JsonResponse
$playlistTransfer = $user->playlistTransfers()->create([
'source' => $source,
'destination' => $destination,
'playlists' => $playlists,
'status' => PlaylistTransfer::STATUS_PENDING,
]);

collect($playlists)
->each(function ($playlist) use ($playlistTransfer, $source, $user) {
$playlist = $user->playlists()->updateOrCreate([
'remote_id' => $playlist['id'],
'service' => $source,
], [
'name' => $playlist['name'],
]);

$playlistTransfer->playlists()->save($playlist);
});

PlaylistTransferJob::dispatch($playlistTransfer);

return response()->json([
Expand Down
9 changes: 6 additions & 3 deletions app/Jobs/CreateAndSearchForTracksJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ public function updateOrCreateTrack(TrackDto $track, array $remoteIds, array|nul
$trackModel->remote_ids,
$remoteIds,
);
$trackModel->isrc_ids = array_merge(
$trackModel->isrc_ids,
$isrc
$trackModel->isrc_ids = array_unique(
array_merge(
$trackModel->isrc_ids,
$isrc
)
);

$trackModel->save();

return $trackModel;
Expand Down
14 changes: 3 additions & 11 deletions app/Jobs/CreatePlaylistAndDispatchTracksJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class CreatePlaylistAndDispatchTracksJob implements ShouldQueue

public function __construct(
private readonly PlaylistTransfer $playlistTransfer,
private readonly array $playlist
private readonly Playlist $playlist
)
{
}
Expand All @@ -31,14 +31,6 @@ public function handle(): void
$source = $this->playlistTransfer->sourceApi();
$destination = $this->playlistTransfer->destinationApi();

$playlistModel = Playlist::query()->firstOrCreate([
'service' => $source::PROVIDER,
'remote_id' => $this->playlist['id'],
], [
'user_id' => $this->playlistTransfer->user_id,
'name' => $this->playlist['name'],
]);

$tracks = $source->getPlaylistTracks($this->playlist['id']);
$destinationPlaylistId = $destination->createPlaylist($this->playlist['name']);

Expand All @@ -58,14 +50,14 @@ public function handle(): void
...collect($tracks)
->map(fn($t) => new CreateAndSearchForTracksJob(
$this->playlistTransfer,
$playlistModel,
$this->playlist,
$t,
))
->toArray(),
new PopulatePlaylistWithTracksJob(
$this->playlistTransfer,
$destinationPlaylistId,
$playlistModel
$this->playlist
),
new IncrementPlaylistsProcessedJob($this->playlistTransfer),
]
Expand Down
7 changes: 5 additions & 2 deletions app/Jobs/PlaylistTransferJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Jobs;

use App\Models\Playlist;
use App\Models\PlaylistTransfer;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
Expand Down Expand Up @@ -30,8 +31,10 @@ public function handle(): void

Bus::chain(
collect($this->playlistTransfer->playlists)
->map(fn($pt) => new CreatePlaylistAndDispatchTracksJob($this->playlistTransfer, $pt))
->toArray(),
->map(fn(Playlist $p) => new CreatePlaylistAndDispatchTracksJob(
$this->playlistTransfer,
$p
))->toArray(),
)->catch(function (Throwable $throwable) {
Log::error(
"A failure occurred with a playlist transfer ",
Expand Down
13 changes: 12 additions & 1 deletion app/Models/PlaylistTransfer.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class PlaylistTransfer extends Model
{
Expand All @@ -15,7 +16,7 @@ class PlaylistTransfer extends Model

protected $guarded = [];

protected $casts = ['playlists' => 'json'];
protected $with = ['playlists'];

public const string STATUS_PENDING = 'pending';
public const string STATUS_IN_PROGRESS = 'in_progress';
Expand Down Expand Up @@ -48,4 +49,14 @@ public function destinationApi(): StreamingService
->firstOrFail()
);
}

public function playlists(): BelongsToMany
{
return $this->belongsToMany(
Playlist::class,
'playlist_transfer_playlists',
'playlist_id',
'playlist_transfer_id'
);
}
}
2 changes: 1 addition & 1 deletion app/Observers/PlaylistTransferObserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function updated(PlaylistTransfer $playlistTransfer): void
) {

if (
$playlistTransfer->playlists_processed === count($playlistTransfer->playlists)
$playlistTransfer->playlists_processed === $playlistTransfer->playlists()->count()
) {
// oh god no
$playlistTransfer->status = PlaylistTransfer::STATUS_COMPLETED;
Expand Down
1 change: 0 additions & 1 deletion database/factories/PlaylistTransferFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public function definition(): array
return [
'source' => $this->faker->word,
'destination' => $this->faker->word,
'playlists' => [['id' => $this->faker->uuid, 'name' => $this->faker->sentence]],
'status' => $this->faker->randomElement(['pending', 'completed', 'failed']),
'playlists_processed' => 0
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public function up(): void
$table->uuid('id')->primary();
$table->string('source');
$table->string('destination');
$table->json('playlists');
$table->integer('playlists_processed')->default(0);
$table->enum('status', ['pending', 'in_progress', 'completed', 'failed'])
->default('pending');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::create('playlist_transfer_playlists', function (Blueprint $table) {
$table->id();
$table->uuid('playlist_id');
$table->uuid('playlist_transfer_id');
$table->unique(['playlist_id', 'playlist_transfer_id']);
});
}

public function down(): void
{
Schema::dropIfExists('playlist_transfer_playlists');
}
};
112 changes: 98 additions & 14 deletions tests/Feature/Controllers/TriggerPlaylistTransferControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,52 @@
namespace Tests\Feature\Controllers;

use App\Jobs\PlaylistTransferJob;
use App\Models\OauthCredential;
use App\Models\Playlist;
use App\Models\PlaylistTransfer;
use App\Services\SpotifyService;
use App\Services\TidalService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Bus;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;

class TriggerPlaylistTransferControllerTest extends TestCase
{
use RefreshDatabase;
use WithFaker;

protected function setUp(): void
{
parent::setUp();

$this->playlistPayload = [
'id' => $this->faker->uuid(),
'name' => $this->faker->word(),
'tracks' => 'asdf',
'owner' => [
'id' => $this->user()->getKey(),
'name' => 'ronald mcdonanld',
],
'number_of_tracks' => 5,
'image_uri' => $this->faker->url(),
];
}

public function test_it_dispatches_playlist_transfer_job()
{
Bus::fake();

OauthCredential::query()->create([
'user_id' => $this->user()->getKey(),
'provider' => TidalService::PROVIDER,
'provider_id' => TidalService::PROVIDER,
]);
OauthCredential::query()->create([
'user_id' => $this->user()->getKey(),
'provider' => SpotifyService::PROVIDER,
'provider_id' => SpotifyService::PROVIDER,
]);

$this->actingAs($this->user())->post('api/playlist-transfers/trigger', [
'source' => SpotifyService::PROVIDER,
'destination' => TidalService::PROVIDER,
'playlists' => ['playlist1', 'playlist2'],
'playlists' => [$this->playlistPayload],
])->assertCreated()->assertJsonStructure([
'message',
'data' => [
'id',
'source',
'destination',
'playlists',
],
]);

Expand All @@ -55,6 +63,82 @@ public function test_it_rejects_with_incomplete_data($payload)
->assertUnprocessable();
}

public function test_it_creates_playlists_and_associates_them_with_playlist_transfer_model()
{
Bus::fake();

$this->assertDatabaseCount('playlists', 0);
$this->assertDatabaseCount('playlist_transfers', 0);

$this->actingAs($this->user())
->post('api/playlist-transfers/trigger', [
'source' => SpotifyService::PROVIDER,
'destination' => TidalService::PROVIDER,
'playlists' => [$this->playlistPayload],
])->assertCreated()->assertJsonStructure([
'message',
'data' => [
'id',
'source',
'destination',
],
]);

$this->assertDatabaseCount('playlists', 1);
$this->assertDatabaseCount('playlist_transfers', 1);

$pt = PlaylistTransfer::query()->first();
$this->assertEquals(
$pt->playlists()->first()->getKey(),
Playlist::query()->first()->getKey()
);
}

public function test_it_updates_existing_playlist_if_already_exists()
{
Bus::fake();

$this->assertDatabaseCount('playlists', 0);
$this->assertDatabaseCount('playlist_transfers', 0);

$this->actingAs($this->user())
->post('api/playlist-transfers/trigger', [
'source' => SpotifyService::PROVIDER,
'destination' => TidalService::PROVIDER,
'playlists' => [
$this->playlistPayload
],
])->assertCreated()->assertJsonStructure([
'message',
'data' => [
'id',
'source',
'destination',
],
]);

$this->assertDatabaseCount('playlists', 1);
$this->assertDatabaseCount('playlist_transfers', 1);

$this->actingAs($this->user())
->post('api/playlist-transfers/trigger', [
'source' => SpotifyService::PROVIDER,
'destination' => TidalService::PROVIDER,
'playlists' => [
$this->playlistPayload
],
])->assertCreated()->assertJsonStructure([
'message',
'data' => [
'id',
'source',
'destination',
],
]);

$this->assertDatabaseCount('playlists', 1);
}

public static function provideIncompletePayloads(): array
{
return [
Expand Down
26 changes: 26 additions & 0 deletions tests/Feature/Models/PlaylistTransferTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Feature\Models;

use App\Models\Playlist;
use App\Models\PlaylistTransfer;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;

class PlaylistTransferTest extends TestCase
{
use RefreshDatabase;

public function test_it_has_many_playlists()
{
/** @var PlaylistTransfer $playlistTransfer */
$playlistTransfer = PlaylistTransfer::factory()->create(['user_id' => $this->user()->getKey()]);

$playlist = Playlist::factory()->create(['user_id' => $this->user()->getKey()]);
$playlistTransfer->playlists()->save($playlist);

$playlistTransfer = PlaylistTransfer::query()->first();
$this->assertCount(1, $playlistTransfer->playlists);
}
}
Loading