mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-25 06:27:59 +01:00
#20: Implemented the genre merging tool.
This commit is contained in:
parent
3ba8467870
commit
07bb5e2c3a
18 changed files with 404 additions and 15 deletions
76
app/Commands/DeleteGenreCommand.php
Normal file
76
app/Commands/DeleteGenreCommand.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Commands;
|
||||
|
||||
use Gate;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Jobs\DeleteGenre;
|
||||
use Validator;
|
||||
|
||||
class DeleteGenreCommand extends CommandBase
|
||||
{
|
||||
use DispatchesJobs;
|
||||
|
||||
|
||||
/** @var Genre */
|
||||
private $_genreToDelete;
|
||||
private $_destinationGenre;
|
||||
|
||||
public function __construct($genreId, $destinationGenreId) {
|
||||
$this->_genreToDelete = Genre::find($genreId);
|
||||
$this->_destinationGenre = Genre::find($destinationGenreId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() {
|
||||
return Gate::allows('delete', $this->_genreToDelete);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute() {
|
||||
$rules = [
|
||||
'genre_to_delete' => 'required',
|
||||
'destination_genre' => 'required',
|
||||
];
|
||||
|
||||
// The validation will fail if the genres don't exist
|
||||
// because they'll be null.
|
||||
$validator = Validator::make([
|
||||
'genre_to_delete' => $this->_genreToDelete,
|
||||
'destination_genre' => $this->_destinationGenre,
|
||||
], $rules);
|
||||
|
||||
|
||||
if ($validator->fails()) {
|
||||
return CommandResponse::fail($validator);
|
||||
}
|
||||
|
||||
$this->dispatch(new DeleteGenre($this->_genreToDelete, $this->_destinationGenre));
|
||||
|
||||
return CommandResponse::succeed(['message' => 'Genre deleted!']);
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ class RenameGenreCommand extends CommandBase
|
|||
|
||||
public function __construct($genreId, $newName)
|
||||
{
|
||||
$this->_genre = Genre::find($genreId);;
|
||||
$this->_genre = Genre::find($genreId);
|
||||
$this->_newName = $newName;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace Poniverse\Ponyfm;
|
|||
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use URL;
|
||||
|
@ -30,13 +31,14 @@ use Venturecraft\Revisionable\RevisionableTrait;
|
|||
class Genre extends Model
|
||||
{
|
||||
protected $table = 'genres';
|
||||
|
||||
protected $fillable = ['name', 'slug'];
|
||||
protected $appends = ['track_count', 'url'];
|
||||
protected $hidden = ['trackCountRelation'];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
use SlugTrait, RevisionableTrait;
|
||||
use SlugTrait, SoftDeletes, RevisionableTrait;
|
||||
|
||||
public function tracks(){
|
||||
return $this->hasMany(Track::class, 'genre_id');
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Input;
|
||||
use Poniverse\Ponyfm\Commands\DeleteGenreCommand;
|
||||
use Poniverse\Ponyfm\Commands\RenameGenreCommand;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Poniverse\Ponyfm\Http\Controllers\ApiControllerBase;
|
||||
|
@ -50,4 +51,11 @@ class GenresController extends ApiControllerBase
|
|||
$command = new RenameGenreCommand($genreId, Input::get('name'));
|
||||
return $this->execute($command);
|
||||
}
|
||||
|
||||
|
||||
public function deleteGenre($genreId)
|
||||
{
|
||||
$command = new DeleteGenreCommand($genreId, Input::get('destination_genre_id'));
|
||||
return $this->execute($command);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,6 +145,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-area']], function() {
|
||||
Route::get('/genres', 'Api\Web\GenresController@getIndex');
|
||||
Route::put('/genres/{id}', 'Api\Web\GenresController@putRename')->where('id', '\d+');
|
||||
Route::delete('/genres/{id}', 'Api\Web\GenresController@deleteGenre')->where('id', '\d+');
|
||||
});
|
||||
|
||||
Route::post('/auth/logout', 'Api\Web\AuthController@postLogout');
|
||||
|
|
78
app/Jobs/DeleteGenre.php
Normal file
78
app/Jobs/DeleteGenre.php
Normal file
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace Poniverse\Ponyfm\Jobs;
|
||||
|
||||
use Auth;
|
||||
use Poniverse\Ponyfm\Genre;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Bus\SelfHandling;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Poniverse\Ponyfm\Track;
|
||||
use SerializesModels;
|
||||
|
||||
class DeleteGenre extends Job implements SelfHandling, ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $executingUser;
|
||||
protected $genreToDelete;
|
||||
protected $destinationGenre;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param Genre $genreToDelete
|
||||
* @param Genre $destinationGenre
|
||||
*/
|
||||
public function __construct(Genre $genreToDelete, Genre $destinationGenre)
|
||||
{
|
||||
$this->executingUser = Auth::user();
|
||||
$this->genreToDelete = $genreToDelete;
|
||||
$this->destinationGenre = $destinationGenre;
|
||||
|
||||
// The genre is deleted synchronously before the job is executed in
|
||||
// order to prevent race conditions.
|
||||
$this->genreToDelete->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// The user who kicked off this job is used when generating revision log entries.
|
||||
Auth::login($this->executingUser);
|
||||
|
||||
// This is done instead of a single UPDATE query in order to
|
||||
// generate revision logs for the change.
|
||||
$this->genreToDelete->tracks()->chunk(200, function ($tracks) {
|
||||
foreach ($tracks as $track) {
|
||||
/** @var Track $track */
|
||||
|
||||
$track->genre_id = $this->destinationGenre->id;
|
||||
$track->save();
|
||||
$track->updateTags();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
48
app/Library/SerializesModels.php
Normal file
48
app/Library/SerializesModels.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Contracts\Database\ModelIdentifier;
|
||||
|
||||
/**
|
||||
* Class SerializesModels
|
||||
* This version of the SerializesModel trait overrides a method to make it work
|
||||
* with soft-deletable models.
|
||||
*
|
||||
* @link https://github.com/laravel/framework/issues/9347#issuecomment-120803564
|
||||
*/
|
||||
trait SerializesModels {
|
||||
use \Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Get the restored property value after deserialization.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getRestoredPropertyValue($value) {
|
||||
if ($value instanceof ModelIdentifier) {
|
||||
return method_exists($value->class, 'withTrashed')
|
||||
? (new $value->class)->withTrashed()->findOrFail($value->id)
|
||||
: (new $value->class)->findOrFail($value->id);
|
||||
} else {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,4 +28,8 @@ class GenrePolicy
|
|||
public function rename(User $user, Genre $genre) {
|
||||
return $user->hasRole('admin');
|
||||
}
|
||||
|
||||
public function delete(User $user, Genre $genre) {
|
||||
return $user->hasRole('admin');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ use Auth;
|
|||
use Cache;
|
||||
use Config;
|
||||
use DB;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Poniverse\Ponyfm\Traits\SlugTrait;
|
||||
use Exception;
|
||||
use External;
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
"barryvdh/laravel-ide-helper": "^2.1",
|
||||
"guzzlehttp/guzzle": "~6.0",
|
||||
"doctrine/dbal": "^2.5",
|
||||
"venturecraft/revisionable": "^1.23"
|
||||
"venturecraft/revisionable": "^1.23",
|
||||
"pda/pheanstalk": "~3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"fzaninotto/faker": "~1.4",
|
||||
|
|
54
composer.lock
generated
54
composer.lock
generated
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "5f66a059010df46b5b6e50c3e4056e17",
|
||||
"content-hash": "07e7a5fff5a8914a7ced3d14959d194f",
|
||||
"hash": "edca1732ab37f49b64614d5729d652d3",
|
||||
"content-hash": "b476009ee841e5b048e73b4fab8372ee",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-ide-helper",
|
||||
|
@ -1622,6 +1622,56 @@
|
|||
],
|
||||
"time": "2015-07-14 17:31:05"
|
||||
},
|
||||
{
|
||||
"name": "pda/pheanstalk",
|
||||
"version": "v3.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pda/pheanstalk.git",
|
||||
"reference": "430e77c551479aad0c6ada0450ee844cf656a18b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pda/pheanstalk/zipball/430e77c551479aad0c6ada0450ee844cf656a18b",
|
||||
"reference": "430e77c551479aad0c6ada0450ee844cf656a18b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pheanstalk\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Annesley",
|
||||
"email": "paul@annesley.cc",
|
||||
"homepage": "http://paul.annesley.cc/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "PHP client for beanstalkd queue",
|
||||
"homepage": "https://github.com/pda/pheanstalk",
|
||||
"keywords": [
|
||||
"beanstalkd"
|
||||
],
|
||||
"time": "2015-08-07 21:42:41"
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/reflection-docblock",
|
||||
"version": "2.0.4",
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2015 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddDeletedAtColumnToGenres extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('genres', function(Blueprint $table) {
|
||||
$table->softDeletes()->index();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('genres', function(Blueprint $table) {
|
||||
$table->dropSoftDeletes();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateFailedJobsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->longText('payload');
|
||||
$table->timestamp('failed_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('failed_jobs');
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
<th>Genre</th>
|
||||
<th class="-status"></th>
|
||||
<th># of tracks (including deleted)</th>
|
||||
<th>Actions</th>
|
||||
<th class="-actions">Actions</th>
|
||||
</thead>
|
||||
<tr ng-repeat="genre in genres">
|
||||
<td>
|
||||
|
@ -23,8 +23,10 @@
|
|||
</td>
|
||||
<td><i ng-show="genre.isSaving" class="icon-cog icon-spin icon-large"></i></td>
|
||||
<td><a ng-href="{{ genre.url }}">{{ genre.track_count }}</a></td>
|
||||
<td>
|
||||
<button class="btn btn-warning" disabled>Merge…</button>
|
||||
<td class="-actions">
|
||||
<button class="btn btn-warning" ng-hide="mergeInProgress" ng-click="startMerge(genre)">Merge…</button>
|
||||
<button class="btn btn-danger" ng-show="mergeInProgress && genreToDelete.id != genre.id" ng-click="finishMerge(genre)">Merge in <em>{{ genreToDelete.name }}</em>…</button>
|
||||
<button class="btn btn-warning" ng-show="mergeInProgress && genreToDelete.id == genre.id" ng-click="cancelMerge()">Cancel merge</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
@ -20,13 +20,21 @@ angular.module('ponyfm').controller 'admin-genres', [
|
|||
|
||||
$scope.genres = []
|
||||
|
||||
# Used for merging/deleting genres
|
||||
$scope.mergeInProgress = false
|
||||
$scope.genreToDelete = null
|
||||
|
||||
setGenres = (genres) ->
|
||||
$scope.genres = []
|
||||
for genre in genres
|
||||
genre.isSaving = false
|
||||
genre.isError = false
|
||||
$scope.genres.push(genre)
|
||||
|
||||
genres.fetch().done setGenres
|
||||
loadGenres = () ->
|
||||
genres.fetch().done setGenres
|
||||
|
||||
loadGenres()
|
||||
|
||||
|
||||
# Renames the given genre
|
||||
|
@ -42,7 +50,17 @@ angular.module('ponyfm').controller 'admin-genres', [
|
|||
genre.isSaving = false
|
||||
|
||||
|
||||
# Merges genre1 into genre2
|
||||
mergeGenre = (genre1, genre2) ->
|
||||
# stub method
|
||||
$scope.startMerge = (genreToDelete) ->
|
||||
$scope.genreToDelete = genreToDelete
|
||||
$scope.mergeInProgress = true
|
||||
|
||||
$scope.cancelMerge = () ->
|
||||
$scope.genreToDelete = null
|
||||
$scope.mergeInProgress = false
|
||||
|
||||
$scope.finishMerge = (destinationGenre) ->
|
||||
$scope.mergeInProgress = false
|
||||
genres.merge($scope.genreToDelete.id, destinationGenre.id)
|
||||
.done (response) ->
|
||||
loadGenres()
|
||||
]
|
||||
|
|
|
@ -41,5 +41,17 @@ angular.module('ponyfm').factory('admin-genres', [
|
|||
|
||||
def.promise()
|
||||
|
||||
merge: (genre_id_to_delete, destination_genre_id) ->
|
||||
url = "/api/web/admin/genres/#{genre_id_to_delete}"
|
||||
def = new $.Deferred()
|
||||
|
||||
$http.delete(url, {params: {destination_genre_id: destination_genre_id}})
|
||||
.success (response)->
|
||||
def.resolve(response)
|
||||
|
||||
.error (response)->
|
||||
def.reject(response)
|
||||
|
||||
def.promise()
|
||||
self
|
||||
])
|
||||
|
|
4
resources/assets/styles/admin.less
vendored
4
resources/assets/styles/admin.less
vendored
|
@ -24,4 +24,8 @@
|
|||
.-status {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.-actions {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,5 +36,9 @@ cp -n "/vagrant/resources/environments/.env.local" "/vagrant/.env"
|
|||
php artisan migrate
|
||||
php artisan db:seed
|
||||
|
||||
echo "Now - if you haven't already, SSH into the VM and run \`php artisan poni:setup\`!"
|
||||
echo "See the README for more details."
|
||||
echo ""
|
||||
echo "+-----------------------------------------------+"
|
||||
echo "| Now - if you haven't already, SSH into the VM |"
|
||||
echo "| and run \`php artisan poni:setup\`! |"
|
||||
echo "| See the README for more details. |"
|
||||
echo "+-----------------------------------------------+"
|
||||
|
|
Loading…
Reference in a new issue