diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php index 11432f50..98b7fcca 100644 --- a/app/Http/Controllers/AdminController.php +++ b/app/Http/Controllers/AdminController.php @@ -508,11 +508,17 @@ class AdminController extends Controller return json(trans('admin.players.delete.success'), 0); } elseif ($action == 'name') { - $this->validate($request, [ + $name = $this->validate($request, [ 'name' => 'required|player_name|min:'.option('player_name_length_min').'|max:'.option('player_name_length_max'), - ]); + ])['name']; - $player->rename($request->input('name')); + $player->rename($name); + + if (option('single_player', false)) { + $owner = $player->user; + $owner->nickname = $name; + $owner->save(); + } return json(trans('admin.players.name.success', ['player' => $player->name]), 0, ['name' => $player->name]); } else { diff --git a/app/Http/Controllers/PlayerController.php b/app/Http/Controllers/PlayerController.php index 9e0bd252..ef2446af 100644 --- a/app/Http/Controllers/PlayerController.php +++ b/app/Http/Controllers/PlayerController.php @@ -65,6 +65,10 @@ class PlayerController extends Controller { $user = Auth::user(); + if (option('single_player', false)) { + return json(trans('user.player.add.single'), 1); + } + $this->validate($request, [ 'player_name' => 'required|player_name|min:'.option('player_name_length_min').'|max:'.option('player_name_length_max'), ]); @@ -99,6 +103,10 @@ class PlayerController extends Controller { $playerName = $this->player->name; + if (option('single_player', false)) { + return json(trans('user.player.delete.single'), 1); + } + event(new PlayerWillBeDeleted($this->player)); $this->player->delete(); @@ -133,6 +141,12 @@ class PlayerController extends Controller $this->player->rename($newName); + if (option('single_player', false)) { + $user = auth()->user(); + $user->nickname = $newName; + $user->save(); + } + return json(trans('user.player.rename.success', ['old' => $oldName, 'new' => $newName]), 0); } @@ -169,4 +183,34 @@ class PlayerController extends Controller return json(trans('user.player.clear.success', ['name' => $this->player->name]), 0); } + + public function bind(Request $request) + { + $name = $this->validate($request, [ + 'player' => 'required|player_name|min:'.option('player_name_length_min').'|max:'.option('player_name_length_max'), + ])['player']; + $user = Auth::user(); + + event(new CheckPlayerExists($name)); + $player = Player::where('name', $name)->first(); + if (! $player) { + event(new PlayerWillBeAdded($name)); + + $player = new Player; + $player->uid = $user->uid; + $player->name = $name; + $player->tid_skin = 0; + $player->save(); + + event(new PlayerWasAdded($player)); + } elseif ($player->uid != $user->uid) { + return json(trans('user.player.rename.repeated'), 1); + } + + $user->players()->where('name', '<>', $name)->delete(); + $user->nickname = $name; + $user->save(); + + return json(trans('user.player.bind.success'), 0); + } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 13b206a5..036d51b6 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -175,6 +175,10 @@ class UserController extends Controller switch ($action) { case 'nickname': + if (option('single_player', false)) { + return json(trans('user.profile.nickname.single'), 1); + } + $this->validate($request, [ 'new_nickname' => 'required|no_special_chars|max:255', ]); diff --git a/app/Http/Middleware/RequireBindPlayer.php b/app/Http/Middleware/RequireBindPlayer.php new file mode 100644 index 00000000..9165ea45 --- /dev/null +++ b/app/Http/Middleware/RequireBindPlayer.php @@ -0,0 +1,40 @@ +is('user/player/bind')) { + return redirect('/user'); + } else { + return $next($request); + } + } + + // This allows us to fetch players list. + if ($request->is('user/player/list')) { + return $next($request); + } + + $count = auth()->user()->players()->count(); + + if ($request->is('user/player/bind')) { + if ($count == 1) { + return redirect('/user'); + } else { + return $next($request); + } + } + + if ($count == 1) { + return $next($request); + } else { + return redirect('user/player/bind'); + } + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 159971b4..bf9a2fca 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -60,6 +60,27 @@ class User extends Authenticatable return $this->belongsToMany(Texture::class, 'user_closet')->withPivot('item_name'); } + /** + * Retrieve the player name of first player. + */ + public function getPlayerNameAttribute() + { + $player = $this->players->first(); + return $player ? $player->name : ''; + } + + /** + * Update the player name of first player. + */ + public function setPlayerNameAttribute($value) + { + $player = $this->players->first(); + if ($player) { + $player->name = $value; + $player->save(); + } + } + /** * Check if given password is correct. * diff --git a/resources/assets/src/route.ts b/resources/assets/src/route.ts index a69f2bae..eae987b6 100644 --- a/resources/assets/src/route.ts +++ b/resources/assets/src/route.ts @@ -18,6 +18,11 @@ export default [ component: () => import('./views/user/Players.vue'), el: '.content', }, + { + path: 'user/player/bind', + component: () => import('./views/user/Bind.vue'), + el: 'form', + }, { path: 'user/profile', component: () => import('./views/user/Profile.vue'), diff --git a/resources/assets/src/views/user/Bind.vue b/resources/assets/src/views/user/Bind.vue new file mode 100644 index 00000000..48ead20a --- /dev/null +++ b/resources/assets/src/views/user/Bind.vue @@ -0,0 +1,84 @@ + + + + + + + diff --git a/resources/assets/tests/views/user/Bind.test.ts b/resources/assets/tests/views/user/Bind.test.ts new file mode 100644 index 00000000..3575b7e7 --- /dev/null +++ b/resources/assets/tests/views/user/Bind.test.ts @@ -0,0 +1,42 @@ +import Vue from 'vue' +import { mount } from '@vue/test-utils' +import Bind from '@/views/user/Bind.vue' +import { swal } from '@/js/notify' + +jest.mock('@/js/notify') + +test('list existed players', async () => { + Vue.prototype.$http.get + .mockResolvedValue([{ name: 'a' }, { name: 'b' }]) + const wrapper = mount(Bind) + await wrapper.vm.$nextTick() + const options = wrapper.findAll('option') + expect(options).toHaveLength(2) +}) + +test('show input box', async () => { + Vue.prototype.$http.get.mockResolvedValue([]) + const wrapper = mount(Bind) + await wrapper.vm.$nextTick() + const input = wrapper.find('input') + expect(input.exists()).toBeTrue() +}) + +test('submit', async () => { + Vue.prototype.$http.get.mockResolvedValue([]) + Vue.prototype.$http.post + .mockResolvedValueOnce({ errno: 1, msg: 'fail' }) + .mockResolvedValueOnce({ errno: 0, msg: 'ok' }) + swal.mockResolvedValue({}) + + const wrapper = mount(Bind) + wrapper.find('input').setValue('abc') + + wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + expect(wrapper.find('.callout').text()).toBe('fail') + + wrapper.find('button').trigger('click') + await wrapper.vm.$nextTick() + expect(swal).toBeCalledWith({ text: 'ok', type: 'success' }) +}) diff --git a/resources/lang/en/front-end.yml b/resources/lang/en/front-end.yml index 320bbea9..37c07ad4 100644 --- a/resources/lang/en/front-end.yml +++ b/resources/lang/en/front-end.yml @@ -139,6 +139,8 @@ user: typeToSearch: Type to search useAs: Apply... resetSelected: Clear selected + bindNewPlayer: You're required to create a player to go ahead. This player will be bound with your account. + bindExistedPlayer: You're required to select a player to go ahead. This player will be bound with your account. Other players will be deleted. closet: use-as: button: Apply... @@ -328,6 +330,7 @@ general: reset: Reset skinlib: Skin Library loading: Loading + wait: Please wait... user: email: Email nickname: Nick Name diff --git a/resources/lang/en/user.yml b/resources/lang/en/user.yml index f01b063f..c09dcb66 100644 --- a/resources/lang/en/user.yml +++ b/resources/lang/en/user.yml @@ -93,9 +93,11 @@ player: add: repeated: The player name is already registered. lack-score: You don't have enough score to add a player. + single: You must own exactly ONE player so you can't add more. success: Player :name was added successfully. delete: + single: You must own exactly ONE player so you can't delete it. success: Player :name was deleted successfully. rename: @@ -108,6 +110,10 @@ player: clear: success: The textures of player :name was resetted successfully. + bind: + title: Bind Players + success: Bound successfully! + profile: avatar: title: Change Avatar? @@ -128,6 +134,7 @@ profile: title: Change Nickname empty: No nickname is set now. rule: Whatever you like expect special characters + single: You're not allowed to update nickname, because we've bound your player with your account. success: Nickname is successfully updated to :nickname email: diff --git a/resources/lang/zh_CN/front-end.yml b/resources/lang/zh_CN/front-end.yml index b0d98fdc..a38d6d9d 100644 --- a/resources/lang/zh_CN/front-end.yml +++ b/resources/lang/zh_CN/front-end.yml @@ -139,6 +139,8 @@ user: typeToSearch: 输入即搜索 useAs: 使用... resetSelected: 重置已选材质 + bindNewPlayer: 您现在需要创建一个角色,才能继续使用我们的服务。这个角色将与您的账号绑定。 + bindExistedPlayer: 您现在需要选择一个角色,才能继续使用我们的服务。这个角色将与您的账号绑定。其它角色将被删除。 closet: use-as: button: 使用... @@ -322,6 +324,7 @@ general: reset: 重置 skinlib: 皮肤库 loading: 正在加载 + wait: 请稍等... user: email: 邮箱 nickname: 昵称 diff --git a/resources/lang/zh_CN/user.yml b/resources/lang/zh_CN/user.yml index 71b7e373..1195cf78 100644 --- a/resources/lang/zh_CN/user.yml +++ b/resources/lang/zh_CN/user.yml @@ -102,9 +102,11 @@ player: add: repeated: 该角色名已被占用 lack-score: 添加角色失败,积分不足 + single: 您必须拥有且只有一个角色,因此您不能添加更多角色。 success: 成功添加了角色 :name delete: + single: 您必须拥有且只有一个角色,因此您不能删除。 success: 角色 :name 已被删除 rename: @@ -117,6 +119,10 @@ player: clear: success: 角色 :name 的材质已被成功重置 + bind: + title: 绑定角色 + success: 绑定成功 + profile: avatar: title: 更改头像? @@ -137,6 +143,7 @@ profile: title: 更改昵称 empty: 当前未设置昵称, rule: 可使用除一些特殊符号外的任意字符 + single: 您不能更改昵称,因为我们已将角色与您的账号进行了绑定。 success: 昵称已成功设置为 :nickname email: diff --git a/resources/views/auth/bind.blade.php b/resources/views/auth/bind.blade.php index 95d8bc92..65cbb789 100644 --- a/resources/views/auth/bind.blade.php +++ b/resources/views/auth/bind.blade.php @@ -12,7 +12,7 @@
@lang('auth.bind.message')
-