mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-21 20:48:00 +01:00
#2: Implemented the user creation tool.
This commit is contained in:
parent
84d51fee2f
commit
09effb6955
20 changed files with 359 additions and 17 deletions
|
@ -30,11 +30,16 @@ class CommandResponse
|
|||
private $_validator;
|
||||
private $_response;
|
||||
private $_didFail;
|
||||
/**
|
||||
* @var int Used for HTTP responses.
|
||||
*/
|
||||
private $_statusCode;
|
||||
|
||||
public static function fail($validatorOrMessages)
|
||||
public static function fail($validatorOrMessages, int $statusCode = 400)
|
||||
{
|
||||
$response = new CommandResponse();
|
||||
$response->_didFail = true;
|
||||
$response->_statusCode = $statusCode;
|
||||
|
||||
if (is_array($validatorOrMessages)) {
|
||||
$response->_messages = $validatorOrMessages;
|
||||
|
@ -47,11 +52,12 @@ class CommandResponse
|
|||
return $response;
|
||||
}
|
||||
|
||||
public static function succeed($response = null)
|
||||
public static function succeed($response = null, int $statusCode = 200)
|
||||
{
|
||||
$cmdResponse = new CommandResponse();
|
||||
$cmdResponse->_didFail = false;
|
||||
$cmdResponse->_response = $response;
|
||||
$cmdResponse->_statusCode = $statusCode;
|
||||
|
||||
return $cmdResponse;
|
||||
}
|
||||
|
@ -76,6 +82,14 @@ class CommandResponse
|
|||
return $this->_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStatusCode():int
|
||||
{
|
||||
return $this->_statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Validator
|
||||
*/
|
||||
|
|
93
app/Commands/CreateUserCommand.php
Normal file
93
app/Commands/CreateUserCommand.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 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 Poniverse\Ponyfm\Models\User;
|
||||
use Validator;
|
||||
|
||||
class CreateUserCommand extends CommandBase
|
||||
{
|
||||
private $username;
|
||||
private $displayName;
|
||||
private $email;
|
||||
private $createArchivedUser;
|
||||
|
||||
public function __construct(
|
||||
string $username,
|
||||
string $displayName,
|
||||
string $email = null,
|
||||
bool $createArchivedUser = false
|
||||
) {
|
||||
$this->username = $username;
|
||||
$this->displayName = $displayName;
|
||||
$this->email = $email;
|
||||
$this->createArchivedUser = $createArchivedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return Gate::allows('create-user');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @return CommandResponse
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$rules = [
|
||||
'username' => config('ponyfm.validation_rules.username'),
|
||||
'display_name' => config('ponyfm.validation_rules.display_name'),
|
||||
'email' => 'email',
|
||||
'create_archived_user' => 'boolean',
|
||||
];
|
||||
|
||||
$validator = Validator::make([
|
||||
'username' => $this->username,
|
||||
'display_name' => $this->displayName,
|
||||
], $rules);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return CommandResponse::fail([
|
||||
'message' => $validator->getMessageBag()->first(),
|
||||
'user' => null
|
||||
]);
|
||||
}
|
||||
|
||||
// Attempt to create the user.
|
||||
$user = User::findOrCreate($this->username, $this->displayName, $this->email, $this->createArchivedUser);
|
||||
if ($user->wasRecentlyCreated) {
|
||||
return CommandResponse::succeed([
|
||||
'message' => 'New user successfully created!',
|
||||
'user' => User::mapPublicUserSummary($user)
|
||||
], 201);
|
||||
} else {
|
||||
return CommandResponse::fail([
|
||||
'message' => 'A user with that username already exists.',
|
||||
'user' => User::mapPublicUserSummary($user)
|
||||
], 409);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -89,7 +89,7 @@ class SaveAccountSettingsCommand extends CommandBase
|
|||
'slug' => [
|
||||
'required',
|
||||
'unique:users,slug,'.$this->_user->id,
|
||||
'min:3',
|
||||
'min:'.config('ponyfm.user_slug_minimum_length'),
|
||||
'regex:/^[a-z\d-]+$/',
|
||||
'is_not_reserved_slug'
|
||||
]
|
||||
|
|
|
@ -44,4 +44,9 @@ class AdminController extends Controller
|
|||
{
|
||||
return View::make('shared.null');
|
||||
}
|
||||
|
||||
public function getUsers()
|
||||
{
|
||||
return View::make('shared.null');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
namespace Poniverse\Ponyfm\Http\Controllers\Api\Web;
|
||||
|
||||
use Gate;
|
||||
use Poniverse\Ponyfm\Commands\CreateUserCommand;
|
||||
use Poniverse\Ponyfm\Models\Album;
|
||||
use Poniverse\Ponyfm\Models\Comment;
|
||||
use Poniverse\Ponyfm\Models\Favourite;
|
||||
|
@ -29,9 +30,9 @@ use Poniverse\Ponyfm\Models\Image;
|
|||
use Poniverse\Ponyfm\Models\Track;
|
||||
use Poniverse\Ponyfm\Models\User;
|
||||
use Poniverse\Ponyfm\Models\Follower;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use App;
|
||||
use Input;
|
||||
use Response;
|
||||
use ColorThief\ColorThief;
|
||||
use Helpers;
|
||||
|
||||
|
@ -228,4 +229,9 @@ class ArtistsController extends ApiControllerBase
|
|||
return Response::json(["artists" => $users, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
|
||||
200);
|
||||
}
|
||||
|
||||
public function postIndex() {
|
||||
$name = Input::json('username');
|
||||
return $this->execute(new CreateUserCommand($name, $name, null, true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ abstract class ApiControllerBase extends Controller
|
|||
return Response::json([
|
||||
'message' => 'Validation failed',
|
||||
'errors' => $result->getMessages()
|
||||
], 400);
|
||||
], $result->getStatusCode());
|
||||
}
|
||||
|
||||
return Response::json($result->getResponse(), 200);
|
||||
return Response::json($result->getResponse(), $result->getStatusCode());
|
||||
}
|
||||
|
||||
public function notAuthorized()
|
||||
|
|
|
@ -101,6 +101,7 @@ Route::group(['prefix' => 'api/web'], function() {
|
|||
Route::get('/comments/{type}/{id}', 'Api\Web\CommentsController@getIndex')->where('id', '\d+');
|
||||
|
||||
Route::get('/artists', 'Api\Web\ArtistsController@getIndex');
|
||||
Route::post('/artists', 'Api\Web\ArtistsController@postIndex');
|
||||
Route::get('/artists/{slug}', 'Api\Web\ArtistsController@getShow');
|
||||
Route::get('/artists/{slug}/content', 'Api\Web\ArtistsController@getContent');
|
||||
Route::get('/artists/{slug}/favourites', 'Api\Web\ArtistsController@getFavourites');
|
||||
|
@ -175,6 +176,7 @@ Route::group(['prefix' => 'admin', 'middleware' => ['auth', 'can:access-admin-ar
|
|||
Route::get('/genres', 'AdminController@getGenres');
|
||||
Route::get('/tracks', 'AdminController@getTracks');
|
||||
Route::get('/show-songs', 'AdminController@getShowSongs');
|
||||
Route::get('/users', 'AdminController@getUsers');
|
||||
Route::get('/', 'AdminController@getIndex');
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
namespace Poniverse\Ponyfm\Models;
|
||||
|
||||
use DB;
|
||||
use Gravatar;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Auth\Passwords\CanResetPassword;
|
||||
|
@ -114,6 +115,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
*/
|
||||
private static function getUniqueSlugForName(string $name):string {
|
||||
$baseSlug = Str::slug($name);
|
||||
|
||||
// Ensure that the slug we generate is long enough.
|
||||
for ($i = Str::length($baseSlug); $i < config('ponyfm.user_slug_minimum_length'); $i++) {
|
||||
$baseSlug = $baseSlug.'-';
|
||||
}
|
||||
|
||||
$slugBeingTried = $baseSlug;
|
||||
$counter = 2;
|
||||
|
||||
|
@ -135,15 +142,23 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $username used to perform the search
|
||||
* @param string $displayName
|
||||
* @param string $email
|
||||
* @param string|null $email set to null if creating an archived user
|
||||
* @param bool $createArchivedUser if true, includes archived users in the search and creates an archived user
|
||||
* @return User
|
||||
*/
|
||||
public static function findOrCreate(string $username, string $displayName, string $email) {
|
||||
$user = static::where('username', $username)
|
||||
->where('is_archived', false)
|
||||
->first();
|
||||
public static function findOrCreate(
|
||||
string $username,
|
||||
string $displayName,
|
||||
string $email = null,
|
||||
bool $createArchivedUser = false
|
||||
) {
|
||||
$user = static::where(DB::raw('LOWER(username)'), Str::lower($username));
|
||||
if (false === $createArchivedUser) {
|
||||
$user = $user->where('is_archived', false);
|
||||
}
|
||||
$user = $user->first();
|
||||
|
||||
if (null !== $user) {
|
||||
return $user;
|
||||
|
@ -156,7 +171,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon
|
|||
$user->slug = self::getUniqueSlugForName($displayName);
|
||||
$user->email = $email;
|
||||
$user->uses_gravatar = true;
|
||||
$user->is_archived = $createArchivedUser;
|
||||
$user->save();
|
||||
$user = $user->fresh();
|
||||
$user->wasRecentlyCreated = true;
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
|
|
@ -68,6 +68,10 @@ class AuthServiceProvider extends ServiceProvider
|
|||
return $user->hasRole('admin');
|
||||
});
|
||||
|
||||
$gate->define('create-user', function(User $user) {
|
||||
return $user->hasRole('admin');
|
||||
});
|
||||
|
||||
$this->registerPolicies($gate);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,4 +89,31 @@ return [
|
|||
|
||||
'indexing_queue' => 'indexing',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global validation rules
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Data fields that are validated in multiple places have their validation
|
||||
| rules centralized here.
|
||||
|
|
||||
*/
|
||||
|
||||
'validation_rules' => [
|
||||
'username' => ['required', 'min:3', 'max:26'],
|
||||
'display_name' => ['required', 'min:3', 'max:26'],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Minimum length of a user slug
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| No profile slugs shorter than this will be generated. This setting is
|
||||
| intended to pre-emptively avoid collisions with very short URL's that may
|
||||
| be desirable for future site functionality.
|
||||
|
|
||||
*/
|
||||
|
||||
'user_slug_minimum_length' => 3
|
||||
];
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"packages": {},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"angular": "^1.5.0",
|
||||
"angular": "^1.5.6",
|
||||
"angular-chart.js": "^1.0.0-alpha6",
|
||||
"angular-strap": "^2.3.8",
|
||||
"angular-ui-router": "^0.2.18",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<li ng-class="{active: stateIncludes('admin.tracks')}"><a href="/admin/tracks">All Tracks</a></li>
|
||||
<li ng-class="{active: stateIncludes('admin.genres')}"><a href="/admin/genres">Genres</a></li>
|
||||
<li ng-class="{active: stateIncludes('admin.showsongs')}"><a href="/admin/show-songs">Show Songs</a></li>
|
||||
<li ng-class="{active: stateIncludes('admin.users')}"><a ui-sref="admin.users">Users</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
||||
|
|
21
public/templates/admin/users.html
Normal file
21
public/templates/admin/users.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<h1>User Management</h1>
|
||||
|
||||
<div class="stretch-to-bottom">
|
||||
|
||||
<section class="user-creator">
|
||||
<h2>Create an archived artist profile</h2>
|
||||
<p>Archived profiles are for organizing music by artists who aren't on Pony.fm themselves. They can always be claimed by the artist and converted to a "proper" profile later.</p>
|
||||
|
||||
<pfm-user-creator></pfm-user-creator>
|
||||
</section>
|
||||
|
||||
<section class="user-search">
|
||||
<h2>Search for a user by name</h2>
|
||||
<p>Use the universal search in the sidebar. /)</p>
|
||||
</section>
|
||||
|
||||
<section class="user-merger">
|
||||
<h2>Merge profiles</h2>
|
||||
<p>Ask a Pony.fm developer or sysadmin to run the following for you: <pre>./artisan accounts:merge</pre></p>
|
||||
</section>
|
||||
</div>
|
32
public/templates/directives/user-creator.html
Normal file
32
public/templates/directives/user-creator.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<form
|
||||
class="user-creator form-inline"
|
||||
ng-submit="$ctrl.createUser()"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="-usernameField form-control x-large"
|
||||
ng-class="{'x-saving': $ctrl.isCreating, 'x-error': !$ctrl.creationSucceeded && $ctrl.creationMessage}"
|
||||
ng-model="$ctrl.usernameToCreate"
|
||||
placeholder="Username"
|
||||
/>
|
||||
|
||||
<input class="-submitButton form-control" type="submit" value="Create!"/>
|
||||
|
||||
<div
|
||||
class="-createdUser"
|
||||
ng-if="$ctrl.creationMessage"
|
||||
ng-class="{'alert-success': $ctrl.creationSucceeded, 'alert-warning': !$ctrl.creationSucceeded}"
|
||||
>
|
||||
<strong>{{ $ctrl.creationMessage }}</strong>
|
||||
<ul ng-if="$ctrl.user">
|
||||
<li ng-if="!$ctrl.user.is_archived"><span class="label label-warning">Caution</span> This is <strong>NOT</strong> an archived profile.</li>
|
||||
<li ng-if="$ctrl.user.is_archived">This is an archived profile.</li>
|
||||
|
||||
<li><a class="-profile-link"
|
||||
ui-sref="content.artist.account.uploader({slug: $ctrl.user.slug})">
|
||||
Go to <strong>{{$ctrl.user.name}}'s</strong> track uploader!</a>
|
||||
</li>
|
||||
<li><a ng-href="{{$ctrl.user.url}}">Visit <strong>{{$ctrl.user.name}}'s</strong> profile!</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</form>
|
|
@ -283,6 +283,11 @@ ponyfm.config [
|
|||
controller: 'admin-tracks'
|
||||
templateUrl: '/templates/admin/tracks.html'
|
||||
|
||||
state.state 'admin.users',
|
||||
url: '/users'
|
||||
controller: 'admin-users'
|
||||
templateUrl: '/templates/admin/users.html'
|
||||
|
||||
# Homepage
|
||||
|
||||
if window.pfm.auth.isLogged
|
||||
|
|
22
resources/assets/scripts/app/controllers/admin-users.coffee
Normal file
22
resources/assets/scripts/app/controllers/admin-users.coffee
Normal file
|
@ -0,0 +1,22 @@
|
|||
# Pony.fm - A community for pony fan music.
|
||||
# Copyright (C) 2016 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/>.
|
||||
|
||||
module.exports = angular.module('ponyfm').controller 'admin-users', [
|
||||
'meta'
|
||||
(meta) ->
|
||||
meta.setTitle('🔒 User management')
|
||||
|
||||
]
|
51
resources/assets/scripts/app/directives/user-creator.coffee
Normal file
51
resources/assets/scripts/app/directives/user-creator.coffee
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Pony.fm - A community for pony fan music.
|
||||
# Copyright (C) 2016 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/>.
|
||||
|
||||
pfmUserCreatorController = [
|
||||
'artists',
|
||||
(artists)->
|
||||
ctrl = this
|
||||
|
||||
ctrl.usernameToCreate = ''
|
||||
ctrl.isCreating = false
|
||||
ctrl.creationMessage = null
|
||||
ctrl.creationSucceeded = false
|
||||
ctrl.user = null
|
||||
|
||||
ctrl.createUser = ->
|
||||
ctrl.isCreating = true
|
||||
ctrl.creationMessage = null
|
||||
ctrl.creationSucceeded = false
|
||||
|
||||
artists.create(ctrl.usernameToCreate)
|
||||
.then (response)->
|
||||
ctrl.creationSucceeded = true
|
||||
ctrl.user = response.data.user
|
||||
ctrl.creationMessage = response.data.message
|
||||
.catch (response)->
|
||||
ctrl.creationSucceeded = false
|
||||
ctrl.user = response.data.errors.user
|
||||
ctrl.creationMessage = response.data.errors.message
|
||||
.finally ->
|
||||
ctrl.isCreating = false
|
||||
|
||||
return ctrl
|
||||
]
|
||||
|
||||
module.exports = angular.module('ponyfm').component('pfmUserCreator', {
|
||||
templateUrl: '/templates/directives/user-creator.html',
|
||||
controller: pfmUserCreatorController,
|
||||
})
|
|
@ -15,8 +15,8 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
module.exports = angular.module('ponyfm').factory('artists', [
|
||||
'$rootScope', '$http'
|
||||
($rootScope, $http) ->
|
||||
'$rootScope', '$http', '$q'
|
||||
($rootScope, $http, $q) ->
|
||||
artistPage = []
|
||||
artists = {}
|
||||
artistContent = {}
|
||||
|
@ -72,5 +72,8 @@ module.exports = angular.module('ponyfm').factory('artists', [
|
|||
|
||||
artistFavourites[slug] = artistsDef.promise()
|
||||
|
||||
create: (username) ->
|
||||
$http.post('/api/web/artists', {username: username})
|
||||
|
||||
self
|
||||
])
|
||||
|
|
1
resources/assets/styles/app.less
vendored
1
resources/assets/styles/app.less
vendored
|
@ -35,4 +35,5 @@
|
|||
@import 'components/uploader';
|
||||
@import 'components/search';
|
||||
@import 'components/track-editor';
|
||||
@import 'components/user-creator';
|
||||
@import 'mobile';
|
||||
|
|
37
resources/assets/styles/components/user-creator.less
vendored
Normal file
37
resources/assets/styles/components/user-creator.less
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* Pony.fm - A community for pony fan music.
|
||||
* Copyright (C) 2016 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/>.
|
||||
*/
|
||||
|
||||
@import '../base/bootstrap/bootstrap';
|
||||
@import '../mixins';
|
||||
|
||||
.user-creator {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.-usernameField {
|
||||
}
|
||||
|
||||
.-submitButton {
|
||||
.btn;
|
||||
.btn-primary;
|
||||
}
|
||||
|
||||
.-createdUser {
|
||||
.alert;
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue