From b9a69cc67b1194d7313448303e897a0a05cdf14f Mon Sep 17 00:00:00 2001 From: ok200lyndon Date: Mon, 19 Aug 2024 15:11:32 +1000 Subject: [PATCH] Feature: Create shops process --- .../Controllers/Api/V1/ApiShopsController.php | 210 ++++++++++++++++++ routes/api.php | 109 ++++++++- .../Feature/API/App/Shops/ShopDeleteTest.php | 64 ++++++ tests/Feature/API/App/Shops/ShopGetTest.php | 79 +++++++ tests/Feature/API/App/Shops/ShopPostTest.php | 88 ++++++++ tests/Feature/API/App/Shops/ShopPutTest.php | 64 ++++++ 6 files changed, 604 insertions(+), 10 deletions(-) create mode 100644 app/Http/Controllers/Api/V1/ApiShopsController.php create mode 100644 tests/Feature/API/App/Shops/ShopDeleteTest.php create mode 100644 tests/Feature/API/App/Shops/ShopGetTest.php create mode 100644 tests/Feature/API/App/Shops/ShopPostTest.php create mode 100644 tests/Feature/API/App/Shops/ShopPutTest.php diff --git a/app/Http/Controllers/Api/V1/ApiShopsController.php b/app/Http/Controllers/Api/V1/ApiShopsController.php new file mode 100644 index 00000000..7562bd21 --- /dev/null +++ b/app/Http/Controllers/Api/V1/ApiShopsController.php @@ -0,0 +1,210 @@ +responseCode = 403; + $this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value; + + return $this->respond(); + } + + #[Endpoint( + title: 'POST /', + description: 'Create a new shop.', + authenticated: true + )] + #[Authenticated] + #[Response( + content: '{"meta":{"responseCode":200,"limit":50,"offset":0,"message":"Saved. Here is the API Token for the user linked to this new team. It will only be displayed ONCE, so please store it in a secure manner.","cached":false,"availableRelations":[]},"data":"{TOKEN}"', + status: 200, + description: '', + )] + public function store(): JsonResponse + { + /** + * The validation array. + */ + $validationArray = [ + 'shop_name' => [ + 'required', + 'string', + ], + 'user_email' => [ + 'required', + 'email', + ], + 'user_name' => [ + 'required', + 'string', + ], + ]; + + $validator = Validator::make($this->request->all(), $validationArray); + + if ($validator->fails()) { + + $this->responseCode = 400; + $this->message = $validator->errors()->first(); + + return $this->respond(); + } + + try { + + /** + * Create a Team for the shop if one does not exist + */ + $shopName = $this->request->get('shop_name'); + + $shopTeam = Team::where('name', $shopName)->first(); + + if (is_null($shopTeam)) { + $shopTeam = new Team(); + $shopTeam->name = $shopName; + $shopTeam->save(); + } + + /** + * Create a User for the shop if one does not exist + */ + $userEmail = $this->request->get('user_email'); + + $shopUser = User::where('email', $userEmail)->first(); + + if (is_null($shopUser)) { + $shopUser = new User(); + $shopUser->email = $userEmail; + $shopUser->password = $userEmail; + $shopUser->name = $this->request->get('user_name'); + $shopUser->current_team_id = $shopTeam->id; + $shopUser->save(); + } + + /** + * Create a TeamUser for the shop if one does not exist + */ + $shopTeamUser = TeamUser::where('team_id', $shopTeam->id)->where('user_id', $shopUser->id)->first(); + + if (is_null($shopTeamUser)) { + $shopTeamUser = new TeamUser(); + $shopTeamUser->user_id = $shopUser->id; + $shopTeamUser->team_id = $shopTeam->id; + $shopTeamUser->save(); + } + + /** + * Create a PAT for the shop that has redemption capabilities + */ + $token = $shopUser->createToken( + name: $shopTeam->name, + abilities: PersonalAccessTokenAbility::redemptionAppTokenAbilities(), + ); + + $this->message = ApiResponse::RESPONSE_SAVED->value . '. Here is the API Token for the user linked to this new team. It will only be displayed ONCE, so please store it in a secure manner.'; + $this->data = $token->plainTextToken; + + } catch (Exception $e) { + + $this->responseCode = 500; + $this->message = ApiResponse::RESPONSE_ERROR->value . ': "' . $e->getMessage() . '".'; + + } + + return $this->respond(); + } + + #[Endpoint( + title: 'GET /{id}', + description: 'Get shop with ID {id}', + authenticated: true + )] + #[Authenticated] + #[Response( + status: 403, + description: 'Method Not Allowed', + )] + public function show(int $id) + { + $this->responseCode = 403; + $this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value; + + return $this->respond(); + } + + #[Endpoint( + title: 'PUT /{id}', + description: 'Update shop with ID {id}.', + authenticated: true + )] + #[Authenticated] + #[Response( + status: 403, + description: 'Method Not Allowed', + )] + public function update(string $id) + { + $this->responseCode = 403; + $this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value; + + return $this->respond(); + } + + #[Endpoint( + title: 'DELETE /', + description: 'DELETE shop with ID {id}.', + authenticated: true + )] + #[Authenticated] + #[Response( + status: 403, + description: 'Method Not Allowed', + )] + public function destroy(string $id) + { + $this->responseCode = 403; + $this->message = ApiResponse::RESPONSE_METHOD_NOT_ALLOWED->value; + + return $this->respond(); + } +} diff --git a/routes/api.php b/routes/api.php index 5820b46d..f8fca1b1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -14,6 +14,7 @@ use App\Http\Controllers\Api\V1\ApiMyTeamController; use App\Http\Controllers\Api\V1\ApiMyTeamsController; use App\Http\Controllers\Api\V1\ApiMyTeamVouchersController; +use App\Http\Controllers\Api\V1\ApiShopsController; use App\Http\Controllers\Api\V1\ApiSystemStatisticsController; use App\Http\Middleware\CheckAdminStatus; use Illuminate\Support\Facades\Route; @@ -186,6 +187,59 @@ ] ); + /** + * Shops + */ + Route::post('/shops', [ApiShopsController::class, 'store']) + ->name('api.v1.shops.post') + ->middleware( + [ + 'abilities:' . + PersonalAccessTokenAbility::SUPER_ADMIN->value . ',' . + PersonalAccessTokenAbility::SHOPS_CREATE->value, + ] + ); + + Route::get('/shops', [ApiShopsController::class, 'index']) + ->name('api.v1.shops.getMany') + ->middleware( + [ + 'abilities:' . + PersonalAccessTokenAbility::SUPER_ADMIN->value . ',' . + PersonalAccessTokenAbility::SHOPS_READ->value, + ] + ); + + Route::get('/shops/{id}', [ApiShopsController::class, 'show']) + ->name('api.v1.shops.get') + ->middleware( + [ + 'abilities:' . + PersonalAccessTokenAbility::SUPER_ADMIN->value . ',' . + PersonalAccessTokenAbility::SHOPS_READ->value, + ] + ); + + Route::put('/shops/{id}', [ApiShopsController::class, 'update']) + ->name('api.v1.shops.put') + ->middleware( + [ + 'abilities:' . + PersonalAccessTokenAbility::SUPER_ADMIN->value . ',' . + PersonalAccessTokenAbility::SHOPS_UPDATE->value, + ] + ); + + Route::delete('/shops/{id}', [ApiShopsController::class, 'destroy']) + ->name('api.v1.shops.delete') + ->middleware( + [ + 'abilities:' . + PersonalAccessTokenAbility::SUPER_ADMIN->value . ',' . + PersonalAccessTokenAbility::SHOPS_DELETE->value, + ] + ); + /** * System Statistics */ @@ -247,16 +301,51 @@ Route::prefix('admin') ->middleware(['auth:sanctum', CheckAdminStatus::class]) ->group(function () { - Route::resource('/audit-items', ApiAdminAuditItemsController::class)->names('api.v1.admin.audit-items'); - Route::resource('/search', ApiAdminSearchController::class)->names('api.v1.admin.search'); - Route::resource('/system-statistics', ApiAdminSystemStatisticsController::class)->names('api.v1.admin.system-statistics'); - Route::resource('/team-merchant-teams', ApiAdminTeamMerchantTeamsController::class)->names('api.v1.admin.team-merchant-teams'); - Route::resource('/team-service-teams', ApiAdminTeamServiceTeamsController::class)->names('api.v1.admin.team-service-teams'); - Route::resource('/team-users', ApiAdminTeamUsersController::class)->names('api.v1.admin.team-users'); - Route::resource('/teams', ApiAdminTeamsController::class)->names('api.v1.admin.teams'); - Route::resource('/user-personal-access-tokens', ApiAdminUserPersonalAccessTokensController::class)->names('api.v1.admin.tokens'); - Route::resource('/users', ApiAdminUsersController::class)->names('api.v1.admin.users'); - }); + Route::resource( + '/audit-items', + ApiAdminAuditItemsController::class + )->names('api.v1.admin.audit-items'); + + Route::resource( + '/search', + ApiAdminSearchController::class + )->names('api.v1.admin.search'); + + Route::resource( + '/system-statistics', + ApiAdminSystemStatisticsController::class + )->names('api.v1.admin.system-statistics'); + + Route::resource( + '/team-merchant-teams', + ApiAdminTeamMerchantTeamsController::class + )->names('api.v1.admin.team-merchant-teams'); + + Route::resource( + '/team-service-teams', + ApiAdminTeamServiceTeamsController::class + )->names('api.v1.admin.team-service-teams'); + Route::resource( + '/team-users', + ApiAdminTeamUsersController::class + )->names('api.v1.admin.team-users'); + + Route::resource( + '/teams', + ApiAdminTeamsController::class + )->names('api.v1.admin.teams'); + + Route::resource( + '/user-personal-access-tokens', + ApiAdminUserPersonalAccessTokensController::class + )->names('api.v1.admin.tokens'); + + Route::resource( + '/users', + ApiAdminUsersController::class + )->names('api.v1.admin.users'); + + }); }); diff --git a/tests/Feature/API/App/Shops/ShopDeleteTest.php b/tests/Feature/API/App/Shops/ShopDeleteTest.php new file mode 100644 index 00000000..17165924 --- /dev/null +++ b/tests/Feature/API/App/Shops/ShopDeleteTest.php @@ -0,0 +1,64 @@ +user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->deleteJson($this->apiRoot . $this->endpoint . '/1'); + + $response->assertStatus(401); + } + + #[Test] + public function standardUserWithoutPermissionCannotDelete() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->deleteJson($this->apiRoot . $this->endpoint . '/1'); + + $response->assertStatus(401)->assertJson( + [ + 'meta' => [ + 'message' => ApiResponse::RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS->value, + ], + ] + ); + + } + + #[Test] + public function itCanNotDeleteASingleResource() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: [ + PersonalAccessTokenAbility::SHOPS_DELETE->value, + ]); + + $response = $this->deleteJson($this->apiRoot . $this->endpoint . '/1'); + + $response->assertStatus(403); + } +} diff --git a/tests/Feature/API/App/Shops/ShopGetTest.php b/tests/Feature/API/App/Shops/ShopGetTest.php new file mode 100644 index 00000000..b25f545a --- /dev/null +++ b/tests/Feature/API/App/Shops/ShopGetTest.php @@ -0,0 +1,79 @@ +user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->getJson($this->apiRoot . $this->endpoint); + + $response->assertStatus(401); + } + + #[Test] + public function standardUserWithoutPermissionCannotAccess() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->getJson($this->apiRoot . $this->endpoint); + + $response->assertStatus(401)->assertJson( + [ + 'meta' => [ + 'message' => ApiResponse::RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS->value, + ], + ] + ); + + } + + #[Test] + public function itCanNotGetAllResources() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: [ + PersonalAccessTokenAbility::SHOPS_READ->value, + ]); + + $response = $this->getJson($this->apiRoot . $this->endpoint); + + $response->assertStatus(403); + + } + + #[Test] + public function itCanNotGetASingleResource() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: [ + PersonalAccessTokenAbility::SHOPS_READ->value, + ]); + + $response = $this->getJson($this->apiRoot . $this->endpoint . '/1'); + + $response->assertStatus(403); + } +} diff --git a/tests/Feature/API/App/Shops/ShopPostTest.php b/tests/Feature/API/App/Shops/ShopPostTest.php new file mode 100644 index 00000000..8b2e2fc7 --- /dev/null +++ b/tests/Feature/API/App/Shops/ShopPostTest.php @@ -0,0 +1,88 @@ +user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->postJson($this->apiRoot . $this->endpoint, []); + + $response->assertStatus(401); + } + + #[Test] + public function standardUserWithoutPermissionCannotCreate() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->postJson($this->apiRoot . $this->endpoint, []); + + $response->assertStatus(401)->assertJson( + [ + 'meta' => [ + 'message' => ApiResponse::RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS->value, + ], + ] + ); + + } + + #[Test] + public function userWithPermissionCanCreate() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: [ + PersonalAccessTokenAbility::SHOPS_CREATE->value, + ]); + + $shopName = fake()->name(); + $userName = fake()->name(); + $userEmail = fake()->email(); + + $user = User::whereEmail($userEmail)->first(); + self::assertNull($user); + + $payload = [ + 'shop_name' => $shopName, + 'user_name' => $userName, + 'user_email' => $userEmail, + ]; + + $response = $this->postJson($this->apiRoot . $this->endpoint, $payload); + + $response->assertStatus(200); + + $responseObj = json_decode($response->getContent(), true); + + self::assertSame( + 'Saved. Here is the API Token for the user linked to this new team. It will only be displayed ONCE, so please store it in a secure manner.', + $responseObj['meta']['message'] + ); + + $user = User::whereEmail($userEmail)->first(); + self::assertNotNull($user); + } +} diff --git a/tests/Feature/API/App/Shops/ShopPutTest.php b/tests/Feature/API/App/Shops/ShopPutTest.php new file mode 100644 index 00000000..1fe32d2e --- /dev/null +++ b/tests/Feature/API/App/Shops/ShopPutTest.php @@ -0,0 +1,64 @@ +user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->putJson($this->apiRoot . $this->endpoint . '/1', []); + + $response->assertStatus(401); + } + + #[Test] + public function standardUserWithoutPermissionCannotUpdate() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: []); + + $response = $this->putJson($this->apiRoot . $this->endpoint . '/1', []); + + $response->assertStatus(401)->assertJson( + [ + 'meta' => [ + 'message' => ApiResponse::RESPONSE_TOKEN_NOT_ALLOWED_TO_DO_THIS->value, + ], + ] + ); + + } + + #[Test] + public function itCanNotUpdateASingleResource() + { + $this->user = $this->createUser(); + + Sanctum::actingAs($this->user, abilities: [ + PersonalAccessTokenAbility::SHOPS_UPDATE->value, + ]); + + $response = $this->putJson($this->apiRoot . $this->endpoint . '/1', []); + + $response->assertStatus(403); + } +}