From dba9ae1b154411f2c39803474fe68487cb7f97f4 Mon Sep 17 00:00:00 2001 From: Peter Deltchev Date: Sat, 28 Oct 2017 16:44:08 -0700 Subject: [PATCH] #58: Implemented a more stable and useful track details endpoint. --- .../Controllers/Api/V1/TracksController.php | 108 +++++++++++++----- app/Models/Track.php | 14 ++- database/factories/ModelFactory.php | 40 ++++++- routes/web.php | 7 +- tests/ApiTest.php | 50 +++++++- 5 files changed, 177 insertions(+), 42 deletions(-) diff --git a/app/Http/Controllers/Api/V1/TracksController.php b/app/Http/Controllers/Api/V1/TracksController.php index 00b7fdba..88fd028f 100644 --- a/app/Http/Controllers/Api/V1/TracksController.php +++ b/app/Http/Controllers/Api/V1/TracksController.php @@ -2,7 +2,7 @@ /** * Pony.fm - A community for pony fan music. - * Copyright (C) 2015 Peter Deltchev + * Copyright (C) 2015-2017 Peter Deltchev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -75,38 +75,59 @@ class TracksController extends ApiControllerBase } } + /** + * Returns a stable representation of a track for the API. This is very + * similar to the radio representation below but finds tracks by ID and is + * expected to be more stable. + * + * @param int $id track ID + * @return \Illuminate\Http\JsonResponse + */ + public function getTrackDetails($id) { + /** @var Track|null $track */ + $track = Track + ::with('user', 'album', 'user.avatar', 'cover', 'genre') + ->published() + ->where('id', $id)->first(); + if (!$track) { + return Response::json(['message' => 'Track not found.'], 404); + } + + return Response::json(static::trackToJson($track, false, true), 200); + } + + /** + * Returns a serialized version of a track for use by radios. + * + * @deprecated in favour of getTrackDetails + * @param $hash + * @return \Illuminate\Http\JsonResponse + */ public function getTrackRadioDetails($hash) { + /** @var Track|null $track */ $track = Track ::with('user', 'album', 'user.avatar', 'cover', 'comments', 'genre') ->published() - ->whereHash($hash)->first(); + ->where('hash', $hash)->first(); if (!$track) { return Response::json(['message' => 'Track not found.'], 403); } - $comments = []; - foreach ($track->comments as $comment) { - $comments[] = [ - 'id' => $comment->id, - 'created_at' => $comment->created_at, - 'content' => $comment->content, - 'user' => [ - 'name' => $comment->user->display_name, - 'id' => $comment->user->id, - 'url' => $comment->user->url, - 'avatars' => [ - 'normal' => $comment->user->getAvatarUrl(Image::NORMAL), - 'thumbnail' => $comment->user->getAvatarUrl(Image::THUMBNAIL), - 'small' => $comment->user->getAvatarUrl(Image::SMALL), - ] - ] - ]; - } + return Response::json(static::trackToJson($track, true, false), 200); + } - return Response::json([ + /** + * Helper method to form the serialized version of a track for the V1 API. + * + * @param Track $track + * @param bool $includeComments if true, includes the track's comments in the serialization + * @return array serialized track + */ + private static function trackToJson(Track $track, bool $includeComments, bool $includeStreamUrl) { + $trackResponse = [ 'id' => $track->id, 'title' => $track->title, 'description' => $track->description, @@ -141,19 +162,52 @@ class TracksController extends ApiControllerBase 'name' => $track->genre->name ] : null, 'type' => [ - 'id' => $track->track_type->id, - 'name' => $track->track_type->title + 'id' => $track->trackType->id, + 'name' => $track->trackType->title ], 'covers' => [ 'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL), 'small' => $track->getCoverUrl(Image::SMALL), 'normal' => $track->getCoverUrl(Image::NORMAL) ], - 'comments' => $comments, - // As of 2015-10-28, this should be expected to produce either - // "direct_upload" or "mlpma" for all tracks. + // As of 2017-10-28, this should be expected to produce + // "direct_upload", "mlpma", or "eqbeats" for all tracks. 'source' => $track->source - ], 200); + ]; + + if ($includeComments) { + $comments = []; + foreach ($track->comments as $comment) { + $comments[] = [ + 'id' => $comment->id, + 'created_at' => $comment->created_at, + 'content' => $comment->content, + 'user' => [ + 'name' => $comment->user->display_name, + 'id' => $comment->user->id, + 'url' => $comment->user->url, + 'avatars' => [ + 'normal' => $comment->user->getAvatarUrl(Image::NORMAL), + 'thumbnail' => $comment->user->getAvatarUrl(Image::THUMBNAIL), + 'small' => $comment->user->getAvatarUrl(Image::SMALL), + ] + ] + ]; + } + + $trackResponse['comments'] = $comments; + } + + if ($includeStreamUrl) { + $trackResponse['streams'] = [ + 'mp3' => [ + 'url' => $track->getStreamUrl('MP3', session('api_client_id')), + 'mime_type' => Track::$Formats['MP3']['mime_type'], + ] + ]; + } + + return $trackResponse; } } diff --git a/app/Models/Track.php b/app/Models/Track.php index e18f4922..fdbb6d2d 100644 --- a/app/Models/Track.php +++ b/app/Models/Track.php @@ -2,7 +2,7 @@ /** * Pony.fm - A community for pony fan music. - * Copyright (C) 2015 Peter Deltchev + * Copyright (C) 2015-2017 Peter Deltchev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -890,11 +890,19 @@ class Track extends Model implements Searchable, Commentable, Favouritable * be used by the on-site player. * * @param string $format one of the format keys from the `$Formats` array + * @param string $apiClientId if a URL is being requested for the third-party + * API, this should be set to the requesting app's + * client ID. * @return string */ - public function getStreamUrl($format = 'MP3') + public function getStreamUrl(string $format = 'MP3', string $apiClientId = null) { - return action('TracksController@getStream', ['id' => $this->id, 'extension' => self::$Formats[$format]['extension']]); + return action('TracksController@getStream', + [ + 'id' => $this->id, + 'extension' => self::$Formats[$format]['extension'] + ] + ($apiClientId !== null ? ['api_client_id' => $apiClientId] : []) + ); } /** diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 6be92289..071b2ff6 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -2,7 +2,7 @@ /** * Pony.fm - A community for pony fan music. - * Copyright (C) 2015 Peter Deltchev + * Copyright (C) 2015-2017 Peter Deltchev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -52,9 +52,8 @@ $factory->define(\Poniverse\Ponyfm\Models\Track::class, function(\Faker\Generato 'user_id' => $user->id, 'hash' => $faker->md5, 'title' => $faker->sentence(5), + 'slug' => $faker->slug, 'track_type_id' => \Poniverse\Ponyfm\Models\TrackType::UNCLASSIFIED_TRACK, - 'genre' => $faker->word, - 'album' => $faker->sentence(5), 'track_number' => null, 'description' => $faker->paragraph(5), 'lyrics' => $faker->paragraph(5), @@ -62,6 +61,39 @@ $factory->define(\Poniverse\Ponyfm\Models\Track::class, function(\Faker\Generato 'is_explicit' => false, 'is_downloadable' => true, 'is_listed' => true, - 'metadata' => '{"this":{"is":["very","random","metadata"]}}' + 'metadata' => '{"this":{"is":["very","random","metadata"]}}', + 'duration' => $faker->randomFloat(null, 30, 600) + ]; +}); + +$factory->define(\Poniverse\Ponyfm\Models\Genre::class, function(\Faker\Generator $faker) { + return [ + 'name' => $faker->word, + 'slug' => $faker->slug, + ]; +}); + +/** + * + * @property integer $id + * @property integer $user_id + * @property string $title + * @property string $slug + * @property string $description + * @property integer $cover_id + * @property integer $track_count + * @property integer $view_count + * @property integer $download_count + * @property integer $favourite_count + * @property integer $comment_count + * @property \Carbon\Carbon $created_at + * @property string $updated_at + * @property \Carbon\Carbon $deleted_at + */ +$factory->define(\Poniverse\Ponyfm\Models\Album::class, function(\Faker\Generator $faker) { + return [ + 'title' => $faker->sentence(5), + 'slug' => $faker->slug, + 'description' => $faker->paragraph(5), ]; }); diff --git a/routes/web.php b/routes/web.php index b54073ab..c50a2297 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,7 +2,7 @@ /** * Pony.fm - A community for pony fan music. - * Copyright (C) 2015 Peter Deltchev + * Copyright (C) 2015-2017 Peter Deltchev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -89,12 +89,13 @@ Route::group(['prefix' => 'notifications/email'], function() { Route::get('oembed', 'TracksController@getOembed'); Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function () { - Route::get('/tracks/radio-details/{hash}', 'Api\V1\TracksController@getTrackRadioDetails'); + Route::get( '/tracks/radio-details/{hash}', 'Api\V1\TracksController@getTrackRadioDetails'); Route::post('/tracks/radio-details/{hash}', 'Api\V1\TracksController@getTrackRadioDetails'); + Route::get( '/tracks/{id}', 'Api\V1\TracksController@getTrackDetails')->where('id', '\d+'); Route::group(['middleware' => 'auth.oauth:ponyfm:tracks:upload'], function () { Route::post('tracks', 'Api\V1\TracksController@postUploadTrack'); - Route::get('/tracks/{id}/upload-status', 'Api\V1\TracksController@getUploadStatus'); + Route::get( '/tracks/{id}/upload-status', 'Api\V1\TracksController@getUploadStatus'); }); }); diff --git a/tests/ApiTest.php b/tests/ApiTest.php index a3dcaa06..4177a874 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -2,7 +2,7 @@ /** * Pony.fm - A community for pony fan music. - * Copyright (C) 2015 Peter Deltchev + * Copyright (C) 2015-2017 Peter Deltchev * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -18,8 +18,11 @@ * along with this program. If not, see . */ +use Carbon\Carbon; use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\DatabaseMigrations; +use Poniverse\Ponyfm\Models\Album; +use Poniverse\Ponyfm\Models\Genre; use Poniverse\Ponyfm\Models\Track; use Poniverse\Ponyfm\Models\User; @@ -74,13 +77,18 @@ class ApiTest extends TestCase public function testUploadWithOptionalData() { + /** @var Track $track */ $track = factory(Track::class)->make(); + /** @var Genre $genre */ + $genre = factory(Genre::class)->make(); + /** @var Album $album */ + $album = factory(Album::class)->make(); $this->callUploadWithParameters([ 'title' => $track->title, 'track_type_id' => $track->track_type_id, - 'genre' => $track->genre, - 'album' => $track->album, + 'genre' => $genre->name, + 'album' => $album->title, 'released_at' => \Carbon\Carbon::create(2015, 1, 1, 1, 1, 1)->toIso8601String(), 'description' => $track->description, 'lyrics' => $track->lyrics, @@ -94,11 +102,11 @@ class ApiTest extends TestCase ]); $this->seeInDatabase('genres', [ - 'name' => $track->genre + 'name' => $genre->name ]); $this->seeInDatabase('albums', [ - 'title' => $track->album + 'title' => $album->title ]); $this->seeInDatabase('images', [ @@ -121,4 +129,36 @@ class ApiTest extends TestCase 'metadata' => $track->metadata ]); } + + public function testGetTrackDetails() + { + /** @var Track $track */ + $track = factory(Track::class)->create(); + /** @var Genre $genre */ + $genre = factory(Genre::class)->create(); + + $track->genre()->associate($genre); + $this->seeInDatabase('tracks', ['id' => $track->id]); + + $track->published_at = Carbon::now(); + $track->save(); + + + $response = $this + ->withSession(['api_client_id' => 'ponyponyponyponypony']) + ->get("/api/v1/tracks/{$track->id}"); + + $response + ->assertResponseStatus(200) + ->seeJsonSubset([ + 'title' => $track->title, + 'description' => $track->description, + 'streams' => [ + 'mp3' => [ + 'url' => $track->getStreamUrl('MP3', 'ponyponyponyponypony'), + 'mime_type' => Track::$Formats['MP3']['mime_type'] + ] + ] + ]); + } }