rewrite players management page with React
This commit is contained in:
parent
c6ef42d477
commit
af351d211b
|
|
@ -6,7 +6,6 @@ use App\Models\Player;
|
||||||
use App\Models\Texture;
|
use App\Models\Texture;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications;
|
use App\Notifications;
|
||||||
use App\Rules;
|
|
||||||
use App\Services\OptionForm;
|
use App\Services\OptionForm;
|
||||||
use App\Services\PluginManager;
|
use App\Services\PluginManager;
|
||||||
use Auth;
|
use Auth;
|
||||||
|
|
@ -599,80 +598,4 @@ class AdminController extends Controller
|
||||||
return json(trans('admin.users.operations.invalid'), 1);
|
return json(trans('admin.users.operations.invalid'), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function playerAjaxHandler(Request $request)
|
|
||||||
{
|
|
||||||
$action = $request->input('action');
|
|
||||||
$currentUser = Auth::user();
|
|
||||||
$player = Player::find($request->input('pid'));
|
|
||||||
|
|
||||||
if (!$player) {
|
|
||||||
return json(trans('general.unexistent-player'), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$owner = $player->user;
|
|
||||||
if (
|
|
||||||
$owner && $owner->uid !== $currentUser->uid &&
|
|
||||||
$owner->permission >= $currentUser->permission
|
|
||||||
) {
|
|
||||||
return json(trans('admin.players.no-permission'), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($action == 'texture') {
|
|
||||||
$this->validate($request, [
|
|
||||||
'type' => 'required',
|
|
||||||
'tid' => 'required|integer',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!Texture::find($request->tid) && $request->tid != 0) {
|
|
||||||
return json(trans('admin.players.textures.non-existent', ['tid' => $request->tid]), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$field = 'tid_'.$request->type;
|
|
||||||
$player->$field = $request->tid;
|
|
||||||
$player->save();
|
|
||||||
|
|
||||||
return json(trans('admin.players.textures.success', ['player' => $player->name]), 0);
|
|
||||||
} elseif ($action == 'owner') {
|
|
||||||
$this->validate($request, [
|
|
||||||
'uid' => 'required|integer',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$user = User::find($request->uid);
|
|
||||||
|
|
||||||
if (!$user) {
|
|
||||||
return json(trans('admin.users.operations.non-existent'), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
$player->uid = $request->input('uid');
|
|
||||||
$player->save();
|
|
||||||
|
|
||||||
return json(trans('admin.players.owner.success', ['player' => $player->name, 'user' => $user->nickname]), 0);
|
|
||||||
} elseif ($action == 'delete') {
|
|
||||||
$player->delete();
|
|
||||||
|
|
||||||
return json(trans('admin.players.delete.success'), 0);
|
|
||||||
} elseif ($action == 'name') {
|
|
||||||
$name = $this->validate($request, [
|
|
||||||
'name' => [
|
|
||||||
'required',
|
|
||||||
new Rules\PlayerName(),
|
|
||||||
'min:'.option('player_name_length_min'),
|
|
||||||
'max:'.option('player_name_length_max'),
|
|
||||||
],
|
|
||||||
])['name'];
|
|
||||||
|
|
||||||
$player->name = $name;
|
|
||||||
$player->save();
|
|
||||||
|
|
||||||
if (option('single_player', false) && $owner) {
|
|
||||||
$owner->nickname = $name;
|
|
||||||
$owner->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
return json(trans('admin.players.name.success', ['player' => $player->name]), 0);
|
|
||||||
} else {
|
|
||||||
return json(trans('admin.users.operations.invalid'), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
136
app/Http/Controllers/PlayersManagementController.php
Normal file
136
app/Http/Controllers/PlayersManagementController.php
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Player;
|
||||||
|
use App\Models\Texture;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Rules;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
|
class PlayersManagementController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware(function (Request $request, $next) {
|
||||||
|
/** @var Player */
|
||||||
|
$player = $request->route('player');
|
||||||
|
$owner = $player->user;
|
||||||
|
|
||||||
|
/** @var User */
|
||||||
|
$currentUser = $request->user();
|
||||||
|
|
||||||
|
if (
|
||||||
|
$owner && $owner->uid !== $currentUser->uid &&
|
||||||
|
$owner->permission >= $currentUser->permission
|
||||||
|
) {
|
||||||
|
return json(trans('admin.players.no-permission'), 1)
|
||||||
|
->setStatusCode(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
})->except(['list']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function list(Request $request)
|
||||||
|
{
|
||||||
|
$query = $request->query('q');
|
||||||
|
|
||||||
|
return Player::usingSearchString($query)->paginate(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(
|
||||||
|
Player $player,
|
||||||
|
Request $request,
|
||||||
|
Dispatcher $dispatcher
|
||||||
|
) {
|
||||||
|
$name = $this->validate($request, [
|
||||||
|
'player_name' => [
|
||||||
|
'required',
|
||||||
|
new Rules\PlayerName(),
|
||||||
|
'min:'.option('player_name_length_min'),
|
||||||
|
'max:'.option('player_name_length_max'),
|
||||||
|
'unique:players,name',
|
||||||
|
],
|
||||||
|
])['player_name'];
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.name.updating', [$player, $name]);
|
||||||
|
|
||||||
|
$oldName = $player->name;
|
||||||
|
$player->name = $name;
|
||||||
|
$player->save();
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.name.updated', [$player, $oldName]);
|
||||||
|
|
||||||
|
return json(trans('admin.players.name.success', ['player' => $player->name]), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function owner(
|
||||||
|
Player $player,
|
||||||
|
Request $request,
|
||||||
|
Dispatcher $dispatcher
|
||||||
|
) {
|
||||||
|
$uid = $this->validate($request, ['uid' => 'required|integer'])['uid'];
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.owner.updating', [$player, $uid]);
|
||||||
|
|
||||||
|
/** @var User */
|
||||||
|
$user = User::find($request->uid);
|
||||||
|
if (empty($user)) {
|
||||||
|
return json(trans('admin.users.operations.non-existent'), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$player->uid = $uid;
|
||||||
|
$player->save();
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.owner.updated', [$player, $user]);
|
||||||
|
|
||||||
|
return json(trans('admin.players.owner.success', [
|
||||||
|
'player' => $player->name,
|
||||||
|
'user' => $user->nickname,
|
||||||
|
]), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function texture(
|
||||||
|
Player $player,
|
||||||
|
Request $request,
|
||||||
|
Dispatcher $dispatcher
|
||||||
|
) {
|
||||||
|
$data = $this->validate($request, [
|
||||||
|
'tid' => 'required|integer',
|
||||||
|
'type' => ['required', Rule::in(['skin', 'cape'])],
|
||||||
|
]);
|
||||||
|
$tid = (int) $data['tid'];
|
||||||
|
$type = $data['type'];
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.texture.updating', [$player, $type, $tid]);
|
||||||
|
|
||||||
|
if (!Texture::find($tid) && $tid !== 0) {
|
||||||
|
return json(trans('admin.players.textures.non-existent', ['tid' => $tid]), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$field = 'tid_'.$type;
|
||||||
|
$previousTid = $player->$field;
|
||||||
|
$player->$field = $tid;
|
||||||
|
$player->save();
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.texture.updated', [$player, $type, $previousTid]);
|
||||||
|
|
||||||
|
return json(trans('admin.players.textures.success', ['player' => $player->name]), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(
|
||||||
|
Player $player,
|
||||||
|
Dispatcher $dispatcher
|
||||||
|
) {
|
||||||
|
$dispatcher->dispatch('player.deleting', [$player]);
|
||||||
|
|
||||||
|
$player->delete();
|
||||||
|
|
||||||
|
$dispatcher->dispatch('player.deleted', [$player]);
|
||||||
|
|
||||||
|
return json(trans('admin.players.delete.success'), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -32,6 +32,7 @@ class Kernel extends HttpKernel
|
||||||
\Illuminate\Session\Middleware\StartSession::class,
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
|
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
|
||||||
|
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||||
\App\Http\Middleware\EnforceEverGreen::class,
|
\App\Http\Middleware\EnforceEverGreen::class,
|
||||||
\App\Http\Middleware\RedirectToSetup::class,
|
\App\Http\Middleware\RedirectToSetup::class,
|
||||||
],
|
],
|
||||||
|
|
|
||||||
21
app/Listeners/SinglePlayer/UpdateOwnerNickName.php
Normal file
21
app/Listeners/SinglePlayer/UpdateOwnerNickName.php
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Listeners\SinglePlayer;
|
||||||
|
|
||||||
|
use App\Models\Player;
|
||||||
|
|
||||||
|
class UpdateOwnerNickName
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Player $player
|
||||||
|
*/
|
||||||
|
public function handle($player)
|
||||||
|
{
|
||||||
|
$owner = $player->user;
|
||||||
|
|
||||||
|
if (option('single_player', false) && $owner) {
|
||||||
|
$owner->nickname = $player->name;
|
||||||
|
$owner->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ use App\Models;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Lorisleiva\LaravelSearchString\Concerns\SearchString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $pid
|
* @property int $pid
|
||||||
|
|
@ -22,6 +23,8 @@ use Illuminate\Support\Carbon;
|
||||||
*/
|
*/
|
||||||
class Player extends Model
|
class Player extends Model
|
||||||
{
|
{
|
||||||
|
use SearchString;
|
||||||
|
|
||||||
public const CREATED_AT = null;
|
public const CREATED_AT = null;
|
||||||
public const UPDATED_AT = 'last_modified';
|
public const UPDATED_AT = 'last_modified';
|
||||||
|
|
||||||
|
|
@ -40,6 +43,14 @@ class Player extends Model
|
||||||
'updated' => PlayerProfileUpdated::class,
|
'updated' => PlayerProfileUpdated::class,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $searchStringColumns = [
|
||||||
|
'pid', 'uid',
|
||||||
|
'tid_skin' => '/^(?:tid_)?skin$/',
|
||||||
|
'tid_cape' => '/^(?:tid_)?cape$/',
|
||||||
|
'name' => ['searchable' => true],
|
||||||
|
'last_modified' => ['date' => true],
|
||||||
|
];
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Models\User::class, 'uid');
|
return $this->belongsTo(Models\User::class, 'uid');
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Listeners;
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
|
|
||||||
class EventServiceProvider extends ServiceProvider
|
class EventServiceProvider extends ServiceProvider
|
||||||
|
|
@ -13,24 +14,27 @@ class EventServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
protected $listen = [
|
protected $listen = [
|
||||||
'App\Events\PlayerRetrieved' => [
|
'App\Events\PlayerRetrieved' => [
|
||||||
'App\Listeners\ResetInvalidTextureForPlayer',
|
Listeners\ResetInvalidTextureForPlayer::class,
|
||||||
],
|
],
|
||||||
'App\Events\TextureDeleting' => [
|
'App\Events\TextureDeleting' => [
|
||||||
'App\Listeners\TextureRemoved',
|
'App\Listeners\TextureRemoved',
|
||||||
],
|
],
|
||||||
'App\Events\PluginWasEnabled' => [
|
'App\Events\PluginWasEnabled' => [
|
||||||
'App\Listeners\CopyPluginAssets',
|
Listeners\CopyPluginAssets::class,
|
||||||
'App\Listeners\GeneratePluginTranslations',
|
Listeners\GeneratePluginTranslations::class,
|
||||||
],
|
],
|
||||||
'plugin.versionChanged' => [
|
'plugin.versionChanged' => [
|
||||||
'App\Listeners\CopyPluginAssets',
|
'App\Listeners\CopyPluginAssets',
|
||||||
'App\Listeners\GeneratePluginTranslations',
|
'App\Listeners\GeneratePluginTranslations',
|
||||||
],
|
],
|
||||||
'App\Events\PluginBootFailed' => [
|
'App\Events\PluginBootFailed' => [
|
||||||
'App\Listeners\NotifyFailedPlugin',
|
Listeners\NotifyFailedPlugin::class,
|
||||||
],
|
],
|
||||||
'App\Events\RenderingHeader' => [
|
'App\Events\RenderingHeader' => [
|
||||||
'App\Listeners\SerializeGlobals',
|
Listeners\SerializeGlobals::class,
|
||||||
|
],
|
||||||
|
'player.name.updated' => [
|
||||||
|
Listeners\SinglePlayer\UpdateOwnerNickName::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
"intervention/image": "^2.5",
|
"intervention/image": "^2.5",
|
||||||
"laravel/framework": "^7.0",
|
"laravel/framework": "^7.0",
|
||||||
"laravel/passport": "^8.4",
|
"laravel/passport": "^8.4",
|
||||||
|
"lorisleiva/laravel-search-string": "^0.1.6",
|
||||||
"nesbot/carbon": "^2.0",
|
"nesbot/carbon": "^2.0",
|
||||||
"nunomaduro/collision": "^4.1",
|
"nunomaduro/collision": "^4.1",
|
||||||
"rcrowe/twigbridge": "^0.11.3",
|
"rcrowe/twigbridge": "^0.11.3",
|
||||||
|
|
|
||||||
48
composer.lock
generated
48
composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e9621ab58940ab0702842b6ba6c80ccb",
|
"content-hash": "d1615cec2f58f3b4eb54ac477e348b42",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "blessing/filter",
|
"name": "blessing/filter",
|
||||||
|
|
@ -2330,6 +2330,52 @@
|
||||||
],
|
],
|
||||||
"time": "2019-07-13T18:58:26+00:00"
|
"time": "2019-07-13T18:58:26+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "lorisleiva/laravel-search-string",
|
||||||
|
"version": "v0.1.6",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lorisleiva/laravel-search-string.git",
|
||||||
|
"reference": "68189cad7614d0c9cb09a83315e1ccfe49e7f0c1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/lorisleiva/laravel-search-string/zipball/68189cad7614d0c9cb09a83315e1ccfe49e7f0c1",
|
||||||
|
"reference": "68189cad7614d0c9cb09a83315e1ccfe49e7f0c1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/support": "^5.5|^6.0|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"orchestra/testbench": "^5.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Lorisleiva\\LaravelSearchString\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Lorisleiva\\LaravelSearchString\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Loris Leiva",
|
||||||
|
"email": "loris.leiva@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Generates database queries based on one unique string using a simple and customizable syntax.",
|
||||||
|
"time": "2020-04-05T15:40:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "monolog/monolog",
|
"name": "monolog/monolog",
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export default [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'admin/players',
|
path: 'admin/players',
|
||||||
component: () => import('../views/admin/Players.vue'),
|
react: () => import('../views/admin/PlayersManagement'),
|
||||||
el: '.content > .container-fluid',
|
el: '.content > .container-fluid',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ export type User = {
|
||||||
export type Player = {
|
export type Player = {
|
||||||
pid: number
|
pid: number
|
||||||
name: string
|
name: string
|
||||||
|
uid: number
|
||||||
tid_skin: number
|
tid_skin: number
|
||||||
tid_cape: number
|
tid_cape: number
|
||||||
|
last_modified: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Texture = {
|
export type Texture = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@import './avatar';
|
@import './avatar';
|
||||||
|
@import './dropdown';
|
||||||
|
|
||||||
a {
|
a {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
|
||||||
15
resources/assets/src/styles/dropdown.scss
Normal file
15
resources/assets/src/styles/dropdown.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
.dropdown-item {
|
||||||
|
&:hover {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dropdown-item-danger {
|
||||||
|
color: var(--danger);
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,246 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="container-fluid">
|
|
||||||
<vue-good-table
|
|
||||||
mode="remote"
|
|
||||||
:rows="players"
|
|
||||||
:total-rows="totalRecords || players.length"
|
|
||||||
:columns="columns"
|
|
||||||
:search-options="tableOptions.search"
|
|
||||||
:pagination-options="tableOptions.pagination"
|
|
||||||
style-class="vgt-table striped"
|
|
||||||
@on-page-change="onPageChange"
|
|
||||||
@on-sort-change="onSortChange"
|
|
||||||
@on-search="onSearch"
|
|
||||||
@on-per-page-change="onPerPageChange"
|
|
||||||
>
|
|
||||||
<template #table-row="props">
|
|
||||||
<span v-if="props.column.field === 'name'">
|
|
||||||
{{ props.formattedRow[props.column.field] }}
|
|
||||||
<a :title="$t('admin.changePlayerName')" data-test="name" @click="changeName(props.row)">
|
|
||||||
<i class="fas fa-edit btn-edit" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="props.column.field === 'uid'">
|
|
||||||
<a
|
|
||||||
:href="`${baseUrl}/admin/users?uid=${props.row.uid}`"
|
|
||||||
:title="$t('admin.inspectHisOwner')"
|
|
||||||
data-toggle="tooltip"
|
|
||||||
data-placement="right"
|
|
||||||
>{{ props.formattedRow[props.column.field] }}</a>
|
|
||||||
<a :title="$t('admin.changeOwner')" data-test="owner" @click="changeOwner(props.row)">
|
|
||||||
<i class="fas fa-edit btn-edit" />
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="props.column.field === 'preview'">
|
|
||||||
<a
|
|
||||||
v-if="props.row.tid_skin"
|
|
||||||
:href="`${baseUrl}/skinlib/show/${props.row.tid_skin}`"
|
|
||||||
>
|
|
||||||
<img :src="`${baseUrl}/preview/${props.row.tid_skin}?height=64`" width="64">
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
v-if="props.row.tid_cape"
|
|
||||||
:href="`${baseUrl}/skinlib/show/${props.row.tid_cape}`"
|
|
||||||
>
|
|
||||||
<img :src="`${baseUrl}/preview/${props.row.tid_cape}?height=64`" width="64">
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<span v-else-if="props.column.field === 'operations'">
|
|
||||||
<button
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#modal-change-texture"
|
|
||||||
class="btn btn-default"
|
|
||||||
@click="textureChanges.originalIndex = props.row.originalIndex"
|
|
||||||
>
|
|
||||||
{{ $t('admin.changeTexture') }}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-danger" @click="deletePlayer(props.row)">
|
|
||||||
{{ $t('admin.deletePlayer') }}
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
<span v-else v-text="props.formattedRow[props.column.field]" />
|
|
||||||
</template>
|
|
||||||
</vue-good-table>
|
|
||||||
|
|
||||||
<modal
|
|
||||||
id="modal-change-texture"
|
|
||||||
:title="$t('admin.changeTexture')"
|
|
||||||
center
|
|
||||||
@confirm="changeTexture"
|
|
||||||
>
|
|
||||||
<div class="form-group">
|
|
||||||
<label v-t="'admin.textureType'" />
|
|
||||||
<select v-model="textureChanges.type" class="form-control">
|
|
||||||
<option v-t="'general.skin'" value="skin" />
|
|
||||||
<option v-t="'general.cape'" value="cape" />
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>TID</label>
|
|
||||||
<input
|
|
||||||
v-model.number="textureChanges.tid"
|
|
||||||
class="form-control"
|
|
||||||
type="text"
|
|
||||||
:placeholder="$t('admin.pidNotice')"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { VueGoodTable } from 'vue-good-table'
|
|
||||||
import 'vue-good-table/dist/vue-good-table.min.css'
|
|
||||||
import Modal from '../../components/Modal.vue'
|
|
||||||
import tableOptions from '../../components/mixins/tableOptions'
|
|
||||||
import serverTable from '../../components/mixins/serverTable'
|
|
||||||
import emitMounted from '../../components/mixins/emitMounted'
|
|
||||||
import { showModal, toast } from '../../scripts/notify'
|
|
||||||
import { truthy } from '../../scripts/validators'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'PlayersManagement',
|
|
||||||
components: {
|
|
||||||
Modal,
|
|
||||||
VueGoodTable,
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
emitMounted,
|
|
||||||
tableOptions,
|
|
||||||
serverTable,
|
|
||||||
],
|
|
||||||
props: {
|
|
||||||
baseUrl: {
|
|
||||||
type: String,
|
|
||||||
default: blessing.base_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
players: [],
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
field: 'pid', label: 'PID', type: 'number',
|
|
||||||
},
|
|
||||||
{ field: 'name', label: this.$t('general.player.player-name') },
|
|
||||||
{
|
|
||||||
field: 'uid', label: this.$t('general.player.owner'), type: 'number', sortable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'preview', label: this.$t('general.player.previews'), globalSearchDisabled: true, sortable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'last_modified', label: this.$t('general.player.last-modified'), sortable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
field: 'operations', label: this.$t('admin.operationsTitle'), globalSearchDisabled: true, sortable: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
textureChanges: {
|
|
||||||
originalIndex: -1,
|
|
||||||
type: 'skin',
|
|
||||||
tid: '',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeMount() {
|
|
||||||
this.fetchData()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async fetchData() {
|
|
||||||
const { data, totalRecords } = await this.$http.get(
|
|
||||||
`/admin/players/list${location.search}`,
|
|
||||||
!location.search && this.serverParams,
|
|
||||||
)
|
|
||||||
this.totalRecords = totalRecords
|
|
||||||
this.players = data
|
|
||||||
},
|
|
||||||
async changeTexture() {
|
|
||||||
const player = this.players[this.textureChanges.originalIndex]
|
|
||||||
const { type, tid } = this.textureChanges
|
|
||||||
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/admin/players?action=texture',
|
|
||||||
{
|
|
||||||
pid: player.pid, type, tid,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (code === 0) {
|
|
||||||
player[`tid_${type}`] = tid
|
|
||||||
toast.success(message)
|
|
||||||
} else {
|
|
||||||
toast.error(message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async changeName(player) {
|
|
||||||
let value
|
|
||||||
try {
|
|
||||||
({ value } = await showModal({
|
|
||||||
mode: 'prompt',
|
|
||||||
text: this.$t('admin.changePlayerNameNotice'),
|
|
||||||
input: player.name,
|
|
||||||
validator: truthy(this.$t('admin.emptyPlayerName')),
|
|
||||||
}))
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/admin/players?action=name',
|
|
||||||
{ pid: player.pid, name: value },
|
|
||||||
)
|
|
||||||
if (code === 0) {
|
|
||||||
player.name = value
|
|
||||||
toast.success(message)
|
|
||||||
} else {
|
|
||||||
toast.error(message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async changeOwner(player) {
|
|
||||||
let value
|
|
||||||
try {
|
|
||||||
({ value } = await showModal({
|
|
||||||
mode: 'prompt',
|
|
||||||
text: this.$t('admin.changePlayerOwner'),
|
|
||||||
input: player.uid,
|
|
||||||
}))
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value = Number.parseInt(value)
|
|
||||||
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/admin/players?action=owner',
|
|
||||||
{ pid: player.pid, uid: value },
|
|
||||||
)
|
|
||||||
if (code === 0) {
|
|
||||||
player.uid = value
|
|
||||||
toast.success(message)
|
|
||||||
} else {
|
|
||||||
toast.error(message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async deletePlayer({ pid, originalIndex }) {
|
|
||||||
try {
|
|
||||||
await showModal({
|
|
||||||
text: this.$t('admin.deletePlayerNotice'),
|
|
||||||
okButtonType: 'danger',
|
|
||||||
})
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { code, message } = await this.$http.post(
|
|
||||||
'/admin/players?action=delete',
|
|
||||||
{ pid },
|
|
||||||
)
|
|
||||||
if (code === 0) {
|
|
||||||
this.$delete(this.players, originalIndex)
|
|
||||||
toast.success(message)
|
|
||||||
} else {
|
|
||||||
toast.error(message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
@use '../../../styles/breakpoints';
|
||||||
|
|
||||||
|
.box {
|
||||||
|
width: 48%;
|
||||||
|
margin: 7px;
|
||||||
|
|
||||||
|
@include breakpoints.less-than('lg') {
|
||||||
|
width: 98%;
|
||||||
|
}
|
||||||
|
}
|
||||||
148
resources/assets/src/views/admin/PlayersManagement/Card.tsx
Normal file
148
resources/assets/src/views/admin/PlayersManagement/Card.tsx
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import { showModal } from '@/scripts/notify'
|
||||||
|
import { Player } from '@/scripts/types'
|
||||||
|
import styles from './Card.module.scss'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
player: Player
|
||||||
|
onUpdateName(): void
|
||||||
|
onUpdateOwner(): void
|
||||||
|
onUpdateTexture(): void
|
||||||
|
onDelete(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Card: React.FC<Props> = (props) => {
|
||||||
|
const { player } = props
|
||||||
|
|
||||||
|
const handlePreviewTextures = () => {
|
||||||
|
showModal({
|
||||||
|
mode: 'alert',
|
||||||
|
title: t('general.player.previews'),
|
||||||
|
children: (
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-6 d-flex justify-content-center">
|
||||||
|
{player.tid_skin > 0 && (
|
||||||
|
<a
|
||||||
|
href={`${blessing.base_url}/skinlib/show/${player.tid_skin}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`${blessing.base_url}/preview/${player.tid_skin}`}
|
||||||
|
alt={`${player.name} - ${t('general.skin')}`}
|
||||||
|
width="128"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col-6 d-flex justify-content-center">
|
||||||
|
{player.tid_cape > 0 && (
|
||||||
|
<a
|
||||||
|
href={`${blessing.base_url}/skinlib/show/${player.tid_cape}`}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`${blessing.base_url}/preview/${player.tid_cape}`}
|
||||||
|
alt={`${player.name} - ${t('general.cape')}`}
|
||||||
|
width="128"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`info-box ${styles.box}`}>
|
||||||
|
<div className="info-box-icon">
|
||||||
|
<img
|
||||||
|
className="bs-avatar"
|
||||||
|
src={`${blessing.base_url}/avatar/player/${player.name}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="info-box-content">
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-10">
|
||||||
|
<div>
|
||||||
|
<b>{player.name}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col-2">
|
||||||
|
<div className="float-right dropdown">
|
||||||
|
<a
|
||||||
|
className="text-gray"
|
||||||
|
href="#"
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
<i className="fas fa-cog"></i>
|
||||||
|
</a>
|
||||||
|
<div className="dropdown-menu dropdown-menu-right">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={handlePreviewTextures}
|
||||||
|
>
|
||||||
|
<i className="fas fa-eye mr-2"></i>
|
||||||
|
{t('general.player.previews')}
|
||||||
|
</a>
|
||||||
|
<div className="dropdown-divider"></div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={props.onUpdateName}
|
||||||
|
>
|
||||||
|
<i className="fas fa-signature mr-2"></i>
|
||||||
|
{t('admin.changePlayerName')}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={props.onUpdateOwner}
|
||||||
|
>
|
||||||
|
<i className="fas fa-user-edit mr-2"></i>
|
||||||
|
{t('admin.changeOwner')}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={props.onUpdateTexture}
|
||||||
|
>
|
||||||
|
<i className="fas fa-tshirt mr-2"></i>
|
||||||
|
{t('admin.changeTexture')}
|
||||||
|
</a>
|
||||||
|
<div className="dropdown-divider"></div>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
className="dropdown-item dropdown-item-danger"
|
||||||
|
onClick={props.onDelete}
|
||||||
|
>
|
||||||
|
<i className="fas fa-trash mr-2"></i>
|
||||||
|
{t('admin.deletePlayer')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span className="mr-2">PID: {player.pid}</span>
|
||||||
|
<span>
|
||||||
|
{t('general.player.owner')}: {player.uid}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small className="text-gray">
|
||||||
|
{`${t('general.player.last-modified')}: `}
|
||||||
|
{player.last_modified}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Card
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import Modal from '@/components/Modal'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
onSubmit(type: 'skin' | 'cape', tid: number): void
|
||||||
|
onClose(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalUpdateTexture: React.FC<Props> = (props) => {
|
||||||
|
const [type, setType] = useState<'skin' | 'cape'>('skin')
|
||||||
|
const [tid, setTid] = useState('')
|
||||||
|
|
||||||
|
const handleTypeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setType(event.target.value as 'skin' | 'cape')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTidChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setTid(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirm = () => {
|
||||||
|
props.onSubmit(type, Number.parseInt(tid))
|
||||||
|
setType('skin')
|
||||||
|
setTid('')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
setType('skin')
|
||||||
|
setTid('')
|
||||||
|
props.onClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
show={props.open}
|
||||||
|
center
|
||||||
|
onConfirm={handleConfirm}
|
||||||
|
onClose={handleClose}
|
||||||
|
>
|
||||||
|
<div className="form-group">
|
||||||
|
<label>{t('admin.textureType')}</label>
|
||||||
|
<div>
|
||||||
|
<label className="mr-5">
|
||||||
|
<input
|
||||||
|
className="mr-1"
|
||||||
|
type="radio"
|
||||||
|
value="skin"
|
||||||
|
checked={type === 'skin'}
|
||||||
|
onChange={handleTypeChange}
|
||||||
|
/>
|
||||||
|
{t('general.skin')}
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
className="mr-1"
|
||||||
|
type="radio"
|
||||||
|
value="cape"
|
||||||
|
checked={type === 'cape'}
|
||||||
|
onChange={handleTypeChange}
|
||||||
|
/>
|
||||||
|
{t('general.cape')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="form-group">
|
||||||
|
<label htmlFor="update-texture-tid">TID</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="update-texture-tid"
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('admin.pidNotice')}
|
||||||
|
value={tid}
|
||||||
|
onChange={handleTidChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalUpdateTexture
|
||||||
200
resources/assets/src/views/admin/PlayersManagement/index.tsx
Normal file
200
resources/assets/src/views/admin/PlayersManagement/index.tsx
Normal file
|
|
@ -0,0 +1,200 @@
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { hot } from 'react-hot-loader/root'
|
||||||
|
import { useImmer } from 'use-immer'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import { Player, Paginator } from '@/scripts/types'
|
||||||
|
import { toast, showModal } from '@/scripts/notify'
|
||||||
|
import Loading from '@/components/Loading'
|
||||||
|
import Pagination from '@/components/Pagination'
|
||||||
|
import Card from './Card'
|
||||||
|
import ModalUpdateTexture from './ModalUpdateTexture'
|
||||||
|
|
||||||
|
const PlayersManagement: React.FC = () => {
|
||||||
|
const [players, setPlayers] = useImmer<Player[]>([])
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [totalPages, setTotalPages] = useState(1)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [query, setQuery] = useState('')
|
||||||
|
const [textureUpdating, setTextureUpdating] = useState(-1)
|
||||||
|
|
||||||
|
const getPlayers = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
const { data, last_page }: Paginator<Player> = await fetch.get(
|
||||||
|
'/admin/players/list',
|
||||||
|
{
|
||||||
|
q: query,
|
||||||
|
page,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
setTotalPages(last_page)
|
||||||
|
setPlayers(() => data)
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getPlayers()
|
||||||
|
}, [page])
|
||||||
|
|
||||||
|
const handleQueryChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQuery(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmitQuery = (event: React.FormEvent) => {
|
||||||
|
event.preventDefault()
|
||||||
|
getPlayers()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateName = async (player: Player, index: number) => {
|
||||||
|
let name: string
|
||||||
|
try {
|
||||||
|
const { value } = await showModal({
|
||||||
|
mode: 'prompt',
|
||||||
|
text: t('admin.changePlayerNameNotice'),
|
||||||
|
input: player.name,
|
||||||
|
validator: (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return t('admin.emptyPlayerName')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
name = value
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code, message } = await fetch.put<fetch.ResponseBody>(
|
||||||
|
`/admin/players/${player.pid}/name`,
|
||||||
|
{ player_name: name },
|
||||||
|
)
|
||||||
|
if (code === 0) {
|
||||||
|
toast.success(message)
|
||||||
|
setPlayers((players) => {
|
||||||
|
players[index].name = name
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdateOwner = async (player: Player, index: number) => {
|
||||||
|
let uid: number
|
||||||
|
try {
|
||||||
|
const { value } = await showModal({
|
||||||
|
mode: 'prompt',
|
||||||
|
text: t('admin.changePlayerOwner'),
|
||||||
|
input: player.uid.toString(),
|
||||||
|
inputType: 'number',
|
||||||
|
})
|
||||||
|
uid = Number.parseInt(value)
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code, message } = await fetch.put<fetch.ResponseBody>(
|
||||||
|
`/admin/players/${player.pid}/owner`,
|
||||||
|
{ uid },
|
||||||
|
)
|
||||||
|
if (code === 0) {
|
||||||
|
toast.success(message)
|
||||||
|
setPlayers((players) => {
|
||||||
|
players[index].uid = uid
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCloseModalUpdateTexture = () => setTextureUpdating(-1)
|
||||||
|
|
||||||
|
const handleUpdateTexture = async (type: 'skin' | 'cape', tid: number) => {
|
||||||
|
const { code, message } = await fetch.put<fetch.ResponseBody>(
|
||||||
|
`/admin/players/${players[textureUpdating].pid}/textures`,
|
||||||
|
{ type, tid },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
toast.success(message)
|
||||||
|
setPlayers((players) => {
|
||||||
|
const field = `tid_${type}` as 'tid_skin' | 'tid_cape'
|
||||||
|
players[textureUpdating][field] = tid
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
toast.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDelete = async (player: Player) => {
|
||||||
|
try {
|
||||||
|
await showModal({
|
||||||
|
text: t('admin.deletePlayerNotice'),
|
||||||
|
okButtonType: 'danger',
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { code, message } = await fetch.del<fetch.ResponseBody>(
|
||||||
|
`/admin/players/${player.pid}`,
|
||||||
|
)
|
||||||
|
if (code === 0) {
|
||||||
|
setPlayers((players) => players.filter(({ pid }) => pid !== player.pid))
|
||||||
|
toast.success(message)
|
||||||
|
} else {
|
||||||
|
toast.error(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="card">
|
||||||
|
<div className="card-header">
|
||||||
|
<form className="input-group" onSubmit={handleSubmitQuery}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
|
title={t('vendor.datatable.search')}
|
||||||
|
value={query}
|
||||||
|
onChange={handleQueryChange}
|
||||||
|
/>
|
||||||
|
<div className="input-group-append">
|
||||||
|
<button className="btn btn-primary" type="submit">
|
||||||
|
{t('vendor.datatable.search')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div className="card-body">
|
||||||
|
{isLoading ? (
|
||||||
|
<Loading />
|
||||||
|
) : players.length === 0 ? (
|
||||||
|
<div className="text-center">No players.</div>
|
||||||
|
) : (
|
||||||
|
<div className="d-flex flex-wrap">
|
||||||
|
{players.map((player, i) => (
|
||||||
|
<Card
|
||||||
|
key={player.pid}
|
||||||
|
player={player}
|
||||||
|
onUpdateName={() => handleUpdateName(player, i)}
|
||||||
|
onUpdateOwner={() => handleUpdateOwner(player, i)}
|
||||||
|
onUpdateTexture={() => setTextureUpdating(i)}
|
||||||
|
onDelete={() => handleDelete(player)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="card-footer">
|
||||||
|
<div className="float-right">
|
||||||
|
<Pagination page={page} totalPages={totalPages} onChange={setPage} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ModalUpdateTexture
|
||||||
|
open={textureUpdating > -1}
|
||||||
|
onSubmit={handleUpdateTexture}
|
||||||
|
onClose={handleCloseModalUpdateTexture}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hot(PlayersManagement)
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
import Vue from 'vue'
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import { flushPromises } from '../../utils'
|
|
||||||
import { showModal } from '@/scripts/notify'
|
|
||||||
import Modal from '@/components/Modal.vue'
|
|
||||||
import Players from '@/views/admin/Players.vue'
|
|
||||||
|
|
||||||
jest.mock('@/scripts/notify')
|
|
||||||
|
|
||||||
test('fetch data after initializing', () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({ data: [] })
|
|
||||||
mount(Players)
|
|
||||||
expect(Vue.prototype.$http.get).toBeCalledWith(
|
|
||||||
'/admin/players/list',
|
|
||||||
{
|
|
||||||
page: 1, perPage: 10, search: '', sortField: 'pid', sortType: 'asc',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('change texture', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({
|
|
||||||
data: [
|
|
||||||
{ pid: 1, tid_skin: 0 },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: '0' })
|
|
||||||
|
|
||||||
const wrapper = mount(Players)
|
|
||||||
await flushPromises()
|
|
||||||
const modal = wrapper.find(Modal)
|
|
||||||
wrapper.findAll('.btn-default').trigger('click')
|
|
||||||
|
|
||||||
wrapper.find('.modal-body input').setValue('5')
|
|
||||||
|
|
||||||
modal.vm.$emit('confirm')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/admin/players?action=texture',
|
|
||||||
{
|
|
||||||
pid: 1, tid: 5, type: 'skin',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
modal.vm.$emit('confirm')
|
|
||||||
await flushPromises()
|
|
||||||
expect(wrapper.html()).toContain('/preview/5?height=64')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('change player name', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({
|
|
||||||
data: [
|
|
||||||
{ pid: 1, name: 'old' },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: '0' })
|
|
||||||
showModal
|
|
||||||
.mockRejectedValueOnce(null)
|
|
||||||
.mockResolvedValue({ value: 'new' })
|
|
||||||
const wrapper = mount(Players)
|
|
||||||
await flushPromises()
|
|
||||||
const button = wrapper.find('[data-test="name"]')
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/admin/players?action=name',
|
|
||||||
{ pid: 1, name: 'new' },
|
|
||||||
)
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(wrapper.text()).toContain('new')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('change owner', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({
|
|
||||||
data: [
|
|
||||||
{ pid: 1, uid: 2 },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: '0' })
|
|
||||||
showModal
|
|
||||||
.mockRejectedValueOnce(null)
|
|
||||||
.mockResolvedValue({ value: '3' })
|
|
||||||
|
|
||||||
const wrapper = mount(Players)
|
|
||||||
await flushPromises()
|
|
||||||
const button = wrapper.find('[data-test="owner"]')
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/admin/players?action=owner',
|
|
||||||
{ pid: 1, uid: 3 },
|
|
||||||
)
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(wrapper.text()).toContain('3')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('delete player', async () => {
|
|
||||||
Vue.prototype.$http.get.mockResolvedValue({
|
|
||||||
data: [
|
|
||||||
{ pid: 1, name: 'to-be-deleted' },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
Vue.prototype.$http.post
|
|
||||||
.mockResolvedValueOnce({ code: 1, message: '1' })
|
|
||||||
.mockResolvedValueOnce({ code: 0, message: '0' })
|
|
||||||
showModal
|
|
||||||
.mockRejectedValueOnce(null)
|
|
||||||
.mockResolvedValue({ value: '' })
|
|
||||||
|
|
||||||
const wrapper = mount(Players)
|
|
||||||
await flushPromises()
|
|
||||||
const button = wrapper.find('.btn-danger')
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
expect(Vue.prototype.$http.post).not.toBeCalled()
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(Vue.prototype.$http.post).toBeCalledWith(
|
|
||||||
'/admin/players?action=delete',
|
|
||||||
{ pid: 1 },
|
|
||||||
)
|
|
||||||
expect(wrapper.text()).toContain('to-be-deleted')
|
|
||||||
|
|
||||||
button.trigger('click')
|
|
||||||
await flushPromises()
|
|
||||||
expect(wrapper.text()).toContain('No data')
|
|
||||||
})
|
|
||||||
362
resources/assets/tests/views/admin/PlayersManagement.test.tsx
Normal file
362
resources/assets/tests/views/admin/PlayersManagement.test.tsx
Normal file
|
|
@ -0,0 +1,362 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { render, waitFor, fireEvent } from '@testing-library/react'
|
||||||
|
import { t } from '@/scripts/i18n'
|
||||||
|
import * as fetch from '@/scripts/net'
|
||||||
|
import { Player, Paginator } from '@/scripts/types'
|
||||||
|
import PlayersManagement from '@/views/admin/PlayersManagement'
|
||||||
|
|
||||||
|
jest.mock('@/scripts/net')
|
||||||
|
|
||||||
|
const fixture: Readonly<Player> = Object.freeze<Player>({
|
||||||
|
pid: 1,
|
||||||
|
name: 'kumiko',
|
||||||
|
uid: 1,
|
||||||
|
tid_skin: 1,
|
||||||
|
tid_cape: 2,
|
||||||
|
last_modified: new Date().toString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function createPaginator(data: Player[]): Paginator<Player> {
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
total: data.length,
|
||||||
|
from: 1,
|
||||||
|
to: data.length,
|
||||||
|
current_page: 1,
|
||||||
|
last_page: 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('search players', async () => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([]))
|
||||||
|
|
||||||
|
const { getByTitle, getByText } = render(<PlayersManagement />)
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.get).toBeCalledWith('/admin/players/list', { q: '', page: 1 }),
|
||||||
|
)
|
||||||
|
|
||||||
|
fireEvent.input(getByTitle(t('vendor.datatable.search')), {
|
||||||
|
target: { value: 's' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('vendor.datatable.search')))
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.get).toBeCalledWith('/admin/players/list', {
|
||||||
|
q: 's',
|
||||||
|
page: 1,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('preview textures', async () => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([fixture]))
|
||||||
|
|
||||||
|
const { getByText, queryByAltText } = render(<PlayersManagement />)
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.player.previews')))
|
||||||
|
|
||||||
|
expect(
|
||||||
|
queryByAltText(`${fixture.name} - ${t('general.skin')}`),
|
||||||
|
).toHaveAttribute('src', `${blessing.base_url}/preview/${fixture.tid_skin}`)
|
||||||
|
expect(
|
||||||
|
queryByAltText(`${fixture.name} - ${t('general.cape')}`),
|
||||||
|
).toHaveAttribute('src', `${blessing.base_url}/preview/${fixture.tid_cape}`)
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update player name', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([fixture]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('empty value', async () => {
|
||||||
|
const { getByText, getByDisplayValue, queryByText } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changePlayerName')))
|
||||||
|
fireEvent.input(getByDisplayValue(fixture.name), {
|
||||||
|
target: { value: '' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
expect(queryByText(t('admin.emptyPlayerName'))).toBeInTheDocument()
|
||||||
|
expect(fetch.put).not.toBeCalled()
|
||||||
|
|
||||||
|
fireEvent.click(getByText(t('general.cancel')))
|
||||||
|
|
||||||
|
expect(queryByText(fixture.name)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('succeeded', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
|
||||||
|
const { getByText, getByDisplayValue, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changePlayerName')))
|
||||||
|
fireEvent.input(getByDisplayValue(fixture.name), {
|
||||||
|
target: { value: 'reina' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/name`, {
|
||||||
|
player_name: 'reina',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||||
|
expect(queryByText('reina')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
const { getByText, getByDisplayValue, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changePlayerName')))
|
||||||
|
fireEvent.input(getByDisplayValue(fixture.name), {
|
||||||
|
target: { value: 'reina' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/name`, {
|
||||||
|
player_name: 'reina',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('alert')).toHaveClass('alert-danger')
|
||||||
|
expect(queryByText(fixture.name)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update owner', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([fixture]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cancelled', async () => {
|
||||||
|
const { getByText, queryByText } = render(<PlayersManagement />)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeOwner')))
|
||||||
|
fireEvent.click(getByText(t('general.cancel')))
|
||||||
|
|
||||||
|
expect(fetch.put).not.toBeCalled()
|
||||||
|
expect(
|
||||||
|
queryByText(`${t('general.player.owner')}: ${fixture.uid}`),
|
||||||
|
).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('succeeded', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
|
||||||
|
const { getByText, getByDisplayValue, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeOwner')))
|
||||||
|
fireEvent.input(getByDisplayValue(fixture.uid.toString()), {
|
||||||
|
target: { value: '2' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/owner`, {
|
||||||
|
uid: 2,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||||
|
expect(queryByText(`${t('general.player.owner')}: 2`)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
const { getByText, getByDisplayValue, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeOwner')))
|
||||||
|
fireEvent.input(getByDisplayValue(fixture.uid.toString()), {
|
||||||
|
target: { value: '2' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(`/admin/players/${fixture.pid}/owner`, {
|
||||||
|
uid: 2,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('alert')).toHaveClass('alert-danger')
|
||||||
|
expect(
|
||||||
|
queryByText(`${t('general.player.owner')}: ${fixture.uid}`),
|
||||||
|
).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('update texture', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([fixture]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cancelled', async () => {
|
||||||
|
const { getByText } = render(<PlayersManagement />)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeTexture')))
|
||||||
|
fireEvent.click(getByText(t('general.cancel')))
|
||||||
|
|
||||||
|
expect(fetch.put).not.toBeCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('skin', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
|
||||||
|
const { getByText, getByLabelText, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeTexture')))
|
||||||
|
fireEvent.input(getByLabelText('TID'), {
|
||||||
|
target: { value: '2' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(
|
||||||
|
`/admin/players/${fixture.pid}/textures`,
|
||||||
|
{
|
||||||
|
type: 'skin',
|
||||||
|
tid: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cape', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
|
||||||
|
const { getByText, getByLabelText, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeTexture')))
|
||||||
|
fireEvent.click(getByLabelText(t('general.cape')))
|
||||||
|
fireEvent.input(getByLabelText('TID'), {
|
||||||
|
target: { value: '2' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(
|
||||||
|
`/admin/players/${fixture.pid}/textures`,
|
||||||
|
{
|
||||||
|
type: 'cape',
|
||||||
|
tid: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed', async () => {
|
||||||
|
fetch.put.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
const { getByText, getByLabelText, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.changeTexture')))
|
||||||
|
fireEvent.input(getByLabelText('TID'), {
|
||||||
|
target: { value: '2' },
|
||||||
|
})
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.put).toBeCalledWith(
|
||||||
|
`/admin/players/${fixture.pid}/textures`,
|
||||||
|
{
|
||||||
|
type: 'skin',
|
||||||
|
tid: 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('alert')).toHaveClass('alert-danger')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('delete player', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetch.get.mockResolvedValue(createPaginator([fixture]))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('cancelled', async () => {
|
||||||
|
const { getByText, queryByText } = render(<PlayersManagement />)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.deletePlayer')))
|
||||||
|
fireEvent.click(getByText(t('general.cancel')))
|
||||||
|
|
||||||
|
expect(fetch.del).not.toBeCalled()
|
||||||
|
expect(queryByText(fixture.name)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('succeeded', async () => {
|
||||||
|
fetch.del.mockResolvedValue({ code: 0, message: 'ok' })
|
||||||
|
|
||||||
|
const { getByText, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.deletePlayer')))
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.del).toBeCalledWith(`/admin/players/${fixture.pid}`),
|
||||||
|
)
|
||||||
|
expect(queryByText('ok')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('status')).toHaveClass('alert-success')
|
||||||
|
expect(queryByText(fixture.name)).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('failed', async () => {
|
||||||
|
fetch.del.mockResolvedValue({ code: 1, message: 'failed' })
|
||||||
|
|
||||||
|
const { getByText, queryByText, queryByRole } = render(
|
||||||
|
<PlayersManagement />,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(fetch.get).toBeCalled())
|
||||||
|
fireEvent.click(getByText(t('admin.deletePlayer')))
|
||||||
|
fireEvent.click(getByText(t('general.confirm')))
|
||||||
|
|
||||||
|
await waitFor(() =>
|
||||||
|
expect(fetch.del).toBeCalledWith(`/admin/players/${fixture.pid}`),
|
||||||
|
)
|
||||||
|
expect(queryByText('failed')).toBeInTheDocument()
|
||||||
|
expect(queryByRole('alert')).toHaveClass('alert-danger')
|
||||||
|
expect(queryByText(fixture.name)).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -45,8 +45,10 @@ const fixtureCape: Readonly<ClosetItem> = Object.freeze<ClosetItem>({
|
||||||
const fixturePlayer: Readonly<Player> = Object.freeze<Player>({
|
const fixturePlayer: Readonly<Player> = Object.freeze<Player>({
|
||||||
pid: 1,
|
pid: 1,
|
||||||
name: 'kumiko',
|
name: 'kumiko',
|
||||||
|
uid: 1,
|
||||||
tid_skin: 1,
|
tid_skin: 1,
|
||||||
tid_cape: 2,
|
tid_cape: 2,
|
||||||
|
last_modified: new Date().toString(),
|
||||||
})
|
})
|
||||||
|
|
||||||
function createPaginator(data: ClosetItem[]): Paginator<ClosetItem> {
|
function createPaginator(data: ClosetItem[]): Paginator<ClosetItem> {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@ jest.mock('@/scripts/net')
|
||||||
const fixture: Readonly<Player> = Object.freeze<Player>({
|
const fixture: Readonly<Player> = Object.freeze<Player>({
|
||||||
pid: 1,
|
pid: 1,
|
||||||
name: 'kumiko',
|
name: 'kumiko',
|
||||||
|
uid: 1,
|
||||||
tid_skin: 1,
|
tid_skin: 1,
|
||||||
tid_cape: 2,
|
tid_cape: 2,
|
||||||
|
last_modified: new Date().toString(),
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
@ -38,7 +40,14 @@ test('loading indicator', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('search players', async () => {
|
test('search players', async () => {
|
||||||
const fixture2: Player = { pid: 2, name: 'reina', tid_skin: 3, tid_cape: 4 }
|
const fixture2: Player = {
|
||||||
|
pid: 2,
|
||||||
|
name: 'reina',
|
||||||
|
uid: 2,
|
||||||
|
tid_skin: 3,
|
||||||
|
tid_cape: 4,
|
||||||
|
last_modified: new Date().toString(),
|
||||||
|
}
|
||||||
fetch.get.mockResolvedValue({ data: [fixture, fixture2] })
|
fetch.get.mockResolvedValue({ data: [fixture, fixture2] })
|
||||||
|
|
||||||
const { getByPlaceholderText, queryByText } = render(<Players />)
|
const { getByPlaceholderText, queryByText } = render(<Players />)
|
||||||
|
|
@ -63,7 +72,14 @@ describe('select player automatically', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('more players', async () => {
|
it('more players', async () => {
|
||||||
const fixture2: Player = { pid: 2, name: 'reina', tid_skin: 3, tid_cape: 4 }
|
const fixture2: Player = {
|
||||||
|
pid: 2,
|
||||||
|
name: 'reina',
|
||||||
|
uid: 2,
|
||||||
|
tid_skin: 3,
|
||||||
|
tid_cape: 4,
|
||||||
|
last_modified: new Date().toString(),
|
||||||
|
}
|
||||||
fetch.get.mockResolvedValue({ data: [fixture, fixture2] })
|
fetch.get.mockResolvedValue({ data: [fixture, fixture2] })
|
||||||
render(<Players />)
|
render(<Players />)
|
||||||
await waitFor(() => expect(fetch.get).toBeCalledTimes(1))
|
await waitFor(() => expect(fetch.get).toBeCalledTimes(1))
|
||||||
|
|
|
||||||
|
|
@ -133,11 +133,16 @@ Route::prefix('admin')
|
||||||
Route::get('list', 'AdminController@getUserData');
|
Route::get('list', 'AdminController@getUserData');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::prefix('players')->group(function () {
|
Route::prefix('players')
|
||||||
Route::view('', 'admin.players');
|
->name('players.')
|
||||||
Route::post('', 'AdminController@playerAjaxHandler');
|
->group(function () {
|
||||||
Route::get('list', 'AdminController@getPlayerData');
|
Route::view('', 'admin.players');
|
||||||
});
|
Route::get('list', 'PlayersManagementController@list')->name('list');
|
||||||
|
Route::put('{player}/name', 'PlayersManagementController@name')->name('name');
|
||||||
|
Route::put('{player}/owner', 'PlayersManagementController@owner')->name('owner');
|
||||||
|
Route::put('{player}/textures', 'PlayersManagementController@texture')->name('texture');
|
||||||
|
Route::delete('{player}', 'PlayersManagementController@delete')->name('delete');
|
||||||
|
});
|
||||||
|
|
||||||
Route::prefix('closet')->group(function () {
|
Route::prefix('closet')->group(function () {
|
||||||
Route::post('{uid}', 'ClosetManagementController@add');
|
Route::post('{uid}', 'ClosetManagementController@add');
|
||||||
|
|
|
||||||
|
|
@ -387,202 +387,4 @@ class AdminControllerTest extends TestCase
|
||||||
]);
|
]);
|
||||||
$this->assertNull(User::find($user->uid));
|
$this->assertNull(User::find($user->uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testPlayerAjaxHandler()
|
|
||||||
{
|
|
||||||
$player = factory(Player::class)->create();
|
|
||||||
|
|
||||||
// Operate on a not-existed player
|
|
||||||
$this->postJson('/admin/players', ['pid' => -1])
|
|
||||||
->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('general.unexistent-player'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// An admin cannot operate another admin's player
|
|
||||||
$admin = factory(User::class)->states('admin')->create();
|
|
||||||
$this->postJson(
|
|
||||||
'/admin/players',
|
|
||||||
['pid' => factory(Player::class)->create(['uid' => $admin->uid])->pid]
|
|
||||||
)->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('admin.players.no-permission'),
|
|
||||||
]);
|
|
||||||
$superAdmin = factory(User::class)->states('superAdmin')->create();
|
|
||||||
$this->postJson(
|
|
||||||
'/admin/players',
|
|
||||||
['pid' => factory(Player::class)->create(['uid' => $superAdmin->uid])->pid]
|
|
||||||
)->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('admin.players.no-permission'),
|
|
||||||
]);
|
|
||||||
// For self is OK
|
|
||||||
$this->actingAs($admin)->postJson(
|
|
||||||
'/admin/players',
|
|
||||||
['pid' => factory(Player::class)->create(['uid' => $admin->uid])->pid]
|
|
||||||
)->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('admin.users.operations.invalid'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Change texture without `type` field
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
])->assertJsonValidationErrors(['type']);
|
|
||||||
|
|
||||||
// Change texture without `tid` field
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'skin',
|
|
||||||
])->assertJsonValidationErrors(['tid']);
|
|
||||||
|
|
||||||
// Change texture with a not-integer value
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'skin',
|
|
||||||
'tid' => 'string',
|
|
||||||
])->assertJsonValidationErrors(['tid']);
|
|
||||||
|
|
||||||
// Invalid texture
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'skin',
|
|
||||||
'tid' => -1,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('admin.players.textures.non-existent', ['tid' => -1]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$skin = factory(Texture::class)->create();
|
|
||||||
$cape = factory(Texture::class)->states('cape')->create();
|
|
||||||
|
|
||||||
// Skin
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'skin',
|
|
||||||
'tid' => $skin->tid,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
|
||||||
]);
|
|
||||||
$player = Player::find($player->pid);
|
|
||||||
$this->assertEquals($skin->tid, $player->tid_skin);
|
|
||||||
|
|
||||||
// Cape
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'cape',
|
|
||||||
'tid' => $cape->tid,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
|
||||||
]);
|
|
||||||
$player = Player::find($player->pid);
|
|
||||||
$this->assertEquals($cape->tid, $player->tid_cape);
|
|
||||||
|
|
||||||
// Reset texture
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'skin',
|
|
||||||
'tid' => 0,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
|
||||||
]);
|
|
||||||
$player = Player::find($player->pid);
|
|
||||||
$this->assertEquals(0, $player->tid_skin);
|
|
||||||
$this->assertNotEquals(0, $player->tid_cape);
|
|
||||||
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'texture',
|
|
||||||
'type' => 'cape',
|
|
||||||
'tid' => 0,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
|
||||||
]);
|
|
||||||
$player = Player::find($player->pid);
|
|
||||||
$this->assertEquals(0, $player->tid_cape);
|
|
||||||
|
|
||||||
// Change owner without `uid` field
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'owner',
|
|
||||||
])->assertJsonValidationErrors(['uid']);
|
|
||||||
|
|
||||||
// Change owner with a not-integer `uid` value
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'owner',
|
|
||||||
'uid' => 'string',
|
|
||||||
])->assertJsonValidationErrors(['uid']);
|
|
||||||
|
|
||||||
// Change owner to a not-existed user
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'owner',
|
|
||||||
'uid' => -1,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 1,
|
|
||||||
'message' => trans('admin.users.operations.non-existent'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Change owner successfully
|
|
||||||
$user = factory(User::class)->create();
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'owner',
|
|
||||||
'uid' => $user->uid,
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans(
|
|
||||||
'admin.players.owner.success',
|
|
||||||
['player' => $player->name, 'user' => $user->nickname]
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Rename a player without `name` field
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'name',
|
|
||||||
])->assertJsonValidationErrors(['name']);
|
|
||||||
|
|
||||||
// Rename a player successfully
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'name',
|
|
||||||
'name' => 'new_name',
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.name.success', ['player' => 'new_name']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Single player
|
|
||||||
option(['single_player' => true]);
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'name',
|
|
||||||
'name' => 'abc',
|
|
||||||
])->assertJson(['code' => 0]);
|
|
||||||
$player->refresh();
|
|
||||||
$this->assertEquals('abc', $player->user->nickname);
|
|
||||||
|
|
||||||
// Delete a player
|
|
||||||
$this->postJson('/admin/players', [
|
|
||||||
'pid' => $player->pid,
|
|
||||||
'action' => 'delete',
|
|
||||||
])->assertJson([
|
|
||||||
'code' => 0,
|
|
||||||
'message' => trans('admin.players.delete.success'),
|
|
||||||
]);
|
|
||||||
$this->assertNull(Player::find($player->pid));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use App\Models\Player;
|
||||||
|
use App\Models\Texture;
|
||||||
|
use App\Models\User;
|
||||||
|
use Event;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
|
||||||
|
class PlayersManagementControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->actingAs(factory(\App\Models\User::class)->states('admin')->create());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testList()
|
||||||
|
{
|
||||||
|
$player = factory(Player::class)->create();
|
||||||
|
|
||||||
|
$this->getJson(route('admin.players.list'))
|
||||||
|
->assertJson(['data' => [$player->toArray()]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAccessControl()
|
||||||
|
{
|
||||||
|
// an admin can't operate another admin's player
|
||||||
|
$admin = factory(User::class)->states('admin')->create();
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create(['uid' => $admin->uid]);
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => 'abcd']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 1,
|
||||||
|
'message' => trans('admin.players.no-permission'),
|
||||||
|
])->assertForbidden();
|
||||||
|
|
||||||
|
// for self is OK
|
||||||
|
$this->actingAs($admin)
|
||||||
|
->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => 'abcd']
|
||||||
|
)->assertJson(['code' => 0]);
|
||||||
|
|
||||||
|
// super admin
|
||||||
|
$superAdmin = factory(User::class)->states('superAdmin')->create();
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create(['uid' => $superAdmin->uid]);
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => 'abcd']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 1,
|
||||||
|
'message' => trans('admin.players.no-permission'),
|
||||||
|
])->assertForbidden();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testName()
|
||||||
|
{
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create();
|
||||||
|
|
||||||
|
// missing `player_name` field
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid])
|
||||||
|
)->assertJsonValidationErrors(['player_name']);
|
||||||
|
|
||||||
|
// duplicated player name
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => $player->name]
|
||||||
|
)->assertJsonValidationErrors(['player_name']);
|
||||||
|
|
||||||
|
// single player
|
||||||
|
option(['single_player' => true]);
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => 'abc']
|
||||||
|
)->assertJson(['code' => 0]);
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals('abc', $player->user->nickname);
|
||||||
|
option(['single_player' => false]);
|
||||||
|
|
||||||
|
// rename a player successfully
|
||||||
|
Event::fake();
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.name', ['player' => $player->pid]),
|
||||||
|
['player_name' => 'new_name']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.name.success', ['player' => 'new_name']),
|
||||||
|
]);
|
||||||
|
$oldName = $player->name;
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals('new_name', $player->name);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.name.updating',
|
||||||
|
function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('new_name', $payload[1]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.name.updated',
|
||||||
|
function ($eventName, $payload) use ($player, $oldName) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals($oldName, $payload[1]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOwner()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create();
|
||||||
|
|
||||||
|
// missing `uid` field
|
||||||
|
$this->putJson(route('admin.players.owner', ['player' => $player->pid]))
|
||||||
|
->assertJsonValidationErrors(['uid']);
|
||||||
|
|
||||||
|
// with a non-integer `uid` value
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.owner', ['player' => $player->pid]),
|
||||||
|
['uid' => 's']
|
||||||
|
)->assertJsonValidationErrors(['uid']);
|
||||||
|
|
||||||
|
// change owner to a not-existed user
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.owner', ['player' => $player->pid]),
|
||||||
|
['uid' => -1]
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 1,
|
||||||
|
'message' => trans('admin.users.operations.non-existent'),
|
||||||
|
]);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.owner.updating',
|
||||||
|
function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals(-1, $payload[1]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertNotDispatched('player.owner.updated');
|
||||||
|
|
||||||
|
// change owner successfully
|
||||||
|
Event::fake();
|
||||||
|
/** @var User */
|
||||||
|
$user = factory(User::class)->create();
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.owner', ['player' => $player->pid]),
|
||||||
|
['uid' => $user->uid]
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans(
|
||||||
|
'admin.players.owner.success',
|
||||||
|
['player' => $player->name, 'user' => $user->nickname]
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.owner.updating',
|
||||||
|
function ($eventName, $payload) use ($player, $user) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals($user->uid, $payload[1]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.owner.updated',
|
||||||
|
function ($eventName, $payload) use ($player, $user) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals($user->uid, $payload[1]->uid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTexture()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create();
|
||||||
|
|
||||||
|
// missing `tid` field
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid])
|
||||||
|
)->assertJsonValidationErrors(['tid']);
|
||||||
|
|
||||||
|
// change texture with a non-integer value
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => 's']
|
||||||
|
)->assertJsonValidationErrors(['tid']);
|
||||||
|
|
||||||
|
// missing `type` field
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => -1]
|
||||||
|
)->assertJsonValidationErrors(['type']);
|
||||||
|
|
||||||
|
// invalid type
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => -1, 'type' => 'elytra']
|
||||||
|
)->assertJsonValidationErrors(['type']);
|
||||||
|
|
||||||
|
// invalid texture
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => -1, 'type' => 'skin']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 1,
|
||||||
|
'message' => trans('admin.players.textures.non-existent', ['tid' => -1]),
|
||||||
|
]);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updating',
|
||||||
|
function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('skin', $payload[1]);
|
||||||
|
$this->assertEquals(-1, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertNotDispatched('player.texture.updated');
|
||||||
|
|
||||||
|
/** @var Texture */
|
||||||
|
$skin = factory(Texture::class)->create();
|
||||||
|
/** @var Texture */
|
||||||
|
$cape = factory(Texture::class)->states('cape')->create();
|
||||||
|
|
||||||
|
// skin
|
||||||
|
Event::fake();
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => $skin->tid, 'type' => 'skin']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
||||||
|
]);
|
||||||
|
$previousTid = $player->tid_skin;
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals($skin->tid, $player->tid_skin);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updating',
|
||||||
|
function ($eventName, $payload) use ($player, $skin) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('skin', $payload[1]);
|
||||||
|
$this->assertEquals($skin->tid, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updated',
|
||||||
|
function ($eventName, $payload) use ($player, $previousTid) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('skin', $payload[1]);
|
||||||
|
$this->assertEquals($previousTid, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// cape
|
||||||
|
Event::fake();
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => $cape->tid, 'type' => 'cape']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
||||||
|
]);
|
||||||
|
$previousTid = $player->tid_cape;
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals($cape->tid, $player->tid_cape);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updating',
|
||||||
|
function ($eventName, $payload) use ($player, $cape) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('cape', $payload[1]);
|
||||||
|
$this->assertEquals($cape->tid, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updated',
|
||||||
|
function ($eventName, $payload) use ($player, $previousTid) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('cape', $payload[1]);
|
||||||
|
$this->assertEquals($previousTid, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// reset texture
|
||||||
|
Event::fake();
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => 0, 'type' => 'skin']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
||||||
|
]);
|
||||||
|
$previousTid = $player->tid_skin;
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals(0, $player->tid_skin);
|
||||||
|
$this->assertNotEquals(0, $player->tid_cape);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updating',
|
||||||
|
function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('skin', $payload[1]);
|
||||||
|
$this->assertEquals(0, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Event::assertDispatched(
|
||||||
|
'player.texture.updated',
|
||||||
|
function ($eventName, $payload) use ($player, $previousTid) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
$this->assertEquals('skin', $payload[1]);
|
||||||
|
$this->assertEquals($previousTid, $payload[2]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->putJson(
|
||||||
|
route('admin.players.texture', ['player' => $player->pid]),
|
||||||
|
['tid' => 0, 'type' => 'cape']
|
||||||
|
)->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.textures.success', ['player' => $player->name]),
|
||||||
|
]);
|
||||||
|
$player->refresh();
|
||||||
|
$this->assertEquals(0, $player->tid_cape);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDelete()
|
||||||
|
{
|
||||||
|
Event::fake();
|
||||||
|
|
||||||
|
/** @var Player */
|
||||||
|
$player = factory(Player::class)->create();
|
||||||
|
|
||||||
|
$this->deleteJson(route('admin.players.delete', ['player' => $player->pid]))
|
||||||
|
->assertJson([
|
||||||
|
'code' => 0,
|
||||||
|
'message' => trans('admin.players.delete.success'),
|
||||||
|
]);
|
||||||
|
Event::assertDispatched('player.deleting', function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
Event::assertDispatched('player.deleted', function ($eventName, $payload) use ($player) {
|
||||||
|
$this->assertEquals($player->pid, $payload[0]->pid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
$this->assertNull(Player::find($player->pid));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user