add more events and filters for PlayerController

This commit is contained in:
Pig Fang 2020-06-02 18:24:00 +08:00
parent 1f291bd671
commit b8ab1f41e5
2 changed files with 141 additions and 38 deletions

View File

@ -10,15 +10,13 @@ use App\Http\Middleware\CheckPlayerExist;
use App\Http\Middleware\CheckPlayerOwner; use App\Http\Middleware\CheckPlayerOwner;
use App\Models\Player; use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User;
use App\Rules; use App\Rules;
use Auth; use Auth;
use Blessing\Filter; use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Event;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Option;
use View;
class PlayerController extends Controller class PlayerController extends Controller
{ {
@ -65,8 +63,9 @@ class PlayerController extends Controller
return Auth::user()->players; return Auth::user()->players;
} }
public function add(Request $request, Dispatcher $dispatcher) public function add(Request $request, Dispatcher $dispatcher, Filter $filter)
{ {
/** @var User */
$user = Auth::user(); $user = Auth::user();
if (option('single_player', false)) { if (option('single_player', false)) {
@ -81,14 +80,20 @@ class PlayerController extends Controller
'max:'.option('player_name_length_max'), 'max:'.option('player_name_length_max'),
], ],
])['name']; ])['name'];
$name = $filter->apply('new_player_name', $name);
$dispatcher->dispatch('player.add.attempt', [$name, $user]); $dispatcher->dispatch('player.add.attempt', [$name, $user]);
if (!Player::where('name', $name)->get()->isEmpty()) { $can = $filter->apply('can_add_player', true, [$name]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
if (Player::where('name', $name)->count() > 0) {
return json(trans('user.player.add.repeated'), 6); return json(trans('user.player.add.repeated'), 6);
} }
if ($user->score < Option::get('score_per_player')) { if ($user->score < (int) option('score_per_player')) {
return json(trans('user.player.add.lack-score'), 7); return json(trans('user.player.add.lack-score'), 7);
} }
@ -102,7 +107,7 @@ class PlayerController extends Controller
$player->tid_cape = 0; $player->tid_cape = 0;
$player->save(); $player->save();
$user->score -= option('score_per_player'); $user->score -= (int) option('score_per_player');
$user->save(); $user->save();
$dispatcher->dispatch('player.added', [$player, $user]); $dispatcher->dispatch('player.added', [$player, $user]);
@ -111,14 +116,20 @@ class PlayerController extends Controller
return json(trans('user.player.add.success', ['name' => $name]), 0, $player->toArray()); return json(trans('user.player.add.success', ['name' => $name]), 0, $player->toArray());
} }
public function delete(Dispatcher $dispatcher, $pid) public function delete(Dispatcher $dispatcher, Filter $filter, $pid)
{ {
/** @var User */
$user = auth()->user(); $user = auth()->user();
$player = Player::find($pid); $player = Player::find($pid);
$playerName = $player->name; $playerName = $player->name;
$dispatcher->dispatch('player.delete.attempt', [$player, $user]); $dispatcher->dispatch('player.delete.attempt', [$player, $user]);
$can = $filter->apply('can_delete_player', true, [$player]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
if (option('single_player', false)) { if (option('single_player', false)) {
return json(trans('user.player.delete.single'), 1); return json(trans('user.player.delete.single'), 1);
} }
@ -129,7 +140,7 @@ class PlayerController extends Controller
$player->delete(); $player->delete();
if (option('return_score')) { if (option('return_score')) {
$user->score += option('score_per_player'); $user->score += (int) option('score_per_player');
$user->save(); $user->save();
} }
@ -145,7 +156,7 @@ class PlayerController extends Controller
Filter $filter, Filter $filter,
$pid $pid
) { ) {
$newName = $request->validate([ $name = $request->validate([
'name' => [ 'name' => [
'required', 'required',
new Rules\PlayerName(), new Rules\PlayerName(),
@ -153,39 +164,55 @@ class PlayerController extends Controller
'max:'.option('player_name_length_max'), 'max:'.option('player_name_length_max'),
], ],
])['name']; ])['name'];
$name = $filter->apply('new_player_name', $name);
$player = Player::find($pid); $player = Player::find($pid);
$dispatcher->dispatch('player.renaming', [$player, $newName]); $dispatcher->dispatch('player.renaming', [$player, $name]);
$can = $filter->apply('user_can_rename_player', true, [$player, $newName]); $can = $filter->apply('user_can_rename_player', true, [$player, $name]);
if ($can instanceof Rejection) { if ($can instanceof Rejection) {
return json($can->getReason(), 1); return json($can->getReason(), 1);
} }
if (!Player::where('name', $newName)->get()->isEmpty()) { if (Player::where('name', $name)->count() > 0) {
return json(trans('user.player.rename.repeated'), 6); return json(trans('user.player.rename.repeated'), 6);
} }
$oldName = $player->name; $old = $player->replicate();
$player->name = $newName; $player->name = $name;
$player->save(); $player->save();
if (option('single_player', false)) { if (option('single_player', false)) {
/** @var User */
$user = auth()->user(); $user = auth()->user();
$user->nickname = $newName; $user->nickname = $name;
$user->save(); $user->save();
} }
$dispatcher->dispatch('player.renamed', [$player, $oldName]); $dispatcher->dispatch('player.renamed', [$player, $old]);
return json(trans('user.player.rename.success', ['old' => $oldName, 'new' => $newName]), 0, $player->toArray()); return json(
trans('user.player.rename.success', ['old' => $old->name, 'new' => $name]),
0,
$player->toArray()
);
} }
public function setTexture(Request $request, Dispatcher $dispatcher, $pid) public function setTexture(
{ Request $request,
Dispatcher $dispatcher,
Filter $filter,
$pid
) {
$player = Player::find($pid); $player = Player::find($pid);
foreach (['skin', 'cape'] as $type) { foreach (['skin', 'cape'] as $type) {
$tid = $request->input($type); $tid = $request->input($type);
$can = $filter->apply('can_set_texture', true, [$player, $type, $tid]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
if ($tid) { if ($tid) {
$texture = Texture::find($tid); $texture = Texture::find($tid);
if (!$texture) { if (!$texture) {
@ -205,12 +232,21 @@ class PlayerController extends Controller
return json(trans('user.player.set.success', ['name' => $player->name]), 0, $player->toArray()); return json(trans('user.player.set.success', ['name' => $player->name]), 0, $player->toArray());
} }
public function clearTexture(Request $request, Dispatcher $dispatcher, $pid) public function clearTexture(
{ Request $request,
Dispatcher $dispatcher,
Filter $filter,
$pid
) {
$player = Player::find($pid); $player = Player::find($pid);
$types = $request->input('type', []); $types = $request->input('type', []);
foreach (['skin', 'cape'] as $type) { foreach (['skin', 'cape'] as $type) {
$can = $filter->apply('can_clear_texture', true, [$player, $type]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
if ($request->has($type) || in_array($type, $types)) { if ($request->has($type) || in_array($type, $types)) {
$dispatcher->dispatch('player.texture.resetting', [$player, $type]); $dispatcher->dispatch('player.texture.resetting', [$player, $type]);
@ -235,10 +271,11 @@ class PlayerController extends Controller
'max:'.option('player_name_length_max'), 'max:'.option('player_name_length_max'),
], ],
])['player']; ])['player'];
/** @var User */
$user = Auth::user(); $user = Auth::user();
$player = Player::where('name', $name)->first(); $player = Player::where('name', $name)->first();
if (!$player) { if (empty($player)) {
$dispatcher->dispatch('player.adding', [$name, $user]); $dispatcher->dispatch('player.adding', [$name, $user]);
event(new PlayerWillBeAdded($name)); event(new PlayerWillBeAdded($name));

View File

@ -6,7 +6,6 @@ use App\Events;
use App\Models\Player; use App\Models\Player;
use App\Models\Texture; use App\Models\Texture;
use App\Models\User; use App\Models\User;
use Blessing\Filter;
use Blessing\Rejection; use Blessing\Rejection;
use Event; use Event;
use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\DatabaseTransactions;
@ -41,6 +40,7 @@ class PlayerControllerTest extends TestCase
public function testAdd() public function testAdd()
{ {
Event::fake(); Event::fake();
$filter = Fakes\Filter::fake();
// Without player name // Without player name
$this->postJson('/user/player/add')->assertJsonValidationErrors('name'); $this->postJson('/user/player/add')->assertJsonValidationErrors('name');
@ -54,7 +54,7 @@ class PlayerControllerTest extends TestCase
// Custom player name rule (regexp) // Custom player name rule (regexp)
option(['player_name_rule' => 'custom']); option(['player_name_rule' => 'custom']);
option(['custom_player_name_regexp' => '/^([0-9]+)$/']); option(['custom_player_name_regexp' => '/^\d+$/']);
$this->postJson( $this->postJson(
'/user/player/add', '/user/player/add',
['name' => 'yjsnpi'] ['name' => 'yjsnpi']
@ -70,6 +70,11 @@ class PlayerControllerTest extends TestCase
'code' => 7, 'code' => 7,
'message' => trans('user.player.add.lack-score'), 'message' => trans('user.player.add.lack-score'),
]); ]);
$filter->assertApplied('new_player_name', function ($name) {
$this->assertEquals('no_score', $name);
return true;
});
Event::assertDispatched('player.add.attempt', function ($event, $payload) use ($user) { Event::assertDispatched('player.add.attempt', function ($event, $payload) use ($user) {
$this->assertEquals('no_score', $payload[0]); $this->assertEquals('no_score', $payload[0]);
$this->assertEquals($user->uid, $payload[1]->uid); $this->assertEquals($user->uid, $payload[1]->uid);
@ -79,8 +84,24 @@ class PlayerControllerTest extends TestCase
Event::assertNotDispatched('player.adding'); Event::assertNotDispatched('player.adding');
Event::assertNotDispatched('player.added'); Event::assertNotDispatched('player.added');
// rejected
Event::fake();
$filter->add('can_add_player', function ($can, $name) {
$this->assertEquals('can', $name);
return new Rejection('rejected');
});
$this->postJson(
'/user/player/add',
['name' => 'can']
)->assertJson(['code' => 1, 'message' => 'rejected']);
Event::assertDispatched('player.add.attempt');
Event::assertNotDispatched('player.adding');
Event::assertNotDispatched('player.added');
// Allowed to use CJK characters // Allowed to use CJK characters
Event::fake(); Event::fake();
Fakes\Filter::fake();
option(['player_name_rule' => 'cjk']); option(['player_name_rule' => 'cjk']);
$user = factory(User::class)->create(); $user = factory(User::class)->create();
$score = $user->score; $score = $user->score;
@ -141,12 +162,25 @@ class PlayerControllerTest extends TestCase
public function testDelete() public function testDelete()
{ {
Event::fake(); Event::fake();
$filter = Fakes\Filter::fake();
$user = factory(User::class)->create(); $user = factory(User::class)->create();
$player = factory(Player::class)->create(['uid' => $user->uid]); $player = factory(Player::class)->create(['uid' => $user->uid]);
$score = $user->score; $score = $user->score;
// rejected
$filter->add('can_delete_player', function ($can, $p) use ($player) {
$this->assertTrue($player->is($p));
return new Rejection('rejected');
});
$this->actingAs($user) $this->actingAs($user)
->postJson('/user/player/delete/'.$player->pid) ->postJson('/user/player/delete/'.$player->pid)
->assertJson(['code' => 1, 'message' => 'rejected']);
// success
$filter = Fakes\Filter::fake();
$this->postJson('/user/player/delete/'.$player->pid)
->assertJson([ ->assertJson([
'code' => 0, 'code' => 0,
'message' => trans('user.player.delete.success', ['name' => $player->name]), 'message' => trans('user.player.delete.success', ['name' => $player->name]),
@ -207,6 +241,7 @@ class PlayerControllerTest extends TestCase
public function testRename() public function testRename()
{ {
Event::fake(); Event::fake();
$filter = Fakes\Filter::fake();
$player = factory(Player::class)->create(); $player = factory(Player::class)->create();
$user = $player->user; $user = $player->user;
@ -233,12 +268,17 @@ class PlayerControllerTest extends TestCase
'message' => trans('user.player.rename.repeated'), 'message' => trans('user.player.rename.repeated'),
]); ]);
Event::assertDispatched('player.renaming'); Event::assertDispatched('player.renaming');
$filter->assertApplied('new_player_name', function ($newName) use ($name) {
$this->assertEquals($name, $newName);
return true;
});
// Rejected by filter // Rejected by filter
$filter = resolve(Filter::class); $filter = Fakes\Filter::fake();
$pid = $player->pid; $filter->add('user_can_rename_player', function ($can, $p, $name) use ($player) {
$filter->add('user_can_rename_player', function ($can, $player, $newName) { $this->assertTrue($player->is($p));
$this->assertEquals('new', $newName); $this->assertEquals('new', $name);
return new Rejection('rejected'); return new Rejection('rejected');
}); });
@ -252,6 +292,7 @@ class PlayerControllerTest extends TestCase
// Success // Success
Event::fake(); Event::fake();
$pid = $player->pid;
$this->postJson('/user/player/rename/'.$pid, ['name' => 'new_name']) $this->postJson('/user/player/rename/'.$pid, ['name' => 'new_name'])
->assertJson([ ->assertJson([
'code' => 0, 'code' => 0,
@ -268,10 +309,9 @@ class PlayerControllerTest extends TestCase
return true; return true;
}); });
Event::assertDispatched('player.renamed', function ($event, $payload) use ($pid) { Event::assertDispatched('player.renamed', function ($event, $payload) use ($player) {
[$player, $oldName] = $payload; $this->assertTrue($player->fresh()->is($payload[0]));
$this->assertEquals($pid, $player->pid); $this->assertNotEquals('new_name', $payload[1]->name);
$this->assertNotEquals('new_name', $oldName);
return true; return true;
}); });
@ -290,9 +330,22 @@ class PlayerControllerTest extends TestCase
$skin = factory(Texture::class)->create(); $skin = factory(Texture::class)->create();
$cape = factory(Texture::class)->states('cape')->create(); $cape = factory(Texture::class)->states('cape')->create();
// Set a not-existed texture // rejected
$filter = Fakes\Filter::fake();
$filter->add('can_set_texture', function ($can, $p, $type, $tid) use ($player) {
$this->assertTrue($player->is($p));
$this->assertEquals('skin', $type);
$this->assertEquals(-1, $tid);
return new Rejection('rejected');
});
$this->actingAs($user) $this->actingAs($user)
->postJson('/user/player/set/'.$player->pid, ['skin' => -1]) ->postJson('/user/player/set/'.$player->pid, ['skin' => -1])
->assertJson(['code' => 1, 'message' => 'rejected']);
// Set a not-existed texture
Fakes\Filter::fake();
$this->postJson('/user/player/set/'.$player->pid, ['skin' => -1])
->assertJson([ ->assertJson([
'code' => 1, 'code' => 1,
'message' => trans('skinlib.non-existent'), 'message' => trans('skinlib.non-existent'),
@ -341,11 +394,24 @@ class PlayerControllerTest extends TestCase
$player->save(); $player->save();
$player->refresh(); $player->refresh();
// rejected
$filter = Fakes\Filter::fake();
$filter->add('can_clear_texture', function ($can, $p, $type) use ($player) {
$this->assertTrue($player->is($p));
$this->assertEquals('skin', $type);
return new Rejection('rejected');
});
$this->actingAs($user) $this->actingAs($user)
->postJson('/user/player/texture/clear/'.$player->pid, [ ->postJson('/user/player/texture/clear/'.$player->pid, ['skin' => true])
'skin' => 1, // "1" stands for "true" ->assertJson(['code' => 1, 'message' => 'rejected']);
'cape' => 1,
'nope' => 1, // Invalid texture type is acceptable // success
Fakes\Filter::fake();
$this->postJson('/user/player/texture/clear/'.$player->pid, [
'skin' => true, // "1" stands for "true"
'cape' => true,
'nope' => true, // invalid texture type is acceptable
])->assertJson([ ])->assertJson([
'code' => 0, 'code' => 0,
'message' => trans('user.player.clear.success', ['name' => $player->name]), 'message' => trans('user.player.clear.success', ['name' => $player->name]),