#58: Implemented a more stable and useful track details endpoint.

This commit is contained in:
Peter Deltchev 2017-10-28 16:44:08 -07:00
parent 29cd3de86e
commit dba9ae1b15
5 changed files with 177 additions and 42 deletions

View file

@ -2,7 +2,7 @@
/** /**
* Pony.fm - A community for pony fan music. * 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 * 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 * 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) public function getTrackRadioDetails($hash)
{ {
/** @var Track|null $track */
$track = Track $track = Track
::with('user', 'album', 'user.avatar', 'cover', 'comments', 'genre') ::with('user', 'album', 'user.avatar', 'cover', 'comments', 'genre')
->published() ->published()
->whereHash($hash)->first(); ->where('hash', $hash)->first();
if (!$track) { if (!$track) {
return Response::json(['message' => 'Track not found.'], 403); return Response::json(['message' => 'Track not found.'], 403);
} }
$comments = []; return Response::json(static::trackToJson($track, true, false), 200);
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([ /**
* 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, 'id' => $track->id,
'title' => $track->title, 'title' => $track->title,
'description' => $track->description, 'description' => $track->description,
@ -141,19 +162,52 @@ class TracksController extends ApiControllerBase
'name' => $track->genre->name 'name' => $track->genre->name
] : null, ] : null,
'type' => [ 'type' => [
'id' => $track->track_type->id, 'id' => $track->trackType->id,
'name' => $track->track_type->title 'name' => $track->trackType->title
], ],
'covers' => [ 'covers' => [
'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL), 'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL),
'small' => $track->getCoverUrl(Image::SMALL), 'small' => $track->getCoverUrl(Image::SMALL),
'normal' => $track->getCoverUrl(Image::NORMAL) 'normal' => $track->getCoverUrl(Image::NORMAL)
], ],
'comments' => $comments,
// As of 2015-10-28, this should be expected to produce either // As of 2017-10-28, this should be expected to produce
// "direct_upload" or "mlpma" for all tracks. // "direct_upload", "mlpma", or "eqbeats" for all tracks.
'source' => $track->source '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;
} }
} }

View file

@ -2,7 +2,7 @@
/** /**
* Pony.fm - A community for pony fan music. * 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 * 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 * 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. * be used by the on-site player.
* *
* @param string $format one of the format keys from the `$Formats` array * @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 * @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] : [])
);
} }
/** /**

View file

@ -2,7 +2,7 @@
/** /**
* Pony.fm - A community for pony fan music. * 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 * 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 * 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, 'user_id' => $user->id,
'hash' => $faker->md5, 'hash' => $faker->md5,
'title' => $faker->sentence(5), 'title' => $faker->sentence(5),
'slug' => $faker->slug,
'track_type_id' => \Poniverse\Ponyfm\Models\TrackType::UNCLASSIFIED_TRACK, 'track_type_id' => \Poniverse\Ponyfm\Models\TrackType::UNCLASSIFIED_TRACK,
'genre' => $faker->word,
'album' => $faker->sentence(5),
'track_number' => null, 'track_number' => null,
'description' => $faker->paragraph(5), 'description' => $faker->paragraph(5),
'lyrics' => $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_explicit' => false,
'is_downloadable' => true, 'is_downloadable' => true,
'is_listed' => 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),
]; ];
}); });

View file

@ -2,7 +2,7 @@
/** /**
* Pony.fm - A community for pony fan music. * 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 * 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 * 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::get('oembed', 'TracksController@getOembed');
Route::group(['prefix' => 'api/v1', 'middleware' => 'json-exceptions'], function () { 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::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::group(['middleware' => 'auth.oauth:ponyfm:tracks:upload'], function () {
Route::post('tracks', 'Api\V1\TracksController@postUploadTrack'); 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');
}); });
}); });

View file

@ -2,7 +2,7 @@
/** /**
* Pony.fm - A community for pony fan music. * 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 * 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 * 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 <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use Carbon\Carbon;
use Illuminate\Foundation\Testing\WithoutMiddleware; use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Poniverse\Ponyfm\Models\Album;
use Poniverse\Ponyfm\Models\Genre;
use Poniverse\Ponyfm\Models\Track; use Poniverse\Ponyfm\Models\Track;
use Poniverse\Ponyfm\Models\User; use Poniverse\Ponyfm\Models\User;
@ -74,13 +77,18 @@ class ApiTest extends TestCase
public function testUploadWithOptionalData() public function testUploadWithOptionalData()
{ {
/** @var Track $track */
$track = factory(Track::class)->make(); $track = factory(Track::class)->make();
/** @var Genre $genre */
$genre = factory(Genre::class)->make();
/** @var Album $album */
$album = factory(Album::class)->make();
$this->callUploadWithParameters([ $this->callUploadWithParameters([
'title' => $track->title, 'title' => $track->title,
'track_type_id' => $track->track_type_id, 'track_type_id' => $track->track_type_id,
'genre' => $track->genre, 'genre' => $genre->name,
'album' => $track->album, 'album' => $album->title,
'released_at' => \Carbon\Carbon::create(2015, 1, 1, 1, 1, 1)->toIso8601String(), 'released_at' => \Carbon\Carbon::create(2015, 1, 1, 1, 1, 1)->toIso8601String(),
'description' => $track->description, 'description' => $track->description,
'lyrics' => $track->lyrics, 'lyrics' => $track->lyrics,
@ -94,11 +102,11 @@ class ApiTest extends TestCase
]); ]);
$this->seeInDatabase('genres', [ $this->seeInDatabase('genres', [
'name' => $track->genre 'name' => $genre->name
]); ]);
$this->seeInDatabase('albums', [ $this->seeInDatabase('albums', [
'title' => $track->album 'title' => $album->title
]); ]);
$this->seeInDatabase('images', [ $this->seeInDatabase('images', [
@ -121,4 +129,36 @@ class ApiTest extends TestCase
'metadata' => $track->metadata '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']
]
]
]);
}
} }