add more events and filters for SkinlibController

This commit is contained in:
Pig Fang 2020-06-07 10:11:35 +08:00
parent b9a40af92d
commit 73c529c152
12 changed files with 465 additions and 147 deletions

View File

@ -120,6 +120,7 @@ class ReportController extends Controller
/** @var Texture */
$texture = $report->texture;
if ($texture) {
$dispatcher->dispatch('texture.deleting', [$texture]);
Storage::disk('textures')->delete($texture->hash);
$texture->delete();
$dispatcher->dispatch('texture.deleted', [$texture]);

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
use Auth;
@ -302,8 +301,15 @@ class SkinlibController extends Controller
]);
}
public function delete(Texture $texture)
public function delete(Texture $texture, Dispatcher $dispatcher, Filter $filter)
{
$can = $filter->apply('can_delete_texture', true, [$texture]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
$dispatcher->dispatch('texture.deleting', [$texture]);
// check if file occupied
if (Texture::where('hash', $texture->hash)->count() === 1) {
Storage::disk('textures')->delete($texture->hash);
@ -311,12 +317,19 @@ class SkinlibController extends Controller
$texture->delete();
$dispatcher->dispatch('texture.deleted', [$texture]);
return json(trans('skinlib.delete.success'), 0);
}
public function privacy(Texture $texture)
public function privacy(Texture $texture, Dispatcher $dispatcher, Filter $filter)
{
$uploader = User::find($texture->uploader);
$can = $filter->apply('can_update_texture_privacy', true, [$texture]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
$uploader = $texture->owner;
$score_diff = $texture->size
* (option('private_score_per_storage') - option('score_per_storage'))
* ($texture->public ? -1 : 1);
@ -327,19 +340,7 @@ class SkinlibController extends Controller
return json(trans('skinlib.upload.lack-score'), 1);
}
$type = $texture->type == 'cape' ? 'cape' : 'skin';
Player::where("tid_$type", $texture->tid)
->where('uid', '<>', session('uid'))
->update(["tid_$type" => 0]);
$texture->likers()->get()->each(function ($user) use ($texture) {
$user->closet()->detach($texture->tid);
if (option('return_score')) {
$user->score += option('score_per_closet_item');
$user->save();
}
$texture->likes--;
});
$dispatcher->dispatch('texture.privacy.updating', [$texture]);
$uploader->score += $score_diff;
$uploader->save();
@ -347,6 +348,8 @@ class SkinlibController extends Controller
$texture->public = !$texture->public;
$texture->save();
$dispatcher->dispatch('texture.privacy.updated', [$texture]);
$message = trans('skinlib.privacy.success', [
'privacy' => (
$texture->public
@ -357,8 +360,12 @@ class SkinlibController extends Controller
return json($message, 0);
}
public function rename(Request $request, Texture $texture)
{
public function rename(
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture
) {
$data = $request->validate(['name' => [
'required',
option('texture_name_regexp')
@ -367,22 +374,46 @@ class SkinlibController extends Controller
]]);
$name = $data['name'];
$can = $filter->apply('can_update_texture_name', true, [$texture, $name]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
$dispatcher->dispatch('texture.name.updating', [$texture, $name]);
$old = $texture->replicate();
$texture->name = $name;
$texture->save();
$dispatcher->dispatch('texture.name.updated', [$texture, $old]);
return json(trans('skinlib.rename.success', ['name' => $name]), 0);
}
public function type(Request $request, Texture $texture)
{
public function type(
Request $request,
Dispatcher $dispatcher,
Filter $filter,
Texture $texture
) {
$data = $request->validate([
'type' => ['required', Rule::in(['steve', 'alex', 'cape'])],
]);
$type = $data['type'];
$can = $filter->apply('can_update_texture_type', true, [$texture, $type]);
if ($can instanceof Rejection) {
return json($can->getReason(), 1);
}
$dispatcher->dispatch('texture.type.updating', [$texture, $type]);
$old = $texture->replicate();
$texture->type = $type;
$texture->save();
$dispatcher->dispatch('texture.type.updated', [$texture, $old]);
return json(trans('skinlib.model.success', ['model' => $type]), 0);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Listeners;
use App\Models\Texture;
use App\Models\User;
class CleanUpCloset
{
public function handle(Texture $texture)
{
// no need to update users' closet
// if texture was switched from "private" to "public"
if ($texture->exists && $texture->public) {
return;
}
$likers = $texture->likers()->get();
$likers->each(function (User $user) use ($texture) {
$user->closet()->detach($texture->tid);
if (option('return_score')) {
$user->score += (int) option('score_per_closet_item');
$user->save();
}
});
if ($texture->exists) {
$texture->decrement('likes', $likers->count());
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Listeners;
use App\Models\Player;
use App\Models\Texture;
class ResetPlayers
{
public function handle(Texture $texture)
{
// no need to update players
// if texture was switched from "private" to "public"
if ($texture->exists && $texture->public) {
return;
}
$type = $texture->type == 'cape' ? 'tid_cape' : 'tid_skin';
$query = Player::where($type, $texture->tid);
// texture was switched from "private" to "public"
if ($texture->exists) {
$query = $query->where('uid', '<>', $texture->uploader);
}
$query->update([$type => 0]);
}
}

View File

@ -1,39 +0,0 @@
<?php
namespace App\Listeners;
use App\Models\User;
class TextureRemoved
{
public function handle($event)
{
$texture = $event->texture;
$texture->likers()->get()->each(function ($user) use ($texture) {
$user->closet()->detach($texture->tid);
if (option('return_score')) {
$user->score += option('score_per_closet_item');
$user->save();
}
});
if ($uploader = User::find($texture->uploader)) {
$ret = 0;
if (option('return_score')) {
$ret += $texture->size * (
$texture->public
? option('score_per_storage')
: option('private_score_per_storage')
);
}
if ($texture->public && option('take_back_scores_after_deletion', true)) {
$ret -= option('score_award_per_texture', 0);
}
$uploader->score += $ret;
$uploader->save();
}
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Listeners;
class UpdateScoreForDeletedTexture
{
public function handle($texture)
{
$uploader = $texture->owner;
if ($uploader) {
$ret = 0;
if (option('return_score')) {
$ret += $texture->size * (
$texture->public
? (int) option('score_per_storage')
: (int) option('private_score_per_storage')
);
}
if ($texture->public && option('take_back_scores_after_deletion', true)) {
$ret -= (int) option('score_award_per_texture', 0);
}
$uploader->score += $ret;
$uploader->save();
}
}
}

View File

@ -12,9 +12,6 @@ class EventServiceProvider extends ServiceProvider
'App\Events\PlayerRetrieved' => [
Listeners\ResetInvalidTextureForPlayer::class,
],
'App\Events\TextureDeleting' => [
Listeners\TextureRemoved::class,
],
'App\Events\PluginWasEnabled' => [
Listeners\CopyPluginAssets::class,
Listeners\GeneratePluginTranslations::class,
@ -32,5 +29,14 @@ class EventServiceProvider extends ServiceProvider
'auth.registration.completed' => [
Listeners\SendEmailVerification::class,
],
'texture.privacy.updated' => [
Listeners\ResetPlayers::class,
Listeners\CleanUpCloset::class,
],
'texture.deleted' => [
Listeners\UpdateScoreForDeletedTexture::class,
Listeners\ResetPlayers::class,
Listeners\CleanUpCloset::class,
],
];
}

View File

@ -281,6 +281,12 @@ class ReportControllerTest extends TestCase
return true;
});
Event::assertDispatched('texture.deleting', function ($event, $payload) use ($tid) {
[$texture] = $payload;
$this->assertEquals($tid, $texture->tid);
return true;
});
Event::assertDispatched('texture.deleted', function ($event, $payload) use ($tid) {
[$texture] = $payload;
$this->assertEquals($tid, $texture->tid);

View File

@ -2,7 +2,6 @@
namespace Tests;
use App\Models\Player;
use App\Models\Texture;
use App\Models\User;
use Blessing\Rejection;
@ -469,13 +468,12 @@ class SkinlibControllerTest extends TestCase
public function testDelete()
{
Event::fake();
/** @var FilesystemAdapter */
$disk = Storage::fake('textures');
$uploader = factory(User::class)->create();
$other = factory(User::class)->create();
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
option(['return_score' => false]);
$duplicate = factory(Texture::class)->create([
'hash' => $texture->hash,
@ -492,6 +490,33 @@ class SkinlibControllerTest extends TestCase
]);
$this->assertNull(Texture::find($duplicate->tid));
$disk->assertExists($texture->hash);
Event::assertDispatched(
'texture.deleting',
function ($eventName, $payload) use ($duplicate) {
$this->assertTrue($duplicate->is($payload[0]));
return true;
}
);
Event::assertDispatched(
'texture.deleted',
function ($eventName, $payload) use ($duplicate) {
$this->assertTrue($duplicate->is($payload[0]));
return true;
}
);
// rejected
$filter = Fakes\Filter::fake();
$filter->add('can_delete_texture', function ($can, $t) use ($texture) {
$this->assertTrue($texture->is($t));
return new Rejection('rejected');
});
$this->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson(['code' => 1, 'message' => 'rejected']);
$filter->remove('can_delete_texture');
$this->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson([
@ -500,81 +525,11 @@ class SkinlibControllerTest extends TestCase
]);
$this->assertNull(Texture::find($texture->tid));
$disk->assertMissing($texture->hash);
// return score
option(['return_score' => true]);
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson([
'code' => 0,
'message' => trans('skinlib.delete.success'),
]);
$this->assertEquals(
$uploader->score + $texture->size * option('score_per_storage'),
$uploader->fresh()->score
);
$uploader->refresh();
$texture = factory(Texture::class)->create([
'uploader' => $uploader->uid,
'public' => false,
]);
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson([
'code' => 0,
'message' => trans('skinlib.delete.success'),
]);
$this->assertEquals(
$uploader->score + $texture->size * option('private_score_per_storage'),
$uploader->fresh()->score
);
option(['return_score' => false]);
// return the award
option(['score_award_per_texture' => 5]);
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$uploader->refresh();
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson(['code' => 0]);
$this->assertEquals($uploader->score - 5, $uploader->fresh()->score);
// option disabled
option(['take_back_scores_after_deletion' => false]);
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$uploader->refresh();
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson(['code' => 0]);
$this->assertEquals($uploader->score, $uploader->fresh()->score);
// private texture
$texture = factory(Texture::class)->create([
'uploader' => $uploader->uid,
'public' => false,
]);
$uploader->refresh();
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson(['code' => 0]);
$this->assertEquals($uploader->score, $uploader->fresh()->score);
// remove from closet
option(['return_score' => true]);
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$other->closet()->attach($texture->tid, ['item_name' => 'a']);
$other->score = 0;
$other->save();
$this->actingAs($uploader)
->deleteJson(route('texture.delete', ['texture' => $texture]))
->assertJson(['code' => 0]);
$other->refresh();
$this->assertEquals(option('score_per_closet_item'), $other->score);
}
public function testPrivacy()
{
Event::fake();
$uploader = factory(User::class)->create();
$other = factory(User::class)->create();
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
@ -591,11 +546,24 @@ class SkinlibControllerTest extends TestCase
]);
$this->assertTrue($texture->fresh()->public);
// rejected
$filter = Fakes\Filter::fake();
$filter->add('can_update_texture_privacy', function ($can, $t) use ($texture) {
$this->assertTrue($texture->fresh()->is($t));
return new Rejection('rejected');
});
$this->actingAs($uploader)
->putJson(route('texture.privacy', ['texture' => $texture]))
->assertJson(['code' => 1, 'message' => 'rejected']);
$filter->remove('can_update_texture_privacy');
$texture->public = true;
$texture->save();
$uploader->score = $texture->size *
(option('private_score_per_storage') - option('score_per_storage'));
$uploader->save();
$replicated = $texture->replicate();
$this->putJson(route('texture.privacy', ['texture' => $texture]))
->assertJson([
'code' => 0,
@ -603,26 +571,34 @@ class SkinlibControllerTest extends TestCase
]);
$this->assertEquals(0, $uploader->fresh()->score);
$this->assertFalse($texture->fresh()->public);
Event::assertDispatched(
'texture.privacy.updating',
function ($eventName, $payload) use ($replicated) {
$this->assertInstanceOf(Texture::class, $payload[0]);
$this->assertTrue($replicated->public);
return true;
}
);
Event::assertDispatched(
'texture.privacy.updated',
function ($eventName, $payload) {
$this->assertFalse($payload[0]->public);
return true;
}
);
// When setting a texture to be private,
// other players should not be able to use it.
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$uploader->score += $texture->size * option('private_score_per_storage');
$uploader->save();
$player = factory(Player::class)->create(['tid_skin' => $texture->tid]);
$other = factory(User::class)->create();
$other->closet()->attach($texture->tid, ['item_name' => 'a']);
$this->putJson(route('texture.privacy', ['texture' => $texture]))
->assertJson([
'code' => 0,
'message' => trans('skinlib.privacy.success', ['privacy' => trans('general.private')]),
]);
$this->assertEquals(0, $player->fresh()->tid_skin);
$this->assertEquals(0, $other->closet()->count());
$this->assertEquals(
$other->score + option('score_per_closet_item'),
$other->fresh()->score
);
// take back the score
option(['score_award_per_texture' => 5]);
@ -650,6 +626,7 @@ class SkinlibControllerTest extends TestCase
public function testRename()
{
Event::fake();
$uploader = factory(User::class)->create();
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
@ -675,13 +652,47 @@ class SkinlibControllerTest extends TestCase
'message' => trans('skinlib.rename.success', ['name' => 'abc']),
]);
$this->assertEquals('abc', $texture->fresh()->name);
Event::assertDispatched(
'texture.name.updating',
function ($eventName, $payload) use ($texture) {
$this->assertTrue($texture->is($payload[0]));
$this->assertEquals('abc', $payload[1]);
return true;
}
);
Event::assertDispatched(
'texture.name.updated',
function ($eventName, $payload) use ($texture) {
$this->assertTrue($texture->fresh()->is($payload[0]));
$this->assertEquals($texture->name, $payload[1]->name);
return true;
}
);
// rejected
$filter = Fakes\Filter::fake();
$filter->add('can_update_texture_name', function ($can, $t, $name) use ($texture) {
$this->assertTrue($texture->is($t));
$this->assertEquals('abc', $name);
return new Rejection('rejected');
});
$this->putJson(
route('texture.name', ['texture' => $texture]),
['name' => 'abc']
)->assertJson(['code' => 1, 'message' => 'rejected']);
}
public function testType()
{
Event::fake();
$uploader = factory(User::class)->create();
$other = factory(User::class)->create();
$texture = factory(Texture::class)->create(['uploader' => $uploader->uid]);
$texture = factory(Texture::class)
->states('alex')
->create(['uploader' => $uploader->uid]);
// missing `type` field
$this->actingAs($uploader)
@ -703,6 +714,24 @@ class SkinlibControllerTest extends TestCase
'message' => trans('skinlib.model.success', ['model' => 'steve']),
]);
$this->assertEquals('steve', $texture->fresh()->type);
Event::assertDispatched(
'texture.type.updating',
function ($eventName, $payload) use ($texture) {
$this->assertTrue($texture->is($payload[0]));
$this->assertEquals('steve', $payload[1]);
return true;
}
);
Event::assertDispatched(
'texture.type.updated',
function ($eventName, $payload) use ($texture) {
$this->assertTrue($texture->fresh()->is($payload[0]));
$this->assertEquals('alex', $payload[1]->type);
return true;
}
);
$duplicate = factory(Texture::class)->states('alex')->create([
'uploader' => $other->uid,
@ -719,5 +748,18 @@ class SkinlibControllerTest extends TestCase
'code' => 0,
'message' => trans('skinlib.model.success', ['model' => 'alex']),
]);
// rejected
$filter = Fakes\Filter::fake();
$filter->add('can_update_texture_type', function ($can, $t, $type) use ($texture) {
$this->assertTrue($texture->is($t));
$this->assertEquals('steve', $type);
return new Rejection('rejected');
});
$this->putJson(
route('texture.type', ['texture' => $texture]),
['type' => 'steve']
)->assertJson(['code' => 1, 'message' => 'rejected']);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace Tests;
use App\Models\Texture;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CleanUpClosetTest extends TestCase
{
use DatabaseTransactions;
public function testPublicTexture()
{
option(['return_score' => true]);
$texture = factory(Texture::class)->create();
$user = factory(User::class)->create(['score' => 0]);
$user->closet()->attach($texture->tid, ['item_name' => '']);
event('texture.privacy.updated', [$texture]);
$user->refresh();
$this->assertEquals($texture->likes, $texture->fresh()->likes);
$this->assertEquals(0, $user->score);
}
public function testPrivateTexture()
{
option(['return_score' => true]);
$texture = factory(Texture::class)->create(['public' => false]);
$user = factory(User::class)->create(['score' => 0]);
$user->closet()->attach($texture->tid, ['item_name' => '']);
$replicated = $texture->replicate();
event('texture.privacy.updated', [$texture]);
$user->refresh();
$this->assertEquals($replicated->likes - 1, $texture->fresh()->likes);
$this->assertEquals((int) option('score_per_closet_item'), $user->score);
$this->assertNull($user->closet()->find($texture->tid));
}
public function testDeletedTexture()
{
option(['return_score' => true]);
$texture = factory(Texture::class)->create();
$user = factory(User::class)->create(['score' => 0]);
$user->closet()->attach($texture->tid, ['item_name' => '']);
$texture->delete();
event('texture.deleted', [$texture]);
$user->refresh();
$this->assertEquals((int) option('score_per_closet_item'), $user->score);
$this->assertNull($user->closet()->find($texture->tid));
}
}

View File

@ -0,0 +1,57 @@
<?php
namespace Tests;
use App\Models\Player;
use App\Models\Texture;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ResetPlayersTest extends TestCase
{
use DatabaseTransactions;
public function testPublicTexture()
{
$texture = factory(Texture::class)->create();
$player = factory(Player::class)->create(['tid_skin' => $texture->tid]);
event('texture.privacy.updated', [$texture]);
$player->refresh();
$this->assertEquals($texture->tid, $player->tid_skin);
}
public function testPrivateTexture()
{
$texture = factory(Texture::class)->create(['public' => false]);
$player = factory(Player::class)->create(['tid_skin' => $texture->tid]);
$playerOfUploader = factory(Player::class)->create([
'uid' => $texture->uploader,
'tid_skin' => $texture->tid,
]);
event('texture.privacy.updated', [$texture]);
$player->refresh();
$playerOfUploader->refresh();
$texture->refresh();
$this->assertEquals(0, $player->tid_skin);
$this->assertEquals($texture->tid, $playerOfUploader->tid_skin);
}
public function testDeletedTexture()
{
$texture = factory(Texture::class)->create();
$player = factory(Player::class)->create(['tid_skin' => $texture->tid]);
$playerOfUploader = factory(Player::class)->create([
'uid' => $texture->uploader,
'tid_skin' => $texture->tid,
]);
$texture->delete();
event('texture.deleted', [$texture]);
$player->refresh();
$playerOfUploader->refresh();
$texture->refresh();
$this->assertEquals(0, $player->tid_skin);
$this->assertEquals(0, $playerOfUploader->tid_skin);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Tests;
use App\Models\Texture;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UpdateScoreForDeletedTextureTest extends TestCase
{
use DatabaseTransactions;
public function testPublicTexture()
{
option(['return_score' => true]);
$texture = factory(Texture::class)->create();
$uploader = $texture->owner->replicate();
event('texture.deleted', [$texture]);
$this->assertEquals(
$uploader->score + $texture->size * (int) option('score_per_storage'),
$texture->owner->fresh()->score
);
}
public function testPrivateTexture()
{
option(['return_score' => true]);
$texture = factory(Texture::class)->create(['public' => false]);
$uploader = $texture->owner->replicate();
event('texture.deleted', [$texture]);
$this->assertEquals(
$uploader->score + $texture->size * (int) option('private_score_per_storage'),
$texture->owner->fresh()->score
);
}
public function testTakeBackAwardOfPublicTexture()
{
option([
'score_award_per_texture' => 5,
'take_back_scores_after_deletion' => true,
'score_per_storage' => 0,
]);
$texture = factory(Texture::class)->create();
$uploader = $texture->owner->replicate();
event('texture.deleted', [$texture]);
$this->assertEquals(
$uploader->score - 5,
$texture->owner->fresh()->score
);
}
public function testTakeBackAwardOfPrivateTexture()
{
option([
'score_award_per_texture' => 5,
'take_back_scores_after_deletion' => true,
'private_score_per_storage' => 0,
]);
$texture = factory(Texture::class)->create(['public' => false]);
$uploader = $texture->owner->replicate();
event('texture.deleted', [$texture]);
$this->assertEquals(
$uploader->score,
$texture->owner->fresh()->score
);
}
}