mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-29 08:07:59 +01:00
Add Commands
This commit is contained in:
parent
0f8d11fa83
commit
b6dad84d67
16 changed files with 1125 additions and 0 deletions
46
app/Commands/AddTrackToPlaylistCommand.php
Normal file
46
app/Commands/AddTrackToPlaylistCommand.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Playlist;
|
||||||
|
use App\Track;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AddTrackToPlaylistCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_track;
|
||||||
|
private $_playlist;
|
||||||
|
|
||||||
|
function __construct($playlistId, $trackId)
|
||||||
|
{
|
||||||
|
$this->_playlist = Playlist::find($playlistId);
|
||||||
|
$this->_track = Track::find($trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $user != null && $this->_playlist && $this->_track && $this->_playlist->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$songIndex = $this->_playlist->tracks()->count() + 1;
|
||||||
|
$this->_playlist->tracks()->attach($this->_track, ['position' => $songIndex]);
|
||||||
|
|
||||||
|
Playlist::whereId($this->_playlist->id)->update([
|
||||||
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM playlist_track WHERE playlist_id = ' . $this->_playlist->id . ')')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['message' => 'Track added!']);
|
||||||
|
}
|
||||||
|
}
|
33
app/Commands/CommandBase.php
Normal file
33
app/Commands/CommandBase.php
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
abstract class CommandBase
|
||||||
|
{
|
||||||
|
private $_listeners = array();
|
||||||
|
|
||||||
|
public function listen($listener)
|
||||||
|
{
|
||||||
|
$this->_listeners[] = $listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function notify($message, $progress)
|
||||||
|
{
|
||||||
|
foreach ($this->_listeners as $listener) {
|
||||||
|
$listener($message, $progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public abstract function execute();
|
||||||
|
}
|
58
app/Commands/CommandResponse.php
Normal file
58
app/Commands/CommandResponse.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Validator;
|
||||||
|
|
||||||
|
class CommandResponse
|
||||||
|
{
|
||||||
|
public static function fail($validator)
|
||||||
|
{
|
||||||
|
$response = new CommandResponse();
|
||||||
|
$response->_didFail = true;
|
||||||
|
$response->_validator = $validator;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function succeed($response = null)
|
||||||
|
{
|
||||||
|
$cmdResponse = new CommandResponse();
|
||||||
|
$cmdResponse->_didFail = false;
|
||||||
|
$cmdResponse->_response = $response;
|
||||||
|
|
||||||
|
return $cmdResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private $_validator;
|
||||||
|
private $_response;
|
||||||
|
private $_didFail;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function didFail()
|
||||||
|
{
|
||||||
|
return $this->_didFail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getResponse()
|
||||||
|
{
|
||||||
|
return $this->_response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Validator
|
||||||
|
*/
|
||||||
|
public function getValidator()
|
||||||
|
{
|
||||||
|
return $this->_validator;
|
||||||
|
}
|
||||||
|
}
|
72
app/Commands/CreateAlbumCommand.php
Normal file
72
app/Commands/CreateAlbumCommand.php
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Album;
|
||||||
|
use App\Image;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CreateAlbumCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
|
||||||
|
function __construct($input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||||
|
'cover_id' => 'exists:images,id',
|
||||||
|
'track_ids' => 'exists:tracks,id'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$album = new Album();
|
||||||
|
$album->user_id = Auth::user()->id;
|
||||||
|
$album->title = $this->_input['title'];
|
||||||
|
$album->description = $this->_input['description'];
|
||||||
|
|
||||||
|
if (isset($this->_input['cover_id'])) {
|
||||||
|
$album->cover_id = $this->_input['cover_id'];
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['cover'])) {
|
||||||
|
$cover = $this->_input['cover'];
|
||||||
|
$album->cover_id = Image::upload($cover, Auth::user())->id;
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['remove_cover']) && $this->_input['remove_cover'] == 'true') {
|
||||||
|
$album->cover_id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackIds = explode(',', $this->_input['track_ids']);
|
||||||
|
$album->save();
|
||||||
|
$album->syncTrackIds($trackIds);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['id' => $album->id]);
|
||||||
|
}
|
||||||
|
}
|
79
app/Commands/CreateCommentCommand.php
Normal file
79
app/Commands/CreateCommentCommand.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Comment;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CreateCommentCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
private $_id;
|
||||||
|
private $_type;
|
||||||
|
|
||||||
|
function __construct($type, $id, $input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
$this->_id = $id;
|
||||||
|
$this->_type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'content' => 'required',
|
||||||
|
'track_id' => 'exists:tracks,id',
|
||||||
|
'albums_id' => 'exists:albums,id',
|
||||||
|
'playlist_id' => 'exists:playlists,id',
|
||||||
|
'profile_id' => 'exists:users,id',
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment = new Comment();
|
||||||
|
$comment->user_id = Auth::user()->id;
|
||||||
|
$comment->content = $this->_input['content'];
|
||||||
|
|
||||||
|
if ($this->_type == 'track') {
|
||||||
|
$column = 'track_id';
|
||||||
|
} else {
|
||||||
|
if ($this->_type == 'user') {
|
||||||
|
$column = 'profile_id';
|
||||||
|
} else {
|
||||||
|
if ($this->_type == 'album') {
|
||||||
|
$column = 'album_id';
|
||||||
|
} else {
|
||||||
|
if ($this->_type == 'playlist') {
|
||||||
|
$column = 'playlist_id';
|
||||||
|
} else {
|
||||||
|
App::abort(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$comment->$column = $this->_id;
|
||||||
|
$comment->save();
|
||||||
|
|
||||||
|
return CommandResponse::succeed(Comment::mapPublic($comment));
|
||||||
|
}
|
||||||
|
}
|
69
app/Commands/CreatePlaylistCommand.php
Normal file
69
app/Commands/CreatePlaylistCommand.php
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Playlist;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class CreatePlaylistCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
|
||||||
|
function __construct($input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'is_public' => 'required',
|
||||||
|
'is_pinned' => 'required'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$playlist = new Playlist();
|
||||||
|
$playlist->user_id = Auth::user()->id;
|
||||||
|
$playlist->title = $this->_input['title'];
|
||||||
|
$playlist->description = $this->_input['description'];
|
||||||
|
$playlist->is_public = $this->_input['is_public'] == 'true';
|
||||||
|
|
||||||
|
$playlist->save();
|
||||||
|
|
||||||
|
if ($this->_input['is_pinned'] == 'true') {
|
||||||
|
$playlist->pin(Auth::user()->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $playlist->id,
|
||||||
|
'title' => $playlist->title,
|
||||||
|
'slug' => $playlist->slug,
|
||||||
|
'created_at' => $playlist->created_at,
|
||||||
|
'description' => $playlist->description,
|
||||||
|
'url' => $playlist->url,
|
||||||
|
'is_pinned' => $this->_input['is_pinned'] == 'true',
|
||||||
|
'is_public' => $this->_input['is_public'] == 'true'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
46
app/Commands/DeleteAlbumCommand.php
Normal file
46
app/Commands/DeleteAlbumCommand.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Album;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DeleteAlbumCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_albumId;
|
||||||
|
private $_album;
|
||||||
|
|
||||||
|
function __construct($albumId)
|
||||||
|
{
|
||||||
|
$this->_albumId = $albumId;
|
||||||
|
$this->_album = ALbum::find($albumId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $this->_album && $user != null && $this->_album->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
foreach ($this->_album->tracks as $track) {
|
||||||
|
$track->album_id = null;
|
||||||
|
$track->track_number = null;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_album->delete();
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
43
app/Commands/DeletePlaylistCommand.php
Normal file
43
app/Commands/DeletePlaylistCommand.php
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Playlist;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class DeletePlaylistCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_playlistId;
|
||||||
|
private $_playlist;
|
||||||
|
|
||||||
|
function __construct($playlistId)
|
||||||
|
{
|
||||||
|
$this->_playlistId = $playlistId;
|
||||||
|
$this->_playlist = Playlist::find($playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $this->_playlist && $user != null && $this->_playlist->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
foreach ($this->_playlist->pins as $pin) {
|
||||||
|
$pin->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_playlist->delete();
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
46
app/Commands/DeleteTrackCommand.php
Normal file
46
app/Commands/DeleteTrackCommand.php
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Track;
|
||||||
|
|
||||||
|
class DeleteTrackCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_trackId;
|
||||||
|
private $_track;
|
||||||
|
|
||||||
|
function __construct($trackId)
|
||||||
|
{
|
||||||
|
$this->_trackId = $trackId;
|
||||||
|
$this->_track = Track::find($trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
|
||||||
|
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
if ($this->_track->album_id != null) {
|
||||||
|
$album = $this->_track->album;
|
||||||
|
$this->_track->album_id = null;
|
||||||
|
$this->_track->track_number = null;
|
||||||
|
$this->_track->delete();
|
||||||
|
$album->updateTrackNumbers();
|
||||||
|
} else {
|
||||||
|
$this->_track->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
79
app/Commands/EditAlbumCommand.php
Normal file
79
app/Commands/EditAlbumCommand.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Album;
|
||||||
|
use App\Image;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EditAlbumCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
private $_albumId;
|
||||||
|
private $_album;
|
||||||
|
|
||||||
|
function __construct($trackId, $input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
$this->_albumId = $trackId;
|
||||||
|
$this->_album = Album::find($trackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $this->_album && $user != null && $this->_album->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||||
|
'cover_id' => 'exists:images,id',
|
||||||
|
'track_ids' => 'exists:tracks,id'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_album->title = $this->_input['title'];
|
||||||
|
$this->_album->description = $this->_input['description'];
|
||||||
|
|
||||||
|
if (isset($this->_input['cover_id'])) {
|
||||||
|
$this->_album->cover_id = $this->_input['cover_id'];
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['cover'])) {
|
||||||
|
$cover = $this->_input['cover'];
|
||||||
|
$this->_album->cover_id = Image::upload($cover, Auth::user())->id;
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['remove_cover']) && $this->_input['remove_cover'] == 'true') {
|
||||||
|
$this->_album->cover_id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$trackIds = explode(',', $this->_input['track_ids']);
|
||||||
|
$this->_album->syncTrackIds($trackIds);
|
||||||
|
$this->_album->save();
|
||||||
|
|
||||||
|
Album::whereId($this->_album->id)->update([
|
||||||
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $this->_album->id . ')')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['real_cover_url' => $this->_album->getCoverUrl(Image::NORMAL)]);
|
||||||
|
}
|
||||||
|
}
|
77
app/Commands/EditPlaylistCommand.php
Normal file
77
app/Commands/EditPlaylistCommand.php
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\PinnedPlaylist;
|
||||||
|
use App\Playlist;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class EditPlaylistCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
private $_playlistId;
|
||||||
|
private $_playlist;
|
||||||
|
|
||||||
|
function __construct($playlistId, $input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
$this->_playlistId = $playlistId;
|
||||||
|
$this->_playlist = Playlist::find($playlistId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $this->_playlist && $user != null && $this->_playlist->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:50',
|
||||||
|
'is_public' => 'required',
|
||||||
|
'is_pinned' => 'required'
|
||||||
|
];
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_playlist->title = $this->_input['title'];
|
||||||
|
$this->_playlist->description = $this->_input['description'];
|
||||||
|
$this->_playlist->is_public = $this->_input['is_public'] == 'true';
|
||||||
|
|
||||||
|
$this->_playlist->save();
|
||||||
|
|
||||||
|
$pin = PinnedPlaylist::whereUserId(Auth::user()->id)->wherePlaylistId($this->_playlistId)->first();
|
||||||
|
if ($pin && $this->_input['is_pinned'] != 'true') {
|
||||||
|
$pin->delete();
|
||||||
|
} else {
|
||||||
|
if (!$pin && $this->_input['is_pinned'] == 'true') {
|
||||||
|
$this->_playlist->pin(Auth::user()->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $this->_playlist->id,
|
||||||
|
'title' => $this->_playlist->title,
|
||||||
|
'slug' => $this->_playlist->slug,
|
||||||
|
'created_at' => $this->_playlist->created_at,
|
||||||
|
'description' => $this->_playlist->description,
|
||||||
|
'url' => $this->_playlist->url,
|
||||||
|
'is_pinned' => $this->_input['is_pinned'] == 'true',
|
||||||
|
'is_public' => $this->_input['is_public'] == 'true'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
161
app/Commands/EditTrackCommand.php
Normal file
161
app/Commands/EditTrackCommand.php
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Album;
|
||||||
|
use App\Image;
|
||||||
|
use App\Track;
|
||||||
|
use App\User;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class EditTrackCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_trackId;
|
||||||
|
private $_track;
|
||||||
|
private $_input;
|
||||||
|
|
||||||
|
function __construct($trackId, $input)
|
||||||
|
{
|
||||||
|
$this->_trackId = $trackId;
|
||||||
|
$this->_track = Track::find($trackId);
|
||||||
|
$this->_input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
|
||||||
|
return $this->_track && $user != null && $this->_track->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$isVocal = (isset($this->_input['is_vocal']) && $this->_input['is_vocal'] == 'true') ? true : false;
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'title' => 'required|min:3|max:80',
|
||||||
|
'released_at' => 'before:' . (date('Y-m-d',
|
||||||
|
time() + (86400 * 2))) . (isset($this->_input['released_at']) && $this->_input['released_at'] != "" ? '|date' : ''),
|
||||||
|
'license_id' => 'required|exists:licenses,id',
|
||||||
|
'genre_id' => 'required|exists:genres,id',
|
||||||
|
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||||
|
'track_type_id' => 'required|exists:track_types,id',
|
||||||
|
'songs' => 'required_when:track_type,2|exists:songs,id',
|
||||||
|
'cover_id' => 'exists:images,id',
|
||||||
|
'album_id' => 'exists:albums,id'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($isVocal) {
|
||||||
|
$rules['lyrics'] = 'required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->_input['track_type_id']) && $this->_input['track_type_id'] == 2) {
|
||||||
|
$rules['show_song_ids'] = 'required|exists:show_songs,id';
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = \Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$track = $this->_track;
|
||||||
|
$track->title = $this->_input['title'];
|
||||||
|
$track->released_at = isset($this->_input['released_at']) && $this->_input['released_at'] != "" ? strtotime($this->_input['released_at']) : null;
|
||||||
|
$track->description = isset($this->_input['description']) ? $this->_input['description'] : '';
|
||||||
|
$track->lyrics = isset($this->_input['lyrics']) ? $this->_input['lyrics'] : '';
|
||||||
|
$track->license_id = $this->_input['license_id'];
|
||||||
|
$track->genre_id = $this->_input['genre_id'];
|
||||||
|
$track->track_type_id = $this->_input['track_type_id'];
|
||||||
|
$track->is_explicit = $this->_input['is_explicit'] == 'true';
|
||||||
|
$track->is_downloadable = $this->_input['is_downloadable'] == 'true';
|
||||||
|
$track->is_listed = $this->_input['is_listed'] == 'true';
|
||||||
|
$track->is_vocal = $isVocal;
|
||||||
|
|
||||||
|
if (isset($this->_input['album_id']) && strlen(trim($this->_input['album_id']))) {
|
||||||
|
if ($track->album_id != null && $track->album_id != $this->_input['album_id']) {
|
||||||
|
$this->removeTrackFromAlbum($track);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($track->album_id != $this->_input['album_id']) {
|
||||||
|
$album = Album::find($this->_input['album_id']);
|
||||||
|
$track->track_number = $album->tracks()->count() + 1;
|
||||||
|
$track->album_id = $this->_input['album_id'];
|
||||||
|
|
||||||
|
Album::whereId($album->id)->update([
|
||||||
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id . ')')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($track->album_id != null) {
|
||||||
|
$this->removeTrackFromAlbum($track);
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->track_number = null;
|
||||||
|
$track->album_id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($track->track_type_id == 2) {
|
||||||
|
$track->showSongs()->sync(explode(',', $this->_input['show_song_ids']));
|
||||||
|
} else {
|
||||||
|
$track->showSongs()->sync([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($track->published_at == null) {
|
||||||
|
$track->published_at = new \DateTime();
|
||||||
|
|
||||||
|
DB::table('tracks')->whereUserId($track->user_id)->update(['is_latest' => false]);
|
||||||
|
$track->is_latest = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->_input['cover_id'])) {
|
||||||
|
$track->cover_id = $this->_input['cover_id'];
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['cover'])) {
|
||||||
|
$cover = $this->_input['cover'];
|
||||||
|
$track->cover_id = Image::upload($cover, Auth::user())->id;
|
||||||
|
} else {
|
||||||
|
if ($this->_input['remove_cover'] == 'true') {
|
||||||
|
$track->cover_id = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
|
||||||
|
User::whereId($this->_track->user_id)->update([
|
||||||
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE deleted_at IS NULL AND published_at IS NOT NULL AND user_id = ' . $this->_track->user_id . ')')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['real_cover_url' => $track->getCoverUrl(Image::NORMAL)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function removeTrackFromAlbum($track)
|
||||||
|
{
|
||||||
|
$album = $track->album;
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
foreach ($album->tracks as $track) {
|
||||||
|
if ($track->id == $this->_trackId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->track_number = ++$index;
|
||||||
|
$track->updateTags();
|
||||||
|
$track->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Album::whereId($album->id)->update([
|
||||||
|
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id . ')')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
88
app/Commands/SaveAccountSettingsCommand.php
Normal file
88
app/Commands/SaveAccountSettingsCommand.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Image;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class SaveAccountSettingsCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_input;
|
||||||
|
|
||||||
|
function __construct($input)
|
||||||
|
{
|
||||||
|
$this->_input = $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return Auth::user() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'display_name' => 'required|min:3|max:26',
|
||||||
|
'bio' => 'textarea_length:250'
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->_input['sync_names'] == 'true') {
|
||||||
|
$this->_input['display_name'] = $user->mlpforums_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->_input['uses_gravatar'] == 'true') {
|
||||||
|
$rules['gravatar'] = 'email';
|
||||||
|
} else {
|
||||||
|
$rules['avatar'] = 'image|mimes:png|min_width:350|min_height:350';
|
||||||
|
$rules['avatar_id'] = 'exists:images,id';
|
||||||
|
}
|
||||||
|
|
||||||
|
$validator = Validator::make($this->_input, $rules);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->_input['uses_gravatar'] != 'true') {
|
||||||
|
if ($user->avatar_id == null && !isset($this->_input['avatar']) && !isset($this->_input['avatar_id'])) {
|
||||||
|
$validator->messages()->add('avatar',
|
||||||
|
'You must upload or select an avatar if you are not using gravatar!');
|
||||||
|
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->bio = $this->_input['bio'];
|
||||||
|
$user->display_name = $this->_input['display_name'];
|
||||||
|
$user->sync_names = $this->_input['sync_names'] == 'true';
|
||||||
|
$user->can_see_explicit_content = $this->_input['can_see_explicit_content'] == 'true';
|
||||||
|
$user->uses_gravatar = $this->_input['uses_gravatar'] == 'true';
|
||||||
|
|
||||||
|
if ($user->uses_gravatar) {
|
||||||
|
$user->avatar_id = null;
|
||||||
|
$user->gravatar = $this->_input['gravatar'];
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['avatar_id'])) {
|
||||||
|
$user->avatar_id = $this->_input['avatar_id'];
|
||||||
|
} else {
|
||||||
|
if (isset($this->_input['avatar'])) {
|
||||||
|
$user->avatar_id = Image::upload($this->_input['avatar'], $user)->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return CommandResponse::succeed();
|
||||||
|
}
|
||||||
|
}
|
75
app/Commands/ToggleFavouriteCommand.php
Normal file
75
app/Commands/ToggleFavouriteCommand.php
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Favourite;
|
||||||
|
use App\ResourceUser;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class ToggleFavouriteCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_resourceType;
|
||||||
|
private $_resourceId;
|
||||||
|
|
||||||
|
function __construct($resourceType, $resourceId)
|
||||||
|
{
|
||||||
|
$this->_resourceId = $resourceId;
|
||||||
|
$this->_resourceType = $resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$typeId = $this->_resourceType . '_id';
|
||||||
|
$existing = Favourite::where($typeId, '=', $this->_resourceId)->whereUserId(Auth::user()->id)->first();
|
||||||
|
$isFavourited = false;
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$existing->delete();
|
||||||
|
} else {
|
||||||
|
$fav = new Favourite();
|
||||||
|
$fav->$typeId = $this->_resourceId;
|
||||||
|
$fav->user_id = Auth::user()->id;
|
||||||
|
$fav->created_at = time();
|
||||||
|
$fav->save();
|
||||||
|
$isFavourited = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resourceUser = ResourceUser::get(Auth::user()->id, $this->_resourceType, $this->_resourceId);
|
||||||
|
$resourceUser->is_favourited = $isFavourited;
|
||||||
|
$resourceUser->save();
|
||||||
|
|
||||||
|
$resourceTable = $this->_resourceType . 's';
|
||||||
|
|
||||||
|
// We do this to prevent a race condition. Sure I could simply increment the count columns and re-save back to the db
|
||||||
|
// but that would require an additional SELECT and the operation would be non-atomic. If two log items are created
|
||||||
|
// for the same resource at the same time, the cached values will still be correct with this method.
|
||||||
|
|
||||||
|
DB::table($resourceTable)->whereId($this->_resourceId)->update([
|
||||||
|
'favourite_count' =>
|
||||||
|
DB::raw('(
|
||||||
|
SELECT
|
||||||
|
COUNT(id)
|
||||||
|
FROM
|
||||||
|
favourites
|
||||||
|
WHERE ' .
|
||||||
|
$typeId . ' = ' . $this->_resourceId . ')')
|
||||||
|
]);
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['is_favourited' => $isFavourited]);
|
||||||
|
}
|
||||||
|
}
|
57
app/Commands/ToggleFollowingCommand.php
Normal file
57
app/Commands/ToggleFollowingCommand.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Follower;
|
||||||
|
use App\ResourceUser;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class ToggleFollowingCommand extends CommandBase
|
||||||
|
{
|
||||||
|
private $_resourceType;
|
||||||
|
private $_resourceId;
|
||||||
|
|
||||||
|
function __construct($resourceType, $resourceId)
|
||||||
|
{
|
||||||
|
$this->_resourceId = $resourceId;
|
||||||
|
$this->_resourceType = $resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
return $user != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$typeId = $this->_resourceType . '_id';
|
||||||
|
$existing = Follower::where($typeId, '=', $this->_resourceId)->whereUserId(Auth::user()->id)->first();
|
||||||
|
$isFollowed = false;
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$existing->delete();
|
||||||
|
} else {
|
||||||
|
$follow = new Follower();
|
||||||
|
$follow->$typeId = $this->_resourceId;
|
||||||
|
$follow->user_id = Auth::user()->id;
|
||||||
|
$follow->created_at = time();
|
||||||
|
$follow->save();
|
||||||
|
$isFollowed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resourceUser = ResourceUser::get(Auth::user()->id, $this->_resourceType, $this->_resourceId);
|
||||||
|
$resourceUser->is_followed = $isFollowed;
|
||||||
|
$resourceUser->save();
|
||||||
|
|
||||||
|
return CommandResponse::succeed(['is_followed' => $isFollowed]);
|
||||||
|
}
|
||||||
|
}
|
96
app/Commands/UploadTrackCommand.php
Normal file
96
app/Commands/UploadTrackCommand.php
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Commands;
|
||||||
|
|
||||||
|
use App\Track;
|
||||||
|
use App\TrackFile;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
|
class UploadTrackCommand extends CommandBase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return \Auth::user() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
* @return CommandResponse
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$user = \Auth::user();
|
||||||
|
$trackFile = \Input::file('track');
|
||||||
|
$audio = \AudioCache::get($trackFile->getPathname());
|
||||||
|
|
||||||
|
$validator = \Validator::make(['track' => $trackFile], [
|
||||||
|
'track' =>
|
||||||
|
'required|'
|
||||||
|
. 'audio_format:flac,pcm_s16le ([1][0][0][0] / 0x0001),pcm_s16be,adpcm_ms ([2][0][0][0] / 0x0002),pcm_s24le ([1][0][0][0] / 0x0001),pcm_s24be,pcm_f32le ([3][0][0][0] / 0x0003),pcm_f32be (fl32 / 0x32336C66)|'
|
||||||
|
. 'audio_channels:1,2|'
|
||||||
|
. 'sample_rate:44100,48000,88200,96000,176400,192000|'
|
||||||
|
. 'min_duration:30'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($validator->fails()) {
|
||||||
|
return CommandResponse::fail($validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
$track = new Track();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$track->user_id = $user->id;
|
||||||
|
$track->title = pathinfo($trackFile->getClientOriginalName(), PATHINFO_FILENAME);
|
||||||
|
$track->duration = $audio->getDuration();
|
||||||
|
$track->is_listed = true;
|
||||||
|
|
||||||
|
$track->save();
|
||||||
|
|
||||||
|
$destination = $track->getDirectory();
|
||||||
|
$track->ensureDirectoryExists();
|
||||||
|
|
||||||
|
$source = $trackFile->getPathname();
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
$processes = [];
|
||||||
|
|
||||||
|
foreach (Track::$Formats as $name => $format) {
|
||||||
|
$trackFile = new TrackFile();
|
||||||
|
$trackFile->is_master = $name === 'FLAC' ? true : false;
|
||||||
|
$trackFile->format = $name;
|
||||||
|
$track->trackFiles()->save($trackFile);
|
||||||
|
|
||||||
|
$target = $destination . '/' . $trackFile->getFilename(); //$track->getFilenameFor($name);
|
||||||
|
|
||||||
|
$command = $format['command'];
|
||||||
|
$command = str_replace('{$source}', '"' . $source . '"', $command);
|
||||||
|
$command = str_replace('{$target}', '"' . $target . '"', $command);
|
||||||
|
|
||||||
|
Log::info('Encoding ' . $track->id . ' into ' . $target);
|
||||||
|
$this->notify('Encoding ' . $name, $index / count(Track::$Formats) * 100);
|
||||||
|
|
||||||
|
$pipes = [];
|
||||||
|
$proc = proc_open($command, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'a']], $pipes);
|
||||||
|
$processes[] = $proc;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($processes as $proc) {
|
||||||
|
proc_close($proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
$track->updateTags();
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$track->delete();
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommandResponse::succeed([
|
||||||
|
'id' => $track->id,
|
||||||
|
'name' => $track->name
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue