From 5915b3ec17b53f303630e8003a66180e4059200d Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Thu, 14 Mar 2019 23:55:49 +0800 Subject: [PATCH] Reimplementing closet --- app/Console/Commands/MigrateCloset.php | 65 ++++ app/Console/Kernel.php | 1 + app/Events/ClosetWasFiltered.php | 15 - app/Events/ClosetWillBeFiltered.php | 15 - app/Http/Controllers/ClosetController.php | 105 +++--- app/Http/Controllers/SetupController.php | 2 +- app/Http/Controllers/SkinlibController.php | 45 +-- app/Models/Closet.php | 268 ---------------- app/Models/Texture.php | 11 +- app/Models/User.php | 23 +- database/factories/ClosetModelFactory.php | 11 - database/factories/TextureModelFactory.php | 3 - .../2016_11_18_133939_create_all_tables.php | 7 - .../2019_03_14_174727_create_closet.php | 32 ++ resources/views/skinlib/show.blade.php | 2 +- tests/ClosetControllerTest.php | 49 +-- tests/MiddlewareTest.php | 2 +- tests/ModelsTest/ClosetTest.php | 51 --- tests/SetupControllerTest.php | 2 +- tests/SkinlibControllerTest.php | 298 ++++++++++-------- 20 files changed, 373 insertions(+), 634 deletions(-) create mode 100644 app/Console/Commands/MigrateCloset.php delete mode 100644 app/Events/ClosetWasFiltered.php delete mode 100644 app/Events/ClosetWillBeFiltered.php delete mode 100644 app/Models/Closet.php delete mode 100644 database/factories/ClosetModelFactory.php create mode 100644 database/migrations/2019_03_14_174727_create_closet.php delete mode 100644 tests/ModelsTest/ClosetTest.php diff --git a/app/Console/Commands/MigrateCloset.php b/app/Console/Commands/MigrateCloset.php new file mode 100644 index 00000000..170ca20f --- /dev/null +++ b/app/Console/Commands/MigrateCloset.php @@ -0,0 +1,65 @@ +info('Nothing to do.'); + return; + } + + $this->info('We will migrate all closets data. Please wait...'); + + $rows = DB::table('closets')->select('*')->get(); + $bar = $this->output->createProgressBar($rows->count()); + + $rows->map(function ($row) use ($bar) { + $closet = User::find($row->uid)->closet(); + collect(json_decode($row->textures, true))->each(function ($item) use ($closet) { + $closet->attach($item['tid'], ['item_name' => $item['name']]); + }); + $bar->advance(); + }); + + Schema::drop('closets'); + $bar->finish(); + $this->info("\nCongrats! Everything are done."); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9058dd75..b7b1b61c 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel Commands\KeyRandomCommand::class, Commands\SaltRandomCommand::class, Commands\MigratePlayersTable::class, + Commands\MigrateCloset::class, ]; /** diff --git a/app/Events/ClosetWasFiltered.php b/app/Events/ClosetWasFiltered.php deleted file mode 100644 index aa8a4036..00000000 --- a/app/Events/ClosetWasFiltered.php +++ /dev/null @@ -1,15 +0,0 @@ -closet = $closet; - } -} diff --git a/app/Events/ClosetWillBeFiltered.php b/app/Events/ClosetWillBeFiltered.php deleted file mode 100644 index a9bc3741..00000000 --- a/app/Events/ClosetWillBeFiltered.php +++ /dev/null @@ -1,15 +0,0 @@ -closet = $closet; - } -} diff --git a/app/Http/Controllers/ClosetController.php b/app/Http/Controllers/ClosetController.php index 89ec510e..a5eddd3a 100644 --- a/app/Http/Controllers/ClosetController.php +++ b/app/Http/Controllers/ClosetController.php @@ -5,29 +5,12 @@ namespace App\Http\Controllers; use View; use Option; use App\Models\User; -use App\Models\Closet; use App\Models\Texture; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class ClosetController extends Controller { - /** - * Instance of Closet. - * - * @var \App\Models\Closet - */ - private $closet; - - public function __construct() - { - $this->middleware(function ($request, $next) { - $this->closet = new Closet(Auth::id()); - - return $next($request); - }); - } - public function index() { return view('user.closet')->with('user', Auth::user()); @@ -37,29 +20,39 @@ class ClosetController extends Controller { $category = $request->input('category', 'skin'); $page = abs($request->input('page', 1)); - $per_page = (int) $request->input('perPage', 6); + $perPage = (int) $request->input('perPage', 6); $q = $request->input('q', null); - $per_page = $per_page > 0 ? $per_page : 6; + $perPage = $perPage > 0 ? $perPage : 6; - $items = collect(); + $user = auth()->user(); + $closet = $user->closet(); - if ($q) { - // Do search - $items = $this->closet->getItems($category)->filter(function ($item) use ($q) { - return stristr($item['name'], $q); - }); + if ($category == 'cape') { + $closet = $closet->where('type', 'cape'); } else { - $items = $this->closet->getItems($category); + $closet = $closet->where(function ($query) { + return $query->where('type', 'steve')->orWhere('type', 'alex'); + }); } + if ($q) { + $closet = $closet->where('item_name', 'like', "%$q%"); + } + + $closet->offset(($page - 1) * $perPage)->limit($perPage); + // Pagination - $total_pages = ceil($items->count() / $per_page); + $items = $closet->get()->map(function ($t) { + $t->name = $t->pivot->item_name; + return $t; + }); + $totalPages = ceil($items->count() / $perPage); return response()->json([ 'category' => $category, - 'items' => $items->forPage($page, $per_page)->values(), - 'total_pages' => $total_pages, + 'items' => $items, + 'total_pages' => $totalPages, ]); } @@ -70,9 +63,9 @@ class ClosetController extends Controller 'name' => 'required|no_special_chars', ]); - $currentUser = Auth::user(); + $user = Auth::user(); - if ($currentUser->getScore() < option('score_per_closet_item')) { + if ($user->getScore() < option('score_per_closet_item')) { return json(trans('user.closet.add.lack-score'), 7); } @@ -81,19 +74,14 @@ class ClosetController extends Controller return json(trans('user.closet.add.not-found'), 1); } - if ($this->closet->add($tid, $request->name)) { - $t = Texture::find($tid); - $t->likes += 1; - $t->save(); - - $this->closet->save(); - - $currentUser->setScore(option('score_per_closet_item'), 'minus'); - - return json(trans('user.closet.add.success', ['name' => $request->input('name')]), 0); - } else { + if ($user->closet()->where('tid', $request->tid)->count() > 0) { return json(trans('user.closet.add.repeated'), 1); } + + $user->closet()->attach($tid, ['item_name' => $request->name]); + $user->setScore(option('score_per_closet_item'), 'minus'); + + return json(trans('user.closet.add.success', ['name' => $request->input('name')]), 0); } public function rename(Request $request) @@ -103,35 +91,34 @@ class ClosetController extends Controller 'new_name' => 'required|no_special_chars', ]); - if ($this->closet->rename($request->tid, $request->new_name)) { - $this->closet->save(); + $user = auth()->user(); - return json(trans('user.closet.rename.success', ['name' => $request->new_name]), 0); - } else { + if ($user->closet()->where('tid', $request->tid)->count() == 0) { return json(trans('user.closet.remove.non-existent'), 1); } + + $user->closet()->updateExistingPivot($request->tid, ['item_name' => $request->new_name]); + return json(trans('user.closet.rename.success', ['name' => $request->new_name]), 0); } public function remove(Request $request) { $this->validate($request, [ - 'tid' => 'required|integer', + 'tid' => 'required|integer', ]); - if ($this->closet->remove($request->tid)) { - $t = Texture::find($request->tid); - $t->likes = $t->likes - 1; - $t->save(); + $user = auth()->user(); - $this->closet->save(); - - if (option('return_score')) { - Auth::user()->setScore(option('score_per_closet_item'), 'plus'); - } - - return json(trans('user.closet.remove.success'), 0); - } else { + if ($user->closet()->where('tid', $request->tid)->count() == 0) { return json(trans('user.closet.remove.non-existent'), 1); } + + $user->closet()->detach($request->tid); + + if (option('return_score')) { + $user->setScore(option('score_per_closet_item'), 'plus'); + } + + return json(trans('user.closet.remove.success'), 0); } } diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index 3cb3255a..61c5ed3d 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -252,7 +252,7 @@ class SetupController extends Controller } $existingTables = []; - $tables = $tables ?: ['users', 'closets', 'players', 'textures', 'options']; + $tables = $tables ?: ['users', 'user_closet', 'players', 'textures', 'options']; foreach ($tables as $tableName) { // Table prefix will be added automatically diff --git a/app/Http/Controllers/SkinlibController.php b/app/Http/Controllers/SkinlibController.php index b88c7449..3dfd79eb 100644 --- a/app/Http/Controllers/SkinlibController.php +++ b/app/Http/Controllers/SkinlibController.php @@ -7,7 +7,6 @@ use Option; use Session; use Storage; use App\Models\User; -use App\Models\Closet; use App\Models\Player; use App\Models\Texture; use Illuminate\Http\Request; @@ -48,7 +47,7 @@ class SkinlibController extends Controller */ public function getSkinlibFiltered(Request $request) { - $currentUser = Auth::user(); + $user = Auth::user(); // Available filters: skin, steve, alex, cape $filter = $request->input('filter', 'skin'); @@ -74,7 +73,7 @@ class SkinlibController extends Controller if ($filter == 'skin') { $query = Texture::where(function ($innerQuery) { // Nested condition, DO NOT MODIFY - $innerQuery->where('type', '=', 'steve')->orWhere('type', '=', 'alex'); + $innerQuery->where('type', 'steve')->orWhere('type', 'alex'); }); } else { $query = Texture::where('type', $filter); @@ -88,14 +87,14 @@ class SkinlibController extends Controller $query = $query->where('uploader', $uploader); } - if (! $currentUser) { + if (! $user) { // Show public textures only to anonymous visitors $query = $query->where('public', true); } else { // Show private textures when show uploaded textures of current user - if ($uploader != $currentUser->uid && ! $currentUser->isAdmin()) { - $query = $query->where(function ($innerQuery) use ($currentUser) { - $innerQuery->where('public', true)->orWhere('uploader', '=', $currentUser->uid); + if ($uploader != $user->uid && ! $user->isAdmin()) { + $query = $query->where(function ($innerQuery) use ($user) { + $innerQuery->where('public', true)->orWhere('uploader', '=', $user->uid); }); } } @@ -107,16 +106,16 @@ class SkinlibController extends Controller ->take($itemsPerPage) ->get(); - if ($currentUser) { - $closet = new Closet($currentUser->uid); + if ($user) { + $closet = $user->closet()->get(); foreach ($textures as $item) { - $item->liked = $closet->has($item->tid); + $item->liked = $closet->contains('tid', $item->tid); } } return response()->json([ 'items' => $textures, - 'current_uid' => $currentUser ? $currentUser->uid : 0, + 'current_uid' => $user ? $user->uid : 0, 'total_pages' => $totalPages, ]); } @@ -152,7 +151,7 @@ class SkinlibController extends Controller public function info($tid) { if ($t = Texture::find($tid)) { - return json($t->toArray()); + return json(array_merge($t->toArray(), ['likes' => $t->likes])); } else { return json([]); } @@ -176,7 +175,6 @@ class SkinlibController extends Controller $t = new Texture(); $t->name = $request->input('name'); $t->type = $request->input('type'); - $t->likes = 1; $t->hash = bs_hash_file($request->file('file')); $t->size = ceil($request->file('file')->getSize() / 1024); $t->public = $request->input('public') == 'true'; @@ -212,11 +210,10 @@ class SkinlibController extends Controller $user->setScore($cost, 'minus'); - if ($user->getCloset()->add($t->tid, $t->name)) { - return json(trans('skinlib.upload.success', ['name' => $request->input('name')]), 0, [ - 'tid' => $t->tid, - ]); - } + $user->closet()->attach($t->tid, ['item_name' => $t->name]); + return json(trans('skinlib.upload.success', ['name' => $request->input('name')]), 0, [ + 'tid' => $t->tid, + ]); } // @codeCoverageIgnore @@ -281,10 +278,14 @@ class SkinlibController extends Controller $type = $t->type == 'cape' ? 'cape' : 'skin'; Player::where("tid_$type", $t->tid) ->where('uid', '<>', session('uid')) - ->get() - ->each(function ($player) use ($type) { - $player->setTexture(["tid_$type" => 0]); - }); + ->update(["tid_$type" => 0]); + + $t->likers()->get()->each(function ($user) use ($t) { + $user->closet()->detach($t->tid); + if (option('return_score')) { + $user->setScore(option('score_per_closet_item'), 'plus'); + } + }); @$users->get($t->uploader)->setScore($score_diff, 'plus'); diff --git a/app/Models/Closet.php b/app/Models/Closet.php deleted file mode 100644 index 3abc1b54..00000000 --- a/app/Models/Closet.php +++ /dev/null @@ -1,268 +0,0 @@ -uid = $uid; - $this->db = DB::table('closets'); - - // Create a new closet if not exists - if ($this->db->where('uid', $uid)->count() == 0) { - $this->db->insert([ - 'uid' => $uid, - 'textures' => '[]', - ]); - } - - // Load items from json string - $this->textures = collect(json_decode( - $this->db->where('uid', $uid)->first()->textures, - true - )); - - event(new Events\ClosetWillBeFiltered($this)); - - // Traverse items in the closet - $removedCount = $this->textures->filter(function ($texture) use ($uid) { - $t = Texture::find($texture['tid']); - - // If the texture was deleted - if (is_null($t)) { - return true; - } - - if (! $t->public && $t->uploader != $uid && ! app('users')->get($uid)->isAdmin()) { - return true; - } - - return false; - })->each(function ($texture) use ($uid) { - $this->remove($texture['tid']); - })->count(); - - event(new Events\ClosetWasFiltered($this)); - - // Return scores if the texture was deleted or set as private - if (option('return_score')) { - app('users')->get($uid)->setScore( - option('score_per_closet_item') * $removedCount, - 'plus' - ); - } - } - - /** - * Get array of instances of App\Models\Texture. - * - * @param string $category "skin" or "cape" or "all". - * @return array - */ - public function getItems($category = 'all') - { - $textures = Texture::whereIn('tid', $this->textures->pluck('tid')->all()) - ->get() - ->map(function ($texture) { - $in_closet = $this->textures - ->where('tid', $texture->tid) - ->first(); - - return [ - 'tid' => $texture->tid, - 'name' => $in_closet['name'], - 'type' => $texture->type, - 'add_at' => $in_closet['add_at'], - ]; - }) - ->sortByDesc('add_at'); - if ($category == 'all') { - return $textures->values()->all(); - } elseif ($category == 'cape') { - return $textures->filter(function ($texture) { - return $texture['type'] == 'cape'; - })->values(); - } else { - return $textures->reject(function ($texture) { - return $texture['type'] == 'cape'; - })->values(); - } - } - - /** - * Add an item to the closet. - * - * @param int $tid - * @param string $name - * @return bool - */ - public function add($tid, $name) - { - if ($this->has($tid)) { - return false; - } - - $this->textures->push([ - 'tid' => (int) $tid, - 'name' => $name, - 'add_at' => time(), - ]); - - $this->closet_modified = true; - - return true; - } - - /** - * Check if texture is in the closet. - * - * @param int $tid - * @return bool - */ - public function has($tid) - { - return $this->textures->contains('tid', $tid); - } - - /** - * Get one texture info. - * - * @param int $tid - * @return array|null Result - */ - public function get($tid) - { - return $this->textures->where('tid', $tid)->first(); - } - - /** - * Rename closet item. - * - * @param int $tid - * @param string $newName - * @return bool - */ - public function rename($tid, $newName) - { - if (! $this->has($tid)) { - return false; - } - - $this->textures->transform(function ($texture) use ($tid, $newName) { - if ($texture['tid'] == $tid) { - $texture['name'] = $newName; - } - - return $texture; - }); - - $this->closet_modified = true; - - return true; - } - - /** - * Remove a texture from closet. - * - * @param int $tid - * @return bool - */ - public function remove($tid) - { - if (! $this->has($tid)) { - return false; - } - $this->textures = $this->textures->reject(function ($texture) use ($tid) { - return $texture['tid'] == $tid; - }); - - $this->closet_modified = true; - - return true; - } - - /** - * Set textures string manually. - * - * @param string $textures - * @return int - */ - public function setTextures($textures) - { - return $this->db->where('uid', $this->uid)->update(['textures' => $textures]); - } - - /** - * Do really database operations. - * - * @return bool - */ - public function save() - { - if (! $this->closet_modified) { - return false; - } - - $this->closet_modified = false; - - return $this->setTextures($this->textures->values()->toJson()); - } - - /** - * Save when the object will be destructed. - * - * @return void - */ - public function __destruct() - { - $this->save(); - } - - /** - * Get all closets. - * - * @return array - */ - public static function all() - { - $result = []; - foreach (DB::table('closets')->pluck('uid') as $uid) { - $result[] = new Closet($uid); - } - - return $result; - } -} diff --git a/app/Models/Texture.php b/app/Models/Texture.php index 0892160c..51f62f7c 100644 --- a/app/Models/Texture.php +++ b/app/Models/Texture.php @@ -16,14 +16,23 @@ class Texture extends Model */ protected $casts = [ 'tid' => 'integer', - 'likes' => 'integer', 'size' => 'integer', 'uploader' => 'integer', 'public' => 'boolean', ]; + public function getLikesAttribute() + { + return $this->likers()->count(); + } + public function scopeLike($query, $field, $value) { return $query->where($field, 'LIKE', "%$value%"); } + + public function likers() + { + return $this->belongsToMany(User::class, 'user_closet')->withPivot('item_name'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index a9b36e62..93d03806 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -18,12 +18,6 @@ class User extends Authenticatable const ADMIN = 1; const SUPER_ADMIN = 2; - /** - * Instance of Closet. - * @var \App\Models\Closet - */ - protected $closet; - /** * Properties for Eloquent Model. */ @@ -61,18 +55,9 @@ class User extends Authenticatable return $this->permission >= static::ADMIN; } - /** - * Get closet instance. - * - * @return \App\Models\Closet - */ - public function getCloset() + public function closet() { - if (! $this->closet) { - $this->closet = new Closet($this->uid); - } - - return $this->closet; + return $this->belongsToMany(Texture::class, 'user_closet')->withPivot('item_name'); } /** @@ -332,11 +317,7 @@ class User extends Authenticatable */ public function delete() { - // Delete the players he owned Player::where('uid', $this->uid)->delete(); - // Delete his closet - DB::table('closets')->where('uid', $this->uid)->delete(); - return parent::delete(); } diff --git a/database/factories/ClosetModelFactory.php b/database/factories/ClosetModelFactory.php deleted file mode 100644 index 11b127eb..00000000 --- a/database/factories/ClosetModelFactory.php +++ /dev/null @@ -1,11 +0,0 @@ -define(Closet::class, function (Faker\Generator $faker) { - return [ - 'uid' => factory(User::class)->create()->uid, - 'textures' => '[]', - ]; -}); diff --git a/database/factories/TextureModelFactory.php b/database/factories/TextureModelFactory.php index d65b2267..8a0201f9 100644 --- a/database/factories/TextureModelFactory.php +++ b/database/factories/TextureModelFactory.php @@ -6,7 +6,6 @@ $factory->define(Texture::class, function (Faker\Generator $faker) { return [ 'name' => $faker->firstName, 'type' => 'steve', - 'likes' => rand(0, 50), 'hash' => $faker->sha256, 'size' => rand(1, 2048), 'uploader' => factory(App\Models\User::class)->create()->uid, @@ -19,7 +18,6 @@ $factory->defineAs(Texture::class, 'alex', function (Faker\Generator $faker) { return [ 'name' => $faker->firstName, 'type' => 'alex', - 'likes' => rand(0, 50), 'hash' => $faker->sha256, 'size' => rand(1, 2048), 'uploader' => factory(App\Models\User::class)->create()->uid, @@ -32,7 +30,6 @@ $factory->defineAs(Texture::class, 'cape', function (Faker\Generator $faker) { return [ 'name' => $faker->firstName, 'type' => 'cape', - 'likes' => rand(0, 50), 'hash' => $faker->sha256, 'size' => rand(1, 2048), 'uploader' => factory(App\Models\User::class)->create()->uid, diff --git a/database/migrations/2016_11_18_133939_create_all_tables.php b/database/migrations/2016_11_18_133939_create_all_tables.php index 2b04f03e..77ab6448 100644 --- a/database/migrations/2016_11_18_133939_create_all_tables.php +++ b/database/migrations/2016_11_18_133939_create_all_tables.php @@ -25,11 +25,6 @@ class CreateAllTables extends Migration $table->dateTime('register_at'); }); - Schema::create('closets', function (Blueprint $table) { - $table->increments('uid'); - $table->longText('textures'); - }); - Schema::create('players', function (Blueprint $table) { $table->increments('pid'); $table->integer('uid'); @@ -42,7 +37,6 @@ class CreateAllTables extends Migration $table->increments('tid'); $table->string('name', 50); $table->string('type', 10); - $table->integer('likes'); $table->string('hash', 64); $table->integer('size'); $table->integer('uploader'); @@ -65,7 +59,6 @@ class CreateAllTables extends Migration public function down() { Schema::drop('users'); - Schema::drop('closets'); Schema::drop('players'); Schema::drop('textures'); Schema::drop('options'); diff --git a/database/migrations/2019_03_14_174727_create_closet.php b/database/migrations/2019_03_14_174727_create_closet.php new file mode 100644 index 00000000..b87f1553 --- /dev/null +++ b/database/migrations/2019_03_14_174727_create_closet.php @@ -0,0 +1,32 @@ +integer('user_uid'); + $table->integer('texture_tid'); + $table->text('item_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_closet'); + } +} diff --git a/resources/views/skinlib/show.blade.php b/resources/views/skinlib/show.blade.php index 18b167b7..b89b1472 100644 --- a/resources/views/skinlib/show.blade.php +++ b/resources/views/skinlib/show.blade.php @@ -47,7 +47,7 @@ Object.defineProperty(blessing, 'extra', { download: {{ option('allow_downloading_texture') ? 'true' : 'false' }}, currentUid: {{ is_null($user) ? '0' : $user->uid }}, admin: {{ $user && $user->isAdmin() ? 'true' : 'false' }}, - inCloset: {{ $user && $user->getCloset()->has($texture->tid) ? 'true' : 'false' }}, + inCloset: {{ $user && $user->closet()->where('tid', $texture->tid)->count() > 0 ? 'true' : 'false' }}, nickname: @php echo ($up = app('users')->get($texture->uploader)) ? '"'.$up->nickname.'"' : 'null' @endphp }) }) diff --git a/tests/ClosetControllerTest.php b/tests/ClosetControllerTest.php index 5eb0b5cd..aff44431 100644 --- a/tests/ClosetControllerTest.php +++ b/tests/ClosetControllerTest.php @@ -3,7 +3,6 @@ namespace Tests; use App\Models\User; -use App\Models\Closet; use App\Models\Texture; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -31,18 +30,16 @@ class ClosetControllerTest extends TestCase public function testGetClosetData() { $textures = factory(Texture::class, 10)->create(); - $closet = new Closet($this->user->uid); - $textures->each(function ($texture) use ($closet) { - $closet->add($texture->tid, $texture->name); + $textures->each(function ($t) { + $this->user->closet()->attach($t->tid, ['item_name' => $t->name]); }); - $closet->save(); // Use default query parameters $this->getJson('/user/closet-data') ->assertJsonStructure([ 'category', 'total_pages', - 'items' => [['tid', 'name', 'type', 'add_at']], + 'items' => [['tid', 'name', 'type']], ]); // Responsive @@ -55,8 +52,7 @@ class ClosetControllerTest extends TestCase // Get capes $cape = factory(Texture::class, 'cape')->create(); - $closet->add($cape->tid, 'custom_name'); - $closet->save(); + $this->user->closet()->attach($cape->tid, ['item_name' => 'custom_name']); $this->getJson('/user/closet-data?category=cape') ->assertJson([ 'category' => 'cape', @@ -65,7 +61,6 @@ class ClosetControllerTest extends TestCase 'tid' => $cape->tid, 'name' => 'custom_name', 'type' => 'cape', - 'add_at' => $closet->get($cape->tid)['add_at'], ]], ]); @@ -79,7 +74,6 @@ class ClosetControllerTest extends TestCase 'tid' => $random->tid, 'name' => $random->name, 'type' => $random->type, - 'add_at' => $closet->get($random->tid)['add_at'], ]], ]); } @@ -87,6 +81,7 @@ class ClosetControllerTest extends TestCase public function testAdd() { $texture = factory(Texture::class)->create(); + $likes = $texture->likes; $name = 'my'; option(['score_per_closet_item' => 10]); @@ -152,11 +147,10 @@ class ClosetControllerTest extends TestCase 'errno' => 0, 'msg' => trans('user.closet.add.success', ['name' => $name]), ]); - $this->assertEquals($texture->likes + 1, Texture::find($texture->tid)->likes); + $this->assertEquals($likes + 1, Texture::find($texture->tid)->likes); $this->user = User::find($this->user->uid); $this->assertEquals(90, $this->user->score); - $closet = new Closet($this->user->uid); - $this->assertTrue($closet->has($texture->tid)); + $this->assertEquals(1, $this->user->closet()->count()); // If the texture is duplicated, should be warned $this->postJson( @@ -217,10 +211,7 @@ class ClosetControllerTest extends TestCase ]); // Rename a closet item successfully - $closet = new Closet($this->user->uid); - $closet->add($texture->tid, 'name'); - $closet->save(); - $closet = new Closet($this->user->uid); + $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $this->postJson( '/user/closet/rename', ['tid' => $texture->tid, 'new_name' => $name] @@ -228,14 +219,13 @@ class ClosetControllerTest extends TestCase 'errno' => 0, 'msg' => trans('user.closet.rename.success', ['name' => 'new']), ]); - $closet->save(); - $closet = new Closet($this->user->uid); - $this->assertFalse(collect($closet->getItems())->where('name', 'new')->isEmpty()); + $this->assertEquals(1, $this->user->closet()->where('item_name', 'new')->count()); } public function testRemove() { $texture = factory(Texture::class)->create(); + $likes = $texture->likes; // Missing `tid` field $this->postJson('/user/closet/remove') @@ -263,9 +253,7 @@ class ClosetControllerTest extends TestCase ]); // Should return score if `return_score` is true - $closet = new Closet($this->user->uid); - $closet->add($texture->tid, 'name'); - $closet->save(); + $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score; $this->postJson( '/user/closet/remove', @@ -274,17 +262,15 @@ class ClosetControllerTest extends TestCase 'errno' => 0, 'msg' => trans('user.closet.remove.success'), ]); - $closet = new Closet($this->user->uid); - $this->assertEquals($texture->likes - 1, Texture::find($texture->tid)->likes); + $this->assertEquals($likes, Texture::find($texture->tid)->likes); $this->assertEquals($score + option('score_per_closet_item'), $this->user->score); - $this->assertFalse($closet->has($texture->tid)); + $this->assertEquals(0, $this->user->closet()->count()); $texture = Texture::find($texture->tid); + $likes = $texture->likes; // Should not return score if `return_score` is false option(['return_score' => false]); - $closet = new Closet($this->user->uid); - $closet->add($texture->tid, 'name'); - $closet->save(); + $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score; $this->postJson( '/user/closet/remove', @@ -293,9 +279,8 @@ class ClosetControllerTest extends TestCase 'errno' => 0, 'msg' => trans('user.closet.remove.success'), ]); - $closet = new Closet($this->user->uid); - $this->assertEquals($texture->likes - 1, Texture::find($texture->tid)->likes); + $this->assertEquals($likes, Texture::find($texture->tid)->likes); $this->assertEquals($score, $this->user->score); - $this->assertFalse($closet->has($texture->tid)); + $this->assertEquals(0, $this->user->closet()->count()); } } diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index 6ada682b..baed70fd 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -109,7 +109,7 @@ class MiddlewareTest extends TestCase $this->get('/setup')->assertSee('Already installed'); $tables = [ - 'closets', 'migrations', 'options', 'players', 'textures', 'users', + 'user_closet', 'migrations', 'options', 'players', 'textures', 'users', ]; array_walk($tables, function ($table) { Schema::dropIfExists($table); diff --git a/tests/ModelsTest/ClosetTest.php b/tests/ModelsTest/ClosetTest.php deleted file mode 100644 index a20c1f42..00000000 --- a/tests/ModelsTest/ClosetTest.php +++ /dev/null @@ -1,51 +0,0 @@ -create(); - (new Closet($user->uid))->save(); - } - $this->assertCount(2, Closet::all()); - } - - public function testFilterInvalidTexture() - { - $other = factory(User::class)->create(); - $texture = factory(Texture::class)->create([ - 'uploader' => $other->uid, - 'public' => false, - ]); - $user = factory(User::class)->create(); - $closet = new Closet($user->uid); - $closet->add(-1, ''); - $closet->add($texture->tid, ''); - $closet->save(); - - $this->assertCount(0, (new Closet($user->uid))->getItems()); - $this->assertEquals( - $user->score + 2 * option('score_per_closet_item'), - User::find($user->uid)->score - ); - - option(['return_score' => false]); - $closet = new Closet($user->uid); - $closet->add(-1, ''); - $closet->add($texture->tid, ''); - $closet->save(); - $user = User::find($user->uid); - $this->assertCount(0, (new Closet($user->uid))->getItems()); - $this->assertEquals($user->score, User::find($user->uid)->score); - } -} diff --git a/tests/SetupControllerTest.php b/tests/SetupControllerTest.php index e6db3694..2891ce1f 100644 --- a/tests/SetupControllerTest.php +++ b/tests/SetupControllerTest.php @@ -33,7 +33,7 @@ class SetupControllerTest extends TestCase protected function dropAllTables() { $tables = [ - 'closets', 'migrations', 'options', 'players', 'textures', 'users', + 'user_closet', 'migrations', 'options', 'players', 'textures', 'users', ]; array_walk($tables, function ($table) { Schema::dropIfExists($table); diff --git a/tests/SkinlibControllerTest.php b/tests/SkinlibControllerTest.php index 3f25ebce..6b7bdaae 100644 --- a/tests/SkinlibControllerTest.php +++ b/tests/SkinlibControllerTest.php @@ -3,7 +3,6 @@ namespace Tests; use App\Models\User; -use App\Models\Closet; use App\Models\Player; use App\Models\Texture; use Illuminate\Support\Str; @@ -15,25 +14,6 @@ class SkinlibControllerTest extends TestCase { use DatabaseTransactions; - protected function serializeTextures($textures) - { - return $textures - ->map(function ($texture) { - return [ - 'tid' => $texture->tid, - 'name' => $texture->name, - 'type' => $texture->type, - 'likes' => $texture->likes, - 'hash' => $texture->hash, - 'size' => $texture->size, - 'uploader' => $texture->uploader, - 'public' => $texture->public, - 'upload_at' => $texture->upload_at->format('Y-m-d H:i:s'), - ]; - }) - ->all(); - } - public function testIndex() { $this->get('/skinlib')->assertViewHas('user'); @@ -54,26 +34,28 @@ class SkinlibControllerTest extends TestCase $capes = factory(Texture::class, 'cape')->times(5)->create(); // Default arguments - $expected = $skins - ->sortByDesc('upload_at') - ->values(); // WTF! DO NOT FORGET IT!! - $this->getJson('/skinlib/data') + $items = $this->getJson('/skinlib/data') ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertCount(10, $items); + $this->assertTrue(collect($items)->every(function ($item) { + return $item['type'] == 'steve' || $item['type'] == 'alex'; + })); // Only steve - $expected = $steves - ->sortByDesc('upload_at') - ->values(); - $this->getJson('/skinlib/data?filter=steve') + $items = $this->getJson('/skinlib/data?filter=steve') ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertCount(5, $items); + $this->assertTrue(collect($items)->every(function ($item) { + return $item['type'] == 'steve'; + })); // Invalid type $this->getJson('/skinlib/data?filter=what') @@ -84,70 +66,87 @@ class SkinlibControllerTest extends TestCase ]); // Only capes - $expected = $capes - ->sortByDesc('upload_at') - ->values(); - $this->getJson('/skinlib/data?filter=cape') + $items = $this->getJson('/skinlib/data?filter=cape') ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertCount(5, $items); + $this->assertTrue(collect($items)->every(function ($item) { + return $item['type'] == 'cape'; + })); // Only specified uploader $uid = $skins->random()->uploader; - $expected = $skins + $owned = $skins ->filter(function ($texture) use ($uid) { return $texture->uploader == $uid; - }) - ->sortByDesc('upload_at') - ->values(); - $this->getJson('/skinlib/data?uploader='.$uid) + }); + $items = $this->getJson('/skinlib/data?uploader='.$uid) ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertCount($owned->count(), $items); + $this->assertTrue(collect($items)->every(function ($item) use ($uid) { + return $item['uploader'] == $uid; + })); // Sort by `tid` - $this->getJson('/skinlib/data?sort=tid') + $ordered = $skins->sortByDesc('tid')->map(function ($skin) { + return $skin->tid; + })->values(); + $items = $this->getJson('/skinlib/data?sort=tid') ->assertJson([ - 'items' => $this->serializeTextures($skins->sortByDesc('tid')->values()), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertArraySubset($ordered, $items); // Search $keyword = Str::limit($skins->random()->name, 1, ''); - $expected = $skins + $keyworded = $skins ->filter(function ($texture) use ($keyword) { return Str::contains($texture->name, $keyword) || Str::contains($texture->name, strtolower($keyword)); - }) - ->sortByDesc('upload_at') - ->values(); - $this->getJson('/skinlib/data?keyword='.$keyword) + }); + $items = $this->getJson('/skinlib/data?keyword='.$keyword) ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertCount($keyworded->count(), $items); // More than one argument $keyword = Str::limit($skins->random()->name, 1, ''); - $expected = $skins + $filtered = $skins ->filter(function ($texture) use ($keyword) { return Str::contains($texture->name, $keyword) || Str::contains($texture->name, strtolower($keyword)); }) - ->sortByDesc('likes') + ->sortByDesc('size') + ->map(function ($skin) { + return $skin->tid; + }) ->values(); - $this->getJson('/skinlib/data?sort=likes&keyword='.$keyword) + $items = $this->getJson('/skinlib/data?sort=size&keyword='.$keyword) ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 1, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount($filtered->count(), $items); + $this->assertArraySubset($filtered, $items); // Pagination $steves = factory(Texture::class) @@ -155,58 +154,88 @@ class SkinlibControllerTest extends TestCase ->create() ->merge($steves); $skins = $steves->merge($alexs); - $expected = $skins + $page1 = $skins ->sortByDesc('upload_at') ->values() + ->map(function ($skin) { + return $skin->tid; + }) ->forPage(1, 20); - $expected = $this->serializeTextures($expected); - $this->getJson('/skinlib/data') + $items = $this->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, 'current_uid' => 0, 'total_pages' => 2, - ]); - $this->getJson('/skinlib/data?page=-5') + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount(20, $items); + $this->assertArraySubset($page1, $items); + $items = $this->getJson('/skinlib/data?page=-5') ->assertJson([ - 'items' => $expected, 'current_uid' => 0, 'total_pages' => 2, - ]); - $expected = $skins + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount(20, $items); + $this->assertArraySubset($page1, $items); + $page2 = $skins ->sortByDesc('upload_at') - ->values() + ->map(function ($skin) { + return $skin->tid; + }) ->forPage(2, 20) ->values(); - $expected = $this->serializeTextures($expected); - $this->getJson('/skinlib/data?page=2') + $items = $this->getJson('/skinlib/data?page=2') ->assertJson([ - 'items' => $expected, 'current_uid' => 0, 'total_pages' => 2, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount(5, $items); + $this->assertArraySubset($page2, $items); $this->getJson('/skinlib/data?page=8') ->assertJson([ 'items' => [], 'current_uid' => 0, 'total_pages' => 2, ]); - $this->getJson('/skinlib/data?items_per_page=-6&page=2') + $items = $this->getJson('/skinlib/data?items_per_page=-6&page=2') ->assertJson([ - 'items' => $expected, 'current_uid' => 0, 'total_pages' => 2, - ]); - $expected = $skins + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount($page2->count(), $items); + $this->assertArraySubset($page2, $items); + $page3 = $skins ->sortByDesc('upload_at') - ->values() + ->map(function ($skin) { + return $skin->tid; + }) ->forPage(3, 8) ->values(); - $this->getJson('/skinlib/data?page=3&items_per_page=8') + $items = $this->getJson('/skinlib/data?page=3&items_per_page=8') ->assertJson([ - 'items' => $this->serializeTextures($expected), 'current_uid' => 0, 'total_pages' => 4, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertCount($page3->count(), $items); + $this->assertArraySubset($page3, $items); // Add some private textures $uploader = factory(User::class)->create(); @@ -216,85 +245,82 @@ class SkinlibControllerTest extends TestCase ->create(['public' => false, 'uploader' => $uploader->uid]); // If not logged in, private textures should not be shown - $expected = $skins + $paged = $skins ->sortByDesc('upload_at') + ->map(function ($skin) { + return $skin->tid; + }) ->values() ->forPage(1, 20); - $expected = $this->serializeTextures($expected); - $this->getJson('/skinlib/data') + $items = $this->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, 'current_uid' => 0, 'total_pages' => 2, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertArraySubset($paged, $items); // Other users should not see someone's private textures - for ($i = 0, $length = count($expected); $i < $length; $i++) { - $expected[$i]['liked'] = false; - } - $this->actAs($otherUser) + $items = $this->actAs($otherUser) ->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, 'current_uid' => $otherUser->uid, 'total_pages' => 2, - ]); + ]) + ->decodeResponseJson('items'); + $this->assertTrue(collect($items)->every(function ($item) { + return !$item['liked']; + })); // A user has added a texture from skin library to his closet - $texture = $skins - ->sortByDesc('upload_at') - ->values() - ->first(); - $closet = new Closet($otherUser->uid); - $closet->add($texture->tid, $texture->name); - $closet->save(); - for ($i = 0, $length = count($expected); $i < $length; $i++) { - if ($expected[$i]['tid'] == $texture->tid) { - $expected[$i]['liked'] = true; - } else { - $expected[$i]['liked'] = false; - } - } + $texture = $skins->sortByDesc('upload_at')->values()->first(); + $otherUser->closet()->attach($texture->tid, ['item_name' => $texture->name]); $this->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, + 'items' => [ + ['tid' => $texture->tid, 'liked' => true] + ], 'current_uid' => $otherUser->uid, 'total_pages' => 2, ]); // Uploader can see his private textures - $expected = $skins + $withPrivate = $skins ->merge($private) ->sortByDesc('upload_at') + ->map(function ($skin) { + return $skin->tid; + }) ->values() ->forPage(1, 20); - $expected = $this->serializeTextures($expected); - for ($i = 0, $length = count($expected); $i < $length; $i++) { - // The reason we use `false` here is that some textures just were - // uploaded by this user, but these textures are not in his closet. - // By default(not in testing like now), when you uploaded a texture, - // that texture will be added to your closet. - // So here, we can assume that a user upload some textures, but he - // has deleted them from his closet. - $expected[$i]['liked'] = false; - } - $this->actAs($uploader) + $items = $this->actAs($uploader) ->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, 'current_uid' => $uploader->uid, 'total_pages' => 2, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertArraySubset($withPrivate, $items); // Administrators can see private textures $admin = factory(User::class, 'admin')->create(); - $this->actAs($admin) + $items = $this->actAs($admin) ->getJson('/skinlib/data') ->assertJson([ - 'items' => $expected, 'current_uid' => $admin->uid, 'total_pages' => 2, - ]); + ]) + ->decodeResponseJson('items'); + $items = array_map(function ($item) { + return $item['tid']; + }, $items); + $this->assertArraySubset($withPrivate, $items); } public function testShow() @@ -769,6 +795,8 @@ class SkinlibControllerTest extends TestCase $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->postJson('/skinlib/privacy', ['tid' => $texture->tid]) ->assertJson([ 'errno' => 0, @@ -776,6 +804,26 @@ class SkinlibControllerTest extends TestCase 'public' => false, ]); $this->assertEquals(0, Player::find($player->pid)->tid_skin); + $this->assertEquals(0, $other->closet()->count()); + $this->assertEquals( + $other->score + option('score_per_closet_item'), + User::find($other->uid)->score + ); + + // Without returning score + option(['return_score' => false]); + $texture = factory(Texture::class)->create(['public' => 'false', 'uploader' => $uploader->uid]); + $other = factory(User::class)->create(); + $other->closet()->attach($texture->tid, ['item_name' => 'a']); + $this->postJson('/skinlib/privacy', ['tid' => $texture->tid]) + ->assertJson([ + 'errno' => 0, + 'public' => false, + ]); + $this->assertEquals( + $other->score, + User::find($other->uid)->score + ); } public function testRename()