diff --git a/app/Http/Controllers/ClosetController.php b/app/Http/Controllers/ClosetController.php index 21de3c84..f775c1d0 100644 --- a/app/Http/Controllers/ClosetController.php +++ b/app/Http/Controllers/ClosetController.php @@ -84,6 +84,10 @@ class ClosetController extends Controller return json(trans('user.closet.add.not-found'), 1); } + if (! $texture->public && $texture->uploader != $user->uid) { + return json(trans('skinlib.show.private'), 1); + } + if ($user->closet()->where('tid', $request->tid)->count() > 0) { return json(trans('user.closet.add.repeated'), 1); } @@ -100,31 +104,22 @@ class ClosetController extends Controller return json(trans('user.closet.add.success', ['name' => $request->input('name')]), 0); } - public function rename(Request $request) + public function rename(Request $request, $tid) { - $this->validate($request, [ - 'tid' => 'required|integer', - 'new_name' => 'required|no_special_chars', - ]); - + $this->validate($request, ['name' => 'required|no_special_chars']); $user = auth()->user(); 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]); + $user->closet()->updateExistingPivot($request->tid, ['item_name' => $request->name]); - return json(trans('user.closet.rename.success', ['name' => $request->new_name]), 0); + return json(trans('user.closet.rename.success', ['name' => $request->name]), 0); } - public function remove(Request $request) + public function remove($tid) { - $this->validate($request, [ - 'tid' => 'required|integer', - ]); - $tid = $request->tid; - $user = auth()->user(); if ($user->closet()->where('tid', $tid)->count() == 0) { diff --git a/resources/assets/src/components/ClosetItem.vue b/resources/assets/src/components/ClosetItem.vue index ab229d4c..0d9bca32 100644 --- a/resources/assets/src/components/ClosetItem.vue +++ b/resources/assets/src/components/ClosetItem.vue @@ -87,8 +87,8 @@ export default { } const { code, message } = await this.$http.post( - '/user/closet/rename', - { tid: this.tid, new_name: newTextureName } + `/user/closet/rename/${this.tid}`, + { new_name: newTextureName } ) if (code === 0) { this.textureName = newTextureName diff --git a/resources/assets/src/components/mixins/removeClosetItem.ts b/resources/assets/src/components/mixins/removeClosetItem.ts index f82bc53e..83096cbf 100644 --- a/resources/assets/src/components/mixins/removeClosetItem.ts +++ b/resources/assets/src/components/mixins/removeClosetItem.ts @@ -15,10 +15,7 @@ export default Vue.extend<{ return } - const { code, message } = await this.$http.post( - '/user/closet/remove', - { tid: this.tid } - ) + const { code, message } = await this.$http.post(`/user/closet/remove/${this.tid}`) if (code === 0) { this.$emit('item-removed') this.$message.success(message!) diff --git a/resources/assets/tests/components/ClosetItem.test.ts b/resources/assets/tests/components/ClosetItem.test.ts index 48c3c1f8..a93ae179 100644 --- a/resources/assets/tests/components/ClosetItem.test.ts +++ b/resources/assets/tests/components/ClosetItem.test.ts @@ -61,8 +61,8 @@ test('rename texture', async () => { await flushPromises() expect(wrapper.find('.texture-name > span').text()).toBe('new-name (steve)') expect(Vue.prototype.$http.post).toBeCalledWith( - '/user/closet/rename', - { tid: 1, new_name: 'new-name' } + '/user/closet/rename/1', + { new_name: 'new-name' } ) }) @@ -88,7 +88,7 @@ test('remove texture', async () => { button.trigger('click') await flushPromises() expect(wrapper.emitted()['item-removed']).toBeTruthy() - expect(Vue.prototype.$http.post).toBeCalledWith('/user/closet/remove', { tid: 1 }) + expect(Vue.prototype.$http.post).toBeCalledWith('/user/closet/remove/1') }) test('set as avatar', async () => { diff --git a/routes/api.php b/routes/api.php index 215bbf50..ad5315a3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -19,3 +19,10 @@ Route::prefix('players')->middleware('auth:jwt,oauth')->group(function () { Route::put('{pid}/textures', 'PlayerController@setTexture'); Route::delete('{pid}/textures', 'PlayerController@clearTexture'); }); + +Route::prefix('closet')->middleware('auth:jwt,oauth')->group(function () { + Route::get('', 'ClosetController@getClosetData'); + Route::post('', 'ClosetController@add'); + Route::put('{tid}', 'ClosetController@rename'); + Route::delete('{tid}', 'ClosetController@remove'); +}); diff --git a/routes/web.php b/routes/web.php index 97213d63..90920b5d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -80,8 +80,8 @@ Route::group([ Route::get('/closet', 'ClosetController@index'); Route::get('/closet-data', 'ClosetController@getClosetData'); Route::post('/closet/add', 'ClosetController@add'); - Route::post('/closet/remove', 'ClosetController@remove'); - Route::post('/closet/rename', 'ClosetController@rename'); + Route::post('/closet/remove/{tid}', 'ClosetController@remove'); + Route::post('/closet/rename/{tid}', 'ClosetController@rename'); // OAuth2 Management Route::view('/oauth/manage', 'user.oauth'); diff --git a/tests/Api/tests/closet.rs b/tests/Api/tests/closet.rs new file mode 100644 index 00000000..a63c77b8 --- /dev/null +++ b/tests/Api/tests/closet.rs @@ -0,0 +1,162 @@ +use crate::auth::login; +use crate::types::JsonBody; +use rusqlite::{params, Connection}; +use serde::Deserialize; +use serde_json::json; +use std::env; + +#[derive(Deserialize)] +struct Closet { + pub category: String, + pub total_pages: usize, + pub items: Vec, +} + +#[derive(Deserialize)] +struct ClosetItem { + pub tid: u32, + pub name: String, + pub r#type: String, + pub size: u32, + pub hash: String, + pub uploader: u32, + pub public: bool, +} + +#[test] +fn fetch_closet_info() { + let token = login(); + let client = reqwest::Client::new(); + + let body = client + .get("http://127.0.0.1:32123/api/closet") + .header("Authorization", token.clone()) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + let closet = body.data().unwrap(); + assert_eq!(closet.category, "skin"); + assert_eq!(closet.total_pages, 0); + assert_eq!(closet.items.len(), 0); + + let body = client + .get("http://127.0.0.1:32123/api/closet") + .header("Authorization", token) + .json(&json!({"category": "cape"})) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + let closet = body.data().unwrap(); + assert_eq!(closet.category, "cape"); + assert_eq!(closet.total_pages, 0); + assert_eq!(closet.items.len(), 0); +} + +#[test] +fn insert_to_closet() { + let conn = Connection::open(env::var("DB_DATABASE").unwrap()).unwrap(); + conn.execute( + "INSERT INTO textures (name, type, hash, size, uploader, public, upload_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params!["steve", "steve", "abc", 1, 1, 1, "2019-01-01 00:00:00"], + ) + .unwrap(); + conn.execute( + "INSERT INTO textures (name, type, hash, size, uploader, public, upload_at) + VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", + params!["cape", "cape", "def", 1, 1, 1, "2019-01-01 00:00:00"], + ) + .unwrap(); + + let token = login(); + let client = reqwest::Client::new(); + + let body = client + .post("http://127.0.0.1:32123/api/closet") + .header("Authorization", token.clone()) + .json(&json!({"tid": 1, "name": "my-first-texture"})) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + + let body = client + .get("http://127.0.0.1:32123/api/closet") + .header("Authorization", token) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + let closet = body.data().unwrap(); + assert_eq!(closet.total_pages, 1); + assert_eq!(closet.items.len(), 1); + let item = closet.items.get(0).unwrap(); + assert_eq!(item.tid, 1); + assert_eq!(item.name, "my-first-texture"); + assert_eq!(item.r#type, "steve"); + assert_eq!(item.size, 1); + assert_eq!(item.hash, "abc"); + assert_eq!(item.uploader, 1); + assert!(item.public); +} + +#[test] +fn modify_name() { + let token = login(); + let client = reqwest::Client::new(); + + let body = client + .put("http://127.0.0.1:32123/api/closet/1") + .header("Authorization", token.clone()) + .json(&json!({"name": "renamed"})) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + + let body = client + .get("http://127.0.0.1:32123/api/closet") + .header("Authorization", token) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + let closet = body.data().unwrap(); + let item = closet.items.get(0).unwrap(); + assert_eq!(item.tid, 1); + assert_eq!(item.name, "renamed"); +} + +#[test] +fn remove_texture() { + let token = login(); + let client = reqwest::Client::new(); + + let body = client + .delete("http://127.0.0.1:32123/api/closet/1") + .header("Authorization", token.clone()) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + + let body = client + .get("http://127.0.0.1:32123/api/closet") + .header("Authorization", token) + .send() + .unwrap() + .json::>() + .unwrap(); + assert!(body.is_success()); + let closet = body.data().unwrap(); + assert_eq!(closet.items.len(), 0); +} diff --git a/tests/Api/tests/main.rs b/tests/Api/tests/main.rs index 5202bcde..9e9ca9f0 100644 --- a/tests/Api/tests/main.rs +++ b/tests/Api/tests/main.rs @@ -1,11 +1,14 @@ -#[cfg(test)] -mod types; - #[cfg(test)] mod auth; #[cfg(test)] -mod user; +mod closet; #[cfg(test)] mod players; + +#[cfg(test)] +mod types; + +#[cfg(test)] +mod user; diff --git a/tests/Api/tests/players.rs b/tests/Api/tests/players.rs index 56481793..f10e32ef 100644 --- a/tests/Api/tests/players.rs +++ b/tests/Api/tests/players.rs @@ -1,9 +1,7 @@ use crate::auth::login; use crate::types::JsonBody; -use rusqlite::{params, Connection}; use serde::Deserialize; use serde_json::json; -use std::env; #[derive(Deserialize)] struct Player { @@ -67,20 +65,6 @@ fn modify_player_name() { #[test] fn modify_textures() { - let conn = Connection::open(env::var("DB_DATABASE").unwrap()).unwrap(); - conn.execute( - "INSERT INTO textures (name, type, hash, size, uploader, public, upload_at) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", - params!["steve", "steve", "abc", 1, 1, 1, "2019-01-01 00:00:00"], - ) - .unwrap(); - conn.execute( - "INSERT INTO textures (name, type, hash, size, uploader, public, upload_at) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)", - params!["cape", "cape", "def", 1, 1, 1, "2019-01-01 00:00:00"], - ) - .unwrap(); - let client = reqwest::Client::new(); let body = client .put("http://127.0.0.1:32123/api/players/1/textures") diff --git a/tests/ClosetControllerTest.php b/tests/ClosetControllerTest.php index 1f5dceb6..9446606f 100644 --- a/tests/ClosetControllerTest.php +++ b/tests/ClosetControllerTest.php @@ -129,8 +129,21 @@ class ClosetControllerTest extends TestCase 'message' => trans('user.closet.add.not-found'), ]); - // Add a texture successfully + // Texture is private option(['score_award_per_like' => 5]); + $privateTexture = factory(Texture::class)->create([ + 'public' => false, + 'uploader' => $uploader->uid + 1, + ]); + $this->postJson( + '/user/closet/add', + ['tid' => $privateTexture->tid, 'name' => $name] + )->assertJson([ + 'code' => 1, + 'message' => trans('skinlib.show.private'), + ]); + + // Add a texture successfully $this->postJson( '/user/closet/add', ['tid' => $texture->tid, 'name' => $name] @@ -160,46 +173,28 @@ class ClosetControllerTest extends TestCase $texture = factory(Texture::class)->create(); $name = 'new'; - // Missing `tid` field - $this->postJson('/user/closet/rename')->assertJsonValidationErrors('tid'); - - // `tid` is not a integer - $this->postJson( - '/user/closet/rename', - ['tid' => 'string'] - )->assertJsonValidationErrors('tid'); - - // Missing `new_name` field - $this->postJson( - '/user/closet/rename', - ['tid' => 0] - )->assertJsonValidationErrors('new_name'); + // Missing `name` field + $this->postJson('/user/closet/rename/0')->assertJsonValidationErrors('name'); // `new_name` field has special characters - $this->postJson( - '/user/closet/rename', - ['tid' => 0, 'new_name' => '\\'] - )->assertJsonValidationErrors('new_name'); + $this->postJson('/user/closet/rename/0', ['name' => '\\']) + ->assertJsonValidationErrors('name'); // Rename a not-existed texture - $this->postJson( - '/user/closet/rename', - ['tid' => -1, 'new_name' => $name] - )->assertJson([ - 'code' => 1, - 'message' => trans('user.closet.remove.non-existent'), - ]); + $this->postJson('/user/closet/rename/-1', ['name' => $name]) + ->assertJson([ + 'code' => 1, + 'message' => trans('user.closet.remove.non-existent'), + ]); // Rename a closet item successfully $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); - $this->postJson( - '/user/closet/rename', - ['tid' => $texture->tid, 'new_name' => $name] - )->assertJson([ - 'code' => 0, - 'message' => trans('user.closet.rename.success', ['name' => 'new']), - ]); - $this->assertEquals(1, $this->user->closet()->where('item_name', 'new')->count()); + $this->postJson('/user/closet/rename/'.$texture->tid, ['name' => $name]) + ->assertJson([ + 'code' => 0, + 'message' => trans('user.closet.rename.success', ['name' => $name]), + ]); + $this->assertEquals(1, $this->user->closet()->where('item_name', $name)->count()); } public function testRemove() @@ -208,35 +203,22 @@ class ClosetControllerTest extends TestCase $texture = factory(Texture::class)->create(['uploader' => $uploader->uid]); $likes = $texture->likes; - // Missing `tid` field - $this->postJson('/user/closet/remove')->assertJsonValidationErrors('tid'); - - // `tid` is not a integer - $this->postJson( - '/user/closet/remove', - ['tid' => 'string'] - )->assertJsonValidationErrors('tid'); - // Rename a not-existed texture - $this->postJson( - '/user/closet/remove', - ['tid' => -1] - )->assertJson([ - 'code' => 1, - 'message' => trans('user.closet.remove.non-existent'), - ]); + $this->postJson('/user/closet/remove/-1') + ->assertJson([ + 'code' => 1, + 'message' => trans('user.closet.remove.non-existent'), + ]); // Should return score if `return_score` is true option(['score_award_per_like' => 5]); $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score; - $this->postJson( - '/user/closet/remove', - ['tid' => $texture->tid] - )->assertJson([ - 'code' => 0, - 'message' => trans('user.closet.remove.success'), - ]); + $this->postJson('/user/closet/remove/'.$texture->tid) + ->assertJson([ + 'code' => 0, + 'message' => trans('user.closet.remove.success'), + ]); $this->assertEquals($likes, Texture::find($texture->tid)->likes); $this->assertEquals($score + option('score_per_closet_item'), $this->user->score); $this->assertEquals(0, $this->user->closet()->count()); @@ -249,13 +231,7 @@ class ClosetControllerTest extends TestCase option(['return_score' => false]); $this->user->closet()->attach($texture->tid, ['item_name' => 'name']); $score = $this->user->score; - $this->postJson( - '/user/closet/remove', - ['tid' => $texture->tid] - )->assertJson([ - 'code' => 0, - 'message' => trans('user.closet.remove.success'), - ]); + $this->postJson('/user/closet/remove/'.$texture->tid)->assertJson(['code' => 0]); $this->assertEquals($likes, Texture::find($texture->tid)->likes); $this->assertEquals($score, $this->user->score); $this->assertEquals(0, $this->user->closet()->count());