diff --git a/app/Http/Controllers/SkinlibController.php b/app/Http/Controllers/SkinlibController.php index 508c682a..83c24623 100644 --- a/app/Http/Controllers/SkinlibController.php +++ b/app/Http/Controllers/SkinlibController.php @@ -249,16 +249,21 @@ class SkinlibController extends Controller $hash = hash_file('sha256', $file); $hash = $filter->apply('uploaded_texture_hash', $hash, [$file]); - $duplicated = Texture::where('hash', $hash)->where('public', true)->first(); + /** @var User */ + $user = Auth::user(); + + $duplicated = Texture::where('hash', $hash) + ->where(function ($query) use ($user) { + return $query->where('public', true) + ->orWhere('uploader', $user->uid); + }) + ->first(); if ($duplicated) { // if the texture already uploaded was set to private, // then allow to re-upload it. return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]); } - /** @var User */ - $user = Auth::user(); - $size = ceil($file->getSize() / 1024); $isPublic = is_string($data['public']) ? $data['public'] === '1' @@ -342,6 +347,15 @@ class SkinlibController extends Controller return json(trans('skinlib.upload.lack-score'), 1); } + if (!$texture->public) { + $duplicated = Texture::where('hash', $texture->hash) + ->where('public', true) + ->first(); + if ($duplicated) { + return json(trans('skinlib.upload.repeated'), 2, ['tid' => $duplicated->tid]); + } + } + $dispatcher->dispatch('texture.privacy.updating', [$texture]); $uploader->score += $score_diff; diff --git a/resources/assets/src/views/skinlib/Show/index.tsx b/resources/assets/src/views/skinlib/Show/index.tsx index be746787..ac20be38 100644 --- a/resources/assets/src/views/skinlib/Show/index.tsx +++ b/resources/assets/src/views/skinlib/Show/index.tsx @@ -191,12 +191,29 @@ const Show: React.FC = () => { return } - const { code, message } = await fetch.put( + type Ok = { code: 0; message: string } + type Err = { code: 1; message: string } + type Duplicated = { code: 2; message: string; data: { tid: number } } + + const resp = await fetch.put( urls.texture.privacy(texture.tid), ) + const { code, message } = resp if (code === 0) { toast.success(message) setTexture((texture) => ({ ...texture, public: !texture.public })) + } else if (resp.code === 2) { + try { + await showModal({ + mode: 'confirm', + text: message, + okButtonText: t('user.viewInSkinlib'), + }) + window.location.href = + blessing.base_url + urls.skinlib.show(resp.data.tid) + } catch { + // + } } else { toast.error(message) } diff --git a/resources/assets/tests/views/skinlib/Show.test.tsx b/resources/assets/tests/views/skinlib/Show.test.tsx index c3fed242..f8b9029e 100644 --- a/resources/assets/tests/views/skinlib/Show.test.tsx +++ b/resources/assets/tests/views/skinlib/Show.test.tsx @@ -575,6 +575,48 @@ describe('change privacy', () => { expect(getByRole('alert')).toHaveClass('alert-danger') expect(queryByText(t('skinlib.setAsPublic'))).toBeInTheDocument() }) + + it('duplicated texture with confirmed', async () => { + fetch.get.mockResolvedValue({ ...fixtureSkin, public: false }) + fetch.put.mockResolvedValue({ + code: 2, + message: 'duplicated', + data: { tid: 2 }, + }) + + const { getByText, queryByText } = render() + await waitFor(() => expect(fetch.get).toBeCalledTimes(1)) + + fireEvent.click(getByText(t('skinlib.setAsPublic'))) + fireEvent.click(getByText(t('general.confirm'))) + await waitFor(() => + expect(fetch.put).toBeCalledWith(urls.texture.privacy(fixtureSkin.tid)), + ) + expect(queryByText('duplicated')).toBeInTheDocument() + expect(queryByText(t('skinlib.setAsPublic'))).toBeInTheDocument() + fireEvent.click(getByText(t('user.viewInSkinlib'))) + }) + + it('duplicated texture with cancelled', async () => { + fetch.get.mockResolvedValue({ ...fixtureSkin, public: false }) + fetch.put.mockResolvedValue({ + code: 2, + message: 'duplicated', + data: { tid: 2 }, + }) + + const { getByText, queryByText } = render() + await waitFor(() => expect(fetch.get).toBeCalledTimes(1)) + + fireEvent.click(getByText(t('skinlib.setAsPublic'))) + fireEvent.click(getByText(t('general.confirm'))) + await waitFor(() => + expect(fetch.put).toBeCalledWith(urls.texture.privacy(fixtureSkin.tid)), + ) + expect(queryByText('duplicated')).toBeInTheDocument() + expect(queryByText(t('skinlib.setAsPublic'))).toBeInTheDocument() + fireEvent.click(getByText(t('general.cancel'))) + }) }) describe('delete texture', () => { diff --git a/resources/misc/changelogs/en/5.1.0.md b/resources/misc/changelogs/en/5.1.0.md index 8b1ddb53..768acbc1 100644 --- a/resources/misc/changelogs/en/5.1.0.md +++ b/resources/misc/changelogs/en/5.1.0.md @@ -5,3 +5,4 @@ ## Fixed - Fixed duplicated route names. +- Fixed duplication of private textures. diff --git a/resources/misc/changelogs/zh_CN/5.1.0.md b/resources/misc/changelogs/zh_CN/5.1.0.md index 130777a2..eda36130 100644 --- a/resources/misc/changelogs/zh_CN/5.1.0.md +++ b/resources/misc/changelogs/zh_CN/5.1.0.md @@ -5,3 +5,4 @@ ## 修复 - 修复重复的路由命名 +- 修复私有材质的重复问题 diff --git a/tests/HttpTest/ControllersTest/SkinlibControllerTest.php b/tests/HttpTest/ControllersTest/SkinlibControllerTest.php index a4f87b83..27dbc326 100644 --- a/tests/HttpTest/ControllersTest/SkinlibControllerTest.php +++ b/tests/HttpTest/ControllersTest/SkinlibControllerTest.php @@ -449,6 +449,20 @@ class SkinlibControllerTest extends TestCase 'data' => ['tid' => $texture->tid], ]); + // upload a duplicated private texture + $texture->uploader = $user->uid; + $texture->save(); + $this->postJson(route('texture.upload'), [ + 'name' => 'texture', + 'public' => true, + 'type' => 'steve', + 'file' => $upload, + ])->assertJson([ + 'code' => 2, + 'message' => trans('skinlib.upload.repeated'), + 'data' => ['tid' => $texture->tid], + ]); + // rejected $filter->add('can_upload_texture', function ($can, $file, $name) { $this->assertInstanceOf(UploadedFile::class, $file); @@ -589,6 +603,19 @@ class SkinlibControllerTest extends TestCase } ); + // duplicated + $duplicated = $texture->replicate(); + $duplicated->uploader = $other->uid; + $duplicated->public = true; + $duplicated->save(); + $texture->public = false; + $texture->save(); + $uploader->score = (int) option('private_score_per_storage'); + $uploader->save(); + $this->putJson(route('texture.privacy', ['texture' => $texture])) + ->assertJson(['code' => 2, 'message' => trans('skinlib.upload.repeated')]); + $duplicated->delete(); + $this->putJson(route('texture.privacy', ['texture' => $texture])) ->assertJson([ 'code' => 0,